alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 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 org.netbeans.editor.GlyphGutter;
import javax.swing.plaf.TextUI;
import org.netbeans.editor.FontMetricsCache;

/**
* 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 int STYLE_CNT = 4;
    
    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;

    /** Listener to changes in settings */
    private PropertyChangeListener settingsListener;

    /** 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)
                || SettingsNames.PRINT_LINE_NUMBER_VISIBLE.equals(settingName)
           ) {
            lineNumberVisibleSetting = SettingsUtil.getBoolean(kitClass, (component != null)
                                       ? SettingsNames.LINE_NUMBER_VISIBLE : SettingsNames.PRINT_LINE_NUMBER_VISIBLE,
                                       (component != null) ? SettingsDefaults.defaultLineNumberVisible
                                       : SettingsDefaults.defaultPrintLineNumberVisible
                                                              );
            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;
                }
                lineHeightCorrection = ((Float)value).floatValue();
            }

            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);
    }
}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.