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