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-2003 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.editor;

import javax.swing.JComponent;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Font;
import java.awt.Image;
import java.net.URL;
import java.net.MalformedURLException;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.DocumentEvent;
import javax.swing.text.BadLocationException;
import java.awt.FontMetrics;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Shape;
import java.awt.image.ImageObserver;
import java.awt.event.InputEvent;
import javax.swing.JPopupMenu;
import org.netbeans.editor.Utilities;
import javax.swing.event.PopupMenuListener;
import javax.swing.event.PopupMenuEvent;
import org.netbeans.editor.FontMetricsCache;
import org.netbeans.editor.LocaleSupport;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.awt.event.*;
import java.lang.ref.WeakReference;
import java.util.Map;
import javax.swing.Action;
import javax.accessibility.*;
import javax.swing.SwingUtilities;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BoxView;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.Position;
import javax.swing.text.View;
import org.netbeans.api.editor.fold.FoldHierarchy;
import org.netbeans.api.editor.fold.FoldHierarchyEvent;
import org.netbeans.api.editor.fold.FoldHierarchyListener;
import org.openide.ErrorManager;

/** GlyphGutter is component for displaying line numbers and annotation
 * glyph icons. Component also allow to "cycle" through the annotations. It
 * means that if there is more than one annotation on the line, only one of them
 * might be visible. And clicking the special cycling button in the gutter the user
 * can cycle through the annotations.
 *
 * @author  David Konecny
 * @since 07/2001
 */

public class GlyphGutter extends JComponent implements Annotations.AnnotationsListener, Accessible, SettingsChangeListener, SideBarFactory {

    /** EditorUI which part this gutter is */
    private EditorUI editorUI;
    
    /** Document to which this gutter is attached*/
    private BaseDocument doc;
    
    /** Annotations manager responsible for annotations for this line */
    private Annotations annos;
    
    /** Cycling button image */
    private Image gutterButton;
    
    /** Backroung color of the gutter */
    private Color backgroundColor;
    
    /** Foreground color of the gutter. Used for drawing line numbers. */
    private Color foreColor;
    
    /** Font used for drawing line numbers */
    private Font font;
    
    /** Height of the line as it was calculated in EditorUI. */
    private int lineHeight;

    /** Flag whther the gutter was initialized or not. The painting is disabled till the
     * gutter is not initialized */
    private boolean init;
    
    /** Width of the column used for drawing line numbers. The value contains
     * also line number margins. */
    private int numberWidth;

    /** Predefined width of the glyph icons */
    private final static int glyphWidth = 16;

    /** Preddefined width of the cycling button */
    private final static int glyphButtonWidth = 9;
    
    /** Whether the line numbers are shown or not */
    private boolean showLineNumbers = true;
    
    /** Image observer used for glyph icons */
    private ImageObserver imgObserver = null;

    /** The gutter height is enlarged by number of lines which specifies this constant */
    private static final int ENLARGE_GUTTER_HEIGHT = 300;
    
    /** The hightest line number. This value is used for calculating width of the gutter */
    private int highestLineNumber = 0;
    
    /** Whether the annotation glyph can be drawn over the line numbers */
    private boolean drawOverLineNumbers = false;

    /* These two variables are used for caching of count of line annos 
     * on the line over which is the mouse caret. Just for sake of optimalization. */
    private int cachedCountOfAnnos = -1;
    private int cachedCountOfAnnosForLine = -1;

    /** Property change listener on AnnotationTypes changes */
    private PropertyChangeListener annoTypesListener;
    private PropertyChangeListener editorUIListener;
    private GlyphGutter.GlyphGutterFoldHierarchyListener glyphGutterFoldHierarchyListener;
    private GutterMouseListener gutterMouseListener;
    private FoldHierarchy foldHierarchy;
    private Map renderingHints;    
    
    public GlyphGutter(){}
    
    public GlyphGutter(EditorUI editorUI) {
        super();
        this.editorUI = editorUI;
        init = false;
        doc = editorUI.getDocument();
        annos = doc.getAnnotations();
        
        // Annotations class is model for this view, so the listener on changes in
        // Annotations must be added here
        annos.addAnnotationsListener(this);

        // do initialization
        init();
        update();
        Settings.addSettingsChangeListener(this);
        setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
        foldHierarchy = FoldHierarchy.get(editorUI.getComponent());
        glyphGutterFoldHierarchyListener = new GlyphGutterFoldHierarchyListener();
        foldHierarchy.addFoldHierarchyListener(glyphGutterFoldHierarchyListener);
        editorUIListener = new EditorUIListener();
        editorUI.addPropertyChangeListener(editorUIListener);
        updateRenderingHints();
        setOpaque (true);
    }

    private void updateRenderingHints(){
        JTextComponent comp = editorUI.getComponent();
        if (comp == null) return;
        Object value = Settings.getValue(Utilities.getKitClass(comp), SettingsNames.RENDERING_HINTS);
        renderingHints = (value instanceof Map) ? (java.util.Map)value : null;
    }
    
    public void settingsChange(SettingsChangeEvent evt) {
        if (editorUI == null) // no long er active
            return;

        JTextComponent component = editorUI.getComponent();
        if (evt == null || component == null) return;

        String settingName = evt.getSettingName();
        if (settingName == null || SettingsNames.RENDERING_HINTS.equals(settingName)) {
            updateRenderingHints();
        }
        
        Class kitClass = evt.getKitClass();        
        if (Utilities.getKitClass(component) != kitClass){
            Rectangle rect = component.getVisibleRect();
            if (rect!=null && rect.width == 0){
                if (SwingUtilities.isEventDispatchThread()) {
                    resize();
                } else {
                    SwingUtilities.invokeLater(
                        new Runnable() {
                            public void run() {
                                resize();
                            }
                        }
                    );
                }
            }
        }
    }
    
    
    /* Read accessible context
     * @return - accessible context
     */
    public AccessibleContext getAccessibleContext () {
        if (accessibleContext == null) {
            accessibleContext = new AccessibleJComponent() {
                public AccessibleRole getAccessibleRole() {
                    return AccessibleRole.PANEL;
                }
            };
        }
        return accessibleContext;
    }

    /** Do initialization of the glyph gutter*/
    protected void init() {
        if (editorUI == null)
            return ;

        URL imageURL = null;

        try {
            // cycling button
            imageURL = new URL("nbresloc:/org/netbeans/editor/resources/glyphbutton.gif"); // NOI18N
        } catch (MalformedURLException ex) {
            Utilities.annotateLoggable(ex);
            return;
        }
        
        if (imageURL != null)
            gutterButton = Toolkit.getDefaultToolkit().getImage(imageURL);

        setToolTipText ("");
        getAccessibleContext().setAccessibleName(LocaleSupport.getString("ACSN_Glyph_Gutter")); // NOI18N
        getAccessibleContext().setAccessibleDescription(LocaleSupport.getString("ACSD_Glyph_Gutter")); // NOI18N

        // add mouse listener for cycling button
        // TODO: clicking the line number should select whole line
        // TODO: clicking the line number abd dragging the mouse should select block of lines
        gutterMouseListener = new GutterMouseListener ();
        addMouseListener (gutterMouseListener);
        addMouseMotionListener (gutterMouseListener);

        // after the glyph icons are loaded it is necessary to repaint the gutter
        imgObserver = new Observer(this);
        
        AnnotationTypes.getTypes().addPropertyChangeListener( annoTypesListener = new PropertyChangeListener() {
            public void propertyChange (PropertyChangeEvent evt) {
                if (evt.getPropertyName() == AnnotationTypes.PROP_GLYPHS_OVER_LINE_NUMBERS ||
                    evt.getPropertyName() == AnnotationTypes.PROP_SHOW_GLYPH_GUTTER) {
                    update();
                }
            }
        });
        
    }
    
