~ubuntu-branches/ubuntu/natty/libswingx-java/natty

« back to all changes in this revision

Viewing changes to src/java/org/jdesktop/swingx/JXSearchField.java

  • Committer: Bazaar Package Importer
  • Author(s): Damien Raude-Morvan
  • Date: 2010-07-26 12:11:27 UTC
  • mfrom: (4.1.4 sid)
  • Revision ID: james.westby@ubuntu.com-20100726121127-k0d3b21nhja0dn93
Tags: 1:1.6.1-1
* New upstream release.
* Switch to 3.0 (quilt) format.
* Bump Standards-Version to 3.9.1: no changes needed.
* Drop Depends on JRE: not requested anymore by new Java Policy.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
package org.jdesktop.swingx;
 
2
 
 
3
import java.awt.Insets;
 
4
import java.awt.event.ActionEvent;
 
5
import java.awt.event.ActionListener;
 
6
import java.awt.event.KeyAdapter;
 
7
import java.awt.event.KeyEvent;
 
8
import java.beans.PropertyChangeEvent;
 
9
import java.beans.PropertyChangeListener;
 
10
 
 
11
import javax.swing.AbstractAction;
 
12
import javax.swing.JButton;
 
13
import javax.swing.JPopupMenu;
 
14
import javax.swing.KeyStroke;
 
15
import javax.swing.SwingUtilities;
 
16
import javax.swing.Timer;
 
17
import javax.swing.text.Document;
 
18
 
 
19
import org.jdesktop.swingx.plaf.SearchFieldAddon;
 
20
import org.jdesktop.swingx.plaf.LookAndFeelAddons;
 
21
import org.jdesktop.swingx.plaf.TextUIWrapper;
 
22
import org.jdesktop.swingx.plaf.UIManagerExt;
 
23
import org.jdesktop.swingx.prompt.BuddyButton;
 
24
import org.jdesktop.swingx.search.NativeSearchFieldSupport;
 
25
import org.jdesktop.swingx.search.RecentSearches;
 
26
 
 
27
/**
 
28
 * A text field with a find icon in which the user enters text that identifies
 
29
 * items to search for.
 
30
 * 
 
31
 * JXSearchField almost looks and behaves like a native Windows Vista search
 
32
 * box, a Mac OS X search field, or a search field like the one used in Mozilla
 
33
 * Thunderbird 2.0 - depending on the current look and feel.
 
34
 * 
 
35
 * JXSearchField is a text field that contains a find button and a cancel
 
36
 * button. The find button normally displays a lens icon appropriate for the
 
37
 * current look and feel. The cancel button is used to clear the text and
 
38
 * therefore only visible when text is present. It normally displays a 'x' like
 
39
 * icon. Text can also be cleared, using the 'Esc' key.
 
40
 * 
 
41
 * The position of the find and cancel buttons can be customized by either
 
42
 * changing the search fields (text) margin or button margin, or by changing the
 
43
 * {@link LayoutStyle}.
 
44
 * 
 
45
 * JXSearchField supports two different search modes: {@link SearchMode#INSTANT}
 
46
 * and {@link SearchMode#REGULAR}.
 
47
 * 
 
48
 * A search can be performed by registering an {@link ActionListener}. The
 
49
 * {@link ActionEvent}s command property contains the text to search for. The
 
50
 * search should be cancelled, when the command text is empty or null.
 
51
 * 
 
52
 * @see RecentSearches
 
53
 * @author Peter Weishapl <petw@gmx.net>
 
54
 * 
 
55
 */
 
