| /* DefaultCaret.java -- | |
| Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc. | |
| This file is part of GNU Classpath. | |
| GNU Classpath is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 2, or (at your option) | |
| any later version. | |
| GNU Classpath is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with GNU Classpath; see the file COPYING. If not, write to the | |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
| 02110-1301 USA. | |
| Linking this library statically or dynamically with other modules is | |
| making a combined work based on this library. Thus, the terms and | |
| conditions of the GNU General Public License cover the whole | |
| combination. | |
| As a special exception, the copyright holders of this library give you | |
| permission to link this library with independent modules to produce an | |
| executable, regardless of the license terms of these independent | |
| modules, and to copy and distribute the resulting executable under | |
| terms of your choice, provided that you also meet, for each linked | |
| independent module, the terms and conditions of the license of that | |
| module. An independent module is a module which is not derived from | |
| or based on this library. If you modify this library, you may extend | |
| this exception to your version of the library, but you are not | |
| obligated to do so. If you do not wish to do so, delete this | |
| exception statement from your version. */ | |
| package javax.swing.text; | |
| import java.awt.Graphics; | |
| import java.awt.Point; | |
| import java.awt.Rectangle; | |
| import java.awt.event.ActionEvent; | |
| import java.awt.event.ActionListener; | |
| import java.awt.event.FocusEvent; | |
| import java.awt.event.FocusListener; | |
| import java.awt.event.MouseEvent; | |
| import java.awt.event.MouseListener; | |
| import java.awt.event.MouseMotionListener; | |
| import java.beans.PropertyChangeEvent; | |
| import java.beans.PropertyChangeListener; | |
| import java.util.EventListener; | |
| import javax.swing.JComponent; | |
| import javax.swing.SwingUtilities; | |
| import javax.swing.Timer; | |
| import javax.swing.event.ChangeEvent; | |
| import javax.swing.event.ChangeListener; | |
| import javax.swing.event.DocumentEvent; | |
| import javax.swing.event.DocumentListener; | |
| import javax.swing.event.EventListenerList; | |
| import javax.swing.text.Position.Bias; | |
| /** | |
| * The default implementation of the {@link Caret} interface. | |
| * | |
| * @author original author unknown | |
| * @author Roman Kennke (roman@kennke.org) | |
| */ | |
| public class DefaultCaret extends Rectangle | |
| implements Caret, FocusListener, MouseListener, MouseMotionListener | |
| { | |
| /** A text component in the current VM which currently has a | |
| * text selection or <code>null</code>. | |
| */ | |
| static JTextComponent componentWithSelection; | |
| /** An implementation of NavigationFilter.FilterBypass which delegates | |
| * to the corresponding methods of the <code>DefaultCaret</code>. | |
| * | |
| * @author Robert Schuster (robertschuster@fsfe.org) | |
| */ | |
| class Bypass extends NavigationFilter.FilterBypass | |
| { | |
| public Caret getCaret() | |
| { | |
| return DefaultCaret.this; | |
| } | |
| public void moveDot(int dot, Bias bias) | |
| { | |
| DefaultCaret.this.moveDotImpl(dot); | |
| } | |
| public void setDot(int dot, Bias bias) | |
| { | |
| DefaultCaret.this.setDotImpl(dot); | |
| } | |
| } | |
| /** | |
| * Controls the blinking of the caret. | |
| * | |
| * @author Roman Kennke (kennke@aicas.com) | |
| * @author Audrius Meskauskas (AudriusA@Bioinformatics.org) | |
| */ | |
| private class BlinkTimerListener implements ActionListener | |
| { | |
| /** | |
| * Forces the next event to be ignored. The next event should be ignored | |
| * if we force the caret to appear. We do not know how long will it take | |
| * to fire the comming event; this may be near immediately. Better to leave | |
| * the caret visible one iteration longer. | |
| */ | |
| boolean ignoreNextEvent; | |
| /** | |
| * Receives notification when the blink timer fires and updates the visible | |
| * state of the caret. | |
| * | |
| * @param event the action event | |
| */ | |
| public void actionPerformed(ActionEvent event) | |
| { | |
| if (ignoreNextEvent) | |
| ignoreNextEvent = false; | |
| else | |
| { | |
| visible = !visible; | |
| repaint(); | |
| } | |
| } | |
| } | |
| /** | |
| * Listens for changes in the text component's document and updates the | |
| * caret accordingly. | |
| * | |
| * @author Roman Kennke (kennke@aicas.com) | |
| */ | |
| private class DocumentHandler implements DocumentListener | |
| { | |
| /** | |
| * Receives notification that some text attributes have changed. No action | |
| * is taken here. | |
| * | |
| * @param event the document event | |
| */ | |
| public void changedUpdate(DocumentEvent event) | |
| { | |
| // Nothing to do here. | |
| } | |
| /** | |
| * Receives notification that some text has been inserted from the text | |
| * component. The caret is moved forward accordingly. | |
| * | |
| * @param event the document event | |
| */ | |
| public void insertUpdate(DocumentEvent event) | |
| { | |
| if (policy == ALWAYS_UPDATE || | |
| (SwingUtilities.isEventDispatchThread() && | |
| policy == UPDATE_WHEN_ON_EDT)) | |
| { | |
| int dot = getDot(); | |
| setDot(dot + event.getLength()); | |
| } | |
| } | |
| /** | |
| * Receives notification that some text has been removed into the text | |
| * component. The caret is moved backwards accordingly. | |
| * | |
| * @param event the document event | |
| */ | |
| public void removeUpdate(DocumentEvent event) | |
| { | |
| if (policy == ALWAYS_UPDATE | |
| || (SwingUtilities.isEventDispatchThread() | |
| && policy == UPDATE_WHEN_ON_EDT)) | |
| { | |
| int dot = getDot(); | |
| setDot(dot - event.getLength()); | |
| } | |
| else if (policy == NEVER_UPDATE | |
| || (! SwingUtilities.isEventDispatchThread() | |
| && policy == UPDATE_WHEN_ON_EDT)) | |
| { | |
| int docLength = event.getDocument().getLength(); | |
| if (getDot() > docLength) | |
| setDot(docLength); | |
| } | |
| } | |
| } | |
| /** | |
| * Listens for property changes on the text document. This is used to add and | |
| * remove our document listener, if the document of the text component has | |
| * changed. | |
| * | |
| * @author Roman Kennke (kennke@aicas.com) | |
| */ | |
| private class PropertyChangeHandler implements PropertyChangeListener | |
| { | |
| /** | |
| * Receives notification when a property has changed on the text component. | |
| * This adds/removes our document listener from the text component's | |
| * document when the document changes. | |
| * | |
| * @param e the property change event | |
| */ | |
| public void propertyChange(PropertyChangeEvent e) | |
| { | |
| String name = e.getPropertyName(); | |
| if (name.equals("document")) | |
| { | |
| Document oldDoc = (Document) e.getOldValue(); | |
| if (oldDoc != null) | |
| oldDoc.removeDocumentListener(documentListener); | |
| Document newDoc = (Document) e.getNewValue(); | |
| if (newDoc != null) | |
| newDoc.addDocumentListener(documentListener); | |
| } | |
| else if (name.equals("editable")) | |
| { | |
| active = (((Boolean) e.getNewValue()).booleanValue() | |
| && textComponent.isEnabled()); | |
| } | |
| else if (name.equals("enabled")) | |
| { | |
| active = (((Boolean) e.getNewValue()).booleanValue() | |
| && textComponent.isEditable()); | |
| } | |
| } | |
| } | |
| /** The serialization UID (compatible with JDK1.5). */ | |
| private static final long serialVersionUID = 4325555698756477346L; | |
| /** | |
| * Indicates the Caret position should always be updated after Document | |
| * changes even if the updates are not performed on the Event Dispatching | |
| * thread. | |
| * | |
| * @since 1.5 | |
| */ | |
| public static final int ALWAYS_UPDATE = 2; | |
| /** | |
| * Indicates the Caret position should not be changed unless the Document | |
| * length becomes less than the Caret position, in which case the Caret | |
| * is moved to the end of the Document. | |
| * | |
| * @since 1.5 | |
| */ | |
| public static final int NEVER_UPDATE = 1; | |
| /** | |
| * Indicates the Caret position should be updated only if Document changes | |
| * are made on the Event Dispatcher thread. | |
| * | |
| * @since 1.5 | |
| */ | |
| public static final int UPDATE_WHEN_ON_EDT = 0; | |
| /** Keeps track of the current update policy **/ | |
| int policy = UPDATE_WHEN_ON_EDT; | |
| /** | |
| * The <code>ChangeEvent</code> that is fired by {@link #fireStateChanged()}. | |
| */ | |
| protected ChangeEvent changeEvent = new ChangeEvent(this); | |
| /** | |
| * Stores all registered event listeners. | |
| */ | |
| protected EventListenerList listenerList = new EventListenerList(); | |
| /** | |
| * Our document listener. | |
| */ | |
| DocumentListener documentListener; | |
| /** | |
| * Our property listener. | |
| */ | |
| PropertyChangeListener propertyChangeListener; | |
| /** | |
| * The text component in which this caret is installed. | |
| * | |
| * (Package private to avoid synthetic accessor method.) | |
| */ | |
| JTextComponent textComponent; | |
| /** | |
| * Indicates if the selection should be visible or not. | |
| */ | |
| private boolean selectionVisible = true; | |
| /** | |
| * The blink rate of this <code>Caret</code>. | |
| */ | |
| private int blinkRate = 500; | |
| /** | |
| * The current dot position. | |
| */ | |
| private int dot = 0; | |
| /** | |
| * The current mark position. | |
| */ | |
| private int mark = 0; | |
| /** | |
| * The current visual caret position. | |
| */ | |
| private Point magicCaretPosition = null; | |
| /** | |
| * Indicates if this <code>Caret</code> is currently visible or not. This is | |
| * package private to avoid an accessor method. | |
| */ | |
| boolean visible = false; | |
| /** Indicates whether the text component where the caret is installed is | |
| * editable and enabled. If either of these properties is <code>false</code> | |
| * the caret is not drawn. | |
| */ | |
| boolean active = true; | |
| /** | |
| * The current highlight entry. | |
| */ | |
| private Object highlightEntry; | |
| private Timer blinkTimer; | |
| private BlinkTimerListener blinkListener; | |
| /** | |
| * A <code>NavigationFilter.FilterBypass</code> instance which | |
| * is provided to the a <code>NavigationFilter</code> to | |
| * unconditionally set or move the caret. | |
| */ | |
| NavigationFilter.FilterBypass bypass; | |
| /** | |
| * Creates a new <code>DefaultCaret</code> instance. | |
| */ | |
| public DefaultCaret() | |
| { | |
| // Nothing to do here. | |
| } | |
| /** Returns the caret's <code>NavigationFilter.FilterBypass</code> instance | |
| * and creates it if it does not yet exist. | |
| * | |
| * @return The caret's <code>NavigationFilter.FilterBypass</code> instance. | |
| */ | |
| private NavigationFilter.FilterBypass getBypass() | |
| { | |
| return (bypass == null) ? bypass = new Bypass() : bypass; | |
| } | |
| /** | |
| * Sets the Caret update policy. | |
| * | |
| * @param policy the new policy. Valid values are: | |
| * ALWAYS_UPDATE: always update the Caret position, even when Document | |
| * updates don't occur on the Event Dispatcher thread. | |
| * NEVER_UPDATE: don't update the Caret position unless the Document | |
| * length becomes less than the Caret position (then update the | |
| * Caret to the end of the Document). | |
| * UPDATE_WHEN_ON_EDT: update the Caret position when the | |
| * Document updates occur on the Event Dispatcher thread. This is the | |
| * default. | |
| * | |
| * @since 1.5 | |
| * @throws IllegalArgumentException if policy is not one of the above. | |
| */ | |
| public void setUpdatePolicy (int policy) | |
| { | |
| if (policy != ALWAYS_UPDATE && policy != NEVER_UPDATE | |
| && policy != UPDATE_WHEN_ON_EDT) | |
| throw new | |
| IllegalArgumentException | |
| ("policy must be ALWAYS_UPDATE, NEVER__UPDATE, or UPDATE_WHEN_ON_EDT"); | |
| this.policy = policy; | |
| } | |
| /** | |
| * Gets the caret update policy. | |
| * | |
| * @return the caret update policy. | |
| * @since 1.5 | |
| */ | |
| public int getUpdatePolicy () | |
| { | |
| return policy; | |
| } | |
| /** | |
| * Moves the caret position when the mouse is dragged over the text | |
| * component, modifying the selectiony. | |
| * | |
| * <p>When the text component where the caret is installed is disabled, | |
| * the selection is not change but you can still scroll the text and | |
| * update the caret's location.</p> | |
| * | |
| * @param event the <code>MouseEvent</code> describing the drag operation | |
| */ | |
| public void mouseDragged(MouseEvent event) | |
| { | |
| if (event.getButton() == MouseEvent.BUTTON1) | |
| { | |
| if (textComponent.isEnabled()) | |
| moveCaret(event); | |
| else | |
| positionCaret(event); | |
| } | |
| } | |
| /** | |
| * Indicates a mouse movement over the text component. Does nothing here. | |
| * | |
| * @param event the <code>MouseEvent</code> describing the mouse operation | |
| */ | |
| public void mouseMoved(MouseEvent event) | |
| { | |
| // Nothing to do here. | |
| } | |
| /** | |
| * When the click is received from Button 1 then the following actions | |
| * are performed here: | |
| * | |
| * <ul> | |
| * <li>If we receive a double click, the caret position (dot) is set | |
| * to the position associated to the mouse click and the word at | |
| * this location is selected. If there is no word at the pointer | |
| * the gap is selected instead.</li> | |
| * <li>If we receive a triple click, the caret position (dot) is set | |
| * to the position associated to the mouse click and the line at | |
| * this location is selected.</li> | |
| * </ul> | |
| * | |
| * @param event the <code>MouseEvent</code> describing the click operation | |
| */ | |
| public void mouseClicked(MouseEvent event) | |
| { | |
| // Do not modify selection if component is disabled. | |
| if (!textComponent.isEnabled()) | |
| return; | |
| int count = event.getClickCount(); | |
| if (event.getButton() == MouseEvent.BUTTON1 && count >= 2) | |
| { | |
| int newDot = getComponent().viewToModel(event.getPoint()); | |
| JTextComponent t = getComponent(); | |
| try | |
| { | |
| if (count == 3) | |
| { | |
| setDot(Utilities.getRowStart(t, newDot)); | |
| moveDot( Utilities.getRowEnd(t, newDot)); | |
| } | |
| else | |
| { | |
| int wordStart = Utilities.getWordStart(t, newDot); | |
| // When the mouse points at the offset of the first character | |
| // in a word Utilities().getPreviousWord will not return that | |
| // word but we want to select that. We have to use | |
| // Utilities.getWordStart() to get it. | |
| if (newDot == wordStart) | |
| { | |
| setDot(wordStart); | |
| moveDot(Utilities.getWordEnd(t, wordStart)); | |
| } | |
| else | |
| { | |
| int nextWord = Utilities.getNextWord(t, newDot); | |
| int previousWord = Utilities.getPreviousWord(t, newDot); | |
| int previousWordEnd = Utilities.getWordEnd(t, previousWord); | |
| // If the user clicked in the space between two words, | |
| // then select the space. | |
| if (newDot >= previousWordEnd && newDot <= nextWord) | |
| { | |
| setDot(previousWordEnd); | |
| moveDot(nextWord); | |
| } | |
| // Otherwise select the word under the mouse pointer. | |
| else | |
| { | |
| setDot(previousWord); | |
| moveDot(previousWordEnd); | |
| } | |
| } | |
| } | |
| } | |
| catch(BadLocationException ble) | |
| { | |
| // TODO: Swallowing ok here? | |
| } | |
| } | |
| } | |
| /** | |
| * Indicates that the mouse has entered the text component. Nothing is done | |
| * here. | |
| * | |
| * @param event the <code>MouseEvent</code> describing the mouse operation | |
| */ | |
| public void mouseEntered(MouseEvent event) | |
| { | |
| // Nothing to do here. | |
| } | |
| /** | |
| * Indicates that the mouse has exited the text component. Nothing is done | |
| * here. | |
| * | |
| * @param event the <code>MouseEvent</code> describing the mouse operation | |
| */ | |
| public void mouseExited(MouseEvent event) | |
| { | |
| // Nothing to do here. | |
| } | |
| /** | |
| * If the button 1 is pressed, the caret position is updated to the | |
| * position of the mouse click and the text component requests the input | |
| * focus if it is enabled. If the SHIFT key is held down, the caret will | |
| * be moved, which might select the text between the old and new location. | |
| * | |
| * @param event the <code>MouseEvent</code> describing the press operation | |
| */ | |
| public void mousePressed(MouseEvent event) | |
| { | |
| // The implementation assumes that consuming the event makes the AWT event | |
| // mechanism forget about this event instance and not transfer focus. | |
| // By observing how the RI reacts the following behavior has been | |
| // implemented (in regard to text components): | |
| // - a left-click moves the caret | |
| // - a left-click when shift is held down expands the selection | |
| // - a right-click or click with any additional mouse button | |
| // on a text component is ignored | |
| // - a middle-click positions the caret and pastes the clipboard | |
| // contents. | |
| // - a middle-click when shift is held down is ignored | |
| if (SwingUtilities.isLeftMouseButton(event)) | |
| { | |
| // Handle the caret. | |
| if (event.isShiftDown() && getDot() != -1) | |
| { | |
| moveCaret(event); | |
| } | |
| else | |
| { | |
| positionCaret(event); | |
| } | |
| // Handle the focus. | |
| if (textComponent != null && textComponent.isEnabled() | |
| && textComponent.isRequestFocusEnabled()) | |
| { | |
| textComponent.requestFocus(); | |
| } | |
| // TODO: Handle double click for selecting words. | |
| } | |
| else if(event.getButton() == MouseEvent.BUTTON2) | |
| { | |
| // Special handling for X11-style pasting. | |
| if (! event.isShiftDown()) | |
| { | |
| positionCaret(event); | |
| textComponent.paste(); | |
| } | |
| } | |
| } | |
| /** | |
| * Indicates that a mouse button has been released on the text component. | |
| * Nothing is done here. | |
| * | |
| * @param event the <code>MouseEvent</code> describing the mouse operation | |
| */ | |
| public void mouseReleased(MouseEvent event) | |
| { | |
| // Nothing to do here. | |
| } | |
| /** | |
| * Sets the caret to <code>visible</code> if the text component is editable. | |
| * | |
| * @param event the <code>FocusEvent</code> | |
| */ | |
| public void focusGained(FocusEvent event) | |
| { | |
| if (textComponent.isEditable()) | |
| { | |
| setVisible(true); | |
| updateTimerStatus(); | |
| } | |
| } | |
| /** | |
| * Sets the caret to <code>invisible</code>. | |
| * | |
| * @param event the <code>FocusEvent</code> | |
| */ | |
| public void focusLost(FocusEvent event) | |
| { | |
| if (textComponent.isEditable() && event.isTemporary() == false) | |
| { | |
| setVisible(false); | |
| // Stop the blinker, if running. | |
| if (blinkTimer != null && blinkTimer.isRunning()) | |
| blinkTimer.stop(); | |
| } | |
| } | |
| /** | |
| * Install (if not present) and start the timer, if the caret must blink. The | |
| * caret does not blink if it is invisible, or the component is disabled or | |
| * not editable. | |
| */ | |
| private void updateTimerStatus() | |
| { | |
| if (textComponent.isEnabled() && textComponent.isEditable()) | |
| { | |
| if (blinkTimer == null) | |
| initBlinkTimer(); | |
| if (!blinkTimer.isRunning()) | |
| blinkTimer.start(); | |
| } | |
| else | |
| { | |
| if (blinkTimer != null) | |
| blinkTimer.stop(); | |
| } | |
| } | |
| /** | |
| * Moves the caret to the position specified in the <code>MouseEvent</code>. | |
| * This will cause a selection if the dot and mark are different. | |
| * | |
| * @param event the <code>MouseEvent</code> from which to fetch the position | |
| */ | |
| protected void moveCaret(MouseEvent event) | |
| { | |
| int newDot = getComponent().viewToModel(event.getPoint()); | |
| moveDot(newDot); | |
| } | |
| /** | |
| * Repositions the caret to the position specified in the | |
| * <code>MouseEvent</code>. | |
| * | |
| * @param event the <code>MouseEvent</code> from which to fetch the position | |
| */ | |
| protected void positionCaret(MouseEvent event) | |
| { | |
| int newDot = getComponent().viewToModel(event.getPoint()); | |
| setDot(newDot); | |
| } | |
| /** | |
| * Deinstalls this <code>Caret</code> from the specified | |
| * <code>JTextComponent</code>. This removes any listeners that have been | |
| * registered by this <code>Caret</code>. | |
| * | |
| * @param c the text component from which to install this caret | |
| */ | |
| public void deinstall(JTextComponent c) | |
| { | |
| textComponent.removeFocusListener(this); | |
| textComponent.removeMouseListener(this); | |
| textComponent.removeMouseMotionListener(this); | |
| textComponent.getDocument().removeDocumentListener(documentListener); | |
| documentListener = null; | |
| textComponent.removePropertyChangeListener(propertyChangeListener); | |
| propertyChangeListener = null; | |
| textComponent = null; | |
| // Deinstall blink timer if present. | |
| if (blinkTimer != null) | |
| blinkTimer.stop(); | |
| blinkTimer = null; | |
| } | |
| /** | |
| * Installs this <code>Caret</code> on the specified | |
| * <code>JTextComponent</code>. This registers a couple of listeners | |
| * on the text component. | |
| * | |
| * @param c the text component on which to install this caret | |
| */ | |
| public void install(JTextComponent c) | |
| { | |
| textComponent = c; | |
| textComponent.addFocusListener(this); | |
| textComponent.addMouseListener(this); | |
| textComponent.addMouseMotionListener(this); | |
| propertyChangeListener = new PropertyChangeHandler(); | |
| textComponent.addPropertyChangeListener(propertyChangeListener); | |
| documentListener = new DocumentHandler(); | |
| Document doc = textComponent.getDocument(); | |
| if (doc != null) | |
| doc.addDocumentListener(documentListener); | |
| active = textComponent.isEditable() && textComponent.isEnabled(); | |
| repaint(); | |
| } | |
| /** | |
| * Sets the current visual position of this <code>Caret</code>. | |
| * | |
| * @param p the Point to use for the saved location. May be <code>null</code> | |
| * to indicate that there is no visual location | |
| */ | |
| public void setMagicCaretPosition(Point p) | |
| { | |
| magicCaretPosition = p; | |
| } | |
| /** | |
| * Returns the current visual position of this <code>Caret</code>. | |
| * | |
| * @return the current visual position of this <code>Caret</code> | |
| * | |
| * @see #setMagicCaretPosition | |
| */ | |
| public Point getMagicCaretPosition() | |
| { | |
| return magicCaretPosition; | |
| } | |
| /** | |
| * Returns the current position of the <code>mark</code>. The | |
| * <code>mark</code> marks the location in the <code>Document</code> that | |
| * is the end of a selection. If there is no selection, the <code>mark</code> | |
| * is the same as the <code>dot</code>. | |
| * | |
| * @return the current position of the mark | |
| */ | |
| public int getMark() | |
| { | |
| return mark; | |
| } | |
| private void clearHighlight() | |
| { | |
| Highlighter highlighter = textComponent.getHighlighter(); | |
| if (highlighter == null) | |
| return; | |
| if (selectionVisible) | |
| { | |
| try | |
| { | |
| if (highlightEntry != null) | |
| highlighter.changeHighlight(highlightEntry, 0, 0); | |
| // Free the global variable which stores the text component with an active | |
| // selection. | |
| if (componentWithSelection == textComponent) | |
| componentWithSelection = null; | |
| } | |
| catch (BadLocationException e) | |
| { | |
| // This should never happen. | |
| throw new InternalError(); | |
| } | |
| } | |
| else | |
| { | |
| if (highlightEntry != null) | |
| { | |
| highlighter.removeHighlight(highlightEntry); | |
| highlightEntry = null; | |
| } | |
| } | |
| } | |
| private void handleHighlight() | |
| { | |
| Highlighter highlighter = textComponent.getHighlighter(); | |
| if (highlighter == null) | |
| return; | |
| int p0 = Math.min(dot, mark); | |
| int p1 = Math.max(dot, mark); | |
| if (selectionVisible) | |
| { | |
| try | |
| { | |
| if (highlightEntry == null) | |
| highlightEntry = highlighter.addHighlight(p0, p1, getSelectionPainter()); | |
| else | |
| highlighter.changeHighlight(highlightEntry, p0, p1); | |
| // If another component currently has a text selection clear that selection | |
| // first. | |
| if (componentWithSelection != null) | |
| if (componentWithSelection != textComponent) | |
| { | |
| Caret c = componentWithSelection.getCaret(); | |
| c.setDot(c.getDot()); | |
| } | |
| componentWithSelection = textComponent; | |
| } | |
| catch (BadLocationException e) | |
| { | |
| // This should never happen. | |
| throw new InternalError(); | |
| } | |
| } | |
| else | |
| { | |
| if (highlightEntry != null) | |
| { | |
| highlighter.removeHighlight(highlightEntry); | |
| highlightEntry = null; | |
| } | |
| } | |
| } | |
| /** | |
| * Sets the visiblity state of the selection. | |
| * | |
| * @param v <code>true</code> if the selection should be visible, | |
| * <code>false</code> otherwise | |
| */ | |
| public void setSelectionVisible(boolean v) | |
| { | |
| if (selectionVisible == v) | |
| return; | |
| selectionVisible = v; | |
| handleHighlight(); | |
| repaint(); | |
| } | |
| /** | |
| * Returns <code>true</code> if the selection is currently visible, | |
| * <code>false</code> otherwise. | |
| * | |
| * @return <code>true</code> if the selection is currently visible, | |
| * <code>false</code> otherwise | |
| */ | |
| public boolean isSelectionVisible() | |
| { | |
| return selectionVisible; | |
| } | |
| /** | |
| * Causes the <code>Caret</code> to repaint itself. | |
| */ | |
| protected final void repaint() | |
| { | |
| getComponent().repaint(x, y, width, height); | |
| } | |
| /** | |
| * Paints this <code>Caret</code> using the specified <code>Graphics</code> | |
| * context. | |
| * | |
| * @param g the graphics context to use | |
| */ | |
| public void paint(Graphics g) | |
| { | |
| JTextComponent comp = getComponent(); | |
| if (comp == null) | |
| return; | |
| // Make sure the dot has a sane position. | |
| dot = Math.min(dot, textComponent.getDocument().getLength()); | |
| dot = Math.max(dot, 0); | |
| Rectangle rect = null; | |
| try | |
| { | |
| rect = textComponent.modelToView(dot); | |
| } | |
| catch (BadLocationException e) | |
| { | |
| // Let's ignore that. This shouldn't really occur. But if it | |
| // does (it seems that this happens when the model is mutating), | |
| // it causes no real damage. Uncomment this for debugging. | |
| // e.printStackTrace(); | |
| } | |
| if (rect == null) | |
| return; | |
| // Check if paint has possibly been called directly, without a previous | |
| // call to damage(). In this case we need to do some cleanup first. | |
| if ((x != rect.x) || (y != rect.y)) | |
| { | |
| repaint(); // Erase previous location of caret. | |
| x = rect.x; | |
| y = rect.y; | |
| width = 1; | |
| height = rect.height; | |
| } | |
| // Now draw the caret on the new position if visible. | |
| if (visible && active) | |
| { | |
| g.setColor(textComponent.getCaretColor()); | |
| g.drawLine(rect.x, rect.y, rect.x, rect.y + rect.height - 1); | |
| } | |
| } | |
| /** | |
| * Returns all registered event listeners of the specified type. | |
| * | |
| * @param listenerType the type of listener to return | |
| * | |
| * @return all registered event listeners of the specified type | |
| */ | |
| public <T extends EventListener> T[] getListeners(Class<T> listenerType) | |
| { | |
| return listenerList.getListeners(listenerType); | |
| } | |
| /** | |
| * Registers a {@link ChangeListener} that is notified whenever that state | |
| * of this <code>Caret</code> changes. | |
| * | |
| * @param listener the listener to register to this caret | |
| */ | |
| public void addChangeListener(ChangeListener listener) | |
| { | |
| listenerList.add(ChangeListener.class, listener); | |
| } | |
| /** | |
| * Removes a {@link ChangeListener} from the list of registered listeners. | |
| * | |
| * @param listener the listener to remove | |
| */ | |
| public void removeChangeListener(ChangeListener listener) | |
| { | |
| listenerList.remove(ChangeListener.class, listener); | |
| } | |
| /** | |
| * Returns all registered {@link ChangeListener}s of this <code>Caret</code>. | |
| * | |
| * @return all registered {@link ChangeListener}s of this <code>Caret</code> | |
| */ | |
| public ChangeListener[] getChangeListeners() | |
| { | |
| return (ChangeListener[]) getListeners(ChangeListener.class); | |
| } | |
| /** | |
| * Notifies all registered {@link ChangeListener}s that the state | |
| * of this <code>Caret</code> has changed. | |
| */ | |
| protected void fireStateChanged() | |
| { | |
| ChangeListener[] listeners = getChangeListeners(); | |
| for (int index = 0; index < listeners.length; ++index) | |
| listeners[index].stateChanged(changeEvent); | |
| } | |
| /** | |
| * Returns the <code>JTextComponent</code> on which this <code>Caret</code> | |
| * is installed. | |
| * | |
| * @return the <code>JTextComponent</code> on which this <code>Caret</code> | |
| * is installed | |
| */ | |
| protected final JTextComponent getComponent() | |
| { | |
| return textComponent; | |
| } | |
| /** | |
| * Returns the blink rate of this <code>Caret</code> in milliseconds. | |
| * A value of <code>0</code> means that the caret does not blink. | |
| * | |
| * @return the blink rate of this <code>Caret</code> or <code>0</code> if | |
| * this caret does not blink | |
| */ | |
| public int getBlinkRate() | |
| { | |
| return blinkRate; | |
| } | |
| /** | |
| * Sets the blink rate of this <code>Caret</code> in milliseconds. | |
| * A value of <code>0</code> means that the caret does not blink. | |
| * | |
| * @param rate the new blink rate to set | |
| */ | |
| public void setBlinkRate(int rate) | |
| { | |
| if (blinkTimer != null) | |
| blinkTimer.setDelay(rate); | |
| blinkRate = rate; | |
| } | |
| /** | |
| * Returns the current position of this <code>Caret</code> within the | |
| * <code>Document</code>. | |
| * | |
| * @return the current position of this <code>Caret</code> within the | |
| * <code>Document</code> | |
| */ | |
| public int getDot() | |
| { | |
| return dot; | |
| } | |
| /** | |
| * Moves the <code>dot</code> location without touching the | |
| * <code>mark</code>. This is used when making a selection. | |
| * | |
| * <p>If the underlying text component has a {@link NavigationFilter} | |
| * installed the caret will call the corresponding method of that object.</p> | |
| * | |
| * @param dot the location where to move the dot | |
| * | |
| * @see #setDot(int) | |
| */ | |
| public void moveDot(int dot) | |
| { | |
| NavigationFilter filter = textComponent.getNavigationFilter(); | |
| if (filter != null) | |
| filter.moveDot(getBypass(), dot, Bias.Forward); | |
| else | |
| moveDotImpl(dot); | |
| } | |
| void moveDotImpl(int dot) | |
| { | |
| if (dot >= 0) | |
| { | |
| Document doc = textComponent.getDocument(); | |
| if (doc != null) | |
| this.dot = Math.min(dot, doc.getLength()); | |
| this.dot = Math.max(this.dot, 0); | |
| handleHighlight(); | |
| appear(); | |
| } | |
| } | |
| /** | |
| * Sets the current position of this <code>Caret</code> within the | |
| * <code>Document</code>. This also sets the <code>mark</code> to the new | |
| * location. | |
| * | |
| * <p>If the underlying text component has a {@link NavigationFilter} | |
| * installed the caret will call the corresponding method of that object.</p> | |
| * | |
| * @param dot | |
| * the new position to be set | |
| * @see #moveDot(int) | |
| */ | |
| public void setDot(int dot) | |
| { | |
| NavigationFilter filter = textComponent.getNavigationFilter(); | |
| if (filter != null) | |
| filter.setDot(getBypass(), dot, Bias.Forward); | |
| else | |
| setDotImpl(dot); | |
| } | |
| void setDotImpl(int dot) | |
| { | |
| if (dot >= 0) | |
| { | |
| Document doc = textComponent.getDocument(); | |
| if (doc != null) | |
| this.dot = Math.min(dot, doc.getLength()); | |
| this.dot = Math.max(this.dot, 0); | |
| this.mark = this.dot; | |
| clearHighlight(); | |
| appear(); | |
| } | |
| } | |
| /** | |
| * Show the caret (may be hidden due blinking) and adjust the timer not to | |
| * hide it (possibly immediately). | |
| * | |
| * @author Audrius Meskauskas (AudriusA@Bioinformatics.org) | |
| */ | |
| void appear() | |
| { | |
| // All machinery is only required if the carret is blinking. | |
| if (blinkListener != null) | |
| { | |
| blinkListener.ignoreNextEvent = true; | |
| // If the caret is visible, erase the current position by repainting | |
| // over. | |
| if (visible) | |
| repaint(); | |
| // Draw the caret in the new position. | |
| visible = true; | |
| Rectangle area = null; | |
| int dot = getDot(); | |
| try | |
| { | |
| area = getComponent().modelToView(dot); | |
| } | |
| catch (BadLocationException e) | |
| { | |
| // Let's ignore that. This shouldn't really occur. But if it | |
| // does (it seems that this happens when the model is mutating), | |
| // it causes no real damage. Uncomment this for debugging. | |
| // e.printStackTrace(); | |
| } | |
| if (area != null) | |
| { | |
| adjustVisibility(area); | |
| if (getMagicCaretPosition() == null) | |
| setMagicCaretPosition(new Point(area.x, area.y)); | |
| damage(area); | |
| } | |
| } | |
| repaint(); | |
| } | |
| /** | |
| * Returns <code>true</code> if this <code>Caret</code> is blinking, | |
| * and <code>false</code> if not. The returned value is independent of | |
| * the visiblity of this <code>Caret</code> as returned by {@link #isVisible()}. | |
| * | |
| * @return <code>true</code> if this <code>Caret</code> is blinking, | |
| * and <code>false</code> if not. | |
| * @see #isVisible() | |
| * @since 1.5 | |
| */ | |
| public boolean isActive() | |
| { | |
| if (blinkTimer != null) | |
| return blinkTimer.isRunning(); | |
| return false; | |
| } | |
| /** | |
| * Returns <code>true</code> if this <code>Caret</code> is currently visible, | |
| * and <code>false</code> if it is not. | |
| * | |
| * @return <code>true</code> if this <code>Caret</code> is currently visible, | |
| * and <code>false</code> if it is not | |
| */ | |
| public boolean isVisible() | |
| { | |
| return visible && active; | |
| } | |
| /** | |
| * Sets the visibility state of the caret. <code>true</code> shows the | |
| * <code>Caret</code>, <code>false</code> hides it. | |
| * | |
| * @param v the visibility to set | |
| */ | |
| public void setVisible(boolean v) | |
| { | |
| if (v != visible) | |
| { | |
| visible = v; | |
| updateTimerStatus(); | |
| Rectangle area = null; | |
| int dot = getDot(); | |
| try | |
| { | |
| area = getComponent().modelToView(dot); | |
| } | |
| catch (BadLocationException e) | |
| { | |
| AssertionError ae; | |
| ae = new AssertionError("Unexpected bad caret location: " + dot); | |
| ae.initCause(e); | |
| throw ae; | |
| } | |
| if (area != null) | |
| damage(area); | |
| } | |
| } | |
| /** | |
| * Returns the {@link Highlighter.HighlightPainter} that should be used | |
| * to paint the selection. | |
| * | |
| * @return the {@link Highlighter.HighlightPainter} that should be used | |
| * to paint the selection | |
| */ | |
| protected Highlighter.HighlightPainter getSelectionPainter() | |
| { | |
| return DefaultHighlighter.DefaultPainter; | |
| } | |
| /** | |
| * Updates the carets rectangle properties to the specified rectangle and | |
| * repaints the caret. | |
| * | |
| * @param r the rectangle to set as the caret rectangle | |
| */ | |
| protected void damage(Rectangle r) | |
| { | |
| if (r == null) | |
| return; | |
| x = r.x; | |
| y = r.y; | |
| width = 1; | |
| // height is normally set in paint and we leave it untouched. However, we | |
| // must set a valid value here, since otherwise the painting mechanism | |
| // sets a zero clip and never calls paint. | |
| if (height <= 0) | |
| try | |
| { | |
| height = textComponent.modelToView(dot).height; | |
| } | |
| catch (BadLocationException ble) | |
| { | |
| // Should not happen. | |
| throw new InternalError("Caret location not within document range."); | |
| } | |
| repaint(); | |
| } | |
| /** | |
| * Adjusts the text component so that the caret is visible. This default | |
| * implementation simply calls | |
| * {@link JComponent#scrollRectToVisible(Rectangle)} on the text component. | |
| * Subclasses may wish to change this. | |
| */ | |
| protected void adjustVisibility(Rectangle rect) | |
| { | |
| getComponent().scrollRectToVisible(rect); | |
| } | |
| /** | |
| * Initializes the blink timer. | |
| */ | |
| private void initBlinkTimer() | |
| { | |
| // Setup the blink timer. | |
| blinkListener = new BlinkTimerListener(); | |
| blinkTimer = new Timer(getBlinkRate(), blinkListener); | |
| blinkTimer.setRepeats(true); | |
| } | |
| } |