1. /*
  2. * @(#)DefaultCaret.java 1.138 04/06/24
  3. *
  4. * Copyright 2004 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. import com.sun.java.swing.SwingUtilities2;
  20. /**
  21. * A default implementation of Caret. The caret is rendered as
  22. * a vertical line in the color specified by the CaretColor property
  23. * of the associated JTextComponent. It can blink at the rate specified
  24. * by the BlinkRate property.
  25. * <p>
  26. * This implementation expects two sources of asynchronous notification.
  27. * The timer thread fires asynchronously, and causes the caret to simply
  28. * repaint the most recent bounding box. The caret also tracks change
  29. * as the document is modified. Typically this will happen on the
  30. * event dispatch thread as a result of some mouse or keyboard event.
  31. * The caret behavior on both synchronous and asynchronous documents updates
  32. * is controlled by <code>UpdatePolicy</code> property. The repaint of the
  33. * new caret location will occur on the event thread in any case, as calls to
  34. * <code>modelToView</code> are only safe on the event thread.
  35. * <p>
  36. * The caret acts as a mouse and focus listener on the text component
  37. * it has been installed in, and defines the caret semantics based upon
  38. * those events. The listener methods can be reimplemented to change the
  39. * semantics.
  40. * By default, the first mouse button will be used to set focus and caret
  41. * position. Dragging the mouse pointer with the first mouse button will
  42. * sweep out a selection that is contiguous in the model. If the associated
  43. * text component is editable, the caret will become visible when focus
  44. * is gained, and invisible when focus is lost.
  45. * <p>
  46. * The Highlighter bound to the associated text component is used to
  47. * render the selection by default.
  48. * Selection appearance can be customized by supplying a
  49. * painter to use for the highlights. By default a painter is used that
  50. * will render a solid color as specified in the associated text component
  51. * in the <code>SelectionColor</code> property. This can easily be changed
  52. * by reimplementing the
  53. * <a href="#getSelectionHighlighter">getSelectionHighlighter</a>
  54. * method.
  55. * <p>
  56. * A customized caret appearance can be achieved by reimplementing
  57. * the paint method. If the paint method is changed, the damage method
  58. * should also be reimplemented to cause a repaint for the area needed
  59. * to render the caret. The caret extends the Rectangle class which
  60. * is used to hold the bounding box for where the caret was last rendered.
  61. * This enables the caret to repaint in a thread-safe manner when the
  62. * caret moves without making a call to modelToView which is unstable
  63. * between model updates and view repair (i.e. the order of delivery
  64. * to DocumentListeners is not guaranteed).
  65. * <p>
  66. * The magic caret position is set to null when the caret position changes.
  67. * A timer is used to determine the new location (after the caret change).
  68. * When the timer fires, if the magic caret position is still null it is
  69. * reset to the current caret position. Any actions that change
  70. * the caret position and want the magic caret position to remain the
  71. * same, must remember the magic caret position, change the cursor, and
  72. * then set the magic caret position to its original value. This has the
  73. * benefit that only actions that want the magic caret position to persist
  74. * (such as open/down) need to know about it.
  75. * <p>
  76. * <strong>Warning:</strong>
  77. * Serialized objects of this class will not be compatible with
  78. * future Swing releases. The current serialization support is
  79. * appropriate for short term storage or RMI between applications running
  80. * the same version of Swing. As of 1.4, support for long term storage
  81. * of all JavaBeans<sup><font size="-2">TM</font></sup>
  82. * has been added to the <code>java.beans</code> package.
  83. * Please see {@link java.beans.XMLEncoder}.
  84. *
  85. * @author Timothy Prinzing
  86. * @version 1.138 06/24/04
  87. * @see Caret
  88. */
  89. public class DefaultCaret extends Rectangle implements Caret, FocusListener, MouseListener, MouseMotionListener {
  90. /**
  91. * Indicates that the caret position is to be updated only when
  92. * document changes are performed on the Event Dispatching Thread.
  93. * @see #setUpdatePolicy
  94. * @see #getUpdatePolicy
  95. * @since 1.5
  96. */
  97. public static final int UPDATE_WHEN_ON_EDT = 0;
  98. /**
  99. * Indicates that the caret should remain at the same
  100. * absolute position in the document regardless of any document
  101. * updates, except when the document length becomes less than
  102. * the current caret position due to removal. In that case the caret
  103. * position is adjusted to the end of the document.
  104. *
  105. * @see #setUpdatePolicy
  106. * @see #getUpdatePolicy
  107. * @since 1.5
  108. */
  109. public static final int NEVER_UPDATE = 1;
  110. /**
  111. * Indicates that the caret position is to be <b>always</b>
  112. * updated accordingly to the document changes regardless whether
  113. * the document updates are performed on the Event Dispatching Thread
  114. * or not.
  115. *
  116. * @see #setUpdatePolicy
  117. * @see #getUpdatePolicy
  118. * @since 1.5
  119. */
  120. public static final int ALWAYS_UPDATE = 2;
  121. /**
  122. * Constructs a default caret.
  123. */
  124. public DefaultCaret() {
  125. }
  126. /**
  127. * Sets the caret movement policy on the document updates. Normally
  128. * the caret updates its absolute position within the document on
  129. * insertions occurred before or at the caret position and
  130. * on removals before the caret position. 'Absolute position'
  131. * means here the position relative to the start of the document.
  132. * For example if
  133. * a character is typed within editable text component it is inserted
  134. * at the caret position and the caret moves to the next absolute
  135. * position within the document due to insertion and if
  136. * <code>BACKSPACE</code> is typed then caret decreases its absolute
  137. * position due to removal of a character before it. Sometimes
  138. * it may be useful to turn off the caret position updates so that
  139. * the caret stays at the same absolute position within the
  140. * document position regardless of any document updates.
  141. * <p>
  142. * The following update policies are allowed:
  143. * <ul>
  144. * <li><code>NEVER_UPDATE</code>: the caret stays at the same
  145. * absolute position in the document regardless of any document
  146. * updates, except when document length becomes less than
  147. * the current caret position due to removal. In that case caret
  148. * position is adjusted to the end of the document.
  149. * The caret doesn't try to keep itself visible by scrolling
  150. * the associated view when using this policy. </li>
  151. * <li><code>ALWAYS_UPDATE</code>: the caret always tracks document
  152. * changes. For regular changes it increases its position
  153. * if an insertion occurs before or at its current position,
  154. * and decreases position if a removal occurs before
  155. * its current position. For undo/redo updates it is always
  156. * moved to the position where update occurred. The caret
  157. * also tries to keep itself visible by calling
  158. * <code>adjustVisibility</code> method.</li>
  159. * <li><code>UPDATE_WHEN_ON_EDT</code>: acts like <code>ALWAYS_UPDATE</code>
  160. * if the document updates are performed on the Event Dispatching Thread
  161. * and like <code>NEVER_UPDATE</code> if updates are performed on
  162. * other thread. </li>
  163. * </ul> <p>
  164. * The default property value is <code>UPDATE_WHEN_ON_EDT</code>.
  165. *
  166. * @param policy one of the following values : <code>UPDATE_WHEN_ON_EDT</code>,
  167. * <code>NEVER_UPDATE</code>, <code>ALWAYS_UPDATE</code>
  168. * @throws IllegalArgumentException if invalid value is passed
  169. *
  170. * @see #getUpdatePolicy
  171. * @see #adjustVisibility
  172. * @see #UPDATE_WHEN_ON_EDT
  173. * @see #NEVER_UPDATE
  174. * @see #ALWAYS_UPDATE
  175. *
  176. * @since 1.5
  177. */
  178. public void setUpdatePolicy(int policy) {
  179. updatePolicy = policy;
  180. }
  181. /**
  182. * Gets the caret movement policy on document updates.
  183. *
  184. * @return one of the following values : <code>UPDATE_WHEN_ON_EDT</code>,
  185. * <code>NEVER_UPDATE</code>, <code>ALWAYS_UPDATE</code>
  186. *
  187. * @see #setUpdatePolicy
  188. * @see #UPDATE_WHEN_ON_EDT
  189. * @see #NEVER_UPDATE
  190. * @see #ALWAYS_UPDATE
  191. *
  192. * @since 1.5
  193. */
  194. public int getUpdatePolicy() {
  195. return updatePolicy;
  196. }
  197. /**
  198. * Gets the text editor component that this caret is
  199. * is bound to.
  200. *
  201. * @return the component
  202. */
  203. protected final JTextComponent getComponent() {
  204. return component;
  205. }
  206. /**
  207. * Cause the caret to be painted. The repaint
  208. * area is the bounding box of the caret (i.e.
  209. * the caret rectangle or <em>this</em>).
  210. * <p>
  211. * This method is thread safe, although most Swing methods
  212. * are not. Please see
  213. * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">
  214. * Threads and Swing</A> for more information.
  215. */
  216. protected final synchronized void repaint() {
  217. if (component != null) {
  218. component.repaint(x, y, width, height);
  219. }
  220. }
  221. /**
  222. * Damages the area surrounding the caret to cause
  223. * it to be repainted in a new location. If paint()
  224. * is reimplemented, this method should also be
  225. * reimplemented. This method should update the
  226. * caret bounds (x, y, width, and height).
  227. *
  228. * @param r the current location of the caret
  229. * @see #paint
  230. */
  231. protected synchronized void damage(Rectangle r) {
  232. if (r != null) {
  233. int damageWidth = getCaretWidth(r.height);
  234. x = r.x - 4 - (damageWidth >> 1);
  235. y = r.y;
  236. width = 9 + damageWidth;
  237. height = r.height;
  238. repaint();
  239. }
  240. }
  241. /**
  242. * Scrolls the associated view (if necessary) to make
  243. * the caret visible. Since how this should be done
  244. * is somewhat of a policy, this method can be
  245. * reimplemented to change the behavior. By default
  246. * the scrollRectToVisible method is called on the
  247. * associated component.
  248. *
  249. * @param nloc the new position to scroll to
  250. */
  251. protected void adjustVisibility(Rectangle nloc) {
  252. if(component == null) {
  253. return;
  254. }
  255. if (SwingUtilities.isEventDispatchThread()) {
  256. component.scrollRectToVisible(nloc);
  257. } else {
  258. SwingUtilities.invokeLater(new SafeScroller(nloc));
  259. }
  260. }
  261. /**
  262. * Gets the painter for the Highlighter.
  263. *
  264. * @return the painter
  265. */
  266. protected Highlighter.HighlightPainter getSelectionPainter() {
  267. return DefaultHighlighter.DefaultPainter;
  268. }
  269. /**
  270. * Tries to set the position of the caret from
  271. * the coordinates of a mouse event, using viewToModel().
  272. *
  273. * @param e the mouse event
  274. */
  275. protected void positionCaret(MouseEvent e) {
  276. Point pt = new Point(e.getX(), e.getY());
  277. Position.Bias[] biasRet = new Position.Bias[1];
  278. int pos = component.getUI().viewToModel(component, pt, biasRet);
  279. if(biasRet[0] == null)
  280. biasRet[0] = Position.Bias.Forward;
  281. if (pos >= 0) {
  282. setDot(pos, biasRet[0]);
  283. }
  284. }
  285. /**
  286. * Tries to move the position of the caret from
  287. * the coordinates of a mouse event, using viewToModel().
  288. * This will cause a selection if the dot and mark
  289. * are different.
  290. *
  291. * @param e the mouse event
  292. */
  293. protected void moveCaret(MouseEvent e) {
  294. Point pt = new Point(e.getX(), e.getY());
  295. Position.Bias[] biasRet = new Position.Bias[1];
  296. int pos = component.getUI().viewToModel(component, pt, biasRet);
  297. if(biasRet[0] == null)
  298. biasRet[0] = Position.Bias.Forward;
  299. if (pos >= 0) {
  300. moveDot(pos, biasRet[0]);
  301. }
  302. }
  303. // --- FocusListener methods --------------------------
  304. /**
  305. * Called when the component containing the caret gains
  306. * focus. This is implemented to set the caret to visible
  307. * if the component is editable.
  308. *
  309. * @param e the focus event
  310. * @see FocusListener#focusGained
  311. */
  312. public void focusGained(FocusEvent e) {
  313. if (component.isEnabled()) {
  314. if (component.isEditable()) {
  315. setVisible(true);
  316. }
  317. setSelectionVisible(true);
  318. }
  319. }
  320. /**
  321. * Called when the component containing the caret loses
  322. * focus. This is implemented to set the caret to visibility
  323. * to false.
  324. *
  325. * @param e the focus event
  326. * @see FocusListener#focusLost
  327. */
  328. public void focusLost(FocusEvent e) {
  329. setVisible(false);
  330. setSelectionVisible(ownsSelection || e.isTemporary());
  331. }
  332. /**
  333. * Selects word based on the MouseEvent
  334. */
  335. private void selectWord(MouseEvent e) {
  336. if (selectedWordEvent != null
  337. && selectedWordEvent.getX() == e.getX()
  338. && selectedWordEvent.getY() == e.getY()) {
  339. //we already done selection for this
  340. return;
  341. }
  342. Action a = null;
  343. ActionMap map = getComponent().getActionMap();
  344. if (map != null) {
  345. a = map.get(DefaultEditorKit.selectWordAction);
  346. }
  347. if (a == null) {
  348. if (selectWord == null) {
  349. selectWord = new DefaultEditorKit.SelectWordAction();
  350. }
  351. a = selectWord;
  352. }
  353. a.actionPerformed(new ActionEvent(getComponent(),
  354. ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e.getModifiers()));
  355. selectedWordEvent = e;
  356. }
  357. // --- MouseListener methods -----------------------------------
  358. /**
  359. * Called when the mouse is clicked. If the click was generated
  360. * from button1, a double click selects a word,
  361. * and a triple click the current line.
  362. *
  363. * @param e the mouse event
  364. * @see MouseListener#mouseClicked
  365. */
  366. public void mouseClicked(MouseEvent e) {
  367. if (! e.isConsumed()) {
  368. int nclicks = e.getClickCount();
  369. if (SwingUtilities.isLeftMouseButton(e)) {
  370. // mouse 1 behavior
  371. if(nclicks == 1) {
  372. selectedWordEvent = null;
  373. } else if(nclicks == 2
  374. && SwingUtilities2.canEventAccessSystemClipboard(e)) {
  375. selectWord(e);
  376. selectedWordEvent = null;
  377. } else if(nclicks == 3
  378. && SwingUtilities2.canEventAccessSystemClipboard(e)) {
  379. Action a = null;
  380. ActionMap map = getComponent().getActionMap();
  381. if (map != null) {
  382. a = map.get(DefaultEditorKit.selectLineAction);
  383. }
  384. if (a == null) {
  385. if (selectLine == null) {
  386. selectLine = new DefaultEditorKit.SelectLineAction();
  387. }
  388. a = selectLine;
  389. }
  390. a.actionPerformed(new ActionEvent(getComponent(),
  391. ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e.getModifiers()));
  392. }
  393. } else if (SwingUtilities.isMiddleMouseButton(e)) {
  394. // mouse 2 behavior
  395. if (nclicks == 1 && component.isEditable() && component.isEnabled()
  396. && SwingUtilities2.canEventAccessSystemClipboard(e)) {
  397. // paste system selection, if it exists
  398. JTextComponent c = (JTextComponent) e.getSource();
  399. if (c != null) {
  400. try {
  401. Toolkit tk = c.getToolkit();
  402. Clipboard buffer = tk.getSystemSelection();
  403. if (buffer != null) {
  404. // platform supports system selections, update it.
  405. adjustCaret(e);
  406. TransferHandler th = c.getTransferHandler();
  407. if (th != null) {
  408. Transferable trans = null;
  409. try {
  410. trans = buffer.getContents(null);
  411. } catch (IllegalStateException ise) {
  412. // clipboard was unavailable
  413. UIManager.getLookAndFeel().provideErrorFeedback(c);
  414. }
  415. if (trans != null) {
  416. th.importData(c, trans);
  417. }
  418. }
  419. adjustFocus(true);
  420. }
  421. } catch (HeadlessException he) {
  422. // do nothing... there is no system clipboard
  423. }
  424. }
  425. }
  426. }
  427. }
  428. }
  429. /**
  430. * If button 1 is pressed, this is implemented to
  431. * request focus on the associated text component,
  432. * and to set the caret position. If the shift key is held down,
  433. * the caret will be moved, potentially resulting in a selection,
  434. * otherwise the
  435. * caret position will be set to the new location. If the component
  436. * is not enabled, there will be no request for focus.
  437. *
  438. * @param e the mouse event
  439. * @see MouseListener#mousePressed
  440. */
  441. public void mousePressed(MouseEvent e) {
  442. if (SwingUtilities.isLeftMouseButton(e)) {
  443. if (e.isConsumed()) {
  444. shouldHandleRelease = true;
  445. } else {
  446. shouldHandleRelease = false;
  447. adjustCaretAndFocus(e);
  448. if (e.getClickCount() == 2
  449. && SwingUtilities2.canEventAccessSystemClipboard(e)) {
  450. selectWord(e);
  451. }
  452. }
  453. }
  454. }
  455. void adjustCaretAndFocus(MouseEvent e) {
  456. adjustCaret(e);
  457. adjustFocus(false);
  458. }
  459. /**
  460. * Adjusts the caret location based on the MouseEvent.
  461. */
  462. private void adjustCaret(MouseEvent e) {
  463. if ((e.getModifiers() & ActionEvent.SHIFT_MASK) != 0 &&
  464. getDot() != -1) {
  465. moveCaret(e);
  466. } else {
  467. positionCaret(e);
  468. }
  469. }
  470. /**
  471. * Adjusts the focus, if necessary.
  472. *
  473. * @param inWindow if true indicates requestFocusInWindow should be used
  474. */
  475. private void adjustFocus(boolean inWindow) {
  476. if ((component != null) && component.isEnabled() &&
  477. component.isRequestFocusEnabled()) {
  478. if (inWindow) {
  479. component.requestFocusInWindow();
  480. }
  481. else {
  482. component.requestFocus();
  483. }
  484. }
  485. }
  486. /**
  487. * Called when the mouse is released.
  488. *
  489. * @param e the mouse event
  490. * @see MouseListener#mouseReleased
  491. */
  492. public void mouseReleased(MouseEvent e) {
  493. if (shouldHandleRelease && SwingUtilities.isLeftMouseButton(e)) {
  494. adjustCaretAndFocus(e);
  495. }
  496. }
  497. /**
  498. * Called when the mouse enters a region.
  499. *
  500. * @param e the mouse event
  501. * @see MouseListener#mouseEntered
  502. */
  503. public void mouseEntered(MouseEvent e) {
  504. }
  505. /**
  506. * Called when the mouse exits a region.
  507. *
  508. * @param e the mouse event
  509. * @see MouseListener#mouseExited
  510. */
  511. public void mouseExited(MouseEvent e) {
  512. }
  513. // --- MouseMotionListener methods -------------------------
  514. /**
  515. * Moves the caret position
  516. * according to the mouse pointer's current
  517. * location. This effectively extends the
  518. * selection. By default, this is only done
  519. * for mouse button 1.
  520. *
  521. * @param e the mouse event
  522. * @see MouseMotionListener#mouseDragged
  523. */
  524. public void mouseDragged(MouseEvent e) {
  525. if ((! e.isConsumed()) && SwingUtilities.isLeftMouseButton(e)) {
  526. moveCaret(e);
  527. }
  528. }
  529. /**
  530. * Called when the mouse is moved.
  531. *
  532. * @param e the mouse event
  533. * @see MouseMotionListener#mouseMoved
  534. */
  535. public void mouseMoved(MouseEvent e) {
  536. }
  537. // ---- Caret methods ---------------------------------
  538. /**
  539. * Renders the caret as a vertical line. If this is reimplemented
  540. * the damage method should also be reimplemented as it assumes the
  541. * shape of the caret is a vertical line. Sets the caret color to
  542. * the value returned by getCaretColor().
  543. * <p>
  544. * If there are multiple text directions present in the associated
  545. * document, a flag indicating the caret bias will be rendered.
  546. * This will occur only if the associated document is a subclass
  547. * of AbstractDocument and there are multiple bidi levels present
  548. * in the bidi element structure (i.e. the text has multiple
  549. * directions associated with it).
  550. *
  551. * @param g the graphics context
  552. * @see #damage
  553. */
  554. public void paint(Graphics g) {
  555. if(isVisible()) {
  556. try {
  557. TextUI mapper = component.getUI();
  558. Rectangle r = mapper.modelToView(component, dot, dotBias);
  559. if ((r == null) || ((r.width == 0) && (r.height == 0))) {
  560. return;
  561. }
  562. if (width > 0 && height > 0 &&
  563. !this._contains(r.x, r.y, r.width, r.height)) {
  564. // We seem to have gotten out of sync and no longer
  565. // contain the right location, adjust accordingly.
  566. Rectangle clip = g.getClipBounds();
  567. if (clip != null && !clip.contains(this)) {
  568. // Clip doesn't contain the old location, force it
  569. // to be repainted lest we leave a caret around.
  570. repaint();
  571. }
  572. // This will potentially cause a repaint of something
  573. // we're already repainting, but without changing the
  574. // semantics of damage we can't really get around this.
  575. damage(r);
  576. }
  577. g.setColor(component.getCaretColor());
  578. int paintWidth = getCaretWidth(r.height);
  579. r.x -= paintWidth >> 1;
  580. g.fillRect(r.x, r.y, paintWidth , r.height - 1);
  581. // see if we should paint a flag to indicate the bias
  582. // of the caret.
  583. // PENDING(prinz) this should be done through
  584. // protected methods so that alternative LAF
  585. // will show bidi information.
  586. Document doc = component.getDocument();
  587. if (doc instanceof AbstractDocument) {
  588. Element bidi = ((AbstractDocument)doc).getBidiRootElement();
  589. if ((bidi != null) && (bidi.getElementCount() > 1)) {
  590. // there are multiple directions present.
  591. flagXPoints[0] = r.x + ((dotLTR) ? paintWidth : 0);
  592. flagYPoints[0] = r.y;
  593. flagXPoints[1] = flagXPoints[0];
  594. flagYPoints[1] = flagYPoints[0] + 4;
  595. flagXPoints[2] = flagXPoints[0] + ((dotLTR) ? 4 : -4);
  596. flagYPoints[2] = flagYPoints[0];
  597. g.fillPolygon(flagXPoints, flagYPoints, 3);
  598. }
  599. }
  600. } catch (BadLocationException e) {
  601. // can't render I guess
  602. //System.err.println("Can't render cursor");
  603. }
  604. }
  605. }
  606. /**
  607. * Called when the UI is being installed into the
  608. * interface of a JTextComponent. This can be used
  609. * to gain access to the model that is being navigated
  610. * by the implementation of this interface. Sets the dot
  611. * and mark to 0, and establishes document, property change,
  612. * focus, mouse, and mouse motion listeners.
  613. *
  614. * @param c the component
  615. * @see Caret#install
  616. */
  617. public void install(JTextComponent c) {
  618. component = c;
  619. Document doc = c.getDocument();
  620. dot = mark = 0;
  621. dotLTR = markLTR = true;
  622. dotBias = markBias = Position.Bias.Forward;
  623. if (doc != null) {
  624. doc.addDocumentListener(handler);
  625. }
  626. c.addPropertyChangeListener(handler);
  627. c.addFocusListener(this);
  628. c.addMouseListener(this);
  629. c.addMouseMotionListener(this);
  630. // if the component already has focus, it won't
  631. // be notified.
  632. if (component.hasFocus()) {
  633. focusGained(null);
  634. }
  635. Number ratio = (Number) c.getClientProperty("caretAspectRatio");
  636. if (ratio != null) {
  637. aspectRatio = ratio.floatValue();
  638. } else {
  639. aspectRatio = -1;
  640. }
  641. Integer width = (Integer) c.getClientProperty("caretWidth");
  642. if (width != null) {
  643. caretWidth = width.intValue();
  644. } else {
  645. caretWidth = -1;
  646. }
  647. }
  648. /**
  649. * Called when the UI is being removed from the
  650. * interface of a JTextComponent. This is used to
  651. * unregister any listeners that were attached.
  652. *
  653. * @param c the component
  654. * @see Caret#deinstall
  655. */
  656. public void deinstall(JTextComponent c) {
  657. c.removeMouseListener(this);
  658. c.removeMouseMotionListener(this);
  659. c.removeFocusListener(this);
  660. c.removePropertyChangeListener(handler);
  661. Document doc = c.getDocument();
  662. if (doc != null) {
  663. doc.removeDocumentListener(handler);
  664. }
  665. synchronized(this) {
  666. component = null;
  667. }
  668. if (flasher != null) {
  669. flasher.stop();
  670. }
  671. }
  672. /**
  673. * Adds a listener to track whenever the caret position has
  674. * been changed.
  675. *
  676. * @param l the listener
  677. * @see Caret#addChangeListener
  678. */
  679. public void addChangeListener(ChangeListener l) {
  680. listenerList.add(ChangeListener.class, l);
  681. }
  682. /**
  683. * Removes a listener that was tracking caret position changes.
  684. *
  685. * @param l the listener
  686. * @see Caret#removeChangeListener
  687. */
  688. public void removeChangeListener(ChangeListener l) {
  689. listenerList.remove(ChangeListener.class, l);
  690. }
  691. /**
  692. * Returns an array of all the change listeners
  693. * registered on this caret.
  694. *
  695. * @return all of this caret's <code>ChangeListener</code>s
  696. * or an empty
  697. * array if no change listeners are currently registered
  698. *
  699. * @see #addChangeListener
  700. * @see #removeChangeListener
  701. *
  702. * @since 1.4
  703. */
  704. public ChangeListener[] getChangeListeners() {
  705. return (ChangeListener[])listenerList.getListeners(
  706. ChangeListener.class);
  707. }
  708. /**
  709. * Notifies all listeners that have registered interest for
  710. * notification on this event type. The event instance
  711. * is lazily created using the parameters passed into
  712. * the fire method. The listener list is processed last to first.
  713. *
  714. * @see EventListenerList
  715. */
  716. protected void fireStateChanged() {
  717. // Guaranteed to return a non-null array
  718. Object[] listeners = listenerList.getListenerList();
  719. // Process the listeners last to first, notifying
  720. // those that are interested in this event
  721. for (int i = listeners.length-2; i>=0; i-=2) {
  722. if (listeners[i]==ChangeListener.class) {
  723. // Lazily create the event:
  724. if (changeEvent == null)
  725. changeEvent = new ChangeEvent(this);
  726. ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
  727. }
  728. }
  729. }
  730. /**
  731. * Returns an array of all the objects currently registered
  732. * as <code><em>Foo</em>Listener</code>s
  733. * upon this caret.
  734. * <code><em>Foo</em>Listener</code>s are registered using the
  735. * <code>add<em>Foo</em>Listener</code> method.
  736. *
  737. * <p>
  738. *
  739. * You can specify the <code>listenerType</code> argument
  740. * with a class literal,
  741. * such as
  742. * <code><em>Foo</em>Listener.class</code>.
  743. * For example, you can query a
  744. * <code>DefaultCaret</code> <code>c</code>
  745. * for its change listeners with the following code:
  746. *
  747. * <pre>ChangeListener[] cls = (ChangeListener[])(c.getListeners(ChangeListener.class));</pre>
  748. *
  749. * If no such listeners exist, this method returns an empty array.
  750. *
  751. * @param listenerType the type of listeners requested; this parameter
  752. * should specify an interface that descends from
  753. * <code>java.util.EventListener</code>
  754. * @return an array of all objects registered as
  755. * <code><em>Foo</em>Listener</code>s on this component,
  756. * or an empty array if no such
  757. * listeners have been added
  758. * @exception ClassCastException if <code>listenerType</code>
  759. * doesn't specify a class or interface that implements
  760. * <code>java.util.EventListener</code>
  761. *
  762. * @see #getChangeListeners
  763. *
  764. * @since 1.3
  765. */
  766. public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
  767. return listenerList.getListeners(listenerType);
  768. }
  769. /**
  770. * Changes the selection visibility.
  771. *
  772. * @param vis the new visibility
  773. */
  774. public void setSelectionVisible(boolean vis) {
  775. if (vis != selectionVisible) {
  776. selectionVisible = vis;
  777. if (selectionVisible) {
  778. // show
  779. Highlighter h = component.getHighlighter();
  780. if ((dot != mark) && (h != null) && (selectionTag == null)) {
  781. int p0 = Math.min(dot, mark);
  782. int p1 = Math.max(dot, mark);
  783. Highlighter.HighlightPainter p = getSelectionPainter();
  784. try {
  785. selectionTag = h.addHighlight(p0, p1, p);
  786. } catch (BadLocationException bl) {
  787. selectionTag = null;
  788. }
  789. }
  790. } else {
  791. // hide
  792. if (selectionTag != null) {
  793. Highlighter h = component.getHighlighter();
  794. h.removeHighlight(selectionTag);
  795. selectionTag = null;
  796. }
  797. }
  798. }
  799. }
  800. /**
  801. * Checks whether the current selection is visible.
  802. *
  803. * @return true if the selection is visible
  804. */
  805. public boolean isSelectionVisible() {
  806. return selectionVisible;
  807. }
  808. /**
  809. * Determines if the caret is currently active.
  810. * <p>
  811. * This method returns whether or not the <code>Caret</code>
  812. * is currently in a blinking state. It does not provide
  813. * information as to whether it is currently blinked on or off.
  814. * To determine if the caret is currently painted use the
  815. * <code>isVisible</code> method.
  816. *
  817. * @return <code>true</code> if active else <code>false</code>
  818. * @see #isVisible
  819. *
  820. * @since 1.5
  821. */
  822. public boolean isActive() {
  823. return active;
  824. }
  825. /**
  826. * Indicates whether or not the caret is currently visible. As the
  827. * caret flashes on and off the return value of this will change
  828. * between true, when the caret is painted, and false, when the
  829. * caret is not painted. <code>isActive</code> indicates whether
  830. * or not the caret is in a blinking state, such that it <b>can</b>
  831. * be visible, and <code>isVisible</code> indicates whether or not
  832. * the caret <b>is</b> actually visible.
  833. * <p>
  834. * Subclasses that wish to render a different flashing caret
  835. * should override paint and only paint the caret if this method
  836. * returns true.
  837. *
  838. * @return true if visible else false
  839. * @see Caret#isVisible
  840. * @see #isActive
  841. */
  842. public boolean isVisible() {
  843. return visible;
  844. }
  845. /**
  846. * Sets the caret visibility, and repaints the caret.
  847. * It is important to understand the relationship between this method,
  848. * <code>isVisible</code> and <code>isActive</code>.
  849. * Calling this method with a value of <code>true</code> activates the
  850. * caret blinking. Setting it to <code>false</code> turns it completely off.
  851. * To determine whether the blinking is active, you should call
  852. * <code>isActive</code>. In effect, <code>isActive</code> is an
  853. * appropriate corresponding "getter" method for this one.
  854. * <code>isVisible</code> can be used to fetch the current
  855. * visibility status of the caret, meaning whether or not it is currently
  856. * painted. This status will change as the caret blinks on and off.
  857. * <p>
  858. * Here's a list showing the potential return values of both
  859. * <code>isActive</code> and <code>isVisible</code>
  860. * after calling this method:
  861. * <p>
  862. * <b><code>setVisible(true)</code></b>:
  863. * <ul>
  864. * <li>isActive(): true</li>
  865. * <li>isVisible(): true or false depending on whether
  866. * or not the caret is blinked on or off</li>
  867. * </ul>
  868. * <p>
  869. * <b><code>setVisible(false)</code></b>:
  870. * <ul>
  871. * <li>isActive(): false</li>
  872. * <li>isVisible(): false</li>
  873. * </ul>
  874. *
  875. * @param e the visibility specifier
  876. * @see #isActive
  877. * @see Caret#setVisible
  878. */
  879. public void setVisible(boolean e) {
  880. // focus lost notification can come in later after the
  881. // caret has been deinstalled, in which case the component
  882. // will be null.
  883. if (component != null) {
  884. active = e;
  885. TextUI mapper = component.getUI();
  886. if (visible != e) {
  887. visible = e;
  888. // repaint the caret
  889. try {
  890. Rectangle loc = mapper.modelToView(component, dot,dotBias);
  891. damage(loc);
  892. } catch (BadLocationException badloc) {
  893. // hmm... not legally positioned
  894. }
  895. }
  896. }
  897. if (flasher != null) {
  898. if (visible) {
  899. flasher.start();
  900. } else {
  901. flasher.stop();
  902. }
  903. }
  904. }
  905. /**
  906. * Sets the caret blink rate.
  907. *
  908. * @param rate the rate in milliseconds, 0 to stop blinking
  909. * @see Caret#setBlinkRate
  910. */
  911. public void setBlinkRate(int rate) {
  912. if (rate != 0) {
  913. if (flasher == null) {
  914. flasher = new Timer(rate, handler);
  915. }
  916. flasher.setDelay(rate);
  917. } else {
  918. if (flasher != null) {
  919. flasher.stop();
  920. flasher.removeActionListener(handler);
  921. flasher = null;
  922. }
  923. }
  924. }
  925. /**
  926. * Gets the caret blink rate.
  927. *
  928. * @return the delay in milliseconds. If this is
  929. * zero the caret will not blink.
  930. * @see Caret#getBlinkRate
  931. */
  932. public int getBlinkRate() {
  933. return (flasher == null) ? 0 : flasher.getDelay();
  934. }
  935. /**
  936. * Fetches the current position of the caret.
  937. *
  938. * @return the position >= 0
  939. * @see Caret#getDot
  940. */
  941. public int getDot() {
  942. return dot;
  943. }
  944. /**
  945. * Fetches the current position of the mark. If there is a selection,
  946. * the dot and mark will not be the same.
  947. *
  948. * @return the position >= 0
  949. * @see Caret#getMark
  950. */
  951. public int getMark() {
  952. return mark;
  953. }
  954. /**
  955. * Sets the caret position and mark to some position. This
  956. * implicitly sets the selection range to zero.
  957. *
  958. * @param dot the position >= 0
  959. * @see Caret#setDot
  960. */
  961. public void setDot(int dot) {
  962. setDot(dot, Position.Bias.Forward);
  963. }
  964. /**
  965. * Moves the caret position to some other position.
  966. *
  967. * @param dot the position >= 0
  968. * @see Caret#moveDot
  969. */
  970. public void moveDot(int dot) {
  971. moveDot(dot, Position.Bias.Forward);
  972. }
  973. // ---- Bidi methods (we could put these in a subclass)
  974. void moveDot(int dot, Position.Bias dotBias) {
  975. if (! component.isEnabled()) {
  976. // don't allow selection on disabled components.
  977. setDot(dot, dotBias);
  978. return;
  979. }
  980. if (dot != this.dot) {
  981. NavigationFilter filter = component.getNavigationFilter();
  982. if (filter != null) {
  983. filter.moveDot(getFilterBypass(), dot, dotBias);
  984. }
  985. else {
  986. handleMoveDot(dot, dotBias);
  987. }
  988. }
  989. }
  990. void handleMoveDot(int dot, Position.Bias dotBias) {
  991. changeCaretPosition(dot, dotBias);
  992. if (selectionVisible) {
  993. Highlighter h = component.getHighlighter();
  994. if (h != null) {
  995. int p0 = Math.min(dot, mark);
  996. int p1 = Math.max(dot, mark);
  997. // if p0 == p1 then there should be no highlight, remove it if necessary
  998. if (p0 == p1) {
  999. if (selectionTag != null) {
  1000. h.removeHighlight(selectionTag);
  1001. selectionTag = null;
  1002. }
  1003. // otherwise, change or add the highlight
  1004. } else {
  1005. try {
  1006. if (selectionTag != null) {
  1007. h.changeHighlight(selectionTag, p0, p1);
  1008. } else {
  1009. Highlighter.HighlightPainter p = getSelectionPainter();
  1010. selectionTag = h.addHighlight(p0, p1, p);
  1011. }
  1012. } catch (BadLocationException e) {
  1013. throw new StateInvariantError("Bad caret position");
  1014. }
  1015. }
  1016. }
  1017. }
  1018. }
  1019. void setDot(int dot, Position.Bias dotBias) {
  1020. NavigationFilter filter = component.getNavigationFilter();
  1021. if (filter != null) {
  1022. filter.setDot(getFilterBypass(), dot, dotBias);
  1023. }
  1024. else {
  1025. handleSetDot(dot, dotBias);
  1026. }
  1027. }
  1028. void handleSetDot(int dot, Position.Bias dotBias) {
  1029. // move dot, if it changed
  1030. Document doc = component.getDocument();
  1031. if (doc != null) {
  1032. dot = Math.min(dot, doc.getLength());
  1033. }
  1034. dot = Math.max(dot, 0);
  1035. // The position (0,Backward) is out of range so disallow it.
  1036. if( dot == 0 )
  1037. dotBias = Position.Bias.Forward;
  1038. mark = dot;
  1039. if (this.dot != dot || this.dotBias != dotBias ||
  1040. selectionTag != null || forceCaretPositionChange) {
  1041. changeCaretPosition(dot, dotBias);
  1042. }
  1043. this.markBias = this.dotBias;
  1044. this.markLTR = dotLTR;
  1045. Highlighter h = component.getHighlighter();
  1046. if ((h != null) && (selectionTag != null)) {
  1047. h.removeHighlight(selectionTag);
  1048. selectionTag = null;
  1049. }
  1050. }
  1051. Position.Bias getDotBias() {
  1052. return dotBias;
  1053. }
  1054. Position.Bias getMarkBias() {
  1055. return markBias;
  1056. }
  1057. boolean isDotLeftToRight() {
  1058. return dotLTR;
  1059. }
  1060. boolean isMarkLeftToRight() {
  1061. return markLTR;
  1062. }
  1063. boolean isPositionLTR(int position, Position.Bias bias) {
  1064. Document doc = component.getDocument();
  1065. if(doc instanceof AbstractDocument ) {
  1066. if(bias == Position.Bias.Backward && --position < 0)
  1067. position = 0;
  1068. return ((AbstractDocument)doc).isLeftToRight(position, position);
  1069. }
  1070. return true;
  1071. }
  1072. Position.Bias guessBiasForOffset(int offset, Position.Bias lastBias,
  1073. boolean lastLTR) {
  1074. // There is an abiguous case here. That if your model looks like:
  1075. // abAB with the cursor at abB]A (visual representation of
  1076. // 3 forward) deleting could either become abB] or
  1077. // ab[B. I'ld actually prefer abB]. But, if I implement that
  1078. // a delete at abBA] would result in aBA] vs a[BA which I
  1079. // think is totally wrong. To get this right we need to know what
  1080. // was deleted. And we could get this from the bidi structure
  1081. // in the change event. So:
  1082. // PENDING: base this off what was deleted.
  1083. if(lastLTR != isPositionLTR(offset, lastBias)) {
  1084. lastBias = Position.Bias.Backward;
  1085. }
  1086. else if(lastBias != Position.Bias.Backward &&
  1087. lastLTR != isPositionLTR(offset, Position.Bias.Backward)) {
  1088. lastBias = Position.Bias.Backward;
  1089. }
  1090. if (lastBias == Position.Bias.Backward && offset > 0) {
  1091. try {
  1092. Segment s = new Segment();
  1093. component.getDocument().getText(offset - 1, 1, s);
  1094. if (s.count > 0 && s.array[s.offset] == '\n') {
  1095. lastBias = Position.Bias.Forward;
  1096. }
  1097. }
  1098. catch (BadLocationException ble) {}
  1099. }
  1100. return lastBias;
  1101. }
  1102. // ---- local methods --------------------------------------------
  1103. /**
  1104. * Sets the caret position (dot) to a new location. This
  1105. * causes the old and new location to be repainted. It
  1106. * also makes sure that the caret is within the visible
  1107. * region of the view, if the view is scrollable.
  1108. */
  1109. void changeCaretPosition(int dot, Position.Bias dotBias) {
  1110. // repaint the old position and set the new value of
  1111. // the dot.
  1112. repaint();
  1113. // Make sure the caret is visible if this window has the focus.
  1114. if (flasher != null && flasher.isRunning()) {
  1115. visible = true;
  1116. flasher.restart();
  1117. }
  1118. // notify listeners at the caret moved
  1119. this.dot = dot;
  1120. this.dotBias = dotBias;
  1121. dotLTR = isPositionLTR(dot, dotBias);
  1122. fireStateChanged();
  1123. updateSystemSelection();
  1124. setMagicCaretPosition(null);
  1125. // We try to repaint the caret later, since things
  1126. // may be unstable at the time this is called
  1127. // (i.e. we don't want to depend upon notification
  1128. // order or the fact that this might happen on
  1129. // an unsafe thread).
  1130. Runnable callRepaintNewCaret = new Runnable() {
  1131. public void run() {
  1132. repaintNewCaret();
  1133. }
  1134. };
  1135. SwingUtilities.invokeLater(callRepaintNewCaret);
  1136. }
  1137. /**
  1138. * Repaints the new caret position, with the
  1139. * assumption that this is happening on the
  1140. * event thread so that calling <code>modelToView</code>
  1141. * is safe.
  1142. */
  1143. void repaintNewCaret() {
  1144. if (component != null) {
  1145. TextUI mapper = component.getUI();
  1146. Document doc = component.getDocument();
  1147. if ((mapper != null) && (doc != null)) {
  1148. // determine the new location and scroll if
  1149. // not visible.
  1150. Rectangle newLoc;
  1151. try {
  1152. newLoc = mapper.modelToView(component, this.dot, this.dotBias);
  1153. } catch (BadLocationException e) {
  1154. newLoc = null;
  1155. }
  1156. if (newLoc != null) {
  1157. adjustVisibility(newLoc);
  1158. // If there is no magic caret position, make one
  1159. if (getMagicCaretPosition() == null) {
  1160. setMagicCaretPosition(new Point(newLoc.x, newLoc.y));
  1161. }
  1162. }
  1163. // repaint the new position
  1164. damage(newLoc);
  1165. }
  1166. }
  1167. }
  1168. private void updateSystemSelection() {
  1169. if ( ! SwingUtilities2.canCurrentEventAccessSystemClipboard() ) {
  1170. return;
  1171. }
  1172. if (this.dot != this.mark && component != null) {
  1173. Clipboard clip = getSystemSelection();
  1174. if (clip != null) {
  1175. String selectedText = null;
  1176. if (component instanceof JPasswordField
  1177. && component.getClientProperty("JPasswordField.cutCopyAllowed") !=
  1178. Boolean.TRUE) {
  1179. //fix for 4793761
  1180. StringBuffer txt = null;
  1181. char echoChar = ((JPasswordField)component).getEchoChar();
  1182. int p0 = Math.min(getDot(), getMark());
  1183. int p1 = Math.max(getDot(), getMark());
  1184. for (int i = p0; i < p1; i++) {
  1185. if (txt == null) {
  1186. txt = new StringBuffer();
  1187. }
  1188. txt.append(echoChar);
  1189. }
  1190. selectedText = (txt != null) ? txt.toString() : null;
  1191. } else {
  1192. selectedText = component.getSelectedText();
  1193. }
  1194. try {
  1195. clip.setContents(
  1196. new StringSelection(selectedText), getClipboardOwner());
  1197. ownsSelection = true;
  1198. } catch (IllegalStateException ise) {
  1199. // clipboard was unavailable
  1200. // no need to provide error feedback to user since updating
  1201. // the system selection is not a user invoked action
  1202. }
  1203. }
  1204. }
  1205. }
  1206. private Clipboard getSystemSelection() {
  1207. try {
  1208. return component.getToolkit().getSystemSelection();
  1209. } catch (HeadlessException he) {
  1210. // do nothing... there is no system clipboard
  1211. } catch (SecurityException se) {
  1212. // do nothing... there is no allowed system clipboard
  1213. }
  1214. return null;
  1215. }
  1216. private ClipboardOwner getClipboardOwner() {
  1217. return handler;
  1218. }
  1219. /**
  1220. * This is invoked after the document changes to verify the current
  1221. * dot/mark is valid. We do this in case the <code>NavigationFilter</code>
  1222. * changed where to position the dot, that resulted in the current location
  1223. * being bogus.
  1224. */
  1225. private void ensureValidPosition() {
  1226. int length = component.getDocument().getLength();
  1227. if (dot > length || mark > length) {
  1228. // Current location is bogus and filter likely vetoed the
  1229. // change, force the reset without giving the filter a
  1230. // chance at changing it.
  1231. handleSetDot(length, Position.Bias.Forward);
  1232. }
  1233. }
  1234. /**
  1235. * Saves the current caret position. This is used when
  1236. * caret up/down actions occur, moving between lines
  1237. * that have uneven end positions.
  1238. *
  1239. * @param p the position
  1240. * @see #getMagicCaretPosition
  1241. */
  1242. public void setMagicCaretPosition(Point p) {
  1243. magicCaretPosition = p;
  1244. }
  1245. /**
  1246. * Gets the saved caret position.
  1247. *
  1248. * @return the position
  1249. * see #setMagicCaretPosition
  1250. */
  1251. public Point getMagicCaretPosition() {
  1252. return magicCaretPosition;
  1253. }
  1254. /**
  1255. * Compares this object to the specified object.
  1256. * The superclass behavior of comparing rectangles
  1257. * is not desired, so this is changed to the Object
  1258. * behavior.
  1259. *
  1260. * @param obj the object to compare this font with
  1261. * @return <code>true</code> if the objects are equal;
  1262. * <code>false</code> otherwise
  1263. */
  1264. public boolean equals(Object obj) {
  1265. return (this == obj);
  1266. }
  1267. public String toString() {
  1268. String s = "Dot=(" + dot + ", " + dotBias + ")";
  1269. s += " Mark=(" + mark + ", " + markBias + ")";
  1270. return s;
  1271. }
  1272. private NavigationFilter.FilterBypass getFilterBypass() {
  1273. if (filterBypass == null) {
  1274. filterBypass = new DefaultFilterBypass();
  1275. }
  1276. return filterBypass;
  1277. }
  1278. // Rectangle.contains returns false if passed a rect with a w or h == 0,
  1279. // this won't (assuming X,Y are contained with this rectangle).
  1280. private boolean _contains(int X, int Y, int W, int H) {
  1281. int w = this.width;
  1282. int h = this.height;
  1283. if ((w | h | W | H) < 0) {
  1284. // At least one of the dimensions is negative...
  1285. return false;
  1286. }
  1287. // Note: if any dimension is zero, tests below must return false...
  1288. int x = this.x;
  1289. int y = this.y;
  1290. if (X < x || Y < y) {
  1291. return false;
  1292. }
  1293. if (W > 0) {
  1294. w += x;
  1295. W += X;
  1296. if (W <= X) {
  1297. // X+W overflowed or W was zero, return false if...
  1298. // either original w or W was zero or
  1299. // x+w did not overflow or
  1300. // the overflowed x+w is smaller than the overflowed X+W
  1301. if (w >= x || W > w) return false;
  1302. } else {
  1303. // X+W did not overflow and W was not zero, return false if...
  1304. // original w was zero or
  1305. // x+w did not overflow and x+w is smaller than X+W
  1306. if (w >= x && W > w) return false;
  1307. }
  1308. }
  1309. else if ((x + w) < X) {
  1310. return false;
  1311. }
  1312. if (H > 0) {
  1313. h += y;
  1314. H += Y;
  1315. if (H <= Y) {
  1316. if (h >= y || H > h) return false;
  1317. } else {
  1318. if (h >= y && H > h) return false;
  1319. }
  1320. }
  1321. else if ((y + h) < Y) {
  1322. return false;
  1323. }
  1324. return true;
  1325. }
  1326. int getCaretWidth(int height) {
  1327. if (aspectRatio > -1) {
  1328. return (int) (aspectRatio * height) + 1;
  1329. }
  1330. if (caretWidth > -1) {
  1331. return caretWidth;
  1332. }
  1333. return 1;
  1334. }
  1335. // --- serialization ---------------------------------------------
  1336. private void readObject(ObjectInputStream s)
  1337. throws ClassNotFoundException, IOException
  1338. {
  1339. s.defaultReadObject();
  1340. handler = new Handler();
  1341. if (!s.readBoolean()) {
  1342. dotBias = Position.Bias.Forward;
  1343. }
  1344. else {
  1345. dotBias = Position.Bias.Backward;
  1346. }
  1347. if (!s.readBoolean()) {
  1348. markBias = Position.Bias.Forward;
  1349. }
  1350. else {
  1351. markBias = Position.Bias.Backward;
  1352. }
  1353. }
  1354. private void writeObject(ObjectOutputStream s) throws IOException {
  1355. s.defaultWriteObject();
  1356. s.writeBoolean((dotBias == Position.Bias.Backward));
  1357. s.writeBoolean((markBias == Position.Bias.Backward));
  1358. }
  1359. // ---- member variables ------------------------------------------
  1360. /**
  1361. * The event listener list.
  1362. */
  1363. protected EventListenerList listenerList = new EventListenerList();
  1364. /**
  1365. * The change event for the model.
  1366. * Only one ChangeEvent is needed per model instance since the
  1367. * event's only (read-only) state is the source property. The source
  1368. * of events generated here is always "this".
  1369. */
  1370. protected transient ChangeEvent changeEvent = null;
  1371. // package-private to avoid inner classes private member
  1372. // access bug
  1373. JTextComponent component;
  1374. int updatePolicy = UPDATE_WHEN_ON_EDT;
  1375. boolean visible;
  1376. boolean active;
  1377. int dot;
  1378. int mark;
  1379. Object selectionTag;
  1380. boolean selectionVisible;
  1381. Timer flasher;
  1382. Point magicCaretPosition;
  1383. transient Position.Bias dotBias;
  1384. transient Position.Bias markBias;
  1385. boolean dotLTR;
  1386. boolean markLTR;
  1387. transient Handler handler = new Handler();
  1388. transient private int[] flagXPoints = new int[3];
  1389. transient private int[] flagYPoints = new int[3];
  1390. private transient NavigationFilter.FilterBypass filterBypass;
  1391. static private transient Action selectWord = null;
  1392. static private transient Action selectLine = null;
  1393. /**
  1394. * This is used to indicate if the caret currently owns the selection.
  1395. * This is always false if the system does not support the system
  1396. * clipboard.
  1397. */
  1398. private boolean ownsSelection;
  1399. /**
  1400. * If this is true, the location of the dot is updated regardless of
  1401. * the current location. This is set in the DocumentListener
  1402. * such that even if the model location of dot hasn't changed (perhaps do
  1403. * to a forward delete) the visual location is updated.
  1404. */
  1405. private boolean forceCaretPositionChange;
  1406. /**
  1407. * Whether or not mouseReleased should adjust the caret and focus.
  1408. * This flag is set by mousePressed if it wanted to adjust the caret
  1409. * and focus but couldn't because of a possible DnD operation.
  1410. */
  1411. private transient boolean shouldHandleRelease;
  1412. /**
  1413. * holds last MouseEvent which caused the word selection
  1414. */
  1415. private transient MouseEvent selectedWordEvent = null;
  1416. /**
  1417. * The width of the caret in pixels.
  1418. */
  1419. private int caretWidth = -1;
  1420. private float aspectRatio = -1;
  1421. class SafeScroller implements Runnable {
  1422. SafeScroller(Rectangle r) {
  1423. this.r = r;
  1424. }
  1425. public void run() {
  1426. if (component != null) {
  1427. component.scrollRectToVisible(r);
  1428. }
  1429. }
  1430. Rectangle r;
  1431. }
  1432. class Handler implements PropertyChangeListener, DocumentListener, ActionListener, ClipboardOwner {
  1433. // --- ActionListener methods ----------------------------------
  1434. /**
  1435. * Invoked when the blink timer fires. This is called
  1436. * asynchronously. The simply changes the visibility
  1437. * and repaints the rectangle that last bounded the caret.
  1438. *
  1439. * @param e the action event
  1440. */
  1441. public void actionPerformed(ActionEvent e) {
  1442. if (width == 0 || height == 0) {
  1443. // setVisible(true) will cause a scroll, only do this if the
  1444. // new location is really valid.
  1445. if (component != null) {
  1446. TextUI mapper = component.getUI();
  1447. try {
  1448. Rectangle r = mapper.modelToView(component, dot,
  1449. dotBias);
  1450. if (r != null && r.width != 0 && r.height != 0) {
  1451. damage(r);
  1452. }
  1453. } catch (BadLocationException ble) {
  1454. }
  1455. }
  1456. }
  1457. visible = !visible;
  1458. repaint();
  1459. }
  1460. // --- DocumentListener methods --------------------------------
  1461. /**
  1462. * Updates the dot and mark if they were changed by
  1463. * the insertion.
  1464. *
  1465. * @param e the document event
  1466. * @see DocumentListener#insertUpdate
  1467. */
  1468. public void insertUpdate(DocumentEvent e) {
  1469. if (getUpdatePolicy() == NEVER_UPDATE ||
  1470. (getUpdatePolicy() == UPDATE_WHEN_ON_EDT &&
  1471. !SwingUtilities.isEventDispatchThread())) {
  1472. if ((e.getOffset() <= dot || e.getOffset() <= mark)
  1473. && selectionTag != null) {
  1474. try {
  1475. component.getHighlighter().changeHighlight(selectionTag,
  1476. Math.min(dot, mark), Math.max(dot, mark));
  1477. } catch (BadLocationException e1) {
  1478. e1.printStackTrace();
  1479. }
  1480. }
  1481. return;
  1482. }
  1483. int adjust = 0;
  1484. int offset = e.getOffset();
  1485. int length = e.getLength();
  1486. int newDot = dot;
  1487. short changed = 0;
  1488. if (e instanceof AbstractDocument.UndoRedoDocumentEvent) {
  1489. setDot(offset + length);
  1490. return;
  1491. }
  1492. if (newDot >= offset) {
  1493. newDot += length;
  1494. changed |= 1;
  1495. }
  1496. int newMark = mark;
  1497. if (newMark >= offset) {
  1498. newMark += length;
  1499. changed |= 2;
  1500. }
  1501. if (changed != 0) {
  1502. Position.Bias dotBias = DefaultCaret.this.dotBias;
  1503. if (dot == offset) {
  1504. Document doc = component.getDocument();
  1505. boolean isNewline;
  1506. try {
  1507. Segment s = new Segment();
  1508. doc.getText(newDot - 1, 1, s);
  1509. isNewline = (s.count > 0 &&
  1510. s.array[s.offset] == '\n');
  1511. } catch (BadLocationException ble) {
  1512. isNewline = false;
  1513. }
  1514. if (isNewline) {
  1515. dotBias = Position.Bias.Forward;
  1516. } else {
  1517. dotBias = Position.Bias.Backward;
  1518. }
  1519. }
  1520. if (newMark == newDot) {
  1521. setDot(newDot, dotBias);
  1522. ensureValidPosition();
  1523. }
  1524. else {
  1525. setDot(newMark, markBias);
  1526. if (getDot() == newMark) {
  1527. // Due this test in case the filter vetoed the
  1528. // change in which case this probably won't be
  1529. // valid either.
  1530. moveDot(newDot, dotBias);
  1531. }
  1532. ensureValidPosition();
  1533. }
  1534. }
  1535. }
  1536. /**
  1537. * Updates the dot and mark if they were changed
  1538. * by the removal.
  1539. *
  1540. * @param e the document event
  1541. * @see DocumentListener#removeUpdate
  1542. */
  1543. public void removeUpdate(DocumentEvent e) {
  1544. if (getUpdatePolicy() == NEVER_UPDATE ||
  1545. (getUpdatePolicy() == UPDATE_WHEN_ON_EDT &&
  1546. !SwingUtilities.isEventDispatchThread())) {
  1547. int length = component.getDocument().getLength();
  1548. dot = Math.min(dot, length);
  1549. mark = Math.min(mark, length);
  1550. if ((e.getOffset() < dot || e.getOffset() < mark)
  1551. && selectionTag != null) {
  1552. try {
  1553. component.getHighlighter().changeHighlight(selectionTag,
  1554. Math.min(dot, mark), Math.max(dot, mark));
  1555. } catch (BadLocationException e1) {
  1556. e1.printStackTrace();
  1557. }
  1558. }
  1559. return;
  1560. }
  1561. int offs0 = e.getOffset();
  1562. int offs1 = offs0 + e.getLength();
  1563. int adjust = 0;
  1564. int newDot = dot;
  1565. boolean adjustDotBias = false;
  1566. int newMark = mark;
  1567. boolean adjustMarkBias = false;
  1568. if(e instanceof AbstractDocument.UndoRedoDocumentEvent) {
  1569. setDot(offs0);
  1570. return;
  1571. }
  1572. if (newDot >= offs1) {
  1573. newDot -= (offs1 - offs0);
  1574. if(newDot == offs1) {
  1575. adjustDotBias = true;
  1576. }
  1577. } else if (newDot >= offs0) {
  1578. newDot = offs0;
  1579. adjustDotBias = true;
  1580. }
  1581. if (newMark >= offs1) {
  1582. newMark -= (offs1 - offs0);
  1583. if(newMark == offs1) {
  1584. adjustMarkBias = true;
  1585. }
  1586. } else if (newMark >= offs0) {
  1587. newMark = offs0;
  1588. adjustMarkBias = true;
  1589. }
  1590. if (newMark == newDot) {
  1591. forceCaretPositionChange = true;
  1592. try {
  1593. setDot(newDot, guessBiasForOffset(newDot, dotBias,
  1594. dotLTR));
  1595. } finally {
  1596. forceCaretPositionChange = false;
  1597. }
  1598. ensureValidPosition();
  1599. } else {
  1600. Position.Bias dotBias = DefaultCaret.this.dotBias;
  1601. Position.Bias markBias = DefaultCaret.this.markBias;
  1602. if(adjustDotBias) {
  1603. dotBias = guessBiasForOffset(newDot, dotBias, dotLTR);
  1604. }
  1605. if(adjustMarkBias) {
  1606. markBias = guessBiasForOffset(mark, markBias, markLTR);
  1607. }
  1608. setDot(newMark, markBias);
  1609. if (getDot() == newMark) {
  1610. // Due this test in case the filter vetoed the change
  1611. // in which case this probably won't be valid either.
  1612. moveDot(newDot, dotBias);
  1613. }
  1614. ensureValidPosition();
  1615. }
  1616. }
  1617. /**
  1618. * Gives notification that an attribute or set of attributes changed.
  1619. *
  1620. * @param e the document event
  1621. * @see DocumentListener#changedUpdate
  1622. */
  1623. public void changedUpdate(DocumentEvent e) {
  1624. if (getUpdatePolicy() == NEVER_UPDATE ||
  1625. (getUpdatePolicy() == UPDATE_WHEN_ON_EDT &&
  1626. !SwingUtilities.isEventDispatchThread())) {
  1627. return;
  1628. }
  1629. if(e instanceof AbstractDocument.UndoRedoDocumentEvent) {
  1630. setDot(e.getOffset() + e.getLength());
  1631. }
  1632. }
  1633. // --- PropertyChangeListener methods -----------------------
  1634. /**
  1635. * This method gets called when a bound property is changed.
  1636. * We are looking for document changes on the editor.
  1637. */
  1638. public void propertyChange(PropertyChangeEvent evt) {
  1639. Object oldValue = evt.getOldValue();
  1640. Object newValue = evt.getNewValue();
  1641. if ((oldValue instanceof Document) || (newValue instanceof Document)) {
  1642. setDot(0);
  1643. if (oldValue != null) {
  1644. ((Document)oldValue).removeDocumentListener(this);
  1645. }
  1646. if (newValue != null) {
  1647. ((Document)newValue).addDocumentListener(this);
  1648. }
  1649. } else if("enabled".equals(evt.getPropertyName())) {
  1650. Boolean enabled = (Boolean) evt.getNewValue();
  1651. if(component.isFocusOwner()) {
  1652. if(enabled == Boolean.TRUE) {
  1653. if(component.isEditable()) {
  1654. setVisible(true);
  1655. }
  1656. setSelectionVisible(true);
  1657. } else {
  1658. setVisible(false);
  1659. setSelectionVisible(false);
  1660. }
  1661. }
  1662. } else if("caretWidth".equals(evt.getPropertyName())) {
  1663. Integer newWidth = (Integer) evt.getNewValue();
  1664. if (newWidth != null) {
  1665. caretWidth = newWidth.intValue();
  1666. } else {
  1667. caretWidth = -1;
  1668. }
  1669. repaint();
  1670. } else if("caretAspectRatio".equals(evt.getPropertyName())) {
  1671. Number newRatio = (Number) evt.getNewValue();
  1672. if (newRatio != null) {
  1673. aspectRatio = newRatio.floatValue();
  1674. } else {
  1675. aspectRatio = -1;
  1676. }
  1677. repaint();
  1678. }
  1679. }
  1680. //
  1681. // ClipboardOwner
  1682. //
  1683. /**
  1684. * Toggles the visibility of the selection when ownership is lost.
  1685. */
  1686. public void lostOwnership(Clipboard clipboard,
  1687. Transferable contents) {
  1688. if (ownsSelection) {
  1689. ownsSelection = false;
  1690. if (component != null && !component.hasFocus()) {
  1691. setSelectionVisible(false);
  1692. }
  1693. }
  1694. }
  1695. }
  1696. private class DefaultFilterBypass extends NavigationFilter.FilterBypass {
  1697. public Caret getCaret() {
  1698. return DefaultCaret.this;
  1699. }
  1700. public void setDot(int dot, Position.Bias bias) {
  1701. handleSetDot(dot, bias);
  1702. }
  1703. public void moveDot(int dot, Position.Bias bias) {
  1704. handleMoveDot(dot, bias);
  1705. }
  1706. }
  1707. }