56
public class JXSearchField extends JXTextField {
 
57
        /**
 
58
         * The default instant search delay.
 
59
         */
 
60
        private static final int DEFAULT_INSTANT_SEARCH_DELAY = 180;
 
61
        /**
 
62
         * The key used to invoke the cancel action.
 
63
         */
 
64
        private static final KeyStroke CANCEL_KEY = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
 
65
 
 
66
        /**
 
67
         * Defines, how the find and cancel button are layouted.
 
68
         */
 
69
        public enum LayoutStyle {
 
70
                /**
 
71
                 * <p>
 
72
                 * In VISTA layout style, the find button is placed on the right side of
 
73
                 * the search field. If text is entered, the find button is replaced by
 
74
                 * the cancel button when the actual search mode is
 
75
                 * {@link SearchMode#INSTANT}. When the search mode is
 
76
                 * {@link SearchMode#REGULAR} the find button will always stay visible
 
77
                 * and the cancel button will never be shown. However, 'Escape' can
 
78
                 * still be pressed to clear the text.
 
79
                 * </p>
 
80
                 */
 
81
                VISTA,
 
82
                /**
 
83
                 * <p>
 
84
                 * In MAC layout style, the find button is placed on the left side of
 
85
                 * the search field and the cancel button on the right side. The cancel
 
86
                 * button is only visible when text is present.
 
87
                 * </p>
 
88
                 */
 
89
                MAC
 
90
        };
 
91
 
 
92
        /**
 
93
         * Defines when action events are posted.
 
94
         */
 
95
        public enum SearchMode {
 
96
                /**
 
97
                 * <p>
 
98
                 * In REGULAR search mode, an action event is fired, when the user
 
99
                 * presses enter or clicks the find button.
 
100
                 * </p>
 
101
                 * <p>
 
102
                 * However, if a find popup menu is set and layout style is
 
103
                 * {@link LayoutStyle#MAC}, no action will be fired, when the find
 
104
                 * button is clicked, because instead the popup menu is shown. A search
 
105
                 * can therefore only be triggered, by pressing the enter key.
 
106
                 * </p>
 
107
                 * <p>
 
108
                 * The find button can have a rollover and a pressed icon, defined by
 
109
                 * the "SearchField.rolloverIcon" and "SearchField.pressedIcon" UI
 
110
                 * properties. When a find popup menu is set,
 
111
                 * "SearchField.popupRolloverIcon" and "SearchField.popupPressedIcon"
 
112
                 * are used.
 
113
                 * </p>
 
114
                 * 
 
115
                 */
 
116
                REGULAR,
 
117
                /**
 
118
                 * In INSTANT search mode, an action event is fired, when the user
 
119
                 * presses enter or changes the search text.
 
120
                 * 
 
121
                 * The action event is delayed about the number of milliseconds
 
122
                 * specified by {@link JXSearchField#getInstantSearchDelay()}.
 
123
                 * 
 
124
                 * No rollover and pressed icon is used for the find button.
 
125
                 */
 
126
                INSTANT
 
127
        }
 
128
 
 
129
        // ensure at least the default ui is registered
 
130
        static {
 
131
                LookAndFeelAddons.contribute(new SearchFieldAddon());
 
132
        }
 
133
 
 
134
        private JButton findButton;
 
135
 
 
136
        private JButton cancelButton;
 
137
 
 
138
        private JButton popupButton;
 
139
 
 
140
        private LayoutStyle layoutStyle;
 
141
 
 
142
        private SearchMode searchMode = SearchMode.INSTANT;
 
143
 
 
144
        private boolean useSeperatePopupButton;
 
145
 
 
146
        private boolean useSeperatePopupButtonSet;
 
147
 
 
148
        private boolean layoutStyleSet;
 
149
 
 
150
        private int instantSearchDelay = DEFAULT_INSTANT_SEARCH_DELAY;
 
151
 
 
152
        private boolean promptFontStyleSet;
 
153
 
 
154
        private Timer instantSearchTimer;
 
155
 
 
156
        private String recentSearchesSaveKey;
 
157
 
 
158
        private RecentSearches recentSearches;
 
159
 
 
160
        /**
 
161
         * Creates a new search field with a default prompt.
 
162
         */
 
163
        public JXSearchField() {
 
164
                this(UIManagerExt.getString("SearchField.prompt"));
 
165
        }
 
166
 
 
167
        /**
 
168
         * Creates a new search field with the given prompt and
 
169
         * {@link SearchMode#INSTANT}.
 
170
         * 
 
171
         * @param prompt
 
172
         */
 
173
        public JXSearchField(String prompt) {
 
174
                super(prompt);
 
175
                // use the native search field if possible.
 
176
                setUseNativeSearchFieldIfPossible(true);
 
177
                // install default actions
 
178
                setCancelAction(new ClearAction());
 
179
                setFindAction(new FindAction());
 
180
 
 
181
                // We cannot register the ClearAction through the Input- and
 
182
                // ActionMap because ToolTipManager registers the escape key with an
 
183
                // action that hides the tooltip every time the tooltip is changed and
 
184
                // then the ClearAction will never be called.
 
185
                addKeyListener(new KeyAdapter() {
 
186
                        public void keyPressed(KeyEvent e) {
 
187
                                if (CANCEL_KEY.equals(KeyStroke.getKeyStroke(e.getKeyCode(), e.getModifiers()))) {
 
188
                                        getCancelAction().actionPerformed(
 
189
                                                        new ActionEvent(JXSearchField.this, e.getID(), KeyEvent.getKeyText(e.getKeyCode())));
 
190
                                }
 
191
                        }
 
192
                });
 
193
 
 
194
                // Map specific native properties to general JXSearchField properties.
 
195
                addPropertyChangeListener(NativeSearchFieldSupport.FIND_POPUP_PROPERTY, new PropertyChangeListener() {
 
196
                        public void propertyChange(PropertyChangeEvent evt) {
 
197
                                JPopupMenu oldPopup = (JPopupMenu) evt.getOldValue();
 
198
                                firePropertyChange("findPopupMenu", oldPopup, evt.getNewValue());
 
199
                        }
 
200
                });
 
201
                addPropertyChangeListener(NativeSearchFieldSupport.CANCEL_ACTION_PROPERTY, new PropertyChangeListener() {
 
202
                        public void propertyChange(PropertyChangeEvent evt) {
 
203
                                ActionListener oldAction = (ActionListener) evt.getOldValue();
 
204
                                firePropertyChange("cancelAction", oldAction, evt.getNewValue());
 
205
                        }
 
206
                });
 
207
                addPropertyChangeListener(NativeSearchFieldSupport.FIND_ACTION_PROPERTY, new PropertyChangeListener() {
 
208
                        public void propertyChange(PropertyChangeEvent evt) {
 
209
                                ActionListener oldAction = (ActionListener) evt.getOldValue();
 
210
                                firePropertyChange("findAction", oldAction, evt.getNewValue());
 
211
                        }
 
212
                });
 
213
        }
 
214
 
 
215
        /**
 
216
         * Returns the current {@link SearchMode}.
 
217
         * 
 
218
         * @return the current {@link SearchMode}.
 
219
         */
 
220
        public SearchMode getSearchMode() {
 
221
                return searchMode;
 
222
        }
 
223
 
 
224
        /**
 
225
         * Returns <code>true</code> if the current {@link SearchMode} is
 
226
         * {@link SearchMode#INSTANT}.
 
227
         * 
 
228
         * @return <code>true</code> if the current {@link SearchMode} is
 
229
         *         {@link SearchMode#INSTANT}
 
230
         */
 
231
        public boolean isInstantSearchMode() {
 
232
                return SearchMode.INSTANT.equals(getSearchMode());
 
233
        }
 
234
 
 
235
        /**
 
236
         * Returns <code>true</code> if the current {@link SearchMode} is
 
237
         * {@link SearchMode#REGULAR}.
 
238
         * 
 
239
         * @return <code>true</code> if the current {@link SearchMode} is
 
240
         *         {@link SearchMode#REGULAR}
 
241
         */
 
242
        public boolean isRegularSearchMode() {
 
243
                return SearchMode.REGULAR.equals(getSearchMode());
 
244
        }
 
245
 
 
246
        /**
 
247
         * Sets the current search mode. See {@link SearchMode} for a description of
 
248
         * the different search modes.
 
249
         * 
 
250
         * @param searchMode
 
251
         *            {@link SearchMode#INSTANT} or {@link SearchMode#REGULAR}
 
252
         */
 
253
        public void setSearchMode(SearchMode searchMode) {
 
254
                firePropertyChange("searchMode", this.searchMode, this.searchMode = searchMode);
 
255
        }
 
256
 
 
257
        /**
 
258
         * Get the instant search delay in milliseconds. The default delay is 50
 
259
         * Milliseconds.
 
260
         * 
 
261
         * @see {@link #setInstantSearchDelay(int)}
 
262
         * @return the instant search delay in milliseconds
 
263
         */
 
264
        public int getInstantSearchDelay() {
 
265
                return instantSearchDelay;
 
266
        }
 
267
 
 
268
        /**
 
269
         * Set the instant search delay in milliseconds. In
 
270
         * {@link SearchMode#INSTANT}, when the user changes the text, an action
 
271
         * event will be fired after the specified instant search delay.
 
272
         * 
 
273
         * It is recommended to use a instant search delay to avoid the firing of
 
274
         * unnecessary events. For example when the user replaces the whole text
 
275
         * with a different text the search fields underlying {@link Document}
 
276
         * typically fires 2 document events. The first one, because the old text is
 
277
         * removed and the second one because the new text is inserted. If the
 
278
         * instant search delay is 0, this would result in 2 action events being
 
279
         * fired. When a instant search delay is used, the first document event
 
280
         * typically is ignored, because the second one is fired before the delay is
 
281
         * over, which results in a correct behavior because only the last and only
 
282
         * relevant event will be delivered.
 
283
         * 
 
284
         * @param instantSearchDelay
 
285
         */
 
286
        public void setInstantSearchDelay(int instantSearchDelay) {
 
287
                firePropertyChange("instantSearchDelay", this.instantSearchDelay, this.instantSearchDelay = instantSearchDelay);
 
288
        }
 
289
 
 
290
        /**
 
291
         * Get the current {@link LayoutStyle}.
 
292
         * 
 
293
         * @return
 
294
         */
 
295
        public LayoutStyle getLayoutStyle() {
 
296
                return layoutStyle;
 
297
        }
 
298
 
 
299
        /**
 
300
         * Returns <code>true</code> if the current {@link LayoutStyle} is
 
301
         * {@link LayoutStyle#VISTA}.
 
302
         * 
 
303
         * @return
 
304
         */
 
305
        public boolean isVistaLayoutStyle() {
 
306
                return LayoutStyle.VISTA.equals(getLayoutStyle());
 
307
        }
 
308
 
 
309
        /**
 
310
         * Returns <code>true</code> if the current {@link LayoutStyle} is
 
311
         * {@link LayoutStyle#MAC}.
 
312
         * 
 
313
         * @return
 
314
         */
 
315
        public boolean isMacLayoutStyle() {
 
316
                return LayoutStyle.MAC.equals(getLayoutStyle());
 
317
        }
 
318
 
 
319
        /**
 
320
         * Set the current {@link LayoutStyle}. See {@link LayoutStyle} for a
 
321
         * description of how this affects layout and behavior of the search field.
 
322
         * 
 
323
         * @param layoutStyle
 
324
         *            {@link LayoutStyle#MAC} or {@link LayoutStyle#VISTA}
 
325
         */
 
326
        public void setLayoutStyle(LayoutStyle layoutStyle) {
 
327
                layoutStyleSet = true;
 
328
                firePropertyChange("layoutStyle", this.layoutStyle, this.layoutStyle = layoutStyle);
 
329
        }
 
330
 
 
331
        /**
 
332
         * Set the margin space around the search field's text.
 
333
         * 
 
334
         * @see javax.swing.text.JTextComponent#setMargin(java.awt.Insets)
 
335
         */
 
336
        public void setMargin(Insets m) {
 
337
                super.setMargin(m);
 
338
        }
 
339
 
 
340
        /**
 
341
         * Returns the cancel action, or an instance of {@link ClearAction}, if
 
342
         * none has been set.
 
343
         * 
 
344
         * @return the cancel action
 
345
         */
 
346
        public final ActionListener getCancelAction() {
 
347
                ActionListener a = NativeSearchFieldSupport.getCancelAction(this);
 
348
                if (a == null) {
 
349
                        a = new ClearAction();
 
350
                }
 
351
                return a;
 
352
        }
 
353
 
 
354
        /**
 
355
         * Sets the action that is invoked, when the user presses the 'Esc' key or
 
356
         * clicks the cancel button.
 
357
         * 
 
358
         * @param cancelAction
 
359
         */
 
360
        public final void setCancelAction(ActionListener cancelAction) {
 
361
                NativeSearchFieldSupport.setCancelAction(this, cancelAction);
 
362
        }
 
363
 
 
364
        /**
 
365
         * Returns the cancel button.
 
366
         * 
 
367
         * Calls {@link #createCancelButton()} to create the cancel button and
 
368
         * registers an {@link ActionListener} that delegates actions to the
 
369
         * {@link ActionListener} returned by {@link #getCancelAction()}, if
 
370
         * needed.
 
371
         * 
 
372
         * @return the cancel button
 
373
         */
 
374
        public final JButton getCancelButton() {
 
375
                if (cancelButton == null) {
 
376
                        cancelButton = createCancelButton();
 
377
                        cancelButton.addActionListener(new ActionListener() {
 
378
                                public void actionPerformed(ActionEvent e) {
 
379
                                        getCancelAction().actionPerformed(e);
 
380
                                }
 
381
                        });
 
382
                }
 
383
                return cancelButton;
 
384
        }
 
385
 
 
386
        /**
 
387
         * Creates and returns the cancel button.
 
388
         * 
 
389
         * Override to use a custom cancel button.
 
390
         * 
 
391
         * @see #getCancelButton()
 
392
         * @return the cancel button
 
393
         */
 
394
        protected JButton createCancelButton() {
 
395
                BuddyButton btn = new BuddyButton();
 
396
 
 
397
                return btn;
 
398
        }
 
399
 
 
400
        /**
 
401
         * Returns the action that is invoked when the enter key is pressed or the
 
402
         * find button is clicked. If no action has been set, a new instance of
 
403
         * {@link FindAction} will be returned.
 
404
         * 
 
405
         * @return the find action
 
406
         */
 
407
        public final ActionListener getFindAction() {
 
408
                ActionListener a = NativeSearchFieldSupport.getFindAction(this);
 
409
                if (a == null) {
 
410
                        a = new FindAction();
 
411
                }
 
412
                return a;
 
413
        }
 
414
 
 
415
        /**
 
416
         * Sets the action that is invoked when the enter key is pressed or the find
 
417
         * button is clicked.
 
418
         * 
 
419
         * @return the find action
 
420
         */
 
421
        public final void setFindAction(ActionListener findAction) {
 
422
                NativeSearchFieldSupport.setFindAction(this, findAction);
 
423
        }
 
424
 
 
425
        /**
 
426
         * Returns the find button.
 
427
         * 
 
428
         * Calls {@link #createFindButton()} to create the find button and registers
 
429
         * an {@link ActionListener} that delegates actions to the
 
430
         * {@link ActionListener} returned by {@link #getFindAction()}, if needed.
 
431
         * 
 
432
         * @return the find button
 
433
         */
 
434
        public final JButton getFindButton() {
 
435
                if (findButton == null) {
 
436
                        findButton = createFindButton();
 
437
                        findButton.addActionListener(new ActionListener() {
 
438
                                public void actionPerformed(ActionEvent e) {
 
439
                                        getFindAction().actionPerformed(e);
 
440
                                }
 
441
                        });
 
442
                }
 
443
                return findButton;
 
444
        }
 
445
 
 
446
        /**
 
447
         * Creates and returns the find button. The buttons action is set to the
 
448
         * action returned by {@link #getSearchAction()}.
 
449
         * 
 
450
         * Override to use a custom find button.
 
451
         * 
 
452
         * @see #getFindButton()
 
453
         * @return the find button
 
454
         */
 
455
        protected JButton createFindButton() {
 
456
                BuddyButton btn = new BuddyButton();
 
457
 
 
458
                return btn;
 
459
        }
 
460
 
 
461
        /**
 
462
         * Returns the popup button. If a find popup menu is set, it will be
 
463
         * displayed when this button is clicked.
 
464
         * 
 
465
         * This button will only be visible, if {@link #isUseSeperatePopupButton()}
 
466
         * returns <code>true</code>. Otherwise the popup menu will be displayed
 
467
         * when the find button is clicked.
 
468
         * 
 
469
         * @return the popup button
 
470
         */
 
471
        public final JButton getPopupButton() {
 
472
                if (popupButton == null) {
 
473
                        popupButton = createPopupButton();
 
474
                }
 
475
                return popupButton;
 
476
        }
 
477
 
 
478
        /**
 
479
         * Creates and returns the popup button. Override to use a custom popup
 
480
         * button.
 
481
         * 
 
482
         * @see #getPopupButton()
 
483
         * @return the popup button
 
484
         */
 
485
        protected JButton createPopupButton() {
 
486
                return new BuddyButton();
 
487
        }
 
488
 
 
489
        /**
 
490
         * Returns <code>true</code> if the popup button should be visible and
 
491
         * used for displaying the find popup menu. Otherwise, the find popup menu
 
492
         * will be displayed when the find button is clicked.
 
493
         * 
 
494
         * @return <code>true</code> if the popup button should be used
 
495
         */
 
496
        public boolean isUseSeperatePopupButton() {
 
497
                return useSeperatePopupButton;
 
498
        }
 
499
 
 
500
        /**
 
501
         * Set if the popup button should be used for displaying the find popup
 
502
         * menu.
 
503
         * 
 
504
         * @param useSeperatePopupButton
 
505
         */
 
506
        public void setUseSeperatePopupButton(boolean useSeperatePopupButton) {
 
507
                useSeperatePopupButtonSet = true;
 
508
                firePropertyChange("useSeperatePopupButton", this.useSeperatePopupButton,
 
509
                                this.useSeperatePopupButton = useSeperatePopupButton);
 
510
        }
 
511
 
 
512
        public boolean isUseNativeSearchFieldIfPossible() {
 
513
                return NativeSearchFieldSupport.isSearchField(this);
 
514
        }
 
515
 
 
516
        public void setUseNativeSearchFieldIfPossible(boolean useNativeSearchFieldIfPossible) {
 
517
                TextUIWrapper.getDefaultWrapper().uninstall(this);
 
518
                NativeSearchFieldSupport.setSearchField(this, useNativeSearchFieldIfPossible);
 
519
                TextUIWrapper.getDefaultWrapper().install(this, true);
 
520
                updateUI();
 
521
        }
 
522
 
 
523
        /**
 
524
         * Updates the cancel, find and popup buttons enabled state in addition to
 
525
         * setting the search fields editable state.
 
526
         * 
 
527
         * @see #updateButtonState()
 
528
         * @see javax.swing.text.JTextComponent#setEditable(boolean)
 
529
         */
 
530
        public void setEditable(boolean b) {
 
531
                super.setEditable(b);
 
532
                updateButtonState();
 
533
        }
 
534
 
 
535
        /**
 
536
         * Updates the cancel, find and popup buttons enabled state in addition to
 
537
         * setting the search fields enabled state.
 
538
         * 
 
539
         * @see #updateButtonState()
 
540
         * @see javax.swing.text.JTextComponent#setEnabled(boolean)
 
541
         */
 
542
        public void setEnabled(boolean enabled) {
 
543
                super.setEnabled(enabled);
 
544
                updateButtonState();
 
545
        }
 
546
 
 
547
        /**
 
548
         * Enables the cancel action if this search field is editable and enabled,
 
549
         * otherwise it will be disabled. Enabled the search action and popup button
 
550
         * if this search field is enabled, otherwise it will be disabled.
 
551
         */
 
552
        protected void updateButtonState() {
 
553
                getCancelButton().setEnabled(isEditable() & isEnabled());
 
554
                getFindButton().setEnabled(isEnabled());
 
555
                getPopupButton().setEnabled(isEnabled());
 
556
        }
 
557
 
 
558
        /**
 
559
         * Sets the popup menu that will be displayed when the popup button is
 
560
         * clicked. If a find popup menu is set and
 
561
         * {@link #isUseSeperatePopupButton()} returns <code>false</code>, the
 
562
         * popup button will be displayed instead of the find button. Otherwise the
 
563
         * popup button will be displayed in addition to the find button.
 
564
         * 
 
565
         * The find popup menu is managed using {@link NativeSearchFieldSupport} to
 
566
         * achieve compatibility with the native search field support provided by
 
567
         * the Mac Look And Feel since Mac OS 10.5.
 
568
         * 
 
569
         * If a recent searches save key has been set and therefore a recent
 
570
         * searches popup menu is installed, this method does nothing. You must
 
571
         * first remove the recent searches save key, by calling
 
572
         * {@link #setRecentSearchesSaveKey(String)} with a <code>null</code>
 
573
         * parameter.
 
574
         * 
 
575
         * @see #setRecentSearchesSaveKey(String)
 
576
         * @see RecentSearches
 
577
         * @param findPopupMenu
 
578
         *            the popup menu, which will be displayed when the popup button
 
579
         *            is clicked
 
580
         */
 
581
        public void setFindPopupMenu(JPopupMenu findPopupMenu) {
 
582
                if (isManagingRecentSearches()) {
 
583
                        return;
 
584
                }
 
585
 
 
586
                NativeSearchFieldSupport.setFindPopupMenu(this, findPopupMenu);
 
587
        }
 
588
 
 
589
        /**
 
590
         * Returns the find popup menu.
 
591
         * 
 
592
         * @see #setFindPopupMenu(JPopupMenu)
 
593
         * @return the find popup menu
 
594
         */
 
595
        public JPopupMenu getFindPopupMenu() {
 
596
                return NativeSearchFieldSupport.getFindPopupMenu(this);
 
597
        }
 
598
 
 
599
        /**
 
600
         * TODO
 
601
         * 
 
602
         * @return
 
603
         */
 
604
        public final boolean isManagingRecentSearches() {
 
605
                return recentSearches != null;
 
606
        }
 
607
 
 
608
        private boolean isValidRecentSearchesKey(String key) {
 
609
                return key != null && key.length() > 0;
 
610
        }
 
611
 
 
612
        /**
 
613
         * Returns the key used to persist recent searches.
 
614
         * 
 
615
         * @see #setRecentSearchesSaveKey(String)
 
616
         * @return
 
617
         */
 
618
        public String getRecentSearchesSaveKey() {
 
619
                return recentSearchesSaveKey;
 
620
        }
 
621
 
 
622
        /**
 
623
         * Installs and manages a recent searches popup menu as the find popup menu,
 
624
         * if <code>recentSearchesSaveKey</code> is not null. Otherwise, removes
 
625
         * the popup menu and stops managing recent searches.
 
626
         * 
 
627
         * @see #setFindAction(ActionListener)
 
628
         * @see #isManagingRecentSearches()
 
629
         * @see RecentSearches
 
630
         * 
 
631
         * @param recentSearchesSaveKey
 
632
         *            this key is used to persist the recent searches.
 
633
         */
 
634
        public void setRecentSearchesSaveKey(String recentSearchesSaveKey) {
 
635
                String oldName = getRecentSearchesSaveKey();
 
636
                this.recentSearchesSaveKey = recentSearchesSaveKey;
 
637
 
 
638
                if (recentSearches != null) {
 
639
                        // set null before uninstalling. otherwise the popup menu is not
 
640
                        // allowed to be changed.
 
641
                        RecentSearches rs = recentSearches;
 
642
                        recentSearches = null;
 
643
                        rs.uninstall(this);
 
644
                }
 
645
 
 
646
                if (isValidRecentSearchesKey(recentSearchesSaveKey)) {
 
647
                        recentSearches = new RecentSearches(recentSearchesSaveKey);
 
648
                        recentSearches.install(this);
 
649
                }
 
650
 
 
651
                firePropertyChange("recentSearchesSaveKey", oldName, this.recentSearchesSaveKey);
 
652
        }
 
653
 
 
654
        /**
 
655
         * TODO
 
656
         * 
 
657
         * @return
 
658
         */
 
659
        public RecentSearches getRecentSearches() {
 
660
                return recentSearches;
 
661
        }
 
662
 
 
663
        /**
 
664
         * Returns the {@link Timer} used to delay the firing of action events in
 
665
         * instant search mode when the user enters text.
 
666
         * 
 
667
         * This timer calls {@link #postActionEvent()}.
 
668
         * 
 
669
         * @return the {@link Timer} used to delay the firing of action events
 
670
         */
 
671
        public Timer getInstantSearchTimer() {
 
672
                if (instantSearchTimer == null) {
 
673
                        instantSearchTimer = new Timer(0, new ActionListener() {
 
674
                                public void actionPerformed(ActionEvent e) {
 
675
                                        postActionEvent();
 
676
                                }
 
677
                        });
 
678
                        instantSearchTimer.setRepeats(false);
 
679
                }
 
680
                return instantSearchTimer;
 
681
        }
 
682
 
 
683
        /**
 
684
         * Returns <code>true</code> if this search field is the focus owner or
 
685
         * the find popup menu is visible.
 
686
         * 
 
687
         * This is a hack to make the search field paint the focus indicator in Mac
 
688
         * OS X Aqua when the find popup menu is visible.
 
689
         * 
 
690
         * @return <code>true</code> if this search field is the focus owner or
 
691
         *         the find popup menu is visible
 
692
         */
 
693
        public boolean hasFocus() {
 
694
                if (getFindPopupMenu() != null && getFindPopupMenu().isVisible()) {
 
695
                        return true;
 
696
                }
 
697
                return super.hasFocus();
 
698
        }
 
699
 
 
700
        /**
 
701
         * Overriden to also update the find popup menu if set.
 
702
         */
 
703
        public void updateUI() {
 
704
                super.updateUI();
 
705
                if (getFindPopupMenu() != null) {
 
706
                        SwingUtilities.updateComponentTreeUI(getFindPopupMenu());
 
707
                }
 
708
        }
 
709
 
 
710
        /**
 
711
         * Hack to enable the UI delegate to set default values depending on the
 
712
         * current Look and Feel, without overriding custom values.
 
713
         */
 
714
        public void setPromptFontStyle(Integer fontStyle) {
 
715
                super.setPromptFontStyle(fontStyle);
 
716
                promptFontStyleSet = true;
 
717
        }
 
718
 
 
719
        /**
 
720
         * Hack to enable the UI delegate to set default values depending on the
 
721
         * current Look and Feel, without overriding custom values.
 
722
         * 
 
723
         * @param propertyName
 
724
         *            the name of the property to change
 
725
         * @param value
 
726
         *            the new value of the property
 
727
         */
 
728
        public void customSetUIProperty(String propertyName, Object value) {
 
729
                customSetUIProperty(propertyName, value, false);
 
730
        }
 
731
 
 
732
        /**
 
733
         * Hack to enable the UI delegate to set default values depending on the
 
734
         * current Look and Feel, without overriding custom values.
 
735
         * 
 
736
         * @param propertyName
 
737
         *            the name of the property to change
 
738
         * @param value
 
739
         *            the new value of the property
 
740
         * @param override
 
741
         *            override custom values
 
742
         */
 
743
        public void customSetUIProperty(String propertyName, Object value, boolean override) {
 
744
                if (propertyName == "useSeperatePopupButton") {
 
745
                        if (!useSeperatePopupButtonSet || override) {
 
746
                                setUseSeperatePopupButton(((Boolean) value).booleanValue());
 
747
                                useSeperatePopupButtonSet = false;
 
748
                        }
 
749
                } else if (propertyName == "layoutStyle") {
 
750
                        if (!layoutStyleSet || override) {
 
751
                                setLayoutStyle(LayoutStyle.valueOf(value.toString()));
 
752
                                layoutStyleSet = false;
 
753
                        }
 
754
                } else if (propertyName == "promptFontStyle") {
 
755
                        if (!promptFontStyleSet || override) {
 
756
                                setPromptFontStyle((Integer) value);
 
757
                                promptFontStyleSet = false;
 
758
                        }
 
759
                } else {
 
760
                        throw new IllegalArgumentException();
 
761
                }
 
762
        }
 
763
 
 
764
        /**
 
765
         * Overriden to prevent any delayed {@link ActionEvent}s from being sent
 
766
         * after posting this action.
 
767
         * 
 
768
         * For example, if the current {@link SearchMode} is
 
769
         * {@link SearchMode#INSTANT} and the instant search delay is greater 0. The
 
770
         * user enters some text and presses enter. This method will be invoked
 
771
         * immediately because the users presses enter. However, this method would
 
772
         * be invoked after the instant search delay, if we would not prevent it
 
773
         * here.
 
774
         */
 
775
        public void postActionEvent() {
 
776
                getInstantSearchTimer().stop();
 
777
                super.postActionEvent();
 
778
        }
 
779
 
 
780
        /**
 
781
         * Invoked when the the cancel button or the 'Esc' key is pressed. Sets the
 
782
         * text in the search field to <code>null</code>.
 
783
         * 
 
784
         */
 
785
        class ClearAction extends AbstractAction {
 
786
                public ClearAction() {
 
787
                        putValue(SHORT_DESCRIPTION, "Clear Search Text");
 
788
                }
 
789
 
 
790
                /**
 
791
                 * Calls {@link #clear()}.
 
792
                 */
 
793
                public void actionPerformed(ActionEvent e) {
 
794
                        clear();
 
795
                }
 
796
 
 
797
                /**
 
798
                 * Sets the search field's text to <code>null</code> and requests the
 
799
                 * focus for the search field.
 
800
                 */
 
801
                public void clear() {
 
802
                        setText(null);
 
803
                        requestFocusInWindow();
 
804
                }
 
805
        }
 
806
 
 
807
        /**
 
808
         * Invoked when the find button is pressed.
 
809
         */
 
810
        public class FindAction extends AbstractAction {
 
811
                public FindAction() {
 
812
                }
 
813
 
 
814
                /**
 
815
                 * In regular search mode posts an action event if the search field is
 
816
                 * the focus owner.
 
817
                 * 
 
818
                 * Also requests the focus for the search field and selects the whole
 
819
                 * text.
 
820
                 */
 
821
                public void actionPerformed(ActionEvent e) {
 
822
                        if (isFocusOwner() && isRegularSearchMode()) {
 
823
                                postActionEvent();
 
824
                        }
 
825
                        requestFocusInWindow();
 
826
                        selectAll();
 
827
                }
 
828
        }
 
829
}