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