diff options
author | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
---|---|---|
committer | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
commit | 554fd8c5195424bdbcabf5de30fdc183aba391bd (patch) | |
tree | 976dc5ab7fddf506dadce60ae936f43f58787092 /libjava/classpath/javax/swing/text/DefaultCaret.java | |
download | cbb-gcc-4.6.4-15d2061ac0796199866debe9ac87130894b0cdd3.tar.bz2 cbb-gcc-4.6.4-15d2061ac0796199866debe9ac87130894b0cdd3.tar.xz |
obtained gcc-4.6.4.tar.bz2 from upstream website;upstream
verified gcc-4.6.4.tar.bz2.sig;
imported gcc-4.6.4 source tree from verified upstream tarball.
downloading a git-generated archive based on the 'upstream' tag
should provide you with a source tree that is binary identical
to the one extracted from the above tarball.
if you have obtained the source via the command 'git clone',
however, do note that line-endings of files in your working
directory might differ from line-endings of the respective
files in the upstream repository.
Diffstat (limited to 'libjava/classpath/javax/swing/text/DefaultCaret.java')
-rw-r--r-- | libjava/classpath/javax/swing/text/DefaultCaret.java | 1287 |
1 files changed, 1287 insertions, 0 deletions
diff --git a/libjava/classpath/javax/swing/text/DefaultCaret.java b/libjava/classpath/javax/swing/text/DefaultCaret.java new file mode 100644 index 000000000..acfcdb368 --- /dev/null +++ b/libjava/classpath/javax/swing/text/DefaultCaret.java @@ -0,0 +1,1287 @@ +/* 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); + } + +} |