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 java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.Position;
import javax.swing.text.View;
import org.netbeans.api.editor.fold.Fold;
import org.netbeans.api.editor.fold.FoldHierarchy;
import org.netbeans.api.editor.fold.FoldHierarchyEvent;
import org.netbeans.api.editor.fold.FoldHierarchyListener;
import org.netbeans.api.editor.fold.FoldUtilities;
import org.netbeans.editor.Coloring;

/**
 *  Code Folding Side Bar. Component responsible for drawing folding signs and responding 
 *  on user fold/unfold action.
 *
 *  @author  Martin Roskanin
 */
public class CodeFoldingSideBar extends JComponent implements SettingsChangeListener{
    
    protected JTextComponent component;    
    
    protected Font font;
    protected Color foreColor;
    protected Color backColor;
    private boolean enabled;
    
    protected List visibleMarks = new ArrayList();
    
    /** Paint operations */
    public static final int PAINT_NOOP             = 0;
    public static final int PAINT_MARK             = 1;
    public static final int PAINT_LINE             = 2;
    public static final int PAINT_END_MARK         = 3;
    public static final int SINGLE_PAINT_MARK      = 4;
    
    
    /** Creates a new instance of CodeFoldingSideBar */
    public CodeFoldingSideBar() {
        setOpaque(true);
    }
    
    public CodeFoldingSideBar(JTextComponent component){
        super();
        this.component = component;
        this.enabled = ((Boolean)Settings.getValue(Utilities.getKitClass(component), SettingsNames.CODE_FOLDING_ENABLE)).booleanValue();
        enableSideBarComponent(this.enabled);
        Settings.addSettingsChangeListener(this);
        addMouseListener(new FoldingMouseListener());
        FoldHierarchy foldHierarchy = FoldHierarchy.get(component);
        foldHierarchy.addFoldHierarchyListener(new SideBarFoldHierarchyListener());
        Document doc = getDocument();
        doc.addDocumentListener( new DocumentListener() {
                public void insertUpdate(DocumentEvent evt) {
                    if (!(evt instanceof BaseDocumentEvent)) return;
                    
                    BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
                    if (bevt.getLFCount() > 0) { // one or more lines inserted
                        repaint();
                    }    
                }
                
                public void removeUpdate(DocumentEvent evt) {
                    if (!(evt instanceof BaseDocumentEvent)) return;
                    
                    BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
                    if (bevt.getLFCount() > 0) { // one or more lines removed
                        repaint();
                    }    
                }
                
                public void changedUpdate(DocumentEvent evt) {
                }
                
            });
        setOpaque(true);
    }
    
    private void enableSideBarComponent(boolean enable){
        if (enable){
            setPreferredSize(new Dimension(getColoringFont().getSize(), component.getHeight()));    
            setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
        }else{
            setPreferredSize(new Dimension(0,0));
            setMaximumSize(new Dimension(0,0));
        }
    }
    
    private Font getDefaultColoringFont(){
        // font in folding coloring not available, get default (or inherited)
        Coloring defaultColoring = getEditorUI().getDefaultColoring();
        if (defaultColoring!=null){
            if (defaultColoring.getFont() != null){
                return defaultColoring.getFont(); 
            }
        }
        return SettingsDefaults.defaultFont;
    }
    
    protected Font getColoringFont(){
        if (font != null) return font;
        Coloring foldColoring = getEditorUI().getColoring(SettingsNames.CODE_FOLDING_BAR_COLORING);
        if (foldColoring != null){
            if (foldColoring.getFont()!=null){
                font = foldColoring.getFont();
                return font;
            }
        }
        font = getDefaultColoringFont();
        return font;
    }
    
    protected Color getForeColor(){
        if (foreColor != null) return foreColor;
        Coloring foldColoring = getEditorUI().getColoring(SettingsNames.CODE_FOLDING_BAR_COLORING);
        if (foldColoring != null && foldColoring.getForeColor()!=null){
            foreColor = foldColoring.getForeColor();
            return foreColor;
        }
        foreColor = getDefaultForeColor();
        return foreColor;
    }
    
    private Color getDefaultForeColor(){
        // font in folding coloring not available, get default (or inherited)
        Coloring defaultColoring = getEditorUI().getDefaultColoring();
        if (defaultColoring!=null && defaultColoring.getForeColor()!=null){
            return defaultColoring.getForeColor();
        }
        return SettingsDefaults.defaultForeColor;
    }
    
