1. /*
  2. * @(#)DefaultCaret.java 1.121 03/01/23
  3. *
  4. * Copyright 2003 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.awt.datatransfer.*;
  11. import java.beans.*;
  12. import java.awt.event.ActionEvent;
  13. import java.awt.event.ActionListener;
  14. import java.io.*;
  15. import javax.swing.*;
  16. import javax.swing.event.*;
  17. import javax.swing.plaf.*;
  18. import java.util.EventListener;
  19. /**
  20. * A default implementation of Caret. The caret is rendered as
  21. * a vertical line in the color specified by the CaretColor property
  22. * of the associated JTextComponent. It can blink at the rate specified
  23. * by the BlinkRate property.
  24. * <p>
  25. * This implementation expects two sources of asynchronous notification.
  26. * The timer thread fires asynchronously, and causes the caret to simply
  27. * repaint the most recent bounding box. The caret also tracks change
  28. * as the document is modified. Typically this will happen on the
  29. * event thread as a result of some mouse or keyboard event. Updates
  30. * can also occur from some other thread mutating the document. There
  31. * is a property <code>AsynchronousMovement</code> that determines if
  32. * the caret will move on asynchronous updates. The default behavior
  33. * is to <em>not</em> update on asynchronous updates. If asynchronous
  34. * updates are allowed, the update thread will fire the caret position
  35. * change to listeners asynchronously. The repaint of the new caret
  36. * location will occur on the event thread in any case, as calls to
  37. * <code>modelToView</code> are only safe on the event thread.
  38. * <p>
  39. * The caret acts as a mouse and focus listener on the text component
  40. * it has been installed in, and defines the caret semantics based upon
  41. * those events. The listener methods can be reimplemented to change the
  42. * semantics.
  43. * By default, the first mouse button will be used to set focus and caret
  44. * position. Dragging the mouse pointer with the first mouse button will
  45. * sweep out a selection that is contiguous in the model. If the associated
  46. * text component is editable, the caret will become visible when focus
  47. * is gained, and invisible when focus is lost.
  48. * <p>
  49. * The Highlighter bound to the associated text component is used to
  50. * render the selection by default.
  51. * Selection appearance can be customized by supplying a
  52. * painter to use for the highlights. By default a painter is used that
  53. * will render a solid color as specified in the associated text component
  54. * in the <code>SelectionColor</code> property. This can easily be changed
  55. * by reimplementing the
  56. * <a href="#getSelectionHighlighter">getSelectionHighlighter</a>
  57. * method.
  58. * <p>
  59. * A customized caret appearance can be achieved by reimplementing
  60. * the paint method. If the paint method is changed, the damage method
  61. * should also be reimplemented to cause a repaint for the area needed
  62. * to render the caret. The caret extends the Rectangle class which
  63. * is used to hold the bounding box for where the caret was last rendered.
  64. * This enables the caret to repaint in a thread-safe manner when the
  65. * caret moves without making a call to modelToView which is unstable
  66. * between model updates and view repair (i.e. the order of delivery
  67. * to DocumentListeners is not guaranteed).
  68. * <p>
  69. * The magic caret position is set to null when the caret position changes.
  70. * A timer is used to determine the new location (after the caret change).
  71. * When the timer fires, if the magic caret position is still null it is
  72. * reset to the current caret position. Any actions that change
  73. * the caret position and want the magic caret position to remain the
  74. * same, must remember the magic caret position, change the cursor, and
  75. * then set the magic caret position to its original value. This has the
  76. * benefit that only actions that want the magic caret position to persist
  77. * (such as open/down) need to know about it.
  78. * <p>
  79. * <strong>Warning:</strong>
  80. * Serialized objects of this class will not be compatible with
  81. * future Swing releases. The current serialization support is
  82. * appropriate for short term storage or RMI between applications running
  83. * the same version of Swing. As of 1.4, support for long term storage
  84. * of all JavaBeans<sup><font size="-2">TM</font></sup>
  85. * has been added to the <code>java.beans</code> package.
  86. * Please see {@link java.beans.XMLEncoder}.
  87. *
  88. * @author Timothy Prinzing
  89. * @version 1.121 01/23/03
  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. if (SwingUtilities.isEventDispatchThread()) {
  183. if (component != null) {
  184. component.scrollRectToVisible(nloc);
  185. }
  186. }
  187. else {
  188. SwingUtilities.invokeLater(new SafeScroller(nloc));
  189. }
  190. }
  191. /**
  192. * Gets the painter for the Highlighter.
  193. *
  194. * @return the painter
  195. */
  196. protected Highlighter.HighlightPainter getSelectionPainter() {
  197. return DefaultHighlighter.DefaultPainter;
  198. }
  199. /**
  200. * Tries to set the position of the caret from
  201. * the coordinates of a mouse event, using viewToModel().
  202. *
  203. * @param e the mouse event
  204. */
  205. protected void positionCaret(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. setDot(pos, biasRet[0]);
  213. }
  214. }
  215. /**
  216. * Tries to move the position of the caret from
  217. * the coordinates of a mouse event, using viewToModel().
  218. * This will cause a selection if the dot and mark
  219. * are different.
  220. *
  221. * @param e the mouse event
  222. */
  223. protected void moveCaret(MouseEvent e) {
  224. Point pt = new Point(e.getX(), e.getY());
  225. Position.Bias[] biasRet = new Position.Bias[1];
  226. int pos = component.getUI().viewToModel(component, pt, biasRet);
  227. if(biasRet[0] == null)
  228. biasRet[0] = Position.Bias.Forward;
  229. if (pos >= 0) {
  230. moveDot(pos, biasRet[0]);
  231. }
  232. }
  233. // --- FocusListener methods --------------------------
  234. /**
  235. * Called when the component containing the caret gains
  236. * focus. This is implemented to set the caret to visible
  237. * if the component is editable.
  238. *
  239. * @param e the focus event
  240. * @see FocusListener#focusGained
  241. */
  242. public void focusGained(FocusEvent e) {
  243. if (component.isEnabled()) {
  244. if (component.isEditable()) {
  245. setVisible(true);
  246. }
  247. setSelectionVisible(true);
  248. }
  249. }
  250. /**
  251. * Called when the component containing the caret loses
  252. * focus. This is implemented to set the caret to visibility
  253. * to false.
  254. *
  255. * @param e the focus event
  256. * @see FocusListener#focusLost
  257. */
  258. public void focusLost(FocusEvent e) {
  259. setVisible(false);
  260. setSelectionVisible(ownsSelection || e.isTemporary());
  261. }
  262. // --- MouseListener methods -----------------------------------
  263. /**
  264. * Called when the mouse is clicked. If the click was generated
  265. * from button1, a double click selects a word,
  266. * and a triple click the current line.
  267. *
  268. * @param e the mouse event
  269. * @see MouseListener#mouseClicked
  270. */
  271. public void mouseClicked(MouseEvent e) {
  272. if (! e.isConsumed()) {
  273. int nclicks = e.getClickCount();
  274. if (SwingUtilities.isLeftMouseButton(e)) {
  275. // mouse 1 behavior
  276. if(e.getClickCount() == 2) {
  277. Action a = new DefaultEditorKit.SelectWordAction();
  278. a.actionPerformed(new ActionEvent(getComponent(),
  279. ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e.getModifiers()));
  280. } else if(e.getClickCount() == 3) {
  281. Action a = new DefaultEditorKit.SelectLineAction();
  282. a.actionPerformed(new ActionEvent(getComponent(),
  283. ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e.getModifiers()));
  284. }
  285. } else if (SwingUtilities.isMiddleMouseButton(e)) {
  286. // mouse 2 behavior
  287. if (nclicks == 1 && component.isEditable() && component.isEnabled()) {
  288. // paste system selection, if it exists
  289. JTextComponent c = (JTextComponent) e.getSource();
  290. if (c != null) {
  291. try {
  292. Toolkit tk = c.getToolkit();
  293. Clipboard buffer = tk.getSystemSelection();
  294. if (buffer != null) {
  295. // platform supports system selections, update it.
  296. adjustCaret(e);
  297. TransferHandler th = c.getTransferHandler();
  298. if (th != null) {
  299. Transferable trans = buffer.getContents(null);
  300. if (trans != null) {
  301. th.importData(c, trans);
  302. }
  303. }
  304. adjustFocus(true);
  305. }
  306. } catch (HeadlessException he) {
  307. // do nothing... there is no system clipboard
  308. }
  309. }
  310. }
  311. }
  312. }
  313. }
  314. /**
  315. * If button 1 is pressed, this is implemented to
  316. * request focus on the associated text component,
  317. * and to set the caret position. If the shift key is held down,
  318. * the caret will be moved, potentially resulting in a selection,
  319. * otherwise the
  320. * caret position will be set to the new location. If the component
  321. * is not enabled, there will be no request for focus.
  322. *
  323. * @param e the mouse event
  324. * @see MouseListener#mousePressed
  325. */
  326. public void mousePressed(MouseEvent e) {
  327. if (SwingUtilities.isLeftMouseButton(e)) {
  328. if (e.isConsumed()) {
  329. shouldHandleRelease = true;
  330. } else {
  331. shouldHandleRelease = false;
  332. adjustCaretAndFocus(e);
  333. }
  334. }
  335. }
  336. void adjustCaretAndFocus(MouseEvent e) {
  337. adjustCaret(e);
  338. adjustFocus(false);
  339. }
  340. /**
  341. * Adjusts the caret location based on the MouseEvent.
  342. */
  343. private void adjustCaret(MouseEvent e) {
  344. if ((e.getModifiers() & ActionEvent.SHIFT_MASK) != 0 &&
  345. getDot() != -1) {
  346. moveCaret(e);
  347. } else {
  348. positionCaret(e);
  349. }
  350. }
  351. /**
  352. * Adjusts the focus, if necessary.
  353. *
  354. * @param inWindow if true indicates requestFocusInWindow should be used
  355. */
  356. private void adjustFocus(boolean inWindow) {
  357. if ((component != null) && component.isEnabled() &&
  358. component.isRequestFocusEnabled()) {
  359. if (inWindow) {
  360. component.requestFocusInWindow();
  361. }
  362. else {
  363. component.requestFocus();
  364. }
  365. }
  366. }
  367. /**
  368. * Called when the mouse is released.
  369. *
  370. * @param e the mouse event
  371. * @see MouseListener#mouseReleased
  372. */
  373. public void mouseReleased(MouseEvent e) {
  374. if (shouldHandleRelease && SwingUtilities.isLeftMouseButton(e)) {
  375. adjustCaretAndFocus(e);
  376. }
  377. }
  378. /**
  379. * Called when the mouse enters a region.
  380. *
  381. * @param e the mouse event
  382. * @see MouseListener#mouseEntered
  383. */
  384. public void mouseEntered(MouseEvent e) {
  385. }
  386. /**
  387. * Called when the mouse exits a region.
  388. *
  389. * @param e the mouse event
  390. * @see MouseListener#mouseExited
  391. */
  392. public void mouseExited(MouseEvent e) {
  393. }
  394. // --- MouseMotionListener methods -------------------------
  395. /**
  396. * Moves the caret position
  397. * according to the mouse pointer's current
  398. * location. This effectively extends the
  399. * selection. By default, this is only done
  400. * for mouse button 1.
  401. *
  402. * @param e the mouse event
  403. * @see MouseMotionListener#mouseDragged
  404. */
  405. public void mouseDragged(MouseEvent e) {
  406. if ((! e.isConsumed()) && SwingUtilities.isLeftMouseButton(e)) {
  407. moveCaret(e);
  408. }
  409. }
  410. /**
  411. * Called when the mouse is moved.
  412. *
  413. * @param e the mouse event
  414. * @see MouseMotionListener#mouseMoved
  415. */
  416. public void mouseMoved(MouseEvent e) {
  417. }
  418. // ---- Caret methods ---------------------------------
  419. /**
  420. * Renders the caret as a vertical line. If this is reimplemented
  421. * the damage method should also be reimplemented as it assumes the
  422. * shape of the caret is a vertical line. Sets the caret color to
  423. * the value returned by getCaretColor().
  424. * <p>
  425. * If there are multiple text directions present in the associated
  426. * document, a flag indicating the caret bias will be rendered.
  427. * This will occur only if the associated document is a subclass
  428. * of AbstractDocument and there are multiple bidi levels present
  429. * in the bidi element structure (i.e. the text has multiple
  430. * directions associated with it).
  431. *
  432. * @param g the graphics context
  433. * @see #damage
  434. */
  435. public void paint(Graphics g) {
  436. if(isVisible()) {
  437. try {
  438. TextUI mapper = component.getUI();
  439. Rectangle r = mapper.modelToView(component, dot, dotBias);
  440. if ((r == null) || ((r.width == 0) && (r.height == 0))) {
  441. return;
  442. }
  443. if (width > 0 && height > 0 &&
  444. !this._contains(r.x, r.y, r.width, r.height)) {
  445. // We seem to have gotten out of sync and no longer
  446. // contain the right location, adjust accordingly.
  447. Rectangle clip = g.getClipBounds();
  448. if (clip != null && !clip.contains(this)) {
  449. // Clip doesn't contain the old location, force it
  450. // to be repainted lest we leave a caret around.
  451. repaint();
  452. }
  453. // This will potentially cause a repaint of something
  454. // we're already repainting, but without changing the
  455. // semantics of damage we can't really get around this.
  456. damage(r);
  457. }
  458. g.setColor(component.getCaretColor());
  459. g.drawLine(r.x, r.y, r.x, r.y + r.height - 1);
  460. // see if we should paint a flag to indicate the bias
  461. // of the caret.
  462. // PENDING(prinz) this should be done through
  463. // protected methods so that alternative LAF
  464. // will show bidi information.
  465. Document doc = component.getDocument();
  466. if (doc instanceof AbstractDocument) {
  467. Element bidi = ((AbstractDocument)doc).getBidiRootElement();
  468. if ((bidi != null) && (bidi.getElementCount() > 1)) {
  469. // there are multiple directions present.
  470. flagXPoints[0] = r.x;
  471. flagYPoints[0] = r.y;
  472. flagXPoints[1] = r.x;
  473. flagYPoints[1] = r.y + 4;
  474. flagYPoints[2] = r.y;
  475. flagXPoints[2] = (dotLTR) ? r.x + 5 : r.x - 4;
  476. g.fillPolygon(flagXPoints, flagYPoints, 3);
  477. }
  478. }
  479. } catch (BadLocationException e) {
  480. // can't render I guess
  481. //System.err.println("Can't render cursor");
  482. }
  483. }
  484. }
  485. /**
  486. * Called when the UI is being installed into the
  487. * interface of a JTextComponent. This can be used
  488. * to gain access to the model that is being navigated
  489. * by the implementation of this interface. Sets the dot
  490. * and mark to 0, and establishes document, property change,
  491. * focus, mouse, and mouse motion listeners.
  492. *
  493. * @param c the component
  494. * @see Caret#install
  495. */
  496. public void install(JTextComponent c) {
  497. component = c;
  498. Document doc = c.getDocument();
  499. dot = mark = 0;
  500. dotLTR = markLTR = true;
  501. dotBias = markBias = Position.Bias.Forward;
  502. if (doc != null) {
  503. doc.addDocumentListener(updateHandler);
  504. }
  505. c.addPropertyChangeListener(updateHandler);
  506. focusListener = new FocusHandler(this);
  507. c.addFocusListener(focusListener);
  508. c.addMouseListener(this);
  509. c.addMouseMotionListener(this);
  510. // if the component already has focus, it won't
  511. // be notified.
  512. if (component.hasFocus()) {
  513. focusGained(null);
  514. }
  515. }
  516. /**
  517. * Called when the UI is being removed from the
  518. * interface of a JTextComponent. This is used to
  519. * unregister any listeners that were attached.
  520. *
  521. * @param c the component
  522. * @see Caret#deinstall
  523. */
  524. public void deinstall(JTextComponent c) {
  525. c.removeMouseListener(this);
  526. c.removeMouseMotionListener(this);
  527. if (focusListener != null) {
  528. c.removeFocusListener(focusListener);
  529. focusListener = null;
  530. }
  531. c.removePropertyChangeListener(updateHandler);
  532. Document doc = c.getDocument();
  533. if (doc != null) {
  534. doc.removeDocumentListener(updateHandler);
  535. }
  536. synchronized(this) {
  537. component = null;
  538. }
  539. if (flasher != null) {
  540. flasher.stop();
  541. }
  542. }
  543. /**
  544. * Adds a listener to track whenever the caret position has
  545. * been changed.
  546. *
  547. * @param l the listener
  548. * @see Caret#addChangeListener
  549. */
  550. public void addChangeListener(ChangeListener l) {
  551. listenerList.add(ChangeListener.class, l);
  552. }
  553. /**
  554. * Removes a listener that was tracking caret position changes.
  555. *
  556. * @param l the listener
  557. * @see Caret#removeChangeListener
  558. */
  559. public void removeChangeListener(ChangeListener l) {
  560. listenerList.remove(ChangeListener.class, l);
  561. }
  562. /**
  563. * Returns an array of all the change listeners
  564. * registered on this caret.
  565. *
  566. * @return all of this caret's <code>ChangeListener</code>s
  567. * or an empty
  568. * array if no change listeners are currently registered
  569. *
  570. * @see #addChangeListener
  571. * @see #removeChangeListener
  572. *
  573. * @since 1.4
  574. */
  575. public ChangeListener[] getChangeListeners() {
  576. return (ChangeListener[])listenerList.getListeners(
  577. ChangeListener.class);
  578. }
  579. /**
  580. * Notifies all listeners that have registered interest for
  581. * notification on this event type. The event instance
  582. * is lazily created using the parameters passed into
  583. * the fire method. The listener list is processed last to first.
  584. *
  585. * @see EventListenerList
  586. */
  587. protected void fireStateChanged() {
  588. // Guaranteed to return a non-null array
  589. Object[] listeners = listenerList.getListenerList();
  590. // Process the listeners last to first, notifying
  591. // those that are interested in this event
  592. for (int i = listeners.length-2; i>=0; i-=2) {
  593. if (listeners[i]==ChangeListener.class) {
  594. // Lazily create the event:
  595. if (changeEvent == null)
  596. changeEvent = new ChangeEvent(this);
  597. ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
  598. }
  599. }
  600. }
  601. /**
  602. * Returns an array of all the objects currently registered
  603. * as <code><em>Foo</em>Listener</code>s
  604. * upon this caret.
  605. * <code><em>Foo</em>Listener</code>s are registered using the
  606. * <code>add<em>Foo</em>Listener</code> method.
  607. *
  608. * <p>
  609. *
  610. * You can specify the <code>listenerType</code> argument
  611. * with a class literal,
  612. * such as
  613. * <code><em>Foo</em>Listener.class</code>.
  614. * For example, you can query a
  615. * <code>DefaultCaret</code> <code>c</code>
  616. * for its change listeners with the following code:
  617. *
  618. * <pre>ChangeListener[] cls = (ChangeListener[])(c.getListeners(ChangeListener.class));</pre>
  619. *
  620. * If no such listeners exist, this method returns an empty array.
  621. *
  622. * @param listenerType the type of listeners requested; this parameter
  623. * should specify an interface that descends from
  624. * <code>java.util.EventListener</code>
  625. * @return an array of all objects registered as
  626. * <code><em>Foo</em>Listener</code>s on this component,
  627. * or an empty array if no such
  628. * listeners have been added
  629. * @exception ClassCastException if <code>listenerType</code>
  630. * doesn't specify a class or interface that implements
  631. * <code>java.util.EventListener</code>
  632. *
  633. * @see #getChangeListeners
  634. *
  635. * @since 1.3
  636. */
  637. public EventListener[] getListeners(Class listenerType) {
  638. return listenerList.getListeners(listenerType);
  639. }
  640. /**
  641. * Changes the selection visibility.
  642. *
  643. * @param vis the new visibility
  644. */
  645. public void setSelectionVisible(boolean vis) {
  646. if (vis != selectionVisible) {
  647. selectionVisible = vis;
  648. if (selectionVisible) {
  649. // show
  650. Highlighter h = component.getHighlighter();
  651. if ((dot != mark) && (h != null) && (selectionTag == null)) {
  652. int p0 = Math.min(dot, mark);
  653. int p1 = Math.max(dot, mark);
  654. Highlighter.HighlightPainter p = getSelectionPainter();
  655. try {
  656. selectionTag = h.addHighlight(p0, p1, p);
  657. } catch (BadLocationException bl) {
  658. selectionTag = null;
  659. }
  660. }
  661. } else {
  662. // hide
  663. if (selectionTag != null) {
  664. Highlighter h = component.getHighlighter();
  665. h.removeHighlight(selectionTag);
  666. selectionTag = null;
  667. }
  668. }
  669. }
  670. }
  671. /**
  672. * Checks whether the current selection is visible.
  673. *
  674. * @return true if the selection is visible
  675. */
  676. public boolean isSelectionVisible() {
  677. return selectionVisible;
  678. }
  679. /**
  680. * Determines if the caret is currently visible.
  681. *
  682. * @return true if visible else false
  683. * @see Caret#isVisible
  684. */
  685. public boolean isVisible() {
  686. return visible;
  687. }
  688. /**
  689. * Sets the caret visibility, and repaints the caret.
  690. *
  691. * @param e the visibility specifier
  692. * @see Caret#setVisible
  693. */
  694. public void setVisible(boolean e) {
  695. // focus lost notification can come in later after the
  696. // caret has been deinstalled, in which case the component
  697. // will be null.
  698. if (component != null) {
  699. TextUI mapper = component.getUI();
  700. if (visible != e) {
  701. visible = e;
  702. // repaint the caret
  703. try {
  704. Rectangle loc = mapper.modelToView(component, dot,dotBias);
  705. damage(loc);
  706. } catch (BadLocationException badloc) {
  707. // hmm... not legally positioned
  708. }
  709. }
  710. }
  711. if (flasher != null) {
  712. if (visible) {
  713. flasher.start();
  714. } else {
  715. flasher.stop();
  716. }
  717. }
  718. }
  719. /**
  720. * Sets the caret blink rate.
  721. *
  722. * @param rate the rate in milliseconds, 0 to stop blinking
  723. * @see Caret#setBlinkRate
  724. */
  725. public void setBlinkRate(int rate) {
  726. if (rate != 0) {
  727. if (flasher == null) {
  728. flasher = new Timer(rate, updateHandler);
  729. }
  730. flasher.setDelay(rate);
  731. } else {
  732. if (flasher != null) {
  733. flasher.stop();
  734. flasher.removeActionListener(updateHandler);
  735. flasher = null;
  736. }
  737. }
  738. }
  739. /**
  740. * Gets the caret blink rate.
  741. *
  742. * @return the delay in milliseconds. If this is
  743. * zero the caret will not blink.
  744. * @see Caret#getBlinkRate
  745. */
  746. public int getBlinkRate() {
  747. return (flasher == null) ? 0 : flasher.getDelay();
  748. }
  749. /**
  750. * Fetches the current position of the caret.
  751. *
  752. * @return the position >= 0
  753. * @see Caret#getDot
  754. */
  755. public int getDot() {
  756. return dot;
  757. }
  758. /**
  759. * Fetches the current position of the mark. If there is a selection,
  760. * the dot and mark will not be the same.
  761. *
  762. * @return the position >= 0
  763. * @see Caret#getMark
  764. */
  765. public int getMark() {
  766. return mark;
  767. }
  768. /**
  769. * Sets the caret position and mark to some position. This
  770. * implicitly sets the selection range to zero.
  771. *
  772. * @param dot the position >= 0
  773. * @see Caret#setDot
  774. */
  775. public void setDot(int dot) {
  776. setDot(dot, Position.Bias.Forward);
  777. }
  778. /**
  779. * Moves the caret position to some other position.
  780. *
  781. * @param dot the position >= 0
  782. * @see Caret#moveDot
  783. */
  784. public void moveDot(int dot) {
  785. moveDot(dot, Position.Bias.Forward);
  786. }
  787. // ---- Bidi methods (we could put these in a subclass)
  788. void moveDot(int dot, Position.Bias dotBias) {
  789. if (! component.isEnabled()) {
  790. // don't allow selection on disabled components.
  791. setDot(dot, dotBias);
  792. return;
  793. }
  794. if (dot != this.dot) {
  795. NavigationFilter filter = component.getNavigationFilter();
  796. if (filter != null) {
  797. filter.moveDot(getFilterBypass(), dot, dotBias);
  798. }
  799. else {
  800. handleMoveDot(dot, dotBias);
  801. }
  802. }
  803. }
  804. void handleMoveDot(int dot, Position.Bias dotBias) {
  805. changeCaretPosition(dot, dotBias);
  806. if (selectionVisible) {
  807. Highlighter h = component.getHighlighter();
  808. if (h != null) {
  809. int p0 = Math.min(dot, mark);
  810. int p1 = Math.max(dot, mark);
  811. // if p0 == p1 then there should be no highlight, remove it if necessary
  812. if (p0 == p1) {
  813. if (selectionTag != null) {
  814. h.removeHighlight(selectionTag);
  815. selectionTag = null;
  816. }
  817. // otherwise, change or add the highlight
  818. } else {
  819. try {
  820. if (selectionTag != null) {
  821. h.changeHighlight(selectionTag, p0, p1);
  822. } else {
  823. Highlighter.HighlightPainter p = getSelectionPainter();
  824. selectionTag = h.addHighlight(p0, p1, p);
  825. }
  826. } catch (BadLocationException e) {
  827. throw new StateInvariantError("Bad caret position");
  828. }
  829. }
  830. }
  831. }
  832. }
  833. void setDot(int dot, Position.Bias dotBias) {
  834. NavigationFilter filter = component.getNavigationFilter();
  835. if (filter != null) {
  836. filter.setDot(getFilterBypass(), dot, dotBias);
  837. }
  838. else {
  839. handleSetDot(dot, dotBias);
  840. }
  841. }
  842. void handleSetDot(int dot, Position.Bias dotBias) {
  843. // move dot, if it changed
  844. Document doc = component.getDocument();
  845. if (doc != null) {
  846. dot = Math.min(dot, doc.getLength());
  847. }
  848. dot = Math.max(dot, 0);
  849. // The position (0,Backward) is out of range so disallow it.
  850. if( dot == 0 )
  851. dotBias = Position.Bias.Forward;
  852. mark = dot;
  853. if (this.dot != dot || this.dotBias != dotBias ||
  854. selectionTag != null || forceCaretPositionChange) {
  855. changeCaretPosition(dot, dotBias);
  856. }
  857. this.markBias = this.dotBias;
  858. this.markLTR = dotLTR;
  859. Highlighter h = component.getHighlighter();
  860. if ((h != null) && (selectionTag != null)) {
  861. h.removeHighlight(selectionTag);
  862. selectionTag = null;
  863. }
  864. }
  865. Position.Bias getDotBias() {
  866. return dotBias;
  867. }
  868. Position.Bias getMarkBias() {
  869. return markBias;
  870. }
  871. boolean isDotLeftToRight() {
  872. return dotLTR;
  873. }
  874. boolean isMarkLeftToRight() {
  875. return markLTR;
  876. }
  877. boolean isPositionLTR(int position, Position.Bias bias) {
  878. Document doc = component.getDocument();
  879. if(doc instanceof AbstractDocument ) {
  880. if(bias == Position.Bias.Backward && --position < 0)
  881. position = 0;
  882. return ((AbstractDocument)doc).isLeftToRight(position, position);
  883. }
  884. return true;
  885. }
  886. Position.Bias guessBiasForOffset(int offset, Position.Bias lastBias,
  887. boolean lastLTR) {
  888. // There is an abiguous case here. That if your model looks like:
  889. // abAB with the cursor at abB]A (visual representation of
  890. // 3 forward) deleting could either become abB] or
  891. // ab[B. I'ld actually prefer abB]. But, if I implement that
  892. // a delete at abBA] would result in aBA] vs a[BA which I
  893. // think is totally wrong. To get this right we need to know what
  894. // was deleted. And we could get this from the bidi structure
  895. // in the change event. So:
  896. // PENDING: base this off what was deleted.
  897. if(lastLTR != isPositionLTR(offset, lastBias)) {
  898. lastBias = Position.Bias.Backward;
  899. }
  900. else if(lastBias != Position.Bias.Backward &&
  901. lastLTR != isPositionLTR(offset, Position.Bias.Backward)) {
  902. lastBias = Position.Bias.Backward;
  903. }
  904. if (lastBias == Position.Bias.Backward && offset > 0) {
  905. try {
  906. Segment s = new Segment();
  907. component.getDocument().getText(offset - 1, 1, s);
  908. if (s.count > 0 && s.array[s.offset] == '\n') {
  909. lastBias = Position.Bias.Forward;
  910. }
  911. }
  912. catch (BadLocationException ble) {}
  913. }
  914. return lastBias;
  915. }
  916. // ---- local methods --------------------------------------------
  917. /**
  918. * Sets the caret position (dot) to a new location. This
  919. * causes the old and new location to be repainted. It
  920. * also makes sure that the caret is within the visible
  921. * region of the view, if the view is scrollable.
  922. */
  923. void changeCaretPosition(int dot, Position.Bias dotBias) {
  924. // repaint the old position and set the new value of
  925. // the dot.
  926. repaint();
  927. // Make sure the caret is visible if this window has the focus.
  928. if (flasher != null && flasher.isRunning()) {
  929. visible = true;
  930. flasher.restart();
  931. }
  932. // notify listeners at the caret moved
  933. this.dot = dot;
  934. this.dotBias = dotBias;
  935. dotLTR = isPositionLTR(dot, dotBias);
  936. fireStateChanged();
  937. updateSystemSelection();
  938. setMagicCaretPosition(null);
  939. // We try to repaint the caret later, since things
  940. // may be unstable at the time this is called
  941. // (i.e. we don't want to depend upon notification
  942. // order or the fact that this might happen on
  943. // an unsafe thread).
  944. Runnable callRepaintNewCaret = new Runnable() {
  945. public void run() {
  946. repaintNewCaret();
  947. }
  948. };
  949. SwingUtilities.invokeLater(callRepaintNewCaret);
  950. }
  951. /**
  952. * Repaints the new caret position, with the
  953. * assumption that this is happening on the
  954. * event thread so that calling <code>modelToView</code>
  955. * is safe.
  956. */
  957. void repaintNewCaret() {
  958. if (component != null) {
  959. TextUI mapper = component.getUI();
  960. Document doc = component.getDocument();
  961. if ((mapper != null) && (doc != null)) {
  962. // determine the new location and scroll if
  963. // not visible.
  964. Rectangle newLoc;
  965. try {
  966. newLoc = mapper.modelToView(component, this.dot, this.dotBias);
  967. } catch (BadLocationException e) {
  968. newLoc = null;
  969. }
  970. if (newLoc != null) {
  971. adjustVisibility(newLoc);
  972. // If there is no magic caret position, make one
  973. if (getMagicCaretPosition() == null) {
  974. setMagicCaretPosition(new Point(newLoc.x, newLoc.y));
  975. }
  976. }
  977. // repaint the new position
  978. damage(newLoc);
  979. }
  980. }
  981. }
  982. private void updateSystemSelection() {
  983. if (this.dot != this.mark && component != null) {
  984. Clipboard clip = getSystemSelection();
  985. if (clip != null) {
  986. clip.setContents(new StringSelection(
  987. component.getSelectedText()), getClipboardOwner());
  988. ownsSelection = true;
  989. }
  990. }
  991. }
  992. private Clipboard getSystemSelection() {
  993. try {
  994. return component.getToolkit().getSystemSelection();
  995. } catch (HeadlessException he) {
  996. // do nothing... there is no system clipboard
  997. } catch (SecurityException se) {
  998. // do nothing... there is no allowed system clipboard
  999. }
  1000. return null;
  1001. }
  1002. private ClipboardOwner getClipboardOwner() {
  1003. if (clipboardOwner == null) {
  1004. clipboardOwner = new ClipboardHandler();
  1005. }
  1006. return clipboardOwner;
  1007. }
  1008. /**
  1009. * This is invoked after the document changes to verify the current
  1010. * dot/mark is valid. We do this in case the <code>NavigationFilter</code>
  1011. * changed where to position the dot, that resulted in the current location
  1012. * being bogus.
  1013. */
  1014. private void ensureValidPosition() {
  1015. int length = component.getDocument().getLength();
  1016. if (dot > length || mark > length) {
  1017. // Current location is bogus and filter likely vetoed the
  1018. // change, force the reset without giving the filter a
  1019. // chance at changing it.
  1020. handleSetDot(length, Position.Bias.Forward);
  1021. }
  1022. }
  1023. /**
  1024. * Saves the current caret position. This is used when
  1025. * caret up/down actions occur, moving between lines
  1026. * that have uneven end positions.
  1027. *
  1028. * @param p the position
  1029. * @see #getMagicCaretPosition
  1030. */
  1031. public void setMagicCaretPosition(Point p) {
  1032. magicCaretPosition = p;
  1033. }
  1034. /**
  1035. * Gets the saved caret position.
  1036. *
  1037. * @return the position
  1038. * see #setMagicCaretPosition
  1039. */
  1040. public Point getMagicCaretPosition() {
  1041. return magicCaretPosition;
  1042. }
  1043. /**
  1044. * Compares this object to the specified object.
  1045. * The superclass behavior of comparing rectangles
  1046. * is not desired, so this is changed to the Object
  1047. * behavior.
  1048. *
  1049. * @param obj the object to compare this font with
  1050. * @return <code>true</code> if the objects are equal;
  1051. * <code>false</code> otherwise
  1052. */
  1053. public boolean equals(Object obj) {
  1054. return (this == obj);
  1055. }
  1056. public String toString() {
  1057. String s = "Dot=(" + dot + ", " + dotBias + ")";
  1058. s += " Mark=(" + mark + ", " + markBias + ")";
  1059. return s;
  1060. }
  1061. private NavigationFilter.FilterBypass getFilterBypass() {
  1062. if (filterBypass == null) {
  1063. filterBypass = new DefaultFilterBypass();
  1064. }
  1065. return filterBypass;
  1066. }
  1067. // Rectangle.contains returns false if passed a rect with a w or h == 0,
  1068. // this won't (assuming X,Y are contained with this rectangle).
  1069. private boolean _contains(int X, int Y, int W, int H) {
  1070. int w = this.width;
  1071. int h = this.height;
  1072. if ((w | h | W | H) < 0) {
  1073. // At least one of the dimensions is negative...
  1074. return false;
  1075. }
  1076. // Note: if any dimension is zero, tests below must return false...
  1077. int x = this.x;
  1078. int y = this.y;
  1079. if (X < x || Y < y) {
  1080. return false;
  1081. }
  1082. if (W > 0) {
  1083. w += x;
  1084. W += X;
  1085. if (W <= X) {
  1086. // X+W overflowed or W was zero, return false if...
  1087. // either original w or W was zero or
  1088. // x+w did not overflow or
  1089. // the overflowed x+w is smaller than the overflowed X+W
  1090. if (w >= x || W > w) return false;
  1091. } else {
  1092. // X+W did not overflow and W was not zero, return false if...
  1093. // original w was zero or
  1094. // x+w did not overflow and x+w is smaller than X+W
  1095. if (w >= x && W > w) return false;
  1096. }
  1097. }
  1098. else if ((x + w) < X) {
  1099. return false;
  1100. }
  1101. if (H > 0) {
  1102. h += y;
  1103. H += Y;
  1104. if (H <= Y) {
  1105. if (h >= y || H > h) return false;
  1106. } else {
  1107. if (h >= y && H > h) return false;
  1108. }
  1109. }
  1110. else if ((y + h) < Y) {
  1111. return false;
  1112. }
  1113. return true;
  1114. }
  1115. // --- serialization ---------------------------------------------
  1116. private void readObject(ObjectInputStream s)
  1117. throws ClassNotFoundException, IOException
  1118. {
  1119. s.defaultReadObject();
  1120. updateHandler = new UpdateHandler();
  1121. if (!s.readBoolean()) {
  1122. dotBias = Position.Bias.Forward;
  1123. }
  1124. else {
  1125. dotBias = Position.Bias.Backward;
  1126. }
  1127. if (!s.readBoolean()) {
  1128. markBias = Position.Bias.Forward;
  1129. }
  1130. else {
  1131. markBias = Position.Bias.Backward;
  1132. }
  1133. }
  1134. private void writeObject(ObjectOutputStream s) throws IOException {
  1135. s.defaultWriteObject();
  1136. s.writeBoolean((dotBias == Position.Bias.Backward));
  1137. s.writeBoolean((markBias == Position.Bias.Backward));
  1138. }
  1139. // ---- member variables ------------------------------------------
  1140. /**
  1141. * The event listener list.
  1142. */
  1143. protected EventListenerList listenerList = new EventListenerList();
  1144. /**
  1145. * The change event for the model.
  1146. * Only one ChangeEvent is needed per model instance since the
  1147. * event's only (read-only) state is the source property. The source
  1148. * of events generated here is always "this".
  1149. */
  1150. protected transient ChangeEvent changeEvent = null;
  1151. // package-private to avoid inner classes private member
  1152. // access bug
  1153. JTextComponent component;
  1154. /**
  1155. * flag to indicate if async updates should
  1156. * move the caret.
  1157. */
  1158. boolean async;
  1159. boolean visible;
  1160. int dot;
  1161. int mark;
  1162. Object selectionTag;
  1163. boolean selectionVisible;
  1164. Timer flasher;
  1165. Point magicCaretPosition;
  1166. transient Position.Bias dotBias;
  1167. transient Position.Bias markBias;
  1168. boolean dotLTR;
  1169. boolean markLTR;
  1170. transient UpdateHandler updateHandler = new UpdateHandler();
  1171. transient private int[] flagXPoints = new int[3];
  1172. transient private int[] flagYPoints = new int[3];
  1173. transient private FocusListener focusListener;
  1174. private transient NavigationFilter.FilterBypass filterBypass;
  1175. private transient ClipboardOwner clipboardOwner;
  1176. /**
  1177. * This is used to indicate if the caret currently owns the selection.
  1178. * This is always false if the system does not support the system
  1179. * clipboard.
  1180. */
  1181. private boolean ownsSelection;
  1182. /**
  1183. * If this is true, the location of the dot is updated regardless of
  1184. * the current location. This is set in the DocumentListener
  1185. * such that even if the model location of dot hasn't changed (perhaps do
  1186. * to a forward delete) the visual location is updated.
  1187. */
  1188. private boolean forceCaretPositionChange;
  1189. /**
  1190. * Whether or not mouseReleased should adjust the caret and focus.
  1191. * This flag is set by mousePressed if it wanted to adjust the caret
  1192. * and focus but couldn't because of a possible DnD operation.
  1193. */
  1194. private transient boolean shouldHandleRelease;
  1195. class SafeScroller implements Runnable {
  1196. SafeScroller(Rectangle r) {
  1197. this.r = r;
  1198. }
  1199. public void run() {
  1200. if (component != null) {
  1201. component.scrollRectToVisible(r);
  1202. }
  1203. }
  1204. Rectangle r;
  1205. }
  1206. /**
  1207. * ClipboardOwner that will toggle the visibility of the selection
  1208. * when ownership is lost.
  1209. */
  1210. private class ClipboardHandler implements ClipboardOwner {
  1211. public void lostOwnership(Clipboard clipboard, Transferable contents) {
  1212. if (ownsSelection) {
  1213. ownsSelection = false;
  1214. if (component != null && !component.hasFocus()) {
  1215. setSelectionVisible(false);
  1216. }
  1217. }
  1218. }
  1219. }
  1220. //
  1221. // DefaultCaret extends Rectangle, which is Serializable. DefaultCaret
  1222. // also implements FocusListener. Swing attempts to remove UI related
  1223. // classes (such as this class) by installing a FocusListener. When
  1224. // that FocusListener is messaged to writeObject it uninstalls the UI
  1225. // (refer to JComponent.EnableSerializationFocusListener). The
  1226. // problem with this is that any other FocusListeners will also get
  1227. // serialized due to how AWT handles listeners. So, for the time being
  1228. // this class is installed as the FocusListener on the JTextComponent,
  1229. // and it will forward the FocusListener methods to the DefaultCaret.
  1230. // Since FocusHandler is not Serializable DefaultCaret will not be
  1231. // pulled in.
  1232. //
  1233. private static class FocusHandler implements FocusListener {
  1234. private transient FocusListener fl;
  1235. FocusHandler(FocusListener fl) {
  1236. this.fl = fl;
  1237. }
  1238. /**
  1239. * Called when the component containing the caret gains
  1240. * focus. This is implemented to set the caret to visible
  1241. * if the component is editable.
  1242. *
  1243. * @param e the focus event
  1244. * @see FocusListener#focusGained
  1245. */
  1246. public void focusGained(FocusEvent e) {
  1247. fl.focusGained(e);
  1248. }
  1249. /**
  1250. * Called when the component containing the caret loses
  1251. * focus. This is implemented to set the caret to visibility
  1252. * to false.
  1253. *
  1254. * @param e the focus event
  1255. * @see FocusListener#focusLost
  1256. */
  1257. public void focusLost(FocusEvent e) {
  1258. fl.focusLost(e);
  1259. }
  1260. }
  1261. class UpdateHandler implements PropertyChangeListener, DocumentListener, ActionListener {
  1262. // --- ActionListener methods ----------------------------------
  1263. /**
  1264. * Invoked when the blink timer fires. This is called
  1265. * asynchronously. The simply changes the visibility
  1266. * and repaints the rectangle that last bounded the caret.
  1267. *
  1268. * @param e the action event
  1269. */
  1270. public void actionPerformed(ActionEvent e) {
  1271. if (!visible && (width == 0 || height == 0)) {
  1272. // setVisible(true) will cause a scroll, only do this if the
  1273. // new location is really valid.
  1274. if (component != null) {
  1275. TextUI mapper = component.getUI();
  1276. try {
  1277. Rectangle r = mapper.modelToView(component, dot,
  1278. dotBias);
  1279. if (r != null && r.width != 0 && r.height != 0) {
  1280. setVisible(true);
  1281. return;
  1282. }
  1283. } catch (BadLocationException ble) {
  1284. }
  1285. }
  1286. }
  1287. visible = !visible;
  1288. repaint();
  1289. }
  1290. // --- DocumentListener methods --------------------------------
  1291. /**
  1292. * Updates the dot and mark if they were changed by
  1293. * the insertion.
  1294. *
  1295. * @param e the document event
  1296. * @see DocumentListener#insertUpdate
  1297. */
  1298. public void insertUpdate(DocumentEvent e) {
  1299. if (async || SwingUtilities.isEventDispatchThread()) {
  1300. int adjust = 0;
  1301. int offset = e.getOffset();
  1302. int length = e.getLength();
  1303. int newDot = dot;
  1304. short changed = 0;
  1305. if (newDot >= offset) {
  1306. newDot += length;
  1307. changed |= 1;
  1308. }
  1309. int newMark = mark;
  1310. if (newMark >= offset) {
  1311. newMark += length;
  1312. changed |= 2;
  1313. }
  1314. if (changed != 0) {
  1315. Position.Bias dotBias = DefaultCaret.this.dotBias;
  1316. if(dot == offset) {
  1317. Document doc = component.getDocument();
  1318. boolean isNewline;
  1319. try {
  1320. Segment s = new Segment();
  1321. doc.getText(newDot - 1, 1, s);
  1322. isNewline = (s.count > 0 &&
  1323. s.array[s.offset] == '\n');
  1324. } catch (BadLocationException ble) {
  1325. isNewline = false;
  1326. }
  1327. if(isNewline) {
  1328. dotBias = Position.Bias.Forward;
  1329. } else {
  1330. dotBias = Position.Bias.Backward;
  1331. }
  1332. }
  1333. if (newMark == newDot) {
  1334. setDot(newDot, dotBias);
  1335. ensureValidPosition();
  1336. }
  1337. else {
  1338. setDot(newMark, markBias);
  1339. if (getDot() == newMark) {
  1340. // Due this test in case the filter vetoed the
  1341. // change in which case this probably won't be
  1342. // valid either.
  1343. moveDot(newDot, dotBias);
  1344. }
  1345. ensureValidPosition();
  1346. }
  1347. }
  1348. }
  1349. }
  1350. /**
  1351. * Updates the dot and mark if they were changed
  1352. * by the removal.
  1353. *
  1354. * @param e the document event
  1355. * @see DocumentListener#removeUpdate
  1356. */
  1357. public void removeUpdate(DocumentEvent e) {
  1358. if (async || SwingUtilities.isEventDispatchThread()) {
  1359. int adjust = 0;
  1360. int offs0 = e.getOffset();
  1361. int offs1 = offs0 + e.getLength();
  1362. int newDot = dot;
  1363. boolean adjustDotBias = false;
  1364. if (newDot >= offs1) {
  1365. newDot -= (offs1 - offs0);
  1366. if(newDot == offs1) {
  1367. adjustDotBias = true;
  1368. }
  1369. } else if (newDot >= offs0) {
  1370. newDot = offs0;
  1371. adjustDotBias = true;
  1372. }
  1373. int newMark = mark;
  1374. boolean adjustMarkBias = false;
  1375. if (newMark >= offs1) {
  1376. newMark -= (offs1 - offs0);
  1377. if(newMark == offs1) {
  1378. adjustMarkBias = true;
  1379. }
  1380. } else if (newMark >= offs0) {
  1381. newMark = offs0;
  1382. adjustMarkBias = true;
  1383. }
  1384. if (newMark == newDot) {
  1385. forceCaretPositionChange = true;
  1386. try {
  1387. setDot(newDot, guessBiasForOffset(newDot, dotBias,
  1388. dotLTR));
  1389. } finally {
  1390. forceCaretPositionChange = false;
  1391. }
  1392. ensureValidPosition();
  1393. } else {
  1394. Position.Bias dotBias = DefaultCaret.this.dotBias;
  1395. Position.Bias markBias = DefaultCaret.this.markBias;
  1396. if(adjustDotBias) {
  1397. dotBias = guessBiasForOffset(newDot, dotBias, dotLTR);
  1398. }
  1399. if(adjustMarkBias) {
  1400. markBias = guessBiasForOffset(mark, markBias, markLTR);
  1401. }
  1402. setDot(newMark, markBias);
  1403. if (getDot() == newMark) {
  1404. // Due this test in case the filter vetoed the change
  1405. // in which case this probably won't be valid either.
  1406. moveDot(newDot, dotBias);
  1407. }
  1408. ensureValidPosition();
  1409. }
  1410. }
  1411. }
  1412. /**
  1413. * Gives notification that an attribute or set of attributes changed.
  1414. *
  1415. * @param e the document event
  1416. * @see DocumentListener#changedUpdate
  1417. */
  1418. public void changedUpdate(DocumentEvent e) {
  1419. }
  1420. // --- PropertyChangeListener methods -----------------------
  1421. /**
  1422. * This method gets called when a bound property is changed.
  1423. * We are looking for document changes on the editor.
  1424. */
  1425. public void propertyChange(PropertyChangeEvent evt) {
  1426. Object oldValue = evt.getOldValue();
  1427. Object newValue = evt.getNewValue();
  1428. if ((oldValue instanceof Document) || (newValue instanceof Document)) {
  1429. setDot(0);
  1430. if (oldValue != null) {
  1431. ((Document)oldValue).removeDocumentListener(this);
  1432. }
  1433. if (newValue != null) {
  1434. ((Document)newValue).addDocumentListener(this);
  1435. }
  1436. }
  1437. }
  1438. }
  1439. private class DefaultFilterBypass extends NavigationFilter.FilterBypass {
  1440. public Caret getCaret() {
  1441. return DefaultCaret.this;
  1442. }
  1443. public void setDot(int dot, Position.Bias bias) {
  1444. handleSetDot(dot, bias);
  1445. }
  1446. public void moveDot(int dot, Position.Bias bias) {
  1447. handleMoveDot(dot, bias);
  1448. }
  1449. }
  1450. }