2
* $Id: SearchFactory.java 3727 2010-07-14 16:56:18Z kschaefe $
4
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
5
* Santa Clara, California 95054, U.S.A. All rights reserved.
7
* This library is free software; you can redistribute it and/or
8
* modify it under the terms of the GNU Lesser General Public
9
* License as published by the Free Software Foundation; either
10
* version 2.1 of the License, or (at your option) any later version.
12
* This library is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
* Lesser General Public License for more details.
17
* You should have received a copy of the GNU Lesser General Public
18
* License along with this library; if not, write to the Free Software
19
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21
package org.jdesktop.swingx.search;
23
import java.awt.Component;
24
import java.awt.Container;
25
import java.awt.Dialog;
26
import java.awt.Frame;
27
import java.awt.KeyboardFocusManager;
28
import java.awt.Point;
29
import java.awt.Window;
30
import java.awt.event.ActionEvent;
31
import java.beans.PropertyChangeEvent;
32
import java.beans.PropertyChangeListener;
33
import java.lang.ref.WeakReference;
34
import java.util.HashSet;
35
import java.util.Iterator;
38
import javax.swing.AbstractAction;
39
import javax.swing.Action;
40
import javax.swing.JComponent;
41
import javax.swing.JOptionPane;
42
import javax.swing.JToolBar;
43
import javax.swing.KeyStroke;
44
import javax.swing.SwingUtilities;
45
import javax.swing.UIManager;
47
import org.jdesktop.swingx.JXDialog;
48
import org.jdesktop.swingx.JXFindBar;
49
import org.jdesktop.swingx.JXFindPanel;
50
import org.jdesktop.swingx.JXFrame;
51
import org.jdesktop.swingx.JXRootPane;
52
import org.jdesktop.swingx.plaf.LookAndFeelAddons;
53
import org.jdesktop.swingx.plaf.UIDependent;
54
import org.jdesktop.swingx.util.Utilities;
57
* Factory to create, configure and show application consistent
58
* search and find widgets.
60
* Typically a shared JXFindBar is used for incremental search, while
61
* a shared JXFindPanel is used for batch search. This implementation
64
* <li> JXFindBar - adds and shows it in the target's toplevel container's
65
* toolbar (assuming a JXRootPane)
66
* <li> JXFindPanel - creates a JXDialog, adds and shows the findPanel in the
71
* PENDING: JW - update (?) views/wiring on focus change. Started brute force -
72
* stop searching. This looks extreme confusing for findBars added to ToolBars
73
* which are empty except for the findbar. Weird problem if triggered from
74
* menu - find widget disappears after having been shown for an instance.
78
* PENDING: add methods to return JXSearchPanels (for use by PatternMatchers).
80
* @author Jeanette Winzenburg
82
public class SearchFactory implements UIDependent {
83
private static class LaFListener implements PropertyChangeListener {
84
private final WeakReference<SearchFactory> ref;
86
public LaFListener(SearchFactory sf) {
87
this.ref = new WeakReference<SearchFactory>(sf);
94
public void propertyChange(PropertyChangeEvent evt) {
95
SearchFactory sf = ref.get();
98
UIManager.removePropertyChangeListener(this);
99
} else if ("lookAndFeel".equals(evt.getPropertyName())) {
105
// PENDING: rename methods to batch/incremental instead of dialog/toolbar
108
// Hack to enforce loading of SwingX framework ResourceBundle
109
LookAndFeelAddons.getAddon();
112
private static SearchFactory searchFactory;
115
/** the shared find widget for batch-find. */
116
protected JXFindPanel findPanel;
118
/** the shared find widget for incremental-find. */
119
protected JXFindBar findBar;
120
/** this is a temporary hack: need to remove the useSearchHighlighter property. */
121
protected JComponent lastFindBarTarget;
123
private boolean useFindBar;
125
private Point lastFindDialogLocation;
127
private FindRemover findRemover;
130
* Returns the shared SearchFactory.
132
* @return the shared <code>SearchFactory</code>
134
public static SearchFactory getInstance() {
135
if (searchFactory == null) {
136
searchFactory = new SearchFactory();
138
return searchFactory;
142
* Sets the shared SearchFactory.
146
public static void setInstance(SearchFactory factory) {
147
searchFactory = factory;
150
public SearchFactory() {
151
UIManager.addPropertyChangeListener(new LaFListener(this));
155
* Returns a common Keystroke for triggering
156
* a search. Tries to be OS-specific. <p>
158
* PENDING: this should be done in the LF and the
159
* keyStroke looked up in the UIManager.
161
* @return the keyStroke to register with a findAction.
163
public KeyStroke getSearchAccelerator() {
164
// JW: this should be handled by the LF!
165
// get the accelerator mnemonic from the UIManager
166
String findMnemonic = "F";
167
KeyStroke findStroke = Utilities.stringToKey("D-" + findMnemonic);
168
// fallback for sandbox (this should be handled in Utilities instead!)
169
if (findStroke == null) {
170
findStroke = KeyStroke.getKeyStroke("control F");
176
* Returns decision about using a batch- vs. incremental-find for the
177
* searchable. This implementation returns the useFindBar property directly.
179
* @param target - the component associated with the searchable
180
* @param searchable - the object to search.
181
* @return true if a incremental-find should be used, false otherwise.
183
public boolean isUseFindBar(JComponent target, Searchable searchable) {
188
* Sets the default search type to incremental or batch, for a
189
* true/false boolean. The default value is false (== batch).
191
* @param incremental a boolean to indicate the default search
192
* type, true for incremental and false for batch.
194
public void setUseFindBar(boolean incremental) {
195
if (incremental == useFindBar) return;
196
this.useFindBar = incremental;
197
getFindRemover().endSearching();
202
* Shows an appropriate find widget targeted at the searchable.
203
* Opens a batch-find or incremental-find
204
* widget based on the return value of <code>isUseFindBar</code>.
206
* @param target - the component associated with the searchable
207
* @param searchable - the object to search.
209
* @see #isUseFindBar(JComponent, Searchable)
210
* @see #setUseFindBar(boolean)
212
public void showFindInput(JComponent target, Searchable searchable) {
213
if (isUseFindBar(target, searchable)) {
214
showFindBar(target, searchable);
216
showFindDialog(target, searchable);
220
//------------------------- incremental search
223
* Show a incremental-find widget targeted at the searchable.
225
* This implementation uses a JXFindBar and inserts it into the
226
* target's toplevel container toolbar.
228
* PENDING: Nothing shown if there is no toolbar found.
230
* @param target - the component associated with the searchable
231
* @param searchable - the object to search.
233
public void showFindBar(JComponent target, Searchable searchable) {
234
if (target == null) return;
235
if (findBar == null) {
236
findBar = getSharedFindBar();
240
Window topLevel = SwingUtilities.getWindowAncestor(target);
241
if (topLevel instanceof JXFrame) {
242
JXRootPane rootPane = ((JXFrame) topLevel).getRootPaneExt();
243
JToolBar toolBar = rootPane.getToolBar();
244
if (toolBar == null) {
245
toolBar = new JToolBar();
246
rootPane.setToolBar(toolBar);
248
toolBar.add(findBar, 0);
249
rootPane.revalidate();
250
KeyboardFocusManager.getCurrentKeyboardFocusManager().focusNextComponent(findBar);
253
lastFindBarTarget = target;
254
findBar.setLocale(target.getLocale());
255
target.putClientProperty(AbstractSearchable.MATCH_HIGHLIGHTER, Boolean.TRUE);
256
getSharedFindBar().setSearchable(searchable);
257
installFindRemover(target, findBar);
261
* Returns the shared JXFindBar. Creates and configures on
264
* @return the shared <code>JXFindBar</code>
266
public JXFindBar getSharedFindBar() {
267
if (findBar == null) {
268
findBar = createFindBar();
269
configureSharedFindBar();
275
* Factory method to create a JXFindBar.
277
* @return the <code>JXFindBar</code>
279
public JXFindBar createFindBar() {
280
return new JXFindBar();
284
protected void installFindRemover(Container target, Container findWidget) {
285
if (target != null) {
286
getFindRemover().addTarget(target);
288
getFindRemover().addTarget(findWidget);
291
private FindRemover getFindRemover() {
292
if (findRemover == null) {
293
findRemover = new FindRemover();
299
* convenience method to remove a component from its parent
300
* and revalidate the parent
302
protected void removeFromParent(JComponent component) {
303
Container oldParent = component.getParent();
304
if (oldParent != null) {
305
oldParent.remove(component);
306
if (oldParent instanceof JComponent) {
307
((JComponent) oldParent).revalidate();
309
// not sure... never have non-j comps
310
oldParent.invalidate();
311
oldParent.validate();
316
protected void stopSearching() {
317
if (findPanel != null) {
318
lastFindDialogLocation = hideSharedFindPanel(false);
319
findPanel.setSearchable(null);
321
if (findBar != null) {
327
* Pre: findbar != null.
329
protected void releaseFindBar() {
330
findBar.setSearchable(null);
331
if (lastFindBarTarget != null) {
332
lastFindBarTarget.putClientProperty(AbstractSearchable.MATCH_HIGHLIGHTER, Boolean.FALSE);
333
lastFindBarTarget = null;
335
removeFromParent(findBar);
340
* Configures the shared FindBar. This method is
341
* called once after creation of the shared FindBar.
342
* Subclasses can override to add configuration code. <p>
344
* Here: registers a custom action to remove the
345
* findbar from its ancestor container.
347
* PRE: findBar != null.
350
protected void configureSharedFindBar() {
351
Action removeAction = new AbstractAction() {
353
public void actionPerformed(ActionEvent e) {
354
removeFromParent(findBar);
361
findBar.getActionMap().put(JXDialog.CLOSE_ACTION_COMMAND, removeAction);
364
//------------------------ batch search
367
* Show a batch-find widget targeted at the given Searchable.
369
* This implementation uses a shared JXFindPanel contained
373
* the component associated with the searchable
374
* @param searchable -
375
* the object to search.
377
public void showFindDialog(JComponent target, Searchable searchable) {
378
Window frame = null; //JOptionPane.getRootFrame();
379
if (target != null) {
380
target.putClientProperty(AbstractSearchable.MATCH_HIGHLIGHTER, Boolean.FALSE);
381
frame = SwingUtilities.getWindowAncestor(target);
382
// if (window instanceof Frame) {
383
// frame = (Frame) window;
386
JXDialog topLevel = getDialogForSharedFindPanel();
388
if ((topLevel != null) && (topLevel.getOwner().equals(frame))) {
389
findDialog = topLevel;
390
// JW: #635-swingx - quick hack to update title to current locale ...
391
// findDialog.setTitle(getSharedFindPanel().getName());
392
KeyboardFocusManager.getCurrentKeyboardFocusManager().focusNextComponent(findDialog);
394
Point location = hideSharedFindPanel(true);
395
if (frame instanceof Frame) {
396
findDialog = new JXDialog((Frame) frame, getSharedFindPanel());
397
} else if (frame instanceof Dialog) {
398
// fix #215-swingx: had problems with secondary modal dialogs.
399
findDialog = new JXDialog((Dialog) frame, getSharedFindPanel());
401
findDialog = new JXDialog(JOptionPane.getRootFrame(), getSharedFindPanel());
403
// RJO: shouldn't we avoid overloaded useage like this in a JSR296 world? swap getName() for getTitle() here?
404
// findDialog.setTitle(getSharedFindPanel().getName());
405
// JW: don't - this will stay on top of all applications!
406
// findDialog.setAlwaysOnTop(true);
408
if (location == null) {
409
findDialog.setLocationRelativeTo(frame);
411
findDialog.setLocation(location);
414
if (target != null) {
415
findDialog.setLocale(target.getLocale());
417
getSharedFindPanel().setSearchable(searchable);
418
installFindRemover(target, findDialog);
419
findDialog.setVisible(true);
424
* Returns the shared JXFindPanel. Lazyly creates and configures on
427
* @return the shared <code>JXFindPanel</code>
429
public JXFindPanel getSharedFindPanel() {
430
if (findPanel == null) {
431
findPanel = createFindPanel();
432
configureSharedFindPanel();
434
// JW: temporary hack around #718-swingx
435
// no longer needed with cleanup of hideSharedFindPanel
436
// if (findPanel.getParent() == null) {
437
// SwingUtilities.updateComponentTreeUI(findPanel);
444
* Factory method to create a JXFindPanel.
446
* @return <code>JXFindPanel</code>
448
public JXFindPanel createFindPanel() {
449
return new JXFindPanel();
454
* Configures the shared FindPanel. This method is
455
* called once after creation of the shared FindPanel.
456
* Subclasses can override to add configuration code. <p>
459
* PRE: findPanel != null.
462
protected void configureSharedFindPanel() {
467
private JXDialog getDialogForSharedFindPanel() {
468
if (findPanel == null) return null;
469
Window window = SwingUtilities.getWindowAncestor(findPanel);
470
return (window instanceof JXDialog) ? (JXDialog) window : null;
475
* Hides the findPanel's toplevel window and returns its location.
476
* If the dispose is true, the findPanel is removed from its parent
477
* and the toplevel window is disposed.
479
* @param dispose boolean to indicate whether the findPanels toplevel
480
* window should be disposed.
481
* @return the location of the window if visible, or the last known
484
protected Point hideSharedFindPanel(boolean dispose) {
485
if (findPanel == null) return null;
486
Window window = SwingUtilities.getWindowAncestor(findPanel);
487
Point location = lastFindDialogLocation;
488
if (window != null) {
489
// PENDING JW: can't remember why it it removed always?
490
if (window.isVisible()) {
491
location = window.getLocationOnScreen();
492
window.setVisible(false);
495
findPanel.getParent().remove(findPanel);
502
public class FindRemover implements PropertyChangeListener {
503
KeyboardFocusManager focusManager;
504
Set<Container> targets;
506
public FindRemover() {
510
public void addTarget(Container target) {
511
getTargets().add(target);
514
public void removeTarget(Container target) {
515
getTargets().remove(target);
518
private Set<Container> getTargets() {
519
if (targets == null) {
520
targets = new HashSet<Container>();
525
private void updateManager() {
526
if (focusManager != null) {
527
focusManager.removePropertyChangeListener("permanentFocusOwner", this);
529
this.focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
530
focusManager.addPropertyChangeListener("permanentFocusOwner", this);
533
public void propertyChange(PropertyChangeEvent ev) {
535
Component c = focusManager.getPermanentFocusOwner();
536
if (c == null) return;
537
for (Iterator<Container> iter = getTargets().iterator(); iter.hasNext();) {
538
Container element = iter.next();
539
if ((element == c) || (SwingUtilities.isDescendingFrom(c, element))) {
546
public void endSearching() {
547
getTargets().clear();
556
public void updateUI() {
557
if (findBar != null) {
558
SwingUtilities.updateComponentTreeUI(findBar);
561
if (findPanel != null) {
562
SwingUtilities.updateComponentTreeUI(findPanel);