    private Color getDefaultBackColor(){
        // font in folding coloring not available, get default (or inherited)
        Coloring defaultColoring = getEditorUI().getDefaultColoring();
        if (defaultColoring!=null){
            return defaultColoring.getBackColor();
        }
        return SettingsDefaults.defaultBackColor;
    }
    
    protected Color getBackColor(){
        if (backColor != null) return backColor;
        Coloring foldColoring = getEditorUI().getColoring(SettingsNames.CODE_FOLDING_BAR_COLORING);
        if (foldColoring != null && foldColoring.getBackColor()!=null){
            backColor = foldColoring.getBackColor();
            return backColor;
        }
        backColor = getDefaultBackColor();
        return backColor;
    }
    
    public void settingsChange(SettingsChangeEvent evt) {
        if (evt == null || Utilities.getKitClass(component) != evt.getKitClass()) return;
        
        if (SettingsNames.CODE_FOLDING_ENABLE.equals(evt.getSettingName())){
            // enable/disable the side bar
            //System.out.println("enabled:"+value.booleanValue());
            this.enabled = ((Boolean)Settings.getValue(evt.getKitClass(), SettingsNames.CODE_FOLDING_ENABLE)).booleanValue();
            enableSideBarComponent(this.enabled);
            revalidate();
        }
        
        String defaultColoringName = SettingsNames.DEFAULT_COLORING+SettingsNames.COLORING_NAME_SUFFIX;
        String foldingColoringName = SettingsNames.CODE_FOLDING_BAR_COLORING+SettingsNames.COLORING_NAME_SUFFIX;
        
        Coloring foldingColoring = getEditorUI().getColoring(SettingsNames.CODE_FOLDING_BAR_COLORING);
        Coloring defaultColoring = getEditorUI().getDefaultColoring();
        
        Font foldingFont = null;
        Color foldingForeColor = null;
        Color foldingBackColor = null;
        if (foldingColoring!=null){
            foldingFont = foldingColoring.getFont();
            foldingForeColor = foldingColoring.getForeColor();
            foldingBackColor = foldingColoring.getBackColor();
        }
        
        if (defaultColoringName.equals(evt.getSettingName())){
            
            if (foldingForeColor == null){
                // inherited fore color
                Color tempColor = getDefaultForeColor();
                if (!tempColor.equals(foreColor)){
                    foreColor = tempColor;
                }
            }

            if (foldingBackColor == null){
                // inherited back color
                Color tempColor = getDefaultBackColor();
                if (!tempColor.equals(backColor)){
                    backColor = tempColor;
                }
            }

            // Font size change
            Font tempFont = getDefaultColoringFont();
            if (!tempFont.equals(font) && foldingFont==null){
                // if different font and foldingFont is inherited
                font = tempFont;
                if (enabled) setPreferredSize(new Dimension(font.getSize(), component.getHeight()));
            }
            repaint();
            
        }else if (foldingColoringName.equals(evt.getSettingName())){
            // Code folding coloring change
            if (foldingColoring == null) return;

            Color tempColor = foldingColoring.getForeColor();
            foreColor = (tempColor!=null) ? tempColor : getDefaultForeColor();
            
            tempColor = foldingColoring.getBackColor();
            backColor = (tempColor!=null) ? tempColor : getDefaultBackColor();
            
            if (foldingFont == null){ //inherit
                Font tempFont = getDefaultColoringFont();
                if (!tempFont.equals(font)){
                    font = tempFont;
                    if (enabled) setPreferredSize(new Dimension(font.getSize(), component.getHeight()));
                }
            }else{
                if (!foldingFont.equals(font)){
                    font = foldingFont;
                    if (enabled) setPreferredSize(new Dimension(font.getSize(), component.getHeight()));
                }
            }
            
            repaint();
        }
    }
    
    protected void collectPaintInfos(Fold fold, Map map, int level, int startIndex, int endIndex){
        View rootView = Utilities.getDocumentView(component);
        if (rootView == null) return;
        
        
        for (int i=0; i=startIndex && startViewIndex<=endIndex)
                collectPaintInfos((Fold)fold.getFold(i), map, level+1, startIndex, endIndex);
        }
        int foldStartOffset = fold.getStartOffset();
        int foldEndOffset = fold.getEndOffset();
        int docLength = rootView.getDocument().getLength();
        
        if (foldEndOffset > docLength) return;
        
        int startViewIndex = rootView.getViewIndex(foldStartOffset, Position.Bias.Forward);
        int endViewIndex = rootView.getViewIndex(foldEndOffset, Position.Bias.Forward);

        try{
            View view;
            BaseTextUI textUI = (BaseTextUI)component.getUI();
            Shape viewShape;
            Rectangle viewRect;
            int markY = -1;
            int y=-1;
            
            // PAINT_MARK
            if (startIndex <= startViewIndex){
                view = rootView.getView(startViewIndex);
                viewShape = textUI.modelToView(component, view.getStartOffset());
                if (viewShape != null) {
                    viewRect = viewShape.getBounds();
                    y = viewRect.y + viewRect.height;
                    boolean isSingleLineFold = startViewIndex == endViewIndex;
                    if (fold.isCollapsed() || isSingleLineFold){
                        map.put(new Integer(viewRect.y), 
                            new CodeFoldingSideBar.PaintInfo((isSingleLineFold?SINGLE_PAINT_MARK:PAINT_MARK), level, viewRect.y, viewRect.height, fold.isCollapsed()));
                        return;
                    }

                    markY = viewRect.y;
                    map.put(new Integer(viewRect.y), new CodeFoldingSideBar.PaintInfo(PAINT_MARK, level, viewRect.y, viewRect.height, fold.isCollapsed()));
                }
            }
            
            //PAINT_LINE
            if (level == 0){
                int loopStart = (startViewIndexendIndex)? endIndex : endViewIndex;
                viewRect = null;
                for (int i=loopStart; i<=loopEnd; i++){
                    view = rootView.getView(i);
                    //viewShape = textUI.modelToView(component, view.getStartOffset());
                    if (view instanceof DrawEngineLineView && y > -1){
                        int h = (int)((DrawEngineLineView)view).getLayoutMajorAxisPreferredSpan();
                        viewRect = new Rectangle(0, y, 0, h);
                        if (i -1 && level == 0){
                    int h = (int)((DrawEngineLineView)view).getLayoutMajorAxisPreferredSpan();
                    viewRect = new Rectangle(0, y, 0, h);
                    y += h;
                }else{
                    viewShape = textUI.modelToView(component, view.getStartOffset()); 
                    if (viewShape !=null){
                        viewRect = viewShape.getBounds();
                        y = viewRect.y + viewRect.height;
                    }
                }
                
                if (viewRect !=null && markY!=viewRect.y){
                    map.put(new Integer(viewRect.y), new CodeFoldingSideBar.PaintInfo(PAINT_END_MARK, level, viewRect.y, viewRect.height));
                }
            }
        }catch(BadLocationException ble){
            ble.printStackTrace();
        }
        
    }
    
    protected List getPaintInfo(int startPos, int endPos){
        List ret = new ArrayList();
        
        List foldList = getFoldList(startPos, endPos);
        if (foldList.size() == 0) {
            return ret;
        }

        BaseTextUI textUI = (BaseTextUI)component.getUI();
        javax.swing.text.Element rootElem = textUI.getRootView(component).getElement();
        View rootView = Utilities.getDocumentView(component);
        if (rootView == null) return ret;

        Document doc = component.getDocument();
        if (!(doc instanceof BaseDocument)) return ret;

        BaseDocument bDoc = (BaseDocument) doc;
        
        Map map = new HashMap();

        bDoc.readLock();

        try {
            FoldHierarchy hierarchy = FoldHierarchy.get(component);
            hierarchy.lock();
            try {

                int startViewIndex = rootView.getViewIndex(startPos,Position.Bias.Forward);
                int endViewIndex = rootView.getViewIndex(endPos,Position.Bias.Forward);

                for (int i=0; i 0){ //[PENDING]
                        g.drawLine(lineX, y, lineX, markY);
                        g.drawLine(lineX, markY + markSize, lineX, y + height);
                    }
                    visibleMarks.add(new Mark(markX, markY, markSize, isFolded));
                } else if (paintOperation == PAINT_LINE){
                    g.drawLine(lineX, y, lineX, y + height );
                } else if (paintOperation == PAINT_END_MARK){
                    g.drawLine(lineX, y, lineX, y + height/2);
                    g.drawLine(lineX, y + height/2, lineX + halfMarkSize, y + height/2);
                    if (paintInfo.getInnerLevel() > 0){//[PENDING]
                        g.drawLine(lineX, y + height/2, lineX, y + height);
                    }
                }
                
            }
        }catch(BadLocationException ble){
            ble.printStackTrace();
        }
    }

