All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jidesoft.swing.TextComponentSearchable Maven / Gradle / Ivy

There is a newer version: 3.6.18
Show newest version
/*
 * @(#)TextComponentSearchable.java 10/11/2005
 *
 * Copyright 2002 - 2005 JIDE Software Inc. All rights reserved.
 */
package com.jidesoft.swing;

import com.jidesoft.swing.event.SearchableEvent;

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Iterator;

/**
 * TextComponentSearchable is an concrete implementation of {@link Searchable} that enables the search
 * function in JTextComponent. 

It's very simple to use it. Assuming you have a JTextComponent, all you need to do is * to call *

 * JTextComponent textComponent = ....;
 * TextComponentSearchable searchable = new TextComponentSearchable(textComponent);
 * 
* Now the JTextComponent will have the search function. *

* There is very little customization you need to do to ListSearchable. The only thing you might need is when the * element in the JTextComponent needs a special conversion to convert to string. If so, you can override * convertElementToString() to provide you own algorithm to do the conversion. *

 * JTextComponent textComponent = ....;
 * TextComponentSearchable searchable = new ListSearchable(textComponent) {
 *      protected String convertElementToString(Object object) {
 *          ...
 *      }
 * 

* protected boolean isActivateKey(KeyEvent e) { // change to a different activation key * return (e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_F && * (KeyEvent.CTRL_MASK & e.getModifiers()) != 0); * } * }; *

*

* Additional customization can be done on the base Searchable class such as background and foreground color, * keystrokes, case sensitivity. TextComponentSearchable also has a special attribute called highlightColor. You can * change it using {@link #setHighlightColor(java.awt.Color)}. *

* Due to the special case of JTextComponent, the searching doesn't support wild card '*' or '?' as in other * Searchables. The other difference is JTextComponent will keep the highlights after search popup hides. If you want to * hide the highlights, just press ESC again (the first ESC will hide popup; the second ESC will hide all highlights if * any). */ public class TextComponentSearchable extends Searchable implements DocumentListener, PropertyChangeListener { private Highlighter.HighlightPainter _highlightPainter; private static final Color DEFAULT_HIGHLIGHT_COLOR = new Color(204, 204, 255); private Color _highlightColor = null; private int _selectedIndex = -1; private HighlighCache _highlighCache; public TextComponentSearchable(JTextComponent textComponent) { super(textComponent); _highlighCache = new HighlighCache(); installHighlightsRemover(); setHighlightColor(DEFAULT_HIGHLIGHT_COLOR); } /** * Uninstalls the handler for ESC key to remove all highlights */ public void uninstallHighlightsRemover() { DelegateAction.restoreAction(_component, JComponent.WHEN_FOCUSED, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0)); } /** * Installs the handler for ESC key to remove all highlights */ public void installHighlightsRemover() { DelegateAction.replaceAction(_component, JComponent.WHEN_FOCUSED, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), new DelegateAction() { private static final long serialVersionUID = 8572103995404313032L; @Override public boolean isDelegateEnabled() { if (_component instanceof JTextComponent) { Iterator highlights = _highlighCache.getAllHighlights(); if (!highlights.hasNext()) { return false; } } return super.isDelegateEnabled(); } @Override public boolean delegateActionPerformed(ActionEvent e) { if (_component instanceof JTextComponent) { Iterator highlights = _highlighCache.getAllHighlights(); if (!highlights.hasNext()) { return false; } } removeAllHighlights(); return true; } }); } @Override public void installListeners() { super.installListeners(); if (_component instanceof JTextComponent) { ((JTextComponent) _component).getDocument().addDocumentListener(this); _component.addPropertyChangeListener("document", this); } } @Override public void uninstallListeners() { super.uninstallListeners(); if (_component instanceof JTextComponent) { ((JTextComponent) _component).getDocument().removeDocumentListener(this); _component.removePropertyChangeListener("document", this); } } @Override protected void setSelectedIndex(int index, boolean incremental) { if (_component instanceof JTextComponent) { if (index == -1) { removeAllHighlights(); _selectedIndex = -1; return; } if (!incremental) { removeAllHighlights(); } String text = getSearchingText(); try { addHighlight(index, text, incremental); } catch (BadLocationException e) { e.printStackTrace(); } } } /** * Adds highlight to text component at specified index and text. * * @param index the index of the text to be highlighted * @param text the text to be highlighted * @param incremental if this is an incremental adding highlight * @throws BadLocationException */ protected void addHighlight(final int index, final String text, boolean incremental) throws BadLocationException { if (_component instanceof JTextComponent) { final JTextComponent textComponent = ((JTextComponent) _component); Object obj = textComponent.getHighlighter().addHighlight(index, index + text.length(), _highlightPainter); _highlighCache.addHighlight(obj); _selectedIndex = index; if (!incremental) { Runnable runnable = new Runnable() { public void run() { scrollTextVisible(textComponent, index, text.length()); } }; SwingUtilities.invokeLater(runnable); } } } private void scrollTextVisible(JTextComponent textComponent, int index, int length) { // scroll highlight visible if (index != -1) { // Scroll the component if needed so that the composed text // becomes visible. try { Rectangle begin = textComponent.modelToView(index); if (begin == null) { return; } Rectangle end = textComponent.modelToView(index + length); if (end == null) { return; } Rectangle bounds = _component.getVisibleRect(); if (begin.x <= bounds.width) { // make sure if scroll back to the beginning as long as selected rect is visible begin.width = end.x; begin.x = 0; } else { begin.width = end.x - begin.x; } textComponent.scrollRectToVisible(begin); } catch (BadLocationException ble) { } } } /** * Removes all highlights from the text component. */ protected void removeAllHighlights() { if (_component instanceof JTextComponent) { Iterator itor = _highlighCache.getAllHighlights(); while (itor.hasNext()) { Object o = itor.next(); ((JTextComponent) _component).getHighlighter().removeHighlight(o); } _highlighCache.removeAllHighlights(); } } @Override protected int getSelectedIndex() { if (_component instanceof JTextComponent) { return _selectedIndex; } return 0; } @Override protected Object getElementAt(int index) { String text = getSearchingText(); if (text != null) { if (_component instanceof JTextComponent) { int endIndex = index + text.length(); int elementCount = getElementCount(); if (endIndex > elementCount) { endIndex = getElementCount(); } try { return ((JTextComponent) _component).getDocument().getText(index, endIndex - index + 1); } catch (BadLocationException e) { return null; } } } return ""; } @Override protected int getElementCount() { if (_component instanceof JTextComponent) { return ((JTextComponent) _component).getDocument().getLength(); } return 0; } /** * Converts the element in JTextComponent to string. The returned value will be the toString() of * whatever element that returned from list.getModel().getElementAt(i). * * @param object * @return the string representing the element in the JTextComponent. */ @Override protected String convertElementToString(Object object) { if (object != null) { return object.toString(); } else { return ""; } } public void propertyChange(PropertyChangeEvent evt) { if (isProcessModelChangeEvent()) { hidePopup(); _text = null; if (evt.getOldValue() instanceof Document) { ((Document) evt.getNewValue()).removeDocumentListener(this); } if (evt.getNewValue() instanceof Document) { ((Document) evt.getNewValue()).addDocumentListener(this); } fireSearchableEvent(new SearchableEvent(this, SearchableEvent.SEARCHABLE_MODEL_CHANGE)); } } public void insertUpdate(DocumentEvent e) { if (isProcessModelChangeEvent()) { hidePopup(); _text = null; fireSearchableEvent(new SearchableEvent(this, SearchableEvent.SEARCHABLE_MODEL_CHANGE)); } } public void removeUpdate(DocumentEvent e) { if (isProcessModelChangeEvent()) { hidePopup(); _text = null; fireSearchableEvent(new SearchableEvent(this, SearchableEvent.SEARCHABLE_MODEL_CHANGE)); } } public void changedUpdate(DocumentEvent e) { if (isProcessModelChangeEvent()) { hidePopup(); _text = null; fireSearchableEvent(new SearchableEvent(this, SearchableEvent.SEARCHABLE_MODEL_CHANGE)); } } @Override protected boolean isActivateKey(KeyEvent e) { if (_component instanceof JTextComponent && ((JTextComponent) _component).isEditable()) { return (e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_F && JideSwingUtilities.isMenuShortcutKeyDown(e)); } else { return super.isActivateKey(e); } } /** * Gets the highlight color. * * @return the highlight color. */ public Color getHighlightColor() { if (_highlightColor != null) { return _highlightColor; } else { return DEFAULT_HIGHLIGHT_COLOR; } } /** * Changes the highlight color. * * @param highlightColor */ public void setHighlightColor(Color highlightColor) { _highlightColor = highlightColor; _highlightPainter = new DefaultHighlighter.DefaultHighlightPainter(_highlightColor); } @Override public int findLast(String s) { if (_component instanceof JTextComponent) { String text = getDocumentText(); if (isCaseSensitive()) { return text.lastIndexOf(s); } else { return lastIndexOf(text, s, text.length()); } } else { return super.findLast(s); } } private String _text = null; /** * Gets the text from Document. * * @return the text of this JTextComponent. It used Document to get the text. */ private String getDocumentText() { if (_text == null) { Document document = ((JTextComponent) _component).getDocument(); try { String text = document.getText(0, document.getLength()); _text = text; } catch (BadLocationException e) { return ""; } } return _text; } @Override public int findFirst(String s) { if (_component instanceof JTextComponent) { String text = getDocumentText(); if (isCaseSensitive()) { return text.indexOf(s); } else { return indexOf(text, s, 0); } } else { return super.findFirst(s); } } static int lastIndexOf(String source, String target, int fromIndex) { int sourceCount = source.length(); int targetCount = target.length(); int rightIndex = sourceCount - targetCount; if (fromIndex < 0) { return -1; } if (fromIndex > rightIndex) { fromIndex = rightIndex; } /* Empty string always matches. */ if (targetCount == 0) { return fromIndex; } char[] lowerTarget = target.toLowerCase().toCharArray(); char[] upperTarget = target.toUpperCase().toCharArray(); int strLastIndex = targetCount - 1; int min = targetCount - 1; int i = min + fromIndex; while (i >= min) { while (i >= min && source.charAt(i) != lowerTarget[strLastIndex] && source.charAt(i) != upperTarget[strLastIndex]) { i--; } if (i < min) { break; } int j = i - 1; int start = j - (targetCount - 1); int k = strLastIndex - 1; while (j > start) { char ch = source.charAt(j); if (ch != lowerTarget[k] && ch != upperTarget[k]) { i--; break; } j--; k--; } if (j <= start) { return start + 1; } } return -1; } private static int indexOf(String source, String target, int fromIndex) { int sourceCount = source.length(); int targetCount = target.length(); if (fromIndex >= sourceCount) { return (targetCount == 0 ? sourceCount : -1); } if (fromIndex < 0) { fromIndex = 0; } if (targetCount == 0) { return fromIndex; } char[] lowerTarget = target.toLowerCase().toCharArray(); char[] upperTarget = target.toUpperCase().toCharArray(); int max = sourceCount - targetCount; for (int i = fromIndex; i <= max; i++) { /* Look for first character. */ char c = source.charAt(i); if (c != lowerTarget[0] && c != upperTarget[0]) { i++; while (i <= max && source.charAt(i) != lowerTarget[0] && source.charAt(i) != upperTarget[0]) { i++; } } /* Found first character, now look at the rest of v2 */ if (i <= max) { int j = i + 1; int end = j + targetCount - 1; for (int k = 1; j < end; j++) { char ch = source.charAt(j); if (ch != lowerTarget[k] && ch != upperTarget[k]) { break; } k++; } if (j == end) { /* Found whole string. */ return i; } } } return -1; } @Override public int findFromCursor(String s) { if (isCountMatch()) { String text = getDocumentText(); int oldIndex; int newIndex = 0; _matchCount = -1; do { oldIndex = newIndex; newIndex = (isCaseSensitive() ? text.indexOf(s, oldIndex) : indexOf(text, s, oldIndex)) + 1; _matchCount++; } while (newIndex > oldIndex); } if (isReverseOrder()) { return reverseFindFromCursor(s); } if (_component instanceof JTextComponent) { String text = getDocumentText(); int selectedIndex = (getCursor() != -1 ? getCursor() : getSelectedIndex()); if (selectedIndex < 0) selectedIndex = 0; int count = getElementCount(); if (count == 0) return s.length() > 0 ? -1 : 0; // find from cursor int found = isCaseSensitive() ? text.indexOf(s, selectedIndex) : indexOf(text, s, selectedIndex); // if not found, start over from the beginning if (found == -1) { found = isCaseSensitive() ? text.indexOf(s, 0) : indexOf(text, s, 0); if (found >= selectedIndex) { found = -1; } } return found; } else { return super.findFromCursor(s); } } @Override public int reverseFindFromCursor(String s) { if (!isReverseOrder()) { return findFromCursor(s); } if (_component instanceof JTextComponent) { String text = getDocumentText(); int selectedIndex = (getCursor() != -1 ? getCursor() : getSelectedIndex()); if (selectedIndex < 0) selectedIndex = 0; int count = getElementCount(); if (count == 0) return s.length() > 0 ? -1 : 0; // find from cursor int found = isCaseSensitive() ? text.lastIndexOf(s, selectedIndex) : lastIndexOf(text, s, selectedIndex); // if not found, start over from the end if (found == -1) { found = isCaseSensitive() ? text.lastIndexOf(s, text.length() - 1) : lastIndexOf(text, s, text.length() - 1); if (found <= selectedIndex) { found = -1; } } return found; } else { return super.findFromCursor(s); } } @Override public int findNext(String s) { if (_component instanceof JTextComponent) { String text = getDocumentText(); int selectedIndex = (getCursor() != -1 ? getCursor() : getSelectedIndex()); if (selectedIndex < 0) selectedIndex = 0; int count = getElementCount(); if (count == 0) return s.length() > 0 ? -1 : 0; // find from cursor int found = isCaseSensitive() ? text.indexOf(s, selectedIndex + 1) : indexOf(text, s, selectedIndex + 1); // if not found, start over from the beginning if (found == -1 && isRepeats()) { found = isCaseSensitive() ? text.indexOf(s, 0) : indexOf(text, s, 0); if (found > selectedIndex) { found = -1; } } return found; } else { return super.findNext(s); } } @Override public int findPrevious(String s) { if (_component instanceof JTextComponent) { String text = getDocumentText(); int selectedIndex = (getCursor() != -1 ? getCursor() : getSelectedIndex()); if (selectedIndex < 0) selectedIndex = 0; int count = getElementCount(); if (count == 0) return s.length() > 0 ? -1 : 0; // find from cursor int found = isCaseSensitive() ? text.lastIndexOf(s, selectedIndex - 1) : lastIndexOf(text, s, selectedIndex - 1); // if not found, start over from the beginning if (found == -1 && isRepeats()) { found = isCaseSensitive() ? text.lastIndexOf(s, count - 1) : lastIndexOf(text, s, count - 1); if (found < selectedIndex) { found = -1; } } return found; } else { return super.findPrevious(s); } } private class HighlighCache extends HashMap { public void addHighlight(Object obj) { put(obj, null); } public void removeHighlight(Object obj) { remove(obj); } public Iterator getAllHighlights() { return keySet().iterator(); } public void removeAllHighlights() { clear(); } } @Override public void hidePopup() { super.hidePopup(); _selectedIndex = -1; } @Override protected void searchingTextEmpty() { setSelectedIndex(-1, false); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy