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