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 }