0001: /*
0002: * @(#)${NAME}
0003: *
0004: * Copyright 2002 - 2004 JIDE Software Inc. All rights reserved.
0005: */
0006: package com.jidesoft.swing;
0007:
0008: import com.jidesoft.plaf.UIDefaultsLookup;
0009: import com.jidesoft.popup.JidePopup;
0010: import com.jidesoft.swing.event.SearchableEvent;
0011: import com.jidesoft.swing.event.SearchableListener;
0012:
0013: import javax.swing.*;
0014: import javax.swing.event.DocumentEvent;
0015: import javax.swing.event.DocumentListener;
0016: import javax.swing.event.EventListenerList;
0017: import java.awt.*;
0018: import java.awt.event.*;
0019: import java.beans.PropertyChangeListener;
0020: import java.beans.PropertyChangeSupport;
0021: import java.util.Locale;
0022: import java.util.regex.Pattern;
0023: import java.util.regex.PatternSyntaxException;
0024:
0025: /**
0026: * JList, JTable and JTree are three data-rich components. They can be used
0027: * to display a huge amount of data so searching function will be very a useful feature in those components.
0028: * <code>Searchable</code> is such a class that can make JList, JTable and JTree searchable.
0029: * User can simply type in any string they want to search for and use arrow keys to navigate
0030: * to next or previous occurrence.
0031: * <p/>
0032: * <code>Searchable</code> is a base abstract class. <code>ListSearchable</code>, <code>TableSearchable</code> and <code>TreeSearchable</code>
0033: * are implementations to make JList, JTable and JTree searchable respectively. For each implementation, there are
0034: * five methods need to be implemented.
0035: * <ul>
0036: * <li><code>protected abstract int getSelectedIndex()</code>
0037: * <li><code>protected abstract void setSelectedIndex(int index, boolean incremental)</code>
0038: * <li><code>protected abstract int getElementCount()</code>
0039: * <li><code>protected abstract Object getElementAt(int index)</code>
0040: * <li><code>protected abstract String convertElementToString(Object element)</code>
0041: * </ul>
0042: * <p/>
0043: * Please look at the javadoc of each method to learn more details.
0044: * <p/>
0045: * The keys used by this class are fully customizable. Subclass can override the methods such as {@link #isActivateKey(java.awt.event.KeyEvent)},
0046: * {@link #isDeactivateKey(java.awt.event.KeyEvent)}, {@link #isFindFirstKey(java.awt.event.KeyEvent)},{@link #isFindLastKey(java.awt.event.KeyEvent)},
0047: * {@link #isFindNextKey(java.awt.event.KeyEvent)}, {@link #isFindPreviousKey(java.awt.event.KeyEvent)} to provide its own set of keys.
0048: * <p/>
0049: * In addition to press up/down arrow to find next occurrence or previous occurrence of
0050: * particular string, there are several other features that are very handy.
0051: * <p/>
0052: * Multiple selection feature - If you press CTRL key and hold it while pressing up and down arrow,
0053: * it will find next/previous occurence while keeping existing selections.
0054: * <br>
0055: * Select all feature - If you type in a searching text and press CTRL+A, all the occurrences of
0056: * that searching string will be selected. This is a very handy feature.
0057: * For example you want to delete all rows in a table whose name column begins with "old".
0058: * So you can type in "old" and press CTRL+A, now all rows begining with "old" will
0059: * be selected. Pressing delete will delete all of them.
0060: * <br>
0061: * Basic regular expression support - It allows '?' to match any letter or digit,
0062: * or '*' to match several letters or digits.
0063: * Even though it's possible to implement full regular expression support, we don't want to do that.
0064: * The reason is the regular expression is very complex, it's probably not a good idea to let user
0065: * type in such a complex expression in a small popup window. However if your user is very familiar
0066: * with regular expression, you can add the feature to <code>Searchable</code>. All you need to do
0067: * is to override {@link #compare(String,String)} method
0068: * and implement by yourself.
0069: * <p/>
0070: * As this is an abstract class, please refer to to javadoc of {@link ListSearchable},{@link TreeSearchable}, and {@link TableSearchable} to find out
0071: * how to use it with JList, JTree and JTable respectively.
0072: * <p/>
0073: * This component has a timer. If user types very fast, it will accumulate them together and generate only one searching action.
0074: * The timer can be controlled by {@link #setSearchingDelay(int)}.
0075: * <p/>
0076: * By default we will use lightweight popup for the sake of performance. But if you use heavyweight component
0077: * which could obscure the lightweight popup, you can call {@link #setHeavyweightComponentEnabled(boolean)} to true
0078: * so that heavyweight popup will be used.
0079: */
0080: public abstract class Searchable {
0081:
0082: private final PropertyChangeSupport _propertyChangeSupport = new PropertyChangeSupport(
0083: this );
0084:
0085: protected final JComponent _component;
0086:
0087: private SearchPopup _popup;
0088: private JLayeredPane _layeredPane;
0089:
0090: private boolean _heavyweightComponentEnabled;
0091:
0092: /**
0093: * optional SearchableProvider
0094: */
0095: private SearchableProvider _searchableProvider;
0096: private Pattern _pattern;
0097: private String _searchText;
0098: private String _previousSearchText;
0099:
0100: private boolean _caseSensitive = false;
0101: private boolean _repeats = false;
0102: private boolean _wildcardEnabled = true;
0103: private Color _mismatchForeground;
0104: private Color _foreground = null;
0105: private Color _background = null;
0106: protected ComponentAdapter _componentListener;
0107:
0108: public final static String PROPERTY_SEARCH_TEXT = "searchText";
0109:
0110: private KeyAdapter _keyListener;
0111:
0112: private FocusAdapter _focusListener;
0113:
0114: private int _cursor = -1;
0115:
0116: private String _searchLabel = getResourceString("Searchable.searchFor");
0117:
0118: private String _noMatchLabel = getResourceString("Searchable.noMatch");
0119:
0120: /**
0121: * The popup location
0122: */
0123: private int _popupLocation = SwingConstants.TOP;
0124:
0125: private int _searchingDelay = 0;
0126:
0127: private boolean _reverseOrder = false;
0128:
0129: /**
0130: * A list of event listeners for this component.
0131: */
0132: protected EventListenerList listenerList = new EventListenerList();
0133:
0134: private Component _popupLocationRelativeTo;
0135:
0136: /**
0137: * Creates a Searchable.
0138: *
0139: * @param component component where the Searchable will be installed.
0140: */
0141: public Searchable(JComponent component) {
0142: _previousSearchText = null;
0143: _component = component;
0144: installListeners();
0145: }
0146:
0147: /**
0148: * Creates a Searchable.
0149: *
0150: * @param component component where the Searchable will be installed.
0151: */
0152: public Searchable(JComponent component,
0153: SearchableProvider searchableProvider) {
0154: _searchableProvider = searchableProvider;
0155: _previousSearchText = null;
0156: _component = component;
0157: installListeners();
0158: }
0159:
0160: /**
0161: * Gets the selected index in the component. The concrete implementation
0162: * should call methods on the component to retrieve the current selected index.
0163: * If the component supports multiple selection,
0164: * it's OK just return the index of the first selection.
0165: * <p>Here are some examples. In the case of JList, the index is the row index.
0166: * In the case of JTree, the index is the row index too. In the case of JTable, depending on the seleection mode,
0167: * the index could be row index (in row selection mode), could be column index (in column selection mode)
0168: * or could the cell index (in cell selection mode).
0169: *
0170: * @return the selected index.
0171: */
0172: protected abstract int getSelectedIndex();
0173:
0174: /**
0175: * Sets the selected index. The concrete implementation should call methods on the component to select
0176: * the element at the specified index. The incremental flag is used to do multiple select. If the flag is true,
0177: * the element at the index should be added to current selection. If false, you should clear previous
0178: * selection and then select the element.
0179: *
0180: * @param index the index to be selected
0181: * @param incremental a flag to enable multiple selection. If the flag is true,
0182: * the element at the index should be added to current selection. If false, you should clear previous
0183: * selection and then select the element.
0184: */
0185: protected abstract void setSelectedIndex(int index,
0186: boolean incremental);
0187:
0188: /**
0189: * Gets the total element count in the component. Different concrete implementation could have different interpretation of
0190: * the count. This is totally OK as long as it's consistent in all the methods. For example, the index parameter in other methods
0191: * should be always a valid value within the total count.
0192: *
0193: * @return the total element count.
0194: */
0195: protected abstract int getElementCount();
0196:
0197: /**
0198: * Gets the element at the specified index. The element could be any data structure that
0199: * internally used in the component. The convertElementToString method will give you a chance
0200: * to convert the element to string which is used to compare with the string that user types in.
0201: *
0202: * @param index the index
0203: * @return the element at the specified index.
0204: */
0205: protected abstract Object getElementAt(int index);
0206:
0207: /**
0208: * Converts the element that returns from getElementAt() to string.
0209: *
0210: * @param element
0211: * @return the string representing the element in the component.
0212: */
0213: protected abstract String convertElementToString(Object element);
0214:
0215: /**
0216: * A text field for searching text.
0217: */
0218: protected class SearchField extends JTextField {
0219: SearchField() {
0220: }
0221:
0222: @Override
0223: public Dimension getPreferredSize() {
0224: Dimension size = super .getPreferredSize();
0225: size.width = getFontMetrics(getFont()).stringWidth(
0226: getText()) + 4;
0227: return size;
0228: }
0229:
0230: @Override
0231: public void processKeyEvent(KeyEvent e) {
0232: int keyCode = e.getKeyCode();
0233: if (keyCode == KeyEvent.VK_BACK_SPACE
0234: && getDocument().getLength() == 0) {
0235: e.consume();
0236: return;
0237: }
0238: final boolean isNavigationKey = isNavigationKey(e);
0239: if (isDeactivateKey(e) && !isNavigationKey) {
0240: hidePopup();
0241: if (keyCode == KeyEvent.VK_ESCAPE)
0242: e.consume();
0243: return;
0244: }
0245: super .processKeyEvent(e);
0246: if (keyCode == KeyEvent.VK_BACK_SPACE || isNavigationKey)
0247: e.consume();
0248: if (isSelectAllKey(e)) {
0249: e.consume();
0250: }
0251: }
0252: }
0253:
0254: /**
0255: * The popup panel for search label and search text field.
0256: */
0257: private class DefaultSearchPopup extends SearchPopup {
0258: private JLabel _label;
0259: private JLabel _noMatch;
0260:
0261: public DefaultSearchPopup(String text) {
0262: initComponents(text);
0263: }
0264:
0265: private void initComponents(String text) {
0266: final Color foreground = Searchable.this .getForeground();
0267: final Color background = Searchable.this .getBackground();
0268:
0269: // setup the label
0270: _label = new JLabel(_searchLabel);
0271: _label.setForeground(foreground);
0272: _label.setVerticalAlignment(JLabel.BOTTOM);
0273:
0274: _noMatch = new JLabel();
0275: _noMatch.setForeground(getMismatchForeground());
0276: _noMatch.setVerticalAlignment(JLabel.BOTTOM);
0277:
0278: //setup text field
0279: _textField = new SearchField();
0280: _textField.setFocusable(false);
0281: _textField.setOpaque(false);
0282: _textField.setBorder(BorderFactory.createEmptyBorder());
0283: _textField.setForeground(foreground);
0284: _textField.setCursor(getCursor());
0285: _textField.getDocument().addDocumentListener(
0286: new DocumentListener() {
0287: private Timer timer = new Timer(200,
0288: new ActionListener() {
0289: public void actionPerformed(
0290: ActionEvent e) {
0291: applyText();
0292: }
0293: });
0294:
0295: public void insertUpdate(DocumentEvent e) {
0296: startTimer();
0297: }
0298:
0299: public void removeUpdate(DocumentEvent e) {
0300: startTimer();
0301: }
0302:
0303: public void changedUpdate(DocumentEvent e) {
0304: startTimer();
0305: }
0306:
0307: protected void applyText() {
0308: String text = _textField.getText().trim();
0309: if (text.length() != 0) {
0310: int found = findFromCursor(text);
0311: if (found == -1) {
0312: _textField
0313: .setForeground(getMismatchForeground());
0314: } else {
0315: _textField
0316: .setForeground(foreground);
0317: }
0318: select(found, null, text);
0319: } else {
0320: hidePopup();
0321: }
0322: }
0323:
0324: void startTimer() {
0325: updatePopupBounds();
0326: if (getSearchingDelay() > 0) {
0327: timer.setDelay(getSearchingDelay());
0328: if (timer.isRunning()) {
0329: timer.restart();
0330: } else {
0331: timer.setRepeats(false);
0332: timer.start();
0333: }
0334: } else {
0335: applyText();
0336: }
0337: }
0338: });
0339: _textField.setText(text);
0340:
0341: setBackground(background);
0342: setBorder(BorderFactory.createCompoundBorder(BorderFactory
0343: .createLineBorder(UIDefaultsLookup
0344: .getColor("controlShadow"), 1),
0345: BorderFactory.createEmptyBorder(0, 6, 1, 8)));
0346: setLayout(new BorderLayout(2, 0));
0347: Dimension size = _label.getPreferredSize();
0348: size.height = _textField.getPreferredSize().height;
0349: _label.setPreferredSize(size);
0350: add(_label, BorderLayout.BEFORE_LINE_BEGINS);
0351: add(_textField, BorderLayout.CENTER);
0352: add(_noMatch, BorderLayout.AFTER_LINE_ENDS);
0353: setPopupBorder(BorderFactory.createEmptyBorder());
0354: }
0355:
0356: @Override
0357: protected void select(int index, KeyEvent e,
0358: String searchingText) {
0359: if (index != -1) {
0360: setSelectedIndex(index, e != null
0361: && isIncrementalSelectKey(e));
0362: _cursor = index;
0363: _textField.setForeground(getForeground());
0364: _noMatch.setText("");
0365: } else {
0366: _textField.setForeground(getMismatchForeground());
0367: _noMatch.setText(_noMatchLabel);
0368: }
0369: updatePopupBounds();
0370: firePropertyChangeEvent(searchingText);
0371: if (index != -1) {
0372: Object element = getElementAt(index);
0373: fireSearchableEvent(new SearchableEvent(
0374: Searchable.this ,
0375: SearchableEvent.SEARCHABLE_MATCH,
0376: searchingText, element,
0377: convertElementToString(element)));
0378: } else {
0379: fireSearchableEvent(new SearchableEvent(
0380: Searchable.this ,
0381: SearchableEvent.SEARCHABLE_NOMATCH,
0382: searchingText));
0383: }
0384: }
0385:
0386: private void updatePopupBounds() {
0387: if (_popup != null) {
0388: _textField.invalidate();
0389: try {
0390: if (!isHeavyweightComponentEnabled()) {
0391: _popup.setSize(_popup.getPreferredSize());
0392: _popup.validate();
0393: } else {
0394: _popup.packPopup();
0395: }
0396: } catch (Exception e) { // catch any potential exception
0397: // see bug report at http://www.jidesoft.com/forum/viewtopic.php?p=8557#8557
0398: }
0399: }
0400: }
0401: }
0402:
0403: /**
0404: * Hides the popup.
0405: */
0406: public void hidePopup() {
0407: if (_popup != null) {
0408: if (isHeavyweightComponentEnabled()) {
0409: _popup.hidePopupImmediately();
0410: } else {
0411: _layeredPane.remove(_popup);
0412: _layeredPane.validate();
0413: _layeredPane.repaint();
0414: _layeredPane = null;
0415: }
0416: _popup = null;
0417: _searchableProvider = null;
0418: fireSearchableEvent(new SearchableEvent(this ,
0419: SearchableEvent.SEARCHABLE_END));
0420: }
0421: _cursor = -1;
0422: }
0423:
0424: public SearchableProvider getSearchableProvider() {
0425: return _searchableProvider;
0426: }
0427:
0428: public void setSearchableProvider(
0429: SearchableProvider searchableProvider) {
0430: _searchableProvider = searchableProvider;
0431: }
0432:
0433: /**
0434: * Installs necessary listeners to the component. This method will be called automatically when Searchable is created.
0435: */
0436: public void installListeners() {
0437: if (_componentListener == null) {
0438: _componentListener = new ComponentAdapter() {
0439: @Override
0440: public void componentHidden(ComponentEvent e) {
0441: super .componentHidden(e);
0442: boolean passive = _searchableProvider == null
0443: || _searchableProvider.isPassive();
0444: if (passive) {
0445: hidePopup();
0446: }
0447: }
0448:
0449: @Override
0450: public void componentResized(ComponentEvent e) {
0451: super .componentResized(e);
0452: boolean passive = _searchableProvider == null
0453: || _searchableProvider.isPassive();
0454: if (passive) {
0455: updateSizeAndLocation();
0456: }
0457: }
0458:
0459: @Override
0460: public void componentMoved(ComponentEvent e) {
0461: super .componentMoved(e);
0462: boolean passive = _searchableProvider == null
0463: || _searchableProvider.isPassive();
0464: if (passive) {
0465: updateSizeAndLocation();
0466: }
0467: }
0468: };
0469: }
0470: _component.addComponentListener(_componentListener);
0471: Component scrollPane = JideSwingUtilities
0472: .getScrollPane(_component);
0473: if (scrollPane != null) {
0474: scrollPane.addComponentListener(_componentListener);
0475: }
0476:
0477: _keyListener = new KeyAdapter() {
0478: @Override
0479: public void keyTyped(KeyEvent e) {
0480: boolean passive = _searchableProvider == null
0481: || _searchableProvider.isPassive();
0482: if (passive) {
0483: keyTypedOrPressed(e);
0484: }
0485: }
0486:
0487: @Override
0488: public void keyPressed(KeyEvent e) {
0489: boolean passive = _searchableProvider == null
0490: || _searchableProvider.isPassive();
0491: if (passive) {
0492: keyTypedOrPressed(e);
0493: }
0494: }
0495: };
0496: JideSwingUtilities.insertKeyListener(getComponent(),
0497: _keyListener, 0);
0498:
0499: _focusListener = new FocusAdapter() {
0500: @Override
0501: public void focusLost(FocusEvent focusevent) {
0502: boolean passive = _searchableProvider == null
0503: || _searchableProvider.isPassive();
0504: if (passive) {
0505: hidePopup();
0506: }
0507: }
0508: };
0509: getComponent().addFocusListener(_focusListener);
0510: }
0511:
0512: /**
0513: * Uninstall the listeners that installed before. This method is never called because
0514: * we don't have the control of the life cyle of the component. However you can call this
0515: * method if you don't want the searchable component not searchable.
0516: */
0517: public void uninstallListeners() {
0518: if (_componentListener != null) {
0519: getComponent().removeComponentListener(_componentListener);
0520: Component scrollPane = JideSwingUtilities
0521: .getScrollPane(getComponent());
0522: if (scrollPane != null) {
0523: scrollPane.removeComponentListener(_componentListener);
0524: }
0525: _componentListener = null;
0526: }
0527:
0528: if (_keyListener != null) {
0529: getComponent().removeKeyListener(_keyListener);
0530: _keyListener = null;
0531: }
0532:
0533: if (_focusListener != null) {
0534: getComponent().removeFocusListener(_focusListener);
0535: _focusListener = null;
0536: }
0537: }
0538:
0539: /**
0540: * Adds the property change listener. The only property change event that will be fired is the "searchText"
0541: * property which will be fired when user types in a different search text in the popup.
0542: *
0543: * @param propertychangelistener
0544: */
0545: public void addPropertyChangeListener(
0546: PropertyChangeListener propertychangelistener) {
0547: _propertyChangeSupport
0548: .addPropertyChangeListener(propertychangelistener);
0549: }
0550:
0551: /**
0552: * Removes the property change listener.
0553: *
0554: * @param propertychangelistener
0555: */
0556: public void removePropertyChangeListener(
0557: PropertyChangeListener propertychangelistener) {
0558: _propertyChangeSupport
0559: .removePropertyChangeListener(propertychangelistener);
0560: }
0561:
0562: public void firePropertyChangeEvent(String searchingText) {
0563: if (!searchingText.equals(_previousSearchText)) {
0564: _propertyChangeSupport.firePropertyChange(
0565: PROPERTY_SEARCH_TEXT, _previousSearchText,
0566: searchingText);
0567: fireSearchableEvent(new SearchableEvent(Searchable.this ,
0568: SearchableEvent.SEARCHABLE_CHANGE, searchingText,
0569: _previousSearchText));
0570: _previousSearchText = searchingText;
0571: }
0572: }
0573:
0574: /**
0575: * Checks if the element matches the searching text.
0576: *
0577: * @param element
0578: * @param searchingText
0579: * @return true if matches.
0580: */
0581: protected boolean compare(Object element, String searchingText) {
0582: String text = convertElementToString(element);
0583: return text != null
0584: && compare(isCaseSensitive() ? text : text
0585: .toLowerCase(), searchingText);
0586: }
0587:
0588: /**
0589: * Checks if the element string matches the searching text. Different from {@link #compare(Object,String)},
0590: * this method is after the element has been converted to string using {@link #convertElementToString(Object)}.
0591: *
0592: * @param text
0593: * @param searchingText
0594: * @return true if matches.
0595: */
0596: protected boolean compare(String text, String searchingText) {
0597: if (!isWildcardEnabled()) {
0598: return searchingText != null
0599: && (searchingText.equals(text) || searchingText
0600: .length() > 0
0601: && text.startsWith(searchingText));
0602: } else {
0603: // if it doesn't have the two special characters we support, we don't need to use regular expression.
0604: int posAny = searchingText.indexOf('*');
0605: int posOne = searchingText.indexOf('?');
0606: //
0607: if ((posAny == -1/* || posAny == searchingText.length() - 1*/)
0608: && (posOne == -1/* || posOne == searchingText.length() - 1*/)) {
0609: return text.startsWith(searchingText);
0610: }
0611:
0612: // use the prvious pattern since nothing changed.
0613: if (_searchText != null
0614: && _searchText.equals(searchingText)
0615: && _pattern != null) {
0616: return _pattern.matcher(text).find();
0617: }
0618:
0619: _searchText = searchingText;
0620: StringBuffer buffer = new StringBuffer();
0621: int length = searchingText.length();
0622: buffer.append('^');
0623: for (int i = 0; i < length; i++) {
0624: char c = searchingText.charAt(i);
0625: // replace '?' with '.'
0626: if (c == '?') {
0627: buffer.append(".");
0628: } else if (c == '*') {
0629: buffer.append(".*");
0630: } else if ("(){}[].^$\\".indexOf(c) != -1) { // escape all other special characters
0631: buffer.append('\\');
0632: buffer.append(c);
0633: } else {
0634: buffer.append(c);
0635: }
0636: }
0637: try {
0638: _pattern = Pattern.compile(buffer.toString(),
0639: isCaseSensitive() ? 0
0640: : Pattern.CASE_INSENSITIVE);
0641: return _pattern.matcher(text).find();
0642: } catch (PatternSyntaxException e) {
0643: return false;
0644: }
0645: }
0646: }
0647:
0648: /**
0649: * Gets the cursor which is the index of current location when searching. The value will be
0650: * used in findNext and findPrevious.
0651: *
0652: * @return the current position of the cursor.
0653: */
0654: public int getCursor() {
0655: return _cursor;
0656: }
0657:
0658: /**
0659: * Sets the cursor which is the index of current location when searching. The value will be
0660: * used in findNext and findPrevious.
0661: *
0662: * @param cursor the new position of the cursor.
0663: */
0664: public void setCursor(int cursor) {
0665: _cursor = cursor;
0666: }
0667:
0668: /**
0669: * Finds the next matching index from the cursor.
0670: *
0671: * @param s
0672: * @return the next index that the element matches the searching text.
0673: */
0674: public int findNext(String s) {
0675: String str = isCaseSensitive() ? s : s.toLowerCase();
0676: int count = getElementCount();
0677: if (count == 0)
0678: return s.length() > 0 ? -1 : 0;
0679: int selectedIndex = (_cursor != -1 ? _cursor
0680: : getSelectedIndex());
0681: for (int i = selectedIndex + 1; i < count; i++) {
0682: Object element = getElementAt(i);
0683: if (compare(element, str))
0684: return i;
0685: }
0686:
0687: if (isRepeats()) {
0688: for (int i = 0; i < selectedIndex; i++) {
0689: Object element = getElementAt(i);
0690: if (compare(element, str))
0691: return i;
0692: }
0693: }
0694:
0695: return selectedIndex == -1 ? -1 : (compare(
0696: getElementAt(selectedIndex), str) ? selectedIndex : -1);
0697: }
0698:
0699: /**
0700: * Finds the previous matching index from the cursor.
0701: *
0702: * @param s
0703: * @return the previous index that the element matches the searching text.
0704: */
0705: public int findPrevious(String s) {
0706: String str = isCaseSensitive() ? s : s.toLowerCase();
0707: int count = getElementCount();
0708: if (count == 0)
0709: return s.length() > 0 ? -1 : 0;
0710: int selectedIndex = (_cursor != -1 ? _cursor
0711: : getSelectedIndex());
0712: for (int i = selectedIndex - 1; i >= 0; i--) {
0713: Object element = getElementAt(i);
0714: if (compare(element, str))
0715: return i;
0716: }
0717:
0718: if (isRepeats()) {
0719: for (int i = count - 1; i >= selectedIndex; i--) {
0720: Object element = getElementAt(i);
0721: if (compare(element, str))
0722: return i;
0723: }
0724: }
0725: return selectedIndex == -1 ? -1 : (compare(
0726: getElementAt(selectedIndex), str) ? selectedIndex : -1);
0727: }
0728:
0729: /**
0730: * Finds the next matching index from the cursor. If it reaches the end, it will restart from the beginning.
0731: * However is the reverseOrder flag is true, it will finds the previous matching index from the cursor. If it reaches
0732: * the beginning, it will restart from the end.
0733: *
0734: * @param s
0735: * @return the next index that the element matches the searching text.
0736: */
0737: public int findFromCursor(String s) {
0738: if (isReverseOrder()) {
0739: return reverseFindFromCursor(s);
0740: }
0741:
0742: String str = isCaseSensitive() ? s : s.toLowerCase();
0743: int selectedIndex = (_cursor != -1 ? _cursor
0744: : getSelectedIndex());
0745: if (selectedIndex < 0)
0746: selectedIndex = 0;
0747: int count = getElementCount();
0748: if (count == 0)
0749: return -1; // no match
0750:
0751: // find from cursor
0752: for (int i = selectedIndex; i < count; i++) {
0753: Object element = getElementAt(i);
0754: if (compare(element, str))
0755: return i;
0756: }
0757:
0758: // if not found, start over from the beginning
0759: for (int i = 0; i < selectedIndex; i++) {
0760: Object element = getElementAt(i);
0761: if (compare(element, str))
0762: return i;
0763: }
0764:
0765: return -1;
0766: }
0767:
0768: /**
0769: * Finds the previous matching index from the cursor. If it reaches the beginning, it will restart from the end.
0770: *
0771: * @param s
0772: * @return the next index that the element matches the searching text.
0773: */
0774: public int reverseFindFromCursor(String s) {
0775: if (!isReverseOrder()) {
0776: return findFromCursor(s);
0777: }
0778:
0779: String str = isCaseSensitive() ? s : s.toLowerCase();
0780: int selectedIndex = (_cursor != -1 ? _cursor
0781: : getSelectedIndex());
0782: if (selectedIndex < 0)
0783: selectedIndex = 0;
0784: int count = getElementCount();
0785: if (count == 0)
0786: return -1; // no match
0787:
0788: // find from cursor to beginning
0789: for (int i = selectedIndex; i >= 0; i--) {
0790: Object element = getElementAt(i);
0791: if (compare(element, str))
0792: return i;
0793: }
0794:
0795: // if not found, start over from the end
0796: for (int i = count - 1; i >= selectedIndex; i--) {
0797: Object element = getElementAt(i);
0798: if (compare(element, str))
0799: return i;
0800: }
0801:
0802: return -1;
0803: }
0804:
0805: /**
0806: * Finds the first element that matches the searching text.
0807: *
0808: * @param s
0809: * @return the first element that matches with the searching text.
0810: */
0811: public int findFirst(String s) {
0812: String str = isCaseSensitive() ? s : s.toLowerCase();
0813: int count = getElementCount();
0814: if (count == 0)
0815: return s.length() > 0 ? -1 : 0;
0816:
0817: for (int i = 0; i < count; i++) {
0818: int index = getIndex(count, i);
0819: Object element = getElementAt(index);
0820: if (compare(element, str))
0821: return index;
0822: }
0823:
0824: return -1;
0825: }
0826:
0827: /**
0828: * Finds the last element that matches the searching text.
0829: *
0830: * @param s
0831: * @return the last element that matches the searching text.
0832: */
0833: public int findLast(String s) {
0834: String str = isCaseSensitive() ? s : s.toLowerCase();
0835: int count = getElementCount();
0836: if (count == 0)
0837: return s.length() > 0 ? -1 : 0;
0838: for (int i = count - 1; i >= 0; i--) {
0839: Object element = getElementAt(i);
0840: if (compare(element, str))
0841: return i;
0842: }
0843: return -1;
0844: }
0845:
0846: /**
0847: * This method is called when a key is typed or pressed.
0848: *
0849: * @param e the KeyEvent.
0850: */
0851: protected void keyTypedOrPressed(KeyEvent e) {
0852: if (_searchableProvider != null
0853: && _searchableProvider.isPassive()) {
0854: _searchableProvider.processKeyEvent(e);
0855: return;
0856: }
0857:
0858: if (isActivateKey(e)) {
0859: String searchingText = "";
0860: if (e.getID() == KeyEvent.KEY_TYPED) {
0861: if (((e.getModifiers() & Toolkit.getDefaultToolkit()
0862: .getMenuShortcutKeyMask()) != 0)) { // if alt key is pressed
0863: return;
0864: }
0865: if (e.isAltDown()) {
0866: return;
0867: }
0868:
0869: searchingText = String.valueOf(e.getKeyChar());
0870: }
0871: showPopup(searchingText);
0872: if (e.getKeyCode() != KeyEvent.VK_ENTER) {
0873: e.consume();
0874: }
0875: }
0876: }
0877:
0878: private int getIndex(int count, int index) {
0879: return isReverseOrder() ? count - index - 1 : index;
0880: }
0881:
0882: /**
0883: * Shows the search popup. By default, the search popup will be visible
0884: * automatically when user types in the first key (in the case of JList, JTree, JTable)
0885: * or types in designated keystroke (in the case of JTextComponent). So this method is only
0886: * used when you want to show the popup manually.
0887: *
0888: * @param searchingText
0889: */
0890: public void showPopup(String searchingText) {
0891: if (_searchableProvider == null) {
0892: fireSearchableEvent(new SearchableEvent(this ,
0893: SearchableEvent.SEARCHABLE_START, searchingText));
0894: showPopup(createSearchPopup(searchingText));
0895: _searchableProvider = new SearchableProvider() {
0896: public String getSearchingText() {
0897: return _popup != null ? _popup.getSearchingText()
0898: : "";
0899: }
0900:
0901: public boolean isPassive() {
0902: return true;
0903: }
0904:
0905: public void processKeyEvent(KeyEvent e) {
0906: if (_popup != null) {
0907: _popup.processKeyEvent(e);
0908: }
0909: }
0910: };
0911: }
0912: }
0913:
0914: /**
0915: * Creates the popup to hold the searching text.
0916: *
0917: * @param searchingText
0918: * @return the searching popup.
0919: */
0920: protected SearchPopup createSearchPopup(String searchingText) {
0921: return new DefaultSearchPopup(searchingText);
0922: }
0923:
0924: /**
0925: * Gets the searching text.
0926: *
0927: * @return the searching text.
0928: */
0929: public String getSearchingText() {
0930: return _searchableProvider != null ? _searchableProvider
0931: .getSearchingText() : "";
0932: }
0933:
0934: private void showPopup(SearchPopup searchpopup) {
0935: JRootPane rootPane = _component.getRootPane();
0936: if (rootPane != null)
0937: _layeredPane = rootPane.getLayeredPane();
0938: else {
0939: _layeredPane = null;
0940: }
0941:
0942: if (_layeredPane == null || isHeavyweightComponentEnabled()) {
0943: _popup = searchpopup;
0944: Point location = updateSizeAndLocation();
0945: if (location != null) {
0946: searchpopup.showPopup(location.x, location.y);
0947: _popup.setVisible(true);
0948: } else {
0949: _popup = null;
0950: }
0951: } else {
0952: if (_popup != null && _layeredPane != null) {
0953: _layeredPane.remove(_popup);
0954: _layeredPane.validate();
0955: _layeredPane.repaint();
0956: _layeredPane = null;
0957: } else if (!_component.isShowing())
0958: _popup = null;
0959: else
0960: _popup = searchpopup;
0961:
0962: if (_popup == null || !_component.isDisplayable())
0963: return;
0964:
0965: if (_layeredPane == null) {
0966: System.err.println("Failed to find layeredPane.");
0967: return;
0968: }
0969:
0970: _layeredPane.add(_popup, JLayeredPane.POPUP_LAYER);
0971:
0972: updateSizeAndLocation();
0973: _popup.setVisible(true);
0974: _popup.validate();
0975: }
0976: }
0977:
0978: private Point updateSizeAndLocation() {
0979: Component component = getPopupLocationRelativeTo();
0980: if (component == null) {
0981: component = JideSwingUtilities.getScrollPane(_component);
0982: }
0983: if (component == null) {
0984: component = _component;
0985: }
0986:
0987: Point componentLocation;
0988: if (_popup != null) {
0989: Dimension size = _popup.getPreferredSize();
0990: switch (_popupLocation) {
0991: case SwingConstants.BOTTOM:
0992: try {
0993: componentLocation = component.getLocationOnScreen();
0994: componentLocation.y += component.getHeight();
0995: } catch (IllegalComponentStateException e) {
0996: return null; // can't get the location so just return.
0997: }
0998: if (!isHeavyweightComponentEnabled()) {
0999: SwingUtilities.convertPointFromScreen(
1000: componentLocation, _layeredPane);
1001: if ((componentLocation.y + size.height > _layeredPane
1002: .getHeight())) {
1003: componentLocation.y = _layeredPane.getHeight()
1004: - size.height;
1005: }
1006: }
1007: break;
1008: case SwingConstants.TOP:
1009: default:
1010: try {
1011: componentLocation = component.getLocationOnScreen();
1012: } catch (IllegalComponentStateException e) {
1013: return null; // can't get the location so just return.
1014: }
1015: if (!isHeavyweightComponentEnabled()) {
1016: SwingUtilities.convertPointFromScreen(
1017: componentLocation, _layeredPane);
1018: }
1019: componentLocation.y -= size.height;
1020: if ((componentLocation.y < 0)) {
1021: componentLocation.y = 0;
1022: }
1023: break;
1024: }
1025: if (!isHeavyweightComponentEnabled()) {
1026: _popup.setLocation(componentLocation);
1027: _popup.setSize(size);
1028: } else {
1029: _popup.packPopup();
1030: }
1031: return componentLocation;
1032: } else {
1033: return null;
1034: }
1035: }
1036:
1037: /**
1038: * Checks if the key is used as a key to find the first occurence.
1039: *
1040: * @param e
1041: * @return true if the key in KeyEvent is a key to find the first occurence. By default, home key is used.
1042: */
1043: protected boolean isFindFirstKey(KeyEvent e) {
1044: return e.getKeyCode() == KeyEvent.VK_HOME;
1045: }
1046:
1047: /**
1048: * Checks if the key is used as a key to find the last occurence.
1049: *
1050: * @param e
1051: * @return true if the key in KeyEvent is a key to find the last occurence. By default, end key is used.
1052: */
1053: protected boolean isFindLastKey(KeyEvent e) {
1054: return e.getKeyCode() == KeyEvent.VK_END;
1055: }
1056:
1057: /**
1058: * Checks if the key is used as a key to find the previous occurence.
1059: *
1060: * @param e
1061: * @return true if the key in KeyEvent is a key to find the previous occurence. By default, up arrow key is used.
1062: */
1063: protected boolean isFindPreviousKey(KeyEvent e) {
1064: return e.getKeyCode() == KeyEvent.VK_UP;
1065: }
1066:
1067: /**
1068: * Checks if the key is used as a key to find the next occurence.
1069: *
1070: * @param e
1071: * @return true if the key in KeyEvent is a key to find the next occurence. By default, down arrow key is used.
1072: */
1073: protected boolean isFindNextKey(KeyEvent e) {
1074: return e.getKeyCode() == KeyEvent.VK_DOWN;
1075: }
1076:
1077: /**
1078: * Checks if the key is used as a navigation key. Navigation keys are keys which are used to
1079: * navigate to other occurences of the searching string.
1080: *
1081: * @param e
1082: * @return true if the key in KeyEvent is a navigation key.
1083: */
1084: protected boolean isNavigationKey(KeyEvent e) {
1085: return isFindFirstKey(e) || isFindLastKey(e)
1086: || isFindNextKey(e) || isFindPreviousKey(e);
1087: }
1088:
1089: /**
1090: * Checks if the key in KeyEvent should activate the search popup.
1091: *
1092: * @param e
1093: * @return true if the keyChar is a letter or a digit or '*' or '?'.
1094: */
1095: protected boolean isActivateKey(KeyEvent e) {
1096: char keyChar = e.getKeyChar();
1097: return e.getID() == KeyEvent.KEY_TYPED
1098: && (Character.isLetterOrDigit(keyChar)
1099: || keyChar == '*' || keyChar == '?');
1100: }
1101:
1102: /**
1103: * Checks if the key in KeyEvent should hide the search popup. If this method return true and
1104: * the key is not used for navigation purpose ({@link #isNavigationKey(java.awt.event.KeyEvent)} return false), the popup will be hidden.
1105: *
1106: * @param e
1107: * @return true if the keyCode in the KeyEvent is escape key, enter key,
1108: * or any of the arrow keys such as page up, page down, home, end, left, right, up and down.
1109: */
1110: protected boolean isDeactivateKey(KeyEvent e) {
1111: int keyCode = e.getKeyCode();
1112: return keyCode == KeyEvent.VK_ENTER
1113: || keyCode == KeyEvent.VK_ESCAPE
1114: || keyCode == KeyEvent.VK_PAGE_UP
1115: || keyCode == KeyEvent.VK_PAGE_DOWN
1116: || keyCode == KeyEvent.VK_HOME
1117: || keyCode == KeyEvent.VK_END
1118: || keyCode == KeyEvent.VK_LEFT
1119: || keyCode == KeyEvent.VK_RIGHT
1120: || keyCode == KeyEvent.VK_UP
1121: || keyCode == KeyEvent.VK_DOWN;
1122: }
1123:
1124: /**
1125: * Checks if the key will trigger selecting all.
1126: *
1127: * @param e
1128: * @return true if the key in KeyEvent is a key to trigger selecting all.
1129: */
1130: protected boolean isSelectAllKey(KeyEvent e) {
1131: return ((e.getModifiers() & Toolkit.getDefaultToolkit()
1132: .getMenuShortcutKeyMask()) != 0)
1133: && e.getKeyCode() == KeyEvent.VK_A;
1134: }
1135:
1136: /**
1137: * Checks if the key will trigger incremental selection.
1138: *
1139: * @param e
1140: * @return true if the key in KeyEvent is a key to trigger incremental selection. By default, ctrl down key is used.
1141: */
1142: protected boolean isIncrementalSelectKey(KeyEvent e) {
1143: return (e.getModifiers() & Toolkit.getDefaultToolkit()
1144: .getMenuShortcutKeyMask()) != 0;
1145: }
1146:
1147: /**
1148: * Gets the foreground color when the searching text doesn't match with any of the elements in the component.
1149: *
1150: * @return the forground color for mismatch. If you never call
1151: * {@link #setMismatchForeground(java.awt.Color)}. red color will be used.
1152: */
1153: public Color getMismatchForeground() {
1154: if (_mismatchForeground == null) {
1155: return Color.RED;
1156: } else {
1157: return _mismatchForeground;
1158: }
1159: }
1160:
1161: /**
1162: * Sets the foreground for mismatch.
1163: *
1164: * @param mismatchForeground
1165: */
1166: public void setMismatchForeground(Color mismatchForeground) {
1167: _mismatchForeground = mismatchForeground;
1168: }
1169:
1170: /**
1171: * Checks if the case is sensitive during searching.
1172: *
1173: * @return true if the searching is case sensitive.
1174: */
1175: public boolean isCaseSensitive() {
1176: return _caseSensitive;
1177: }
1178:
1179: /**
1180: * Sets the case sensitive flag. By default, it's false meaning it's a case insensitive search.
1181: *
1182: * @param caseSensitive
1183: */
1184: public void setCaseSensitive(boolean caseSensitive) {
1185: _caseSensitive = caseSensitive;
1186: }
1187:
1188: /**
1189: * If it returns a positive number, it will wait for that many ms
1190: * before doing the search. When the searching is complex, this
1191: * flag will be useful to make the searching efficient. In the other words,
1192: * if user types in several keys very quickly, there will be only one search.
1193: * If it returns 0 or negative number, each key will generate a search.
1194: *
1195: * @return the number of ms delay before searching starts.
1196: */
1197: public int getSearchingDelay() {
1198: return _searchingDelay;
1199: }
1200:
1201: /**
1202: * If this flag is set to a positive number, it will wait for that many ms
1203: * before doing the search. When the searching is complex, this
1204: * flag will be useful to make the searching efficient. In the other words,
1205: * if user types in several keys very quickly, there will be only one search.
1206: * If this flag is set to 0 or a negative number, each key will generate a search with no delay.
1207: *
1208: * @param searchingDelay the number of ms delay before searching start.
1209: */
1210: public void setSearchingDelay(int searchingDelay) {
1211: _searchingDelay = searchingDelay;
1212: }
1213:
1214: /**
1215: * Checks if restart from the beginning when searching reaches the end
1216: * or restart from the end when reaches beginning. Default is false.
1217: *
1218: * @return true or false.
1219: */
1220: public boolean isRepeats() {
1221: return _repeats;
1222: }
1223:
1224: /**
1225: * Sets the repeat flag. By default, it's false meaning it will stop searching
1226: * when reaching the end or reaching the beginning.
1227: *
1228: * @param repeats
1229: */
1230: public void setRepeats(boolean repeats) {
1231: _repeats = repeats;
1232: }
1233:
1234: /**
1235: * Gets the foreground color used inn the search popup.
1236: *
1237: * @return the foreground. By default it will use the foreground of tooltip.
1238: */
1239: public Color getForeground() {
1240: if (_foreground == null) {
1241: return UIDefaultsLookup.getColor("ToolTip.foreground");
1242: } else {
1243: return _foreground;
1244: }
1245: }
1246:
1247: /**
1248: * Sets the foreground color used by popup.
1249: *
1250: * @param foreground
1251: */
1252: public void setForeground(Color foreground) {
1253: _foreground = foreground;
1254: }
1255:
1256: /**
1257: * Gets the background color used inn the search popup.
1258: *
1259: * @return the background. By default it will use the background of tooltip.
1260: */
1261: public Color getBackground() {
1262: if (_background == null) {
1263: return UIDefaultsLookup.getColor("ToolTip.background");
1264: } else {
1265: return _background;
1266: }
1267: }
1268:
1269: /**
1270: * Sets the background color used by popup.
1271: *
1272: * @param background
1273: */
1274: public void setBackground(Color background) {
1275: _background = background;
1276: }
1277:
1278: /**
1279: * Checks if it supports wildcard in searching text. By default it is true which means user can type
1280: * in "*" or "?" to match with any charactors or any charactor. If it's false, it will treat "*" or "?"
1281: * as a regular charactor.
1282: *
1283: * @return true if it supports wildcard.
1284: */
1285: public boolean isWildcardEnabled() {
1286: return _wildcardEnabled;
1287: }
1288:
1289: /**
1290: * Enable or disable the usage of wildcard.
1291: *
1292: * @param wildcardEnabled
1293: * @see #isWildcardEnabled()
1294: */
1295: public void setWildcardEnabled(boolean wildcardEnabled) {
1296: _wildcardEnabled = wildcardEnabled;
1297: }
1298:
1299: /**
1300: * Gets the current text that appears in the search popup. By default it is "Search for: ".
1301: *
1302: * @return the text that appears in the search popup.
1303: */
1304: public String getSearchLabel() {
1305: return _searchLabel;
1306: }
1307:
1308: /**
1309: * Sets the text that appears in the search popup.
1310: *
1311: * @param searchLabel
1312: */
1313: public void setSearchLabel(String searchLabel) {
1314: _searchLabel = searchLabel;
1315: }
1316:
1317: /**
1318: * Adds the specified listener to receive searchable events from this
1319: * searchable.
1320: *
1321: * @param l the searchable listener
1322: */
1323: public void addSearchableListener(SearchableListener l) {
1324: listenerList.add(SearchableListener.class, l);
1325: }
1326:
1327: /**
1328: * Removes the specified searchable listener so that it no longer
1329: * receives searchable events.
1330: *
1331: * @param l the searchable listener
1332: */
1333: public void removeSearchableListener(SearchableListener l) {
1334: listenerList.remove(SearchableListener.class, l);
1335: }
1336:
1337: /**
1338: * Returns an array of all the <code>SearchableListener</code>s added
1339: * to this <code>SearchableGroup</code> with
1340: * <code>addSearchableListener</code>.
1341: *
1342: * @return all of the <code>SearchableListener</code>s added or an empty
1343: * array if no listeners have been added
1344: * @see #addSearchableListener
1345: */
1346: public SearchableListener[] getSearchableListeners() {
1347: return listenerList.getListeners(SearchableListener.class);
1348: }
1349:
1350: /**
1351: * Fires a searchable event.
1352: *
1353: * @param e the event
1354: */
1355: protected void fireSearchableEvent(SearchableEvent e) {
1356: Object[] listeners = listenerList.getListenerList();
1357: for (int i = listeners.length - 2; i >= 0; i -= 2) {
1358: if (listeners[i] == SearchableListener.class) {
1359: ((SearchableListener) listeners[i + 1])
1360: .searchableEventFired(e);
1361: }
1362: }
1363: }
1364:
1365: /**
1366: * Gets the actual component which installed this Searchable.
1367: *
1368: * @return the actual component which installed this Searchable.
1369: */
1370: public Component getComponent() {
1371: return _component;
1372: }
1373:
1374: /**
1375: * Gets the popup location. It could be either {@link SwingConstants#TOP} or {@link SwingConstants#BOTTOM}.
1376: *
1377: * @return the popup location.
1378: */
1379: public int getPopupLocation() {
1380: return _popupLocation;
1381: }
1382:
1383: /**
1384: * Sets the popup location.
1385: *
1386: * @param popupLocation the popup location. The valid values are either {@link SwingConstants#TOP} or {@link SwingConstants#BOTTOM}.
1387: */
1388: public void setPopupLocation(int popupLocation) {
1389: _popupLocation = popupLocation;
1390: }
1391:
1392: public abstract class SearchPopup extends JidePopup {
1393: protected SearchField _textField;
1394:
1395: @Override
1396: public void processKeyEvent(KeyEvent e) {
1397: _textField.processKeyEvent(e);
1398: if (e.isConsumed()) {
1399: String text = getSearchingText();
1400: if (text.length() == 0) {
1401: return;
1402: }
1403:
1404: if (isSelectAllKey(e)) {
1405: selectAll(e, text);
1406: return;
1407: }
1408:
1409: int found;
1410: if (isFindPreviousKey(e)) {
1411: found = findPrevious(text);
1412: select(found, e, text);
1413: } else if (isFindNextKey(e)) {
1414: found = findNext(text);
1415: select(found, e, text);
1416: } else if (isFindFirstKey(e)) {
1417: found = findFirst(text);
1418: select(found, e, text);
1419: } else if (isFindLastKey(e)) {
1420: found = findLast(text);
1421: select(found, e, text);
1422: }
1423: // else {
1424: // found = findFromCursor(text);
1425: // }
1426: }
1427: if (e.getKeyCode() != KeyEvent.VK_ENTER) {
1428: e.consume();
1429: }
1430: }
1431:
1432: private void selectAll(KeyEvent e, String text) {
1433: boolean oldReverseOrder = isReverseOrder(); // keep the old reverse order and we will set it back.
1434: if (oldReverseOrder) {
1435: setReverseOrder(false);
1436: }
1437:
1438: int index = findFirst(text);
1439: if (index != -1) {
1440: setSelectedIndex(index, false); // clear side effect of ctrl-a will select all items
1441: _cursor = index; // as setSelectedIndex is used directly, we have to manually set the cursor value.
1442: }
1443:
1444: boolean oldRepeats = isRepeats(); // set repeats to false and set it back later.
1445: if (oldRepeats) {
1446: setRepeats(false);
1447: }
1448:
1449: while (index != -1) {
1450: int newIndex = findNext(text);
1451: if (index == newIndex) {
1452: index = -1;
1453: } else {
1454: index = newIndex;
1455: }
1456: if (index == -1) {
1457: break;
1458: }
1459: select(index, e, text);
1460: }
1461:
1462: if (oldRepeats) {
1463: setRepeats(oldRepeats);
1464: }
1465:
1466: if (oldReverseOrder) {
1467: setReverseOrder(oldReverseOrder);
1468: }
1469: }
1470:
1471: public String getSearchingText() {
1472: return _textField != null ? _textField.getText() : "";
1473: }
1474:
1475: abstract protected void select(int index, KeyEvent e,
1476: String searchingText);
1477: }
1478:
1479: /**
1480: * Checks the searching order. By default the searchable starts searching from top to bottom. If this flag
1481: * is false, it searchs from bottom to top.
1482: *
1483: * @return the reverseOrder flag.
1484: */
1485: public boolean isReverseOrder() {
1486: return _reverseOrder;
1487: }
1488:
1489: /**
1490: * Sets the searching order. By default the searchable starts searching from top to bottom. If this flag
1491: * is false, it searchs from bottom to top.
1492: *
1493: * @param reverseOrder
1494: */
1495: public void setReverseOrder(boolean reverseOrder) {
1496: _reverseOrder = reverseOrder;
1497: }
1498:
1499: /**
1500: * Gets the localized string from resource bundle. Subclass can override it to provide its own string.
1501: * Available keys are defined in swing.properties that begin with "Searchable.".
1502: *
1503: * @param key
1504: * @return the localized string.
1505: */
1506: protected String getResourceString(String key) {
1507: return Resource.getResourceBundle(Locale.getDefault())
1508: .getString(key);
1509: }
1510:
1511: /**
1512: * Check if the searchable popup is visible.
1513: *
1514: * @return true if visible. Otherwise, false.
1515: */
1516: public boolean isPopupVisible() {
1517: return _popup != null;
1518: }
1519:
1520: public boolean isHeavyweightComponentEnabled() {
1521: return _heavyweightComponentEnabled;
1522: }
1523:
1524: public void setHeavyweightComponentEnabled(
1525: boolean heavyweightComponentEnabled) {
1526: _heavyweightComponentEnabled = heavyweightComponentEnabled;
1527: }
1528:
1529: /**
1530: * Gets the component that the location of the popup relative to.
1531: *
1532: * @return the component that the location of the popup relative to.
1533: */
1534: public Component getPopupLocationRelativeTo() {
1535: return _popupLocationRelativeTo;
1536: }
1537:
1538: /**
1539: * Sets the location of the popup relative to the specified component. Then based on the value of
1540: * {@link #getPopupLocation()}. If you never set, we will use the searchable component or its scroll pane (if exists)
1541: * as the popupLocationRelativeTo component.
1542: *
1543: * @param popupLocationRelativeTo
1544: */
1545: public void setPopupLocationRelativeTo(
1546: Component popupLocationRelativeTo) {
1547: _popupLocationRelativeTo = popupLocationRelativeTo;
1548: }
1549: }
|