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-2007 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: package org.netbeans.modules.visualweb.css2;
0042:
0043: import org.netbeans.modules.visualweb.api.designer.cssengine.CssProvider;
0044: import org.netbeans.modules.visualweb.api.designer.cssengine.CssValue;
0045: import org.netbeans.modules.visualweb.designer.CssUtilities;
0046:
0047: import javax.swing.JViewport;
0048: import org.netbeans.modules.visualweb.api.designer.DomProvider;
0049:
0050: import org.openide.ErrorManager;
0051: import org.w3c.dom.Element;
0052: import org.w3c.dom.Node;
0053:
0054: import org.netbeans.modules.visualweb.designer.DesignerPane;
0055: import org.netbeans.modules.visualweb.designer.DesignerUtils;
0056: import org.netbeans.modules.visualweb.designer.WebForm;
0057: import org.netbeans.modules.visualweb.api.designer.cssengine.XhtmlCss;
0058: import org.netbeans.modules.visualweb.designer.html.HtmlAttribute;
0059: import org.netbeans.modules.visualweb.designer.html.HtmlTag;
0060:
0061: /**
0062: * Represents the document - with a <body> tag. Used to display not only
0063: * our document page, but iframes as well.
0064: *
0065: * @author Tor Norbye.
0066: */
0067: public abstract class DocumentBox extends ContainerBox {
0068: // Display statistics such as number of boxes created, time required, etc.
0069: private static final boolean debugstats = System
0070: .getProperty("designer.stats") != null;
0071:
0072: // Statistics
0073: private static int numBoxes;
0074: private static int numLeaves;
0075: private static int maxChildren;
0076: private static int maxDepth;
0077: private static int maxElementDepth;
0078: private static int numElements;
0079: private static int textNodes;
0080: private static int textBoxes;
0081: private static int spaceBoxes;
0082: private static int blankTextNodes;
0083: protected FormatContext context;
0084: protected int layoutWidth;
0085: protected int layoutHeight;
0086: protected DesignerPane pane;
0087: protected JViewport viewport;
0088: protected int currWidth = -1;
0089: protected Element body;
0090: protected int maxWidth = -1;
0091: protected boolean layoutValid = false;
0092: protected BoxList fixedBoxes;
0093:
0094: /** Currently scrolled-to x position in the top left corner of the
0095: * viewport. */
0096: protected int viewportX;
0097:
0098: /** Currently scrolled-to y position in the top left corner of the
0099: * viewport. */
0100: protected int viewportY;
0101:
0102: /** Creates a new instance of PageBox */
0103: public DocumentBox(DesignerPane pane, WebForm webform,
0104: Element body, BoxType boxType, boolean inline,
0105: boolean replaced) {
0106: // XXX What do we pass in as a containing block?
0107: super (webform, body, boxType, inline, replaced);
0108: this .pane = pane; // XXX do we need this in the document
0109: this .body = body;
0110:
0111: x = 0;
0112: y = 0;
0113: width = 0;
0114: height = 0;
0115: }
0116:
0117: /**
0118: * Create the children. The FrameBox uses its own create context
0119: * since the document contained within is hidden to the outside,
0120: * and it gets its own local context for the entire document.
0121: */
0122: protected void createChildren(CreateContext context) {
0123: // TODO instead of checking on the box count, which could be 0
0124: // for valid reasons, have a dedicated flag here which is
0125: // invalidated on document edits, etc.
0126: CreateContext cc;
0127:
0128: if (context != null) {
0129: cc = new CreateContext(context);
0130: } else {
0131: cc = new CreateContext();
0132: }
0133:
0134: cc.pushPage(webform);
0135:
0136: try {
0137: // Font font = CssLookup.getFont(body, DesignerSettings.getInstance().getDefaultFontSize());
0138: // Font font = CssProvider.getValueService().getFontForElement(body, DesignerSettings.getInstance().getDefaultFontSize(), Font.PLAIN);
0139: // cc.metrics = Toolkit.getDefaultToolkit().getFontMetrics(font);
0140: // XXX Missing text.
0141: cc.metrics = CssUtilities.getDesignerFontMetricsForElement(
0142: body, null, webform.getDefaultFontSize());
0143:
0144: super .createChildren(cc);
0145: fixedBoxes = cc.getFixedBoxes();
0146: } finally {
0147: cc.popPage();
0148: }
0149: }
0150:
0151: public void relayout(FormatContext context) {
0152: /*
0153: if (contentWidth == AUTO) {
0154: contentWidth = getIntrinsicWidth();
0155: }
0156: if (contentHeight == AUTO) {
0157: contentHeight = getIntrinsicHeight();
0158: }
0159: */
0160:
0161: // Note - we don't pass in context.initialCB since
0162: // fixed boxes should not be relative to the outer viewport
0163: // by default
0164: relayout(null, contentWidth, contentHeight, -1);
0165: }
0166:
0167: /**
0168: * Layout the page hierarchy.
0169: */
0170: public void relayout(JViewport viewport, int initialWidth,
0171: int initialHeight, int wrapWidth) {
0172: layoutValid = true;
0173:
0174: if (initialWidth == currWidth) {
0175: return;
0176: }
0177:
0178: if (initialWidth == Integer.MAX_VALUE) {
0179: // Intermediate/invalid startup state - don't do layout.
0180: // We'll soon get a resize with an appropriate size.
0181: return;
0182: }
0183:
0184: currWidth = initialWidth;
0185:
0186: // All the layout computations have to use the wrap width for the containing blocks etc
0187: // to get wrapping, attachments to the right side, etc. to work correctly. However, that
0188: // means we end up with a root box sized by the wrapping column - which causes various
0189: // painting problems for the scrollpane etc. So when we're done, we'll set the width
0190: // back to the initialwidth if that's larger than the computed width.
0191: int savedWidth = -1;
0192:
0193: if (wrapWidth != -1) {
0194: savedWidth = initialWidth;
0195: initialWidth = wrapWidth;
0196: }
0197:
0198: if (debugstats) {
0199: // During development only
0200: // CSSEngine.styleLookupCount = 0;
0201: CssProvider.getEngineService()
0202: .clearEngineStyleLookupCount();
0203: }
0204:
0205: // Ensure box hierarchy has been created
0206: long create = 0;
0207:
0208: // Ensure box hierarchy has been created
0209: long start = 0;
0210:
0211: if (getBoxCount() == 0) {
0212: // TODO instead of checking on the box count, which could be 0
0213: // for valid reasons, have a dedicated flag here which is
0214: // invalidated on document edits, etc.
0215: if (debugstats) {
0216: start = System.currentTimeMillis();
0217: }
0218:
0219: // XhtmlCssEngine engine = CssLookup.getCssEngine(body);
0220: // if (engine != null) {
0221: // engine.clearTransientStyleSheetNodes();
0222: // }
0223:
0224: // XXX #110849 Fixing the relayout. It seems it depends on uncomputed CSS values,
0225: // which seems to be wrong, but it is hard to fix that.
0226: // This fixes the layout, but might be a potential performance issue (with larger pages, projects).
0227: // CssProvider.getEngineService().clearTransientStyleSheetNodesForDocument(body.getOwnerDocument());
0228: CssProvider.getEngineService()
0229: .clearComputedStylesForElement(body); // TEMP
0230:
0231: createChildren(null);
0232:
0233: if (debugstats) {
0234: long end = System.currentTimeMillis();
0235: create = end - start;
0236: }
0237: }
0238:
0239: // Perform layout
0240: if (debugstats) {
0241: start = System.currentTimeMillis();
0242: }
0243:
0244: // Initialize body margins and padding
0245: initialize();
0246:
0247: // Auto margins are not valid on the page box
0248: if (leftMargin == AUTO) {
0249: leftMargin = 0;
0250: }
0251:
0252: if (rightMargin == AUTO) {
0253: rightMargin = 0;
0254: }
0255:
0256: if (topMargin == AUTO) {
0257: topMargin = 0;
0258: }
0259:
0260: if (bottomMargin == AUTO) {
0261: bottomMargin = 0;
0262: }
0263:
0264: width = initialWidth;
0265: height = initialHeight; // XXX if AUTO don't copy to child
0266:
0267: if (width != AUTO) {
0268: contentWidth = width
0269: - (leftPadding + leftBorderWidth + leftMargin
0270: + rightMargin + rightBorderWidth + rightPadding);
0271: } else {
0272: contentWidth = AUTO;
0273: }
0274:
0275: if (height != AUTO) {
0276: contentHeight = height
0277: - (topPadding + topBorderWidth + topMargin
0278: + bottomMargin + bottomBorderWidth + bottomPadding);
0279: } else {
0280: contentHeight = AUTO;
0281: }
0282:
0283: setContainingBlock(leftBorderWidth + leftPadding,
0284: topBorderWidth + topPadding, contentWidth,
0285: contentHeight);
0286:
0287: context = new FormatContext();
0288: context.initialCB = new ViewportBox(viewport, initialWidth,
0289: initialHeight);
0290: context.initialWidth = initialWidth;
0291: context.initialHeight = initialHeight;
0292:
0293: try {
0294: layoutContext(context);
0295: } catch (Throwable e) { // want to catch assertion errors too
0296: ErrorManager.getDefault().notify(e);
0297: e.printStackTrace();
0298: layoutValid = false;
0299: // XXX #126314 Possible NPE.
0300: if (pane != null) {
0301: pane.repaint();
0302: }
0303:
0304: return;
0305: }
0306:
0307: if (savedWidth > width) {
0308: width = savedWidth;
0309: contentWidth = width
0310: - (leftPadding + leftBorderWidth + leftMargin
0311: + rightMargin + rightBorderWidth + rightPadding);
0312: }
0313:
0314: updateSizeInfo();
0315:
0316: long layout = 0;
0317:
0318: if (debugstats) {
0319: long end = System.currentTimeMillis();
0320: layout = end - start;
0321: org.openide.awt.StatusDisplayer.getDefault().setStatusText(
0322: "Box Creation: " + create + " ms, layout: "
0323: + layout + " ms");
0324: }
0325:
0326: if (DEBUGFORMAT) {
0327: StringBuffer sb = new StringBuffer(1000);
0328: printLayout(sb);
0329: System.out.println(sb.toString());
0330: }
0331:
0332: if (debugstats) {
0333: gatherStatistics();
0334:
0335: // Additional statistics that may be interesting:
0336: // Average and maximum number of styles per element
0337: // Should tell me something about the speed of style lookups
0338: System.out.println("\nLayout Statistics:\n");
0339: System.out.println("Number of boxes: " + numBoxes);
0340: System.out.println("Number of leaf boxes: " + numLeaves);
0341: System.out.println("Number of elements: " + numElements);
0342: // System.out.println("Number of CSS style lookups: " + CSSEngine.styleLookupCount);
0343: System.out.println("Number of CSS style lookups: "
0344: + CssProvider.getEngineService()
0345: .getEngineStyleLookupCount());
0346: System.out.println("Average lookups per box: " +
0347: // (CSSEngine.styleLookupCount / numBoxes));
0348: (CssProvider.getEngineService()
0349: .getEngineStyleLookupCount() / numBoxes));
0350:
0351: if (numBoxes > numLeaves) {
0352: System.out
0353: .println("Average lookups per non-leaf box: " +
0354: // (CSSEngine.styleLookupCount / (numBoxes - numLeaves)));
0355: (CssProvider.getEngineService()
0356: .getEngineStyleLookupCount() / (numBoxes - numLeaves)));
0357: }
0358:
0359: System.out.println("Maximum box tree depth: " + maxDepth);
0360: System.out.println("Maximum element tree depth: "
0361: + maxElementDepth);
0362: System.out.println("Maximum child count: " + maxChildren);
0363:
0364: if (numBoxes > numLeaves) {
0365: System.out.println("Average child count: "
0366: + ((numBoxes - 1) / (numBoxes - numLeaves)));
0367: }
0368:
0369: System.out.println("Number of text nodes: " + textNodes);
0370: System.out.println("Number of blank-only text nodes: "
0371: + blankTextNodes);
0372: System.out.println("Number of text boxes: " + textBoxes);
0373: System.out.println("Number of space boxes: " + spaceBoxes);
0374: System.out.println("Box Creation: " + (create / 1000.0)
0375: + " sec");
0376: System.out.println("Layout Computation: "
0377: + (layout / 1000.0) + " sec");
0378: }
0379:
0380: // XXX It would be nice to know how wide we actually are, let's
0381: // say widthActual. That way we know that we have a correct layout
0382: // for any x in the interval [widthActual,width], so we can
0383: // suppress resize requests until the widths is outside of that range!
0384: // (Note to self: that's only partially true; the actual width
0385: // could be less because of margins; these would have to be INCLUDED
0386: // in my actual width computation for the below to be correct)
0387: }
0388:
0389: protected void layoutContext(FormatContext context) {
0390: super .relayout(context);
0391:
0392: // XXX #99918 Ajusting the fixed boxes.
0393: adjustFixedBoxesIssue99918();
0394: }
0395:
0396: /** XXX #99918 Adjusts the position of fixed boxes,
0397: * when it is possible to compute so called 'static box',
0398: * that one is not possible to reliably compute in this architecture
0399: * at the intended place (getStaticLeft, getStaticTop in CssBox)
0400: * when the top or left values are auto, so it is hacked here. */
0401: private void adjustFixedBoxesIssue99918() {
0402: BoxList fixed = fixedBoxes;
0403: if (fixed == null) {
0404: return;
0405: }
0406: int size = fixed.size();
0407: if (size == 0) {
0408: return;
0409: }
0410: for (int i = 0; i < size; i++) {
0411: CssBox fixedBox = fixed.get(i);
0412: adjustFixedBoxLeftIssue99918(fixedBox);
0413: adjustFixedBoxTopIssue99918(fixedBox);
0414: }
0415: }
0416:
0417: private void adjustFixedBoxLeftIssue99918(CssBox fixedBox) {
0418: if (CssProvider.getValueService().isAutoValue(
0419: CssProvider.getEngineService()
0420: .getComputedValueForElement(
0421: fixedBox.getElement(),
0422: XhtmlCss.LEFT_INDEX))) {
0423: CssBox parentBox = fixedBox.getParent();
0424: if (parentBox != null
0425: && parentBox != fixedBox.getPositionedBy()) {
0426: fixedBox.left += parentBox.getAbsoluteX();
0427: fixedBox.setX(fixedBox.left);
0428: }
0429: }
0430: }
0431:
0432: private void adjustFixedBoxTopIssue99918(CssBox fixedBox) {
0433: if (CssProvider.getValueService().isAutoValue(
0434: CssProvider.getEngineService()
0435: .getComputedValueForElement(
0436: fixedBox.getElement(),
0437: XhtmlCss.TOP_INDEX))) {
0438: CssBox parentBox = fixedBox.getParent();
0439: if (parentBox != null
0440: && parentBox != fixedBox.getPositionedBy()) {
0441: fixedBox.top += parentBox.getAbsoluteY();
0442: fixedBox.setY(fixedBox.top);
0443: }
0444: }
0445: }
0446:
0447: protected void updateSizeInfo() {
0448: updateExtents(0, 0, 0); // NOT getAbsoluteX()/getAbsoluteY(), since
0449: //paint will call super which adds them in
0450:
0451: int extentWidth = extentX2 - extentX;
0452: int extentHeight = extentY2 - extentY;
0453: width = extentWidth;
0454: height = extentHeight;
0455: contentWidth = width
0456: - (leftPadding + leftBorderWidth + leftMargin
0457: + rightMargin + rightBorderWidth + rightPadding);
0458: contentHeight = height
0459: - (topPadding + topBorderWidth + topMargin
0460: + bottomMargin + bottomBorderWidth + bottomPadding);
0461:
0462: layoutWidth = width;
0463: layoutHeight = height;
0464: }
0465:
0466: /*
0467: public void setSize(int width, int height) {
0468: //Log.err.log("************************************************************************************\nPageBox.setSize - width= " + width + "\nLayoutValid was " + layoutValid + " and contextwidth=" + (context != null ? Integer.toString(layoutWidth) : "null"));
0469: if (!layoutValid) {
0470: maxWidth = width;
0471: //setWidth(width);
0472: //setHeight(height);
0473: //layout();
0474: }
0475: }
0476: */
0477: public void redoLayout(boolean immediate) {
0478: currWidth = -1;
0479: removeBoxes();
0480: layoutValid = false;
0481: webform.getManager().updateInsertBox();
0482:
0483: if (immediate) {
0484: relayout(null);
0485: }
0486: }
0487:
0488: // FOR DEBUGFORMATTING ONLY!
0489: public void printLayout(StringBuffer sb) {
0490: sb.append("\nLAYOUT for " + this + "\n----------------\n");
0491: sb.append("\nInline Content/Lineboxes:");
0492: printLayout(this , sb, 0);
0493: }
0494:
0495: public int getAbsoluteX() {
0496: return leftMargin;
0497: }
0498:
0499: public int getAbsoluteY() {
0500: return effectiveTopMargin;
0501: }
0502:
0503: /**
0504: * A node was inserted into the document, below the given parent.
0505: */
0506: public void inserted(Node node, Node parent) {
0507: assert parent != null;
0508: assert parent.getNodeType() == Node.ELEMENT_NODE;
0509: assert node.getNodeType() == Node.ELEMENT_NODE;
0510:
0511: if (!layoutValid) { // next paint will do a full relayout anyway
0512: redoLayout(false); // ensure that boxes are null too incase layout was set to valid
0513:
0514: // with only the intent of a relayout without box recreation
0515: return;
0516: }
0517:
0518: if (context == null) {
0519: // Will be doing full relayout on next repaint anyway
0520: return;
0521: }
0522:
0523: Element element = null;
0524: CssBox target = null;
0525:
0526: if ((node.getNodeType() == Node.TEXT_NODE)
0527: || (node.getNodeType() == Node.CDATA_SECTION_NODE)
0528: || (node.getNodeType() == Node.ENTITY_REFERENCE_NODE)) {
0529: // If you change some text that's already "flown",
0530: // the target should be the line box group
0531: // containing the text
0532: // XXX how do I find an existing LBG for a node?
0533: // How do I decide where to add one?
0534: // Let's say you have <p><p> and the caret is in between these;
0535: // how do we end up adding it in the right place?
0536: redoLayout(true);
0537:
0538: return;
0539:
0540: // If there is no line box group for this, we've
0541: // gotta add one
0542: } else if (node.getNodeType() == Node.ELEMENT_NODE) {
0543: element = (Element) node;
0544:
0545: // Table cells need special handling
0546: // I could go subclass addNode in TableBox to try to handle this more
0547: // elegantly. Worry about removeBox too.
0548: // Value display = CssLookup.getValue(element, XhtmlCss.DISPLAY_INDEX);
0549: CssValue cssDisplay = CssProvider.getEngineService()
0550: .getComputedValueForElement(element,
0551: XhtmlCss.DISPLAY_INDEX);
0552:
0553: // if ((display == CssValueConstants.TABLE_ROW_VALUE) ||
0554: // (display == CssValueConstants.TABLE_CELL_VALUE)) {
0555: if (CssProvider.getValueService().isTableRowValue(
0556: cssDisplay)
0557: || CssProvider.getValueService().isTableCellValue(
0558: cssDisplay)) {
0559: // What about (display == CssValueConstants.TABLE_ROW_GROUP_VALUE) ?
0560: // I only need to check for TBODY, THEAD, TFOOT, COL, etc.
0561: // if I support dynamically inserting these (or text nodes
0562: // dynamically within them).
0563: // while ((element != null)
0564: // (CssLookup.getValue(element, XhtmlCss.DISPLAY_INDEX) != CssValueConstants.TABLE_VALUE)) {
0565: while (element != null
0566: && !CssProvider
0567: .getValueService()
0568: .isTableValue(
0569: CssProvider
0570: .getEngineService()
0571: .getComputedValueForElement(
0572: element,
0573: XhtmlCss.DISPLAY_INDEX))) {
0574: // TODO - what if the td is not inside a table? We'll
0575: // get a class cast exception here - make this safer
0576: element = (Element) parent;
0577: parent = element.getParentNode();
0578: }
0579:
0580: if (element == null) {
0581: ErrorManager.getDefault().log(
0582: "Unexpected <td> outside of a table: "
0583: + node);
0584: redoLayout(true);
0585:
0586: return;
0587: }
0588:
0589: if (node != element) {
0590: // changed(node, node.getParentNode(), false);
0591: changed(node, node.getParentNode(), null);
0592: return;
0593: }
0594:
0595: node = element;
0596: }
0597:
0598: // XXX todo -- use the mapper?
0599: // target = (ContainerBox)doc.getWebForm().getMapper().findBox(element);
0600: // target = CssBox.getBox(element);
0601: target = getWebForm().findCssBoxForElement(element);
0602: }
0603:
0604: Element parentElement = (Element) parent;
0605:
0606: // Update box hierarchy
0607: // Gotta figure out the parent of the inserted node,
0608: // discover which box it corresponds to, and then insert
0609: // it in the proper child position.
0610: // ContainerBox parentBox = (ContainerBox)webform.getMapper().findBox(parentElement);
0611: // XXX #6484485 Possible ClassCastException.
0612: // ContainerBox parentBox = (ContainerBox)ModelViewMapper.findBox(webform.getPane().getPageBox(), parentElement);
0613: CssBox box = ModelViewMapper.findBox(webform.getPane()
0614: .getPageBox(), parentElement);
0615: ContainerBox parentBox;
0616: if (box instanceof ContainerBox) {
0617: parentBox = (ContainerBox) box;
0618: } else {
0619: // XXX #118387 Only when non-null report issue, if null it means it is added to top (html).
0620: if (box != null) {
0621: ErrorManager.getDefault().notify(
0622: ErrorManager.INFORMATIONAL,
0623: new IllegalStateException(
0624: "There was expected ContainerBox for parent element="
0625: + parentElement // NOI18N
0626: + ", but it is box=" + box)); // NOI18N
0627: }
0628: parentBox = null;
0629: }
0630:
0631: if (parentBox == null) {
0632: redoLayout(true);
0633:
0634: return;
0635: }
0636:
0637: if (parentBox instanceof LineBox) {
0638: parentBox = parentBox.getParent();
0639: }
0640:
0641: CreateContext cc = new CreateContext();
0642: cc.pushPage(webform);
0643: // Font font = CssLookup.getFont(body, DesignerSettings.getInstance().getDefaultFontSize());
0644: // Font font = CssProvider.getValueService().getFontForElement(body, DesignerSettings.getInstance().getDefaultFontSize(), Font.PLAIN);
0645: // cc.metrics = Toolkit.getDefaultToolkit().getFontMetrics(font);
0646: // XXX Missing text.
0647: cc.metrics = CssUtilities.getDesignerFontMetricsForElement(
0648: body, null, webform.getDefaultFontSize());
0649:
0650: // Previous and next boxes
0651: Node prevNode = node.getPreviousSibling();
0652:
0653: for (; prevNode != null; prevNode = prevNode
0654: .getPreviousSibling()) {
0655: if ((prevNode.getNodeType() == Node.TEXT_NODE)
0656: && DesignerUtils.onlyWhitespace(prevNode
0657: .getNodeValue())) {
0658: continue;
0659: }
0660:
0661: if (prevNode.getNodeType() == Node.ELEMENT_NODE) {
0662: Element e = (Element) prevNode;
0663: HtmlTag tag = HtmlTag.getTag(e.getTagName());
0664: if ((tag != null && tag.isHiddenTag())
0665: || ((tag == HtmlTag.INPUT) && e.getAttribute(
0666: HtmlAttribute.TYPE).equals("hidden"))) {
0667: continue;
0668: }
0669: }
0670:
0671: break;
0672: }
0673:
0674: Node nextNode = node.getNextSibling();
0675:
0676: for (; nextNode != null; nextNode = nextNode.getNextSibling()) {
0677: if ((nextNode.getNodeType() == Node.TEXT_NODE)
0678: && DesignerUtils.onlyWhitespace(nextNode
0679: .getNodeValue())) {
0680: continue;
0681: }
0682:
0683: if (nextNode.getNodeType() == Node.ELEMENT_NODE) {
0684: Element e = (Element) nextNode;
0685: HtmlTag tag = HtmlTag.getTag(e.getTagName());
0686: if ((tag != null && tag.isHiddenTag())
0687: || ((tag == HtmlTag.INPUT) && e.getAttribute(
0688: HtmlAttribute.TYPE).equals("hidden"))) {
0689: continue;
0690: }
0691: }
0692:
0693: break;
0694: }
0695:
0696: /*
0697: CssBox prevBox = prevNode != null &&
0698: prevNode.getNodeType() == Node.ELEMENT_NODE ?
0699: CssBox.getBox((Element)prevNode) : null;
0700: */
0701: CssBox prevBox = null;
0702:
0703: if (prevNode != null) {
0704: if (prevNode.getNodeType() == Node.ELEMENT_NODE) {
0705: // XXX use the Mapper?
0706: // prevBox = CssBox.getBox((Element)prevNode);
0707: prevBox = getWebForm().findCssBoxForElement(
0708: (Element) prevNode);
0709: } else {
0710: prevBox = ModelViewMapper.findBox(parentBox, prevNode,
0711: 0);
0712: }
0713: }
0714:
0715: /*
0716: CssBox nextBox = nextNode != null &&
0717: nextNode.getNodeType() == Node.ELEMENT_NODE ?
0718: CssBox.getBox((Element)nextNode) : null;
0719: */
0720: CssBox nextBox = null;
0721:
0722: if (nextNode != null) {
0723: if (nextNode.getNodeType() == Node.ELEMENT_NODE) {
0724: // XXX use the mapper?
0725: // nextBox = CssBox.getBox((Element)nextNode);
0726: nextBox = getWebForm().findCssBoxForElement(
0727: (Element) nextNode);
0728: } else {
0729: nextBox = ModelViewMapper.findBox(parentBox, nextNode,
0730: 0);
0731: }
0732: }
0733:
0734: // Incremental Layout: may not do the right thing when we add a block box
0735: // in and we have either prev or next nodes that are inline boxes
0736: if ((nextBox == null) && (prevBox != null)) {
0737: int index = prevBox.getParentIndex() + 1;
0738:
0739: if (prevBox.getParent() instanceof LineBoxGroup) {
0740: BoxList boxes = ((LineBoxGroup) prevBox.getParent())
0741: .getManagedBoxes();
0742:
0743: if (boxes.size() > index) {
0744: nextBox = boxes.get(index);
0745: }
0746: } else {
0747: if (prevBox.getParent().getBoxCount() > index) {
0748: nextBox = prevBox.getParent().getBox(index);
0749: }
0750: }
0751:
0752: // XXX #109446.
0753: if (nextBox != null) {
0754: Element prevComponentRootElement = ModelViewMapper
0755: .findClosestComponentRootElement(webform,
0756: prevBox.getElement());
0757: if (prevComponentRootElement == ModelViewMapper
0758: .findClosestComponentRootElement(webform,
0759: nextBox.getElement())) {
0760: if (prevComponentRootElement != ModelViewMapper
0761: .findClosestComponentRootElement(webform,
0762: node)) {
0763: prevBox = getWebForm().findCssBoxForElement(
0764: prevComponentRootElement);
0765: nextBox = null;
0766: }
0767: }
0768: }
0769: } else if ((nextBox != null) && (prevBox == null)) {
0770: int index = nextBox.getParentIndex() - 1;
0771:
0772: if (index >= 0) {
0773: if (nextBox.getParent() instanceof LineBoxGroup) {
0774: prevBox = ((LineBoxGroup) nextBox.getParent())
0775: .getManagedBoxes().get(index);
0776: } else {
0777: prevBox = nextBox.getParent().getBox(index);
0778: }
0779: }
0780:
0781: // XXX #109446.
0782: if (prevBox != null) {
0783: Element nextComponentRootElement = ModelViewMapper
0784: .findClosestComponentRootElement(webform,
0785: nextBox.getElement());
0786: if (nextComponentRootElement == ModelViewMapper
0787: .findClosestComponentRootElement(webform,
0788: prevBox.getElement())) {
0789: if (nextComponentRootElement != ModelViewMapper
0790: .findClosestComponentRootElement(webform,
0791: node)) {
0792: prevBox = null;
0793: nextBox = getWebForm().findCssBoxForElement(
0794: nextComponentRootElement);
0795: }
0796: }
0797: }
0798: }
0799:
0800: // XXX #126648 There are issues when inserting into the line, rather redo the layout completelly.
0801: if (prevBox != null && prevBox.isInlineBox() && nextBox != null
0802: && nextBox.isInlineBox()) {
0803: redoLayout(true);
0804: return;
0805: }
0806:
0807: try {
0808: parentBox.addNode(cc, node, null, prevBox, nextBox);
0809: } catch (Throwable ex) { // want to catch assertions too
0810: ErrorManager.getDefault().notify(ErrorManager.WARNING, ex);
0811: redoLayout(true);
0812:
0813: return;
0814: }
0815:
0816: if (cc.getFixedBoxes() != null) {
0817: // We've added a fixed box
0818: if (fixedBoxes == null) {
0819: fixedBoxes = cc.getFixedBoxes();
0820: } else {
0821: BoxList fp = cc.getFixedBoxes();
0822:
0823: for (int i = 0, n = fp.size(); i < n; i++) {
0824: fixedBoxes.add(fp.get(i), null, null);
0825: }
0826: }
0827: }
0828:
0829: // Update layout
0830: if (target == null) {
0831: // XXX use the mapper?
0832: // target = CssBox.getBox(element);
0833: target = getWebForm().findCssBoxForElement(element);
0834:
0835: if (target == null) {
0836: // Internal error - didn't find box for element
0837: redoLayout(true);
0838:
0839: return;
0840: }
0841: }
0842:
0843: // I may have inserted another linebox - make sure its layout is processed
0844: // as well.
0845: if (cc.prevChangedBox != null) {
0846: updateLayout(cc.prevChangedBox);
0847: }
0848:
0849: // Unfortunately, we may have "preloaded" the component with styles above
0850: // before we had actual containing blocks assigned to the elements, so
0851: // clear the styles first
0852: // CssLookup.clearComputedStyles(target.getElement());
0853: CssProvider.getEngineService().clearComputedStylesForElement(
0854: target.getElement());
0855:
0856: updateLayout(target);
0857:
0858: // I may have inserted another linebox - make sure its layout is processed
0859: // as well.
0860: if (cc.nextChangedBox != null) {
0861: updateLayout(cc.nextChangedBox);
0862: }
0863:
0864: // XXX #99918.
0865: adjustFixedBoxesIssue99918();
0866:
0867: updateSizeInfo();
0868:
0869: if ((Math.abs(extentX) > 50000) || (Math.abs(extentY) > 50000)
0870: || (Math.abs(extentX2) > 50000)
0871: || (Math.abs(extentY2) > 50000)) {
0872: // Something went horribly wrong during incremental layout, so
0873: // do it from scratch
0874: redoLayout(true);
0875:
0876: return;
0877: }
0878:
0879: webform.getManager().updateInsertBox();
0880:
0881: if (pane != null) {
0882: pane.repaint();
0883: }
0884: }
0885:
0886: public void removed(Node node, Node parent) {
0887: boxes = null;
0888: redoLayout(false);
0889: }
0890:
0891: /**
0892: * @todo Remove the parent pointer, we don't need it for change events
0893: */
0894: public void changed(Node node, Node parent,
0895: Element[] changedElements) {
0896: assert parent != null;
0897:
0898: //assert node.getNodeType() == Node.ELEMENT_NODE;
0899: if (!layoutValid) { // next paint will do a full relayout anyway
0900: redoLayout(false); // ensure that boxes are null too incase layout was set to valid
0901:
0902: // with only the intent of a relayout without box recreation
0903: return;
0904: }
0905:
0906: if (context == null) {
0907: // Will be doing full relayout on next repaint anyway
0908: return;
0909: }
0910:
0911: Element element = null;
0912: CssBox target = null;
0913:
0914: if ((node.getNodeType() == Node.TEXT_NODE)
0915: || (node.getNodeType() == Node.CDATA_SECTION_NODE)
0916: || (node.getNodeType() == Node.ENTITY_REFERENCE_NODE)) {
0917: // If you change some text that's already "flown",
0918: // the target should be the line box group
0919: // containing the text
0920: // XXX how do I find an existing LBG for a node?
0921: // How do I decide where to add one?
0922: // Let's say you have <p><p> and the caret is in between these;
0923: // how do we end up adding it in the right place?
0924: // For now, redoing all layout for text changes!
0925: redoLayout(true);
0926:
0927: return;
0928:
0929: // If there is no line box group for this, we've
0930: // gotta add one
0931: } else if (node.getNodeType() == Node.ELEMENT_NODE) {
0932: element = (Element) node;
0933:
0934: // Table cells need special handling
0935: // I could go subclass addNode in TableBox to try to handle this more
0936: // elegantly. Worry about removeBox too.
0937: // Value display = CssLookup.getValue(element, XhtmlCss.DISPLAY_INDEX);
0938: CssValue cssDisplay = CssProvider.getEngineService()
0939: .getComputedValueForElement(element,
0940: XhtmlCss.DISPLAY_INDEX);
0941:
0942: // if ((display == CssValueConstants.TABLE_ROW_VALUE) ||
0943: // (display == CssValueConstants.TABLE_CELL_VALUE)) {
0944: if (CssProvider.getValueService().isTableRowValue(
0945: cssDisplay)
0946: || CssProvider.getValueService().isTableCellValue(
0947: cssDisplay)) {
0948: // What about (display == CssValueConstants.TABLE_ROW_GROUP_VALUE) ?
0949: // I only need to check for TBODY, THEAD, TFOOT, COL, etc.
0950: // if I support dynamically inserting these (or text nodes
0951: // dynamically within them).
0952: // while ((element != null) &&
0953: // (CssLookup.getValue(element, XhtmlCss.DISPLAY_INDEX) != CssValueConstants.TABLE_VALUE)) {
0954: while (element != null
0955: && parent != null
0956: && !CssProvider
0957: .getValueService()
0958: .isTableValue(
0959: CssProvider
0960: .getEngineService()
0961: .getComputedValueForElement(
0962: element,
0963: XhtmlCss.DISPLAY_INDEX))) {
0964: element = (Element) parent;
0965: parent = element.getParentNode();
0966: }
0967:
0968: if (element == null) {
0969: // Unexpected <td> outside of a table
0970: redoLayout(true);
0971:
0972: return;
0973: }
0974:
0975: node = element;
0976: }
0977:
0978: // XXX use the mapper?
0979: // target = CssBox.getBox(element);
0980: target = getWebForm().findCssBoxForElement(element);
0981: } else {
0982: // Unexpected node for change event
0983: redoLayout(true);
0984:
0985: return;
0986: }
0987:
0988: // Unfortunately, we may have "preloaded" the component with styles above
0989: // before we had actual containing blocks assigned to the elements, so
0990: // clear the styles first
0991: // CssLookup.clearComputedStyles(target.getElement());
0992: // XXX This needs to be cleared before the new box is added into the hierarchy (see below).
0993: // CssProvider.getEngineService().clearComputedStylesForElement(element);
0994: if (changedElements == null || changedElements.length == 0) {
0995: CssProvider.getEngineService()
0996: .clearComputedStylesForElement(element);
0997: } else {
0998: // XXX #105179 Update style only for the changed elements (and their children).
0999: for (Element changedElement : changedElements) {
1000: CssProvider.getEngineService()
1001: .clearComputedStylesForElement(changedElement);
1002: }
1003: }
1004:
1005: //!CQ parent may be the Document itself... assert parent.getNodeType() == Node.ELEMENT_NODE;
1006: //Element parentElement = (Element)parent;
1007: if (target == null) {
1008: // Internal error - didn't find box for element
1009: redoLayout(true);
1010:
1011: return;
1012: }
1013:
1014: ContainerBox parentBox = target.getParent();
1015:
1016: if (parentBox instanceof LineBox) {
1017: parentBox = parentBox.getParent();
1018: }
1019:
1020: /* Temporarily disabled; gotta resolve issue of how to
1021: * transfer updated styles from the source element (which
1022: * is manipulated by the GridHandler) to the generated
1023: * JSF element (which is rendered during layout)
1024: if (wasMove && target.getBoxType().isAbsolutelyPositioned()) {
1025: //parentBox.positionBox(target, context);
1026: parentBox.layoutChild(target, context, false);
1027: updateSizeInfo();
1028: if (Math.abs(extentX) > 50000 || Math.abs(extentY) > 50000 ||
1029: Math.abs(extentX2) > 50000 || Math.abs(extentY2) > 50000) {
1030: // Something went horribly wrong during incremental layout, so
1031: // do it from scratch
1032: redoLayout(true);
1033: return;
1034: }
1035: if (pane != null) {
1036: pane.repaint();
1037: }
1038: return;
1039: }
1040: */
1041:
1042: CreateContext cc = new CreateContext();
1043: cc.pushPage(webform);
1044: // Font font = CssLookup.getFont(body, DesignerSettings.getInstance().getDefaultFontSize());
1045: // Font font = CssProvider.getValueService().getFontForElement(body, DesignerSettings.getInstance().getDefaultFontSize(), Font.PLAIN);
1046: // cc.metrics = Toolkit.getDefaultToolkit().getFontMetrics(font);
1047: // XXX Missing text.
1048: cc.metrics = CssUtilities.getDesignerFontMetricsForElement(
1049: body, null, webform.getDefaultFontSize());
1050:
1051: // XXX #109306 Remove also sibling boxes representing the same element.
1052: // XXX Why they don't have common parent?
1053: CssBox[] boxesToRemove = parentBox.getBoxesToRemove(target);
1054:
1055: // Add the new box right behind the old box
1056: parentBox.addNode(cc, node, null, target, null);
1057:
1058: // Remove the old box
1059: CssBox deleted = target;
1060:
1061: // XXX Removing the old box (and its connected siblings).
1062: boolean removed = false;
1063: for (CssBox boxToRemove : boxesToRemove) {
1064: if (parentBox.removeBox(boxToRemove)) {
1065: removed = true;
1066: }
1067: }
1068: // if (!parentBox.removeBox(target)) {
1069: if (!removed) {
1070: // XXX Suspicious (presumably not working attemt) to the recovery.
1071: // Internal error, but try to gracefully recover
1072: redoLayout(true);
1073:
1074: return;
1075: }
1076:
1077: if (fixedBoxes != null) {
1078: fixedBoxes.remove(target);
1079:
1080: if (fixedBoxes.size() == 0) {
1081: fixedBoxes = null;
1082: }
1083: }
1084:
1085: if (cc.getFixedBoxes() != null) {
1086: // We've added a fixed box
1087: if (fixedBoxes == null) {
1088: fixedBoxes = cc.getFixedBoxes();
1089: } else {
1090: BoxList fp = cc.getFixedBoxes();
1091:
1092: for (int i = 0, n = fp.size(); i < n; i++) {
1093: fixedBoxes.add(fp.get(i), null, null);
1094: }
1095: }
1096: }
1097:
1098: // Update layout: look up new box for this element
1099: // XXX use the mapper
1100: // target = CssBox.getBox(element);
1101: target = getWebForm().findCssBoxForElement(element);
1102:
1103: if (target == null) {
1104: // Internal error - didn't find box for element
1105: redoLayout(true);
1106:
1107: return;
1108: }
1109:
1110: // I may have inserted another linebox - make sure its layout is processed
1111: // as well.
1112: // XXX That can't happen for change, right?
1113: if (cc.prevChangedBox != null) {
1114: updateLayout(cc.prevChangedBox);
1115: }
1116:
1117: // // Unfortunately, we may have "preloaded" the component with styles above
1118: // // before we had actual containing blocks assigned to the elements, so
1119: // // clear the styles first
1120: //// CssLookup.clearComputedStyles(target.getElement());
1121: // CssProvider.getEngineService().clearComputedStylesForElement(target.getElement());
1122:
1123: boolean redoDeleted = target.getParent() != parentBox;
1124: if (redoDeleted) {
1125: // XXX #119509 The original update didn't work well (see below).
1126: redoLayout(true);
1127: return;
1128: }
1129:
1130: updateLayout(target);
1131:
1132: // XXX #119509 This original way didn't work well (see above).
1133: // if (redoDeleted) {
1134: // // Have changed parents - gotta ensure the layout is accurate in
1135: // // the old tree too
1136: // // This is for example the case if an inline element changes its positioning
1137: // // from normal to absolute - the old linebox needs updating
1138: // //updateLayout(parentBox);
1139: // if (parentBox.getParentIndex() == -1) {
1140: // // The parent box itself was removed, so notify its parent
1141: // // This should only happen when the parentbox is a lineboxgroup
1142: // assert parentBox instanceof LineBoxGroup;
1143: //
1144: // ContainerBox p = parentBox.getParent();
1145: //
1146: // if (p != null) {
1147: // p.notifyChildResize(parentBox, context);
1148: // }
1149: // } else {
1150: // parentBox.notifyChildResize(deleted, context);
1151: // }
1152: // }
1153:
1154: // I may have inserted another linebox - make sure its layout is processed
1155: // as well.
1156: // XXX That can't happen for change, right?
1157: if (cc.nextChangedBox != null) {
1158: assert false;
1159: updateLayout(cc.nextChangedBox);
1160: }
1161:
1162: // XXX #99918.
1163: adjustFixedBoxesIssue99918();
1164:
1165: updateSizeInfo();
1166:
1167: if ((Math.abs(extentX) > 50000) || (Math.abs(extentY) > 50000)
1168: || (Math.abs(extentX2) > 50000)
1169: || (Math.abs(extentY2) > 50000)) {
1170: // Something went horribly wrong during incremental layout, so
1171: // do it from scratch
1172: redoLayout(true);
1173:
1174: return;
1175: }
1176:
1177: webform.getManager().updateInsertBox();
1178:
1179: if (pane != null) {
1180: pane.repaint();
1181: }
1182: }
1183:
1184: /**
1185: * The box hiearchy has changed: update the layout, and return the topmost
1186: * box whose dimensions changed.
1187: */
1188: protected CssBox updateLayout(CssBox target) {
1189: // Relayout the target - down the hierarchy
1190: // Create FormatContext; what about LineBox? Need to
1191: // find
1192: // For now, just reusing existing context
1193: //int oldWidth = target.contentWidth;
1194: //int oldHeight = target.contentHeight;
1195: ContainerBox parent = target.getParent();
1196:
1197: // If we insert a new line box, it needs a containing block
1198: // We generally work our way outwards with layout, but this assumes containing
1199: // blocks are known since they (horizontal ones) are computed top down
1200: if (parent instanceof LineBoxGroup
1201: && (parent.containingBlockWidth <= 0)) {
1202: parent.getParent().setContainingBlock(parent, context);
1203: }
1204:
1205: parent.layoutChild(target, context, true);
1206:
1207: // Call positionBox(target, context); ?
1208: // Have the dimensions changed? If not, we're done
1209: // XXX No - what if something else changed, like top/left?
1210: // And gotta update extents too - and scrollbar
1211: // And of course I've gotta position it!
1212:
1213: /*
1214: if (target.contentWidth == oldWidth &&
1215: target.contentHeight == oldHeight) {
1216: // TODO what if the effective margin has changed?
1217: // That might require us to propagate the change upwards
1218: // too!
1219: return;
1220: }
1221: */
1222:
1223: // Sometimes (in particular, for inline boxes) the parent has changed;
1224: // once we've called layoutChild, the inserted inline box is placed
1225: // into a linebox. Make sure we don't miss a resize requirement here.
1226: return parent.notifyChildResize(target, context);
1227: }
1228:
1229: //private static int numStyles;
1230: private void gatherStatistics() {
1231: numBoxes = 0;
1232: maxChildren = 0;
1233: maxDepth = 0;
1234: maxElementDepth = 0;
1235: numLeaves = 0;
1236: numElements = 0;
1237: textNodes = 0;
1238: textBoxes = 0;
1239: spaceBoxes = 0;
1240: blankTextNodes = 0;
1241:
1242: //numStyles = 0;
1243: gatherStatisticsBoxTree(this , 0);
1244: gatherStatisticsElementTree(body, 0);
1245:
1246: // Count styles -- how?
1247: }
1248:
1249: private void gatherStatisticsBoxTree(CssBox box, int depth) {
1250: int childCount = box.getBoxCount();
1251:
1252: for (int i = 0; i < childCount; i++) {
1253: CssBox child = box.getBox(i);
1254: gatherStatisticsBoxTree(child, depth + 1);
1255: }
1256:
1257: numBoxes++;
1258:
1259: if (depth > maxDepth) {
1260: maxDepth = depth;
1261: }
1262:
1263: if (childCount > maxChildren) {
1264: maxChildren = childCount;
1265: }
1266:
1267: if (childCount == 0) {
1268: numLeaves++;
1269: }
1270:
1271: if (box.getBoxType() == BoxType.TEXT) {
1272: textBoxes++;
1273: } else if (box.getBoxType() == BoxType.SPACE) {
1274: spaceBoxes++;
1275: }
1276: }
1277:
1278: private void gatherStatisticsElementTree(Node node, int depth) {
1279: org.w3c.dom.NodeList list = node.getChildNodes();
1280: int len = list.getLength();
1281:
1282: for (int i = 0; i < len; i++) {
1283: org.w3c.dom.Node child = (org.w3c.dom.Node) list.item(i);
1284: gatherStatisticsElementTree(child, depth + 1);
1285: }
1286:
1287: numElements++;
1288:
1289: if (depth > maxElementDepth) {
1290: maxElementDepth = depth;
1291: }
1292:
1293: if ((node.getNodeType() == Node.TEXT_NODE)
1294: || (node.getNodeType() == Node.CDATA_SECTION_NODE)) {
1295: textNodes++;
1296:
1297: if (DesignerUtils.onlyWhitespace(node.getNodeValue())) {
1298: blankTextNodes++;
1299: }
1300: }
1301: }
1302: }
|