0001: /*
0002: * Sun Public License Notice
0003: *
0004: * The contents of this file are subject to the Sun Public License
0005: * Version 1.0 (the "License"). You may not use this file except in
0006: * compliance with the License. A copy of the License is available at
0007: * http://www.sun.com/
0008: *
0009: * The Original Code is NetBeans. The Initial Developer of the Original
0010: * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
0011: * Microsystems, Inc. All Rights Reserved.
0012: */
0013:
0014: package org.netbeans.editor;
0015:
0016: import java.awt.Color;
0017: import java.awt.Font;
0018: import java.awt.Graphics;
0019: import java.awt.Point;
0020: import java.awt.Rectangle;
0021: import java.awt.event.ActionEvent;
0022: import java.awt.event.ActionListener;
0023: import java.awt.event.FocusEvent;
0024: import java.awt.event.FocusListener;
0025: import java.awt.event.InputEvent;
0026: import java.awt.event.MouseEvent;
0027: import java.awt.event.MouseListener;
0028: import java.awt.event.MouseMotionListener;
0029: import java.beans.PropertyChangeEvent;
0030: import java.beans.PropertyChangeListener;
0031:
0032: import javax.swing.Action;
0033: import javax.swing.SwingUtilities;
0034: import javax.swing.Timer;
0035: import javax.swing.event.ChangeEvent;
0036: import javax.swing.event.ChangeListener;
0037: import javax.swing.event.DocumentEvent;
0038: import javax.swing.event.DocumentListener;
0039: import javax.swing.event.EventListenerList;
0040: import javax.swing.text.BadLocationException;
0041: import javax.swing.text.Caret;
0042: import javax.swing.text.Document;
0043: import javax.swing.text.JTextComponent;
0044:
0045: /**
0046: * Caret implementation
0047: *
0048: * @author Miloslav Metelka
0049: * @version 1.00
0050: */
0051:
0052: public class BaseCaret extends Rectangle implements Caret,
0053: FocusListener, MouseListener, MouseMotionListener,
0054: PropertyChangeListener, DocumentListener, ActionListener,
0055: SettingsChangeListener {
0056:
0057: /** Caret type representing block covering current character */
0058: public static final String BLOCK_CARET = "block-caret"; // NOI18N
0059:
0060: /** Default caret type */
0061: public static final String LINE_CARET = "line-caret"; // NOI18N
0062:
0063: /** One dot thin line compatible with Swing default caret */
0064: public static final String THIN_LINE_CARET = "thin-line-caret"; // NOI18N
0065:
0066: private static final boolean debugCaretFocus = Boolean
0067: .getBoolean("netbeans.debug.editor.caret.focus"); // NOI18N
0068:
0069: /** Component this caret is bound to */
0070: protected JTextComponent component;
0071:
0072: /**
0073: * Position of the caret on the screen. This helps to compute caret position
0074: * on the next after jump.
0075: */
0076: Point magicCaretPosition;
0077:
0078: /** Draw mark designating the position of the caret. */
0079: MarkFactory.DrawMark caretMark = new MarkFactory.CaretMark();
0080:
0081: /** Draw mark that supports caret mark in creating selection */
0082: MarkFactory.DrawMark selectionMark = new MarkFactory.DrawMark(
0083: DrawLayerFactory.CARET_LAYER_NAME, null);
0084:
0085: /** Is the caret visible */
0086: boolean visible;
0087:
0088: /**
0089: * Caret is visible and the blink is visible. Both must be true in order to
0090: * show the caret.
0091: */
0092: boolean blinkVisible;
0093:
0094: /** Is the selection currently visible? */
0095: boolean selectionVisible;
0096:
0097: /** Listeners */
0098: protected EventListenerList listenerList = new EventListenerList();
0099:
0100: /** Timer used for blinking the caret */
0101: protected Timer flasher;
0102:
0103: /** Type of the caret */
0104: String type;
0105:
0106: /** Is the caret italic for italic fonts */
0107: boolean italic;
0108:
0109: private int xPoints[] = new int[4];
0110: private int yPoints[] = new int[4];
0111: private Action selectWordAction;
0112: private Action selectLineAction;
0113:
0114: /**
0115: * Change event. Only one instance needed because it has only source
0116: * property
0117: */
0118: protected ChangeEvent changeEvent;
0119:
0120: private static char emptyDotChar[] = { ' ' };
0121:
0122: /** Dot array of one character under caret */
0123: protected char dotChar[] = emptyDotChar;
0124:
0125: private boolean overwriteMode;
0126:
0127: /**
0128: * Remembering document on which caret listens avoids duplicate listener
0129: * addition to SwingPropertyChangeSupport due to the bug 4200280
0130: */
0131: private BaseDocument listenDoc;
0132:
0133: /** Caret draw graphics */
0134: CaretDG caretDG = new CaretDG();
0135:
0136: /**
0137: * Font of the text underlying the caret. It can be used in caret painting.
0138: */
0139: protected Font afterCaretFont;
0140:
0141: /** Font of the text right before the caret */
0142: protected Font beforeCaretFont;
0143:
0144: /**
0145: * Foreground color of the text underlying the caret. It can be used in
0146: * caret painting.
0147: */
0148: protected Color textForeColor;
0149:
0150: /**
0151: * Background color of the text underlying the caret. It can be used in
0152: * caret painting.
0153: */
0154: protected Color textBackColor;
0155:
0156: private transient FocusListener focusListener;
0157:
0158: private transient boolean nextPaintUpdate;
0159: private transient Rectangle nextPaintScrollRect;
0160: private transient int nextPaintScrollPolicy;
0161:
0162: static final long serialVersionUID = -9113841520331402768L;
0163:
0164: public BaseCaret() {
0165: Settings.addSettingsChangeListener(this );
0166: }
0167:
0168: /**
0169: * Called when settings were changed. The method is called also in
0170: * constructor, so the code must count with the evt being null.
0171: */
0172: public void settingsChange(SettingsChangeEvent evt) {
0173: if (evt != null
0174: && SettingsNames.CARET_BLINK_RATE.equals(evt
0175: .getSettingName())) {
0176: Object value = evt.getNewValue();
0177: if (value instanceof Integer) {
0178: setBlinkRate(((Integer) value).intValue());
0179: }
0180: }
0181: updateType();
0182: }
0183:
0184: void updateType() {
0185: JTextComponent c = component;
0186: if (c != null) {
0187: Class kitClass = Utilities.getKitClass(c);
0188: String newType;
0189: boolean newItalic;
0190: Color caretColor;
0191: if (overwriteMode) {
0192: newType = SettingsUtil.getString(kitClass,
0193: SettingsNames.CARET_TYPE_OVERWRITE_MODE,
0194: LINE_CARET);
0195: newItalic = SettingsUtil.getBoolean(kitClass,
0196: SettingsNames.CARET_ITALIC_OVERWRITE_MODE,
0197: false);
0198: caretColor = getColor(
0199: kitClass,
0200: SettingsNames.CARET_COLOR_OVERWRITE_MODE,
0201: SettingsDefaults.defaultCaretColorOvwerwriteMode);
0202:
0203: } else { // insert mode
0204: newType = SettingsUtil.getString(kitClass,
0205: SettingsNames.CARET_TYPE_INSERT_MODE,
0206: LINE_CARET);
0207: newItalic = SettingsUtil.getBoolean(kitClass,
0208: SettingsNames.CARET_ITALIC_INSERT_MODE, false);
0209: caretColor = getColor(kitClass,
0210: SettingsNames.CARET_COLOR_INSERT_MODE,
0211: SettingsDefaults.defaultCaretColorInsertMode);
0212: }
0213:
0214: this .type = newType;
0215: this .italic = newItalic;
0216: c.setCaretColor(caretColor);
0217:
0218: dispatchUpdate();
0219: }
0220: }
0221:
0222: private static Color getColor(Class kitClass, String settingName,
0223: Color defaultValue) {
0224: Object value = Settings.getValue(kitClass, settingName);
0225: return (value instanceof Color) ? (Color) value : defaultValue;
0226: }
0227:
0228: /** Called when UI is being installed into JTextComponent */
0229: public void install(JTextComponent c) {
0230: component = c;
0231: component.addPropertyChangeListener(this );
0232: focusListener = new FocusHandler(this );
0233: component.addFocusListener(focusListener);
0234: component.addMouseListener(this );
0235: component.addMouseMotionListener(this );
0236:
0237: EditorUI editorUI = Utilities.getEditorUI(component);
0238: editorUI.addLayer(new DrawLayerFactory.CaretLayer(),
0239: DrawLayerFactory.CARET_LAYER_VISIBILITY);
0240: caretMark.setEditorUI(editorUI);
0241: selectionMark.setEditorUI(editorUI);
0242: editorUI.addPropertyChangeListener(this );
0243:
0244: BaseDocument doc = Utilities.getDocument(c);
0245: if (doc != null) {
0246: modelChanged(null, doc);
0247: }
0248:
0249: if (component.hasFocus()) {
0250: focusGained(null); // emulate focus gained
0251: if (debugCaretFocus) {
0252: System.err
0253: .println("Component has focus, calling focusGained() on doc="
0254: + component.getDocument().getProperty(
0255: Document.TitleProperty));
0256: }
0257:
0258: }
0259: }
0260:
0261: /** Called when UI is being removed from JTextComponent */
0262: public void deinstall(JTextComponent c) {
0263: component = null; // invalidate
0264:
0265: if (flasher != null) {
0266: setBlinkRate(0);
0267: }
0268:
0269: Utilities.getEditorUI(c).removeLayer(
0270: DrawLayerFactory.CARET_LAYER_NAME);
0271:
0272: c.removeMouseMotionListener(this );
0273: c.removeMouseListener(this );
0274: if (focusListener != null) {
0275: c.removeFocusListener(focusListener);
0276: focusListener = null;
0277: }
0278: c.removePropertyChangeListener(this );
0279:
0280: modelChanged(listenDoc, null);
0281: }
0282:
0283: protected void modelChanged(BaseDocument oldDoc, BaseDocument newDoc) {
0284: // [PENDING] !!! this body looks strange because of the bug 4200280
0285: if (oldDoc != null && listenDoc == oldDoc) {
0286: oldDoc.removeDocumentListener(this );
0287:
0288: try {
0289: caretMark.remove();
0290: selectionMark.remove();
0291: } catch (InvalidMarkException e) {
0292: if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
0293: e.printStackTrace();
0294: }
0295: }
0296:
0297: listenDoc = null;
0298: }
0299:
0300: if (newDoc != null) {
0301: if (listenDoc != null) {
0302: // deinstall from the listenDoc first
0303: modelChanged(listenDoc, null);
0304: }
0305:
0306: newDoc.addDocumentListener(this );
0307: listenDoc = newDoc;
0308:
0309: try {
0310: Utilities.insertMark(newDoc, caretMark, 0);
0311: Utilities.insertMark(newDoc, selectionMark, 0);
0312: } catch (InvalidMarkException e) {
0313: if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
0314: e.printStackTrace();
0315: }
0316: } catch (BadLocationException e) {
0317: if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
0318: e.printStackTrace();
0319: }
0320: }
0321:
0322: settingsChange(null); // update settings
0323:
0324: SwingUtilities.invokeLater(new Runnable() {
0325: public void run() {
0326: updateType();
0327: }
0328: });
0329:
0330: }
0331: }
0332:
0333: /** Renders the caret */
0334: public void paint(Graphics g) {
0335: if (nextPaintUpdate) { // need to update caret first
0336: // running in AWT -> no dispatching
0337: nextPaintUpdate = false; // no further updating
0338: update(nextPaintScrollRect, nextPaintScrollPolicy);
0339:
0340: // fix for issue #13049
0341: nextPaintScrollRect = null;
0342:
0343: }
0344:
0345: if (visible && blinkVisible) {
0346: paintCustomCaret(g);
0347: }
0348: }
0349:
0350: protected void paintCustomCaret(Graphics g) {
0351: JTextComponent c = component;
0352: if (c != null) {
0353: EditorUI editorUI = Utilities.getEditorUI(c);
0354: if (THIN_LINE_CARET.equals(type)) { // thin line caret
0355: g.setColor(c.getCaretColor());
0356: int upperX = x;
0357: if (beforeCaretFont != null
0358: && beforeCaretFont.isItalic() && italic) {
0359: upperX += Math
0360: .tan(beforeCaretFont.getItalicAngle())
0361: * height;
0362: }
0363: g.drawLine((int) upperX, y, x, (y + height - 1));
0364:
0365: } else if (BLOCK_CARET.equals(type)) { // block caret
0366: g.setColor(c.getCaretColor());
0367: g.setFont(afterCaretFont);
0368: if (afterCaretFont.isItalic() && italic) { // paint italic
0369: // caret
0370: int upperX = (int) (x + Math.tan(afterCaretFont
0371: .getItalicAngle())
0372: * height);
0373: xPoints[0] = upperX;
0374: yPoints[0] = y;
0375: xPoints[1] = upperX + width;
0376: yPoints[1] = y;
0377: xPoints[2] = x + width;
0378: yPoints[2] = y + height - 1;
0379: xPoints[3] = x;
0380: yPoints[3] = y + height - 1;
0381: g.fillPolygon(xPoints, yPoints, 4);
0382:
0383: } else { // paint non-italic caret
0384: g.fillRect(x, y, width, height);
0385: }
0386:
0387: if (!Character.isWhitespace(dotChar[0])) {
0388: g.setColor(Color.white);
0389: int ascent = FontMetricsCache.getFontMetrics(
0390: afterCaretFont, c).getAscent();
0391: g.drawChars(dotChar, 0, 1, x, y
0392: + editorUI.getLineAscent());
0393: }
0394:
0395: } else { // two dot line caret
0396: g.setColor(c.getCaretColor());
0397: int blkWidth = 2;
0398: if (beforeCaretFont != null
0399: && beforeCaretFont.isItalic() && italic) {
0400: int upperX = (int) (x + Math.tan(beforeCaretFont
0401: .getItalicAngle())
0402: * height);
0403: xPoints[0] = upperX;
0404: yPoints[0] = y;
0405: xPoints[1] = upperX + blkWidth;
0406: yPoints[1] = y;
0407: xPoints[2] = x + blkWidth;
0408: yPoints[2] = y + height - 1;
0409: xPoints[3] = x;
0410: yPoints[3] = y + height - 1;
0411: g.fillPolygon(xPoints, yPoints, 4);
0412: } else { // paint non-italic caret
0413: g.fillRect(x, y, blkWidth, height - 1);
0414: }
0415: }
0416: }
0417: }
0418:
0419: /** Update the caret's visual position */
0420: void dispatchUpdate() {
0421: dispatchUpdate(null, EditorUI.SCROLL_MOVE);
0422: }
0423:
0424: void dispatchUpdate(final Rectangle scrollRect,
0425: final int scrollPolicy) {
0426: JTextComponent c = component;
0427: EditorUI editorUI = Utilities.getEditorUI(c);
0428: if (!editorUI.isFontsInited()) { // fonts not yet initialized
0429: if (scrollRect != null) { // do not hide the "really" scrolling
0430: // requests
0431: nextPaintScrollRect = scrollRect;
0432: }
0433: nextPaintScrollPolicy = scrollPolicy;
0434: nextPaintUpdate = true;
0435: return;
0436: }
0437:
0438: /*
0439: * part of fix of #18860 - Using runInEventDispatchThread() in AWT
0440: * thread means that the code is executed immediately which can lead to
0441: * problems once the insert/remove in document is performed because the
0442: * update() uses views to find out the visual position and if the views
0443: * doc listener is added AFTER the caret's listener then the views are
0444: * not updated yet. Using SwingUtilities.invokeLater() should solve the
0445: * problem although the view extent could flip once the extent would be
0446: * explicitely scrolled to area that does not cover the caret's
0447: * rectangle. It needs to be tested so that it does not happen.
0448: */
0449: // Utilities.runInEventDispatchThread(
0450: SwingUtilities.invokeLater(new Runnable() {
0451: public void run() {
0452: JTextComponent c2 = component;
0453: if (c2 != null) {
0454: BaseDocument doc = Utilities.getDocument(c2);
0455: if (doc != null) {
0456: doc.readLock();
0457: try {
0458: update(scrollRect, scrollPolicy);
0459: } finally {
0460: doc.readUnlock();
0461: }
0462: }
0463: }
0464:
0465: }
0466: });
0467: }
0468:
0469: /**
0470: * Update the caret. The document is read-locked while calling this method.
0471: *
0472: * @param scrollRect
0473: * rectangle that should be visible after the updating of the
0474: * caret. It can be null to do no scrolling (only update caret)
0475: * or it can be caret rectangle to update the caret and make it
0476: * visible or some other rectangle to guarantee that the
0477: * rectangle will be visible.
0478: * @param scrollPolicy
0479: * scrolling policy as defined in EditorUI. It has no meaning if
0480: * <code>scrollRect</code> is null.
0481: */
0482: protected void update(Rectangle scrollRect, int scrollPolicy) {
0483: JTextComponent c = component;
0484: if (c != null) {
0485: BaseTextUI ui = (BaseTextUI) c.getUI();
0486: EditorUI editorUI = ui.getEditorUI();
0487: BaseDocument doc = Utilities.getDocument(c);
0488: if (doc != null) {
0489: Rectangle oldCaretRect = new Rectangle(this );
0490: if (italic) { // caret is italic - add char height to the
0491: // width of the rect
0492: oldCaretRect.width += oldCaretRect.height;
0493: }
0494:
0495: int dot = getDot();
0496: try {
0497: ui.modelToViewDG(dot, caretDG);
0498: } catch (BadLocationException e) {
0499: if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
0500: e.printStackTrace();
0501: }
0502: }
0503: resetBlink();
0504:
0505: if (scrollRect == null
0506: || !editorUI.scrollRectToVisibleFragile(
0507: scrollRect, scrollPolicy)) {
0508: oldCaretRect.add(this ); // adds the NEW caret rect -
0509: // important!
0510: c.repaint(oldCaretRect);
0511: }
0512: }
0513: }
0514: }
0515:
0516: /**
0517: * Redefine to Object.equals() to prevent defaulting to Rectangle.equals()
0518: * which would cause incorrect firing
0519: */
0520: public boolean equals(Object o) {
0521: return (this == o);
0522: }
0523:
0524: /** Adds listener to track when caret position was changed */
0525: public void addChangeListener(ChangeListener l) {
0526: listenerList.add(ChangeListener.class, l);
0527: }
0528:
0529: /** Removes listeners to caret position changes */
0530: public void removeChangeListener(ChangeListener l) {
0531: listenerList.remove(ChangeListener.class, l);
0532: }
0533:
0534: /** Notifies listeners that caret position has changed */
0535: protected void fireStateChanged() {
0536: Object listeners[] = listenerList.getListenerList();
0537: for (int i = listeners.length - 2; i >= 0; i -= 2) {
0538: if (listeners[i] == ChangeListener.class) {
0539: if (changeEvent == null) {
0540: changeEvent = new ChangeEvent(this );
0541: }
0542: ((ChangeListener) listeners[i + 1])
0543: .stateChanged(changeEvent);
0544: }
0545: }
0546: }
0547:
0548: /** Is the caret currently visible */
0549: public final boolean isVisible() {
0550: return visible;
0551: }
0552:
0553: protected void setVisibleImpl(boolean v) {
0554: synchronized (this ) {
0555: Timer t = flasher;
0556: if (t != null) {
0557: if (visible) {
0558: t.stop();
0559: }
0560: if (v) {
0561: t.start();
0562: } else {
0563: t.stop();
0564: }
0565: }
0566: visible = v;
0567: }
0568: JTextComponent c = component;
0569: if (c != null) {
0570: Rectangle repaintRect = this ;
0571: if (italic) {
0572: repaintRect = new Rectangle(this );
0573: repaintRect.width += repaintRect.height;
0574: }
0575: c.repaint(repaintRect);
0576: }
0577: }
0578:
0579: synchronized void resetBlink() {
0580: Timer t = flasher;
0581: if (t != null) {
0582: t.stop();
0583: blinkVisible = true;
0584: if (isVisible()) {
0585: t.start();
0586: }
0587: }
0588: }
0589:
0590: /** Sets the caret visibility */
0591: public void setVisible(final boolean v) {
0592: SwingUtilities.invokeLater(new Runnable() {
0593: public void run() {
0594: setVisibleImpl(v);
0595: }
0596: });
0597: }
0598:
0599: /** Is the selection visible? */
0600: public final boolean isSelectionVisible() {
0601: return selectionVisible;
0602: }
0603:
0604: /** Sets the selection visibility */
0605: public void setSelectionVisible(boolean v) {
0606: if (selectionVisible == v) {
0607: return;
0608: }
0609: JTextComponent c = component;
0610: if (c != null) {
0611: selectionVisible = v;
0612: if (selectionVisible) {
0613: int caretPos = getDot();
0614: int selPos = getMark();
0615: boolean selMarkFirst = (selPos < caretPos);
0616: selectionMark.activateLayer = selMarkFirst;
0617: caretMark.activateLayer = !selMarkFirst
0618: && !(selPos == caretPos);
0619: } else { // make selection invisible
0620: caretMark.activateLayer = false;
0621: selectionMark.activateLayer = false;
0622: }
0623:
0624: // repaint the block
0625: BaseTextUI ui = (BaseTextUI) c.getUI();
0626: try {
0627: ui.getEditorUI().repaintBlock(caretMark.getOffset(),
0628: selectionMark.getOffset());
0629: } catch (BadLocationException e) {
0630: if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
0631: e.printStackTrace();
0632: }
0633: } catch (InvalidMarkException e) {
0634: if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
0635: e.printStackTrace();
0636: }
0637: }
0638:
0639: }
0640: }
0641:
0642: /**
0643: * Saves the current caret position. This is used when caret up or down
0644: * actions occur, moving between lines that have uneven end positions.
0645: *
0646: * @param p
0647: * the Point to use for the saved position
0648: */
0649: public void setMagicCaretPosition(Point p) {
0650: magicCaretPosition = p;
0651: }
0652:
0653: /** Get position used to mark begining of the selected block */
0654: public final Point getMagicCaretPosition() {
0655: return magicCaretPosition;
0656: }
0657:
0658: /**
0659: * Sets the caret blink rate.
0660: *
0661: * @param rate
0662: * blink rate in milliseconds, 0 means no blink
0663: */
0664: public synchronized void setBlinkRate(int rate) {
0665: if (flasher == null && rate > 0) {
0666: flasher = new Timer(rate, new WeakTimerListener(this ));
0667: }
0668: if (flasher != null) {
0669: if (rate > 0) {
0670: if (flasher.getDelay() != rate) {
0671: flasher.setDelay(rate);
0672: }
0673: } else { // zero rate - don't blink
0674: flasher.stop();
0675: flasher.removeActionListener(this );
0676: flasher = null;
0677: }
0678: }
0679: }
0680:
0681: /** Returns blink rate of the caret or 0 if caret doesn't blink */
0682: public synchronized int getBlinkRate() {
0683: return (flasher != null) ? flasher.getDelay() : 0;
0684: }
0685:
0686: /** Gets the current position of the caret */
0687: public int getDot() {
0688: if (component != null) {
0689: try {
0690: return caretMark.getOffset();
0691: } catch (InvalidMarkException e) {
0692: }
0693: }
0694: return 0;
0695: }
0696:
0697: /**
0698: * Gets the current position of the selection mark. If there's a selection
0699: * this position will be different from the caret position.
0700: */
0701: public int getMark() {
0702: if (component != null) {
0703: if (selectionVisible) {
0704: try {
0705: return selectionMark.getOffset();
0706: } catch (InvalidMarkException e) {
0707: }
0708: } else { // selection not visible
0709: return getDot(); // must return same position as dot
0710: }
0711: }
0712: return 0;
0713: }
0714:
0715: public void setDot(int offset) {
0716: setDot(offset, this , EditorUI.SCROLL_DEFAULT);
0717: }
0718:
0719: /**
0720: * Sets the caret position to some position. This causes removal of the
0721: * active selection.
0722: */
0723: public void setDot(int offset, Rectangle scrollRect,
0724: int scrollPolicy) {
0725: JTextComponent c = component;
0726: if (c != null) {
0727: setSelectionVisible(false);
0728: BaseDocument doc = (BaseDocument) c.getDocument();
0729: if (doc != null) {
0730: try {
0731: Utilities.moveMark(doc, caretMark, offset);
0732: } catch (BadLocationException e) {
0733: // setting the caret to wrong position leaves it at current
0734: // position
0735: } catch (InvalidMarkException e) {
0736: // Caret not installed or inside the initial-read
0737: }
0738: }
0739: fireStateChanged();
0740: dispatchUpdate(scrollRect, scrollPolicy);
0741: }
0742: }
0743:
0744: public void moveDot(int offset) {
0745: moveDot(offset, this , EditorUI.SCROLL_MOVE);
0746: }
0747:
0748: /** Makes selection by moving dot but leaving mark */
0749: public void moveDot(int offset, Rectangle scrollRect,
0750: int scrollPolicy) {
0751: JTextComponent c = component;
0752: if (c != null) {
0753: BaseDocument doc = (BaseDocument) c.getDocument();
0754: try {
0755: int oldCaretPos = getDot();
0756: if (offset == oldCaretPos) { // no change
0757: return;
0758: }
0759: int selPos; // current position of selection mark
0760:
0761: if (selectionVisible) {
0762: selPos = selectionMark.getOffset();
0763: } else {
0764: Utilities.moveMark(doc, selectionMark, oldCaretPos);
0765: selPos = oldCaretPos;
0766: }
0767:
0768: Utilities.moveMark(doc, caretMark, offset);
0769: if (selectionVisible) { // selection already visible
0770: boolean selMarkFirst = (selPos < offset);
0771: selectionMark.activateLayer = selMarkFirst;
0772: caretMark.activateLayer = !selMarkFirst
0773: && !(selPos == offset);
0774: Utilities.getEditorUI(c).repaintBlock(oldCaretPos,
0775: offset);
0776: if (selPos == offset) { // same positions -> invisible
0777: // selection
0778: setSelectionVisible(false);
0779: }
0780:
0781: } else { // selection not yet visible
0782: setSelectionVisible(true);
0783: }
0784: } catch (BadLocationException e) {
0785: // position is incorrect
0786: } catch (InvalidMarkException e) {
0787: if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
0788: e.printStackTrace();
0789: }
0790: }
0791: fireStateChanged();
0792: dispatchUpdate(scrollRect, scrollPolicy);
0793: }
0794: }
0795:
0796: // DocumentListener methods
0797: public void insertUpdate(DocumentEvent evt) {
0798: JTextComponent c = component;
0799: if (c != null) {
0800: BaseDocument doc = (BaseDocument) component.getDocument();
0801: BaseDocumentEvent bevt = (BaseDocumentEvent) evt;
0802: if ((bevt.isInUndo() || bevt.isInRedo())
0803: && component == Utilities.getLastActiveComponent()) {
0804: // in undo mode and current component
0805: setDot(evt.getOffset() + evt.getLength());
0806: } else {
0807: fireStateChanged();
0808: if (evt.getLength() == 0) {
0809: updateType();
0810: setVisible(false);
0811: setVisible(c.isEnabled() && c.hasFocus());
0812: }
0813:
0814: // Scroll to caret only for component with focus
0815: dispatchUpdate(c.hasFocus() ? this : null,
0816: EditorUI.SCROLL_MOVE);
0817: }
0818: }
0819: }
0820:
0821: public void removeUpdate(DocumentEvent evt) {
0822: JTextComponent c = component;
0823: if (c != null) {
0824: BaseDocument doc = (BaseDocument) c.getDocument();
0825: // make selection invisible if removal shrinked block to zero size
0826: if (selectionVisible && (getDot() == getMark())) {
0827: setSelectionVisible(false);
0828: }
0829:
0830: BaseDocumentEvent bevt = (BaseDocumentEvent) evt;
0831: if ((bevt.isInUndo() || bevt.isInRedo())
0832: && c == Utilities.getLastActiveComponent()) {
0833: // in undo mode and current component
0834: setDot(evt.getOffset());
0835: } else {
0836: fireStateChanged();
0837: // Scroll to caret only for component with focus
0838: dispatchUpdate(c.hasFocus() ? this : null,
0839: EditorUI.SCROLL_MOVE);
0840: }
0841: }
0842: }
0843:
0844: public void changedUpdate(DocumentEvent evt) {
0845: dispatchUpdate();
0846: }
0847:
0848: // FocusListener methods
0849: public void focusGained(FocusEvent evt) {
0850: if (debugCaretFocus) {
0851: System.err.println("BaseCaret.focusGained() in doc="
0852: + component.getDocument().getProperty(
0853: Document.TitleProperty));
0854: }
0855:
0856: JTextComponent c = component;
0857: if (c != null) {
0858: updateType();
0859: setVisible(c.isEnabled()); // invisible caret if disabled
0860: }
0861: }
0862:
0863: public void focusLost(FocusEvent evt) {
0864: if (debugCaretFocus) {
0865: System.err.println("BaseCaret.focusLost() in doc="
0866: + component.getDocument().getProperty(
0867: Document.TitleProperty));
0868: }
0869:
0870: setVisible(false);
0871: }
0872:
0873: // MouseListener methods
0874: public void mouseClicked(MouseEvent evt) {
0875: JTextComponent c = component;
0876: if (c != null) {
0877: if (SwingUtilities.isLeftMouseButton(evt)) {
0878: if (evt.getClickCount() == 2) {
0879: if (selectWordAction == null) {
0880: BaseTextUI ui = (BaseTextUI) c.getUI();
0881: selectWordAction = ((BaseKit) ui
0882: .getEditorKit(c))
0883: .getActionByName(BaseKit.selectWordAction);
0884: }
0885: selectWordAction.actionPerformed(null);
0886: } else if (evt.getClickCount() == 3) {
0887: if (selectLineAction == null) {
0888: BaseTextUI ui = (BaseTextUI) c.getUI();
0889: selectLineAction = ((BaseKit) ui
0890: .getEditorKit(c))
0891: .getActionByName(BaseKit.selectLineAction);
0892: }
0893: selectLineAction.actionPerformed(null);
0894: }
0895: }
0896: }
0897: }
0898:
0899: public void mousePressed(MouseEvent evt) {
0900: JTextComponent c = component;
0901: if (c != null) {
0902: Utilities.getEditorUI(c).getWordMatch().clear(); // [PENDING]
0903: // should be
0904: // done cleanly
0905:
0906: // Position the cursor at the appropriate place in the document
0907: if ((SwingUtilities.isLeftMouseButton(evt) && (evt
0908: .getModifiers() & (InputEvent.META_MASK | InputEvent.ALT_MASK)) == 0)
0909: || !isSelectionVisible()) {
0910: int offset = ((BaseTextUI) c.getUI()).viewToModel(c,
0911: evt.getX(), evt.getY());
0912: if (offset >= 0) {
0913: if ((evt.getModifiers() & InputEvent.SHIFT_MASK) != 0) {
0914: moveDot(offset);
0915: } else {
0916: setDot(offset);
0917: }
0918: setMagicCaretPosition(null);
0919: }
0920: if (c.isEnabled()) {
0921: c.requestFocus();
0922: }
0923: }
0924: }
0925: }
0926:
0927: public void mouseReleased(MouseEvent evt) {
0928: }
0929:
0930: public void mouseEntered(MouseEvent evt) {
0931: }
0932:
0933: public void mouseExited(MouseEvent evt) {
0934: }
0935:
0936: // MouseMotionListener methods
0937: public void mouseDragged(MouseEvent evt) {
0938: JTextComponent c = component;
0939: if (SwingUtilities.isLeftMouseButton(evt)) {
0940: if (c != null) {
0941: int offset = ((BaseTextUI) c.getUI()).viewToModel(c,
0942: evt.getX(), evt.getY());
0943: // fix for #15204
0944: if (offset == -1)
0945: offset = 0;
0946: moveDot(offset);
0947: }
0948: }
0949: }
0950:
0951: public void mouseMoved(MouseEvent evt) {
0952: }
0953:
0954: // PropertyChangeListener methods
0955: public void propertyChange(PropertyChangeEvent evt) {
0956: String propName = evt.getPropertyName();
0957: if ("document".equals(propName)) {
0958: BaseDocument oldDoc = (evt.getOldValue() instanceof BaseDocument) ? (BaseDocument) evt
0959: .getOldValue()
0960: : null;
0961: BaseDocument newDoc = (evt.getNewValue() instanceof BaseDocument) ? (BaseDocument) evt
0962: .getNewValue()
0963: : null;
0964: modelChanged(oldDoc, newDoc);
0965: } else if (EditorUI.OVERWRITE_MODE_PROPERTY.equals(propName)) {
0966: Boolean b = (Boolean) evt.getNewValue();
0967: overwriteMode = (b != null) ? b.booleanValue() : false;
0968: updateType();
0969: }
0970: }
0971:
0972: // ActionListener methods
0973: /** Fired when blink timer fires */
0974: public void actionPerformed(ActionEvent evt) {
0975: JTextComponent c = component;
0976: if (c != null) {
0977: blinkVisible = !blinkVisible;
0978: Rectangle repaintRect = this ;
0979: if (italic) {
0980: repaintRect = new Rectangle(this );
0981: repaintRect.width += repaintRect.height;
0982: }
0983: c.repaint(repaintRect);
0984: }
0985: }
0986:
0987: /**
0988: * Caret draw graphics used to update the caret position and the character
0989: * the caret sits on.
0990: */
0991: final class CaretDG extends DrawGraphics.SimpleDG {
0992:
0993: Font previousFont;
0994:
0995: public void setFont(Font font) {
0996: previousFont = getFont(); // get the current font before change
0997: super .setFont(font);
0998: }
0999:
1000: public boolean targetOffsetReached(int offset, char ch, int x,
1001: int charWidth, DrawContext ctx) {
1002:
1003: JTextComponent c = BaseCaret.this .component;
1004: if (c != null) {
1005: BaseCaret.this .beforeCaretFont = (offset == ctx
1006: .getFragmentOffset()) ? previousFont : ctx
1007: .getFont();
1008: BaseCaret.this .afterCaretFont = ctx.getFont();
1009:
1010: BaseCaret.this .x = x;
1011: BaseCaret.this .y = this .getY();
1012: BaseCaret.this .width = charWidth;
1013: BaseCaret.this .height = Utilities.getEditorUI(c)
1014: .getLineHeight();
1015: BaseCaret.this .textForeColor = ctx.getForeColor();
1016: BaseCaret.this .textBackColor = ctx.getBackColor();
1017: BaseCaret.this .dotChar[0] = ch;
1018: }
1019: return false;
1020: }
1021:
1022: }
1023:
1024: private static class FocusHandler implements FocusListener {
1025: private transient FocusListener fl;
1026:
1027: FocusHandler(FocusListener fl) {
1028: this .fl = fl;
1029: }
1030:
1031: public void focusGained(FocusEvent e) {
1032: fl.focusGained(e);
1033: }
1034:
1035: public void focusLost(FocusEvent e) {
1036: fl.focusLost(e);
1037: }
1038: }
1039:
1040: }
|