    private List getFoldList(int start, int end) {
        FoldHierarchy hierarchy = FoldHierarchy.get(component);
        
        hierarchy.lock();
        try {
            List ret = new ArrayList();
            Fold rootFold = hierarchy.getRootFold();
            int index = FoldUtilities.findFoldEndIndex(rootFold, start);
            int foldCount = rootFold.getFoldCount();
            while (index < foldCount) {
                Fold f = rootFold.getFold(index);
                if (f.getStartOffset() <= end) {
                    ret.add(f);
                } else {
                    break; // no more relevant folds
                }
                index++;
            }
            return ret;
        } finally {
            hierarchy.unlock();
        }
    }
    
    
    public class PaintInfo{
        
        int paintOperation;
        int innerLevel;
        int paintY;
        int paintHeight;
        boolean isCollapsed;
        
        public PaintInfo(int paintOperation, int innerLevel, int paintY, int paintHeight, boolean isCollapsed){
            this.paintOperation = paintOperation;
            this.innerLevel = innerLevel;
            this.paintY = paintY;
            this.paintHeight = paintHeight;
            this.isCollapsed = isCollapsed;
        }

        public PaintInfo(int paintOperation, int innerLevel, int paintY, int paintHeight){
            this(paintOperation, innerLevel, paintY, paintHeight, false);
        }
        
        public int getPaintOperation(){
            return paintOperation;
        }
        
        public int getInnerLevel(){
            return innerLevel;
        }
        
        public int getPaintY(){
            return paintY;
        }
        
        public int getPaintHeight(){
            return paintHeight;
        }
        
        public boolean isCollapsed(){
            return isCollapsed;
        }
        
        public void setPaintOperation(int paintOperation){
            this.paintOperation = paintOperation;
        }
        
        public void setInnerLevel(int innerLevel){
            this.innerLevel = innerLevel;
        }
        
        public String toString(){
            StringBuffer sb = new StringBuffer("");
            if (paintOperation == PAINT_NOOP){
                sb.append("PAINT_NOOP\n"); // NOI18N
            }else if (paintOperation == PAINT_MARK){
                sb.append("PAINT_MARK\n"); // NOI18N
            }else if (paintOperation == PAINT_LINE){
                sb.append("PAINT_LINE\n"); // NOI18N
            }else if (paintOperation == PAINT_END_MARK) {
                sb.append("PAINT_END_MARK\n"); // NOI18N
            }
            sb.append("level:"+innerLevel); // NOI18N
            sb.append("\ncollapsedFold:"+isCollapsed); // NOI18N
            return sb.toString();
        }
    }
    
    /** Keeps info of visible folding mark */
    public class Mark{
        public int x;
        public int y;
        public int size;
        public boolean isFolded;
        
        public Mark(int x, int y, int size, boolean isFolded){
            this.x = x;
            this.y = y;
            this.size = size;
            this.isFolded = isFolded;
        }
    }
    
    /** Listening on clicking on folding marks */
    class FoldingMouseListener extends MouseAdapter {
        
        public FoldingMouseListener(){
            super();
        }

        public void mousePressed (MouseEvent e) {
            if (e == null || !SwingUtilities.isLeftMouseButton(e)) return;
            int x = e.getX();
            int y = e.getY();
            for (int i=0; i= mark.x && x <= (mark.x + mark.size) && y >= mark.y && y <= (mark.y + mark.size)){
                    performAction(mark);
                }
            }
        }
    }

    class SideBarFoldHierarchyListener implements FoldHierarchyListener{
    
        public SideBarFoldHierarchyListener(){
        }
        
        public void foldHierarchyChanged(FoldHierarchyEvent evt) {
            repaint();
        }
    }
    
}
... 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.