This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 258798
Collapse All | Expand All

(-)a/editor.lib2/src/org/netbeans/api/editor/caret/CaretItem.java (-32 / +47 lines)
Lines 44-49 Link Here
44
import java.awt.Point;
44
import java.awt.Point;
45
import java.awt.Rectangle;
45
import java.awt.Rectangle;
46
import java.util.logging.Logger;
46
import java.util.logging.Logger;
47
import javax.swing.text.JTextComponent;
47
import javax.swing.text.Position;
48
import javax.swing.text.Position;
48
import org.netbeans.api.annotations.common.CheckForNull;
49
import org.netbeans.api.annotations.common.CheckForNull;
49
50
Lines 62-73 Link Here
62
    // -J-Dorg.netbeans.modules.editor.lib2.CaretItem.level=FINEST
63
    // -J-Dorg.netbeans.modules.editor.lib2.CaretItem.level=FINEST
63
    private static final Logger LOG = Logger.getLogger(CaretItem.class.getName());
64
    private static final Logger LOG = Logger.getLogger(CaretItem.class.getName());
64
65
65
    private static final int TRANSACTION_MARK_REMOVED = 1;
66
    private static final int REMOVED_IN_TRANSACTION = 1;
66
    
67
    
67
    private static final int INFO_OBSOLETE = 2;
68
    private static final int INFO_OBSOLETE = 2;
68
69
69
    private static final int UPDATE_VISUAL_BOUNDS = 4;
70
    private static final int UPDATE_CARET_BOUNDS = 4;
70
71
72
    private static final int CARET_PAINTED = 8;
73
    
71
    private final EditorCaret editorCaret;
74
    private final EditorCaret editorCaret;
72
    
75
    
73
    private Position dotPos;
76
    private Position dotPos;
Lines 97-103 Link Here
97
        this.editorCaret = editorCaret;
100
        this.editorCaret = editorCaret;
98
        this.dotPos = dotPos;
101
        this.dotPos = dotPos;
99
        this.markPos = markPos;
102
        this.markPos = markPos;
100
        this.statusBits = UPDATE_VISUAL_BOUNDS; // Request visual bounds updating automatically
103
        this.statusBits = UPDATE_CARET_BOUNDS; // Request visual bounds updating automatically
101
    }
104
    }
102
    
105
    
103
    EditorCaret editorCaret() {
106
    EditorCaret editorCaret() {
Lines 203-213 Link Here
203
        this.magicCaretPosition = newMagicCaretPosition;
206
        this.magicCaretPosition = newMagicCaretPosition;
204
    }
207
    }
205
208
206
    void setCaretBounds(Rectangle newCaretBounds) {
209
    /**
210
     * Set new caret bounds while repainting both original and new bounds.
211
     *
212
     * @param newCaretBounds non-null new bounds
213
     */
214
    synchronized Rectangle setCaretBoundsWithRepaint(Rectangle newCaretBounds, JTextComponent c) {
215
        Rectangle oldCaretBounds = this.caretBounds;
216
        boolean repaintOld = (oldCaretBounds != null && (this.statusBits & CARET_PAINTED) != 0);
217
        this.statusBits &= ~CARET_PAINTED;
218
        if (repaintOld) {
219
            c.repaint(oldCaretBounds); // First schedule repaint of the original bounds (even if new bounds will possibly be the same)
220
        }
221
        if (!repaintOld || !newCaretBounds.equals(oldCaretBounds)) {
222
            c.repaint(newCaretBounds);
223
        }
207
        this.caretBounds = newCaretBounds;
224
        this.caretBounds = newCaretBounds;
225
        return oldCaretBounds;
208
    }
226
    }
209
227
210
    Rectangle getCaretBounds() {
228
    synchronized Rectangle getCaretBounds() {
211
        return this.caretBounds;
229
        return this.caretBounds;
212
    }
230
    }
213
231
Lines 219-261 Link Here
219
        this.transactionIndexHint = transactionIndexHint;
237
        this.transactionIndexHint = transactionIndexHint;
220
    }
238
    }
221
239
222
    void markTransactionMarkRemoved() {
240
    synchronized void markRemovedInTransaction() {
223
        this.statusBits |= TRANSACTION_MARK_REMOVED;
241
        this.statusBits |= REMOVED_IN_TRANSACTION;
224
    }
242
    }
225
243
226
    boolean isTransactionMarkRemoved() {
244
    synchronized boolean getAndClearRemovedInTransaction() {
227
        return (this.statusBits & TRANSACTION_MARK_REMOVED) != 0;
245
        boolean ret = (this.statusBits & REMOVED_IN_TRANSACTION) != 0;
246
        this.statusBits &= ~REMOVED_IN_TRANSACTION;
247
        return ret;
228
    }
248
    }
229
249
230
    void clearTransactionMarkRemoved() {
250
    synchronized void markInfoObsolete() {
231
        this.statusBits &= ~TRANSACTION_MARK_REMOVED;
251
        this.statusBits |= INFO_OBSOLETE;
252
    }
253
254
    synchronized boolean getAndClearInfoObsolete() {
255
        boolean ret = (this.statusBits & INFO_OBSOLETE) != 0;
256
        this.statusBits &= ~INFO_OBSOLETE;
257
        return ret;
258
    }
259
260
    synchronized void markUpdateCaretBounds() {
261
        this.statusBits |= UPDATE_CARET_BOUNDS;
232
    }
262
    }
233
    
263
    
234
    void markUpdateVisualBounds() {
264
    synchronized boolean getAndClearUpdateCaretBounds() {
235
        this.statusBits |= UPDATE_VISUAL_BOUNDS;
265
        boolean ret = (this.statusBits & UPDATE_CARET_BOUNDS) != 0;
266
        this.statusBits &= ~UPDATE_CARET_BOUNDS;
267
        return ret;
236
    }
268
    }
237
    
269
    
238
    boolean isUpdateVisualBounds() {
270
    synchronized void markCaretPainted() {
239
        return (this.statusBits & UPDATE_VISUAL_BOUNDS) != 0;
271
        this.statusBits |= CARET_PAINTED;
240
    }
272
    }
241
    
273
    
242
    void clearUpdateVisualBounds() {
243
        this.statusBits &= ~UPDATE_VISUAL_BOUNDS;
244
    }
245
    
246
    void markInfoObsolete() {
247
        this.statusBits |= INFO_OBSOLETE;
248
    }
249
    
250
    boolean isInfoObsolete() {
251
        return (this.statusBits & INFO_OBSOLETE) != 0;
252
    }
253
    
254
    void clearInfoObsolete() {
255
        this.statusBits &= ~INFO_OBSOLETE;
256
    }
257
    
258
259
    @Override
274
    @Override
260
    public int compareTo(Object o) {
275
    public int compareTo(Object o) {
261
        return getDot() - ((CaretItem)o).getDot();
276
        return getDot() - ((CaretItem)o).getDot();
(-)a/editor.lib2/src/org/netbeans/api/editor/caret/CaretTransaction.java (-18 / +18 lines)
Lines 99-104 Link Here
99
    
99
    
100
    private GapList<CaretItem> extraRemovedItems;
100
    private GapList<CaretItem> extraRemovedItems;
101
    
101
    
102
    private GapList<CaretItem> allRemovedItems;
103
    
102
    private int[] indexes;
104
    private int[] indexes;
103
    
105
    
104
    private int indexesLength;
106
    private int indexesLength;
Lines 157-163 Link Here
157
                    caretItem.setMarkPos(markPos);
159
                    caretItem.setMarkPos(markPos);
158
                }
160
                }
159
                updateAffectedIndexes(index, index + 1);
161
                updateAffectedIndexes(index, index + 1);
160
                caretItem.markUpdateVisualBounds();
162
                caretItem.markUpdateCaretBounds();
161
                caretItem.markInfoObsolete();
163
                caretItem.markInfoObsolete();
162
                dotOrMarkChanged = true;
164
                dotOrMarkChanged = true;
163
                return true;
165
                return true;
Lines 172-178 Link Here
172
        int index = findCaretItemIndex(origCaretItems, caretItem);
174
        int index = findCaretItemIndex(origCaretItems, caretItem);
173
        if (index != -1) {
175
        if (index != -1) {
174
            caretItem.setMagicCaretPosition(p);
176
            caretItem.setMagicCaretPosition(p);
175
            caretItem.clearInfo();
177
            caretItem.markInfoObsolete();
176
            updateAffectedIndexes(index, index + 1);
178
            updateAffectedIndexes(index, index + 1);
177
            return true;
179
            return true;
178
        }
180
        }
Lines 346-352 Link Here
346
                        setSelectionStartEnd(itemInfo, lastInfo.startPos, true);
348
                        setSelectionStartEnd(itemInfo, lastInfo.startPos, true);
347
                    }
349
                    }
348
                    // Remove lastInfo's getCaret item
350
                    // Remove lastInfo's getCaret item
349
                    lastInfo.caretItem.markTransactionMarkRemoved();
351
                    lastInfo.caretItem.markRemovedInTransaction();
350
                    origSortedItems.copyElements(copyStartIndex, i - 1, nonOverlappingItems);
352
                    origSortedItems.copyElements(copyStartIndex, i - 1, nonOverlappingItems);
351
                    copyStartIndex = i;
353
                    copyStartIndex = i;
352
                    
354
                    
Lines 356-362 Link Here
356
                        setSelectionStartEnd(lastInfo, itemInfo.endPos, false);
358
                        setSelectionStartEnd(lastInfo, itemInfo.endPos, false);
357
                    }
359
                    }
358
                    // Remove itemInfo's getCaret item
360
                    // Remove itemInfo's getCaret item
359
                    itemInfo.caretItem.markTransactionMarkRemoved();
361
                    itemInfo.caretItem.markRemovedInTransaction();
360
                    origSortedItems.copyElements(copyStartIndex, i, nonOverlappingItems);
362
                    origSortedItems.copyElements(copyStartIndex, i, nonOverlappingItems);
361
                    copyStartIndex = i + 1;
363
                    copyStartIndex = i + 1;
362
                }
364
                }
Lines 380-387 Link Here
380
            replaceItems = new GapList<>(origItemsSize);
382
            replaceItems = new GapList<>(origItemsSize);
381
            for (i = 0; i < origItemsSize; i++) {
383
            for (i = 0; i < origItemsSize; i++) {
382
                CaretItem caretItem = origItems.get(i);
384
                CaretItem caretItem = origItems.get(i);
383
                if (caretItem.isTransactionMarkRemoved()) {
385
                if (caretItem.getAndClearRemovedInTransaction()) {
384
                    caretItem.clearTransactionMarkRemoved();
385
                    if (extraRemovedItems == null) {
386
                    if (extraRemovedItems == null) {
386
                        extraRemovedItems = new GapList<>();
387
                        extraRemovedItems = new GapList<>();
387
                    }
388
                    }
Lines 394-414 Link Here
394
        }
395
        }
395
    }
396
    }
396
    
397
    
397
    GapList<CaretItem> addRemovedItems(GapList<CaretItem> toItems) {
398
    GapList<CaretItem> allRemovedItems() {
398
        int removeSize = modEndIndex - modIndex;
399
        int removeSize = modEndIndex - modIndex;
399
        int extraRemovedSize = (extraRemovedItems != null) ? extraRemovedItems.size() : 0;
400
        int extraRemovedSize = (extraRemovedItems != null) ? extraRemovedItems.size() : 0;
400
        if (removeSize + extraRemovedSize > 0) {
401
        if (removeSize + extraRemovedSize > 0) {
401
            if (toItems == null) {
402
            if (allRemovedItems == null) {
402
                toItems = new GapList<>(removeSize + extraRemovedSize);
403
                allRemovedItems = new GapList<>(removeSize + extraRemovedSize);
403
            }
404
                if (removeSize > 0) {
404
            if (removeSize > 0) {
405
                    allRemovedItems.addAll(origCaretItems, modIndex, removeSize);
405
                toItems.addAll(origCaretItems, modIndex, removeSize);
406
                }
406
            }
407
                if (extraRemovedSize > 0) {
407
            if (extraRemovedSize > 0) {
408
                    allRemovedItems.addAll(extraRemovedItems);
408
                toItems.addAll(extraRemovedItems);
409
                }
409
            }
410
            }
410
        }
411
        }
411
        return toItems;
412
        return allRemovedItems;
412
    }
413
    }
413
    
414
    
414
    GapList<CaretItem> addUpdateVisualBoundsItems(GapList<CaretItem> toItems) {
415
    GapList<CaretItem> addUpdateVisualBoundsItems(GapList<CaretItem> toItems) {
Lines 416-423 Link Here
416
        int size = items.size();
417
        int size = items.size();
417
        for (int i = 0; i < size; i++) {
418
        for (int i = 0; i < size; i++) {
418
            CaretItem caretItem = items.get(i);
419
            CaretItem caretItem = items.get(i);
419
            if (caretItem.isUpdateVisualBounds()) {
420
            if (caretItem.getAndClearUpdateCaretBounds()) {
420
                caretItem.clearUpdateVisualBounds();
421
                if (toItems == null) {
421
                if (toItems == null) {
422
                    toItems = new GapList<>();
422
                    toItems = new GapList<>();
423
                }
423
                }
(-)a/editor.lib2/src/org/netbeans/api/editor/caret/EditorCaret.java (-333 / +269 lines)
Lines 54-59 Link Here
54
import java.awt.Graphics2D;
54
import java.awt.Graphics2D;
55
import java.awt.Point;
55
import java.awt.Point;
56
import java.awt.Rectangle;
56
import java.awt.Rectangle;
57
import java.awt.Shape;
57
import java.awt.Stroke;
58
import java.awt.Stroke;
58
import java.awt.datatransfer.Clipboard;
59
import java.awt.datatransfer.Clipboard;
59
import java.awt.datatransfer.DataFlavor;
60
import java.awt.datatransfer.DataFlavor;
Lines 128-134 Link Here
128
import org.netbeans.modules.editor.lib2.RectangularSelectionUtils;
129
import org.netbeans.modules.editor.lib2.RectangularSelectionUtils;
129
import org.netbeans.modules.editor.lib2.actions.EditorActionUtilities;
130
import org.netbeans.modules.editor.lib2.actions.EditorActionUtilities;
130
import org.netbeans.modules.editor.lib2.highlighting.CaretOverwriteModeHighlighting;
131
import org.netbeans.modules.editor.lib2.highlighting.CaretOverwriteModeHighlighting;
131
import org.netbeans.modules.editor.lib2.view.DocumentView;
132
import org.netbeans.modules.editor.lib2.view.LockedViewHierarchy;
132
import org.netbeans.modules.editor.lib2.view.LockedViewHierarchy;
133
import org.netbeans.modules.editor.lib2.view.ViewHierarchy;
133
import org.netbeans.modules.editor.lib2.view.ViewHierarchy;
134
import org.netbeans.modules.editor.lib2.view.ViewHierarchyEvent;
134
import org.netbeans.modules.editor.lib2.view.ViewHierarchyEvent;
Lines 164-169 Link Here
164
    // -J-Dorg.netbeans.editor.BaseCaret.level=FINEST
164
    // -J-Dorg.netbeans.editor.BaseCaret.level=FINEST
165
    private static final Logger LOG = Logger.getLogger(EditorCaret.class.getName());
165
    private static final Logger LOG = Logger.getLogger(EditorCaret.class.getName());
166
    
166
    
167
    static final long serialVersionUID = 0L;
168
    
167
    static {
169
    static {
168
        RectangularSelectionCaretAccessor.register(new RectangularSelectionCaretAccessor() {
170
        RectangularSelectionCaretAccessor.register(new RectangularSelectionCaretAccessor() {
169
            @Override
171
            @Override
Lines 183-190 Link Here
183
        });
185
        });
184
    }
186
    }
185
187
186
    static final long serialVersionUID = 0L;
187
188
    /**
188
    /**
189
     * Non-empty list of individual carets in the order they were created.
189
     * Non-empty list of individual carets in the order they were created.
190
     * At least one item is always present.
190
     * At least one item is always present.
Lines 236-245 Link Here
236
    /**
236
    /**
237
     * Whether blinking caret is currently visible on the screen.
237
     * Whether blinking caret is currently visible on the screen.
238
     * <br>
238
     * <br>
239
     * This changes from true to false after each tick of a timer
239
     * This changes from true to false and back as caret(s) blink.
240
     * (assuming <code>visible == true</code>).
240
     * <br>
241
     * If false then original locations do not need to be repainted.
241
     */
242
     */
242
    private boolean blinkVisible;
243
    private boolean showing;
243
244
244
    /**
245
    /**
245
     * Determine if a possible selection would be displayed or not.
246
     * Determine if a possible selection would be displayed or not.
Lines 254-261 Link Here
254
    
255
    
255
    private MouseState mouseState = MouseState.DEFAULT;
256
    private MouseState mouseState = MouseState.DEFAULT;
256
    
257
    
257
    /** Timer used for blinking the caret */
258
    /**
258
    private Timer flasher;
259
     * Default delay for caret blinking if the system is responsive enough.
260
     * Zero if caret blinking is disabled.
261
     */
262
    private int blinkDefaultDelay;
263
    
264
    /**
265
     * Currently used delay according to system responsiveness.
266
     */
267
    private int blinkCurrentDelay;
268
    
269
    /**
270
     * Last time when blink timer action was invoked or zero if the blinking delay
271
     * should not be examined upon next timer action invocation.
272
     * If there are too many carets being painted and the system becomes busy
273
     * the blinkCurrentDelay may be increased to lower the blinking frequency
274
     * and allow the system to be responsive.
275
     */
276
    private long lastBlinkTime;
277
    
278
    /**
279
     * Carets blinking timer.
280
     */
281
    private Timer blinkTimer;
282
    
283
    private ActionListener weakTimerListener;
259
284
260
    private Action selectWordAction;
285
    private Action selectWordAction;
261
    private Action selectLineAction;
286
    private Action selectLineAction;
Lines 269-286 Link Here
269
    private CaretTransaction activeTransaction;
294
    private CaretTransaction activeTransaction;
270
    
295
    
271
    /**
296
    /**
272
     * Items from previous transaction(s) that need their visual rectangle
273
     * to get repainted to clear the previous caret representation visually.
274
     */
275
    private GapList<CaretItem> pendingRepaintRemovedItemsList;
276
277
    /**
278
     * Items from previous transaction(s) that need their visual bounds
279
     * to be recomputed and caret to be repainted then.
280
     */
281
    private GapList<CaretItem> pendingUpdateVisualBoundsItemsList;
282
    
283
    /**
284
     * Caret item to which the view should scroll or null for no scrolling.
297
     * Caret item to which the view should scroll or null for no scrolling.
285
     */
298
     */
286
    private CaretItem scrollToItem;
299
    private CaretItem scrollToItem;
Lines 381-386 Link Here
381
     * If there is a selection, the mark will not be the same as the dot.
394
     * If there is a selection, the mark will not be the same as the dot.
382
     * @return mark offset &gt;=0
395
     * @return mark offset &gt;=0
383
     */
396
     */
397
  
384
    @Override
398
    @Override
385
    public int getMark() {
399
    public int getMark() {
386
        return getLastCaret().getMark();
400
        return getLastCaret().getMark();
Lines 606-612 Link Here
606
     *  or no document installed in the text component.
620
     *  or no document installed in the text component.
607
     *  <br>
621
     *  <br>
608
     *  Note that adding a new caret to offset where another caret is already located may lead
622
     *  Note that adding a new caret to offset where another caret is already located may lead
609
     *  to its immediate removal.
623
     *  or no document installed in the text component.
610
     */
624
     */
611
    public int addCaret(@NonNull Position dotPos, @NonNull Position markPos) {
625
    public int addCaret(@NonNull Position dotPos, @NonNull Position markPos) {
612
        return runTransaction(CaretTransaction.RemoveType.NO_REMOVE, 0,
626
        return runTransaction(CaretTransaction.RemoveType.NO_REMOVE, 0,
Lines 749-757 Link Here
749
            }
763
            }
750
        }
764
        }
751
        synchronized (listenerList) {
765
        synchronized (listenerList) {
752
            if (flasher != null) {
766
            if (blinkTimer != null) {
753
                if (this.visible) {
767
                if (this.visible) {
754
                    flasher.stop();
768
                    blinkTimer.stop();
755
                }
769
                }
756
                if (LOG.isLoggable(Level.FINER)) {
770
                if (LOG.isLoggable(Level.FINER)) {
757
                    LOG.finer((visible ? "Starting" : "Stopping") + // NOI18N
771
                    LOG.finer((visible ? "Starting" : "Stopping") + // NOI18N
Lines 759-767 Link Here
759
                }
773
                }
760
                this.visible = visible;
774
                this.visible = visible;
761
                if (visible) {
775
                if (visible) {
762
                    flasher.start();
776
                    blinkTimer.start();
763
                } else {
777
                } else {
764
                    flasher.stop();
778
                    blinkTimer.stop();
765
                }
779
                }
766
            }
780
            }
767
        }
781
        }
Lines 811-821 Link Here
811
        Boolean b = (Boolean) c.getClientProperty(EditorUtilities.CARET_OVERWRITE_MODE_PROPERTY);
825
        Boolean b = (Boolean) c.getClientProperty(EditorUtilities.CARET_OVERWRITE_MODE_PROPERTY);
812
        overwriteMode = (b != null) ? b : false;
826
        overwriteMode = (b != null) ? b : false;
813
        updateOverwriteModeLayer(true);
827
        updateOverwriteModeLayer(true);
814
        setBlinkVisible(true);
828
        setShowing(true);
815
        
829
        
816
        // Attempt to assign initial bounds - usually here the component
830
        // Attempt to assign initial bounds - usually here the component
817
        // is not yet added to the component hierarchy.
831
        // is not yet added to the component hierarchy.
818
        updateAllCaretsBounds();
832
        requestUpdateAllCaretsBounds();
819
        
833
        
820
        if(getLastCaretItem().getCaretBounds() == null) {
834
        if(getLastCaretItem().getCaretBounds() == null) {
821
            // For null bounds wait for the component to get resized
835
            // For null bounds wait for the component to get resized
Lines 848-854 Link Here
848
        }
862
        }
849
        
863
        
850
        synchronized (listenerList) {
864
        synchronized (listenerList) {
851
            if (flasher != null) {
865
            if (blinkTimer != null) {
852
                setBlinkRate(0);
866
                setBlinkRate(0);
853
            }
867
            }
854
        }
868
        }
Lines 867-897 Link Here
867
    @Override
881
    @Override
868
    public void paint(Graphics g) {
882
    public void paint(Graphics g) {
869
        JTextComponent c = component;
883
        JTextComponent c = component;
870
        if (c == null) return;
884
        if (c == null || !isShowing()) {
871
        
885
            return;
872
        // Check whether the caret was moved but the component was not
873
        // validated yet and therefore the caret bounds are still null
874
        // and if so compute the bounds and scroll the view if necessary.
875
        // TODO - could this be done by an extra flag rather than bounds checking??
876
        CaretItem lastCaret = getLastCaretItem();
877
        if (getDot() != 0 && lastCaret.getCaretBounds() == null) {
878
            update(true);
879
        }
886
        }
880
        
887
888
        // Only paint carets that are part of the clip - use sorted carets
889
        Rectangle clipBounds = g.getClipBounds();
881
        List<CaretInfo> carets = getSortedCarets();
890
        List<CaretInfo> carets = getSortedCarets();
882
        for (CaretInfo caretInfo : carets) { // TODO only paint the items in the clipped area - use binary search to located first item
891
        int low = 0;
883
            CaretItem caretItem = caretInfo.getCaretItem();
892
        int caretsSize = carets.size();
884
            if (LOG.isLoggable(Level.FINEST)) {
893
        int high = caretsSize - 1;
885
                LOG.finest("BaseCaret.paint(): caretBounds=" + caretItem.getCaretBounds() + dumpVisibility() + '\n');
894
        while (low <= high) {
895
            int mid = (low + high) >>> 1;
896
            CaretInfo caretInfo = carets.get(mid);
897
            Rectangle midBounds = caretInfo.getCaretItem().getCaretBounds();
898
899
            if (midBounds.y + midBounds.height <= clipBounds.y) {
900
                low = mid + 1;
901
            } else { // No exact match (may be multiple carets on sinle line)
902
                high = mid - 1;
886
            }
903
            }
887
            if (caretItem.getCaretBounds() != null && isVisible() && blinkVisible) {
904
        }
888
                paintCaret(g, caretItem);
905
906
        Color origColor = g.getColor();
907
        try {
908
            g.setColor(c.getCaretColor());
909
            // Use "low" index (which is higher than "high" at end of binary-search)
910
            for (int i = low; i < caretsSize; i++) {
911
                CaretInfo caretInfo = carets.get(i);
912
                CaretItem caretItem = caretInfo.getCaretItem();
913
                if (LOG.isLoggable(Level.FINEST)) {
914
                    LOG.finest("BaseCaret.paint(): caretBounds=" + caretItem.getCaretBounds() + dumpVisibility() + '\n');
915
                }
916
                if (caretItem.getCaretBounds() != null) {
917
                    Rectangle caretBounds = caretItem.getCaretBounds();
918
                    switch (type) {
919
                        case THICK_LINE_CARET:
920
                            g.fillRect(caretBounds.x, caretBounds.y, this.thickCaretWidth, caretBounds.height - 1);
921
                            break;
922
923
                        case THIN_LINE_CARET:
924
                            int upperX = caretItem.getCaretBounds().x;
925
                            g.drawLine((int) upperX, caretItem.getCaretBounds().y, caretItem.getCaretBounds().x,
926
                                    (caretItem.getCaretBounds().y + caretItem.getCaretBounds().height - 1));
927
                            break;
928
929
                        case BLOCK_CARET:
930
                            // Use a CaretOverwriteModeHighlighting layer to paint the caret
931
                            break;
932
933
                        default:
934
                            throw new IllegalStateException("Invalid caret type=" + type);
935
                    }
936
                }
889
            }
937
            }
938
            
939
            // Possibly service rectangular selection
890
            if (rectangularSelection && rsPaintRect != null && g instanceof Graphics2D) {
940
            if (rectangularSelection && rsPaintRect != null && g instanceof Graphics2D) {
891
                Graphics2D g2d = (Graphics2D) g;
941
                Graphics2D g2d = (Graphics2D) g;
892
                Stroke stroke = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[] {4, 2}, 0);
942
                Stroke stroke = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[] {4, 2}, 0);
893
                Stroke origStroke = g2d.getStroke();
943
                Stroke origStroke = g2d.getStroke();
894
                Color origColor = g2d.getColor();
895
                try {
944
                try {
896
                    // Render translucent rectangle
945
                    // Render translucent rectangle
897
                    Color selColor = c.getSelectionColor();
946
                    Color selColor = c.getSelectionColor();
Lines 913-921 Link Here
913
962
914
                } finally {
963
                } finally {
915
                    g2d.setStroke(origStroke);
964
                    g2d.setStroke(origStroke);
916
                    g2d.setColor(origColor);
917
                }
965
                }
918
            }
966
            }
967
        } finally {
968
            g.setColor(origColor);
919
        }
969
        }
920
    }
970
    }
921
971
Lines 932-952 Link Here
932
            LOG.finer("setBlinkRate(" + rate + ")" + dumpVisibility() + '\n'); // NOI18N
982
            LOG.finer("setBlinkRate(" + rate + ")" + dumpVisibility() + '\n'); // NOI18N
933
        }
983
        }
934
        synchronized (listenerList) {
984
        synchronized (listenerList) {
935
            if (flasher == null && rate > 0) {
985
            this.blinkDefaultDelay = rate;
936
                flasher = new Timer(rate, listenerImpl);
986
            this.blinkCurrentDelay = rate;
987
            if (blinkTimer == null && rate > 0) {
988
                blinkTimer = new Timer(rate, null);
989
                blinkTimer.addActionListener(weakTimerListener = WeakListeners.create(
990
                        ActionListener.class, listenerImpl, blinkTimer));
937
            }
991
            }
938
            if (flasher != null) {
992
            if (blinkTimer != null) {
939
                if (rate > 0) {
993
                if (rate > 0) {
940
                    if (flasher.getDelay() != rate) {
994
                    if (blinkTimer.getDelay() != rate) {
941
                        flasher.setDelay(rate);
995
                        blinkTimer.setDelay(rate);
942
                    }
996
                    }
943
                } else { // zero rate - don't blink
997
                } else { // zero rate - don't blink
944
                    flasher.stop();
998
                    blinkTimer.stop();
945
                    flasher.removeActionListener(listenerImpl);
999
                    if (weakTimerListener != null) {
946
                    flasher = null;
1000
                        blinkTimer.removeActionListener(weakTimerListener);
947
                    setBlinkVisible(true);
1001
                    }
1002
                    blinkTimer = null;
1003
                    setShowing(true);
948
                    if (LOG.isLoggable(Level.FINER)){
1004
                    if (LOG.isLoggable(Level.FINER)){
949
                        LOG.finer("Zero blink rate - no blinking. flasher=null; blinkVisible=true"); // NOI18N
1005
                        LOG.finer("Zero blink rate - no blinking. blinkTimer=null; showing=true"); // NOI18N
950
                    }
1006
                    }
951
                }
1007
                }
952
            }
1008
            }
Lines 956-962 Link Here
956
    @Override
1012
    @Override
957
    public int getBlinkRate() {
1013
    public int getBlinkRate() {
958
        synchronized (listenerList) {
1014
        synchronized (listenerList) {
959
            return (flasher != null) ? flasher.getDelay() : 0;
1015
            return blinkDefaultDelay;
960
        }
1016
        }
961
    }
1017
    }
962
1018
Lines 1122-1153 Link Here
1122
                        synchronized (listenerList) {
1178
                        synchronized (listenerList) {
1123
                            GapList<CaretItem> replaceItems = activeTransaction.getReplaceItems();
1179
                            GapList<CaretItem> replaceItems = activeTransaction.getReplaceItems();
1124
                            if (replaceItems != null) {
1180
                            if (replaceItems != null) {
1181
                                caretItems = replaceItems;
1125
                                diffCount = replaceItems.size() - caretItems.size();
1182
                                diffCount = replaceItems.size() - caretItems.size();
1126
                                caretItems = replaceItems;
1127
                                sortedCaretItems = activeTransaction.getSortedCaretItems();
1128
                                for (CaretItem caretItem : caretItems) {
1183
                                for (CaretItem caretItem : caretItems) {
1129
                                    if (caretItem.isInfoObsolete()) {
1184
                                    if (caretItem.getAndClearInfoObsolete()) {
1130
                                        caretItem.clearInfoObsolete();
1131
                                        caretItem.clearInfo();
1185
                                        caretItem.clearInfo();
1132
                                    }
1186
                                    }
1133
                                }
1187
                                }
1134
                                assert (sortedCaretItems != null) : "Null sortedCaretItems! removeType=" + removeType; // NOI18N
1188
                               sortedCaretItems = activeTransaction.getSortedCaretItems();
1135
                            }
1189
                               assert (sortedCaretItems != null) : "Null sortedCaretItems! removeType=" + removeType; // NOI18N
1136
                            if (activeTransaction.isAnyChange()) {
1137
                                caretInfos = null;
1138
                                sortedCaretInfos = null;
1139
                            }
1190
                            }
1140
                        }
1191
                        }
1141
                        pendingRepaintRemovedItemsList = activeTransaction.
1192
                        if (activeTransaction.isAnyChange()) {
1142
                                addRemovedItems(pendingRepaintRemovedItemsList);
1193
                            caretInfos = null;
1143
                        pendingUpdateVisualBoundsItemsList = activeTransaction.
1194
                            sortedCaretInfos = null;
1144
                                addUpdateVisualBoundsItems(pendingUpdateVisualBoundsItemsList);
1195
                        }
1145
                        if (pendingUpdateVisualBoundsItemsList != null || pendingRepaintRemovedItemsList != null) {
1196
                        // Repaint bounds of removed items
1146
                            // For now clear the lists and use old way TODO update to selective updating and rendering
1197
                        GapList<CaretItem> removedItems = activeTransaction.allRemovedItems();
1198
                        if (removedItems != null) {
1199
                            for (CaretItem removedItem : removedItems) {
1200
                                Rectangle caretBounds = removedItem.getCaretBounds();
1201
                                if (caretBounds != null) {
1202
                                    c.repaint(caretBounds);
1203
                                }
1204
                            }
1205
                        }
1206
                        if (activeTransaction.isAnyChange()) {
1147
                            fireStateChanged();
1207
                            fireStateChanged();
1148
                            dispatchUpdate(true);
1208
                            dispatchUpdate(true);
1149
                            pendingRepaintRemovedItemsList = null;
1150
                            pendingUpdateVisualBoundsItemsList = null;
1151
                        }
1209
                        }
1152
                        return diffCount;
1210
                        return diffCount;
1153
                    } finally {
1211
                    } finally {
Lines 1174-1211 Link Here
1174
        }
1232
        }
1175
    }
1233
    }
1176
    
1234
    
1177
    private void moveDotCaret(int offset, CaretItem caret) throws IllegalStateException {
1235
    private void updateRectangularSelectionDotRect() { // Assumes update caret bounds of getLastCaretItem()
1178
        JTextComponent c = component;
1236
        if (rectangularSelection) {
1179
        AbstractDocument doc;
1237
            Rectangle caretBounds = getLastCaretItem().getCaretBounds();
1180
        if (c != null && (doc = activeDoc) != null) {
1238
            if (caretBounds != null) {
1181
            if (offset >= 0 && offset <= doc.getLength()) {
1239
                if (rsDotRect != null) {
1182
                doc.readLock();
1240
                    rsDotRect.y = caretBounds.y;
1183
                try {
1241
                    rsDotRect.height = caretBounds.height;
1184
                    int oldCaretPos = caret.getDot();
1242
                } else {
1185
                    if (offset == oldCaretPos) { // no change
1243
                    rsDotRect = caretBounds;
1186
                        return;
1187
                    }
1188
                    caret.setDotPos(doc.createPosition(offset));
1189
                    // Selection highlighting should be handled automatically by highlighting layers
1190
                    if (rectangularSelection) {
1191
                        Rectangle r = c.modelToView(offset);
1192
                        if (rsDotRect != null) {
1193
                            rsDotRect.y = r.y;
1194
                            rsDotRect.height = r.height;
1195
                        } else {
1196
                            rsDotRect = r;
1197
                        }
1198
                        updateRectangularSelectionPaintRect();
1199
                    }
1200
                } catch (BadLocationException e) {
1201
                    throw new IllegalStateException(e.toString());
1202
                    // position is incorrect
1203
                } finally {
1204
                    doc.readUnlock();
1205
                }
1244
                }
1206
            }
1245
            }
1207
            fireStateChanged();
1246
            updateRectangularSelectionPaintRect();
1208
            dispatchUpdate(true);
1209
        }
1247
        }
1210
    }
1248
    }
1211
    
1249
    
Lines 1235-1241 Link Here
1235
        };
1273
        };
1236
        
1274
        
1237
        // Always fire in EDT
1275
        // Always fire in EDT
1238
        if (inAtomicUnlock) { // Cannot fire within atomic lock9
1276
        if (inAtomicUnlock) { // Cannot fire within atomic lock
1239
            SwingUtilities.invokeLater(runnable);
1277
            SwingUtilities.invokeLater(runnable);
1240
        } else {
1278
        } else {
1241
            ViewUtils.runInEDT(runnable);
1279
            ViewUtils.runInEDT(runnable);
Lines 1333-1411 Link Here
1333
    }
1371
    }
1334
1372
1335
    /**
1373
    /**
1336
     * Assign new caret bounds into <code>caretBounds</code> variable.
1374
     * Schedule recomputation of visual bounds of all carets.
1337
     *
1338
     * @return true if the new caret bounds were successfully computed
1339
     *  and assigned or false otherwise.
1340
     */
1375
     */
1341
    private boolean updateAllCaretsBounds() {
1376
    private void requestUpdateAllCaretsBounds() {
1342
        JTextComponent c = component;
1377
        JTextComponent c = component;
1343
        AbstractDocument doc;
1378
        AbstractDocument doc;
1344
        boolean ret = false;
1345
        if (c != null && (doc = activeDoc) != null) {
1379
        if (c != null && (doc = activeDoc) != null) {
1346
            doc.readLock();
1380
            doc.readLock();
1347
            try {
1381
            try {
1348
                List<CaretInfo> sortedCarets = getSortedCarets();
1382
                List<CaretInfo> sortedCarets = getSortedCarets();
1349
                for (CaretInfo caret : sortedCarets) {
1383
                for (CaretInfo caret : sortedCarets) {
1350
                    ret |= updateRealCaretBounds(caret.getCaretItem(), doc, c);
1384
                    caret.getCaretItem().markUpdateCaretBounds();
1351
                }
1385
                }
1352
            } finally {
1386
            } finally {
1353
                doc.readUnlock();
1387
                doc.readUnlock();
1354
            }
1388
            }
1355
        }
1389
        }
1356
        return ret;
1357
    }
1390
    }
1358
    
1391
    
1359
    private boolean updateCaretBounds(CaretItem caret) {
1360
        JTextComponent c = component;
1361
        boolean ret = false;
1362
        AbstractDocument doc;
1363
        if (c != null && (doc = activeDoc) != null) {
1364
            doc.readLock();
1365
            try {
1366
                ret = updateRealCaretBounds(caret, doc, c);
1367
            } finally {
1368
                doc.readUnlock();
1369
            }
1370
        }
1371
        return ret;
1372
    }
1373
    
1374
    private boolean updateRealCaretBounds(CaretItem caret, Document doc, JTextComponent c) {
1375
        Position dotPos = caret.getDotPosition();
1376
        int offset = dotPos == null? 0 : dotPos.getOffset();
1377
        if (offset > doc.getLength()) {
1378
            offset = doc.getLength();
1379
        }
1380
        Rectangle newCaretBounds;
1381
        try {
1382
            DocumentView docView = DocumentView.get(c);
1383
            if (docView != null) {
1384
                // docView.syncViewsRebuild(); // Make sure pending views changes are resolved
1385
            }
1386
            newCaretBounds = c.getUI().modelToView(
1387
                    c, offset, Position.Bias.Forward);
1388
            // [TODO] Temporary fix - impl should remember real bounds computed by paintCustomCaret()
1389
            if (newCaretBounds != null) {
1390
                newCaretBounds.width = Math.max(newCaretBounds.width, 2);
1391
            }
1392
1393
        } catch (BadLocationException e) {
1394
            
1395
            newCaretBounds = null;
1396
        }
1397
        if (newCaretBounds != null) {
1398
            if (LOG.isLoggable(Level.FINE)) {
1399
                LOG.log(Level.FINE, "updateCaretBounds: old={0}, new={1}, offset={2}",
1400
                        new Object[]{caret.getCaretBounds(), newCaretBounds, offset}); //NOI18N
1401
            }
1402
            caret.setCaretBounds(newCaretBounds);
1403
            return true;
1404
        } else {
1405
            return false;
1406
        }
1407
    }
1408
1409
    private void modelChanged(Document oldDoc, Document newDoc) {
1392
    private void modelChanged(Document oldDoc, Document newDoc) {
1410
        if (oldDoc != null) {
1393
        if (oldDoc != null) {
1411
            // ideally the oldDoc param shouldn't exist and only listenDoc should be used
1394
            // ideally the oldDoc param shouldn't exist and only listenDoc should be used
Lines 1450-1481 Link Here
1450
        }
1433
        }
1451
    }
1434
    }
1452
    
1435
    
1453
    private void paintCaret(Graphics g, CaretItem caret) {
1454
        JTextComponent c = component;
1455
        if (c != null) {
1456
            g.setColor(c.getCaretColor());
1457
            Rectangle caretBounds = caret.getCaretBounds();
1458
            switch (type) {
1459
                case THICK_LINE_CARET:
1460
                    g.fillRect(caretBounds.x, caretBounds.y, this.thickCaretWidth, caretBounds.height - 1);
1461
                    break;
1462
1463
                case THIN_LINE_CARET:
1464
                    int upperX = caret.getCaretBounds().x;
1465
                    g.drawLine((int) upperX, caret.getCaretBounds().y, caret.getCaretBounds().x,
1466
                            (caret.getCaretBounds().y + caret.getCaretBounds().height - 1));
1467
                    break;
1468
1469
                case BLOCK_CARET:
1470
                    // Use a CaretOverwriteModeHighlighting layer to paint the caret
1471
                    break;
1472
1473
                default:
1474
                    throw new IllegalStateException("Invalid caret type=" + type);
1475
            }
1476
        }
1477
    }
1478
1479
    void dispatchUpdate() {
1436
    void dispatchUpdate() {
1480
        JTextComponent c = component;
1437
        JTextComponent c = component;
1481
        if (c != null) {
1438
        if (c != null) {
Lines 1525-1635 Link Here
1525
            }
1482
            }
1526
            Document doc = c.getDocument();
1483
            Document doc = c.getDocument();
1527
            if (doc != null) {
1484
            if (doc != null) {
1528
                List<CaretInfo> sortedCarets = getSortedCarets();
1485
                LockedViewHierarchy lvh = ViewHierarchy.get(c).lock();
1529
                for (CaretInfo caret : sortedCarets) {
1486
                try {
1530
                    CaretItem caretItem = caret.getCaretItem();
1487
                    CaretItem lastCaretItem = getLastCaretItem();
1531
                    Rectangle oldCaretBounds = caretItem.getCaretBounds(); // no need to deep copy
1488
                    List<CaretInfo> sortedCarets = getSortedCarets();
1532
                    if (oldCaretBounds != null) {
1489
                    for (CaretInfo caret : sortedCarets) {
1533
                        c.repaint(oldCaretBounds);
1490
                        CaretItem caretItem = caret.getCaretItem();
1534
                    }
1491
                        if (caretItem.getAndClearUpdateCaretBounds()) {
1535
1492
                            Shape caretShape = lvh.modelToView(caretItem.getDot(), Position.Bias.Forward);
1536
                    // note - the order is important ! caret bounds must be updated even if the fold flag is true.
1493
                            if (caretShape != null) {
1537
                    if (updateCaretBounds(caretItem) || updateAfterFoldHierarchyChange) {
1494
                                Rectangle newCaretBounds = caretShape.getBounds();
1538
                        Rectangle scrollBounds = new Rectangle(caretItem.getCaretBounds());
1495
                                Rectangle oldCaretBounds = caretItem.setCaretBoundsWithRepaint(newCaretBounds, c);
1539
1496
                                // Only scroll the view for the LAST caret to be visible
1540
                        // Optimization to avoid extra repaint:
1497
                                if (scrollViewToCaret && caretItem == lastCaretItem) {
1541
                        // If the caret bounds were not yet assigned then attempt
1498
                                    Rectangle scrollBounds = newCaretBounds; // Must possibly be cloned upon change
1542
                        // to scroll the window so that there is an extra vertical space 
1499
                                    
1543
                        // for the possible horizontal scrollbar that may appear
1500
                                    // For null old bounds (likely at begining of component displayment) ensure that a possible
1544
                        // if the line-view creation process finds line-view that
1501
                                    // horizontal scrollbar would hide the caret so enlarge the scroll bounds by hscrollbar height.
1545
                        // is too wide and so the horizontal scrollbar will appear
1502
                                    if (oldCaretBounds == null) {
1546
                        // consuming an extra vertical space at the bottom.
1503
                                        Component viewport = c.getParent();
1547
                        if (oldCaretBounds == null) {
1504
                                        if (viewport instanceof JViewport) {
1548
                            Component viewport = c.getParent();
1505
                                            Component scrollPane = viewport.getParent();
1549
                            if (viewport instanceof JViewport) {
1506
                                            if (scrollPane instanceof JScrollPane) {
1550
                                Component scrollPane = viewport.getParent();
1507
                                                JScrollBar hScrollBar = ((JScrollPane) scrollPane).getHorizontalScrollBar();
1551
                                if (scrollPane instanceof JScrollPane) {
1508
                                                if (hScrollBar != null) {
1552
                                    JScrollBar hScrollBar = ((JScrollPane) scrollPane).getHorizontalScrollBar();
1509
                                                    int hScrollBarHeight = hScrollBar.getPreferredSize().height;
1553
                                    if (hScrollBar != null) {
1510
                                                    Dimension extentSize = ((JViewport) viewport).getExtentSize();
1554
                                        int hScrollBarHeight = hScrollBar.getPreferredSize().height;
1511
                                                    // If the extent size is high enough then extend
1555
                                        Dimension extentSize = ((JViewport) viewport).getExtentSize();
1512
                                                    // the scroll region by extra vertical space
1556
                                        // If the extent size is high enough then extend
1513
                                                    if (extentSize.height >= caretItem.getCaretBounds().height + hScrollBarHeight) {
1557
                                        // the scroll region by extra vertical space
1514
                                                        scrollBounds = new Rectangle(scrollBounds); // Clone
1558
                                        if (extentSize.height >= caretItem.getCaretBounds().height + hScrollBarHeight) {
1515
                                                        scrollBounds.height += hScrollBarHeight;
1559
                                            scrollBounds.height += hScrollBarHeight;
1516
                                                    }
1517
                                                }
1518
                                            }
1560
                                        }
1519
                                        }
1561
                                    }
1520
                                    }
1521
                                    
1522
                                    if (LOG.isLoggable(Level.FINER)) {
1523
                                        LOG.finer("Scrolling to: " + scrollBounds);
1524
                                    }
1525
                                    c.scrollRectToVisible(scrollBounds);
1562
                                }
1526
                                }
1563
                            }
1527
                            }
1564
                        }
1528
                        }
1565
1566
                        Rectangle visibleBounds = c.getVisibleRect();
1567
1568
                        // If folds have changed attempt to scroll the view so that 
1569
                        // relative caret's visual position gets retained
1570
                        // (the absolute position will change because of collapsed/expanded folds).
1571
                        boolean doScroll = scrollViewToCaret;
1572
                        boolean explicit = false;
1573
                        if (oldCaretBounds != null && (!scrollViewToCaret || updateAfterFoldHierarchyChange)) {
1574
                            int oldRelY = oldCaretBounds.y - visibleBounds.y;
1575
                            // Only fix if the caret is within visible bounds and the new x or y coord differs from the old one
1576
                            if (LOG.isLoggable(Level.FINER)) {
1577
                                LOG.log(Level.FINER, "oldCaretBounds: {0}, visibleBounds: {1}, caretBounds: {2}",
1578
                                        new Object[]{oldCaretBounds, visibleBounds, caretItem.getCaretBounds()});
1579
                            }
1580
                            if (oldRelY >= 0 && oldRelY < visibleBounds.height
1581
                                    && (oldCaretBounds.y != caretItem.getCaretBounds().y || oldCaretBounds.x != caretItem.getCaretBounds().x)) {
1582
                                doScroll = true; // Perform explicit scrolling
1583
                                explicit = true;
1584
                                int oldRelX = oldCaretBounds.x - visibleBounds.x;
1585
                                // Do not retain the horizontal caret bounds by scrolling
1586
                                // since many modifications do not explicitly say that they are typing modifications
1587
                                // and this would cause problems like #176268
1588
//                            scrollBounds.x = Math.max(caretBounds.x - oldRelX, 0);
1589
                                scrollBounds.y = Math.max(caretItem.getCaretBounds().y - oldRelY, 0);
1590
//                            scrollBounds.width = visibleBounds.width;
1591
                                scrollBounds.height = visibleBounds.height;
1592
                            }
1593
                        }
1594
1595
                        // Historically the caret is expected to appear
1596
                        // in the middle of the window if setDot() gets called
1597
                        // e.g. by double-clicking in Navigator.
1598
                        // If the caret bounds are more than a caret height below the present
1599
                        // visible view bounds (or above the view bounds)
1600
                        // then scroll the window so that the caret is in the middle
1601
                        // of the visible window to see the context around the caret.
1602
                        // This should work fine with PgUp/Down because these
1603
                        // scroll the view explicitly.
1604
                        if (scrollViewToCaret
1605
                                && !explicit
1606
                                && // #219580: if the preceding if-block computed new scrollBounds, it cannot be offset yet more
1607
                                /* # 70915 !updateAfterFoldHierarchyChange && */ (caretItem.getCaretBounds().y > visibleBounds.y + visibleBounds.height + caretItem.getCaretBounds().height
1608
                                || caretItem.getCaretBounds().y + caretItem.getCaretBounds().height < visibleBounds.y - caretItem.getCaretBounds().height)) {
1609
                            // Scroll into the middle
1610
                            scrollBounds.y -= (visibleBounds.height - caretItem.getCaretBounds().height) / 2;
1611
                            scrollBounds.height = visibleBounds.height;
1612
                        }
1613
                        if (LOG.isLoggable(Level.FINER)) {
1614
                            LOG.finer("Resetting fold flag, current: " + updateAfterFoldHierarchyChange);
1615
                        }
1616
                        updateAfterFoldHierarchyChange = false;
1617
1618
                        // Ensure that the viewport will be scrolled either to make the caret visible
1619
                        // or to retain cart's relative visual position against the begining of the viewport's visible rectangle.
1620
                        if (doScroll) {
1621
                            if (LOG.isLoggable(Level.FINER)) {
1622
                                LOG.finer("Scrolling to: " + scrollBounds);
1623
                            }
1624
                            c.scrollRectToVisible(scrollBounds);
1625
                            if (!c.getVisibleRect().intersects(scrollBounds)) {
1626
                                // HACK: see #219580: for some reason, the scrollRectToVisible may fail.
1627
                                c.scrollRectToVisible(scrollBounds);
1628
                            }
1629
                        }
1630
                        resetBlink();
1631
                        c.repaint(caretItem.getCaretBounds());
1632
                    }
1529
                    }
1530
                } finally {
1531
                    lvh.unlock();
1633
                }
1532
                }
1634
            }
1533
            }
1635
        }
1534
        }
Lines 1747-1767 Link Here
1747
    }
1646
    }
1748
1647
1749
    private String dumpVisibility() {
1648
    private String dumpVisibility() {
1750
        return "visible=" + isVisible() + ", blinkVisible=" + blinkVisible;
1649
        return "visible=" + isVisible() + ", showing=" + showing;
1751
    }
1650
    }
1752
1651
1753
    /*private*/ void resetBlink() {
1652
    /*private*/ void resetBlink() {
1754
        boolean visible = isVisible();
1653
        boolean visible = isVisible();
1755
        synchronized (listenerList) {
1654
        synchronized (listenerList) {
1756
            if (flasher != null) {
1655
            if (blinkTimer != null) {
1757
                flasher.stop();
1656
                blinkTimer.stop();
1758
                setBlinkVisible(true);
1657
                setShowing(true);
1658
                lastBlinkTime = System.currentTimeMillis();
1759
                if (visible) {
1659
                if (visible) {
1760
                    if (LOG.isLoggable(Level.FINER)){
1660
                    if (LOG.isLoggable(Level.FINER)){
1761
                        LOG.finer("Reset blinking (caret already visible)" + // NOI18N
1661
                        LOG.finer("Reset blinking (caret already visible)" + // NOI18N
1762
                                " - starting the caret blinking timer: " + dumpVisibility() + '\n'); // NOI18N
1662
                                " - starting the caret blinking timer: " + dumpVisibility() + '\n'); // NOI18N
1763
                    }
1663
                    }
1764
                    flasher.start();
1664
                    blinkTimer.start();
1765
                } else {
1665
                } else {
1766
                    if (LOG.isLoggable(Level.FINER)){
1666
                    if (LOG.isLoggable(Level.FINER)){
1767
                        LOG.finer("Reset blinking (caret not visible)" + // NOI18N
1667
                        LOG.finer("Reset blinking (caret not visible)" + // NOI18N
Lines 1771-1780 Link Here
1771
            }
1671
            }
1772
        }
1672
        }
1773
    }
1673
    }
1774
    
1674
1775
    /*private*/ void setBlinkVisible(boolean blinkVisible) {
1675
    /**
1676
     * Return true if the caret is visible and it should currently be painted on the screen.
1677
     * This flag is being toggled by caret blinking timer.
1678
     *
1679
     * @return true if caret is currently painted on the screen.
1680
     */
1681
    boolean isShowing() {
1682
        return showing;
1683
    }
1684
1685
    /**
1686
     * Set whether the carets should physically be showing on screen.
1687
     * <br>
1688
     * It's set to from true to false and vice versa by caret blinking timer.
1689
     *
1690
     * @param showing true if the carets should be showing on screen or false if not.
1691
     */
1692
    /*private*/ void setShowing(boolean showing) {
1776
        synchronized (listenerList) {
1693
        synchronized (listenerList) {
1777
            this.blinkVisible = blinkVisible;
1694
            this.showing = showing;
1778
        }
1695
        }
1779
        updateOverwriteModeLayer(false);
1696
        updateOverwriteModeLayer(false);
1780
    }
1697
    }
Lines 1785-1791 Link Here
1785
            CaretOverwriteModeHighlighting overwriteModeHighlighting = (CaretOverwriteModeHighlighting)
1702
            CaretOverwriteModeHighlighting overwriteModeHighlighting = (CaretOverwriteModeHighlighting)
1786
                    c.getClientProperty(CaretOverwriteModeHighlighting.class);
1703
                    c.getClientProperty(CaretOverwriteModeHighlighting.class);
1787
            if (overwriteModeHighlighting != null) {
1704
            if (overwriteModeHighlighting != null) {
1788
                overwriteModeHighlighting.setVisible(visible && blinkVisible);
1705
                overwriteModeHighlighting.setVisible(visible && showing);
1789
            }
1706
            }
1790
        }
1707
        }
1791
    }
1708
    }
Lines 1944-1958 Link Here
1944
        return false;
1861
        return false;
1945
    }
1862
    }
1946
    
1863
    
1947
    private void refresh() {
1948
        updateType();
1949
        SwingUtilities.invokeLater(new Runnable() {
1950
            public @Override void run() {
1951
                updateAllCaretsBounds(); // the line height etc. may have change
1952
            }
1953
        });
1954
    }
1955
    
1956
    private static String logMouseEvent(MouseEvent evt) {
1864
    private static String logMouseEvent(MouseEvent evt) {
1957
        return "x=" + evt.getX() + ", y=" + evt.getY() + ", clicks=" + evt.getClickCount() //NOI18N
1865
        return "x=" + evt.getX() + ", y=" + evt.getY() + ", clicks=" + evt.getClickCount() //NOI18N
1958
            + ", component=" + s2s(evt.getComponent()) //NOI18N
1866
            + ", component=" + s2s(evt.getComponent()) //NOI18N
Lines 1964-1976 Link Here
1964
    }
1872
    }
1965
1873
1966
    private final class ListenerImpl extends ComponentAdapter
1874
    private final class ListenerImpl extends ComponentAdapter
1967
    implements DocumentListener, AtomicLockListener, MouseListener, MouseMotionListener, FocusListener, ViewHierarchyListener,
1875
    implements ActionListener, DocumentListener, AtomicLockListener, MouseListener,
1968
            PropertyChangeListener, ActionListener, PreferenceChangeListener, KeyListener
1876
            MouseMotionListener, FocusListener, ViewHierarchyListener,
1877
            PropertyChangeListener, PreferenceChangeListener, KeyListener
1969
    {
1878
    {
1970
1879
1971
        ListenerImpl() {
1880
        ListenerImpl() {
1972
        }
1881
        }
1973
1882
1883
        @Override
1884
        public void actionPerformed(ActionEvent e) {
1885
            JTextComponent c = component;
1886
            if (c != null) {
1887
                setShowing(!showing);
1888
                List<CaretInfo> sortedCarets = getSortedCarets(); // TODO only repaint carets showing on screen
1889
                for (CaretInfo caret : sortedCarets) {
1890
                    CaretItem caretItem = caret.getCaretItem();
1891
                    if (caretItem.getCaretBounds() != null) {
1892
                        Rectangle repaintRect = caretItem.getCaretBounds();
1893
                        c.repaint(repaintRect);
1894
                    }
1895
                }
1896
                // Check if the system is responsive enough to blink at the current blink rate
1897
                long tm = System.currentTimeMillis();
1898
                if (tm - (blinkCurrentDelay + (blinkCurrentDelay >> 2)) > 0L) {
1899
                    
1900
                }
1901
            }
1902
        }
1903
        
1974
        public @Override void preferenceChange(PreferenceChangeEvent evt) {
1904
        public @Override void preferenceChange(PreferenceChangeEvent evt) {
1975
            String setingName = evt == null ? null : evt.getKey();
1905
            String setingName = evt == null ? null : evt.getKey();
1976
            if (setingName == null || SimpleValueNames.CARET_BLINK_RATE.equals(setingName)) {
1906
            if (setingName == null || SimpleValueNames.CARET_BLINK_RATE.equals(setingName)) {
Lines 1979-1985 Link Here
1979
                    rate = EditorPreferencesDefaults.defaultCaretBlinkRate;
1909
                    rate = EditorPreferencesDefaults.defaultCaretBlinkRate;
1980
                }
1910
                }
1981
                setBlinkRate(rate);
1911
                setBlinkRate(rate);
1982
                refresh();
1983
            }
1912
            }
1984
        }
1913
        }
1985
1914
Lines 2051-2075 Link Here
2051
            }
1980
            }
2052
        }
1981
        }
2053
1982
2054
        // ActionListener methods
2055
        /**
2056
         * Fired when blink timer fires
2057
         */
2058
        public @Override void actionPerformed(ActionEvent evt) {
2059
            JTextComponent c = component;
2060
            if (c != null) {
2061
                setBlinkVisible(!blinkVisible);
2062
                List<CaretInfo> sortedCarets = getSortedCarets(); // TODO only repaint carets showing on screen
2063
                for (CaretInfo caret : sortedCarets) {
2064
                    CaretItem caretItem = caret.getCaretItem();
2065
                    if (caretItem.getCaretBounds() != null) {
2066
                        Rectangle repaintRect = caretItem.getCaretBounds();
2067
                        c.repaint(repaintRect);
2068
                    }
2069
                }
2070
            }
2071
        }
2072
2073
        // DocumentListener methods
1983
        // DocumentListener methods
2074
        public @Override void insertUpdate(DocumentEvent evt) {
1984
        public @Override void insertUpdate(DocumentEvent evt) {
2075
            JTextComponent c = component;
1985
            JTextComponent c = component;
Lines 2078-2085 Link Here
2078
                int offset = evt.getOffset();
1988
                int offset = evt.getOffset();
2079
                final int endOffset = offset + evt.getLength();
1989
                final int endOffset = offset + evt.getLength();
2080
                if (offset == 0) {
1990
                if (offset == 0) {
2081
                    // Manually shift carets at offset zero
1991
                    // Manually shift carets at offset zero - do this always even when inside atomic lock
2082
                    runTransaction(CaretTransaction.RemoveType.DOCUMENT_INSERT_ZERO_OFFSET, endOffset, null, null);
1992
                    runTransaction(CaretTransaction.RemoveType.DOCUMENT_INSERT_ZERO_OFFSET, 0, null, null);
2083
                }
1993
                }
2084
                // [TODO] proper undo solution
1994
                // [TODO] proper undo solution
2085
                modified = true;
1995
                modified = true;
Lines 2241-2247 Link Here
2241
2151
2242
                case CHAR_SELECTION:
2152
                case CHAR_SELECTION:
2243
                    if (evt.isAltDown() && evt.isShiftDown()) {
2153
                    if (evt.isAltDown() && evt.isShiftDown()) {
2244
                        moveDotCaret(offset, getLastCaretItem());
2154
                        moveDot(offset);
2245
                    } else {
2155
                    } else {
2246
                        moveDot(offset); // Will do setDot() if no selection
2156
                        moveDot(offset); // Will do setDot() if no selection
2247
                        adjustRectangularSelectionMouseX(evt.getX(), evt.getY()); // also fires state change
2157
                        adjustRectangularSelectionMouseX(evt.getX(), evt.getY()); // also fires state change
Lines 2405-2411 Link Here
2405
2315
2406
                        case CHAR_SELECTION:
2316
                        case CHAR_SELECTION:
2407
                            if (evt.isAltDown() && evt.isShiftDown()) {
2317
                            if (evt.isAltDown() && evt.isShiftDown()) {
2408
                                moveDotCaret(offset, getLastCaretItem());
2318
                                moveDot(offset);
2409
                            } else {
2319
                            } else {
2410
                                moveDot(offset);
2320
                                moveDot(offset);
2411
                                adjustRectangularSelectionMouseX(evt.getX(), evt.getY());
2321
                                adjustRectangularSelectionMouseX(evt.getX(), evt.getY());
Lines 2566-2573 Link Here
2566
        @Override
2476
        @Override
2567
        public void keyReleased(KeyEvent e) {
2477
        public void keyReleased(KeyEvent e) {
2568
        }
2478
        }
2479
2480
    } // End of ListenerImpl class
2481
    
2482
    private final class Blinker implements Runnable {
2569
        
2483
        
2570
    } // End of ListenerImpl class
2484
        /**
2485
         * Fired when blink task gets executed.
2486
         */
2487
        @Override
2488
        public void run() {
2489
            JTextComponent c = component;
2490
            if (c != null) {
2491
                setShowing(!showing);
2492
                // Repaint all carets
2493
                List<CaretInfo> sortedCarets = getSortedCarets(); // TODO only repaint carets showing on screen
2494
                for (CaretInfo caret : sortedCarets) {
2495
                    CaretItem caretItem = caret.getCaretItem();
2496
                    if (caretItem.getCaretBounds() != null) {
2497
                        Rectangle repaintRect = caretItem.getCaretBounds();
2498
                        if (repaintRect != null) {
2499
                            c.repaint(repaintRect);
2500
                        }
2501
                    }
2502
                }
2503
            }
2504
        }
2505
2506
    }
2571
    
2507
    
2572
    
2508
    
2573
    private enum CaretType {
2509
    private enum CaretType {

Return to bug 258798