All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.editor.BaseCaret Maven / Gradle / Ivy

/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.editor;

import java.awt.Rectangle;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Font;
import java.awt.Point;
import java.awt.Component;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.FocusListener;
import java.awt.event.FocusEvent;
import java.awt.event.InputEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.io.IOException;
import javax.swing.Action;
import javax.swing.Timer;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.Caret;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.EventListenerList;
import javax.swing.text.AbstractDocument;
import javax.swing.text.Position;
import org.netbeans.api.editor.fold.Fold;
import org.netbeans.api.editor.fold.FoldHierarchy;
import org.netbeans.api.editor.fold.FoldHierarchyEvent;
import org.netbeans.api.editor.fold.FoldHierarchyListener;
import org.netbeans.api.editor.fold.FoldUtilities;

/**
* Caret implementation
*
* @author Miloslav Metelka
* @version 1.00
*/

public class BaseCaret extends Rectangle implements Caret, FocusListener,
MouseListener, MouseMotionListener, PropertyChangeListener,
DocumentListener, ActionListener, SettingsChangeListener,
AtomicLockListener, FoldHierarchyListener {

    /** Caret type representing block covering current character */
    public static final String BLOCK_CARET = "block-caret"; // NOI18N

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

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

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

    private static final boolean debugCaretFocusExtra
    = Boolean.getBoolean("netbeans.debug.editor.caret.focus.extra"); // NOI18N
    
    /** Component this caret is bound to */
    protected JTextComponent component;

    /** Position of the caret on the screen. This helps to compute
    * caret position on the next after jump.
    */
    Point magicCaretPosition;

    /** Draw mark designating the position of the caret.  */
    MarkFactory.DrawMark caretMark = new MarkFactory.CaretMark();

    /** Draw mark that supports caret mark in creating selection */
    MarkFactory.DrawMark selectionMark = new MarkFactory.DrawMark(
                                             DrawLayerFactory.CARET_LAYER_NAME, null);

    /** Is the caret visible */
    boolean visible;

    /** Caret is visible and the blink is visible. Both must be true
    * in order to show the caret.
    */
    boolean blinkVisible;

    /** Is the selection currently visible? */
    boolean selectionVisible;

    /** Listeners */
    protected EventListenerList listenerList = new EventListenerList();

    /** Timer used for blinking the caret */
    protected Timer flasher;

    /** Type of the caret */
    String type;

    /** Is the caret italic for italic fonts */
    boolean italic;

    private int xPoints[] = new int[4];
    private int yPoints[] = new int[4];
    private Action selectWordAction;
    private Action selectLineAction;

    /** Change event. Only one instance needed because it has only source property */
    protected ChangeEvent changeEvent;

    private static char emptyDotChar[] = { ' ' };

    /** Dot array of one character under caret */
    protected char dotChar[] = emptyDotChar;

    private boolean overwriteMode;

    /** Remembering document on which caret listens avoids
    * duplicate listener addition to SwingPropertyChangeSupport
    * due to the bug 4200280
    */
    private BaseDocument listenDoc;

    /** Font of the text underlying the caret. It can be used
    * in caret painting.
    */
    protected Font afterCaretFont;

    /** Font of the text right before the caret */
    protected Font beforeCaretFont;

    /** Foreground color of the text underlying the caret. It can be used
    * in caret painting.
    */
    protected Color textForeColor;

    /** Background color of the text underlying the caret. It can be used
    * in caret painting.
    */
    protected Color textBackColor;

    private transient FocusListener focusListener;

    private transient boolean nextPaintUpdate;
    private transient Rectangle nextPaintScrollRect;
    private transient int nextPaintScrollPolicy;
    
    /** Whether the text is being modified under atomic lock.
     * If so just one caret change is fired at the end of all modifications.
     */
    private transient boolean inAtomicLock;
    
    /** Helps to check whether there was modification performed
     * and so the caret change needs to be fired.
     */
    private transient boolean modified;
    
    /** Whether there was an undo done in the modification and the offset of the modification */
    private transient int undoOffset = -1;
    
    /** is jdk14 version running? */
    private boolean jdk14;
    private static final Class[] EMPTY_CLASS_ARRAY = new Class[] {};
    private static final Object[] EMPTY_OBJECT_ARRAY = new Object[] {};

    static final long serialVersionUID =-9113841520331402768L;
 
    public BaseCaret() {
        Settings.addSettingsChangeListener(this);
        String specVer = System.getProperty("java.specification.version"); //NOI18N
        jdk14 = specVer!=null && specVer.startsWith("1.4"); //NOI18N
    }

    /** Called when settings were changed. The method is called
    * also in constructor, so the code must count with the evt being null.
    */
    public void settingsChange(SettingsChangeEvent evt) {
        if( evt != null && SettingsNames.CARET_BLINK_RATE.equals( evt.getSettingName() ) ) {
            
            JTextComponent c = component;
            if (c == null) return;
            if (evt.getKitClass() != Utilities.getKitClass(c)) return;
            
            Object value = evt.getNewValue();
            if( value instanceof Integer ) {
                setBlinkRate( ((Integer)value).intValue() );
            }
        }
        updateType();
    }

    void updateType() {
        JTextComponent c = component;
        if (c != null) {
            Class kitClass = Utilities.getKitClass(c);
            if (kitClass==null) return;
            String newType;
            boolean newItalic;
            Color caretColor;
            if (overwriteMode) {
                newType = SettingsUtil.getString(kitClass,
                                                 SettingsNames.CARET_TYPE_OVERWRITE_MODE, LINE_CARET);
                newItalic = SettingsUtil.getBoolean(kitClass,
                                                    SettingsNames.CARET_ITALIC_OVERWRITE_MODE, false);
                caretColor = getColor( kitClass, SettingsNames.CARET_COLOR_OVERWRITE_MODE,
                    SettingsDefaults.defaultCaretColorOvwerwriteMode );
                
            } else { // insert mode
                newType = SettingsUtil.getString(kitClass,
                                                 SettingsNames.CARET_TYPE_INSERT_MODE, LINE_CARET);
                newItalic = SettingsUtil.getBoolean(kitClass,
                                                    SettingsNames.CARET_ITALIC_INSERT_MODE, false);
                caretColor = getColor( kitClass, SettingsNames.CARET_COLOR_INSERT_MODE,
                    SettingsDefaults.defaultCaretColorInsertMode );
            }

            this.type = newType;
            this.italic = newItalic;
            c.setCaretColor(caretColor);
            if (debugCaretFocusExtra){
                System.err.println("Updating caret color:"+caretColor);
            }

            dispatchUpdate();
        }
    }

    private static Color getColor( Class kitClass, String settingName,
                           Color defaultValue) {
        Object value = Settings.getValue(kitClass, settingName);
        return (value instanceof Color) ? (Color)value : defaultValue;
    }


    /** Called when UI is being installed into JTextComponent */
    public void install(JTextComponent c) {
        component = c;
        blinkVisible = true;
        component.addPropertyChangeListener(this);
        focusListener = new FocusHandler(this);
        component.addFocusListener(focusListener);
        component.addMouseListener(this);
        component.addMouseMotionListener(this);

        EditorUI editorUI = Utilities.getEditorUI(component);
        editorUI.addLayer(new DrawLayerFactory.CaretLayer(),
                DrawLayerFactory.CARET_LAYER_VISIBILITY);
        caretMark.setEditorUI(editorUI);
        selectionMark.setEditorUI(editorUI);
        editorUI.addPropertyChangeListener( this );
        
        FoldHierarchy hierarchy = FoldHierarchy.get(c);
        if (hierarchy != null) {
            hierarchy.addFoldHierarchyListener(this);
        }
        
        BaseDocument doc = Utilities.getDocument(c);
        if (doc != null) {
            modelChanged(null, doc);
        }

        if (component.hasFocus()) {
            focusGained(null); // emulate focus gained
            if (debugCaretFocus || debugCaretFocusExtra) {
                System.err.println("Component has focus, calling focusGained() on doc="
                    + component.getDocument().getProperty(Document.TitleProperty));
            }

        }
    }

    /** Called when UI is being removed from JTextComponent */
    public void deinstall(JTextComponent c) {
        component = null; // invalidate

        if (flasher != null) {
            setBlinkRate(0);
        }

        Utilities.getEditorUI(c).removeLayer(DrawLayerFactory.CARET_LAYER_NAME);

        c.removeMouseMotionListener(this);
        c.removeMouseListener(this);
        if (focusListener != null) {
            c.removeFocusListener(focusListener);
            focusListener = null;
        }
        c.removePropertyChangeListener(this);
        
        FoldHierarchy hierarchy = FoldHierarchy.get(c);
        if (hierarchy != null) {
            hierarchy.removeFoldHierarchyListener(this);
        }
        
        modelChanged(listenDoc, null);
    }

    protected void modelChanged(BaseDocument oldDoc, BaseDocument newDoc) {
        // [PENDING] !!! this body looks strange because of the bug 4200280
        if (oldDoc != null && listenDoc == oldDoc) {
            oldDoc.removeDocumentListener(this);
            oldDoc.removeAtomicLockListener(this);

            try {
                caretMark.remove();
                selectionMark.remove();
            } catch (InvalidMarkException e) {
                Utilities.annotateLoggable(e);
            }

            listenDoc = null;
        }


        if (newDoc != null) {
            if (listenDoc != null) {
                // deinstall from the listenDoc first
                modelChanged(listenDoc, null);
            }

            newDoc.addDocumentListener(this);
            listenDoc = newDoc;
            newDoc.addAtomicLockListener(this);

            try {
                Utilities.insertMark(newDoc, caretMark, 0);
                Utilities.insertMark(newDoc, selectionMark, 0);
            } catch (InvalidMarkException e) {
                Utilities.annotateLoggable(e);
            } catch (BadLocationException e) {
                Utilities.annotateLoggable(e);
            }

            settingsChange(null); // update settings

            SwingUtilities.invokeLater(
                new Runnable() {
                    public void run() {
                        updateType();
                    }
                }
            );

        }
    }

    /** Renders the caret */
    public void paint(Graphics g) {
        JTextComponent c = component;
        if (c == null) return;
        EditorUI editorUI = Utilities.getEditorUI(c);
        /* Fix of #8123 - besides nextPaintUpdate flag
         * it's also necessary to check the isFontsInited() here.
         */
        if (nextPaintUpdate && editorUI.isFontsInited()) { // need to update caret first
            // running in AWT -> no dispatching
            nextPaintUpdate = false; // no further updating
            update(nextPaintScrollRect, nextPaintScrollPolicy);
            
            // fix for issue #13049
            nextPaintScrollRect = null;
            
        }

        if (visible && blinkVisible) {
            paintCustomCaret(g);
        }
    }

    protected void paintCustomCaret(Graphics g) {
        JTextComponent c = component;
        if (c != null) {
            EditorUI editorUI = Utilities.getEditorUI(c);
            if (THIN_LINE_CARET.equals(type)) { // thin line caret
                g.setColor(c.getCaretColor());
                int upperX = x;
                if (beforeCaretFont != null && beforeCaretFont.isItalic() && italic) {
                    upperX += Math.tan(beforeCaretFont.getItalicAngle()) * height;
                }
                g.drawLine((int)upperX, y, x, (y + height - 1));

            } else if (BLOCK_CARET.equals(type)) { // block caret
                g.setColor(c.getCaretColor());
                if (afterCaretFont != null) g.setFont(afterCaretFont);
                if (afterCaretFont != null && afterCaretFont.isItalic() && italic) { // paint italic caret
                    int upperX = (int)(x + Math.tan(afterCaretFont.getItalicAngle()) * height);
                    xPoints[0] = upperX; yPoints[0] = y;
                    xPoints[1] = upperX + width; yPoints[1] = y;
                    xPoints[2] = x + width; yPoints[2] = y + height - 1;
                    xPoints[3] = x; yPoints[3] = y + height - 1;
                    g.fillPolygon(xPoints, yPoints, 4);

                } else { // paint non-italic caret
                    g.fillRect(x, y, width, height);
                }
                
                if (!Character.isWhitespace(dotChar[0])) {
                    g.setColor(Color.white);
                    // int ascent = FontMetricsCache.getFontMetrics(afterCaretFont, c).getAscent();
                    g.drawChars(dotChar, 0, 1, x, y + editorUI.getLineAscent());
                }

            } else { // two dot line caret
                g.setColor(c.getCaretColor());
                int blkWidth = 2;
                if (beforeCaretFont != null && beforeCaretFont.isItalic() && italic) {
                    int upperX = (int)(x + Math.tan(beforeCaretFont.getItalicAngle()) * height);
                    xPoints[0] = upperX; yPoints[0] = y;
                    xPoints[1] = upperX + blkWidth; yPoints[1] = y;
                    xPoints[2] = x + blkWidth; yPoints[2] = y + height - 1;
                    xPoints[3] = x; yPoints[3] = y + height - 1;
                    g.fillPolygon(xPoints, yPoints, 4);
                } else { // paint non-italic caret
                    g.fillRect(x, y, blkWidth, height - 1);
                }
            }
        }
    }

    /** Update the caret's visual position */
    void dispatchUpdate() {
        dispatchUpdate(null, EditorUI.SCROLL_MOVE);
    }

    void dispatchUpdate(final Rectangle scrollRect, final int scrollPolicy) {
        JTextComponent c = component;
        if (c == null) return;
        EditorUI editorUI = Utilities.getEditorUI(c);
        if (!editorUI.isFontsInited()) { // fonts not yet initialized
            if (scrollRect != null) { // do not hide the "really" scrolling requests
                nextPaintScrollRect = scrollRect;
            }
            nextPaintScrollPolicy = scrollPolicy;
            nextPaintUpdate = true;
            return;
        }
        
        /* part of fix of #18860 - Using runInEventDispatchThread() in AWT thread
         * means that the code is executed immediately which can lead
         * to problems once the insert/remove in document is performed
         * because the update() uses views to find out the visual position
         * and if the views doc listener is added AFTER the caret's listener
         * then the views are not updated yet. Using SwingUtilities.invokeLater()
         * should solve the problem although the view extent could flip
         * once the extent would be explicitely scrolled to area that does
         * not cover the caret's rectangle. It needs to be tested
         * so that it does not happen.
         */
//        Utilities.runInEventDispatchThread(
        SwingUtilities.invokeLater(
            new Runnable() {
                public void run() {
                    JTextComponent c2 = component;
                    if (c2 != null) {
                        BaseDocument doc = Utilities.getDocument(c2);
                        if (doc != null) {
                            doc.readLock();
                            try {
                                update(scrollRect, scrollPolicy);
                            } finally {
                                doc.readUnlock();
                            }
                        }
                    }

                }
            }
        );
    }

    /** Update the caret. The document is read-locked while calling this method.
     * @param scrollRect rectangle that should be visible after the updating
     *   of the caret. It can be null to do no scrolling (only update caret)
     *   or it can be caret rectangle to update the caret and make it visible
     *   or some other rectangle to guarantee that the rectangle will be visible.
     * @param scrollPolicy scrolling policy as defined in EditorUI. It has
     *   no meaning if scrollRect is null.
     */
    protected void update(Rectangle scrollRect, int scrollPolicy) {
        JTextComponent c = component;
        if (c != null) {
            BaseTextUI ui = (BaseTextUI)c.getUI();
            EditorUI editorUI = ui.getEditorUI();
            BaseDocument doc = Utilities.getDocument(c);
            if (doc != null) {
                Rectangle oldCaretRect = new Rectangle(this);
                if (italic) { // caret is italic - add char height to the width of the rect
                    oldCaretRect.width += oldCaretRect.height;
                }

                int dot = getDot();

                try {
                    //ui.modelToViewDG(dot, caretDG);
                    Rectangle r = component.getUI().modelToView(component, dot, Position.Bias.Forward);
                    if (r!=null){
                        if (isVisible()){
                            c.repaint(new Rectangle(this));
                        }
                        this.x = r.x;
                        this.y = r.y;
                        this.width = r.width;
                        this.height = r.height;
                    }
                } catch (BadLocationException e) {
                    Utilities.annotateLoggable(e);
                }
                
                resetBlink();
                
                if (scrollRect == null
                    || !editorUI.scrollRectToVisibleFragile(scrollRect, scrollPolicy)
                ) {
                    oldCaretRect.add(this); // adds the NEW caret rect - important!
                    c.repaint(oldCaretRect);
                }
            }
        }
    }
    
    private void updateSystemSelection() {
        if (!jdk14){
            return;
        }

        if (getDot() != getMark() && component != null) {
            Clipboard clip = getSystemSelection();
            
            if (clip != null) {
                clip.setContents(new java.awt.datatransfer.StringSelection(component.getSelectedText()), null);
            }
        }
    }

    private Clipboard getSystemSelection() {
        if (!jdk14){
            return null;
        }

        try {
            // XXX remove reflection after NB will be compiled on jdk 1.4
            java.lang.reflect.Method m;
            m = component.getToolkit().getClass().getMethod("getSystemSelection", EMPTY_CLASS_ARRAY); //NOI18N
            return (Clipboard)(m.invoke(component.getToolkit(), EMPTY_OBJECT_ARRAY));
        } catch (Exception he) {
        }
        return null;
    }
    
    /** Redefine to Object.equals() to prevent defaulting to Rectangle.equals()
    * which would cause incorrect firing
    */
    public boolean equals(Object o) {
        return (this == o);
    }

    /** Adds listener to track when caret position was changed */
    public void addChangeListener(ChangeListener l) {
        listenerList.add(ChangeListener.class, l);
    }

    /** Removes listeners to caret position changes */
    public void removeChangeListener(ChangeListener l) {
        listenerList.remove(ChangeListener.class, l);
    }

    /** Notifies listeners that caret position has changed */
    protected void fireStateChanged() {
        // Fix of #24336 - always do in AWT thread
        Utilities.runInEventDispatchThread(new Runnable() {
            public void run() {
                Object listeners[] = listenerList.getListenerList();
                for (int i = listeners.length - 2; i >= 0 ; i -= 2) {
                    if (listeners[i] == ChangeListener.class) {
                        if (changeEvent == null) {
                            changeEvent = new ChangeEvent(BaseCaret.this);
                        }
                        ((ChangeListener)listeners[i + 1]).stateChanged(changeEvent);
                    }
                }
            }
        });
        updateSystemSelection();
    }

    /** Is the caret currently visible */
    public final boolean isVisible() {
        return visible;
    }

    protected void setVisibleImpl(boolean v) {
        synchronized (this) {
            Timer t = flasher;
            if (t != null) {
                if (visible) {
                    t.stop();
                }
                if (v) {
                    if (debugCaretFocusExtra){
                        System.err.println("starting the caret blinking timer: visible="+visible+", blinkVisible="+blinkVisible);
                    }
                    t.start();
                } else {
                    if (debugCaretFocusExtra){
                        System.err.println("stopping the caret blinking timer");
                    }
                    t.stop();
                }
            }
            visible = v;
        }
        JTextComponent c = component;
        if (c != null) {
            Rectangle repaintRect = this;
            if (italic) {
                repaintRect = new Rectangle(this);
                repaintRect.width += repaintRect.height;
            }
            c.repaint(repaintRect);
        }
    }

    synchronized void resetBlink() {
        Timer t = flasher;
        if (t != null) {
            t.stop();
            blinkVisible = true;
            if (isVisible()) {
                if (debugCaretFocusExtra){
                    System.err.println("Reset blinking (caret already visible) - starting the caret blinking timer: visible="+visible+", blinkVisible="+blinkVisible);
                }
               t.start();
            }else{
                if (debugCaretFocusExtra){
                    System.err.println("Reset blinking (caret not visible) - caret blinking timer couldn't be started: visible="+visible+", blinkVisible="+blinkVisible);
                }
            }
        }
    }

    /** Sets the caret visibility */
    public void setVisible(final boolean v) {
        SwingUtilities.invokeLater(
            new Runnable() {
                public void run() {
                    setVisibleImpl(v);
                }
            }
        );
    }

    /** Is the selection visible? */
    public final boolean isSelectionVisible() {
        return selectionVisible;
    }

    /** Sets the selection visibility */
    public void setSelectionVisible(boolean v) {
        if (selectionVisible == v) {
            return;
        }
        JTextComponent c = component;
        if (c != null) {
            selectionVisible = v;
            if (selectionVisible) {
                int caretPos = getDot();
                int selPos = getMark();
                boolean selMarkFirst = (selPos < caretPos);
                selectionMark.activateLayer = selMarkFirst;
                caretMark.activateLayer = !selMarkFirst && !(selPos == caretPos);
            } else { // make selection invisible
                caretMark.activateLayer = false;
                selectionMark.activateLayer = false;
            }

            // repaint the block
            BaseTextUI ui = (BaseTextUI)c.getUI();
            try {
                ui.getEditorUI().repaintBlock(caretMark.getOffset(), selectionMark.getOffset());
            } catch (BadLocationException e) {
                Utilities.annotateLoggable(e);
            } catch (InvalidMarkException e) {
                Utilities.annotateLoggable(e);
            }

        }
    }

    /** Saves the current caret position.  This is used when
    * caret up or down actions occur, moving between lines
    * that have uneven end positions.
    *
    * @param p  the Point to use for the saved position
    */
    public void setMagicCaretPosition(Point p) {
        magicCaretPosition = p;
    }

    /** Get position used to mark begining of the selected block */
    public final Point getMagicCaretPosition() {
        return magicCaretPosition;
    }

    /** Sets the caret blink rate.
    * @param rate blink rate in milliseconds, 0 means no blink
    */
    public synchronized void setBlinkRate(int rate) {
        if (flasher == null && rate > 0) {
            flasher = new Timer(rate, new WeakTimerListener(this));
        }
        if (flasher != null) {
            if (rate > 0) {
                if (flasher.getDelay() != rate) {
                    if (debugCaretFocusExtra){
                        System.err.println("blink rate:"+rate);
                    }
                    flasher.setDelay(rate);
                }
            } else { // zero rate - don't blink
                if (debugCaretFocusExtra){
                    System.err.println("zero rate - don't blink");
                    System.err.println("setting blinkVisible to true and disabling timer");
                }
                flasher.stop();
                flasher.removeActionListener(this);
                flasher = null;
                blinkVisible = true;
            }
        }
    }

    /** Returns blink rate of the caret or 0 if caret doesn't blink */
    public synchronized int getBlinkRate() {
        return (flasher != null) ? flasher.getDelay() : 0;
    }

    /** Gets the current position of the caret */
    public int getDot() {
        if (component != null) {
            try {
                return caretMark.getOffset();
            } catch (InvalidMarkException e) {
            }
        }
        return 0;
    }

    /** Gets the current position of the selection mark.
    * If there's a selection this position will be different
    * from the caret position.
    */
    public int getMark() {
        if (component != null) {
            if (selectionVisible) {
                try {
                    return selectionMark.getOffset();
                } catch (InvalidMarkException e) {
                }
            } else { // selection not visible
                return getDot(); // must return same position as dot
            }
        }
        return 0;
    }

    public void setDot(int offset) {
        setDot(offset, this, EditorUI.SCROLL_DEFAULT);
    }

    public void setDot(int offset, boolean expandFold) {
        setDot(offset, this, EditorUI.SCROLL_DEFAULT, expandFold);
    }
    
    
    /** Sets the caret position to some position. This
     * causes removal of the active selection. If expandFold set to true
     * fold containing offset position will be expanded.
     */
    
    public void setDot(int offset, Rectangle scrollRect, int scrollPolicy, boolean expandFold) {
        JTextComponent c = component;
        if (c != null) {
            setSelectionVisible(false);
            BaseDocument doc = (BaseDocument)c.getDocument();
            boolean dotChanged = false;
            doc.readLock();
            try {
                if (doc != null && offset >= 0 && offset <= doc.getLength()) {
                    dotChanged = true;
                    try {
                        Utilities.moveMark(doc, caretMark, offset);
                        // Unfold fold 
                        FoldHierarchy hierarchy = FoldHierarchy.get(c);
                        hierarchy.lock();
                        try {
                            Fold collapsed = FoldUtilities.findCollapsedFold(hierarchy, offset, offset);
                            if (expandFold && collapsed != null && collapsed.getStartOffset() < offset &&
                                collapsed.getEndOffset() > offset) {
                                hierarchy.expand(collapsed);
                            }
                        } finally {
                            hierarchy.unlock();
                        }
                    } catch (BadLocationException e) {
                        throw new IllegalStateException(e.toString());
                        // setting the caret to wrong position leaves it at current position
                    } catch (InvalidMarkException e) {
                        throw new IllegalStateException(e.toString());
                        // Caret not installed or inside the initial-read
                    }
                }
            } finally {
                doc.readUnlock();
            }
            
            if (dotChanged) {
                fireStateChanged();
                dispatchUpdate(scrollRect, scrollPolicy);
            }
        }
    }
    
    /** Sets the caret position to some position. This
    * causes removal of the active selection.
    */
    public void setDot(int offset, Rectangle scrollRect, int scrollPolicy) {
        setDot(offset, scrollRect, scrollPolicy, true);
    }

    public void moveDot(int offset) {
        moveDot(offset, this, EditorUI.SCROLL_MOVE);
    }

    /** Makes selection by moving dot but leaving mark */
    public void moveDot(int offset, Rectangle scrollRect, int scrollPolicy) {
        JTextComponent c = component;
        if (c != null) {
            BaseDocument doc = (BaseDocument)c.getDocument();
            if (doc != null && offset >= 0 && offset <= doc.getLength()) {
                try {
                    int oldCaretPos = getDot();
                    if (offset == oldCaretPos) { // no change
                        return;
                    }
                    int selPos; // current position of selection mark

                    if (selectionVisible) {
                        selPos = selectionMark.getOffset();
                    } else {
                        Utilities.moveMark(doc, selectionMark, oldCaretPos);
                        selPos = oldCaretPos;
                    }

                    Utilities.moveMark(doc, caretMark, offset);
                    if (selectionVisible) { // selection already visible
                        boolean selMarkFirst = (selPos < offset);
                        selectionMark.activateLayer = selMarkFirst;
                        caretMark.activateLayer = !selMarkFirst && !(selPos == offset);
                        Utilities.getEditorUI(c).repaintBlock(oldCaretPos, offset);
                        if (selPos == offset) { // same positions -> invisible selection
                            setSelectionVisible(false);
                        }

                    } else { // selection not yet visible
                        setSelectionVisible(true);
                    }
                } catch (BadLocationException e) {
                    throw new IllegalStateException(e.toString());
                    // position is incorrect
                } catch (InvalidMarkException e) {
                    throw new IllegalStateException(e.toString());
                }
            }
            fireStateChanged();
            dispatchUpdate(scrollRect, scrollPolicy);
        }
    }

    // DocumentListener methods
    public void insertUpdate(DocumentEvent evt) {
        JTextComponent c = component;
        if (c != null) {
            BaseDocument doc = (BaseDocument)component.getDocument();
            BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
            if ((bevt.isInUndo() || bevt.isInRedo())
                    && component == Utilities.getLastActiveComponent()
               ) {
                // in undo mode and current component
                undoOffset = evt.getOffset() + evt.getLength();
            } else {
                undoOffset = -1;
            }

            modified = true;

            modifiedUpdate();
        }
    }

    public void removeUpdate(DocumentEvent evt) {
        JTextComponent c = component;
        if (c != null) {
            BaseDocument doc = (BaseDocument)c.getDocument();
            // make selection invisible if removal shrinked block to zero size
            if (selectionVisible && (getDot() == getMark())) {
                setSelectionVisible(false);
            }
            
            BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
            if ((bevt.isInUndo() || bevt.isInRedo())
                && c == Utilities.getLastActiveComponent()
            ) {
                // in undo mode and current component
                undoOffset = evt.getOffset();
            } else {
                undoOffset = -1;
            }

            modified = true;
            
            modifiedUpdate();
        }
    }
    
    private void modifiedUpdate() {
        if (!inAtomicLock) {
            JTextComponent c = component;
            if (modified && c != null) {
                if (undoOffset >= 0) { // last modification was undo => set the dot to undoOffset
                    setDot(undoOffset);
                } else { // last modification was not undo
                    fireStateChanged();
                    // Scroll to caret only for component with focus
                    dispatchUpdate(c.hasFocus() ? this : null, EditorUI.SCROLL_MOVE);
                }
                modified = false;
            }
        }
    }
    
    public void atomicLock(AtomicLockEvent evt) {
        inAtomicLock = true;
    }
    
    public void atomicUnlock(AtomicLockEvent evt) {
        inAtomicLock = false;
        modifiedUpdate();
    }
    
    public void changedUpdate(DocumentEvent evt) {
        dispatchUpdate();
    }

    // FocusListener methods
    public void focusGained(FocusEvent evt) {
        if (debugCaretFocusExtra){
            System.err.println("");
        }
        if (debugCaretFocus || debugCaretFocusExtra) {
            System.err.println("BaseCaret.focusGained() in doc="
                               + component.getDocument().getProperty(Document.TitleProperty));
        }

        JTextComponent c = component;
        if (c != null) {
            updateType();
            if (debugCaretFocusExtra){
                System.err.println("going to set caret visible to: "+c.isEnabled());
            }
            setVisible(c.isEnabled()); // invisible caret if disabled
        }else{
            if (debugCaretFocusExtra){
                System.err.println("component is null, caret will not be dislayed");
            }
        }
    }

    public void focusLost(FocusEvent evt) {
        if (debugCaretFocusExtra){
            System.err.println("");
        }
        if (debugCaretFocus || debugCaretFocusExtra) {
            System.err.println("BaseCaret.focusLost() in doc="
                               + component.getDocument().getProperty(Document.TitleProperty));
        }

        // XXX #17959. Eliminate "strange" focus lost event.
        SwingUtilities.invokeLater(new Runnable(){
            public void run(){
                Component c = component;
                if(c != null) {
                    java.awt.Window window = SwingUtilities.windowForComponent(c);
                    if(window != null) {
                        if(window.getFocusOwner() == c) {
                            if (debugCaretFocusExtra){
                                System.err.println("caret will remain visible, windowForComponent(c).getFocusOwner is the same as the c");
                                System.err.println("window.getFocusOwner:"+window.getFocusOwner());
                                System.err.println("component:"+c);
                            }
                            return;
                        }
                    }
                }
                if (debugCaretFocusExtra) {
                    System.err.println("going to set caret visible to: false");
                }
                setVisible(false);
            }
        });
    }

    // MouseListener methods
    public void mouseClicked(MouseEvent evt) {
        JTextComponent c = component;
        if (c != null) {
            if (SwingUtilities.isLeftMouseButton(evt)) {
                if (evt.getClickCount() == 2) {
                    BaseTextUI ui = (BaseTextUI)c.getUI();
                    // Expand fold if offset is in collapsed fold
                    int offset = ui.viewToModel(c,
                                    evt.getX(), evt.getY());
                    FoldHierarchy hierarchy = FoldHierarchy.get(c);
                    Document doc = c.getDocument();
                    if (doc instanceof AbstractDocument) {
                        AbstractDocument adoc = (AbstractDocument)doc;
                        adoc.readLock();
                        try {
                            hierarchy.lock();
                            try {
                                Fold collapsed = FoldUtilities.findCollapsedFold(
                                    hierarchy, offset, offset);
                                if (collapsed != null && collapsed.getStartOffset() <= offset &&
                                    collapsed.getEndOffset() >= offset) {
                                    hierarchy.expand(collapsed);
                                } else {
                                    if (selectWordAction == null) {
                                        selectWordAction = ((BaseKit)ui.getEditorKit(
                                                                c)).getActionByName(BaseKit.selectWordAction);
                                    }
                                    selectWordAction.actionPerformed(null);
                                }
                            } finally {
                                hierarchy.unlock();
                            }
                        } finally {
                            adoc.readUnlock();
                        }
                    }
                } else if (evt.getClickCount() == 3) {
                    if (selectLineAction == null) {
                        BaseTextUI ui = (BaseTextUI)c.getUI();
                        selectLineAction = ((BaseKit)ui.getEditorKit(
                                                c)).getActionByName(BaseKit.selectLineAction);
                    }
                    selectLineAction.actionPerformed(null);
                }
            } else if (SwingUtilities.isMiddleMouseButton(evt)){
		if (evt.getClickCount() == 1) {
		    if (c == null) return;
                    Toolkit tk = c.getToolkit();
                    Clipboard buffer = getSystemSelection();
                    
                    if (buffer == null) return;

                    Transferable trans = buffer.getContents(null);
                    if (trans == null) return;

                    BaseDocument doc = (BaseDocument)c.getDocument();
                    if (doc == null) return;
                    
                    int offset = ((BaseTextUI)c.getUI()).viewToModel(c,
                                    evt.getX(), evt.getY());

                    try{
                        String pastingString = (String)trans.getTransferData(DataFlavor.stringFlavor);
                        if (pastingString == null) return;
                         try {
                             doc.atomicLock();
                             try {
                                 doc.insertString(offset, pastingString, null);
                                 setDot(offset+pastingString.length());
                             } finally {
                                 doc.atomicUnlock();
                             }
                         } catch( BadLocationException exc ) {
                         }
                    }catch(UnsupportedFlavorException ufe){
                    }catch(IOException ioe){
                    }
		}
            }
        }
    }

    public void mousePressed(MouseEvent evt) {
        JTextComponent c = component;
        if (c != null) {
            Utilities.getEditorUI(c).getWordMatch().clear(); // [PENDING] should be done cleanly

            // Position the cursor at the appropriate place in the document
            if ((SwingUtilities.isLeftMouseButton(evt) && 
                 (evt.getModifiers() & (InputEvent.META_MASK|InputEvent.ALT_MASK)) == 0) ||
               !isSelectionVisible()) {
                int offset = ((BaseTextUI)c.getUI()).viewToModel(c,
                          evt.getX(), evt.getY());
                if (offset >= 0) {
                    if ((evt.getModifiers() & InputEvent.SHIFT_MASK) != 0) {
                        moveDot(offset);
                    } else {
                        setDot(offset);
                    }
                    setMagicCaretPosition(null);
                }
                if (c.isEnabled()) {
                    c.requestFocus();
                }
            }
        }
    }

    public void mouseReleased(MouseEvent evt) {
    }

    public void mouseEntered(MouseEvent evt) {
    }

    public void mouseExited(MouseEvent evt) {
    }

    // MouseMotionListener methods
    public void mouseDragged(MouseEvent evt) {
        JTextComponent c = component;
        if (SwingUtilities.isLeftMouseButton(evt)) {
            if (c != null) {
                int offset = ((BaseTextUI)c.getUI()).viewToModel(c,
                          evt.getX(), evt.getY());
                // fix for #15204
                if (offset == -1)
                    offset = 0;
                // fix of #22846
                if (offset >= 0 && (evt.getModifiers() & InputEvent.SHIFT_MASK) == 0)
                    moveDot(offset);
            }
        }
    }

    public void mouseMoved(MouseEvent evt) {
    }

    // PropertyChangeListener methods
    public void propertyChange(PropertyChangeEvent evt) {
        String propName = evt.getPropertyName();
        if ("document".equals(propName)) { // NOI18N
            BaseDocument oldDoc = (evt.getOldValue() instanceof BaseDocument)
                                  ? (BaseDocument)evt.getOldValue() : null;
            BaseDocument newDoc = (evt.getNewValue() instanceof BaseDocument)
                                  ? (BaseDocument)evt.getNewValue() : null;
            modelChanged(oldDoc, newDoc);
        } else if (EditorUI.OVERWRITE_MODE_PROPERTY.equals(propName)) {
            Boolean b = (Boolean)evt.getNewValue();
            overwriteMode = (b != null) ? b.booleanValue() : false;
            updateType();
        }
    }

    // ActionListener methods
    /** Fired when blink timer fires */
    public void actionPerformed(ActionEvent evt) {
        JTextComponent c = component;
        if (c != null) {
            blinkVisible = !blinkVisible;
            Rectangle repaintRect = this;
            if (italic) {
                repaintRect = new Rectangle(this);
                repaintRect.width += repaintRect.height;
            }
            c.repaint(repaintRect);
        }
    }

    public void foldHierarchyChanged(FoldHierarchyEvent evt) {
        foldHierarchyUpdate((FoldHierarchy)evt.getSource());
    }    

    private void foldHierarchyUpdate(FoldHierarchy hierarchy) {
        int caretOffset = getDot();
        
        // Update caret offset when it's inside collapsed fold
        Fold collapsed = FoldUtilities.findCollapsedFold(hierarchy,
            caretOffset, caretOffset);
        if (collapsed != null && collapsed.getStartOffset() < caretOffset) {
            setDot(collapsed.getEndOffset(), false);
        }
        
        // Update caret's visual position
        dispatchUpdate();
    }
    
    private static class FocusHandler implements FocusListener {
        private transient FocusListener fl;

        FocusHandler(FocusListener fl) {
            this.fl = fl;
        }

        public void focusGained(FocusEvent e) {
            fl.focusGained(e);
        }

        public void focusLost(FocusEvent e) {
            fl.focusLost(e);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy