| /* 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 EventListener[] getListeners(Class 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(); |
| |
| adjustVisibility(this); |
| } |
| } |
| |
| /** |
| * 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(); |
| |
| adjustVisibility(this); |
| } |
| } |
| |
| /** |
| * 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) |
| 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); |
| } |
| |
| } |