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

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

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

package org.netbeans.editor;

import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import javax.swing.event.DocumentEvent;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.Position;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import org.netbeans.editor.view.spi.EstimatedSpanView;
import org.netbeans.editor.view.spi.LockView;
import org.netbeans.editor.view.spi.ViewLayoutState;
import org.netbeans.editor.view.spi.ViewUtilities;
import org.openide.ErrorManager;

/**
 * Line view implementation. It works over LineElement and 
 * delegates drawing to DrawEngine.
 *
 * @author  Martin Roskanin
 */
class DrawEngineLineView extends View implements ViewLayoutState, EstimatedSpanView {
    
    /**
     * Bit that indicates whether x is the major axis.
     */
    private static final int X_MAJOR_AXIS_BIT = 1;
    
    /**
     * Bit that indicates that the major axis info is valid.
     */
    private static final int MAJOR_AXIS_PREFERENCE_CHANGED_BIT = 2;

    /**
     * Bit that indicates that the minor axis info is valid.
     */
    private static final int MINOR_AXIS_PREFERENCE_CHANGED_BIT = 4;
    
    /**
     * Bit that indicates that size of the view is valid.
     */
    private static final int VIEW_SIZE_INVALID_BIT = 8;
    
    /**
     * Bit value in statusBits determining
     * whether there is a pending layout update scheduled
     * for this layout state.
     */
    private static final int UPDATE_LAYOUT_PENDING_BIT = 16;
    
    private static final int ESTIMATED_SPAN_BIT = 32;

    protected static final int LAST_USED_BIT = ESTIMATED_SPAN_BIT;

    /**
     * Bit composition being used to test whether 
     * the layout is up-to-date or not.
     */
    private static final int ANY_INVALID
        = MAJOR_AXIS_PREFERENCE_CHANGED_BIT
        | MINOR_AXIS_PREFERENCE_CHANGED_BIT
        | VIEW_SIZE_INVALID_BIT;


    private int statusBits; // 4 bytes

    private int viewRawIndex; // 8 bytes

    private double layoutMajorAxisRawOffset; // double => 16 bytes

    // major axis
    private float layoutMajorAxisPreferredSpan; // 20 bytes
    
    // minor axis
    private float layoutMinorAxisPreferredSpan; // 24 bytes
    
    
    /** Draw graphics for converting position to coords */
    ModelToViewDG modelToViewDG; // 28 bytes
    
    /** Draw graphics for converting coords to position */
    ViewToModelDG viewToModelDG; // 32 bytes
    

    public DrawEngineLineView(Element elem) {
        super(elem);
    }
    
    private int getBaseX(int orig) {
        return orig + getEditorUI().getTextMargin().left;
    }
    
    private JTextComponent getComponent() {
        return (JTextComponent)getContainer();
    }
    
    private BaseTextUI getBaseTextUI(){
        return (BaseTextUI)getComponent().getUI();
    }
    
    private EditorUI getEditorUI(){
        return getBaseTextUI().getEditorUI();
    }
    
    private ModelToViewDG getModelToViewDG() {
        if (modelToViewDG == null) {
            modelToViewDG = new ModelToViewDG();
        }
        return modelToViewDG;
    }
    
    private ViewToModelDG getViewToModelDG() {
        if (viewToModelDG == null) {
            viewToModelDG = new ViewToModelDG();
        }
        return viewToModelDG;
    }
    
    public boolean isEstimatedSpan() {
        return isStatusBitsNonZero(ESTIMATED_SPAN_BIT);
    }
    
    public void setEstimatedSpan(boolean estimatedSpan) {
        if (isEstimatedSpan() != estimatedSpan) { // really changed
            if (estimatedSpan) {
                setStatusBits(ESTIMATED_SPAN_BIT);
            } else { // changing from true to false
                clearStatusBits(ESTIMATED_SPAN_BIT);

                getParent().preferenceChanged(this, true, true);
            }
        }
    }
    
    protected boolean isFragment(){
        return false;
    }
    
    /**
     * Get the offset prior to ending '\n' in the corresponding line element.
     */
    private int getEOLffset(){
        return super.getEndOffset() - 1; // offset prior to ending '\n'
    }
    
    /**
     * Get either the EOL offset or the end of the fragment
     * if the fragment is inside the view.
     */
    private int getAdjustedEOLOffset() {
        return Math.min(getEndOffset(), getEOLffset());
    }
    
    public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
        preferenceChanged(this, true, false);
    }
    
    public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
        preferenceChanged(this, true, false);
    }
    
    public float getAlignment(int axis) {
	return 0f;
    }
    
    public void paint(Graphics g, Shape a) {
        if (!(getDocument() instanceof BaseDocument)) return; //#48134
        // When painting make sure the estimated span is set to false
        setEstimatedSpan(false);
        // No modifications to allocReadOnly variable!
        Rectangle allocReadOnly = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
        int startOffset = getStartOffset();
        int endOffset = getAdjustedEOLOffset();
        try{
            if (isFragment()){
                Rectangle oldClipRect = g.getClipBounds();                
                Rectangle newClip = new Rectangle(oldClipRect);
                Rectangle startOffsetClip = modelToView(startOffset, a, Position.Bias.Forward).getBounds();
                Rectangle endOffsetClip = modelToView(endOffset, a, Position.Bias.Forward).getBounds();
                newClip.width = Math.min(oldClipRect.width, endOffsetClip.x);
                
                if (newClip.width + newClip.x > endOffsetClip.x) {
                    newClip.width = newClip.width - (newClip.width + newClip.x - endOffsetClip.x);
                }
                
                g.setClip(newClip);

                int shift = startOffsetClip.x - getEditorUI().getTextMargin().left - allocReadOnly.x;
                g.translate(-shift,0);
                
                DrawEngine.getDrawEngine().draw(this, new DrawGraphics.GraphicsDG(g),
                getEditorUI(), startOffset, endOffset,
                getBaseX(allocReadOnly.x), allocReadOnly.y, Integer.MAX_VALUE);
                
                g.translate(shift,0);                
                g.setClip(oldClipRect);

            }else{
                JTextComponent component = getComponent();
                if (component!=null){
                    DrawEngine drawEngine = (DrawEngine)component.getClientProperty(DrawEngine.PreinitializedDrawEngine.class);
                    if (drawEngine != null){
                        drawEngine.draw(this, new DrawGraphics.GraphicsDG(g),
                        getEditorUI(), startOffset, endOffset,
                        getBaseX(allocReadOnly.x), allocReadOnly.y, Integer.MAX_VALUE);
                    }else{
                        DrawEngine.getDrawEngine().draw(this, new DrawGraphics.GraphicsDG(g),
                        getEditorUI(), startOffset, endOffset,
                        getBaseX(allocReadOnly.x), allocReadOnly.y, Integer.MAX_VALUE);
                    }

                }
            }
        }catch(BadLocationException ble){
            ble.printStackTrace();
        }
    }
    
    public float getPreferredSpan(int axis) {
        switch (axis) {
            case Y_AXIS:
                /*try{
                    Shape retShape = modelToView(getStartOffset(), new Rectangle(), Position.Bias.Forward);
                    int ret = retShape.getBounds().height;
                    return Math.max(ret, 1f);
                }catch(BadLocationException ble){
                    ble.printStackTrace();
                }
                 */
                return getEditorUI().getLineHeight();
            case X_AXIS:
                try{
                    int offset = Math.max(0, getEndOffset() - 1);
                    Shape retShape = modelToView(offset, new Rectangle(), Position.Bias.Forward);
                    int ret = retShape.getBounds().x + retShape.getBounds().width;
                    return Math.max(ret, 1f);
                }catch(BadLocationException ble){
                    ble.printStackTrace();
                }
        }
        
        return 1f;
    }
    
    private Rectangle getModel2ViewRect(int startOffset, int endOffset, int startX, int startY, int targetOffset){
        Rectangle ret = new Rectangle();
        ret.y = startY;
        if (isEstimatedSpan()) {
            ret.height = getEditorUI().getLineHeight();
            ret.x = startX;
            ret.width = 1;

        } else { // exact measurements
            try{
                ModelToViewDG modelToViewDG = getModelToViewDG();
//                synchronized (modelToViewDG){ - view access is single-threaded
                    modelToViewDG.r = ret; // set the current rectangle
                    DrawEngine.getDrawEngine().draw(this, modelToViewDG, getEditorUI(),
                        startOffset, endOffset,
                        startX, startY, targetOffset);
                    LockView lv = LockView.get(this);
                    if (lv!=null && (lv.getLockThread() != Thread.currentThread())){
                        throw new IllegalStateException("View access without view lock"); // NOI18N
                    }
                    modelToViewDG.r = null;
//                }
            }catch(BadLocationException ble){
                ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ble);
            }
        }
        return ret;
    }
    
    public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
        BaseDocument doc = (BaseDocument)getDocument();
        if (pos < 0 || pos > doc.getLength()) {
            throw new BadLocationException("Invalid offset=" + pos, pos); // NOI18N
        }

        // No modifications to allocReadOnly variable!
        Rectangle allocReadOnly = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
        Rectangle ret = getModel2ViewRect(
            getStartOffset(), 
            getAdjustedEOLOffset(),
            getBaseX(allocReadOnly.x), 
            allocReadOnly.y,
            pos
        );
        
        return ret;
    }
    
    public int viewToModel(float x, float y, Shape a, Position.Bias[] biasReturn) {
        if (isEstimatedSpan()) {
            return getStartOffset();
        }

        int intX = (int)x;
        int intY = (int)y;
        if (biasReturn != null) {
            biasReturn[0] = Position.Bias.Forward;
        }
        int pos = getStartOffset();
        Rectangle shapeRect = (a!=null) ? a.getBounds() :  new Rectangle();
        
        try {
            int eolPos = getAdjustedEOLOffset();
            ViewToModelDG viewToModelDG = getViewToModelDG();
//            synchronized (viewToModelDG) { - view access is single-threaded
                viewToModelDG.setTargetX(intX);
                viewToModelDG.setEOLOffset(eolPos);
                DrawEngine.getDrawEngine().draw(this, viewToModelDG, getEditorUI(), getStartOffset() , eolPos,
                getBaseX(0) + shapeRect.x, shapeRect.y, -1);
                pos = viewToModelDG.getOffset();
                return pos;
//            }
        } catch (BadLocationException e) {
            // return begining of line in this case
        }
        return pos;
    }

    
    final class ViewToModelDG extends DrawGraphics.SimpleDG {

        int targetX;
        int offset;
        int eolOffset;
        
        void setTargetX(int targetX) {
            this.targetX = targetX;
        }
        
        void setEOLOffset(int eolOffset) {
            this.eolOffset = eolOffset;
            this.offset = eolOffset;
        }
        
        int getOffset() {
            return offset;
        }
        
        public boolean targetOffsetReached(int offset, char ch, int x,
        int charWidth, DrawContext ctx) {

            if (offset <= eolOffset) {
                if (x + charWidth < targetX) {
                    this.offset = offset;
                    return true;
                } else { // target position inside the char
                    this.offset = offset;
                    if (targetX > x + charWidth / 2) {
                        Document doc = getDocument();
                        if (ch != '\n' && doc != null && offset < doc.getLength()) {
                            this.offset++;
                        }
                    }
                    return false;
                }
            }
            return false;
        }
    }
    
    final class ModelToViewDG extends DrawGraphics.SimpleDG {
        
        Rectangle r;
        
        public boolean targetOffsetReached(int pos, char ch, int x,
        int charWidth, DrawContext ctx) {
            r.x = x;
            r.y = getY();
            r.width = charWidth;
            r.height = getEditorUI().getLineHeight();
            return false;
        }
        
    }

    
    public View createFragment(int p0, int p1){
        Element elem = getElement();
        return  // necessary conditions in accordance with javadoc
                p0>=0 && p0>=elem.getStartOffset() && p00 && p1<=elem.getEndOffset() && p1>elem.getStartOffset() &&
                // create fragment only if one of the element differs from valid start or end offset
                (p0!=elem.getStartOffset() || p1!=elem.getEndOffset()) ?
                    new FragmentView(getElement(), p0 - elem.getStartOffset(), p1 - p0) :
                    this;
    }

    public double getLayoutMajorAxisPreferredSpan() {
        return layoutMajorAxisPreferredSpan;
    }    
    
    public float getLayoutMajorAxisPreferredSpanFloat() {
        return layoutMajorAxisPreferredSpan;
    }

    protected void setLayoutMajorAxisPreferredSpan(float layoutMajorAxisPreferredSpan) {
        this.layoutMajorAxisPreferredSpan = layoutMajorAxisPreferredSpan;
    }
    
    public double getLayoutMajorAxisRawOffset() {
        return layoutMajorAxisRawOffset;
    }
    
    public void setLayoutMajorAxisRawOffset(double layoutMajorAxisRawOffset) {
        this.layoutMajorAxisRawOffset = layoutMajorAxisRawOffset;
    }
    
    public float getLayoutMinorAxisAlignment() {
        return getAlignment(getMinorAxis()); // not cached
    }
    
    public float getLayoutMinorAxisMaximumSpan() {
        return getLayoutMinorAxisPreferredSpan();
    }
    
    public float getLayoutMinorAxisMinimumSpan() {
        return getLayoutMinorAxisPreferredSpan();
    }
    
    public float getLayoutMinorAxisPreferredSpan() {
        return layoutMinorAxisPreferredSpan;
    }
    
    protected void setLayoutMinorAxisPreferredSpan(float layoutMinorAxisPreferredSpan) {
        this.layoutMinorAxisPreferredSpan = layoutMinorAxisPreferredSpan;
    }

    public View getView() {
        return this;
    }
    
    public int getViewRawIndex() {
        return viewRawIndex;
    }
    
    public void setViewRawIndex(int viewRawIndex) {
        this.viewRawIndex = viewRawIndex;
    }
    
    public boolean isFlyweight() {
        return false;
    }
    
    public ViewLayoutState selectLayoutMajorAxis(int majorAxis) {
//        assert ViewUtilities.isAxisValid(majorAxis);

        if (majorAxis == View.X_AXIS) {
            setStatusBits(X_MAJOR_AXIS_BIT);
        } else { // y axis
            clearStatusBits(X_MAJOR_AXIS_BIT);
        }
        
        return this;
    }
    
    protected final ViewLayoutState.Parent getLayoutStateParent() {
        View parent = getView().getParent();
        return (parent instanceof ViewLayoutState.Parent)
            ? ((ViewLayoutState.Parent)parent)
            : null;
    }

    public void updateLayout() {
        // First check whether the layout still need updates
        if (isLayoutValid()) {
            return; // nothing to do
        }

        ViewLayoutState.Parent lsParent = getLayoutStateParent();
        if (lsParent == null) {
            return;
        }

        // Check whether minor axis has changed
        if (isStatusBitsNonZero(MINOR_AXIS_PREFERENCE_CHANGED_BIT)) { // minor not valid
            clearStatusBits(MINOR_AXIS_PREFERENCE_CHANGED_BIT);

            int minorAxis = getMinorAxis();
            if (minorAxisUpdateLayout(minorAxis)) {
                lsParent.minorAxisPreferenceChanged(this);
            }
        }

        // Check whether major axis has changed
        if (isStatusBitsNonZero(MAJOR_AXIS_PREFERENCE_CHANGED_BIT)) { // major not valid
            clearStatusBits(MAJOR_AXIS_PREFERENCE_CHANGED_BIT);

            float oldSpan = getLayoutMajorAxisPreferredSpanFloat();
            float newSpan = getPreferredSpan(getMajorAxis());
            setLayoutMajorAxisPreferredSpan(newSpan);
            double majorAxisSpanDelta = newSpan - oldSpan;
            if (majorAxisSpanDelta != 0) {
                lsParent.majorAxisPreferenceChanged(this, majorAxisSpanDelta);
            }
        }

        // Check whether size must be set on the view
        if (isStatusBitsNonZero(VIEW_SIZE_INVALID_BIT)) {
            clearStatusBits(VIEW_SIZE_INVALID_BIT);

            float width;
            float height;
            float majorAxisSpan = (float)getLayoutMajorAxisPreferredSpan();
            float minorAxisSpan = lsParent.getMinorAxisSpan(this);
            if (isXMajorAxis()) { // x is major axis
                width = majorAxisSpan;
                height = minorAxisSpan;
            } else {
                width = minorAxisSpan;
                height = majorAxisSpan;
            }

            setSize(width, height);
        }
        
        // Possibly update layout again
        updateLayout();
    }
    
    protected boolean minorAxisUpdateLayout(int minorAxis) {
        boolean minorAxisPreferenceChanged = false;
        float val;
        
        val = getPreferredSpan(minorAxis);
        if (val != getLayoutMinorAxisPreferredSpan()) {
            setLayoutMinorAxisPreferredSpan(val);
            minorAxisPreferenceChanged = true;
        }
        
        return minorAxisPreferenceChanged;
    }

    public void viewPreferenceChanged(boolean width, boolean height) {
        if (isXMajorAxis()) { // x is major axis
            if (width) {
                setStatusBits(MAJOR_AXIS_PREFERENCE_CHANGED_BIT); // major no longer valid
            }
            if (height) {
                setStatusBits(MINOR_AXIS_PREFERENCE_CHANGED_BIT); // minor no longer valid
            }
        } else {
            if (width) {
                setStatusBits(MINOR_AXIS_PREFERENCE_CHANGED_BIT); // minor no longer valid
            }
            if (height) {
                setStatusBits(MAJOR_AXIS_PREFERENCE_CHANGED_BIT); // major no longer valid
            }
        }
        setStatusBits(VIEW_SIZE_INVALID_BIT); // child size no longer valid
    }
    
    public void markViewSizeInvalid() {
        setStatusBits(VIEW_SIZE_INVALID_BIT);
    }

    public boolean isLayoutValid() {
        return !isStatusBitsNonZero(ANY_INVALID);
    }

    protected final boolean isXMajorAxis() {
        return isStatusBitsNonZero(X_MAJOR_AXIS_BIT);
    }
    
    protected final int getMajorAxis() {
        return isXMajorAxis() ? View.X_AXIS : View.Y_AXIS;
    }
    
    protected final int getMinorAxis() {
        return isXMajorAxis() ? View.Y_AXIS : View.X_AXIS;
    }
    
    protected final int getStatusBits(int bits) {
        return (statusBits & bits);
    }
    
    protected final boolean isStatusBitsNonZero(int bits) {
        return (getStatusBits(bits) != 0);
    }
    
    protected final void setStatusBits(int bits) {
        statusBits |= bits;
    }
    
    protected final void clearStatusBits(int bits) {
        statusBits &= ~bits;
    }

    
    /** Fragment View of DrawEngineLineView, typicaly created via createFragment method */
    static class FragmentView extends DrawEngineLineView{
        
        private Position startPos;
        private Position endPos;
        
        public FragmentView(Element elem, int offset, int length){
            super(elem);
            try {
                Document doc = elem.getDocument();
                this.startPos = doc.createPosition(super.getStartOffset() + offset);
                this.endPos = doc.createPosition(startPos.getOffset() + length);
            } catch (BadLocationException e) {
                ErrorManager.getDefault().notify(e);
            }
        }

        protected boolean isFragment(){
            return true;
        }

        public int getStartOffset() {
            return startPos.getOffset();
        }
        
        public int getEndOffset() {
            return endPos.getOffset();
        }
        
    }
    
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy