0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: /**
0018: * @author Evgeniya G. Maenkova
0019: * @version $Revision$
0020: */package javax.swing.text;
0021:
0022: import java.awt.Color;
0023: import java.awt.Component;
0024: import java.awt.EventQueue;
0025: import java.awt.Graphics;
0026: import java.awt.Point;
0027: import java.awt.Rectangle;
0028: import java.awt.event.ActionEvent;
0029: import java.awt.event.ActionListener;
0030: import java.awt.event.FocusEvent;
0031: import java.awt.event.FocusListener;
0032: import java.awt.event.MouseEvent;
0033: import java.awt.event.MouseListener;
0034: import java.awt.event.MouseMotionListener;
0035: import java.beans.PropertyChangeEvent;
0036: import java.beans.PropertyChangeListener;
0037: import java.io.IOException;
0038: import java.io.ObjectInputStream;
0039: import java.io.ObjectOutputStream;
0040: import java.util.EventListener;
0041:
0042: import javax.swing.AbstractAction;
0043: import javax.swing.SwingConstants;
0044: import javax.swing.SwingUtilities;
0045: import javax.swing.Timer;
0046: import javax.swing.event.ChangeEvent;
0047: import javax.swing.event.ChangeListener;
0048: import javax.swing.event.DocumentEvent;
0049: import javax.swing.event.DocumentListener;
0050: import javax.swing.event.EventListenerList;
0051: import javax.swing.text.Position.Bias;
0052:
0053: import org.apache.harmony.awt.text.AWTTextAction;
0054: import org.apache.harmony.awt.text.ActionNames;
0055: import org.apache.harmony.awt.text.ActionSet;
0056: import org.apache.harmony.awt.text.TextKit;
0057: import org.apache.harmony.awt.text.TextUtils;
0058: import org.apache.harmony.x.swing.StringConstants;
0059:
0060: import org.apache.harmony.x.swing.internal.nls.Messages;
0061:
0062: public class DefaultCaret extends Rectangle implements Caret,
0063: FocusListener, MouseListener, MouseMotionListener {
0064:
0065: public static final int ALWAYS_UPDATE = 2;
0066:
0067: public static final int NEVER_UPDATE = 1;
0068:
0069: public static final int UPDATE_WHEN_ON_EDT = 0;
0070:
0071: protected EventListenerList listenerList = new EventListenerList();
0072:
0073: protected transient ChangeEvent changeEvent = new ChangeEvent(this );
0074:
0075: private int mark;
0076:
0077: private int dot;
0078:
0079: private transient Position.Bias dotBias = Position.Bias.Forward;
0080:
0081: private transient Position.Bias markBias = Position.Bias.Forward;
0082:
0083: private boolean async;
0084:
0085: //Position in document to follow the adjucent caret position
0086: //on document updates. Help to get new bias (dot) after
0087: //document updates.
0088: private int dotTrack;
0089:
0090: //current policy (for 1.5.0 sets by setUpdatePolicy)
0091: private int selectedPolicy = UPDATE_WHEN_ON_EDT;
0092:
0093: private int blinkRate;
0094:
0095: //installed text component
0096: private Component component;
0097:
0098: TextKit textKit;
0099:
0100: //current selection color
0101: private Color selectionColor;
0102:
0103: //current caret color
0104: private Color caretColor;
0105:
0106: //value selectionColor in case of JTextComponent.getSelectionColor()
0107: //equals null
0108: private static final Color DEF_SEL_COLOR = new Color(192, 224, 255);
0109:
0110: //value selectionColor in case of JTextComponent.getCaretColor()
0111: //equals null
0112: private static final Color DEF_CARET_COLOR = new Color(0, 0, 0);
0113:
0114: //delay for magicTimer
0115: static final int DEFAULT_MAGIC_DELAY = 600;
0116:
0117: private static final int APEX_NUMBER = 3;
0118:
0119: private static final int TRIANGLE_HEIGHT = 4;
0120:
0121: private static final int RIGHT_TRIANGLE_WIDTH = 5;
0122:
0123: private static final int LEFT_TRIANGLE_WIDTH = 4;
0124:
0125: //current painter for selection
0126: private transient DefaultHighlighter.DefaultHighlightPainter painter;
0127:
0128: //reference to current selection, if any
0129: private transient Object selectionTag;
0130:
0131: //DocumentListener for document of current JTextComponent
0132: private transient DocumentListener dh = new DocumentHandler();
0133:
0134: //PropertyChangeListener for current JTextComponent
0135: private transient PropertyHandler pch;
0136:
0137: //used by mouseClicked method
0138: private static final transient AWTTextAction SELECT_WORD_ACTION = ActionSet.actionMap
0139: .get(ActionNames.selectWordAction);
0140:
0141: //used by mouseClicked method
0142: private static final transient AWTTextAction SELECT_LINE_ACTION = ActionSet.actionMap
0143: .get(ActionNames.selectLineAction);
0144:
0145: Point magicCaretPosition;
0146:
0147: private transient boolean isVisible;
0148:
0149: private boolean isSelectionVisible;
0150:
0151: //Timer to repaint caret according to current blinkRate
0152: transient Object blinkTimer;
0153:
0154: //Timer to set magicCaretPosition to current caret position
0155: transient Object magicTimer;
0156:
0157: //defines whether caret be painting or blinking, if blink on
0158: transient boolean shouldDraw = true;
0159:
0160: //defines x coordinates of flag, in case of bidirectional text
0161: private transient int[] triangleX = new int[APEX_NUMBER];
0162:
0163: //defines y coordinates of flag, in case of bidirectional text
0164: private transient int[] triangleY = new int[APEX_NUMBER];
0165:
0166: //used for modelToView calls
0167: private transient Position.Bias[] bias = new Position.Bias[1];
0168:
0169: //for DnD support, selection doesn't change when drag
0170: private boolean handleMouseDrag = true;
0171:
0172: //flag to restore selection
0173: private boolean restoreSelection;
0174:
0175: //used when JTextComponent has NavigationFilter
0176: private transient FilterBypass filterBypass = new FilterBypass(this );
0177:
0178: private transient NavigationFilter navigationFilter;
0179:
0180: private Highlighter highlighter;
0181:
0182: private Document document;
0183:
0184: private boolean isBidiDocument;
0185:
0186: //This variable remembers last coordinates, where mouse button
0187: //was pressed. Handling MouseEvent in MouseClicked method depends
0188: //on this.
0189: private int[] lastPressPoint = new int[2];
0190:
0191: //action for blinkTimer
0192: Object blinkAction;
0193:
0194: //Action for magic timer. If MagicCaretPosition still null,
0195: //MagicCaretPosition set to current caret position, else do nothing.
0196: Object magicAction;
0197:
0198: private class PropertyHandler implements PropertyChangeListener {
0199: public void propertyChange(final PropertyChangeEvent evt) {
0200: String proptertyName = evt.getPropertyName();
0201: Object newValue = evt.getNewValue();
0202: if (StringConstants.TEXT_COMPONENT_DOCUMENT_PROPERTY
0203: .equals(proptertyName)) {
0204: Document oldDoc = (Document) evt.getOldValue();
0205: document = textKit.getDocument();
0206: if (oldDoc != null) {
0207: oldDoc.removeDocumentListener(dh);
0208: }
0209: if (document != null) {
0210: updateBidiInfo();
0211: document.addDocumentListener(dh);
0212: setDot(0);
0213: }
0214: } else if (StringConstants.TEXT_COMPONENT_CARET_COLOR_PROPERTY
0215: .equals(proptertyName)) {
0216: caretColor = (Color) newValue;
0217: } else if (StringConstants.TEXT_COMPONENT_SELECTION_COLOR_PROPERTY
0218: .equals(proptertyName)) {
0219: selectionColor = (Color) newValue;
0220: painter = new DefaultHighlighter.DefaultHighlightPainter(
0221: selectionColor);
0222: } else if (StringConstants.TEXT_COMPONENT_HIGHLIGHTER_PROPERTY
0223: .equals(proptertyName)) {
0224: highlighter = (Highlighter) newValue;
0225: } else if (StringConstants.TEXT_COMPONENT_NAV_FILTER_NAME
0226: .equals(proptertyName)) {
0227: navigationFilter = (NavigationFilter) newValue;
0228: }
0229: }
0230: }
0231:
0232: //DocumentListener, to change dot, dotBias, mark, current selection on
0233: // document updates
0234: //according to AsynchronousMovement property.
0235: //Don't repaint caret, JText component do painting on document updates.
0236: private class DocumentHandler implements DocumentListener {
0237:
0238: /*
0239: * Returns position after string removing (length - length of string,
0240: * offset position where string is removed). Pos - position, which
0241: * should new position be defined for
0242: */
0243: private int newPosOnRemove(final int pos, final int length,
0244: final int offset) {
0245: int endUpdate = offset + length;
0246: if ((pos > offset) && (endUpdate <= pos)) {
0247: return pos - length;
0248: }
0249: if ((pos > offset) && (endUpdate > pos)) {
0250: return offset;
0251: }
0252: return pos;
0253: }
0254:
0255: /*
0256: * Returns position after string inserting (length - length of string,
0257: * offset position where string is added). Pos - position, which
0258: * should new position be defined for
0259: */
0260: private int newPosOnInsert(final int pos, final int length,
0261: final int offset) {
0262: if (offset <= pos) {
0263: return pos + length;
0264: }
0265: return pos;
0266: }
0267:
0268: /**
0269: * Caret position don't change on changeUpdate of Document
0270: */
0271: public void changedUpdate(final DocumentEvent e) {
0272:
0273: }
0274:
0275: public synchronized void removeUpdate(final DocumentEvent e) {
0276: final int docLength = document.getLength();
0277: final boolean condOnNeverUpdate = (docLength <= Math.max(
0278: dot, mark));
0279: final boolean isNeverUpdate = (selectedPolicy == NEVER_UPDATE);
0280: final boolean isUpdateWhenOnEdt = (selectedPolicy == UPDATE_WHEN_ON_EDT);
0281: final boolean isUpdateWhenOnEdtAndAsync = (isUpdateWhenOnEdt && !EventQueue
0282: .isDispatchThread());
0283: if ((isNeverUpdate || isUpdateWhenOnEdtAndAsync)
0284: && !condOnNeverUpdate) {
0285: return;
0286: }
0287:
0288: removeUpdate(e, isNeverUpdate || isUpdateWhenOnEdtAndAsync,
0289: docLength);
0290: }
0291:
0292: private void removeUpdate(final DocumentEvent e,
0293: final boolean trivialUpdate, final int docLength) {
0294: int length = e.getLength();
0295: int offset = e.getOffset();
0296: int newMark;
0297: int newDot;
0298: Position.Bias newBias;
0299:
0300: if (trivialUpdate) {
0301: newMark = Math.min(mark, docLength);
0302: newDot = Math.min(dot, docLength);
0303: newBias = dotBias;
0304: } else {
0305: newMark = newPosOnRemove(mark, length, offset);
0306: dotTrack = newPosOnRemove(dotTrack, length, offset);
0307: newDot = newPosOnRemove(dot, length, offset);
0308: newBias = getNewBias(dotTrack, newDot);
0309: }
0310:
0311: mark = newMark;
0312: moveDot(newDot, newBias);
0313: if (dot == mark) {
0314: markBias = dotBias;
0315: }
0316: }
0317:
0318: public synchronized void insertUpdate(final DocumentEvent e) {
0319: if ((selectedPolicy == NEVER_UPDATE)
0320: || ((selectedPolicy == UPDATE_WHEN_ON_EDT) && !EventQueue
0321: .isDispatchThread())) {
0322: return;
0323: }
0324:
0325: int length = e.getLength();
0326: int offset = e.getOffset();
0327: String s = null;
0328: try {
0329: s = document.getText(offset, 1);
0330: } catch (final BadLocationException ex) {
0331: }
0332:
0333: if (offset == dot
0334: && (length + offset) != document.getLength()) {
0335: dotBias = Position.Bias.Backward;
0336: }
0337:
0338: if (s.equals("\n")) {
0339: dotBias = Position.Bias.Forward;
0340: }
0341:
0342: mark = newPosOnInsert(mark, length, offset);
0343: moveDot(newPosOnInsert(dot, length, offset), dotBias);
0344: if (dot == mark) {
0345: markBias = dotBias;
0346: }
0347: updateBidiInfo();
0348: }
0349:
0350: }
0351:
0352: /**
0353: * Default Implementation if NavigationFilter.FilterBypass. Used in setDot()
0354: * and moveDot(), when component.getNavigationFilter doesn't equal null.
0355: */
0356: private class FilterBypass extends NavigationFilter.FilterBypass {
0357: DefaultCaret caret;
0358:
0359: FilterBypass(final DefaultCaret dc) {
0360: caret = dc;
0361: }
0362:
0363: @Override
0364: public Caret getCaret() {
0365: return caret;
0366: }
0367:
0368: @Override
0369: public void setDot(final int i, final Bias b) {
0370: caret.internalSetDot(i, b);
0371: }
0372:
0373: @Override
0374: public void moveDot(final int i, final Bias b) {
0375: caret.internalMoveDot(i, b);
0376: }
0377:
0378: }
0379:
0380: /**
0381: * Sets all fiels to default values
0382: */
0383: public DefaultCaret() {
0384: blinkTimer = createTimer(false, 0);
0385: magicTimer = createTimer(true, 0);
0386: painter = new DefaultHighlighter.DefaultHighlightPainter(
0387: selectionColor);
0388: }
0389:
0390: public void addChangeListener(final ChangeListener changeListener) {
0391: if (changeListener != null) {
0392: listenerList.add(ChangeListener.class, changeListener);
0393: }
0394: }
0395:
0396: /**
0397: * Adds selection according to current dot and mark, if isSelectionVisible
0398: * equals true.
0399: *
0400: */
0401: private void addHighlight() {
0402: if (mark == dot) {
0403: return;
0404: }
0405: if (!isSelectionVisible) {
0406: restoreSelection = true;
0407: removeHighlight();
0408: return;
0409: }
0410:
0411: if (selectionTag == null) {
0412: selectionTag = addHighlight(Math.min(dot, mark), Math.max(
0413: dot, mark));
0414: }
0415: }
0416:
0417: protected void adjustVisibility(final Rectangle r) {
0418: if (r != null) {
0419: textKit.scrollRectToVisible(new Rectangle(r.x, r.y,
0420: r.width + 1, r.height));
0421: }
0422: }
0423:
0424: /**
0425: * Repaint caret according to current dot and dotBias.
0426: *
0427: */
0428: private void calcNewPos() {
0429: Rectangle p = null;
0430: try {
0431: p = textKit.modelToView(dot, dotBias);
0432: } catch (final BadLocationException e) {
0433: }
0434: if (p == null) {
0435: return;
0436: }
0437: damage(p);
0438: }
0439:
0440: /**
0441: * Sets dot to new position i, repaint caret, sets magic caret position to
0442: * null. Calls fireStateChanged.
0443: *
0444: * @param i new dot
0445: */
0446: private void changeDot(final int i) {
0447: dot = i;
0448: magicCaretPosition = null;
0449: shouldDraw = true;
0450: dotTrack = changePosTrack(dot, dotBias);
0451: calcNewPos();
0452: fireStateChanged();
0453: }
0454:
0455: /**
0456: * Changes current selection, if any. Removes current selection, if dot
0457: * equals mark.
0458: */
0459: private void changeHighlight() {
0460: if (dot == mark) {
0461: if (selectionTag != null) {
0462: removeHighlight();
0463: }
0464: } else {
0465: if (selectionTag == null) {
0466: addHighlight();
0467: } else {
0468: changeHighlight(selectionTag, Math.min(dot, mark), Math
0469: .max(dot, mark));
0470: }
0471: }
0472: }
0473:
0474: /*
0475: * Calculate new adjacent position value according to pos and bias.
0476: *
0477: * @param newPos offset, which should new adjucent position be defined for
0478: * @param newBias bias of pos
0479: * @return new adjucent position
0480: */
0481: private int changePosTrack(final int newPos,
0482: final Position.Bias newBias) {
0483: if (newBias == Position.Bias.Forward) {
0484: return newPos + 1;
0485: } else {
0486: return newPos;
0487: }
0488: }
0489:
0490: protected synchronized void damage(final Rectangle r) {
0491: repaint();
0492: if (r == null) {
0493: return;
0494: }
0495: x = r.x;
0496: y = r.y;
0497: width = 0;
0498: height = r.height - 2;
0499: adjustVisibility(r);
0500: repaint();
0501: }
0502:
0503: /**
0504: * Stops timer for blinking.
0505: *
0506: */
0507: public void deinstall(final JTextComponent comp) {
0508: if (component == null || comp != component) {
0509: return;
0510: }
0511: if (document != null) {
0512: document.removeDocumentListener(dh);
0513: }
0514: component.removePropertyChangeListener(pch);
0515: component.removeMouseListener(this );
0516: component.removeMouseMotionListener(this );
0517: component.removeFocusListener(this );
0518: stopTimer(blinkTimer);
0519: stopTimer(magicTimer);
0520: highlighter = null;
0521: component = null;
0522: textKit = null;
0523: }
0524:
0525: @Override
0526: public boolean equals(final Object obj) {
0527: return this == obj;
0528: }
0529:
0530: protected void fireStateChanged() {
0531: if (isVisible) {
0532: TextUtils.setNativeCaretPosition(this , component);
0533: }
0534: Object[] listeners = listenerList.getListenerList();
0535: for (int i = listeners.length - 2; i >= 0; i -= 2) {
0536: if (listeners[i] == ChangeListener.class) {
0537: if (changeEvent == null) {
0538: changeEvent = new ChangeEvent(this );
0539: }
0540: ((ChangeListener) listeners[i + 1])
0541: .stateChanged(changeEvent);
0542: }
0543: }
0544: }
0545:
0546: /**
0547: * Sets isSelectionVisible to true
0548: *
0549: */
0550: public void focusGained(final FocusEvent fe) {
0551: isSelectionVisible = true;
0552: if (restoreSelection) {
0553: addHighlight();
0554: restoreSelection = false;
0555: }
0556: if (isComponentEditable()) {
0557: setVisible(true);
0558: repaint();
0559: } else {
0560: setVisible(false);
0561: }
0562: }
0563:
0564: /**
0565: * Sets isSelectionVisible to true
0566: *
0567: */
0568: public void focusLost(final FocusEvent fe) {
0569: setVisible(false);
0570: isSelectionVisible = true;
0571: Component c = fe.getOppositeComponent();
0572: if (c != null && !fe.isTemporary()
0573: && isRestoreSelectionCondition(c)) {
0574: restoreSelection = true;
0575: removeHighlight();
0576: }
0577: repaint();
0578: }
0579:
0580: private boolean getAsynchronousMovement() {
0581: return async;
0582: }
0583:
0584: public int getBlinkRate() {
0585: return blinkRate;
0586: }
0587:
0588: public ChangeListener[] getChangeListeners() {
0589: return listenerList.getListeners(ChangeListener.class);
0590:
0591: }
0592:
0593: protected final JTextComponent getComponent() {
0594: return component instanceof JTextComponent ? (JTextComponent) component
0595: : null;
0596: }
0597:
0598: /*
0599: * Defines flag direction, to paint caret in case of bidirectional text
0600: *
0601: * @return true - if flag in the left direction, false - if flag in the
0602: * right direction
0603: */
0604: private boolean getDirection() {
0605: AbstractDocument ad = ((AbstractDocument) document);
0606: int length = ad.getLength();
0607:
0608: boolean currentDirection = (dot >= 1) ? ad
0609: .isLeftToRight(dot - 1) : false;
0610: boolean nextDirection = (dot <= length) ? ad.isLeftToRight(dot)
0611: : true;
0612:
0613: if (currentDirection == nextDirection) {
0614: return (currentDirection) ? false : true;
0615: }
0616: if (currentDirection) {
0617: return (dotBias == Position.Bias.Backward) ? false : true;
0618: } else {
0619: return (dotBias == Position.Bias.Backward) ? true : false;
0620: }
0621: }
0622:
0623: public int getDot() {
0624: return dot;
0625: }
0626:
0627: /**
0628: * Returns current dot bias
0629: *
0630: * @return dotBias dot bias
0631: */
0632: Position.Bias getDotBias() {
0633: return dotBias;
0634: }
0635:
0636: public <T extends EventListener> T[] getListeners(final Class<T> c) {
0637: T[] evL = null;
0638: try {
0639: evL = listenerList.getListeners(c);
0640: } catch (final ClassCastException e) {
0641: throw e;
0642: }
0643: return evL;
0644: }
0645:
0646: public Point getMagicCaretPosition() {
0647: return magicCaretPosition;
0648: //return stubMagicCaretPosition;
0649: }
0650:
0651: public int getMark() {
0652: return mark;
0653: }
0654:
0655: /**
0656: * Get new bias for current position and its adjucent position
0657: *
0658: * @param posTrack adjucent position
0659: * @param pos current position
0660: * @return bias
0661: */
0662: private Position.Bias getNewBias(final int posTrack, final int pos) {
0663: return (posTrack == pos) ? Position.Bias.Backward
0664: : Position.Bias.Forward;
0665: }
0666:
0667: protected Highlighter.HighlightPainter getSelectionPainter() {
0668: return painter;
0669: }
0670:
0671: public int getUpdatePolicy() {
0672: return selectedPolicy;
0673: }
0674:
0675: /**
0676: * Adds listeners to c, if c doesn't equal null.
0677: * Adds DocumentListener to
0678: * c, if c doesn't equals null. Sets textUI, caretColor, selectionColor to
0679: * value from c, if they don't equal null. Sets new painter according to
0680: * selectionColor.
0681: */
0682: public void install(final JTextComponent c) {
0683: setComponent(c);
0684:
0685: component.addMouseListener(this );
0686: component.addMouseMotionListener(this );
0687: component.addFocusListener(this );
0688: component.addPropertyChangeListener(getPropertyHandler());
0689: highlighter = c.getHighlighter();
0690: navigationFilter = c.getNavigationFilter();
0691: painter = new DefaultHighlighter.DefaultHighlightPainter(
0692: selectionColor);
0693: }
0694:
0695: void setComponent(final Component c) {
0696: component = c;
0697: textKit = TextUtils.getTextKit(component);
0698: document = textKit.getDocument();
0699: updateBidiInfo();
0700: if (document != null) {
0701: document.addDocumentListener(dh);
0702: }
0703: selectionColor = getSelectionColor();
0704: caretColor = getCaretColor();
0705: }
0706:
0707: public boolean isActive() {
0708: //return shouldDraw;
0709: return isVisible;
0710: }
0711:
0712: public boolean isSelectionVisible() {
0713: return isSelectionVisible;
0714: }
0715:
0716: public boolean isVisible() {
0717: return isVisible;
0718: }
0719:
0720: //MouseClicked is called if mouse button was released farther
0721: //than 5 pixels (from place, where mouse button was pressed).
0722: //Sometimes, it is not enough. For example, when width of
0723: //a letter is smaller than 5 pixels.
0724: //So it's necessarily to filter these mouse events.
0725:
0726: private boolean needClick(final MouseEvent e) {
0727: return selectionTag == null
0728: || (Math.abs(e.getX() - lastPressPoint[0]) > 1 && Math
0729: .abs(e.getY() - lastPressPoint[1]) > 1);
0730:
0731: }
0732:
0733: public void mouseClicked(final MouseEvent me) {
0734: if (!needClick(me)) {
0735: return;
0736: }
0737: int clickCount = me.getClickCount();
0738: if (me.getButton() == MouseEvent.BUTTON1) {
0739: if (clickCount == 1) {
0740: positionCaret(me);
0741: }
0742: if (clickCount == 2) {
0743: SELECT_WORD_ACTION.performAction(textKit);
0744: }
0745: if (clickCount == 3) {
0746: SELECT_LINE_ACTION.performAction(textKit);
0747: }
0748: }
0749: }
0750:
0751: /**
0752: * Calculates offset, which correspond MouseEvent. If DragAndDropCondition
0753: * return false, moveDot is called.
0754: */
0755: public void mouseDragged(final MouseEvent me) {
0756:
0757: int offset = textKit.viewToModel(
0758: new Point(me.getX(), me.getY()), bias);
0759: if (offset < 0) {
0760: return;
0761: }
0762: int mask = MouseEvent.BUTTON1_DOWN_MASK;
0763: if ((me.getModifiersEx() & mask) != mask) {
0764: return;
0765: }
0766: if (handleMouseDrag) { //(! DragAndDropCondition(offset))
0767: moveDot(offset, bias[0]);
0768: }
0769: }
0770:
0771: /**
0772: * If component.getDragEnabled() returns true and offset in selection,
0773: * return true. Otherwise, returns false.
0774: */
0775: private boolean isDragAndDropCondition(final int offset) {
0776: return isDragEnabled() ? (offset >= Math.min(dot, mark) && offset <= Math
0777: .max(dot, mark))
0778: : false;
0779: }
0780:
0781: public void mouseEntered(final MouseEvent me) {
0782: }
0783:
0784: public void mouseExited(final MouseEvent me) {
0785: }
0786:
0787: public void mouseMoved(final MouseEvent me) {
0788: }
0789:
0790: /**
0791: * Calculates offset, which corresponds to MouseEvent. If shift-button isn't
0792: * pressed, DragAndDropCondition return false, then setDot is called.
0793: */
0794: public void mousePressed(final MouseEvent me) {
0795: int offset;
0796: int mask = MouseEvent.SHIFT_DOWN_MASK;
0797: if (me.getButton() != MouseEvent.BUTTON1
0798: || !component.isEnabled()) {
0799: return;
0800: }
0801: component.requestFocusInWindow();
0802: offset = textKit.viewToModel(new Point(me.getX(), me.getY()),
0803: bias);
0804: if (offset < 0) {
0805: return;
0806: }
0807: rememberPressPoint(me);
0808: if ((me.getModifiersEx() & mask) == mask) {
0809: moveDot(offset, bias[0]);
0810: } else {
0811: boolean condition = isDragAndDropCondition(offset);
0812: if (!condition) {
0813: setDot(offset, bias[0]);
0814: }
0815: handleMouseDrag = !(condition && dot != mark);
0816: }
0817:
0818: }
0819:
0820: private void rememberPressPoint(final MouseEvent e) {
0821: lastPressPoint[0] = e.getX();
0822: lastPressPoint[1] = e.getY();
0823: }
0824:
0825: public void mouseReleased(final MouseEvent me) {
0826:
0827: }
0828:
0829: protected void moveCaret(final MouseEvent me) {
0830: int offset = textKit.viewToModel(
0831: new Point(me.getX(), me.getY()), bias);
0832: if (offset >= 0) {
0833: moveDot(offset);
0834: }
0835: }
0836:
0837: /**
0838: * Calls changeDot, don't change mark. Adds or changes highlight, it depends
0839: * on current dot and mark
0840: *
0841: */
0842: public void moveDot(final int i) {
0843: moveDot(i, Position.Bias.Forward);
0844: }
0845:
0846: /**
0847: * If current JTextComponent has NavigationFilter then call
0848: * getComponent.getNavigationFilter.moveDot. Otherwise, calls changeDot,
0849: * don't change mark. Sets new dot, new dotBias. Adds or changes highlight,
0850: * it dependes on current dot and mark
0851: *
0852: * @param i new dot
0853: * @param b new dot bias
0854: */
0855:
0856: void moveDot(final int i, final Position.Bias b) {
0857: if (navigationFilter == null) {
0858: internalMoveDot(i, b);
0859: } else {
0860: navigationFilter.moveDot(filterBypass, i, b);
0861: }
0862: }
0863:
0864: private void internalMoveDot(final int i, final Position.Bias b) {
0865: dotBias = b;
0866: changeDot(i);
0867: changeHighlight();
0868: }
0869:
0870: public void paint(final Graphics g) {
0871: try {
0872: if (!isVisible || !shouldDraw) {
0873: return;
0874: }
0875: Rectangle p = textKit.modelToView(dot, dotBias);
0876: if (p == null) {
0877: return;
0878: }
0879: this .setBounds(p.x, p.y, 0, p.height - 2);
0880: g.setColor(caretColor);
0881: g.drawRect(x, y, width, height);
0882:
0883: if (isBidiDocument) {
0884: triangleX[0] = x;
0885: triangleX[1] = x;
0886: triangleX[2] = getDirection() ? (x - LEFT_TRIANGLE_WIDTH)
0887: : (x + RIGHT_TRIANGLE_WIDTH);
0888: triangleY[0] = y;
0889: triangleY[1] = y + TRIANGLE_HEIGHT;
0890: triangleY[2] = y;
0891: g.fillPolygon(triangleX, triangleY, APEX_NUMBER);
0892: }
0893: } catch (BadLocationException e) {
0894:
0895: }
0896: }
0897:
0898: protected void positionCaret(final MouseEvent me) {
0899: int offset = textKit.viewToModel(
0900: new Point(me.getX(), me.getY()), bias);
0901: if (offset >= 0) {
0902: setDot(offset, bias[0]);
0903: }
0904: }
0905:
0906: /**
0907: * Reads object by default, reads string representation dotBias, markBias,
0908: * sets dotBias, markBias. Sets blinkTimer, magicTimer, textUI, dh, pch,
0909: * selectWord, selectLine, triangleX, triangleY, bias.
0910: *
0911: * @param s
0912: * @throws IOException
0913: * @throws ClassNotFoundException
0914: */
0915: private void readObject(final ObjectInputStream s)
0916: throws IOException, ClassNotFoundException {
0917:
0918: s.defaultReadObject();
0919: String forwardString = Position.Bias.Forward.toString();
0920: dotBias = (forwardString.equals(s.readUTF())) ? Position.Bias.Forward
0921: : Position.Bias.Backward;
0922:
0923: markBias = (forwardString.equals(s.readUTF())) ? Position.Bias.Forward
0924: : Position.Bias.Backward;
0925: painter = new DefaultHighlighter.DefaultHighlightPainter(
0926: selectionColor);
0927:
0928: dh = new DocumentHandler();
0929: pch = new PropertyHandler();
0930:
0931: if (component != null) {
0932: component.addPropertyChangeListener(pch);
0933: if (textKit.getDocument() != null) {
0934: textKit.getDocument().addDocumentListener(dh);
0935: }
0936: }
0937:
0938: blinkTimer = createTimer(false, 0);
0939: magicTimer = createTimer(true, 0);
0940:
0941: triangleX = new int[APEX_NUMBER];
0942: triangleY = new int[APEX_NUMBER];
0943:
0944: bias = new Position.Bias[1];
0945: }
0946:
0947: public void removeChangeListener(final ChangeListener chL) {
0948: listenerList.remove(ChangeListener.class, chL);
0949: }
0950:
0951: /**
0952: * Removes current selection, selectionTag doesn't equal null. Sets
0953: * selectionTag to null.
0954: */
0955: private void removeHighlight() {
0956: if (selectionTag == null) {
0957: return;
0958: }
0959: removeHighlight(selectionTag);
0960: selectionTag = null;
0961: }
0962:
0963: protected final synchronized void repaint() {
0964: if (component != null) {
0965: if (!isBidiDocument) {
0966: //Commented since current 2d implementation cannot properly repaint region
0967: // of 1 pixel width.
0968: //component.repaint(0, x, y, width + 1, height + 1);
0969: component.repaint(0, x - 2, y, width + 4, height + 1);
0970: } else {
0971: component.repaint(0, x - 5, y, width + 10, height + 1);
0972: }
0973: }
0974: }
0975:
0976: final void setAsynchronousMovement(final boolean b) {
0977: async = b;
0978: int i = (b) ? DefaultCaret.ALWAYS_UPDATE
0979: : DefaultCaret.UPDATE_WHEN_ON_EDT;
0980: setUpdatePolicy(i);
0981: }
0982:
0983: /**
0984: * Restarts timer for blinking, if blink on
0985: *
0986: */
0987: public void setBlinkRate(final int i) {
0988: if (i < 0) {
0989: throw new IllegalArgumentException(Messages.getString(
0990: "swing.64", i)); //$NON-NLS-1$
0991: }
0992: blinkRate = i;
0993: stopTimer(blinkTimer);
0994: if (blinkRate > 0) {
0995: setTimerDelay(blinkTimer, blinkRate);
0996: if (isVisible) {
0997: startTimer(blinkTimer);
0998: }
0999: }
1000: }
1001:
1002: public void setDot(final int i) {
1003: setDot(i, Position.Bias.Forward);
1004: return;
1005: }
1006:
1007: /**
1008: * If current JTextComponent has NavigationFilter then call
1009: * getComponent.getNavigationFilter.setDot. Otherwise, sets dot and mark to
1010: * i, sets dotBias and markBias to b. Removes highlight, if any.
1011: *
1012: * @param i new dot
1013: * @param b new dotBias
1014: */
1015:
1016: void setDot(final int i, final Position.Bias b) {
1017: if (navigationFilter == null) {
1018: internalSetDot(i, b);
1019: } else {
1020: navigationFilter.setDot(filterBypass, i, b);
1021: }
1022: }
1023:
1024: private void internalSetDot(final int i, final Position.Bias b) {
1025: dotBias = b;
1026: markBias = b;
1027: mark = i;
1028: changeDot(i);
1029: removeHighlight();
1030: }
1031:
1032: public void setMagicCaretPosition(final Point p) {
1033: magicCaretPosition = p;
1034: //stubMagicCaretPosition = p;
1035: }
1036:
1037: public void setSelectionVisible(final boolean b) {
1038: isSelectionVisible = b;
1039: if (b) {
1040: addHighlight();
1041: restoreSelection = false;
1042: } else {
1043: restoreSelection = true;
1044: removeHighlight();
1045: }
1046: }
1047:
1048: public void setUpdatePolicy(final int policy) {
1049: if (policy >= 0 && policy <= 2) {
1050: selectedPolicy = policy;
1051: } else {
1052: throw new IllegalArgumentException();
1053: }
1054: }
1055:
1056: public void setVisible(final boolean b) {
1057: isVisible = b;
1058: if (b) {
1059: startTimer(magicTimer);
1060: if (blinkRate > 0) {
1061: startTimer(blinkTimer);
1062: }
1063: } else {
1064: stopTimer(blinkTimer);
1065: stopTimer(magicTimer);
1066: }
1067: }
1068:
1069: /*
1070: * The format of the string is based on 1.5 release behavior
1071: * of BasicTextUI.BasicCaret class which can be revealed
1072: * using the following code:
1073: *
1074: * JTextArea textArea = new JTextArea();
1075: * System.out.println(textArea.getCaret());
1076: * System.out.println(textArea.getCaret().getClass().getName());
1077: */
1078: @Override
1079: public String toString() {
1080: return "Dot=(" + dot + ", " + dotBias.toString() + ") "
1081: + "Mark=(" + mark + ", " + markBias.toString() + ")";
1082: }
1083:
1084: private void updateBidiInfo() {
1085: isBidiDocument = TextUtils.isBidirectional(document);
1086: }
1087:
1088: /**
1089: * Writes object bu default, writes string representation dotBias, markBias
1090: *
1091: * @param s
1092: * @throws IOException
1093: */
1094: private void writeObject(final ObjectOutputStream s)
1095: throws IOException {
1096: s.defaultWriteObject();
1097: s.writeUTF(dotBias.toString());
1098: s.writeUTF(markBias.toString());
1099: }
1100:
1101: private PropertyChangeListener getPropertyHandler() {
1102: if (pch == null) {
1103: pch = new PropertyHandler();
1104: }
1105: return pch;
1106: }
1107:
1108: Object createTimer(final boolean isMagicTimer, final int delay) {
1109: return isMagicTimer ? new javax.swing.Timer(
1110: DEFAULT_MAGIC_DELAY, (ActionListener) getMagicAction())
1111: : new javax.swing.Timer(delay,
1112: (ActionListener) getBlinkAction());
1113: }
1114:
1115: void startTimer(final Object timer) {
1116: ((Timer) timer).start();
1117: }
1118:
1119: void setTimerDelay(final Object timer, final int delay) {
1120: ((Timer) timer).setDelay(delay);
1121: }
1122:
1123: void stopTimer(final Object timer) {
1124: ((javax.swing.Timer) timer).stop();
1125: }
1126:
1127: Object getMagicAction() {
1128: if (magicAction == null) {
1129: magicAction = new AbstractAction() {
1130: public void actionPerformed(final ActionEvent e) {
1131: if (magicCaretPosition == null) {
1132: magicCaretPosition = new Point(x, y);
1133: }
1134: }
1135: };
1136: }
1137: return magicAction;
1138: }
1139:
1140: Object getBlinkAction() {
1141: if (blinkAction == null) {
1142: blinkAction = new AbstractAction() {
1143: public void actionPerformed(final ActionEvent e) {
1144: shouldDraw = !shouldDraw;
1145: EventQueue.invokeLater(new Runnable() {
1146: public void run() {
1147: repaint();
1148: }
1149: });
1150: }
1151: };
1152: }
1153: return blinkAction;
1154: }
1155:
1156: boolean isRestoreSelectionCondition(final Component c) {
1157: return SwingUtilities.windowForComponent(c) == SwingUtilities
1158: .windowForComponent(component);
1159: }
1160:
1161: Color getCaretColor() {
1162: JTextComponent textComponent = getComponent();
1163: Color componentsColor = textComponent.getCaretColor();
1164: return componentsColor != null ? componentsColor
1165: : DEF_CARET_COLOR;
1166: }
1167:
1168: Color getSelectionColor() {
1169: JTextComponent textComponent = getComponent();
1170: Color componentsColor = textComponent.getSelectionColor();
1171: return componentsColor != null ? componentsColor
1172: : DEF_SEL_COLOR;
1173: }
1174:
1175: boolean isComponentEditable() {
1176: return ((JTextComponent) component).isEditable()
1177: && component.isEnabled();
1178: }
1179:
1180: boolean isDragEnabled() {
1181: return ((JTextComponent) component).getDragEnabled();
1182: }
1183:
1184: Object addHighlight(final int p0, final int p1) {
1185: if (highlighter != null && component.isEnabled()) {
1186: Object result = null;
1187: try {
1188: result = highlighter.addHighlight(p0, p1, painter);
1189: } catch (BadLocationException e) {
1190: }
1191: return result;
1192: } else {
1193: return null;
1194: }
1195: }
1196:
1197: void changeHighlight(final Object tag, final int p0, final int p1) {
1198: if (highlighter != null) {
1199: try {
1200: highlighter.changeHighlight(tag, p0, p1);
1201: } catch (final BadLocationException e) {
1202: }
1203: }
1204: }
1205:
1206: void removeHighlight(final Object tag) {
1207: if (highlighter != null) {
1208: highlighter.removeHighlight(tag);
1209: }
1210: }
1211:
1212: void setMagicCaretPosition(final int pos, final int direction,
1213: final Point oldPoint) {
1214: try {
1215: Point newPoint = null;
1216: if (direction == SwingConstants.SOUTH
1217: || direction == SwingConstants.NORTH) {
1218: if (oldPoint == null) {
1219: Rectangle r = textKit.modelToView(pos,
1220: Position.Bias.Forward).getBounds();
1221: newPoint = new Point(r.x, r.y);
1222: } else {
1223: newPoint = oldPoint;
1224: }
1225: }
1226:
1227: setMagicCaretPosition(newPoint);
1228: } catch (BadLocationException e) {
1229: e.printStackTrace();
1230: }
1231: }
1232: }
|