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

org.netbeans.editor.EditorUI 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-2000 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.editor;

import java.awt.*;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.util.Hashtable;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeSupport;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JViewport;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
import javax.swing.text.JTextComponent;
import javax.swing.text.Caret;
import javax.swing.text.BadLocationException;
import javax.swing.text.View;
import javax.swing.plaf.TextUI;

/**
* Editor UI for the component. All the additional UI features
* like advanced scrolling, info about fonts, abbreviations,
* keyword matching are based on this class.
*
* @author Miloslav Metelka
* @version 1.00
*/
public class EditorUI implements ChangeListener, PropertyChangeListener, SettingsChangeListener {

    public static final String OVERWRITE_MODE_PROPERTY = "overwriteMode"; // NOI18N

    public static final String COMPONENT_PROPERTY = "component"; // NOI18N

    /** Default scrolling type is used for the standard
    * setDot() call. If the area is on the screen, it
    * jumps to it, otherwise it centers the requested area
    * vertically in the middle of the window and it uses
    * smallest covering on the right side.
    */
    public static final int SCROLL_DEFAULT = 0;

    /** Scrolling type used for regular caret moves.
    * The scrollJump is used when the caret requests area outside the screen.
    */
    public static final int SCROLL_MOVE = 1;

    /** Scrolling type where the smallest covering
    * for the requested rectangle is used. It's useful
    * for going to the end of the line for example.
    */
    public static final int SCROLL_SMALLEST = 2;

    /** Scrolling type for find operations, that can
    * request additional configurable area in each
    * direction, so the context around is visible too.
    */
    public static final int SCROLL_FIND = 3;


    private static final Insets NULL_INSETS = new Insets(0, 0, 0, 0);
    
    private static final Insets DEFAULT_INSETS = new Insets(0, SettingsDefaults.defaultTextLeftMarginWidth.intValue(), 0, 0);    

    private static final Dimension NULL_DIMENSION = new Dimension(0, 0);

    /** Default margin on the left and right side of the line number */
    public static final Insets defaultLineNumberMargin = new Insets(0, 3, 0, 3);

    private static final boolean debugUpdateLineHeight
    = Boolean.getBoolean("netbeans.debug.editor.updateLineHeight");

    /** Map holding the coloring maps for the different languages.
     * It helps to minimize the amount of the coloring maps
     * and also save the time necessary for their creation.
     */
    private static final HashMap sharedColoringMaps = new HashMap(57);
    private static final SettingsChangeListener clearingListener
        = new SettingsChangeListener() {
            public void settingsChange(SettingsChangeEvent evt) {
                // Fired when the Settings are locked
                sharedColoringMaps.clear();
            }
        };


    static {
        Settings.addSettingsChangeListener( clearingListener );
    }


    /** Component this extended UI is related to. */
    private JTextComponent component;

    private JComponent extComponent;
    
    private JToolBar toolBarComponent;

    /** Property change support for firing property changes */
    PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

    /** Document for the case ext ui is constructed without the component */
    private BaseDocument printDoc;

    /** Draw layer chain */
    private DrawLayerList drawLayerList = new DrawLayerList();

    /** Map holding the [name, coloring] pairs */
    private Map coloringMap;

    /** Character (or better line) height. Particular view can use a different
    * character height however most views will probably use this one.
    */
    private int lineHeight = 1; // prevent possible division by zero

    private float lineHeightCorrection = 1.0f;

    /** Ascent of the line which is maximum ascent of all the fonts used. */
    private int lineAscent;

    /** Width of the space in the default coloring's font */
    int defaultSpaceWidth = 1;

    /** Flag to initialize fonts */
    private boolean fontsInited;
    /** First paint after preferenceChanged after fonts were inited. */
    private boolean fontsInitedPreferenceChanged;

    /** Should the search words be colored? */
    boolean highlightSearch;

    /** Enable displaying line numbers. Both this flag and lineNumberVisibleSetting
    * must be true to have the line numbers visible in the window. This flag is false
    * by default. It's turned on automatically if the getExtComponent is called.
    */
    boolean lineNumberEnabled;

    /** This flag corresponds to the LINE_NUMBER_VISIBLE setting. */
    boolean lineNumberVisibleSetting;

    /** Whether to show line numbers or not. This flag is obtained using bitwise AND
    * operation on lineNumberEnabled flag and lineNumberVisibleSetting flag.
    */
    boolean lineNumberVisible;

    /** Line number total width with indentation. It includes left and right
    * line-number margins and lineNumberDigitWidth * lineNumberMaxDigitCount.
    */
    int lineNumberWidth;

    /** Width of one digit used for line numbering. It's based
    * on the information from the line coloring.
    */
    int lineNumberDigitWidth;

    /** Current maximum count of digits in line number */
    int lineNumberMaxDigitCount;

    /** This is the size of the editor as component while the real size
    * of the lines edited can be lower. The reason why to use this
    * virtual size is that each resizing of the component means
    * revalidating and therefore repainting of the whole component.
    */
    Rectangle virtualSize = new Rectangle();

    //  /** This is the increment by which the size of the component
    //  * is increased.
    //  */
    //  Rectangle virtualSizeIncrement = new Rectangle(); !!!

    /** Margin between the line-number bar and the text. */
    int textLeftMarginWidth;

    /** This is the full margin around the text. The left margin
    * is an addition of component's margin and lineNumberWidth 
    * and textLeftMarginWidth.
    */
    Insets textMargin = DEFAULT_INSETS;

    /** How much columns/lines to add when the scroll is performed
    * so that the component is not scrolled so often.
    * Negative number means portion of the extent width/height
    */
    Insets scrollJumpInsets;

    /** How much columns/lines to add when the scroll is performed
    * so that the component is not scrolled so often.
    * Negative number means portion of the extent width/height
    */
    Insets scrollFindInsets;

    /** Flag saying whether either the width or height in virtualSize
    * were updated.
    */
    boolean virtualSizeUpdated;

    /** EditorUI properties */
    Hashtable props = new Hashtable(11);

    boolean textLimitLineVisible;

    Color textLimitLineColor;

    int textLimitWidth;

    private Rectangle lastExtentBounds = new Rectangle();

    private Dimension componentSizeIncrement = new Dimension();

    private Abbrev abbrev;

    private WordMatch wordMatch;

    private Object componentLock;

    /** Status bar */
    StatusBar statusBar;

    private FocusAdapter focusL;

    Map renderingHints;

    /** Glyph gutter used for drawing of annotation glyph icons. */
    private GlyphGutter glyphGutter = null;

    /** The line numbers can be shown in glyph gutter and therefore it is necessary 
     * to disable drawing of lines here. During the printing on the the other hand, line 
     * numbers must be visible. */
    private boolean disableLineNumbers = true;

    /** Left right corner of the JScrollPane */
    private JPanel glyphCorner;
    
    /** Whether printing coloring map or component coloring map should be used. */
    private boolean usePrintColoringMap;
    
    public static final String LINE_HEIGHT_CHANGED_PROP = "line-height-changed-prop"; //NOI18N

    /** init paste action #39678 */
    private static boolean isPasteActionInited = false;

    /** Construct extended UI for the use with a text component */
    public EditorUI() {
        this.usePrintColoringMap = false;
        
        Settings.addSettingsChangeListener(this);

        focusL = new FocusAdapter() {
                     public void focusGained(FocusEvent evt) {
                         Registry.activate(getComponent());
                         /* Fix of #25475 - copyAction's enabled flag
                          * must be updated on focus change
                          */
                         stateChanged(null);
                         if (component!=null){
                            BaseTextUI ui = (BaseTextUI)component.getUI();
                            if (ui!=null) ui.refresh();
                         }
                     }
                 };

    }

    /** Construct extended UI for printing the given document */
    public EditorUI(BaseDocument printDoc) {
        this(printDoc, true, true);
    }
        
