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