    /** Update colors, fonts, sizes and invalidate itself. This method is
     * called from EditorUI.update() */
    public void update() {
        if (editorUI == null)
            return ;
        Coloring lineColoring = (Coloring)editorUI.getColoringMap().get(SettingsNames.LINE_NUMBER_COLORING);
        Coloring defaultColoring = (Coloring)editorUI.getDefaultColoring();
        
        // fix for issue #16940
        // the real cause of this problem is that closed document is not garbage collected, 
        // because of *some* references (see #16072) and so any change in AnnotationTypes.PROP_*
        // properties is fired which must update this component although it is not visible anymore
        if (lineColoring == null)
            return;
        
        if (lineColoring.getBackColor() != null)
            backgroundColor = lineColoring.getBackColor();
        else
            backgroundColor = defaultColoring.getBackColor();

        if (lineColoring.getForeColor() != null)
            foreColor = lineColoring.getForeColor();
        else
            foreColor = defaultColoring.getForeColor();
        
        if (lineColoring.getFont() != null)
            font = lineColoring.getFont();
        else
            font = defaultColoring.getFont();

        lineHeight = editorUI.getLineHeight();

        showLineNumbers = editorUI.lineNumberVisibleSetting;

        drawOverLineNumbers = AnnotationTypes.getTypes().isGlyphsOverLineNumbers().booleanValue();
        
        
        
        init = true;

        // initialize the value with current number of lines
        highestLineNumber = getLineCount();
        
        repaint();
        resize();
    }
    
   
    protected void resize() {
        Dimension dim = new Dimension();
        dim.width = getWidthDimension();
        dim.height = getHeightDimension();
        
        // enlarge the gutter so that inserting new lines into 
        // document does not cause resizing too often
        dim.height += ENLARGE_GUTTER_HEIGHT * lineHeight;
        
        numberWidth = getLineNumberWidth();
        if (!showLineNumbers)
            numberWidth = 0;
        
        setPreferredSize(dim);

        revalidate();
    }

    /** Return number of lines in the document */
    protected int getLineCount() {
        int lineCnt;
        try {
            if (doc != null) {
                lineCnt = Utilities.getLineOffset(doc, doc.getLength()) + 1;
            } else { // deactivated
                lineCnt = 1;
            }
        } catch (BadLocationException e) {
            lineCnt = 1;
        }
        return lineCnt;
    }

    /** Gets number of digits in the number */
    protected int getDigitCount(int number) {
        return Integer.toString(number).length();
    }

    protected int getLineNumberWidth() {
        int newWidth = 0;
        if (editorUI != null) {
            Insets insets = editorUI.getLineNumberMargin();
            if (insets != null) {
                newWidth += insets.left + insets.right;
            }
            newWidth += getDigitCount(highestLineNumber) * editorUI.getLineNumberDigitWidth();
        }
        return newWidth;
    }
    
    protected int getWidthDimension() {
        int newWidth = 0;
        
        if (editorUI != null) {
            if (annos.isGlyphColumn() || AnnotationTypes.getTypes().isShowGlyphGutter().booleanValue())
                newWidth += glyphWidth;

            if (annos.isGlyphButtonColumn())
                newWidth += glyphButtonWidth;

            if (showLineNumbers) {
                int lineNumberWidth = getLineNumberWidth();
                if (drawOverLineNumbers) {
                    if (lineNumberWidth > newWidth)
                        newWidth = lineNumberWidth;
                } else
                    newWidth += lineNumberWidth;
            }
        }
        
        return newWidth;
    }
    
    protected int getHeightDimension() {
        if (editorUI == null)
            return 0;
        JComponent comp = editorUI.getComponent();
        if (comp == null)
            return 0;
        return highestLineNumber * lineHeight + (int)comp.getSize().getHeight();
    }
    

    void paintGutterForView(Graphics g, View view, int y){
        if (editorUI == null)
            return ;
        Rectangle clip = g.getClipBounds();
        JTextComponent component = editorUI.getComponent();
        if (component == null) return;
        BaseTextUI textUI = (BaseTextUI)component.getUI();


        Rectangle rec = new Rectangle(0, y, 0, editorUI.getLineHeight());

// Should already be filled by back color in paintComponent()
//        g.setColor(backgroundColor);
//        Rectangle fillRect = new Rectangle(clip.x, rec.y, getSize().width, rec.height);
//        g.fillRect(0, rec.y, getSize().width, rec.height);

        g.setFont(font); 
        g.setColor(foreColor);

        FontMetrics fm = FontMetricsCache.getFontMetrics(font, this);
        int rightMargin = 0;
        Insets margin = editorUI.getLineNumberMargin();
        if (margin != null) rightMargin = margin.right;
        Element rootElem = textUI.getRootView(component).getElement();
        int line = rootElem.getElementIndex(view.getStartOffset());
        // find the nearest visible line with an annotation
        int lineWithAnno = annos.getNextLineWithAnnotation(line);

        int lineNumberWidth = fm.stringWidth(String.valueOf(line + 1));

        if (showLineNumbers && ( (!drawOverLineNumbers) || (drawOverLineNumbers && line != lineWithAnno) ) ) {
            g.drawString(String.valueOf(line + 1), numberWidth-lineNumberWidth-rightMargin, y + editorUI.getLineAscent());
        }

        // draw anotation if we get to the line with some annotation
        if (line == lineWithAnno) {

            int count = annos.getNumberOfAnnotations(line);
            AnnotationDesc anno = annos.getActiveAnnotation(line);

            int xPos = numberWidth;
            if (drawOverLineNumbers) {
                xPos = getWidth() - glyphWidth;
                if (count > 1)
                    xPos -= glyphButtonWidth;
            }

            if (anno != null) {
                // draw the glyph only when the annotation type has its own icon (no the default one)
                // or in case there is more than one annotations on the line
                if ( ! (count == 1 && anno.isDefaultGlyph()) ) {
                    if (anno.getGlyph() != null && prepareImage(anno.getGlyph(), imgObserver))
                        g.drawImage(anno.getGlyph(), xPos, y + (lineHeight-anno.getGlyph().getHeight(null)) / 2 + 1, null);
                }
            }

            // draw cycling button if there is more than one annotations on the line
            if (count > 1)
                if (anno.getGlyph() != null && prepareImage(gutterButton, imgObserver) && prepareImage(anno.getGlyph(), imgObserver))
                    g.drawImage(gutterButton, xPos+glyphWidth, y + (lineHeight-anno.getGlyph().getHeight(null)) / 2, null);

            // update the value with next line with some anntoation
            lineWithAnno = annos.getNextLineWithAnnotation(line+1);
        }
    }
    
    /** Paint the gutter itself */
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (editorUI == null)
            return ;

        // Possibly apply the rendering hints
        if (renderingHints != null) {
            ((java.awt.Graphics2D)g).setRenderingHints(renderingHints);
        }
        
        // if the gutter was not initialized yet, skip the painting        
        if (!init) return;
        
        Rectangle clip = g.getClipBounds();   

        JTextComponent component = editorUI.getComponent();
        if (component == null) return;
        
        BaseTextUI textUI = (BaseTextUI)component.getUI();
        Element rootElem = textUI.getRootView(component).getElement();

        View rootView = Utilities.getDocumentView(component);
        if (rootView == null) return;
      
        g.setColor(backgroundColor);
        g.fillRect(clip.x, clip.y, clip.width, clip.height);

        AbstractDocument doc = (AbstractDocument)component.getDocument();
        doc.readLock();
        try{
            foldHierarchy.lock();
            try{
                int startPos = textUI.getPosFromY(clip.y);
                int startViewIndex = rootView.getViewIndex(startPos,Position.Bias.Forward);
                int rootViewCount = rootView.getViewCount();

                if (startViewIndex >= 0 && startViewIndex < rootViewCount) {
                    // find the nearest visible line with an annotation
                    int lineWithAnno = annos.getNextLineWithAnnotation(rootElem.getElementIndex(startPos));
                    Rectangle rec = textUI.modelToView(component, rootView.getView(startViewIndex).getStartOffset());
                    int y = (rec == null) ? 0 : rec.y;

                    int clipEndY = clip.y + clip.height;
                    for (int i = startViewIndex; i < rootViewCount; i++){
                        View view = rootView.getView(i);                
                        paintGutterForView(g, view, y);
                        y += editorUI.getLineHeight();
                        if (y >= clipEndY) {
                            break;
                        }
                    }
                }
                
            }finally{
                foldHierarchy.unlock();
            }
        }catch(BadLocationException ble){
            ErrorManager.getDefault().notify(ble);
        }finally{
            doc.readUnlock();
        }
    }

    /** Data for the line has changed and the line must be redraw. */
    public void changedLine(int line) {
        
        if (!init || editorUI == null)
            return;

        // reset cache if there was some change
        cachedCountOfAnnos = -1;
        
        // redraw also lines around - three lines will be redrawn
        if (line > 0)
            line--;
        JTextComponent component = editorUI.getComponent();
        if (component!=null){
            BaseTextUI textUI = (BaseTextUI)component.getUI();
            try{
                Element rootElem = component.getDocument().getDefaultRootElement();
                if (line >= rootElem.getElementCount()) { // #42504
                    return;
                }
                Element lineElem = rootElem.getElement(line);
                if (lineElem == null) return;
                int lineOffset = lineElem.getStartOffset();
                Rectangle mtvRect = textUI.modelToView(component, lineOffset);
                if (mtvRect == null) return;
                repaint(0, mtvRect.y, (int)getSize().getWidth(), 3*lineHeight);
                checkSize();
            }catch(BadLocationException ble){
                ErrorManager.getDefault().notify(ble);
            }
        }
    }

    /** Repaint whole gutter.*/
    public void changedAll() {

        if (!init || editorUI == null)
            return;

        // reset cache if there was some change
        cachedCountOfAnnos = -1;
        
        int lineCnt;
        try {
            lineCnt = Utilities.getLineOffset(doc, doc.getLength()) + 1;
        } catch (BadLocationException e) {
            lineCnt = 1;
        }

        repaint();
        checkSize();
    }

    /** Check whether it is not necessary to resize the gutter */
    protected void checkSize() {
        int count = getLineCount();
        if (count > highestLineNumber) {
            highestLineNumber = count;
        }
        Dimension dim = getPreferredSize();
        if (getWidthDimension() > dim.width ||
            getHeightDimension() > dim.height) {
            resize();
        }
        
    }

    /** Get tooltip text for the mouse position */
    // TODO: does not work for asynchronous tooltip texts
    public String getToolTipText (MouseEvent e) {
        if (editorUI == null)
            return null;
        int line = getLineFromMouseEvent(e);
        if (annos.getNumberOfAnnotations(line) == 0)
            return null;
        if (isMouseOverCycleButton(e) && annos.getNumberOfAnnotations(line) > 1) {
            return java.text.MessageFormat.format (
                LocaleSupport.getString ("cycling-glyph_tooltip"), //NOI18N
                new Object[] { new Integer (annos.getNumberOfAnnotations(line)) });
        }
        else if (isMouseOverGlyph(e)) {
            return annos.getActiveAnnotation(line).getShortDescription();
        }
        else
            return null;
    }

    /** Count the X position of the glyph on the line. */
    private int getXPosOfGlyph(int line) {
        if (editorUI == null)
            return 0;
        int xPos = numberWidth;
        if (drawOverLineNumbers) {
            xPos = getWidth() - glyphWidth;
            if (cachedCountOfAnnos == -1 || cachedCountOfAnnosForLine != line) {
                cachedCountOfAnnos = annos.getNumberOfAnnotations(line);
                cachedCountOfAnnosForLine = line;
            }
            if (cachedCountOfAnnos > 1)
                xPos -= glyphButtonWidth;
        }
        return xPos;
    }

    /** Check whether the mouse is over some glyph icon or not */
    private boolean isMouseOverGlyph(MouseEvent e) {
        int line = getLineFromMouseEvent(e);
        if (e.getX() >= getXPosOfGlyph(line) && e.getX() <= getXPosOfGlyph(line)+glyphWidth)
            return true;
        else
            return false;
    }
    
    /** Check whether the mouse is over the cycling button or not */
    private boolean isMouseOverCycleButton(MouseEvent e) {
        int line = getLineFromMouseEvent(e);
        if (e.getX() >= getXPosOfGlyph(line)+glyphWidth && e.getX() <= getXPosOfGlyph(line)+glyphWidth+glyphButtonWidth)
            return true;
        else
            return false;
    }

    public JComponent createSideBar(JTextComponent target) {
        EditorUI eui = Utilities.getEditorUI(target);
        GlyphGutter glyph = new GlyphGutter(eui);
        eui.setGlyphGutter(glyph);
        return glyph;
    }
    
    private int getLineFromMouseEvent(MouseEvent e){
        int line = -1;
        if (editorUI != null) {
            try{
                JTextComponent component = editorUI.getComponent();
                BaseTextUI textUI = (BaseTextUI)component.getUI();
                int clickOffset = textUI.viewToModel(component, new Point(0, e.getY()));
                line = Utilities.getLineOffset(doc, clickOffset);
            }catch (BadLocationException ble){
            }
        }
        return line;
    }
    
    class GutterMouseListener extends MouseAdapter implements MouseMotionListener {
        
        /** start line of the dragging. */
        private int dragStartLine;
        /** end line of the dragging. */
        private int dragEndLine;
        /** end line of last selection. */
        private int currentEndLine;
        /** If true, the selection goes forwards. */
        private boolean selectForward;

        public void mouseClicked(MouseEvent e) {
            if (editorUI==null)
                return;
            // cycling button was clicked by left mouse button
            if (e.getModifiers() == InputEvent.BUTTON1_MASK) {
                if (isMouseOverCycleButton(e)) {
                    int line = getLineFromMouseEvent(e);
                    annos.activateNextAnnotation(line);
                } else {
                    Action actions[] = ImplementationProvider.getDefault().getGlyphGutterActions(editorUI.getComponent());
                    if (actions != null && actions.length >0) {
                        Action a = actions[0]; //TODO - create GUI chooser
                        if (a!=null && a.isEnabled()){
                            int currentLine = -1;
                            int line = getLineFromMouseEvent(e);
                            if (line == -1) return;
                            try {
                                currentLine = Utilities.getLineOffset(doc, editorUI.getComponent().getCaret().getDot());
                            } catch (BadLocationException ex) {
                                return;
                            }
                            if (line != currentLine) {
                                int offset = Utilities.getRowStartFromLineOffset(doc, line);
                                JumpList.checkAddEntry();
                                editorUI.getComponent().getCaret().setDot(offset);
                            }
                            a.actionPerformed(new ActionEvent(editorUI.getComponent(), 0, ""));
                            repaint();
                        }
                    } else {
                        Toolkit.getDefaultToolkit().beep();
                    }
                }
            }
        }

        private void showPopup(MouseEvent e) {
            if (editorUI == null)
                return;
            // annotation glyph was clicked by right mouse button
            if (e.isPopupTrigger()) {
                int line = getLineFromMouseEvent(e);
                int offset;
                if (annos.getActiveAnnotation(line) != null)
                    offset = annos.getActiveAnnotation(line).getOffset();
                else
                    offset = Utilities.getRowStartFromLineOffset(doc, line);
                if (editorUI.getComponent().getCaret().getDot() != offset)
                    JumpList.checkAddEntry();
                editorUI.getComponent().getCaret().setDot(offset);
                JPopupMenu pm = annos.createPopupMenu(Utilities.getKit(editorUI.getComponent()), line);
                if (pm != null) {
                    pm.show(GlyphGutter.this, e.getX(), e.getY());
                }
                pm.addPopupMenuListener( new PopupMenuListener() {
                        public void popupMenuCanceled(PopupMenuEvent e2) {
                            editorUI.getComponent().requestFocus();
                        }
                        public void popupMenuWillBecomeInvisible(PopupMenuEvent e2) {
                            editorUI.getComponent().requestFocus();
                        }
                        public void popupMenuWillBecomeVisible(PopupMenuEvent e2) {
                        }
                    });
            }
        }
        
        public void mouseReleased(MouseEvent e) {
            showPopup(e);
        }
        
        public void mousePressed (MouseEvent e) {
            showPopup(e);
            // "click gutter selects line" functionality was disabled
//            // only react when it is not a cycling button
//            if ((e.getModifiers() & InputEvent.BUTTON1_MASK) == InputEvent.BUTTON1_MASK) {
//                if (! isMouseOverCycleButton(e)) {
//                    dragStartLine = (int)( (float)e.getY() / (float)lineHeight );
//                    updateSelection (true);
//                }
//            }
        }
        
        public void mouseDragged(MouseEvent e) {
            // "click gutter selects line" functionality was disabled
//            dragEndLine = (int)( (float)e.getY() / (float)lineHeight );
//            updateSelection (false);
        }
        
        public void mouseMoved(MouseEvent e) {}
        
        /** Updates the selection */
        private void updateSelection (boolean newSelection) {
            if (editorUI == null)
                return ;
            javax.swing.text.JTextComponent comp = Utilities.getLastActiveComponent ();
            try {
                if (newSelection) {
                    selectForward = true;
                    // try to get the startOffset. In case of -1 it is most
                    // likely the end of the document
                    int rowStart = Utilities.getRowStartFromLineOffset (doc, dragStartLine);
                    if (rowStart < 0) {
                        rowStart = Utilities.getRowStart (doc, doc.getLength ());
                        dragStartLine = Utilities.getLineOffset (doc, rowStart);
                    }
                    comp.setCaretPosition (rowStart);
                    int offSet = Utilities.getRowEnd (doc, rowStart);
                    if (offSet < doc.getLength()) {
                        offSet = offSet + 1;
                    }
                    comp.moveCaretPosition (offSet);
                    currentEndLine = dragEndLine = dragStartLine;
                } else {
                    if (currentEndLine == dragEndLine) return;
                    // select backwards
                    if (dragEndLine < dragStartLine) {
                        if (selectForward) {
                            // selection start should be at start of (dragLine + 1)
                            int offSet = Utilities.getRowStartFromLineOffset (doc, dragStartLine + 1);
                            if (offSet < 0) {
                                offSet = Utilities.getRowEnd (doc, Utilities.getRowStartFromLineOffset (doc, dragStartLine));
                            }
                            comp.setCaretPosition (offSet);
                            selectForward = false;
                        }
                        int rowStart = Utilities.getRowStartFromLineOffset (doc, dragEndLine);
                        if (rowStart < 0) rowStart = 0;
                        comp.moveCaretPosition (rowStart);
                    }
                    // select forwards
                    else {
                        if (! selectForward) {
                            // select start should be at dragStartLine
                            comp.setCaretPosition (Utilities.getRowStartFromLineOffset (doc, dragStartLine));
                            selectForward = true;
                        }
                        // try to get the begin of (endLine + 1)
                        int offSet = Utilities.getRowStartFromLineOffset (doc, dragEndLine + 1);;
                        // for last line or more -1 is returned, so set to docLength...
                        if (offSet < 0) {
                            offSet = doc.getLength ();
                        }
                        comp.moveCaretPosition (offSet);
                    }
                }
                currentEndLine = dragEndLine;
            } catch (BadLocationException ble) {
                ErrorManager.getDefault().notify(ble);
            }
        }
    }

    class GlyphGutterFoldHierarchyListener implements FoldHierarchyListener{
    
        public GlyphGutterFoldHierarchyListener(){
        }
        
        public void foldHierarchyChanged(FoldHierarchyEvent evt) {
            repaint();
        }
    }
    
    /** Listening to EditorUI to properly deinstall attached listeners */
    class EditorUIListener implements PropertyChangeListener{
        public void propertyChange (PropertyChangeEvent evt) {
            if (evt!=null && EditorUI.COMPONENT_PROPERTY.equals(evt.getPropertyName())) {
                if (evt.getNewValue() == null){
                    // component deinstalled, lets uninstall all isteners
                    editorUI.removePropertyChangeListener(editorUIListener);
                    annos.removeAnnotationsListener(GlyphGutter.this);
                    foldHierarchy.removeFoldHierarchyListener(glyphGutterFoldHierarchyListener);
                    if (gutterMouseListener!=null){
                        removeMouseListener(gutterMouseListener);
                        removeMouseMotionListener(gutterMouseListener);
                    }
                    if (annoTypesListener !=null){
                        AnnotationTypes.getTypes().removePropertyChangeListener(annoTypesListener);
                    }
                    foldHierarchy.removeFoldHierarchyListener(glyphGutterFoldHierarchyListener);
                    foldHierarchy = null;
                    // Release document reference
                    doc = null;
                    editorUI.removePropertyChangeListener(this);
                    editorUI = null;
                    annos = null;
                }
            }
        }
    }

    private static class Observer implements ImageObserver {

        WeakReference glyphGutter;

        private Observer(GlyphGutter gutter) {
            glyphGutter = new WeakReference(gutter);
        }

        public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
            if ((infoflags & ImageObserver.ALLBITS) == ImageObserver.ALLBITS) {
                GlyphGutter obj = (GlyphGutter)glyphGutter.get();
                if (obj != null)
                    obj.repaint();
            }
            return true;
        }
    }

}
... 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.