001 /* 002 * $Id: SearchFactory.java,v 1.15 2006/05/14 15:55:55 dmouse Exp $ 003 * 004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, 005 * Santa Clara, California 95054, U.S.A. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 020 */ 021 package org.jdesktop.swingx; 022 023 import java.awt.Component; 024 import java.awt.Container; 025 import java.awt.Frame; 026 import java.awt.KeyboardFocusManager; 027 import java.awt.Point; 028 import java.awt.Window; 029 import java.awt.event.ActionEvent; 030 import java.beans.PropertyChangeEvent; 031 import java.beans.PropertyChangeListener; 032 import java.util.HashSet; 033 import java.util.Iterator; 034 import java.util.Set; 035 036 import javax.swing.AbstractAction; 037 import javax.swing.Action; 038 import javax.swing.JComponent; 039 import javax.swing.JOptionPane; 040 import javax.swing.JToolBar; 041 import javax.swing.SwingUtilities; 042 043 import org.jdesktop.swingx.plaf.LookAndFeelAddons; 044 045 /** 046 * Factory to create, configure and show application consistent 047 * search and find widgets. 048 * 049 * Typically a shared JXFindBar is used for incremental search, while 050 * a shared JXFindPanel is used for batch search. This implementation 051 * 052 * <ul> 053 * <li> JXFindBar - adds and shows it in the target's toplevel container's 054 * toolbar (assuming a JXRootPane) 055 * <li> JXFindPanel - creates a JXDialog, adds and shows the findPanel in the 056 * Dialog 057 * </ul> 058 * 059 * 060 * PENDING: JW - update (?) views/wiring on focus change. Started brute force - 061 * stop searching. This looks extreme confusing for findBars added to ToolBars 062 * which are empty except for the findbar. Weird problem if triggered from 063 * menu - find widget disappears after having been shown for an instance. 064 * Where's the focus? 065 * 066 * 067 * PENDING: add methods to return JXSearchPanels (for use by PatternMatchers). 068 * 069 * @author Jeanette Winzenburg 070 */ 071 public class SearchFactory { 072 // PENDING: rename methods to batch/incremental instead of dialog/toolbar 073 074 static { 075 // Hack to enforce loading of SwingX framework ResourceBundle 076 LookAndFeelAddons.getAddon(); 077 } 078 079 private static SearchFactory searchFactory; 080 081 082 /** the shared find widget for batch-find. */ 083 protected JXFindPanel findPanel; 084 085 /** the shared find widget for incremental-find. */ 086 protected JXFindBar findBar; 087 /** this is a temporary hack: need to remove the useSearchHighlighter property. */ 088 protected JComponent lastFindBarTarget; 089 090 private boolean useFindBar; 091 092 private Point lastFindDialogLocation; 093 094 private FindRemover findRemover; 095 096 /** 097 * returns the shared SearchFactory. 098 * 099 * @return the shared <code>SearchFactory</code> 100 */ 101 public static SearchFactory getInstance() { 102 if (searchFactory == null) { 103 searchFactory = new SearchFactory(); 104 } 105 return searchFactory; 106 } 107 108 /** 109 * sets the shared SearchFactory. 110 * 111 * @param factory 112 */ 113 public static void setInstance(SearchFactory factory) { 114 searchFactory = factory; 115 } 116 117 /** 118 * Shows an appropriate find widget targeted at the searchable. 119 * This implementation opens a batch-find or incremental-find 120 * widget based on the showFindInToolBar property (which defaults 121 * to false). 122 * 123 * 124 * @param target - the component associated with the searchable 125 * @param searchable - the object to search. 126 */ 127 public void showFindInput(JComponent target, Searchable searchable) { 128 if (isUseFindBar(target, searchable)) { 129 showFindBar(target, searchable); 130 } else { 131 showFindDialog(target, searchable); 132 } 133 } 134 135 /** 136 * Show a incremental-find widget targeted at the searchable. 137 * 138 * This implementation uses a JXFindBar and inserts it into the 139 * target's toplevel container toolbar. 140 * 141 * PENDING: Nothing shown if there is no toolbar found. 142 * 143 * @param target - the component associated with the searchable 144 * @param searchable - the object to search. 145 */ 146 public void showFindBar(JComponent target, Searchable searchable) { 147 if (target == null) return; 148 if (findBar == null) { 149 findBar = getSharedFindBar(); 150 } else { 151 releaseFindBar(); 152 } 153 Window topLevel = SwingUtilities.getWindowAncestor(target); 154 if (topLevel instanceof JXFrame) { 155 JXRootPane rootPane = ((JXFrame) topLevel).getRootPaneExt(); 156 JToolBar toolBar = rootPane.getToolBar(); 157 if (toolBar == null) { 158 toolBar = new JToolBar(); 159 rootPane.setToolBar(toolBar); 160 } 161 toolBar.add(findBar, 0); 162 rootPane.revalidate(); 163 KeyboardFocusManager.getCurrentKeyboardFocusManager().focusNextComponent(findBar); 164 165 } 166 lastFindBarTarget = target; 167 target.putClientProperty(AbstractSearchable.MATCH_HIGHLIGHTER, Boolean.TRUE); 168 installFindRemover(target, findBar); 169 getSharedFindBar().setSearchable(searchable); 170 } 171 172 protected void installFindRemover(Container target, Container findWidget) { 173 if (target != null) { 174 getFindRemover().addTarget(target); 175 } 176 getFindRemover().addTarget(findWidget); 177 } 178 private FindRemover getFindRemover() { 179 if (findRemover == null) { 180 findRemover = new FindRemover(); 181 } 182 return findRemover; 183 } 184 185 /** 186 * convenience method to remove a component from its parent 187 * and revalidate the parent 188 */ 189 protected void removeFromParent(JComponent component) { 190 Container oldParent = component.getParent(); 191 if (oldParent != null) { 192 oldParent.remove(component); 193 if (oldParent instanceof JComponent) { 194 ((JComponent) oldParent).revalidate(); 195 } else { 196 // not sure... never have non-j comps 197 oldParent.invalidate(); 198 oldParent.validate(); 199 } 200 } 201 } 202 203 /** 204 * returns the shared JXFindBar. Creates and configures on 205 * first call. 206 * @return the shared <code>JXFindBar</code> 207 */ 208 public JXFindBar getSharedFindBar() { 209 if (findBar == null) { 210 findBar = createFindBar(); 211 configureSharedFindBar(); 212 } 213 return findBar; 214 } 215 216 /** 217 * called after creation of shared FindBar. 218 * Subclasses can add configuration code. 219 * Here: registers a custom action to remove the 220 * findbar from its ancestor container. 221 * 222 * PRE: findBar != null. 223 * 224 */ 225 protected void configureSharedFindBar() { 226 Action removeAction = new AbstractAction() { 227 228 public void actionPerformed(ActionEvent e) { 229 removeFromParent(findBar); 230 // stopSearching(); 231 // releaseFindBar(); 232 233 } 234 235 }; 236 findBar.getActionMap().put(JXDialog.CLOSE_ACTION_COMMAND, removeAction); 237 } 238 239 /** 240 * Factory method to create a JXFindBar. 241 * 242 * @return the <code>JXFindBar</code> 243 */ 244 public JXFindBar createFindBar() { 245 return new JXFindBar(); 246 } 247 248 249 /** 250 * returns the shared JXFindPanel. Creates and configures on 251 * first call. 252 * @return the shared <code>JXFindPanel</code> 253 */ 254 public JXFindPanel getSharedFindPanel() { 255 if (findPanel == null) { 256 findPanel = createFindPanel(); 257 configureSharedFindPanel(); 258 } 259 return findPanel; 260 } 261 262 /** 263 * called after creation of shared FindPanel. 264 * Subclasses can add configuration code. 265 * Here: no-op 266 * PRE: findPanel != null. 267 * 268 */ 269 protected void configureSharedFindPanel() { 270 } 271 272 /** 273 * Factory method to create a JXFindPanel. 274 * 275 * @return <code>JXFindPanel</code> 276 */ 277 public JXFindPanel createFindPanel() { 278 return new JXFindPanel(); 279 } 280 281 282 /** 283 * Show a batch-find widget targeted at the given Searchable. 284 * 285 * This implementation uses a shared JXFindPanel contained 286 * JXDialog. 287 * 288 * @param target - 289 * the component associated with the searchable 290 * @param searchable - 291 * the object to search. 292 */ 293 public void showFindDialog(JComponent target, Searchable searchable) { 294 Frame frame = JOptionPane.getRootFrame(); 295 if (target != null) { 296 target.putClientProperty(AbstractSearchable.MATCH_HIGHLIGHTER, Boolean.FALSE); 297 Window window = SwingUtilities.getWindowAncestor(target); 298 if (window instanceof Frame) { 299 frame = (Frame) window; 300 } 301 } 302 JXDialog topLevel = getDialogForSharedFilePanel(); 303 JXDialog findDialog; 304 if ((topLevel != null) && (topLevel.getOwner().equals(frame))) { 305 findDialog = topLevel; 306 KeyboardFocusManager.getCurrentKeyboardFocusManager().focusNextComponent(findDialog); 307 } else { 308 Point location = hideSharedFilePanel(); 309 findDialog = new JXDialog(frame, getSharedFindPanel()); 310 // JW: don't - this will stay on top of all applications! 311 // findDialog.setAlwaysOnTop(true); 312 findDialog.pack(); 313 if (location == null) { 314 findDialog.setLocationRelativeTo(frame); 315 } else { 316 findDialog.setLocation(location); 317 } 318 } 319 320 findDialog.setVisible(true); 321 installFindRemover(target, findDialog); 322 getSharedFindPanel().setSearchable(searchable); 323 } 324 325 326 private JXDialog getDialogForSharedFilePanel() { 327 if (findPanel == null) return null; 328 Window window = SwingUtilities.getWindowAncestor(findPanel); 329 return (window instanceof JXDialog) ? (JXDialog) window : null; 330 } 331 332 protected Point hideSharedFilePanel() { 333 if (findPanel == null) return null; 334 Window window = SwingUtilities.getWindowAncestor(findPanel); 335 Point location = lastFindDialogLocation; 336 if (window != null) { 337 findPanel.getParent().remove(findPanel); 338 if (window.isVisible()) { 339 location = window.getLocationOnScreen(); 340 } 341 window.dispose(); 342 } 343 return location; 344 } 345 346 protected void stopSearching() { 347 if (findPanel != null) { 348 lastFindDialogLocation = hideSharedFilePanel(); 349 findPanel.setSearchable(null); 350 } 351 if (findBar != null) { 352 releaseFindBar(); 353 } 354 355 } 356 357 /** 358 * Pre: findbar != null. 359 */ 360 protected void releaseFindBar() { 361 findBar.setSearchable(null); 362 if (lastFindBarTarget != null) { 363 lastFindBarTarget.putClientProperty(AbstractSearchable.MATCH_HIGHLIGHTER, Boolean.FALSE); 364 lastFindBarTarget = null; 365 } 366 removeFromParent(findBar); 367 } 368 369 /** 370 * Returns decision about using a batch- vs. incremental-find for the 371 * searchable. This implementation returns the useFindBar property 372 * directly. 373 * 374 * @param target - 375 * the component associated with the searchable 376 * @param searchable - 377 * the object to search. 378 * @return true if a incremental-find should be used, false otherwise. 379 */ 380 public boolean isUseFindBar(JComponent target, Searchable searchable) { 381 return useFindBar; 382 } 383 384 /** 385 * 386 * @param inToolBar 387 */ 388 public void setUseFindBar(boolean inToolBar) { 389 if (inToolBar == useFindBar) return; 390 this.useFindBar = inToolBar; 391 getFindRemover().endSearching(); 392 } 393 394 public class FindRemover implements PropertyChangeListener { 395 KeyboardFocusManager focusManager; 396 Set<Container> targets; 397 398 public FindRemover() { 399 updateManager(); 400 } 401 402 public void addTarget(Container target) { 403 getTargets().add(target); 404 } 405 406 public void removeTarget(Container target) { 407 getTargets().remove(target); 408 } 409 410 private Set<Container> getTargets() { 411 if (targets == null) { 412 targets = new HashSet<Container>(); 413 } 414 return targets; 415 } 416 417 /** 418 * 419 */ 420 private void updateManager() { 421 if (focusManager != null) { 422 focusManager.removePropertyChangeListener("permanentFocusOwner", this); 423 } 424 this.focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); 425 focusManager.addPropertyChangeListener("permanentFocusOwner", this); 426 } 427 428 public void propertyChange(PropertyChangeEvent ev) { 429 430 Component c = focusManager.getPermanentFocusOwner(); 431 if (c == null) return; 432 for (Iterator<Container> iter = getTargets().iterator(); iter.hasNext();) { 433 Container element = iter.next(); 434 if ((element == c) || (SwingUtilities.isDescendingFrom(c, element))) { 435 return; 436 } 437 } 438 endSearching(); 439 } 440 441 /** 442 * 443 */ 444 public void endSearching() { 445 getTargets().clear(); 446 stopSearching(); 447 } 448 } 449 450 }