    /**
     * Construct extended UI for printing the given document
     * and specify which set of colors should be used.
     *
     * @param printDoc document that should be printed.
     * @param usePrintColoringMap use printing coloring settings instead
     *  of the regular ones.
     * @param lineNumberEnabled if set to false the line numbers will not be printed.
     *  If set to true the visibility of line numbers depends on lineNumberVisibleSetting.
     */
    public EditorUI(BaseDocument printDoc, boolean usePrintColoringMap, boolean lineNumberEnabled) {
        this.printDoc = printDoc;
        this.usePrintColoringMap = usePrintColoringMap;

        settingsChange(null);

        setLineNumberEnabled(lineNumberEnabled);

        updateLineNumberWidth(0);

        drawLayerList.add(printDoc.getDrawLayerList());
        
        // the fix of #37363
        drawLayerList.remove(DrawLayerFactory.GUARDED_LAYER_NAME);
        drawLayerList.remove(DrawLayerFactory.HIGHLIGHT_SEARCH_LAYER_NAME);
        drawLayerList.remove(DrawLayerFactory.INC_SEARCH_LAYER_NAME);
    }

    /** Gets the coloring map that can be shared by the components
      * with the same kit. Only the component coloring map is provided.
      */
    protected static Map getSharedColoringMap(Class kitClass) {
        synchronized (Settings.class) { // must sync like this against dedloks
            Map cm = (Map)sharedColoringMaps.get(kitClass);
            if (cm == null) {
                cm = SettingsUtil.getColoringMap(kitClass, false, true);
                // Test if there's a default coloring
                if (cm.get(SettingsNames.DEFAULT_COLORING) == null) {
                    cm.put(SettingsNames.DEFAULT_COLORING, SettingsDefaults.defaultColoring);
                }

                sharedColoringMaps.put(kitClass, cm);
            }

            return cm;
        }
    }


    /** Called when the BaseTextUI is being installed
    * into the component.
    */
    protected void installUI(JTextComponent c) {
        synchronized (getComponentLock()) {
            this.component = c;
            putProperty(COMPONENT_PROPERTY, c);

            // listen on component
            component.addPropertyChangeListener(this);
            component.addFocusListener(focusL);

            // listen on caret
            Caret caret = component.getCaret();
            if (caret != null) {
                caret.addChangeListener(this);
            }

            BaseDocument doc = getDocument();
            if (doc != null) {
                modelChanged(null, doc);
            }
        }

        // Make sure all the things depending on non-null component will be updated
        settingsChange(null);
        
        // fix for issue #16352
        getDefaultColoring().apply(component);
    }

    /** Called when the BaseTextUI is being uninstalled
    * from the component.
    */
    protected void uninstallUI(JTextComponent c) {
        synchronized (getComponentLock()) {
            
            // fix for issue 12996
            if (component != null) {
                
            // stop listening on caret
            Caret caret = component.getCaret();
            if (caret != null) {
                caret.removeChangeListener(this);
            }

            // stop listening on component
            component.removePropertyChangeListener(this);
            component.removeFocusListener(focusL);
            
            }

            BaseDocument doc = getDocument();
            if (doc != null) {
                modelChanged(doc, null);
            }

            component = null;
            putProperty(COMPONENT_PROPERTY, null);

            // Clear the font-metrics cache
            FontMetricsCache.clear();
        }
    }

    /** Get the lock assuring the component will not be changed
    * by installUI() or uninstallUI().
    * It's useful for the classes that want to listen for the
    * component change in EditorUI.
    */
    public Object getComponentLock() {
        if (componentLock == null) {
            componentLock = new ComponentLock();
        }
        return componentLock;
    }
    static class ComponentLock {};

    public void addPropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.addPropertyChangeListener(l);
    }

    public void addPropertyChangeListener(String propertyName, PropertyChangeListener l) {
        propertyChangeSupport.addPropertyChangeListener(propertyName, l);
    }

    public void removePropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.removePropertyChangeListener(l);
    }

    public void removePropertyChangeListener(String propertyName, PropertyChangeListener l) {
        propertyChangeSupport.removePropertyChangeListener(propertyName, l);
    }

    protected final void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
        propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue);
    }

    public void settingsChange(SettingsChangeEvent evt) {
        if (component != null) {
            if (Utilities.getKit(component) == null) {
                return; // prevent problems if not garbage collected and settings changed
            }
        }

        Class kitClass = getKitClass();
        String settingName = (evt != null) ? evt.getSettingName() : null;

        if (settingName == null || SettingsNames.LINE_NUMBER_VISIBLE.equals(settingName)) {
            lineNumberVisibleSetting = SettingsUtil.getBoolean(kitClass, 
                                           SettingsNames.LINE_NUMBER_VISIBLE,
                                           SettingsDefaults.defaultLineNumberVisible);
            lineNumberVisible = lineNumberEnabled && lineNumberVisibleSetting;
            
            // if this is printing, the drawing of original line numbers must be enabled
            if (component == null)
                disableLineNumbers = false;
            
            if (disableLineNumbers)
                lineNumberVisible = false;
        }

        BaseDocument doc = getDocument();
        if (doc != null) {

            if (settingName == null || SettingsNames.TEXT_LEFT_MARGIN_WIDTH.equals(settingName)) {
                textLeftMarginWidth = SettingsUtil.getInteger(kitClass,
                                      SettingsNames.TEXT_LEFT_MARGIN_WIDTH,
                                      SettingsDefaults.defaultTextLeftMarginWidth);
            }

            if (settingName == null || SettingsNames.LINE_HEIGHT_CORRECTION.equals(settingName)) {
                Object value = Settings.getValue(kitClass, SettingsNames.LINE_HEIGHT_CORRECTION);
                if (!(value instanceof Float) || ((Float)value).floatValue() < 0) {
                    value = SettingsDefaults.defaultLineHeightCorrection;
                }

                float newLineHeightCorrection = ((Float)value).floatValue();
                if (newLineHeightCorrection != lineHeightCorrection){
                    lineHeightCorrection = newLineHeightCorrection;
                    updateLineHeight(null);
                }
            }

            if (settingName == null || SettingsNames.TEXT_LIMIT_LINE_VISIBLE.equals(settingName)) {
                textLimitLineVisible = SettingsUtil.getBoolean(kitClass,
                                       SettingsNames.TEXT_LIMIT_LINE_VISIBLE, SettingsDefaults.defaultTextLimitLineVisible);
            }

            if (settingName == null || SettingsNames.TEXT_LIMIT_LINE_COLOR.equals(settingName)) {
                Object value = Settings.getValue(kitClass, SettingsNames.TEXT_LIMIT_LINE_COLOR);
                textLimitLineColor = (value instanceof Color) ? (Color)value
                                     : SettingsDefaults.defaultTextLimitLineColor;
            }

            if (settingName == null || SettingsNames.TEXT_LIMIT_WIDTH.equals(settingName)) {
                textLimitWidth = SettingsUtil.getPositiveInteger(kitClass,
                                 SettingsNames.TEXT_LIMIT_WIDTH, SettingsDefaults.defaultTextLimitWidth);
            }

            // component only properties
            if (component != null) {
                if (settingName == null || SettingsNames.SCROLL_JUMP_INSETS.equals(settingName)) {
                    Object value = Settings.getValue(kitClass, SettingsNames.SCROLL_JUMP_INSETS);
                    scrollJumpInsets = (value instanceof Insets) ? (Insets)value : NULL_INSETS;
                }

                if (settingName == null || SettingsNames.SCROLL_FIND_INSETS.equals(settingName)) {
                    Object value = Settings.getValue(kitClass, SettingsNames.SCROLL_FIND_INSETS);
                    scrollFindInsets = (value instanceof Insets) ? (Insets)value : NULL_INSETS;
                }

                if (settingName == null || SettingsNames.COMPONENT_SIZE_INCREMENT.equals(settingName)) {
                    Object value = Settings.getValue(kitClass, SettingsNames.COMPONENT_SIZE_INCREMENT);
                    componentSizeIncrement = (value instanceof Dimension) ? (Dimension)value : NULL_DIMENSION;
                }

                if (settingName == null || SettingsNames.RENDERING_HINTS.equals(settingName)) {
                    Object value = Settings.getValue(kitClass, SettingsNames.RENDERING_HINTS);
                    renderingHints = (value instanceof Map) ? (Map)value : null;
                }

                if (settingName == null || SettingsNames.CARET_COLOR_INSERT_MODE.equals(settingName)
                        || SettingsNames.CARET_COLOR_OVERWRITE_MODE.equals(settingName)
                   ) {
                    Boolean b = (Boolean)getProperty(OVERWRITE_MODE_PROPERTY);
                    Color caretColor;
                    if (b == null || !b.booleanValue()) {
                        Object value = Settings.getValue(kitClass, SettingsNames.CARET_COLOR_INSERT_MODE);
                        caretColor = (value instanceof Color) ? (Color)value
                                     : SettingsDefaults.defaultCaretColorInsertMode;

                    } else {
                        Object value = Settings.getValue(kitClass, SettingsNames.CARET_COLOR_OVERWRITE_MODE);
                        caretColor = (value instanceof Color) ? (Color)value
                                     : SettingsDefaults.defaultCaretColorOvwerwriteMode;
                    }

                    if (caretColor != null) {
                        component.setCaretColor(caretColor);
                    }
                }



                // fix for issues 13842, 14003
                if (SwingUtilities.isEventDispatchThread()) {
                    component.setKeymap(Utilities.getKit(component).getKeymap());                    
                    BaseTextUI ui = (BaseTextUI)component.getUI();
                    ui.updateHeight();
                    component.repaint();
                } else {
                    SwingUtilities.invokeLater(
                        new Runnable() {
                            public void run() {
                                JTextComponent c = component;
                                if (c != null) {
                                    BaseKit kit = Utilities.getKit(c);
                                    if (kit != null) {
                                        c.setKeymap(kit.getKeymap());                                    
                                        BaseTextUI ui = (BaseTextUI)c.getUI();
                                        if (ui != null) {
                                            ui.updateHeight();
                                            c.repaint();
                                        }
                                    }
                                }
                            }
                        }
                    );
                }
            }
        }

        coloringMap = null; // reset coloring map so it's lazily rebuilt
         /* make sure there's no pending preferenceChanged() request
          * because if it would be then the fontsInited = false
          * would have no effect.
          */
        fontsInitedPreferenceChanged = false;
        fontsInited = false;

    }

    public void stateChanged(ChangeEvent evt) {
        SwingUtilities.invokeLater(
            new Runnable() {
                
                /** @return true if the document supports guarded sections
                 * and when either the caret is in guarded block
                 * or when selection spans any guarded block(s).
                 */
                private boolean isCaretGuarded(){
                    JTextComponent c = component;
                    BaseDocument bdoc = getDocument();
                    boolean inGuardedBlock = false;
                    if (bdoc instanceof GuardedDocument){
                        GuardedDocument gdoc = (GuardedDocument)bdoc;

                        boolean selectionSpansGuardedSection = false;
                        for (int i=c.getSelectionStart(); i viewWidth) {
            r.x = viewWidth - r.width;
            if (r.x < 0) {
                r.x = 0;
                r.width = viewWidth;
            }
            return scrollRectToVisibleImpl(r, scrollPolicy, bounds); // recall
        }
        // r must be within virtualSize's height
        if (r.y +r.height  > viewHeight) {
            r.y = viewHeight - r.height;
            if (r.y < 0) {
                r.y = 0;
                r.height = viewHeight;
            }
            return scrollRectToVisibleImpl(r, scrollPolicy, bounds);
        }

        // if r extends bounds dimension it must be corrected now
        if (r.width > bounds.width || r.height > bounds.height) {
            Rectangle caretRect = new Rectangle((Rectangle)component.getCaret());
            if (caretRect.x >= r.x
                    && caretRect.x + caretRect.width <= r.x + r.width
                    && caretRect.y >= r.y
                    && caretRect.y + caretRect.height <= r.y + r.height
               ) { // caret inside requested rect
                // move scroll rect for best caret visibility
                int overX = r.width - bounds.width;
                int overY = r.height - bounds.height;
                if (overX > 0) {
                    r.x -= overX * (caretRect.x - r.x) / r.width;
                }
                if (overY > 0) {
                    r.y -= overY * (caretRect.y - r.y) / r.height;
                }
            }
            r.height = bounds.height;
            r.width = bounds.width; // could be different algorithm
            return scrollRectToVisibleImpl(r, scrollPolicy, bounds);
        }

        int newX = bounds.x;
        int newY = bounds.y;
        boolean move = false;
        // now the scroll rect is within bounds of the component
        // and can have size of the extent at maximum
        if (r.x < bounds.x) {
            move = true;
            switch (scrollPolicy) {
            case SCROLL_MOVE:
                newX = (scrollJumpInsets.left < 0)
                       ? (bounds.width * (-scrollJumpInsets.left) / 100)
                       : scrollJumpInsets.left * defaultSpaceWidth;
                newX = Math.min(newX, bounds.x + bounds.width - (r.x + r.width));
                newX = Math.max(r.x - newX, 0); // new bounds.x
                break;
            case SCROLL_DEFAULT:
            case SCROLL_SMALLEST:
            default:
                newX = r.x;
                break;
            }
            updateVirtualWidth(newX + bounds.width);
        } else if (r.x + r.width > bounds.x + bounds.width) {
            move = true;
            switch (scrollPolicy) {
            case SCROLL_SMALLEST:
                newX = r.x + r.width - bounds.width;
                break;
            default:
                newX = (scrollJumpInsets.right < 0)
                       ? (bounds.width * (-scrollJumpInsets.right) / 100 )
                       : scrollJumpInsets.right * defaultSpaceWidth;
                newX = Math.min(newX, bounds.width - r.width);
                newX = (r.x + r.width) + newX - bounds.width;
                break;
            }

            updateVirtualWidth(newX + bounds.width);
        }

        if (r.y < bounds.y) {
            move = true;
            switch (scrollPolicy) {
            case SCROLL_MOVE:
                newY = r.y;
                newY -= (scrollJumpInsets.top < 0)
                        ? (bounds.height * (-scrollJumpInsets.top) / 100 )
                        : scrollJumpInsets.top * lineHeight;
                break;
            case SCROLL_SMALLEST:
                newY = r.y;
                break;
            case SCROLL_DEFAULT:
            default:
                newY = r.y - (bounds.height - r.height) / 2; // center
                break;
            }
            newY = Math.max(newY, 0);
        } else if (r.y + r.height > bounds.y + bounds.height) {
            move = true;
            switch (scrollPolicy) {
            case SCROLL_MOVE:
                newY = (r.y + r.height) - bounds.height;
                newY += (scrollJumpInsets.bottom < 0)
                        ? (bounds.height * (-scrollJumpInsets.bottom) / 100 )
                        : scrollJumpInsets.bottom * lineHeight;
                break;
            case SCROLL_SMALLEST:
                newY = (r.y + r.height) - bounds.height;
                break;
            case SCROLL_DEFAULT:
            default:
                newY = r.y - (bounds.height - r.height) / 2; // center
                break;
            }
            newY = Math.max(newY, 0);
        }

        if (move) {
            setExtentPosition(newX, newY);
        }
        return move;
    }

    void setExtentPosition(int x, int y) {
        JViewport port = getParentViewport();
        if (port != null) {
            Point p = new Point(Math.max(x, 0), Math.max(y, 0));
            port.setViewPosition(p);
        }
    }

    public void adjustWindow(int caretPercentFromWindowTop) {
        final Rectangle bounds = getExtentBounds();
        if (component != null && (component.getCaret() instanceof Rectangle)) {
            Rectangle caretRect = (Rectangle)component.getCaret();
            bounds.y = caretRect.y - (caretPercentFromWindowTop * bounds.height) / 100
                       + (caretPercentFromWindowTop * lineHeight) / 100;
            Utilities.runInEventDispatchThread(
                new Runnable() {
                    public void run() {
                        scrollRectToVisible(bounds, SCROLL_SMALLEST);
                    }
                }
            );
        }
    }

    /** Set the dot according to the currently visible screen window.
    * #param percentFromWindowTop percentage giving the distance of the caret
    *  from the top of the currently visible window.
    */
    public void adjustCaret(int percentFromWindowTop) {
        JTextComponent c = component;
        if (c != null) {
            Rectangle bounds = getExtentBounds();
            bounds.y += (percentFromWindowTop * bounds.height) / 100
                        - (percentFromWindowTop * lineHeight) / 100;
            try {
                int offset = ((BaseTextUI)c.getUI()).getPosFromY(bounds.y);
                if (offset >= 0) {
                    caretSetDot(offset, null, SCROLL_SMALLEST);
                }
            } catch (BadLocationException e) {
            }
        }
    }

    /** Set the position of the caret and scroll the extent if necessary.
     * @param offset position where the caret should be placed
     * @param scrollRect rectangle that should become visible. It can be null
     *   when no scrolling should be done.
     * @param scrollPolicy policy to be used when scrolling.
     * @deprecated
     */
    public void caretSetDot(int offset, Rectangle scrollRect, int scrollPolicy) {
        if (component != null) {
            Caret caret = component.getCaret();
            if (caret instanceof BaseCaret) {
                ((BaseCaret)caret).setDot(offset, scrollRect, scrollPolicy);
            } else {
                caret.setDot(offset);
            }
        }
    }

    /** Set the position of the caret and scroll the extent if necessary.
     * @param offset position where the caret should be placed
     * @param scrollRect rectangle that should become visible. It can be null
     *   when no scrolling should be done.
     * @param scrollPolicy policy to be used when scrolling.
     * @deprecated
     */
    public void caretMoveDot(int offset, Rectangle scrollRect, int scrollPolicy) {
        if (component != null) {
            Caret caret = component.getCaret();
            if (caret instanceof BaseCaret) {
                ((BaseCaret)caret).moveDot(offset, scrollRect, scrollPolicy);
            } else {
                caret.moveDot(offset);
            }
        }
    }

    /** This method is called by textui to do the paint.
    * It is forwarded either to paint through the image
    * and then copy the image area to the screen or to
    * paint directly to this graphics. The real work occurs
    * in draw-engine.
    */
    protected void paint(Graphics g) {
        if (component != null) { // component must be installed
            if (fontsInitedPreferenceChanged) {
                fontsInitedPreferenceChanged = false;
                fontsInited = true;
                getExtentBounds(lastExtentBounds);
            }

            if (!fontsInited && g != null) {
                update(g);
            }
           // ((BaseTextUI)component.getUI()).paintRegion(g);
        }
    }

    /** Returns the line number margin */
    public Insets getLineNumberMargin() {
        return defaultLineNumberMargin;
    }

    private int computeLineNumberDigitWidth(Graphics g){
        // Handle line number fonts and widths
        Coloring dc = getDefaultColoring();        
        Coloring lnc = (Coloring)getColoringMap().get(SettingsNames.LINE_NUMBER_COLORING);
        if (lnc != null) {
            Font lnFont = lnc.getFont();
            if (lnFont == null) {
                lnFont = dc.getFont();
            }
            if (component == null) return lineNumberDigitWidth;
            FontMetrics lnFM = (g == null) ? 
                FontMetricsCache.getFontMetrics(lnFont, component) : g.getFontMetrics(lnFont);
            if (lnFM == null) return lineNumberDigitWidth;
            int maxWidth = 1;
            char[] digit = new char[1]; // will be used for '0' - '9'
            for (int i = 0; i <= 9; i++) {
                digit[0] = (char)('0' + i);
                maxWidth = Math.max(maxWidth, lnFM.charsWidth(digit, 0, 1));
            }
            return maxWidth;
        }
        return lineNumberDigitWidth;
    }
    
    /** Returns width of the one digit */
    public int getLineNumberDigitWidth() {
        return (lineNumberDigitWidth == 0) ? computeLineNumberDigitWidth(null) : lineNumberDigitWidth;
    }

    /** Is glyph gutter created and visible for the document or not */
    public boolean isGlyphGutterVisible() {
        return glyphGutter != null;
    }
    
    public final GlyphGutter getGlyphGutter() {
        return glyphGutter;
    }
    
    protected void updateScrollPaneCornerColor() {
        Coloring lineColoring = (Coloring)getColoringMap().get(SettingsNames.LINE_NUMBER_COLORING);
        Coloring defaultColoring = (Coloring)getDefaultColoring();
        
        Color backgroundColor;
        if (lineColoring.getBackColor() != null)
            backgroundColor = lineColoring.getBackColor();
        else
            backgroundColor = defaultColoring.getBackColor();
        
        glyphCorner.setBackground(backgroundColor);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy