1. /*
  2. * @(#)DefaultCaret.java 1.80 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.text;
  8. import java.awt.*;
  9. import java.awt.event.*;
  10. import java.beans.*;
  11. import java.awt.event.ActionEvent;
  12. import java.awt.event.ActionListener;
  13. import java.io.*;
  14. import javax.swing.*;
  15. import javax.swing.event.*;
  16. import javax.swing.plaf.*;
  17. /**
  18. * A default implementation of Caret. The caret is rendered as
  19. * a vertical line in the color specified by the CaretColor property
  20. * of the associated JTextComponent. It can blink at the rate specified
  21. * by the BlinkRate property.
  22. * <p>
  23. * This implementation expects two sources of asynchronous notification.
  24. * The timer thread fires asynchronously, and causes the caret to simply
  25. * repaint the most recent bounding box. The caret also tracks change
  26. * as the document is modified. Typically this will happen on the
  27. * event thread as a result of some mouse or keyboard event. Updates
  28. * can also occur from some other thread mutating the document. There
  29. * is a property <code>AsynchronousMovement</code> that determines if
  30. * the caret will move on asynchronous updates. The default behavior
  31. * is to <em>not</em> update on asynchronous updates. If asynchronous
  32. * updates are allowed, the update thread will fire the caret position
  33. * change to listeners asynchronously. The repaint of the new caret
  34. * location will occur on the event thread in any case, as calls to
  35. * <code>modelToView</code> are only safe on the event thread.
  36. * <p>
  37. * The caret acts as a mouse and focus listener on the text component
  38. * it has been installed in, and defines the caret semantics based upon
  39. * those events. The listener methods can be reimplemented to change the
  40. * semantics.
  41. * By default, the first mouse button will be used to set focus and caret
  42. * position. Dragging the mouse pointer with the first mouse button will
  43. * sweep out a selection that is contiguous in the model. If the associated
  44. * text component is editable, the caret will become visible when focus
  45. * is gained, and invisible when focus is lost.
  46. * <p>
  47. * The Highlighter bound to the associated text component is used to
  48. * render the selection by default.
  49. * Selection appearance can be customized by supplying a
  50. * painter to use for the highlights. By default a painter is used that
  51. * will render a solid color as specified in the associated text component
  52. * in the <code>SelectionColor</code> property. This can easily be changed
  53. * by reimplementing the
  54. * <a href="#getSelectionHighlighter">getSelectionHighlighter</a>
  55. * method.
  56. * <p>
  57. * A customized caret appearance can be achieved by reimplementing
  58. * the paint method. If the paint method is changed, the damage method
  59. * should also be reimplemented to cause a repaint for the area needed
  60. * to render the caret. The caret extends the Rectangle class which
  61. * is used to hold the bounding box for where the caret was last rendered.
  62. * This enables the caret to repaint in a thread-safe manner when the
  63. * caret moves without making a call to modelToView which is unstable
  64. * between model updates and view repair (i.e. the order of delivery
  65. * to DocumentListeners is not guaranteed).
  66. * <p>
  67. * <strong>Warning:</strong>
  68. * Serialized objects of this class will not be compatible with
  69. * future Swing releases. The current serialization support is appropriate
  70. * for short term storage or RMI between applications running the same
  71. * version of Swing. A future release of Swing will provide support for
  72. * long term persistence.
  73. *
  74. * @author Timothy Prinzing
  75. * @version 1.80 11/29/01
  76. * @see Caret
  77. */
  78. public class DefaultCaret extends Rectangle implements Caret, FocusListener, MouseListener, MouseMotionListener {
  79. /**
  80. * Constructs a default caret.
  81. */
  82. public DefaultCaret() {
  83. async = false;
  84. }
  85. /**
  86. * Get the flag that determines whether or not
  87. * asynchronous updates will move the caret.
  88. * Normally the caret is moved by events from
  89. * the event thread such as mouse or keyboard
  90. * events. Changes from another thread might
  91. * be used to load a file, or show changes
  92. * from another user. This flag determines
  93. * whether those changes will move the caret.
  94. */
  95. boolean getAsynchronousMovement() {
  96. return async;
  97. }
  98. /**
  99. * Set the flag that determines whether or not
  100. * asynchronous updates will move the caret.
  101. * Normally the caret is moved by events from
  102. * the event thread such as mouse or keyboard
  103. * events. Changes from another thread might
  104. * be used to load a file, or show changes
  105. * from another user. This flag determines
  106. * whether those changes will move the caret.
  107. *
  108. * @param m move the caret on asynchronous
  109. * updates if true.
  110. */
  111. void setAsynchronousMovement(boolean m) {
  112. async = m;
  113. }
  114. /**
  115. * Gets the text editor component that this caret is
  116. * is bound to.
  117. *
  118. * @return the component
  119. */
  120. protected final JTextComponent getComponent() {
  121. return component;
  122. }
  123. /**
  124. * Cause the caret to be painted. The repaint
  125. * area is the bounding box of the caret (i.e.
  126. * the caret rectangle or <em>this</em>).
  127. * <p>
  128. * This method is thread safe, although most Swing methods
  129. * are not. Please see
  130. * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">
  131. * Threads and Swing</A> for more information.
  132. */
  133. protected final synchronized void repaint() {
  134. if (component != null) {
  135. component.repaint(x, y, width, height);
  136. }
  137. }
  138. /**
  139. * Damages the area surrounding the caret to cause
  140. * it to be repainted in a new location. If paint()
  141. * is reimplemented, this method should also be
  142. * reimplemented. This method should update the
  143. * caret bounds (x, y, width, and height).
  144. *
  145. * @param r the current location of the caret
  146. * @see #paint
  147. */
  148. protected synchronized void damage(Rectangle r) {
  149. if (r != null) {
  150. x = r.x - 4;
  151. y = r.y;
  152. width = 10;
  153. height = r.height;
  154. repaint();
  155. }
  156. }
  157. /**
  158. * Scrolls the associated view (if necessary) to make
  159. * the caret visible. Since how this should be done
  160. * is somewhat of a policy, this method can be
  161. * reimplemented to change the behavior. By default
  162. * the scrollRectToVisible method is called on the
  163. * associated component.
  164. *
  165. * @param nloc the new position to scroll to
  166. */
  167. protected void adjustVisibility(Rectangle nloc) {
  168. SwingUtilities.invokeLater(new SafeScroller(nloc));
  169. }
  170. /**
  171. * Gets the painter for the Highlighter.
  172. *
  173. * @return the painter
  174. */
  175. protected Highlighter.HighlightPainter getSelectionPainter() {
  176. return DefaultHighlighter.DefaultPainter;
  177. }
  178. /**
  179. * Tries to set the position of the caret from
  180. * the coordinates of a mouse event, using viewToModel().
  181. *
  182. * @param e the mouse event
  183. */
  184. protected void positionCaret(MouseEvent e) {
  185. Point pt = new Point(e.getX(), e.getY());
  186. Position.Bias[] biasRet = new Position.Bias[1];
  187. int pos = component.getUI().viewToModel(component, pt, biasRet);
  188. if(biasRet[0] == null)
  189. biasRet[0] = Position.Bias.Forward;
  190. if (pos >= 0) {
  191. setDot(pos, biasRet[0]);
  192. // clear the prefferred caret position
  193. // see: JCaret's UpAction/DownAction
  194. setMagicCaretPosition(null);
  195. }
  196. }
  197. /**
  198. * Tries to move the position of the caret from
  199. * the coordinates of a mouse event, using viewToModel().
  200. * This will cause a selection if the dot and mark
  201. * are different.
  202. *
  203. * @param e the mouse event
  204. */
  205. protected void moveCaret(MouseEvent e) {
  206. Point pt = new Point(e.getX(), e.getY());
  207. Position.Bias[] biasRet = new Position.Bias[1];
  208. int pos = component.getUI().viewToModel(component, pt, biasRet);
  209. if(biasRet[0] == null)
  210. biasRet[0] = Position.Bias.Forward;
  211. if (pos >= 0) {
  212. moveDot(pos, biasRet[0]);
  213. }
  214. }
  215. // --- FocusListener methods --------------------------
  216. /**
  217. * Called when the component containing the caret gains
  218. * focus. This is implemented to set the caret to visible
  219. * if the component is editable.
  220. *
  221. * @param e the focus event
  222. * @see FocusListener#focusGained
  223. */
  224. public void focusGained(FocusEvent e) {
  225. if (component.isEditable()) {
  226. setVisible(true);
  227. setSelectionVisible(true);
  228. }
  229. }
  230. /**
  231. * Called when the component containing the caret loses
  232. * focus. This is implemented to set the caret to visibility
  233. * to false.
  234. *
  235. * @param e the focus event
  236. * @see FocusListener#focusLost
  237. */
  238. public void focusLost(FocusEvent e) {
  239. setVisible(false);
  240. setSelectionVisible(false);
  241. }
  242. // --- MouseListener methods -----------------------------------
  243. /**
  244. * Called when the mouse is clicked. If the click was generated
  245. * from button1, a double click selects a word,
  246. * and a triple click the current line.
  247. *
  248. * @param e the mouse event
  249. * @see MouseListener#mouseClicked
  250. */
  251. public void mouseClicked(MouseEvent e) {
  252. if ((e.getModifiers() & InputEvent.BUTTON1_MASK) != 0) {
  253. if(e.getClickCount() == 2) {
  254. Action a = new DefaultEditorKit.SelectWordAction();
  255. a.actionPerformed(null);
  256. } else if(e.getClickCount() == 3) {
  257. Action a = new DefaultEditorKit.SelectLineAction();
  258. a.actionPerformed(null);
  259. }
  260. }
  261. }
  262. /**
  263. * If button 1 is pressed, this is implemented to
  264. * request focus on the associated text component,
  265. * and to set the caret position. If the component
  266. * is not enabled, there will be no request for focus.
  267. *
  268. * @param e the mouse event
  269. * @see MouseListener#mousePressed
  270. */
  271. public void mousePressed(MouseEvent e) {
  272. if(SwingUtilities.isLeftMouseButton(e)) {
  273. positionCaret(e);
  274. if ((component != null) && component.isEnabled()) {
  275. component.requestFocus();
  276. }
  277. }
  278. }
  279. /**
  280. * Called when the mouse is released.
  281. *
  282. * @param e the mouse event
  283. * @see MouseListener#mouseReleased
  284. */
  285. public void mouseReleased(MouseEvent e) {
  286. }
  287. /**
  288. * Called when the mouse enters a region.
  289. *
  290. * @param e the mouse event
  291. * @see MouseListener#mouseEntered
  292. */
  293. public void mouseEntered(MouseEvent e) {
  294. }
  295. /**
  296. * Called when the mouse exits a region.
  297. *
  298. * @param e the mouse event
  299. * @see MouseListener#mouseExited
  300. */
  301. public void mouseExited(MouseEvent e) {
  302. }
  303. // --- MouseMotionListener methods -------------------------
  304. /**
  305. * Moves the caret position
  306. * according to the mouse pointer's current
  307. * location. This effectively extends the
  308. * selection. By default, this is only done
  309. * for mouse button 1.
  310. *
  311. * @param e the mouse event
  312. * @see MouseMotionListener#mouseDragged
  313. */
  314. public void mouseDragged(MouseEvent e) {
  315. if (SwingUtilities.isLeftMouseButton(e)) {
  316. moveCaret(e);
  317. }
  318. }
  319. /**
  320. * Called when the mouse is moved.
  321. *
  322. * @param e the mouse event
  323. * @see MouseMotionListener#mouseMoved
  324. */
  325. public void mouseMoved(MouseEvent e) {
  326. }
  327. // ---- Caret methods ---------------------------------
  328. /**
  329. * Renders the caret as a vertical line. If this is reimplemented
  330. * the damage method should also be reimplemented as it assumes the
  331. * shape of the caret is a vertical line. Sets the caret color to
  332. * the value returned by getCaretColor().
  333. * <p>
  334. * If there are multiple text directions present in the associated
  335. * document, a flag indicating the caret bias will be rendered.
  336. * This will occur only if the associated document is a subclass
  337. * of AbstractDocument and there are multiple bidi levels present
  338. * in the bidi element structure (i.e. the text has multiple
  339. * directions associated with it).
  340. *
  341. * @param g the graphics context
  342. * @see #damage
  343. */
  344. public void paint(Graphics g) {
  345. if(isVisible()) {
  346. try {
  347. TextUI mapper = component.getUI();
  348. Rectangle r = mapper.modelToView(component, dot, dotBias);
  349. g.setColor(component.getCaretColor());
  350. g.drawLine(r.x, r.y, r.x, r.y + r.height - 1);
  351. // see if we should paint a flag to indicate the bias
  352. // of the caret.
  353. // PENDING(prinz) this should be done through
  354. // protected methods so that alternative LAF
  355. // will show bidi information.
  356. Document doc = component.getDocument();
  357. if (doc instanceof AbstractDocument) {
  358. Element bidi = ((AbstractDocument)doc).getBidiRootElement();
  359. if ((bidi != null) && (bidi.getElementCount() > 1)) {
  360. // there are multiple directions present.
  361. flagXPoints[0] = r.x;
  362. flagYPoints[0] = r.y;
  363. flagXPoints[1] = r.x;
  364. flagYPoints[1] = r.y + 4;
  365. flagYPoints[2] = r.y;
  366. flagXPoints[2] = (dotLTR) ? r.x + 5 : r.x - 4;
  367. g.fillPolygon(flagXPoints, flagYPoints, 3);
  368. }
  369. }
  370. } catch (BadLocationException e) {
  371. // can't render I guess
  372. //System.err.println("Can't render cursor");
  373. }
  374. }
  375. }
  376. /**
  377. * Called when the UI is being installed into the
  378. * interface of a JTextComponent. This can be used
  379. * to gain access to the model that is being navigated
  380. * by the implementation of this interface. Sets the dot
  381. * and mark to 0, and establishes document, property change,
  382. * focus, mouse, and mouse motion listeners.
  383. *
  384. * @param c the component
  385. * @see Caret#install
  386. */
  387. public void install(JTextComponent c) {
  388. component = c;
  389. Document doc = c.getDocument();
  390. dot = mark = 0;
  391. dotLTR = markLTR = true;
  392. dotBias = markBias = Position.Bias.Forward;
  393. if (doc != null) {
  394. doc.addDocumentListener(updateHandler);
  395. }
  396. c.addPropertyChangeListener(updateHandler);
  397. focusListener = new FocusHandler(this);
  398. c.addFocusListener(focusListener);
  399. c.addMouseListener(this);
  400. c.addMouseMotionListener(this);
  401. // if the component already has focus, it won't
  402. // be notified.
  403. if (component.hasFocus()) {
  404. focusGained(null);
  405. }
  406. }
  407. /**
  408. * Called when the UI is being removed from the
  409. * interface of a JTextComponent. This is used to
  410. * unregister any listeners that were attached.
  411. *
  412. * @param c the component
  413. * @see Caret#deinstall
  414. */
  415. public void deinstall(JTextComponent c) {
  416. c.removeMouseListener(this);
  417. c.removeMouseMotionListener(this);
  418. if (focusListener != null) {
  419. c.removeFocusListener(focusListener);
  420. focusListener = null;
  421. }
  422. c.removePropertyChangeListener(updateHandler);
  423. Document doc = c.getDocument();
  424. if (doc != null) {
  425. doc.removeDocumentListener(updateHandler);
  426. }
  427. synchronized(this) {
  428. component = null;
  429. }
  430. if (flasher != null) {
  431. flasher.stop();
  432. }
  433. }
  434. /**
  435. * Adds a listener to track whenever the caret position has
  436. * been changed.
  437. *
  438. * @param l the listener
  439. * @see Caret#addChangeListener
  440. */
  441. public void addChangeListener(ChangeListener l) {
  442. listenerList.add(ChangeListener.class, l);
  443. }
  444. /**
  445. * Removes a listener that was tracking caret position changes.
  446. *
  447. * @param l the listener
  448. * @see Caret#removeChangeListener
  449. */
  450. public void removeChangeListener(ChangeListener l) {
  451. listenerList.remove(ChangeListener.class, l);
  452. }
  453. /**
  454. * Notifies all listeners that have registered interest for
  455. * notification on this event type. The event instance
  456. * is lazily created using the parameters passed into
  457. * the fire method. The listener list is processed last to first.
  458. *
  459. * @see EventListenerList
  460. */
  461. protected void fireStateChanged() {
  462. // Guaranteed to return a non-null array
  463. Object[] listeners = listenerList.getListenerList();
  464. // Process the listeners last to first, notifying
  465. // those that are interested in this event
  466. for (int i = listeners.length-2; i>=0; i-=2) {
  467. if (listeners[i]==ChangeListener.class) {
  468. // Lazily create the event:
  469. if (changeEvent == null)
  470. changeEvent = new ChangeEvent(this);
  471. ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
  472. }
  473. }
  474. }
  475. /**
  476. * Changes the selection visibility.
  477. *
  478. * @param vis the new visibility
  479. */
  480. public void setSelectionVisible(boolean vis) {
  481. if (vis) {
  482. // show
  483. Highlighter h = component.getHighlighter();
  484. if ((dot != mark) && (h != null) && (selectionTag == null)) {
  485. int p0 = Math.min(dot, mark);
  486. int p1 = Math.max(dot, mark);
  487. Highlighter.HighlightPainter p = getSelectionPainter();
  488. try {
  489. selectionTag = h.addHighlight(p0, p1, p);
  490. } catch (BadLocationException bl) {
  491. selectionTag = null;
  492. }
  493. }
  494. } else {
  495. // hide
  496. if (selectionTag != null) {
  497. Highlighter h = component.getHighlighter();
  498. h.removeHighlight(selectionTag);
  499. selectionTag = null;
  500. }
  501. }
  502. }
  503. /**
  504. * Checks whether the current selection is visible.
  505. *
  506. * @return true if the selection is visible
  507. */
  508. public boolean isSelectionVisible() {
  509. return (selectionTag != null);
  510. }
  511. /**
  512. * Determines if the caret is currently visible.
  513. *
  514. * @return true if visible else false
  515. * @see Caret#isVisible
  516. */
  517. public boolean isVisible() {
  518. return visible;
  519. }
  520. /**
  521. * Sets the caret visibility, and repaints the caret.
  522. *
  523. * @param e the visibility specifier
  524. * @see Caret#setVisible
  525. */
  526. public void setVisible(boolean e) {
  527. // focus lost notification can come in later after the
  528. // caret has been deinstalled, in which case the component
  529. // will be null.
  530. if (component != null) {
  531. if (visible != e) {
  532. // repaint the caret
  533. try {
  534. Rectangle loc = component.modelToView(dot);
  535. damage(loc);
  536. } catch (BadLocationException badloc) {
  537. // hmm... not legally positioned
  538. }
  539. }
  540. visible = e;
  541. }
  542. if (flasher != null) {
  543. if (visible) {
  544. flasher.start();
  545. } else {
  546. flasher.stop();
  547. }
  548. }
  549. }
  550. /**
  551. * Sets the caret blink rate.
  552. *
  553. * @param rate the rate in milliseconds, 0 to stop blinking
  554. * @see Caret#setBlinkRate
  555. */
  556. public void setBlinkRate(int rate) {
  557. if (rate != 0) {
  558. if (flasher == null) {
  559. flasher = new Timer(rate, updateHandler);
  560. }
  561. flasher.setDelay(rate);
  562. } else {
  563. if (flasher != null) {
  564. flasher.stop();
  565. flasher.removeActionListener(updateHandler);
  566. flasher = null;
  567. }
  568. }
  569. }
  570. /**
  571. * Gets the caret blink rate.
  572. *
  573. * @returns the delay in milliseconds. If this is
  574. * zero the caret will not blink.
  575. * @see Caret#getBlinkRate
  576. */
  577. public int getBlinkRate() {
  578. return (flasher == null) ? 0 : flasher.getDelay();
  579. }
  580. /**
  581. * Fetches the current position of the caret.
  582. *
  583. * @return the position >= 0
  584. * @see Caret#getDot
  585. */
  586. public int getDot() {
  587. return dot;
  588. }
  589. /**
  590. * Fetches the current position of the mark. If there is a selection,
  591. * the dot and mark will not be the same.
  592. *
  593. * @return the position >= 0
  594. * @see Caret#getMark
  595. */
  596. public int getMark() {
  597. return mark;
  598. }
  599. /**
  600. * Sets the caret position and mark to some position. This
  601. * implicitly sets the selection range to zero.
  602. *
  603. * @param dot the position >= 0
  604. * @see Caret#setDot
  605. */
  606. public void setDot(int dot) {
  607. setDot(dot, Position.Bias.Forward);
  608. }
  609. /**
  610. * Moves the caret position to some other position.
  611. *
  612. * @param dot the position >= 0
  613. * @see Caret#moveDot
  614. */
  615. public void moveDot(int dot) {
  616. moveDot(dot, Position.Bias.Forward);
  617. }
  618. // ---- Bidi methods (we could put these in a subclass)
  619. void moveDot(int dot, Position.Bias dotBias) {
  620. if (! component.isEnabled()) {
  621. // don't allow selection on disabled components.
  622. setDot(dot, dotBias);
  623. return;
  624. }
  625. if (dot != this.dot) {
  626. changeCaretPosition(dot, dotBias);
  627. Highlighter h = component.getHighlighter();
  628. if (h != null) {
  629. int p0 = Math.min(dot, mark);
  630. int p1 = Math.max(dot, mark);
  631. try {
  632. if (selectionTag != null) {
  633. h.changeHighlight(selectionTag, p0, p1);
  634. } else {
  635. Highlighter.HighlightPainter p = getSelectionPainter();
  636. selectionTag = h.addHighlight(p0, p1, p);
  637. }
  638. } catch (BadLocationException e) {
  639. throw new StateInvariantError("Bad caret position");
  640. }
  641. }
  642. }
  643. }
  644. void setDot(int dot, Position.Bias dotBias) {
  645. // move dot, if it changed
  646. Document doc = component.getDocument();
  647. if (doc != null) {
  648. dot = Math.min(dot, doc.getLength());
  649. }
  650. dot = Math.max(dot, 0);
  651. // The position (0,Backward) is out of range so disallow it.
  652. if( dot == 0 )
  653. dotBias = Position.Bias.Forward;
  654. mark = dot;
  655. if (this.dot != dot || this.dotBias != dotBias ||
  656. selectionTag != null) {
  657. changeCaretPosition(dot, dotBias);
  658. }
  659. this.markBias = this.dotBias;
  660. this.markLTR = dotLTR;
  661. Highlighter h = component.getHighlighter();
  662. if ((h != null) && (selectionTag != null)) {
  663. h.removeHighlight(selectionTag);
  664. selectionTag = null;
  665. }
  666. }
  667. Position.Bias getDotBias() {
  668. return dotBias;
  669. }
  670. Position.Bias getMarkBias() {
  671. return markBias;
  672. }
  673. boolean isDotLeftToRight() {
  674. return dotLTR;
  675. }
  676. boolean isMarkLeftToRight() {
  677. return markLTR;
  678. }
  679. boolean isPositionLTR(int position, Position.Bias bias) {
  680. Document doc = component.getDocument();
  681. if(doc instanceof AbstractDocument ) {
  682. if(bias == Position.Bias.Backward && --position < 0)
  683. position = 0;
  684. return ((AbstractDocument)doc).isLeftToRight(position, position);
  685. }
  686. return true;
  687. }
  688. Position.Bias guessBiasForOffset(int offset, Position.Bias lastBias,
  689. boolean lastLTR) {
  690. // There is an abiguous case here. That if your model looks like:
  691. // abAB with the cursor at abB]A (visual representation of
  692. // 3 forward) deleting could either become abB] or
  693. // ab[B. I'ld actually prefer abB]. But, if I implement that
  694. // a delete at abBA] would result in aBA] vs a[BA which I
  695. // think is totally wrong. To get this right we need to know what
  696. // was deleted. And we could get this from the bidi structure
  697. // in the change event. So:
  698. // PENDING: base this off what was deleted.
  699. if(lastLTR != isPositionLTR(offset, lastBias)) {
  700. lastBias = Position.Bias.Backward;
  701. }
  702. else if(lastBias != Position.Bias.Backward &&
  703. lastLTR != isPositionLTR(offset, Position.Bias.Backward)) {
  704. lastBias = Position.Bias.Backward;
  705. }
  706. if (lastBias == Position.Bias.Backward && offset > 0) {
  707. try {
  708. String text = component.getDocument().getText(offset - 1, 1);
  709. if (text.length() > 0 && text.charAt(0) == '\n') {
  710. lastBias = Position.Bias.Forward;
  711. }
  712. }
  713. catch (BadLocationException ble) {}
  714. }
  715. return lastBias;
  716. }
  717. // ---- local methods --------------------------------------------
  718. /**
  719. * Sets the caret position (dot) to a new location. This
  720. * causes the old and new location to be repainted. It
  721. * also makes sure that the caret is within the visible
  722. * region of the view, if the view is scrollable.
  723. */
  724. void changeCaretPosition(int dot, Position.Bias dotBias) {
  725. // repaint the old position and set the new value of
  726. // the dot.
  727. repaint();
  728. // Make sure the caret is visible if this window has the focus.
  729. if (flasher != null && flasher.isRunning()) {
  730. visible = true;
  731. flasher.restart();
  732. }
  733. // notify listeners at the caret moved
  734. this.dot = dot;
  735. this.dotBias = dotBias;
  736. dotLTR = isPositionLTR(dot, dotBias);
  737. fireStateChanged();
  738. // We try to repaint the caret later, since things
  739. // may be unstable at the time this is called
  740. // (i.e. we don't want to depend upon notification
  741. // order or the fact that this might happen on
  742. // an unsafe thread).
  743. Runnable callRepaintNewCaret = new Runnable() {
  744. public void run() {
  745. repaintNewCaret();
  746. }
  747. };
  748. SwingUtilities.invokeLater(callRepaintNewCaret);
  749. }
  750. /**
  751. * Repaints the new caret position, with the
  752. * assumption that this is happening on the
  753. * event thread so that calling <code>modelToView</code>
  754. * is safe.
  755. */
  756. void repaintNewCaret() {
  757. if (component != null) {
  758. TextUI mapper = component.getUI();
  759. Document doc = component.getDocument();
  760. if ((mapper != null) && (doc != null)) {
  761. // determine the new location and scroll if
  762. // not visible.
  763. Rectangle newLoc;
  764. try {
  765. newLoc = mapper.modelToView(component, this.dot, this.dotBias);
  766. } catch (BadLocationException e) {
  767. newLoc = null;
  768. }
  769. if (newLoc != null) {
  770. adjustVisibility(newLoc);
  771. }
  772. // repaint the new position
  773. damage(newLoc);
  774. }
  775. }
  776. }
  777. /**
  778. * Saves the current caret position. This is used when
  779. * caret up/down actions occur, moving between lines
  780. * that have uneven end positions.
  781. *
  782. * @param p the position
  783. * @see #getMagicCaretPosition
  784. * @see UpAction
  785. * @see DownAction
  786. */
  787. public void setMagicCaretPosition(Point p) {
  788. magicCaretPosition = p;
  789. }
  790. /**
  791. * Gets the saved caret position.
  792. *
  793. * @return the position
  794. * see #setMagicCaretPosition
  795. */
  796. public Point getMagicCaretPosition() {
  797. return magicCaretPosition;
  798. }
  799. /**
  800. * Compares this object to the specifed object.
  801. * The superclass behavior of comparing rectangles
  802. * is not desired, so this is changed to the Object
  803. * behavior.
  804. *
  805. * @param obj the object to compare this font with.
  806. * @return <code>true</code> if the objects are equal;
  807. * <code>false</code> otherwise.
  808. */
  809. public boolean equals(Object obj) {
  810. return (this == obj);
  811. }
  812. public String toString() {
  813. String s = "Dot=(" + dot + ", " + dotBias + ")";
  814. s += " Mark=(" + mark + ", " + markBias + ")";
  815. return s;
  816. }
  817. // --- serialization ---------------------------------------------
  818. private void readObject(ObjectInputStream s)
  819. throws ClassNotFoundException, IOException
  820. {
  821. s.defaultReadObject();
  822. updateHandler = new UpdateHandler();
  823. if (!s.readBoolean()) {
  824. dotBias = Position.Bias.Forward;
  825. }
  826. else {
  827. dotBias = Position.Bias.Backward;
  828. }
  829. if (!s.readBoolean()) {
  830. markBias = Position.Bias.Forward;
  831. }
  832. else {
  833. markBias = Position.Bias.Backward;
  834. }
  835. }
  836. private void writeObject(ObjectOutputStream s) throws IOException {
  837. s.defaultWriteObject();
  838. s.writeBoolean((dotBias == Position.Bias.Backward));
  839. s.writeBoolean((markBias == Position.Bias.Backward));
  840. }
  841. // ---- member variables ------------------------------------------
  842. /**
  843. * The event listener list.
  844. */
  845. protected EventListenerList listenerList = new EventListenerList();
  846. /**
  847. * The change event for the model.
  848. * Only one ChangeEvent is needed per model instance since the
  849. * event's only (read-only) state is the source property. The source
  850. * of events generated here is always "this".
  851. */
  852. protected transient ChangeEvent changeEvent = null;
  853. // package-private to avoid inner classes private member
  854. // access bug
  855. JTextComponent component;
  856. /**
  857. * flag to indicate if async updates should
  858. * move the caret.
  859. */
  860. boolean async;
  861. boolean visible;
  862. int dot;
  863. int mark;
  864. Object selectionTag;
  865. Timer flasher;
  866. Point magicCaretPosition;
  867. transient Position.Bias dotBias;
  868. transient Position.Bias markBias;
  869. boolean dotLTR;
  870. boolean markLTR;
  871. transient UpdateHandler updateHandler = new UpdateHandler();
  872. transient private int[] flagXPoints = new int[3];
  873. transient private int[] flagYPoints = new int[3];
  874. transient private FocusListener focusListener;
  875. class SafeScroller implements Runnable {
  876. SafeScroller(Rectangle r) {
  877. this.r = r;
  878. }
  879. public void run() {
  880. if (component != null) {
  881. component.scrollRectToVisible(r);
  882. }
  883. }
  884. Rectangle r;
  885. }
  886. //
  887. // DefaultCaret extends Rectangle, which is Serializable. DefaultCaret
  888. // also implements FocusListener. Swing attempts to remove UI related
  889. // classes (such as this class) by installing a FocusListener. When
  890. // that FocusListener is messaged to writeObject it uninstalls the UI
  891. // (refer to JComponent.EnableSerializationFocusListener). The
  892. // problem with this is that any other FocusListeners will also get
  893. // serialized due to how AWT handles listeners. So, for the time being
  894. // this class is installed as the FocusListener on the JTextComponent,
  895. // and it will forward the FocusListener methods to the DefaultCaret.
  896. // Since FocusHandler is not Serializable DefaultCaret will not be
  897. // pulled in.
  898. //
  899. private static class FocusHandler implements FocusListener {
  900. private transient FocusListener fl;
  901. FocusHandler(FocusListener fl) {
  902. this.fl = fl;
  903. }
  904. /**
  905. * Called when the component containing the caret gains
  906. * focus. This is implemented to set the caret to visible
  907. * if the component is editable.
  908. *
  909. * @param e the focus event
  910. * @see FocusListener#focusGained
  911. */
  912. public void focusGained(FocusEvent e) {
  913. fl.focusGained(e);
  914. }
  915. /**
  916. * Called when the component containing the caret loses
  917. * focus. This is implemented to set the caret to visibility
  918. * to false.
  919. *
  920. * @param e the focus event
  921. * @see FocusListener#focusLost
  922. */
  923. public void focusLost(FocusEvent e) {
  924. fl.focusLost(e);
  925. }
  926. }
  927. class UpdateHandler implements PropertyChangeListener, DocumentListener, ActionListener {
  928. // --- ActionListener methods ----------------------------------
  929. /**
  930. * Invoked when the blink timer fires. This is called
  931. * asynchronously. The simply changes the visibility
  932. * and repaints the rectangle that last bounded the caret.
  933. *
  934. * @param e the action event
  935. */
  936. public void actionPerformed(ActionEvent e) {
  937. if (!visible && (width == 0 || height == 0)) {
  938. setVisible(true);
  939. }
  940. else {
  941. visible = !visible;
  942. repaint();
  943. }
  944. }
  945. // --- DocumentListener methods --------------------------------
  946. /**
  947. * Updates the dot and mark if they were changed by
  948. * the insertion.
  949. *
  950. * @param e the document event
  951. * @see DocumentListener#insertUpdate
  952. */
  953. public void insertUpdate(DocumentEvent e) {
  954. if (async || SwingUtilities.isEventDispatchThread()) {
  955. int adjust = 0;
  956. int offset = e.getOffset();
  957. int length = e.getLength();
  958. if (dot >= offset) {
  959. adjust = length;
  960. }
  961. if (mark >= offset) {
  962. mark += length;
  963. }
  964. if (adjust != 0) {
  965. if(dot == offset) {
  966. Document doc = component.getDocument();
  967. int newDot = dot + adjust;
  968. boolean isNewline;
  969. try {
  970. isNewline = doc.getText(newDot - 1, 1).equals("\n");
  971. } catch (BadLocationException ble) {
  972. isNewline = false;
  973. }
  974. if(isNewline) {
  975. changeCaretPosition(newDot, Position.Bias.Forward);
  976. } else {
  977. changeCaretPosition(newDot, Position.Bias.Backward);
  978. }
  979. } else {
  980. changeCaretPosition(dot + adjust, dotBias);
  981. }
  982. }
  983. }
  984. }
  985. /**
  986. * Updates the dot and mark if they were changed
  987. * by the removal.
  988. *
  989. * @param e the document event
  990. * @see DocumentListener#removeUpdate
  991. */
  992. public void removeUpdate(DocumentEvent e) {
  993. if (async || SwingUtilities.isEventDispatchThread()) {
  994. int adjust = 0;
  995. int offs0 = e.getOffset();
  996. int offs1 = offs0 + e.getLength();
  997. boolean adjustDotBias = false;
  998. if (dot >= offs1) {
  999. adjust = offs1 - offs0;
  1000. if(dot == offs1) {
  1001. adjustDotBias = true;
  1002. }
  1003. } else if (dot >= offs0) {
  1004. adjust = dot - offs0;
  1005. adjustDotBias = true;
  1006. }
  1007. boolean adjustMarkBias = false;
  1008. if (mark >= offs1) {
  1009. mark -= offs1 - offs0;
  1010. if(mark == offs1) {
  1011. adjustMarkBias = true;
  1012. }
  1013. } else if (mark >= offs0) {
  1014. mark = offs0;
  1015. adjustMarkBias = true;
  1016. }
  1017. if (mark == (dot - adjust)) {
  1018. setDot(dot - adjust, guessBiasForOffset(dot - adjust,
  1019. dotBias, dotLTR));
  1020. } else {
  1021. if(adjustDotBias) {
  1022. dotBias = guessBiasForOffset(dot - adjust, dotBias,
  1023. dotLTR);
  1024. }
  1025. if(adjustMarkBias) {
  1026. markBias = guessBiasForOffset(mark, markBias, markLTR);
  1027. }
  1028. changeCaretPosition(dot - adjust, dotBias);
  1029. markLTR = isPositionLTR(mark, markBias);
  1030. }
  1031. }
  1032. }
  1033. /**
  1034. * Gives notification that an attribute or set of attributes changed.
  1035. *
  1036. * @param e the document event
  1037. * @see DocumentListener#changedUpdate
  1038. */
  1039. public void changedUpdate(DocumentEvent e) {
  1040. }
  1041. // --- PropertyChangeListener methods -----------------------
  1042. /**
  1043. * This method gets called when a bound property is changed.
  1044. * We are looking for document changes on the editor.
  1045. */
  1046. public void propertyChange(PropertyChangeEvent evt) {
  1047. Object oldValue = evt.getOldValue();
  1048. Object newValue = evt.getNewValue();
  1049. if ((oldValue instanceof Document) || (newValue instanceof Document)) {
  1050. setDot(0);
  1051. if (oldValue != null) {
  1052. ((Document)oldValue).removeDocumentListener(this);
  1053. }
  1054. if (newValue != null) {
  1055. ((Document)newValue).addDocumentListener(this);
  1056. }
  1057. }
  1058. }
  1059. }
  1060. }