0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans.editor;
0043:
0044: import java.awt.Graphics;
0045: import java.awt.Rectangle;
0046: import java.awt.Shape;
0047: import java.util.logging.Level;
0048: import java.util.logging.Logger;
0049: import javax.swing.event.DocumentEvent;
0050: import javax.swing.text.BadLocationException;
0051: import javax.swing.text.Document;
0052: import javax.swing.text.Element;
0053: import javax.swing.text.JTextComponent;
0054: import javax.swing.text.Position;
0055: import javax.swing.text.View;
0056: import javax.swing.text.ViewFactory;
0057: import org.netbeans.editor.view.spi.EstimatedSpanView;
0058: import org.netbeans.editor.view.spi.LockView;
0059: import org.netbeans.editor.view.spi.ViewLayoutState;
0060: import org.netbeans.lib.editor.util.ArrayUtilities;
0061: import org.netbeans.lib.editor.view.GapDocumentView;
0062:
0063: /**
0064: * Line view implementation. It works over LineElement and
0065: * delegates drawing to DrawEngine.
0066: *
0067: * @author Martin Roskanin
0068: */
0069: /* package */class DrawEngineLineView extends View implements
0070: ViewLayoutState, EstimatedSpanView {
0071:
0072: private static final Logger LOG = Logger
0073: .getLogger(DrawEngineLineView.class.getName());
0074: private static final boolean loggable = LOG
0075: .isLoggable(Level.FINEST);
0076: private static final long PERF_TRESHOLD = Long.getLong(
0077: "DrawEngineLineView.PERF_TRESHOLD", -1); //NOI18N, log events lasting longer then PERF_TRASHOLD msec
0078:
0079: /**
0080: * Bit that indicates whether x is the major axis.
0081: */
0082: private static final int X_MAJOR_AXIS_BIT = 1;
0083:
0084: /**
0085: * Bit that indicates that the major axis info is valid.
0086: */
0087: private static final int MAJOR_AXIS_PREFERENCE_CHANGED_BIT = 2;
0088:
0089: /**
0090: * Bit that indicates that the minor axis info is valid.
0091: */
0092: private static final int MINOR_AXIS_PREFERENCE_CHANGED_BIT = 4;
0093:
0094: /**
0095: * Bit that indicates that size of the view is valid.
0096: */
0097: private static final int VIEW_SIZE_INVALID_BIT = 8;
0098:
0099: /**
0100: * Bit value in <code>statusBits</code> determining
0101: * whether there is a pending layout update scheduled
0102: * for this layout state.
0103: */
0104: private static final int UPDATE_LAYOUT_PENDING_BIT = 16;
0105:
0106: private static final int ESTIMATED_SPAN_BIT = 32;
0107:
0108: protected static final int LAST_USED_BIT = ESTIMATED_SPAN_BIT;
0109:
0110: /**
0111: * Bit composition being used to test whether
0112: * the layout is up-to-date or not.
0113: */
0114: private static final int ANY_INVALID = MAJOR_AXIS_PREFERENCE_CHANGED_BIT
0115: | MINOR_AXIS_PREFERENCE_CHANGED_BIT | VIEW_SIZE_INVALID_BIT;
0116:
0117: private int statusBits; // 4 bytes
0118:
0119: private int viewRawIndex; // 8 bytes
0120:
0121: private double layoutMajorAxisRawOffset; // double => 16 bytes
0122:
0123: // major axis
0124: private float layoutMajorAxisPreferredSpan; // 20 bytes
0125:
0126: // minor axis
0127: private float layoutMinorAxisPreferredSpan; // 24 bytes
0128:
0129: /** Draw graphics for converting position to coords */
0130: //ModelToViewDG modelToViewDG; // 28 bytes
0131: /** Draw graphics for converting coords to position */
0132: private ViewToModelDG viewToModelDG; // 32 bytes
0133:
0134: public DrawEngineLineView(Element elem) {
0135: super (elem);
0136: }
0137:
0138: private int getBaseX(int orig) {
0139: return orig + getEditorUI().getTextMargin().left;
0140: }
0141:
0142: private JTextComponent getComponent() {
0143: return (JTextComponent) getContainer();
0144: }
0145:
0146: private BaseTextUI getBaseTextUI() {
0147: return (BaseTextUI) getComponent().getUI();
0148: }
0149:
0150: private EditorUI getEditorUI() {
0151: return getBaseTextUI().getEditorUI();
0152: }
0153:
0154: private ModelToViewDG getModelToViewDG() {
0155: /* fix of issue #55419
0156: if (modelToViewDG == null) {
0157: modelToViewDG = new ModelToViewDG();
0158: }
0159: return modelToViewDG;
0160: */
0161: return new ModelToViewDG();
0162: }
0163:
0164: private ViewToModelDG getViewToModelDG() {
0165: if (viewToModelDG == null) {
0166: viewToModelDG = new ViewToModelDG();
0167: }
0168: return viewToModelDG;
0169: }
0170:
0171: public boolean isEstimatedSpan() {
0172: return isStatusBitsNonZero(ESTIMATED_SPAN_BIT);
0173: }
0174:
0175: public void setEstimatedSpan(boolean estimatedSpan) {
0176: if (isEstimatedSpan() != estimatedSpan) { // really changed
0177: if (estimatedSpan) {
0178: setStatusBits(ESTIMATED_SPAN_BIT);
0179: } else { // changing from true to false
0180: clearStatusBits(ESTIMATED_SPAN_BIT);
0181:
0182: getParent().preferenceChanged(this , true, true);
0183: }
0184: }
0185: }
0186:
0187: protected boolean isFragment() {
0188: return false;
0189: }
0190:
0191: /**
0192: * Get the offset prior to ending '\n' in the corresponding line element.
0193: */
0194: private int getEOLffset() {
0195: return super .getEndOffset() - 1; // offset prior to ending '\n'
0196: }
0197:
0198: /**
0199: * Get either the EOL offset or the end of the fragment
0200: * if the fragment is inside the view.
0201: */
0202: private int getAdjustedEOLOffset() {
0203: return Math.min(getEndOffset(), getEOLffset());
0204: }
0205:
0206: public @Override
0207: void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
0208: resetMarkers(e.getOffset());
0209: preferenceChanged(this , true, false);
0210: }
0211:
0212: public @Override
0213: void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
0214: resetMarkers(e.getOffset());
0215: preferenceChanged(this , true, false);
0216: }
0217:
0218: public @Override
0219: float getAlignment(int axis) {
0220: return 0f;
0221: }
0222:
0223: public void paint(Graphics g, Shape a) {
0224: if (!(getDocument() instanceof BaseDocument))
0225: return; //#48134
0226: // When painting make sure the estimated span is set to false
0227: setEstimatedSpan(false);
0228: // No modifications to allocReadOnly variable!
0229: Rectangle allocReadOnly = (a instanceof Rectangle) ? (Rectangle) a
0230: : a.getBounds();
0231: int startOffset = getStartOffset();
0232: int endOffset = getAdjustedEOLOffset();
0233: try {
0234: if (isFragment()) {
0235: Rectangle oldClipRect = g.getClipBounds();
0236: Rectangle newClip = new Rectangle(oldClipRect);
0237: Rectangle startOffsetClip = modelToView(startOffset, a,
0238: Position.Bias.Forward).getBounds();
0239: Rectangle endOffsetClip = modelToView(endOffset, a,
0240: Position.Bias.Forward).getBounds();
0241: View parent = getParent();
0242: if (parent instanceof FoldMultiLineView
0243: && !equals(parent
0244: .getView(parent.getViewCount() - 1))) {
0245: newClip.width = Math.min(oldClipRect.width,
0246: endOffsetClip.x);
0247:
0248: if (newClip.width + newClip.x > endOffsetClip.x) {
0249: newClip.width = newClip.width
0250: - (newClip.width + newClip.x - endOffsetClip.x);
0251: }
0252:
0253: g.setClip(newClip);
0254: }
0255:
0256: int shift = startOffsetClip.x
0257: - getEditorUI().getTextMargin().left
0258: - allocReadOnly.x;
0259: g.translate(-shift, 0);
0260:
0261: DrawEngine.getDrawEngine().draw(this ,
0262: new DrawGraphics.GraphicsDG(g), getEditorUI(),
0263: startOffset, endOffset,
0264: getBaseX(allocReadOnly.x), allocReadOnly.y,
0265: Integer.MAX_VALUE);
0266:
0267: g.translate(shift, 0);
0268: g.setClip(oldClipRect);
0269:
0270: } else {
0271: JTextComponent component = getComponent();
0272: if (component != null) {
0273: long ts1 = 0, ts2 = 0;
0274: if (loggable) {
0275: ts1 = System.currentTimeMillis();
0276: }
0277:
0278: // Translate the graphics clip region to the document offsets
0279: // and their view region
0280: Rectangle clip = g.getClipBounds();
0281: int fromOffset = viewToModel(clip.x, clip.y,
0282: allocReadOnly, null);
0283: int toOffset = viewToModel(clip.x + clip.width,
0284: clip.y, allocReadOnly, null);
0285:
0286: fromOffset = Math.max(fromOffset - 1,
0287: getStartOffset());
0288: toOffset = Math.min(toOffset + 1,
0289: getAdjustedEOLOffset());
0290:
0291: Rectangle rr = modelToView(fromOffset,
0292: allocReadOnly, Position.Bias.Forward)
0293: .getBounds();
0294:
0295: DrawEngine.getDrawEngine().draw(this ,
0296: new DrawGraphics.GraphicsDG(g),
0297: getEditorUI(), fromOffset, toOffset, rr.x,
0298: rr.y, Integer.MAX_VALUE);
0299:
0300: if (loggable) {
0301: ts2 = System.currentTimeMillis();
0302: if (ts2 - ts1 > PERF_TRESHOLD) {
0303: LOG.finest("paint: " + //NOI18N
0304: "<"
0305: + fromOffset
0306: + ", "
0307: + toOffset
0308: + ">, "
0309: + //NOI18N
0310: "DrawEngine.startX = "
0311: + rr.x
0312: + ", DrawEngine.startY = "
0313: + rr.y
0314: + ", "
0315: + //NOI18N
0316: "shape = [" + allocReadOnly.x
0317: + ", " + allocReadOnly.y + ", "
0318: + allocReadOnly.width + ", "
0319: + allocReadOnly.height
0320: + "], "
0321: + //NOI18N
0322: "clip = [" + clip.x + ", " + clip.y
0323: + ", " + clip.width + ", "
0324: + clip.height + "] " + //NOI18N
0325: "took " + (ts2 - ts1) + " msec"); //NOI18N
0326: }
0327: }
0328: }
0329: }
0330: } catch (BadLocationException ble) {
0331: LOG.log(Level.INFO, "Painting the view failed", ble); //NOI18N
0332: }
0333: }
0334:
0335: public float getPreferredSpan(int axis) {
0336: switch (axis) {
0337: case Y_AXIS:
0338: return getEditorUI().getLineHeight();
0339: case X_AXIS:
0340: // try {
0341: int offset = Math.max(0, getEndOffset() - 1);
0342: Shape retShape = modelToView(offset, new Rectangle(),
0343: Position.Bias.Forward, false);
0344: int ret = retShape.getBounds().x
0345: + retShape.getBounds().width;
0346: return Math.max(ret, 1f);
0347: // } catch (BadLocationException ble) {
0348: // LOG.log(Level.INFO, "Can't determine x-axis span", ble); //NOI18N
0349: // }
0350: }
0351:
0352: return 1f;
0353: }
0354:
0355: // Markers are placed every MARKERS_DIST characters. The first marker
0356: // at the startOffset and has x-coordinate 0 (in the view's internal coordinate system).
0357: // Each marker's value is its distance from the beginning of the view in the
0358: // view's internal coordinate system. Please note that the view's internal
0359: // coordinate system is different from the JTextComponent's system, which is
0360: // for example the system of x, y and shape parameters passed to m2v and v2m methods.
0361: private static final int MARKERS_DIST;
0362: static {
0363: int markersDist = 128;
0364: try {
0365: markersDist = (Integer) Class.forName(
0366: "org.netbeans.editor.DrawEngineTest").getField(
0367: "TEST_MARKERS_DIST").get(null); //NOI18N
0368: } catch (Exception e) {
0369: // ignore
0370: }
0371: MARKERS_DIST = markersDist;
0372: LOG.fine("DrawEngineLineView.MARKERS_DIST = " + MARKERS_DIST); //NOI18N
0373: }
0374: private int[] markers = new int[] { 0 };
0375: private int markersLength = markers.length;
0376:
0377: public void highlightsChanged(int changeStart, int changeEnd) {
0378: checkViewAccess();
0379: resetMarkers(changeStart);
0380: preferenceChanged(this , true, false);
0381: }
0382:
0383: private void resetMarkers(int offset) {
0384: if (offset < getStartOffset() || offset > getEndOffset()) {
0385: // may happen when undo, see #115122
0386: markersLength = 1;
0387: } else {
0388: markersLength = Math.min(markersLength,
0389: (offset - getStartOffset()) / MARKERS_DIST + 1);
0390: }
0391:
0392: if (loggable) {
0393: LOG.finest("resetMarkers: "
0394: + //NOI18N
0395: "<" + getStartOffset() + ", " + getEndOffset()
0396: + ">, offset = " + offset + //NOI18N
0397: " -> markersLength = " + markersLength); //NOI18N
0398: }
0399: }
0400:
0401: // startX, startY are in the JTextComponent's coordinate space
0402: private Rectangle getModel2ViewRect(int startOffset, int endOffset,
0403: int startX, int startY, int targetOffset) {
0404: long ts1 = 0, ts2 = 0;
0405: EditorUI eui = getEditorUI();
0406: Rectangle ret;
0407:
0408: View parent;
0409: if ((((parent = getParent()) instanceof GapDocumentView) && ((GapDocumentView) parent)
0410: .isPendingUpdate())
0411: || isEstimatedSpan()) {
0412: ret = new Rectangle(getBaseX(startX), startY, 1, eui
0413: .getLineHeight());
0414:
0415: } else {
0416: if (loggable) {
0417: ts1 = System.currentTimeMillis();
0418: }
0419:
0420: // XXX: nearly works for monospaced fonts, the only problem is with tabs
0421: // ret = new Rectangle(
0422: // getBaseX((targetOffset - startOffset) * charWidth + startX),
0423: // startY,
0424: // charWidth,
0425: // eui.getLineHeight()
0426: // );
0427:
0428: int targetMarkerIdx = (targetOffset - startOffset)
0429: / MARKERS_DIST;
0430: int markerIdx = Math
0431: .min(targetMarkerIdx, markersLength - 1);
0432: int markerX = markers[markerIdx];
0433: int markerOffset = startOffset + markerIdx * MARKERS_DIST;
0434:
0435: ret = new Rectangle(getBaseX(markerX), startY, 1, eui
0436: .getLineHeight());
0437: try {
0438: ModelToViewDG g = getModelToViewDG();
0439: g.setRectangle(ret); // set the current rectangle
0440:
0441: if (markers.length <= targetMarkerIdx) {
0442: int[] arr = new int[targetMarkerIdx + 1];
0443: System
0444: .arraycopy(markers, 0, arr, 0,
0445: markers.length);
0446: markers = arr;
0447: }
0448:
0449: for (; markerIdx < targetMarkerIdx; markerIdx++) {
0450: DrawEngine.getDrawEngine().draw(this , g, eui,
0451: markerOffset, markerOffset + MARKERS_DIST,
0452: markerX, startY,
0453: markerOffset + MARKERS_DIST);
0454:
0455: markerOffset += MARKERS_DIST;
0456: markerX = ret.x;
0457: markers[markerIdx + 1] = markerX;
0458: }
0459:
0460: if (targetMarkerIdx >= markersLength) {
0461: markersLength = targetMarkerIdx + 1;
0462: }
0463:
0464: DrawEngine.getDrawEngine().draw(this , g, eui,
0465: markerOffset, endOffset,
0466: getBaseX(markerX + startX), startY,
0467: targetOffset);
0468:
0469: g.setRectangle(null);
0470: } catch (BadLocationException ble) {
0471: // Log and return an estimated view
0472: LOG.log(Level.INFO, "Model-to-view translation failed",
0473: ble); //NOI18N
0474: ret = new Rectangle(getBaseX(startX), startY, 1, eui
0475: .getLineHeight());
0476: }
0477:
0478: if (loggable) {
0479: ts2 = System.currentTimeMillis();
0480: }
0481: }
0482:
0483: if (loggable && ts2 - ts1 > PERF_TRESHOLD) {
0484: LOG.finest("m2v: " + //NOI18N
0485: "<" + startOffset + ", " + endOffset
0486: + ">, targetOffset = " + targetOffset
0487: + ", "
0488: + //NOI18N
0489: "[" + startX + ", " + startY
0490: + "] "
0491: + //NOI18N
0492: "-> [" + ret.getBounds().x + ", "
0493: + ret.getBounds().y + ", " + ret.getBounds().width
0494: + ", " + ret.getBounds().height + "]" + //NOI18N
0495: " took " + (ts2 - ts1) + " msec"); //NOI18N
0496: }
0497: return ret;
0498: }
0499:
0500: // shape is in the JTextComponent's coordinate space
0501: public Shape modelToView(int pos, Shape shape, Position.Bias b) {
0502: return modelToView(pos, shape, b, true); // ensure exact span (not estimated)
0503: }
0504:
0505: // shape is in the JTextComponent's coordinate space
0506: public Shape modelToView(int pos, Shape shape, Position.Bias bias,
0507: boolean exactSpan) {
0508: assert shape != null : "The shape parameter must not be null"; //NOI18N
0509: checkViewAccess();
0510:
0511: if (!(getDocument() instanceof BaseDocument)) {
0512: return new Rectangle();
0513: }
0514:
0515: if (exactSpan) { // ensure that span will not be estimated
0516: setEstimatedSpan(false);
0517: }
0518:
0519: if (bias == Position.Bias.Forward
0520: && (pos < super .getStartOffset() || pos >= super
0521: .getEndOffset())
0522: || bias == Position.Bias.Backward
0523: && (pos <= super .getStartOffset() || pos > super
0524: .getEndOffset())) {
0525: BadLocationException ble = new BadLocationException(
0526: "Invalid offset = "
0527: + pos //NOI18N
0528: + ", bias = "
0529: + bias //NOI18N
0530: + ", outside of the view <"
0531: + super .getStartOffset()
0532: + ", "
0533: + super .getEndOffset()
0534: + ">" //NOI18N
0535: + ", isFragment = "
0536: + isFragment() //NOI18N
0537: + (isFragment() ? ", fragment boundaries <"
0538: + getStartOffset() + ", "
0539: + getEndOffset() + ">" : ""), pos); // NOI18N
0540: LOG.log(Level.INFO, null, ble);
0541: return new Rectangle(getBaseX(shape.getBounds().x), shape
0542: .getBounds().y, 1, getEditorUI().getLineHeight());
0543: }
0544:
0545: if (isFragment()
0546: && (pos < getStartOffset() || pos > getEndOffset())) {
0547: BadLocationException ble = new BadLocationException(
0548: "Invalid offset = "
0549: + pos //NOI18N
0550: + ", bias = "
0551: + bias //NOI18N
0552: + ", outside of the fragment view" //NOI18N
0553: + " <" + getStartOffset() + ", "
0554: + getEndOffset() + ">", pos); // NOI18N
0555: LOG.log(Level.INFO, null, ble);
0556: return new Rectangle(getBaseX(shape.getBounds().x), shape
0557: .getBounds().y, 1, getEditorUI().getLineHeight());
0558: }
0559:
0560: if (bias == Position.Bias.Backward) {
0561: pos--;
0562: }
0563:
0564: Rectangle ret = getModel2ViewRect(getStartOffset(),
0565: getEndOffset(), shape.getBounds().x,
0566: shape.getBounds().y, pos);
0567:
0568: return ret;
0569: }
0570:
0571: // x, y, shape are in the JTextComponent's coordinate space
0572: public int viewToModel(float x, float y, Shape shape,
0573: Position.Bias[] biasReturn) {
0574: assert shape != null : "The shape parameter must not be null"; //NOI18N
0575: checkViewAccess();
0576:
0577: if (!(getDocument() instanceof BaseDocument)) {
0578: return 0;
0579: }
0580:
0581: long ts1 = 0, ts2 = 0;
0582: int pos = getStartOffset();
0583:
0584: if (biasReturn != null) {
0585: biasReturn[0] = Position.Bias.Forward;
0586: }
0587:
0588: if (!isEstimatedSpan() && x > shape.getBounds().x) {
0589: if (loggable) {
0590: ts1 = System.currentTimeMillis();
0591: }
0592:
0593: EditorUI eui = getEditorUI();
0594: int xx = Math.max(0, (int) x - shape.getBounds().x
0595: - eui.getTextMargin().left);
0596:
0597: // XXX: nearly works for monospaced fonts, the only problem is with tabs
0598: // int chars = xx / charWidth;
0599: // if (chars > getAdjustedEOLOffset() - getStartOffset()) {
0600: // chars = getAdjustedEOLOffset() - getStartOffset();
0601: // }
0602: // pos += chars;
0603:
0604: int markerIdx = ArrayUtilities.binarySearch(markers, 0,
0605: markersLength - 1, xx);
0606: if (markerIdx >= 0) {
0607: // hit the marker
0608: pos = getStartOffset() + markerIdx * MARKERS_DIST;
0609: } else {
0610: // get the index of the last marker before xx
0611: markerIdx = -markerIdx - 2;
0612: int markerX = markers[markerIdx];
0613: int markerOffset = getStartOffset() + markerIdx
0614: * MARKERS_DIST;
0615:
0616: try {
0617: ViewToModelDG g = getViewToModelDG();
0618:
0619: for (;;) {
0620: int nextOffset = Math.min(
0621: getAdjustedEOLOffset(), markerOffset
0622: + MARKERS_DIST - 1);
0623:
0624: g.setTargetX(xx);
0625: g.setEOLOffset(nextOffset);
0626:
0627: DrawEngine.getDrawEngine().draw(this , g, eui,
0628: markerOffset, nextOffset, markerX,
0629: shape.getBounds().y, -1);
0630:
0631: if (g.getX() >= xx
0632: || g.getOffset() >= getAdjustedEOLOffset()) {
0633: break;
0634: }
0635:
0636: markerOffset += MARKERS_DIST;
0637: markerX = g.getX();
0638:
0639: if (markerIdx + 1 >= markers.length) {
0640: int[] arr = new int[markers.length + 10];
0641: System.arraycopy(markers, 0, arr, 0,
0642: markers.length);
0643: markers = arr;
0644: }
0645: markers[++markerIdx] = markerX;
0646: markersLength = markerIdx + 1;
0647: }
0648:
0649: pos = Math.min(g.getOffset(),
0650: getAdjustedEOLOffset());
0651: } catch (BadLocationException ble) {
0652: // Log and return start offset
0653: LOG.log(Level.INFO,
0654: "View-to-model translation failed", ble); //NOI18N
0655: }
0656: }
0657:
0658: if (loggable) {
0659: ts2 = System.currentTimeMillis();
0660: }
0661: }
0662:
0663: if (loggable && ts2 - ts1 > PERF_TRESHOLD) {
0664: LOG.finest("v2m: "
0665: + //NOI18N
0666: "[" + x + ", "
0667: + y
0668: + "], "
0669: + //NOI18N
0670: "[" + shape.getBounds().x + ", "
0671: + shape.getBounds().y + ", "
0672: + shape.getBounds().width + ", "
0673: + shape.getBounds().height + "] " + //NOI18N
0674: "-> " + pos + //NOI18N
0675: " took " + (ts2 - ts1) + " msec"); //NOI18N
0676: }
0677: return pos;
0678: }
0679:
0680: private void checkViewAccess() {
0681: LockView view = LockView.get(this );
0682: if (view != null
0683: && (view.getLockThread() != Thread.currentThread())) {
0684: throw new IllegalStateException(
0685: "View access without view lock"); // NOI18N
0686: }
0687: }
0688:
0689: private final class ViewToModelDG extends DrawGraphics.SimpleDG {
0690:
0691: private int targetX;
0692: private int offset;
0693: private int eolOffset;
0694:
0695: public void setTargetX(int targetX) {
0696: this .targetX = targetX;
0697: }
0698:
0699: public void setEOLOffset(int eolOffset) {
0700: this .eolOffset = eolOffset;
0701: this .offset = eolOffset;
0702: }
0703:
0704: public int getOffset() {
0705: return offset;
0706: }
0707:
0708: public @Override
0709: boolean targetOffsetReached(int offset, char ch, int x,
0710: int charWidth, DrawContext ctx) {
0711: if (offset <= eolOffset) {
0712: if (x + charWidth < targetX) {
0713: this .offset = offset;
0714: return true;
0715: } else { // target position inside the char
0716: this .offset = offset;
0717: if (targetX > x + charWidth / 2) {
0718: Document doc = getDocument();
0719: if (ch != '\n' && doc != null
0720: && offset < doc.getLength()) { //NOI18N
0721: this .offset++;
0722: }
0723: }
0724: return false;
0725: }
0726: } else {
0727: return false;
0728: }
0729: }
0730:
0731: } // End of ViewToModelDG class
0732:
0733: private final class ModelToViewDG extends DrawGraphics.SimpleDG {
0734:
0735: private Rectangle r;
0736:
0737: public Rectangle getRectangle() {
0738: return r;
0739: }
0740:
0741: public void setRectangle(Rectangle r) {
0742: this .r = r;
0743: }
0744:
0745: public @Override
0746: boolean targetOffsetReached(int pos, char ch, int x,
0747: int charWidth, DrawContext ctx) {
0748: r.x = x;
0749: r.y = getY();
0750: r.width = charWidth;
0751: r.height = getEditorUI().getLineHeight();
0752: return false;
0753: }
0754: } // End of ModelToViewDG class
0755:
0756: public @Override
0757: View createFragment(int p0, int p1) {
0758: Element elem = getElement();
0759: return // necessary conditions in accordance with javadoc
0760: p0 >= 0 && p0 >= elem.getStartOffset()
0761: && p0 < elem.getEndOffset()
0762: && p1 > 0
0763: && p1 <= elem.getEndOffset()
0764: && p1 > elem.getStartOffset()
0765: &&
0766: // create fragment only if one of the element differs from valid start or end offset
0767: (p0 != elem.getStartOffset() || p1 != elem
0768: .getEndOffset()) ? new FragmentView(
0769: getElement(), p0 - elem.getStartOffset(), p1 - p0)
0770: : this ;
0771: }
0772:
0773: public double getLayoutMajorAxisPreferredSpan() {
0774: return layoutMajorAxisPreferredSpan;
0775: }
0776:
0777: public float getLayoutMajorAxisPreferredSpanFloat() {
0778: return layoutMajorAxisPreferredSpan;
0779: }
0780:
0781: protected void setLayoutMajorAxisPreferredSpan(
0782: float layoutMajorAxisPreferredSpan) {
0783: this .layoutMajorAxisPreferredSpan = layoutMajorAxisPreferredSpan;
0784: }
0785:
0786: public double getLayoutMajorAxisRawOffset() {
0787: return layoutMajorAxisRawOffset;
0788: }
0789:
0790: public void setLayoutMajorAxisRawOffset(
0791: double layoutMajorAxisRawOffset) {
0792: this .layoutMajorAxisRawOffset = layoutMajorAxisRawOffset;
0793: }
0794:
0795: public float getLayoutMinorAxisAlignment() {
0796: return getAlignment(getMinorAxis()); // not cached
0797: }
0798:
0799: public float getLayoutMinorAxisMaximumSpan() {
0800: return getLayoutMinorAxisPreferredSpan();
0801: }
0802:
0803: public float getLayoutMinorAxisMinimumSpan() {
0804: return getLayoutMinorAxisPreferredSpan();
0805: }
0806:
0807: public float getLayoutMinorAxisPreferredSpan() {
0808: return layoutMinorAxisPreferredSpan;
0809: }
0810:
0811: protected void setLayoutMinorAxisPreferredSpan(
0812: float layoutMinorAxisPreferredSpan) {
0813: this .layoutMinorAxisPreferredSpan = layoutMinorAxisPreferredSpan;
0814: }
0815:
0816: public View getView() {
0817: return this ;
0818: }
0819:
0820: public int getViewRawIndex() {
0821: return viewRawIndex;
0822: }
0823:
0824: public void setViewRawIndex(int viewRawIndex) {
0825: this .viewRawIndex = viewRawIndex;
0826: }
0827:
0828: public boolean isFlyweight() {
0829: return false;
0830: }
0831:
0832: public ViewLayoutState selectLayoutMajorAxis(int majorAxis) {
0833: // assert ViewUtilities.isAxisValid(majorAxis);
0834:
0835: if (majorAxis == View.X_AXIS) {
0836: setStatusBits(X_MAJOR_AXIS_BIT);
0837: } else { // y axis
0838: clearStatusBits(X_MAJOR_AXIS_BIT);
0839: }
0840:
0841: return this ;
0842: }
0843:
0844: protected final ViewLayoutState.Parent getLayoutStateParent() {
0845: View parent = getView().getParent();
0846: return (parent instanceof ViewLayoutState.Parent) ? ((ViewLayoutState.Parent) parent)
0847: : null;
0848: }
0849:
0850: public void updateLayout() {
0851: // First check whether the layout still need updates
0852: if (isLayoutValid()) {
0853: return; // nothing to do
0854: }
0855:
0856: ViewLayoutState.Parent lsParent = getLayoutStateParent();
0857: if (lsParent == null) {
0858: return;
0859: }
0860:
0861: // Check whether minor axis has changed
0862: if (isStatusBitsNonZero(MINOR_AXIS_PREFERENCE_CHANGED_BIT)) { // minor not valid
0863: clearStatusBits(MINOR_AXIS_PREFERENCE_CHANGED_BIT);
0864:
0865: int minorAxis = getMinorAxis();
0866: if (minorAxisUpdateLayout(minorAxis)) {
0867: lsParent.minorAxisPreferenceChanged(this );
0868: }
0869: }
0870:
0871: // Check whether major axis has changed
0872: if (isStatusBitsNonZero(MAJOR_AXIS_PREFERENCE_CHANGED_BIT)) { // major not valid
0873: clearStatusBits(MAJOR_AXIS_PREFERENCE_CHANGED_BIT);
0874:
0875: float oldSpan = getLayoutMajorAxisPreferredSpanFloat();
0876: float newSpan = getPreferredSpan(getMajorAxis());
0877: setLayoutMajorAxisPreferredSpan(newSpan);
0878: double majorAxisSpanDelta = newSpan - oldSpan;
0879: if (majorAxisSpanDelta != 0) {
0880: lsParent.majorAxisPreferenceChanged(this ,
0881: majorAxisSpanDelta);
0882: }
0883: }
0884:
0885: // Check whether size must be set on the view
0886: if (isStatusBitsNonZero(VIEW_SIZE_INVALID_BIT)) {
0887: clearStatusBits(VIEW_SIZE_INVALID_BIT);
0888:
0889: float width;
0890: float height;
0891: float majorAxisSpan = (float) getLayoutMajorAxisPreferredSpan();
0892: float minorAxisSpan = lsParent.getMinorAxisSpan(this );
0893: if (isXMajorAxis()) { // x is major axis
0894: width = majorAxisSpan;
0895: height = minorAxisSpan;
0896: } else {
0897: width = minorAxisSpan;
0898: height = majorAxisSpan;
0899: }
0900:
0901: setSize(width, height);
0902: }
0903:
0904: // Possibly update layout again
0905: updateLayout();
0906: }
0907:
0908: protected boolean minorAxisUpdateLayout(int minorAxis) {
0909: boolean minorAxisPreferenceChanged = false;
0910: float val;
0911:
0912: val = getPreferredSpan(minorAxis);
0913: if (val != getLayoutMinorAxisPreferredSpan()) {
0914: setLayoutMinorAxisPreferredSpan(val);
0915: minorAxisPreferenceChanged = true;
0916: }
0917:
0918: return minorAxisPreferenceChanged;
0919: }
0920:
0921: public void viewPreferenceChanged(boolean width, boolean height) {
0922: if (isXMajorAxis()) { // x is major axis
0923: if (width) {
0924: setStatusBits(MAJOR_AXIS_PREFERENCE_CHANGED_BIT); // major no longer valid
0925: }
0926: if (height) {
0927: setStatusBits(MINOR_AXIS_PREFERENCE_CHANGED_BIT); // minor no longer valid
0928: }
0929: } else {
0930: if (width) {
0931: setStatusBits(MINOR_AXIS_PREFERENCE_CHANGED_BIT); // minor no longer valid
0932: }
0933: if (height) {
0934: setStatusBits(MAJOR_AXIS_PREFERENCE_CHANGED_BIT); // major no longer valid
0935: }
0936: }
0937: setStatusBits(VIEW_SIZE_INVALID_BIT); // child size no longer valid
0938: }
0939:
0940: public void markViewSizeInvalid() {
0941: setStatusBits(VIEW_SIZE_INVALID_BIT);
0942: }
0943:
0944: public boolean isLayoutValid() {
0945: return !isStatusBitsNonZero(ANY_INVALID);
0946: }
0947:
0948: protected final boolean isXMajorAxis() {
0949: return isStatusBitsNonZero(X_MAJOR_AXIS_BIT);
0950: }
0951:
0952: protected final int getMajorAxis() {
0953: return isXMajorAxis() ? View.X_AXIS : View.Y_AXIS;
0954: }
0955:
0956: protected final int getMinorAxis() {
0957: return isXMajorAxis() ? View.Y_AXIS : View.X_AXIS;
0958: }
0959:
0960: protected final int getStatusBits(int bits) {
0961: return (statusBits & bits);
0962: }
0963:
0964: protected final boolean isStatusBitsNonZero(int bits) {
0965: return (getStatusBits(bits) != 0);
0966: }
0967:
0968: protected final void setStatusBits(int bits) {
0969: statusBits |= bits;
0970: }
0971:
0972: protected final void clearStatusBits(int bits) {
0973: statusBits &= ~bits;
0974: }
0975:
0976: /** Fragment View of DrawEngineLineView, typicaly created via createFragment method */
0977: private static final class FragmentView extends DrawEngineLineView {
0978:
0979: private Position startPos;
0980: private Position endPos;
0981:
0982: public FragmentView(Element elem, int offset, int length) {
0983: super (elem);
0984: try {
0985: Document doc = elem.getDocument();
0986: this .startPos = doc.createPosition(super
0987: .getStartOffset()
0988: + offset);
0989: this .endPos = doc.createPosition(startPos.getOffset()
0990: + length);
0991: } catch (BadLocationException ble) {
0992: LOG.log(Level.INFO,
0993: "Can't create fragment view, offset = "
0994: + offset + ", length = " + length, ble); //NOI18N
0995: }
0996: }
0997:
0998: protected @Override
0999: boolean isFragment() {
1000: return true;
1001: }
1002:
1003: public @Override
1004: int getStartOffset() {
1005: return startPos.getOffset();
1006: }
1007:
1008: public @Override
1009: int getEndOffset() {
1010: return endPos.getOffset();
1011: }
1012:
1013: } // End of FragmentView class
1014:
1015: }
|