KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > editor > BaseCaret


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.editor;
21
22 import java.awt.Container JavaDoc;
23 import java.awt.Dimension JavaDoc;
24 import java.awt.Rectangle JavaDoc;
25 import java.awt.Graphics JavaDoc;
26 import java.awt.Color JavaDoc;
27 import java.awt.Font JavaDoc;
28 import java.awt.Point JavaDoc;
29 import java.awt.Component JavaDoc;
30 import java.awt.Toolkit JavaDoc;
31 import java.awt.datatransfer.Clipboard JavaDoc;
32 import java.awt.datatransfer.DataFlavor JavaDoc;
33 import java.awt.datatransfer.Transferable JavaDoc;
34 import java.awt.datatransfer.UnsupportedFlavorException JavaDoc;
35 import java.awt.event.ComponentEvent JavaDoc;
36 import java.awt.event.ComponentAdapter JavaDoc;
37 import java.awt.event.ComponentListener JavaDoc;
38 import java.awt.event.MouseListener JavaDoc;
39 import java.awt.event.MouseEvent JavaDoc;
40 import java.awt.event.MouseMotionListener JavaDoc;
41 import java.awt.event.ActionListener JavaDoc;
42 import java.awt.event.ActionEvent JavaDoc;
43 import java.awt.event.FocusListener JavaDoc;
44 import java.awt.event.FocusEvent JavaDoc;
45 import java.awt.event.InputEvent JavaDoc;
46 import java.beans.PropertyChangeListener JavaDoc;
47 import java.beans.PropertyChangeEvent JavaDoc;
48 import java.io.IOException JavaDoc;
49 import javax.swing.Action JavaDoc;
50 import javax.swing.JComponent JavaDoc;
51 import javax.swing.JScrollBar JavaDoc;
52 import javax.swing.JScrollPane JavaDoc;
53 import javax.swing.JViewport JavaDoc;
54 import javax.swing.Timer JavaDoc;
55 import javax.swing.SwingUtilities JavaDoc;
56 import javax.swing.TransferHandler JavaDoc;
57 import javax.swing.text.BadLocationException JavaDoc;
58 import javax.swing.text.Document JavaDoc;
59 import javax.swing.text.JTextComponent JavaDoc;
60 import javax.swing.text.Caret JavaDoc;
61 import javax.swing.event.ChangeListener JavaDoc;
62 import javax.swing.event.ChangeEvent JavaDoc;
63 import javax.swing.event.DocumentListener JavaDoc;
64 import javax.swing.event.DocumentEvent JavaDoc;
65 import javax.swing.event.EventListenerList JavaDoc;
66 import javax.swing.text.AbstractDocument JavaDoc;
67 import javax.swing.text.Position JavaDoc;
68 import org.netbeans.api.editor.fold.Fold;
69 import org.netbeans.api.editor.fold.FoldHierarchy;
70 import org.netbeans.api.editor.fold.FoldHierarchyEvent;
71 import org.netbeans.api.editor.fold.FoldHierarchyListener;
72 import org.netbeans.api.editor.fold.FoldStateChange;
73 import org.netbeans.api.editor.fold.FoldUtilities;
74 import org.netbeans.lib.editor.util.swing.DocumentListenerPriority;
75 import org.openide.util.WeakListeners;
76
77 /**
78 * Caret implementation
79 *
80 * @author Miloslav Metelka
81 * @version 1.00
82 */

83
84 public class BaseCaret implements Caret JavaDoc,
85 MouseListener JavaDoc, MouseMotionListener JavaDoc, PropertyChangeListener JavaDoc,
86 DocumentListener JavaDoc, ActionListener JavaDoc, SettingsChangeListener,
87 AtomicLockListener, FoldHierarchyListener {
88
89     /** Caret type representing block covering current character */
90     public static final String JavaDoc BLOCK_CARET = "block-caret"; // NOI18N
91

92     /** Default caret type */
93     public static final String JavaDoc LINE_CARET = "line-caret"; // NOI18N
94

95     /** One dot thin line compatible with Swing default caret */
96     public static final String JavaDoc THIN_LINE_CARET = "thin-line-caret"; // NOI18N
97

98     private static final boolean debugCaretFocus
99     = Boolean.getBoolean("netbeans.debug.editor.caret.focus"); // NOI18N
100

101     private static final boolean debugCaretFocusExtra
102     = Boolean.getBoolean("netbeans.debug.editor.caret.focus.extra"); // NOI18N
103

104     /**
105      * Implementation of various listeners.
106      */

107     private ListenerImpl listenerImpl;
108     
109     /**
110      * Present bounds of the caret. This rectangle needs to be repainted
111      * prior the caret gets repainted elsewhere.
112      */

113     private Rectangle JavaDoc caretBounds;
114     
115     /** Component this caret is bound to */
116     protected JTextComponent JavaDoc component;
117
118     /** Position of the caret on the screen. This helps to compute
119     * caret position on the next after jump.
120     */

121     Point JavaDoc magicCaretPosition;
122
123     /** Draw mark designating the position of the caret. */
124     MarkFactory.ContextMark caretMark = new MarkFactory.ContextMark(Position.Bias.Forward, false);
125
126     /** Draw mark that supports caret mark in creating selection */
127     MarkFactory.ContextMark selectionMark = new MarkFactory.ContextMark(Position.Bias.Forward, false);
128
129     /** Is the caret visible */
130     boolean caretVisible;
131
132     /** Whether blinking caret is currently visible.
133      * <code>caretVisible</code> must be also true in order to paint the caret.
134      */

135     boolean blinkVisible;
136
137     /** Is the selection currently visible? */
138     boolean selectionVisible;
139
140     /** Listeners */
141     protected EventListenerList JavaDoc listenerList = new EventListenerList JavaDoc();
142
143     /** Timer used for blinking the caret */
144     protected Timer JavaDoc flasher;
145
146     /** Type of the caret */
147     String JavaDoc type;
148
149     /** Is the caret italic for italic fonts */
150     boolean italic;
151
152     private int xPoints[] = new int[4];
153     private int yPoints[] = new int[4];
154     private Action JavaDoc selectWordAction;
155     private Action JavaDoc selectLineAction;
156
157     /** Change event. Only one instance needed because it has only source property */
158     protected ChangeEvent JavaDoc changeEvent;
159
160     /** Dot array of one character under caret */
161     protected char dotChar[] = {' '};
162
163     private boolean overwriteMode;
164
165     /** Remembering document on which caret listens avoids
166     * duplicate listener addition to SwingPropertyChangeSupport
167     * due to the bug 4200280
168     */

169     private BaseDocument listenDoc;
170
171     /** Font of the text underlying the caret. It can be used
172     * in caret painting.
173     */

174     protected Font JavaDoc afterCaretFont;
175
176     /** Font of the text right before the caret */
177     protected Font JavaDoc beforeCaretFont;
178
179     /** Foreground color of the text underlying the caret. It can be used
180     * in caret painting.
181     */

182     protected Color JavaDoc textForeColor;
183
184     /** Background color of the text underlying the caret. It can be used
185     * in caret painting.
186     */

187     protected Color JavaDoc textBackColor;
188
189     private transient FocusListener JavaDoc focusListener;
190
191     /** Whether the text is being modified under atomic lock.
192      * If so just one caret change is fired at the end of all modifications.
193      */

194     private transient boolean inAtomicLock;
195     
196     /** Helps to check whether there was modification performed
197      * and so the caret change needs to be fired.
198      */

199     private transient boolean modified;
200     
201     /** Whether there was an undo done in the modification and the offset of the modification */
202     private transient int undoOffset = -1;
203     
204     static final long serialVersionUID =-9113841520331402768L;
205
206     private MouseEvent JavaDoc dndArmedEvent = null;
207     
208     /**
209      * Set to true once the folds have changed. The caret should retain
210      * its relative visual position on the screen.
211      */

212     private boolean updateAfterFoldHierarchyChange;
213     
214     public BaseCaret() {
215         listenerImpl = new ListenerImpl();
216         Settings.addSettingsChangeListener(this);
217     }
218
219     /** Called when settings were changed. The method is called
220     * also in constructor, so the code must count with the evt being null.
221     */

222     public void settingsChange(SettingsChangeEvent evt) {
223         if( evt != null && SettingsNames.CARET_BLINK_RATE.equals( evt.getSettingName() ) ) {
224             
225             JTextComponent JavaDoc c = component;
226             if (c == null) return;
227             if (evt.getKitClass() != Utilities.getKitClass(c)) return;
228             
229             Object JavaDoc value = evt.getNewValue();
230             if( value instanceof Integer JavaDoc ) {
231                 setBlinkRate( ((Integer JavaDoc)value).intValue() );
232             }
233         }
234         updateType();
235         SwingUtilities.invokeLater(new Runnable JavaDoc() {
236             public void run() {
237                 updateCaretBounds(); // the line height etc. may have change
238
}
239         });
240     }
241
242     void updateType() {
243         JTextComponent JavaDoc c = component;
244         if (c != null) {
245             Class JavaDoc kitClass = Utilities.getKitClass(c);
246             if (kitClass==null) return;
247             String JavaDoc newType;
248             boolean newItalic;
249             Color JavaDoc caretColor;
250             if (overwriteMode) {
251                 newType = SettingsUtil.getString(kitClass,
252                                                  SettingsNames.CARET_TYPE_OVERWRITE_MODE, LINE_CARET);
253                 newItalic = SettingsUtil.getBoolean(kitClass,
254                                                     SettingsNames.CARET_ITALIC_OVERWRITE_MODE, false);
255                 caretColor = getColor( kitClass, SettingsNames.CARET_COLOR_OVERWRITE_MODE,
256                     SettingsDefaults.defaultCaretColorOvwerwriteMode );
257                 
258             } else { // insert mode
259
newType = SettingsUtil.getString(kitClass,
260                                                  SettingsNames.CARET_TYPE_INSERT_MODE, LINE_CARET);
261                 newItalic = SettingsUtil.getBoolean(kitClass,
262                                                     SettingsNames.CARET_ITALIC_INSERT_MODE, false);
263                 caretColor = getColor( kitClass, SettingsNames.CARET_COLOR_INSERT_MODE,
264                     SettingsDefaults.defaultCaretColorInsertMode );
265             }
266
267             this.type = newType;
268             this.italic = newItalic;
269             c.setCaretColor(caretColor);
270             if (debugCaretFocusExtra){
271                 System.err.println("Updating caret color:"+caretColor); // NOI18N
272
}
273
274             resetBlink();
275         }
276     }
277
278     private static Color JavaDoc getColor( Class JavaDoc kitClass, String JavaDoc settingName,
279                            Color JavaDoc defaultValue) {
280         Object JavaDoc value = Settings.getValue(kitClass, settingName);
281         return (value instanceof Color JavaDoc) ? (Color JavaDoc)value : defaultValue;
282     }
283
284     /**
285      * Assign new caret bounds into <code>caretBounds</code> variable.
286      *
287      * @return true if the new caret bounds were successfully computed
288      * and assigned or false otherwise.
289      */

290     private boolean updateCaretBounds() {
291         JTextComponent JavaDoc c = component;
292         if (c != null) {
293             int offset = getDot();
294             Rectangle JavaDoc newCaretBounds;
295             try {
296                 newCaretBounds = c.getUI().modelToView(
297                         c, offset, Position.Bias.Forward);
298                 BaseDocument doc = Utilities.getDocument(c);
299                 if (doc != null) {
300                     doc.getChars(offset, this.dotChar, 0, 1);
301                 }
302             } catch (BadLocationException JavaDoc e) {
303                 newCaretBounds = null;
304                 Utilities.annotateLoggable(e);
305             }
306         
307             if (newCaretBounds != null) {
308                 caretBounds = newCaretBounds;
309                 return true;
310             }
311         }
312         return false;
313     }
314
315     /** Called when UI is being installed into JTextComponent */
316     public void install(JTextComponent JavaDoc c) {
317         assert (SwingUtilities.isEventDispatchThread()); // must be done in AWT
318
component = c;
319         blinkVisible = true;
320         
321         // Assign dot and mark positions
322
BaseDocument doc = Utilities.getDocument(c);
323         if (doc != null) {
324             modelChanged(null, doc);
325         }
326
327         // Attempt to assign initial bounds - usually here the component
328
// is not yet added to the component hierarchy.
329
updateCaretBounds();
330         
331         if (caretBounds == null) {
332             // For null bounds wait for the component to get resized
333
// and attempt to recompute bounds then
334
component.addComponentListener(listenerImpl);
335         }
336
337         component.addPropertyChangeListener(this);
338         component.addFocusListener(listenerImpl);
339         component.addMouseListener(this);
340         component.addMouseMotionListener(this);
341
342         EditorUI editorUI = Utilities.getEditorUI(component);
343         editorUI.addPropertyChangeListener( this );
344         
345         FoldHierarchy hierarchy = FoldHierarchy.get(c);
346         if (hierarchy != null) {
347             hierarchy.addFoldHierarchyListener(this);
348         }
349         
350         if (component.hasFocus()) {
351             if (debugCaretFocus || debugCaretFocusExtra) {
352                 System.err.println("Component has focus, calling BaseCaret.focusGained(); doc=" // NOI18N
353
+ component.getDocument().getProperty(Document.TitleProperty));
354             }
355             listenerImpl.focusGained(null); // emulate focus gained
356
}
357
358         dispatchUpdate(false);
359     }
360
361     /** Called when UI is being removed from JTextComponent */
362     public void deinstall(JTextComponent JavaDoc c) {
363         component = null; // invalidate
364

365         synchronized (listenerImpl) {
366             if (flasher != null) {
367                 setBlinkRate(0);
368             }
369         }
370
371         c.removeMouseMotionListener(this);
372         c.removeMouseListener(this);
373         c.removeFocusListener(listenerImpl);
374         c.removePropertyChangeListener(this);
375         
376         FoldHierarchy hierarchy = FoldHierarchy.get(c);
377         if (hierarchy != null) {
378             hierarchy.removeFoldHierarchyListener(this);
379         }
380         
381         modelChanged(listenDoc, null);
382     }
383
384     protected void modelChanged(BaseDocument oldDoc, BaseDocument newDoc) {
385         if (oldDoc != null) {
386             // ideally the oldDoc param shouldn't exist and only listenDoc should be used
387
assert (oldDoc == listenDoc);
388
389             org.netbeans.lib.editor.util.swing.DocumentUtilities.removeDocumentListener(
390                     oldDoc, this, DocumentListenerPriority.CARET_UPDATE);
391             oldDoc.removeAtomicLockListener(this);
392
393             try {
394                 caretMark.remove();
395                 selectionMark.remove();
396             } catch (InvalidMarkException e) {
397                 Utilities.annotateLoggable(e);
398             }
399
400             listenDoc = null;
401         }
402
403
404         if (newDoc != null) {
405
406             org.netbeans.lib.editor.util.swing.DocumentUtilities.addDocumentListener(
407                     newDoc, this, DocumentListenerPriority.CARET_UPDATE);
408             listenDoc = newDoc;
409             newDoc.addAtomicLockListener(this);
410
411             try {
412                 Utilities.insertMark(newDoc, caretMark, 0);
413                 Utilities.insertMark(newDoc, selectionMark, 0);
414             } catch (InvalidMarkException e) {
415                 Utilities.annotateLoggable(e);
416             } catch (BadLocationException JavaDoc e) {
417                 Utilities.annotateLoggable(e);
418             }
419
420             settingsChange(null); // update settings
421

422             Utilities.runInEventDispatchThread(
423                 new Runnable JavaDoc() {
424                     public void run() {
425                         updateType();
426                     }
427                 }
428             );
429
430         }
431     }
432
433     /** Renders the caret */
434     public void paint(Graphics JavaDoc g) {
435         JTextComponent JavaDoc c = component;
436         if (c == null) return;
437         EditorUI editorUI = Utilities.getEditorUI(c);
438
439         // #70915 Check whether the caret was moved but the component was not
440
// validated yet and therefore the caret bounds are still null
441
// and if so compute the bounds and scroll the view if necessary.
442
if (getDot() != 0 && caretBounds == null) {
443             update(true);
444         }
445         if (caretBounds != null && isVisible() && blinkVisible) {
446             paintCustomCaret(g);
447         }
448     }
449
450     protected void paintCustomCaret(Graphics JavaDoc g) {
451         JTextComponent JavaDoc c = component;
452         if (c != null) {
453             EditorUI editorUI = Utilities.getEditorUI(c);
454             g.setColor(c.getCaretColor());
455             if (THIN_LINE_CARET.equals(type)) { // thin line caret
456
int upperX = caretBounds.x;
457                 if (beforeCaretFont != null && beforeCaretFont.isItalic() && italic) {
458                     upperX += Math.tan(beforeCaretFont.getItalicAngle()) * caretBounds.height;
459                 }
460                 g.drawLine((int)upperX, caretBounds.y, caretBounds.x,
461                         (caretBounds.y + caretBounds.height - 1));
462
463             } else if (BLOCK_CARET.equals(type)) { // block caret
464
if (afterCaretFont != null) g.setFont(afterCaretFont);
465                 if (afterCaretFont != null && afterCaretFont.isItalic() && italic) { // paint italic caret
466
int upperX = (int)(caretBounds.x
467                             + Math.tan(afterCaretFont.getItalicAngle()) * caretBounds.height);
468                     xPoints[0] = upperX;
469                     yPoints[0] = caretBounds.y;
470                     xPoints[1] = upperX + caretBounds.width;
471                     yPoints[1] = caretBounds.y;
472                     xPoints[2] = caretBounds.x + caretBounds.width;
473                     yPoints[2] = caretBounds.y + caretBounds.height - 1;
474                     xPoints[3] = caretBounds.x;
475                     yPoints[3] = caretBounds.y + caretBounds.height - 1;
476                     g.fillPolygon(xPoints, yPoints, 4);
477
478                 } else { // paint non-italic caret
479
g.fillRect(caretBounds.x, caretBounds.y, caretBounds.width, caretBounds.height);
480                 }
481                 
482                 if (!Character.isWhitespace(dotChar[0])) {
483                     g.setColor(Color.white);
484                     // int ascent = FontMetricsCache.getFontMetrics(afterCaretFont, c).getAscent();
485
g.drawChars(dotChar, 0, 1, caretBounds.x,
486                             caretBounds.y + editorUI.getLineAscent());
487                 }
488
489             } else { // two dot line caret
490
int blkWidth = 2;
491                 if (beforeCaretFont != null && beforeCaretFont.isItalic() && italic) {
492                     int upperX = (int)(caretBounds.x
493                             + Math.tan(beforeCaretFont.getItalicAngle()) * caretBounds.height);
494                     xPoints[0] = upperX;
495                     yPoints[0] = caretBounds.y;
496                     xPoints[1] = upperX + blkWidth;
497                     yPoints[1] = caretBounds.y;
498                     xPoints[2] = caretBounds.x + blkWidth;
499                     yPoints[2] = caretBounds.y + caretBounds.height - 1;
500                     xPoints[3] = caretBounds.x;
501                     yPoints[3] = caretBounds.y + caretBounds.height - 1;
502                     g.fillPolygon(xPoints, yPoints, 4);
503
504                 } else { // paint non-italic caret
505
g.fillRect(caretBounds.x, caretBounds.y, blkWidth, caretBounds.height - 1);
506                 }
507             }
508         }
509     }
510
511     /** Update the caret's visual position */
512     void dispatchUpdate(final boolean scrollViewToCaret) {
513         /* After using SwingUtilities.invokeLater() due to fix of #18860
514          * there is another fix of #35034 which ensures that the caret's
515          * document listener will be added AFTER the views hierarchy's
516          * document listener so the code can run synchronously again
517          * which should eliminate the problem with caret lag.
518          * However the document can be modified from non-AWT thread
519          * which is the case in #57316 and in that case the code
520          * must run asynchronously in AWT thread.
521          */

522         Utilities.runInEventDispatchThread(
523             new Runnable JavaDoc() {
524                 public void run() {
525                     JTextComponent JavaDoc c = component;
526                     if (c != null) {
527                         BaseDocument doc = Utilities.getDocument(c);
528                         if (doc != null) {
529                             doc.readLock();
530                             try {
531                                 update(scrollViewToCaret);
532                             } finally {
533                                 doc.readUnlock();
534                             }
535                         }
536                     }
537                 }
538             }
539         );
540     }
541
542     /**
543      * Update the caret's visual position.
544      * <br/>
545      * The document is read-locked while calling this method.
546      *
547      * @param scrollViewToCaret whether the view of the text component should be
548      * scrolled to the position of the caret.
549      */

550     protected void update(boolean scrollViewToCaret) {
551         JTextComponent JavaDoc c = component;
552         if (c != null) {
553             BaseTextUI ui = (BaseTextUI)c.getUI();
554             EditorUI editorUI = ui.getEditorUI();
555             BaseDocument doc = Utilities.getDocument(c);
556             if (doc != null) {
557                 Rectangle JavaDoc oldCaretBounds = caretBounds; // no need to deep copy
558
if (oldCaretBounds != null) {
559                     if (italic) { // caret is italic - add char height to the width of the rect
560
oldCaretBounds.width += oldCaretBounds.height;
561                     }
562                     c.repaint(oldCaretBounds);
563                 }
564
565                 int dot = getDot();
566
567                 if (updateCaretBounds() && (scrollViewToCaret || updateAfterFoldHierarchyChange)) {
568                     Rectangle JavaDoc scrollBounds = new Rectangle JavaDoc(caretBounds);
569                     
570                     // Optimization to avoid extra repaint:
571
// If the caret bounds were not yet assigned then attempt
572
// to scroll the window so that there is an extra vertical space
573
// for the possible horizontal scrollbar that may appear
574
// if the line-view creation process finds line-view that
575
// is too wide and so the horizontal scrollbar will appear
576
// consuming an extra vertical space at the bottom.
577
if (oldCaretBounds == null) {
578                         Component JavaDoc viewport = c.getParent();
579                         if (viewport instanceof JViewport JavaDoc) {
580                             Component JavaDoc scrollPane = viewport.getParent();
581                             if (scrollPane instanceof JScrollPane JavaDoc) {
582                                 JScrollBar JavaDoc hScrollBar = ((JScrollPane JavaDoc)scrollPane).getHorizontalScrollBar();
583                                 if (hScrollBar != null) {
584                                     int hScrollBarHeight = hScrollBar.getPreferredSize().height;
585                                     Dimension JavaDoc extentSize = ((JViewport JavaDoc)viewport).getExtentSize();
586                                     // If the extent size is high enough then extend
587
// the scroll region by extra vertical space
588
if (extentSize.height >= caretBounds.height + hScrollBarHeight) {
589                                         scrollBounds.height += hScrollBarHeight;
590                                     }
591                                 }
592                             }
593                         }
594                     }
595                     
596                     Rectangle JavaDoc visibleBounds = c.getVisibleRect();
597                     
598                     // If folds have changed attempt to scrolll the view so that
599
// relative caret's visual position gets retained
600
// (the absolute position will change because of collapsed/expanded folds).
601
if (oldCaretBounds != null && updateAfterFoldHierarchyChange) {
602                         int oldRelY = oldCaretBounds.y - visibleBounds.y;
603                         // Only fix if the caret is within visible bounds
604
if (oldRelY < visibleBounds.height) {
605                             scrollBounds.y = Math.max(caretBounds.y - oldRelY, 0);
606                             scrollBounds.height = visibleBounds.height;
607                         }
608                     }
609
610                     // Historically the caret is expected to appear
611
// in the middle of the window if setDot() gets called
612
// e.g. by double-clicking in Navigator.
613
// If the caret bounds are more than a caret height below the present
614
// visible view bounds (or above the view bounds)
615
// then scroll the window so that the caret is in the middle
616
// of the visible window to see the context around the caret.
617
// This should work fine with PgUp/Down because these
618
// scroll the view explicitly.
619
if (!updateAfterFoldHierarchyChange &&
620                         (caretBounds.y > visibleBounds.y + visibleBounds.height + caretBounds.height
621                             || caretBounds.y + caretBounds.height < visibleBounds.y - caretBounds.height)
622                     ) {
623                         // Scroll into the middle
624
scrollBounds.y -= (visibleBounds.height - caretBounds.height) / 2;
625                         scrollBounds.height = visibleBounds.height;
626                     }
627
628                     updateAfterFoldHierarchyChange = false;
629                     // Ensure that the viewport will be scrolled so that
630
// the caret is visible
631
c.scrollRectToVisible(scrollBounds);
632
633                     resetBlink();
634
635                     c.repaint(caretBounds);
636                 }
637             }
638         }
639     }
640     
641     private void updateSystemSelection() {
642         if (getDot() != getMark() && component != null) {
643             Clipboard JavaDoc clip = getSystemSelection();
644             
645             if (clip != null) {
646                 clip.setContents(new java.awt.datatransfer.StringSelection JavaDoc(component.getSelectedText()), null);
647             }
648         }
649     }
650
651     private Clipboard JavaDoc getSystemSelection() {
652         return component.getToolkit().getSystemSelection();
653     }
654     
655     private void updateJumpList() {
656         JumpList.dotMoved(component, getDot());
657     }
658     
659     /** Redefine to Object.equals() to prevent defaulting to Rectangle.equals()
660     * which would cause incorrect firing
661     */

662     public boolean equals(Object JavaDoc o) {
663         return (this == o);
664     }
665
666     /** Adds listener to track when caret position was changed */
667     public void addChangeListener(ChangeListener JavaDoc l) {
668         listenerList.add(ChangeListener JavaDoc.class, l);
669     }
670
671     /** Removes listeners to caret position changes */
672     public void removeChangeListener(ChangeListener JavaDoc l) {
673         listenerList.remove(ChangeListener JavaDoc.class, l);
674     }
675
676     /** Notifies listeners that caret position has changed */
677     protected void fireStateChanged() {
678         // Fix of #24336 - always do in AWT thread
679
Utilities.runInEventDispatchThread(new Runnable JavaDoc() {
680             public void run() {
681                 Object JavaDoc listeners[] = listenerList.getListenerList();
682                 for (int i = listeners.length - 2; i >= 0 ; i -= 2) {
683                     if (listeners[i] == ChangeListener JavaDoc.class) {
684                         if (changeEvent == null) {
685                             changeEvent = new ChangeEvent JavaDoc(BaseCaret.this);
686                         }
687                         ((ChangeListener JavaDoc)listeners[i + 1]).stateChanged(changeEvent);
688                     }
689                 }
690             }
691         });
692         updateSystemSelection();
693         updateJumpList();
694     }
695
696     /**
697      * Whether the caret currently visible.
698      * <br>
699      * Although the caret is visible it may be in a state when it's
700      * not physically showing on screen in case when it's blinking.
701      */

702     public final boolean isVisible() {
703         return caretVisible;
704     }
705
706     protected void setVisibleImpl(boolean v) {
707         boolean visible = isVisible();
708         synchronized (this) {
709             synchronized (listenerImpl) {
710                 if (flasher != null) {
711                     if (visible) {
712                         flasher.stop();
713                     }
714                     if (v) {
715                         if (debugCaretFocusExtra){
716                             System.err.println("starting the caret blinking timer: visible=" // NOI18N
717
+ visible + ", blinkVisible=" + blinkVisible); // NOI18N
718
}
719                         flasher.start();
720
721                     } else {
722                         if (debugCaretFocusExtra){
723                             System.err.println("stopping the caret blinking timer"); // NOI18N
724
}
725                         flasher.stop();
726                     }
727                 }
728             }
729
730             caretVisible = v;
731         }
732         JTextComponent JavaDoc c = component;
733         if (c != null && caretBounds != null) {
734             Rectangle JavaDoc repaintRect = caretBounds;
735             if (italic) {
736                 repaintRect = new Rectangle JavaDoc(repaintRect); // copy
737
repaintRect.width += repaintRect.height; // ensure enough horizontally
738
}
739             c.repaint(repaintRect);
740         }
741     }
742
743     synchronized void resetBlink() {
744         boolean visible = isVisible();
745         synchronized (listenerImpl) {
746             if (flasher != null) {
747                 flasher.stop();
748                 blinkVisible = true;
749                 if (visible) {
750                     if (debugCaretFocusExtra){
751                         System.err.println("Reset blinking (caret already visible)" // NOI18N
752
+ " - starting the caret blinking timer: visible=" // NOI18N
753
+ visible + ", blinkVisible=" + blinkVisible // NOI18N
754
);
755                     }
756                     flasher.start();
757                 } else {
758                     if (debugCaretFocusExtra){
759                         System.err.println("Reset blinking (caret not visible)" // NOI18N
760
+ " - caret blinking timer not started: visible=" // NOI18N
761
+ visible + ", blinkVisible=" + blinkVisible // NOI18N
762
);
763                     }
764                 }
765             }
766         }
767     }
768
769     /** Sets the caret visibility */
770     public void setVisible(final boolean v) {
771         Utilities.runInEventDispatchThread(
772             new Runnable JavaDoc() {
773                 public void run() {
774                     setVisibleImpl(v);
775                 }
776             }
777         );
778     }
779
780     /** Is the selection visible? */
781     public final boolean isSelectionVisible() {
782         return selectionVisible;
783     }
784
785     /** Sets the selection visibility */
786     public void setSelectionVisible(boolean v) {
787         if (selectionVisible == v) {
788             return;
789         }
790         JTextComponent JavaDoc c = component;
791         if (c != null) {
792             selectionVisible = v;
793
794             // repaint the block
795
BaseTextUI ui = (BaseTextUI)c.getUI();
796             try {
797                 ui.getEditorUI().repaintBlock(caretMark.getOffset(), selectionMark.getOffset());
798             } catch (BadLocationException JavaDoc e) {
799                 Utilities.annotateLoggable(e);
800             } catch (InvalidMarkException e) {
801                 Utilities.annotateLoggable(e);
802             }
803
804         }
805     }
806
807     /** Saves the current caret position. This is used when
808     * caret up or down actions occur, moving between lines
809     * that have uneven end positions.
810     *
811     * @param p the Point to use for the saved position
812     */

813     public void setMagicCaretPosition(Point JavaDoc p) {
814         magicCaretPosition = p;
815     }
816
817     /** Get position used to mark begining of the selected block */
818     public final Point JavaDoc getMagicCaretPosition() {
819         return magicCaretPosition;
820     }
821
822     /** Sets the caret blink rate.
823     * @param rate blink rate in milliseconds, 0 means no blink
824     */

825     public synchronized void setBlinkRate(int rate) {
826         synchronized (listenerImpl) {
827             if (flasher == null && rate > 0) {
828                 flasher = new Timer JavaDoc(rate, new WeakTimerListener(this));
829             }
830             if (flasher != null) {
831                 if (rate > 0) {
832                     if (flasher.getDelay() != rate) {
833                         if (debugCaretFocusExtra){
834                             System.err.println("blink rate:"+rate); // NOI18N
835
}
836                         flasher.setDelay(rate);
837                     }
838                 } else { // zero rate - don't blink
839
if (debugCaretFocusExtra){
840                         System.err.println("zero rate - don't blink"); // NOI18N
841
System.err.println("setting blinkVisible to true and disabling timer"); // NOI18N
842
}
843                     flasher.stop();
844                     flasher.removeActionListener(this);
845                     flasher = null;
846                     blinkVisible = true;
847                 }
848             }
849         }
850     }
851
852     /** Returns blink rate of the caret or 0 if caret doesn't blink */
853     public synchronized int getBlinkRate() {
854         synchronized (listenerImpl) {
855             return (flasher != null) ? flasher.getDelay() : 0;
856         }
857     }
858
859     /** Gets the current position of the caret */
860     public int getDot() {
861         if (component != null) {
862             try {
863                 return caretMark.getOffset();
864             } catch (InvalidMarkException e) {
865             }
866         }
867         return 0;
868     }
869
870     /** Gets the current position of the selection mark.
871     * If there's a selection this position will be different
872     * from the caret position.
873     */

874     public int getMark() {
875         if (component != null) {
876             if (selectionVisible) {
877                 try {
878                     return selectionMark.getOffset();
879                 } catch (InvalidMarkException e) {
880                 }
881             } else { // selection not visible
882
return getDot(); // must return same position as dot
883
}
884         }
885         return 0;
886     }
887
888     /**
889      * Assign the caret a new offset in the underlying document.
890      * <br/>
891      * This method implicitly sets the selection range to zero.
892      */

893     public void setDot(int offset) {
894         // The first call to this method in NB is done when the component
895
// is already connected to the component hierarchy but its size
896
// is still (0,0,0,0) (although its preferred size is already non-empty).
897
// This causes the TextUI.modelToView() to return null
898
// because BasicTextUI.getVisibleEditorRect() returns null.
899
// Thus caretBounds will be null in such case although
900
// the offset in setDot() is already non-zero.
901
// In such case the component listener listens for resizing
902
// of the editor component and reassigns the caretBounds
903
// once the component gets resized.
904
setDot(offset, caretBounds, EditorUI.SCROLL_DEFAULT);
905     }
906
907     public void setDot(int offset, boolean expandFold) {
908         setDot(offset, caretBounds, EditorUI.SCROLL_DEFAULT, expandFold);
909     }
910     
911     
912     /** Sets the caret position to some position. This
913      * causes removal of the active selection. If expandFold set to true
914      * fold containing offset position will be expanded.
915      *
916      * <p>
917      * <b>Note:</b> This method is deprecated and the present implementation
918      * ignores values of scrollRect and scrollPolicy parameters.
919      *
920      * @param offset offset in the document to which the caret should be positioned.
921      * @param scrollRect rectangle to which the editor window should be scrolled.
922      * @param scrollPolicy the way how scrolling should be done.
923      * One of <code>EditorUI.SCROLL_*</code> constants.
924      * @param expandFold whether possible fold at the caret position should be expanded.
925      *
926      * @deprecated use #setDot(int, boolean) preceded by <code>JComponent.scrollRectToVisible()</code>.
927      */

928     
929     public void setDot(int offset, Rectangle JavaDoc scrollRect, int scrollPolicy, boolean expandFold) {
930         JTextComponent JavaDoc c = component;
931         if (c != null) {
932             setSelectionVisible(false);
933             BaseDocument doc = (BaseDocument)c.getDocument();
934             boolean dotChanged = false;
935             doc.readLock();
936             try {
937                 if (doc != null && offset >= 0 && offset <= doc.getLength()) {
938                     dotChanged = true;
939                     try {
940                         Utilities.moveMark(doc, caretMark, offset);
941                         // Unfold fold
942
FoldHierarchy hierarchy = FoldHierarchy.get(c);
943                         hierarchy.lock();
944                         try {
945                             Fold collapsed = null;
946                             while (expandFold && (collapsed = FoldUtilities.findCollapsedFold(hierarchy, offset, offset)) != null && collapsed.getStartOffset() < offset &&
947                                 collapsed.getEndOffset() > offset) {
948                                 hierarchy.expand(collapsed);
949                             }
950                         } finally {
951                             hierarchy.unlock();
952                         }
953                     } catch (BadLocationException JavaDoc e) {
954                         throw new IllegalStateException JavaDoc(e.toString());
955                         // setting the caret to wrong position leaves it at current position
956
} catch (InvalidMarkException e) {
957                         throw new IllegalStateException JavaDoc(e.toString());
958                         // Caret not installed or inside the initial-read
959
}
960                 }
961             } finally {
962                 doc.readUnlock();
963             }
964             
965             if (dotChanged) {
966                 fireStateChanged();
967                 dispatchUpdate(true);
968             }
969         }
970     }
971     
972     /** Sets the caret position to some position. This
973      * causes removal of the active selection.
974      *
975      * <p>
976      * <b>Note:</b> This method is deprecated and the present implementation
977      * ignores values of scrollRect and scrollPolicy parameters.
978      *
979      * @param offset offset in the document to which the caret should be positioned.
980      * @param scrollRect rectangle to which the editor window should be scrolled.
981      * @param scrollPolicy the way how scrolling should be done.
982      * One of <code>EditorUI.SCROLL_*</code> constants.
983      *
984      * @deprecated use #setDot(int) preceded by <code>JComponent.scrollRectToVisible()</code>.
985      */

986     public void setDot(int offset, Rectangle JavaDoc scrollRect, int scrollPolicy) {
987         setDot(offset, scrollRect, scrollPolicy, true);
988     }
989
990     public void moveDot(int offset) {
991         moveDot(offset, caretBounds, EditorUI.SCROLL_MOVE);
992     }
993
994     /** Makes selection by moving dot but leaving mark.
995      *
996      * <p>
997      * <b>Note:</b> This method is deprecated and the present implementation
998      * ignores values of scrollRect and scrollPolicy parameters.
999      *
1000     * @param offset offset in the document to which the caret should be positioned.
1001     * @param scrollRect rectangle to which the editor window should be scrolled.
1002     * @param scrollPolicy the way how scrolling should be done.
1003     * One of <code>EditorUI.SCROLL_*</code> constants.
1004     *
1005     * @deprecated use #setDot(int) preceded by <code>JComponent.scrollRectToVisible()</code>.
1006     */

1007    public void moveDot(int offset, Rectangle JavaDoc scrollRect, int scrollPolicy) {
1008        JTextComponent JavaDoc c = component;
1009        if (c != null) {
1010            BaseDocument doc = (BaseDocument)c.getDocument();
1011            if (doc != null && offset >= 0 && offset <= doc.getLength()) {
1012                try {
1013                    int oldCaretPos = getDot();
1014                    if (offset == oldCaretPos) { // no change
1015
return;
1016                    }
1017                    int selPos; // current position of selection mark
1018

1019                    if (selectionVisible) {
1020                        selPos = selectionMark.getOffset();
1021                    } else {
1022                        Utilities.moveMark(doc, selectionMark, oldCaretPos);
1023                        selPos = oldCaretPos;
1024                    }
1025
1026                    Utilities.moveMark(doc, caretMark, offset);
1027                    if (selectionVisible) { // selection already visible
1028
Utilities.getEditorUI(c).repaintBlock(oldCaretPos, offset);
1029                        if (selPos == offset) { // same positions -> invisible selection
1030
setSelectionVisible(false);
1031                        }
1032
1033                    } else { // selection not yet visible
1034
setSelectionVisible(true);
1035                    }
1036                } catch (BadLocationException JavaDoc e) {
1037                    throw new IllegalStateException JavaDoc(e.toString());
1038                    // position is incorrect
1039
} catch (InvalidMarkException e) {
1040                    throw new IllegalStateException JavaDoc(e.toString());
1041                }
1042            }
1043            fireStateChanged();
1044            dispatchUpdate(true);
1045        }
1046    }
1047
1048    // DocumentListener methods
1049
public void insertUpdate(DocumentEvent JavaDoc evt) {
1050        JTextComponent JavaDoc c = component;
1051        if (c != null) {
1052            BaseDocument doc = (BaseDocument)component.getDocument();
1053            BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
1054            if ((bevt.isInUndo() || bevt.isInRedo())
1055                    && component == Utilities.getLastActiveComponent()
1056               ) {
1057                // in undo mode and current component
1058
undoOffset = evt.getOffset() + evt.getLength();
1059            } else {
1060                undoOffset = -1;
1061            }
1062
1063            modified = true;
1064
1065            modifiedUpdate();
1066        }
1067    }
1068
1069    public void removeUpdate(DocumentEvent JavaDoc evt) {
1070        JTextComponent JavaDoc c = component;
1071        if (c != null) {
1072            BaseDocument doc = (BaseDocument)c.getDocument();
1073            // make selection invisible if removal shrinked block to zero size
1074
if (selectionVisible && (getDot() == getMark())) {
1075                setSelectionVisible(false);
1076            }
1077            
1078            BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
1079            if ((bevt.isInUndo() || bevt.isInRedo())
1080                && c == Utilities.getLastActiveComponent()
1081            ) {
1082                // in undo mode and current component
1083
undoOffset = evt.getOffset();
1084            } else {
1085                undoOffset = -1;
1086            }
1087
1088            modified = true;
1089            
1090            modifiedUpdate();
1091        }
1092    }
1093    
1094    private void modifiedUpdate() {
1095        if (!inAtomicLock) {
1096            JTextComponent JavaDoc c = component;
1097            if (modified && c != null) {
1098                if (undoOffset >= 0) { // last modification was undo => set the dot to undoOffset
1099
setDot(undoOffset);
1100                } else { // last modification was not undo
1101
fireStateChanged();
1102                    // Scroll to caret only for component with focus
1103
dispatchUpdate(c.hasFocus());
1104                }
1105                modified = false;
1106            }
1107        }
1108    }
1109    
1110    public void atomicLock(AtomicLockEvent evt) {
1111        inAtomicLock = true;
1112    }
1113    
1114    public void atomicUnlock(AtomicLockEvent evt) {
1115        inAtomicLock = false;
1116        modifiedUpdate();
1117    }
1118    
1119    public void changedUpdate(DocumentEvent JavaDoc evt) {
1120        // dispatchUpdate();
1121
}
1122
1123    // MouseListener methods
1124
public void mouseClicked(MouseEvent JavaDoc evt) {
1125        JTextComponent JavaDoc c = component;
1126        if (c != null) {
1127            if (SwingUtilities.isLeftMouseButton(evt)) {
1128                if (evt.getClickCount() == 2) {
1129                    BaseTextUI ui = (BaseTextUI)c.getUI();
1130                    // Expand fold if offset is in collapsed fold
1131
int offset = ui.viewToModel(c,
1132                                    evt.getX(), evt.getY());
1133                    FoldHierarchy hierarchy = FoldHierarchy.get(c);
1134                    Document JavaDoc doc = c.getDocument();
1135                    if (doc instanceof AbstractDocument JavaDoc) {
1136                        AbstractDocument JavaDoc adoc = (AbstractDocument JavaDoc)doc;
1137                        adoc.readLock();
1138                        try {
1139                            hierarchy.lock();
1140                            try {
1141                                Fold collapsed = FoldUtilities.findCollapsedFold(
1142                                    hierarchy, offset, offset);
1143                                if (collapsed != null && collapsed.getStartOffset() <= offset &&
1144                                    collapsed.getEndOffset() >= offset) {
1145                                    hierarchy.expand(collapsed);
1146                                } else {
1147                                    if (selectWordAction == null) {
1148                                        selectWordAction = ((BaseKit)ui.getEditorKit(
1149                                                                c)).getActionByName(BaseKit.selectWordAction);
1150                                    }
1151                                    selectWordAction.actionPerformed(null);
1152                                }
1153                            } finally {
1154                                hierarchy.unlock();
1155                            }
1156                        } finally {
1157                            adoc.readUnlock();
1158                        }
1159                    }
1160                } else if (evt.getClickCount() == 3) {
1161                    if (selectLineAction == null) {
1162                        BaseTextUI ui = (BaseTextUI)c.getUI();
1163                        selectLineAction = ((BaseKit)ui.getEditorKit(
1164                                                c)).getActionByName(BaseKit.selectLineAction);
1165                    }
1166                    selectLineAction.actionPerformed(null);
1167                }
1168            } else if (SwingUtilities.isMiddleMouseButton(evt)){
1169        if (evt.getClickCount() == 1) {
1170            if (c == null) return;
1171                    Toolkit JavaDoc tk = c.getToolkit();
1172                    Clipboard JavaDoc buffer = getSystemSelection();
1173                    
1174                    if (buffer == null) return;
1175
1176                    Transferable JavaDoc trans = buffer.getContents(null);
1177                    if (trans == null) return;
1178
1179                    BaseDocument doc = (BaseDocument)c.getDocument();
1180                    if (doc == null) return;
1181                    
1182                    int offset = ((BaseTextUI)c.getUI()).viewToModel(c,
1183                                    evt.getX(), evt.getY());
1184
1185                    try{
1186                        String JavaDoc pastingString = (String JavaDoc)trans.getTransferData(DataFlavor.stringFlavor);
1187                        if (pastingString == null) return;
1188                         try {
1189                             doc.atomicLock();
1190                             try {
1191                                 doc.insertString(offset, pastingString, null);
1192                                 setDot(offset+pastingString.length());
1193                             } finally {
1194                                 doc.atomicUnlock();
1195                             }
1196                         } catch( BadLocationException JavaDoc exc ) {
1197                         }
1198                    }catch(UnsupportedFlavorException JavaDoc ufe){
1199                    }catch(IOException JavaDoc ioe){
1200                    }
1201        }
1202            }
1203        }
1204    }
1205
1206    private void mousePressedImpl(MouseEvent JavaDoc evt){
1207        JTextComponent JavaDoc c = component;
1208        if (c != null) {
1209            Utilities.getEditorUI(c).getWordMatch().clear(); // [PENDING] should be done cleanly
1210

1211            // Position the cursor at the appropriate place in the document
1212
if ((SwingUtilities.isLeftMouseButton(evt) &&
1213                !(evt.isPopupTrigger()) &&
1214                 (evt.getModifiers() & (InputEvent.META_MASK|InputEvent.ALT_MASK)) == 0) ||
1215               !isSelectionVisible()) {
1216                int offset = ((BaseTextUI)c.getUI()).viewToModel(c,
1217                          evt.getX(), evt.getY());
1218                if (offset >= 0) {
1219                    if ((evt.getModifiers() & InputEvent.SHIFT_MASK) != 0) {
1220                        moveDot(offset);
1221                    } else {
1222                        setDot(offset);
1223                    }
1224                    setMagicCaretPosition(null);
1225                }
1226                if (c.isEnabled()) {
1227                    c.requestFocus();
1228                }
1229            }
1230        }
1231    }
1232    
1233    public void mousePressed(MouseEvent JavaDoc evt) {
1234        dndArmedEvent = null;
1235
1236    if (isDragPossible(evt) && mapDragOperationFromModifiers(evt) != TransferHandler.NONE) {
1237            dndArmedEvent = evt;
1238        evt.consume();
1239            return;
1240    }
1241        
1242        mousePressedImpl(evt);
1243    }
1244
1245    public void mouseReleased(MouseEvent JavaDoc evt) {
1246        if (dndArmedEvent != null){
1247            mousePressedImpl(evt);
1248        }
1249        dndArmedEvent = null;
1250    }
1251
1252    public void mouseEntered(MouseEvent JavaDoc evt) {
1253    }
1254
1255    public void mouseExited(MouseEvent JavaDoc evt) {
1256    }
1257
1258    
1259    protected int mapDragOperationFromModifiers(MouseEvent JavaDoc e) {
1260        int mods = e.getModifiersEx();
1261        
1262        if ((mods & InputEvent.BUTTON1_DOWN_MASK) != InputEvent.BUTTON1_DOWN_MASK) {
1263            return TransferHandler.NONE;
1264        }
1265        
1266        return TransferHandler.COPY_OR_MOVE;
1267    }
1268    /**
1269     * Determines if the following are true:
1270     * <ul>
1271     * <li>the press event is located over a selection
1272     * <li>the dragEnabled property is true
1273     * <li>A TranferHandler is installed
1274     * </ul>
1275     * <p>
1276     * This is implemented to check for a TransferHandler.
1277     * Subclasses should perform the remaining conditions.
1278     */

1279    protected boolean isDragPossible(MouseEvent JavaDoc e) {
1280        JComponent JavaDoc comp = getEventComponent(e);
1281        boolean possible = (comp == null) ? true : (comp.getTransferHandler() != null);
1282        if (possible){
1283            JTextComponent JavaDoc c = (JTextComponent JavaDoc) getEventComponent(e);
1284            if (c.getDragEnabled()) {
1285                Caret JavaDoc caret = c.getCaret();
1286                int dot = caret.getDot();
1287                int mark = caret.getMark();
1288                if (dot != mark) {
1289                    Point JavaDoc p = new Point JavaDoc(e.getX(), e.getY());
1290                    int pos = c.viewToModel(p);
1291
1292                    int p0 = Math.min(dot, mark);
1293                    int p1 = Math.max(dot, mark);
1294                    if ((pos >= p0) && (pos < p1)) {
1295                        return true;
1296                    }
1297                }
1298            }
1299        }
1300        return false;
1301    }
1302    
1303    protected JComponent JavaDoc getEventComponent(MouseEvent JavaDoc e) {
1304    Object JavaDoc src = e.getSource();
1305    if (src instanceof JComponent JavaDoc) {
1306        JComponent JavaDoc c = (JComponent JavaDoc) src;
1307        return c;
1308    }
1309    return null;
1310    }
1311    
1312    // MouseMotionListener methods
1313
public void mouseDragged(MouseEvent JavaDoc evt) {
1314        if (dndArmedEvent != null){
1315            evt.consume();
1316            return;
1317        }
1318        
1319        JTextComponent JavaDoc c = component;
1320        
1321        if (SwingUtilities.isLeftMouseButton(evt)) {
1322            if (c != null) {
1323                int offset = ((BaseTextUI)c.getUI()).viewToModel(c,
1324                          evt.getX(), evt.getY());
1325                // fix for #15204
1326
if (offset == -1)
1327                    offset = 0;
1328                // fix of #22846
1329
if (offset >= 0 && (evt.getModifiers() & InputEvent.SHIFT_MASK) == 0)
1330                    moveDot(offset);
1331            }
1332        }
1333    }
1334
1335    public void mouseMoved(MouseEvent JavaDoc evt) {
1336    }
1337
1338    // PropertyChangeListener methods
1339
public void propertyChange(PropertyChangeEvent JavaDoc evt) {
1340        String JavaDoc propName = evt.getPropertyName();
1341
1342        if ("document".equals(propName)) { // NOI18N
1343
BaseDocument newDoc = (evt.getNewValue() instanceof BaseDocument)
1344                                  ? (BaseDocument)evt.getNewValue() : null;
1345            modelChanged(listenDoc, newDoc);
1346
1347        } else if (EditorUI.OVERWRITE_MODE_PROPERTY.equals(propName)) {
1348            Boolean JavaDoc b = (Boolean JavaDoc)evt.getNewValue();
1349            overwriteMode = (b != null) ? b.booleanValue() : false;
1350            updateType();
1351
1352        } else if ("ancestor".equals(propName) && evt.getSource() == component) { // NOI18N
1353
// The following code ensures that when the width of the line views
1354
// gets computed on background after the file gets opened
1355
// (so the horizontal scrollbar gets added after several seconds
1356
// for larger files) that the suddenly added horizontal scrollbar
1357
// will not hide the caret laying on the last line of the viewport.
1358
// A component listener gets installed into horizontal scrollbar
1359
// and if it's fired the caret's bounds will be checked whether
1360
// they intersect with the horizontal scrollbar
1361
// and if so the view will be scrolled.
1362
Container JavaDoc parent = component.getParent();
1363            if (parent instanceof JViewport JavaDoc) {
1364                parent = parent.getParent(); // parent of viewport
1365
if (parent instanceof JScrollPane JavaDoc) {
1366                    JScrollPane JavaDoc scrollPane = (JScrollPane JavaDoc)parent;
1367                    JScrollBar JavaDoc hScrollBar = scrollPane.getHorizontalScrollBar();
1368                    if (hScrollBar != null) {
1369                        // Add weak listener so that editor pane could be removed
1370
// from scrollpane without being held by scrollbar
1371
hScrollBar.addComponentListener(
1372                                (ComponentListener JavaDoc)WeakListeners.create(
1373                                ComponentListener JavaDoc.class, listenerImpl, hScrollBar));
1374                    }
1375                }
1376            }
1377        }
1378    }
1379
1380    // ActionListener methods
1381
/** Fired when blink timer fires */
1382    public void actionPerformed(ActionEvent JavaDoc evt) {
1383        JTextComponent JavaDoc c = component;
1384        if (c != null) {
1385            blinkVisible = !blinkVisible;
1386            if (caretBounds != null) {
1387                Rectangle JavaDoc repaintRect = caretBounds;
1388                if (italic) {
1389                    repaintRect = new Rectangle JavaDoc(repaintRect); // clone
1390
repaintRect.width += repaintRect.height;
1391                }
1392                c.repaint(repaintRect);
1393            }
1394        }
1395    }
1396
1397    public void foldHierarchyChanged(FoldHierarchyEvent evt) {
1398        int caretOffset = getDot();
1399        int addedFoldCnt = evt.getAddedFoldCount();
1400        if (addedFoldCnt > 0) {
1401            FoldHierarchy hierarchy = (FoldHierarchy)evt.getSource();
1402            Fold collapsed = null;
1403            while ((collapsed = FoldUtilities.findCollapsedFold(hierarchy, caretOffset, caretOffset)) != null && collapsed.getStartOffset() < caretOffset &&
1404                    collapsed.getEndOffset() > caretOffset) {
1405                hierarchy.expand(collapsed);
1406            }
1407        } else {
1408            int endOffset = -1;
1409            // Set the caret's offset to the end of just collapsed fold if necessary
1410
if (evt.getAffectedStartOffset() <= caretOffset && evt.getAffectedEndOffset() >= caretOffset) {
1411                for (int i = 0; i < evt.getFoldStateChangeCount(); i++) {
1412                    FoldStateChange change = evt.getFoldStateChange(i);
1413                    if (change.isCollapsedChanged()) {
1414                        Fold fold = change.getFold();
1415                        if (fold.isCollapsed() && fold.getStartOffset() <= caretOffset && fold.getEndOffset() >= caretOffset) {
1416                            if (fold.getEndOffset() > endOffset)
1417                                endOffset = fold.getEndOffset();
1418                        }
1419                    }
1420                }
1421                if (endOffset > -1)
1422                    setDot(endOffset, false);
1423            }
1424        }
1425        // Update caret's visual position
1426
updateAfterFoldHierarchyChange = true;
1427        dispatchUpdate(false); // do not scroll the window
1428
}
1429    
1430    private class ListenerImpl extends ComponentAdapter JavaDoc
1431    implements FocusListener JavaDoc {
1432
1433        ListenerImpl() {
1434        }
1435
1436        // FocusListener methods
1437
public void focusGained(FocusEvent JavaDoc evt) {
1438            if (debugCaretFocus || debugCaretFocusExtra) {
1439                System.err.println(
1440                        (debugCaretFocusExtra ? "\n" : "") // NOI18N
1441
+ "BaseCaret.focusGained(); doc=" // NOI18N
1442
+ component.getDocument().getProperty(Document.TitleProperty)
1443                );
1444            }
1445            
1446            JTextComponent JavaDoc c = component;
1447            if (c != null) {
1448                updateType();
1449                if (debugCaretFocusExtra) {
1450                    System.err.println("going to set caret visible to: "+c.isEnabled()); // NOI18N
1451
}
1452                setVisible(c.isEnabled()); // invisible caret if disabled
1453
} else {
1454                if (debugCaretFocusExtra) {
1455                    System.err.println("component is null, caret will not be dislayed"); // NOI18N
1456
}
1457            }
1458        }
1459
1460        public void focusLost(FocusEvent JavaDoc evt) {
1461            if (debugCaretFocus || debugCaretFocusExtra) {
1462                System.err.println((debugCaretFocusExtra ? "\n" : "") // NOI18N
1463
+ "BaseCaret.focusLost(); doc=" // NOI18N
1464
+ component.getDocument().getProperty(Document.TitleProperty)
1465                        + "\nFOCUS GAINER: " + evt.getOppositeComponent() // NOI18N
1466
);
1467                if (debugCaretFocusExtra) {
1468                    System.err.println("FOCUS EVENT: " + evt); // NOI18N
1469
}
1470            }
1471        setVisible(false);
1472        }
1473
1474        // ComponentListener methods
1475
/**
1476         * May be called for either component or horizontal scrollbar.
1477         */

1478        public void componentShown(ComponentEvent JavaDoc e) {
1479            // Called when horizontal scrollbar gets visible
1480
// (but the same listener added to component as well so must check first)
1481
// Check whether present caret position will not get hidden
1482
// under horizontal scrollbar and if so scroll the view
1483
Component JavaDoc hScrollBar = e.getComponent();
1484            if (hScrollBar != component) { // really called for horizontal scrollbar
1485
Component JavaDoc scrollPane = hScrollBar.getParent();
1486                if (caretBounds != null && scrollPane instanceof JScrollPane JavaDoc) {
1487                    Rectangle JavaDoc viewRect = ((JScrollPane JavaDoc)scrollPane).getViewport().getViewRect();
1488                    Rectangle JavaDoc hScrollBarRect = new Rectangle JavaDoc(
1489                            viewRect.x,
1490                            viewRect.y + viewRect.height,
1491                            hScrollBar.getWidth(),
1492                            hScrollBar.getHeight()
1493                            );
1494                    if (hScrollBarRect.intersects(caretBounds)) {
1495                        // Update caret's position
1496
dispatchUpdate(true); // should be visible so scroll the view
1497
}
1498                }
1499            }
1500        }
1501
1502        
1503        /**
1504         * May be called for either component or horizontal scrollbar.
1505         */

1506        public void componentResized(ComponentEvent JavaDoc e) {
1507            Component JavaDoc c = e.getComponent();
1508            if (c == component) { // called for component
1509
// In case the caretBounds are still null
1510
// (component not connected to hierarchy yet or it has zero size
1511
// so the modelToView() returned null) re-attempt to compute the bounds.
1512
if (caretBounds == null) {
1513                    dispatchUpdate(true);
1514                    if (caretBounds != null) { // detach the listener - no longer necessary
1515
c.removeComponentListener(this);
1516                    }
1517                }
1518            }
1519        }
1520
1521    }
1522
1523}
1524
Popular Tags