001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.visualweb.css2;
043:
044: import java.awt.Color;
045: import java.awt.Graphics;
046:
047: import org.netbeans.modules.visualweb.api.designer.DomProvider.DomPosition;
048: import org.netbeans.modules.visualweb.api.designer.cssengine.CssProvider;
049: import org.netbeans.modules.visualweb.api.designer.cssengine.CssValue;
050: import org.netbeans.modules.visualweb.designer.WebForm;
051: import org.netbeans.modules.visualweb.api.designer.cssengine.XhtmlCss;
052: import org.netbeans.modules.visualweb.designer.CssUtilities;
053: import org.netbeans.modules.visualweb.designer.DesignerUtils;
054:
055: import org.openide.ErrorManager;
056:
057: import org.w3c.dom.Element;
058: import org.w3c.dom.Node;
059:
060: /**
061: * LineBox, used during inline formatting of CSS2 content.
062: * The LineBox wholds Boxes for a single line.
063: * <p>
064: * See http://www.w3.org/TR/REC-CSS2/visuren.html
065: *
066: * @todo Should the line box know its width? Its margins?
067: *
068: * NOTE: This class has close ties with the ModelMapper class so be
069: * sure to keep the two in sync.
070: *
071: * @author Tor Norbye
072: */
073: public class LineBox extends ContainerBox {
074: private int maxWidth;
075: private int nextX;
076:
077: /** True iff the linebox should be float positioned */
078: boolean isFloated;
079:
080: public LineBox(WebForm webform, Element element, int maxWidth,
081: int indent) {
082: // A LineBox isn't exactly an inline box, but it's NOT a block
083: // level box. Perhaps I should reverse the logic and call
084: // the constructor parameter "blocklevel" instead.
085: super (webform, element, BoxType.LINEBOX, true, false);
086:
087: // XXX why am I passing element to super? I shouldn't do that
088: this .maxWidth = maxWidth;
089: this .nextX = indent;
090:
091: effectiveTopMargin = 0; // Is this still necessary?
092: effectiveBottomMargin = 0;
093: }
094:
095: protected String paramString() {
096: StringBuffer sb = new StringBuffer();
097:
098: for (int i = 0; i < getBoxCount(); i++) {
099: CssBox child = getBox(i);
100:
101: if (child.getBoxType() == BoxType.TEXT) {
102: sb.append(((TextBox) child).getText());
103: } else if (child.getBoxType() == BoxType.SPACE) {
104: sb.append(' ');
105: } else if (child.getBoxType() == BoxType.LINEBREAK) {
106: sb.append("<br>");
107: } else if (child.getElement() != null) {
108: sb.append('<');
109: sb.append(child.getElement().getTagName());
110: sb.append('>');
111: }
112: }
113:
114: return "floated=" + isFloated + "," + "contents="
115: + sb.toString() + "," + // NOI18N
116: super .paramString();
117: }
118:
119: /** LineBoxes do not have margins, borders, grids, etc. like regular
120: * boxes.
121: */
122: protected void initialize() {
123: effectiveTopMargin = 0;
124: effectiveBottomMargin = 0;
125: }
126:
127: /** LineBoxes do not have margins, borders, grids, etc. like regular
128: * boxes.
129: */
130: protected void initializeInvariants() {
131: }
132:
133: /**
134: * Return the maximum/allocated width of this line box.
135: * This will typically be the width of the containing block,
136: * but floats may reduce the width.
137: */
138: public int getMaxWidth() {
139: return maxWidth;
140: }
141:
142: /**
143: * Check whether the given box will fit in this line box.
144: */
145: public boolean canFit(CssBox box) {
146:
147: int boxWidth = box.getWidth();
148:
149: return !((nextX + leftMargin + boxWidth) > maxWidth);
150: }
151:
152: /**
153: * Check whether the given box will fit in this line box.
154: * Now counting floats.
155: */
156: public boolean canFit(FormatContext context, CssBox box, int targetY) {
157: if (box.getParent() == null) {
158: return (canFit(box));
159: }
160: //we need to recalculate the height and the width for a newly added
161: //inline box, because it may not fit if there is a clear box and
162: //the inline box height is bigger than the line height. In such a case,
163: //this line will overlap with the clear box
164:
165: //a resulting line height would be a maximum of the boxes height
166: int maxHeight = Math.max(box.getHeight(), box.getHeight());
167: //need to recalculate the line width
168: int maxWidth = context.getMaxWidth(this , this .getParent(),
169: targetY, maxHeight);
170:
171: int boxWidth = box.getWidth();
172:
173: return !((nextX + leftMargin + boxWidth) > maxWidth);
174: }
175:
176: /**
177: * Return true iff the line box is "full", e.g. it cannot accomodate
178: * any more content.
179: */
180: public boolean isFull() {
181: return nextX >= maxWidth;
182: }
183:
184: /**
185: * Return the "real" width of the box. This is the actual width
186: * of the text/boxes in the line box, as opposed to the allocated
187: * width.
188: *
189: * @todo If text-align is justify, we should return maxWidth since
190: * we will fill it out.
191: */
192: public int getActualWidth() {
193: return nextX;
194: }
195:
196: /**
197: * Return the next x position that we will assign to a box
198: * added to this line box. The caller is responsible for actually
199: * making sure the box will fit (by calling canFit()) first.
200: */
201: public int getNextX() {
202: return nextX;
203: }
204:
205: /**
206: * Report whether the line box is empty (contains no inline boxes).
207: *
208: * @return true iff there are no inline boxes in this linebox.
209: */
210: public boolean isEmpty() {
211: return getBoxCount() == 0;
212: }
213:
214: /**
215: * Adjust the horizontal alingment of this line. Shift the
216: * linebox around in the containing block; this mostly mean moving
217: * it to the left or right containing block edge, but it also has to
218: * for account for floats.
219: * lineHeight must be passed in; we need the line height to
220: * decide if we intersect any floats. I could just use the box height
221: * (which will be set after applyVerticalAlignments) but this ensures
222: * that the caller doesn't accidentally call applyHorizontalAlignments
223: * before applyVerticalAlignments, etc.
224: * @todo This method works by moving the entire linebox left/right.
225: * That's not right (and wouldn't work for justify). It also
226: * conflicts with what is computed in addToLineBox (where we set
227: * the position.) Revisit this.
228: */
229: public void applyHorizontalAlignments(int leftEdge, int lineHeight,
230: FormatContext context) {
231: if (isEmpty()) {
232: return;
233: }
234:
235: // TODO Left edge code is wrong - not taking floats into consideration
236: // Value al = null;
237: CssValue cssAl = null;
238:
239: Element element = getElement();
240: if (element != null) {
241: // al = CssLookup.getValue(element, XhtmlCss.TEXT_ALIGN_INDEX);
242: cssAl = CssProvider.getEngineService()
243: .getComputedValueForElement(element,
244: XhtmlCss.TEXT_ALIGN_INDEX);
245: } else {
246: ErrorManager
247: .getDefault()
248: .log(
249: "linebox element was null - can't look up an alignment for it");
250: }
251:
252: // if (al != null) {
253: if (cssAl != null) {
254: // if (al == CssValueConstants.LEFT_VALUE) {
255: if (CssProvider.getValueService().isLeftValue(cssAl)) {
256: // Done below - fall through
257: // } else if (al.equals(CssValueConstants.RIGHT_VALUE)) {
258: // XXX Revise if really equals is needed or it is just a mistake and identity check is OK.
259: } else if (CssProvider.getValueService()
260: .isRightValue(cssAl)) {
261: // XXX No, gotta subtrack rightPadding, rightBorderWidth,
262: // and rightPadding!
263: int x = /*containingBlockX+*/(leftEdge + containingBlockWidth)
264: - getWidth();
265: setX(x);
266:
267: return;
268: // } else if (al == CssValueConstants.CENTER_VALUE || al == CssValueConstants.RAVECENTER_VALUE) {
269: } else if (CssProvider.getValueService().isCenterValue(
270: cssAl)
271: || CssProvider.getValueService().isRaveCenterValue(
272: cssAl)) {
273: // XXX No, gotta subtrack rightPadding, rightBorderWidth,
274: // and rightPadding!
275: int x = /*containingBlockX*/+leftEdge
276: + ((containingBlockWidth - getWidth()) / 2);
277: setX(x);
278:
279: return;
280: // } else if (al == CssValueConstants.JUSTIFY_VALUE) {
281: } else if (CssProvider.getValueService().isJustifyValue(
282: cssAl)) {
283: // "Conforming user agents may interpret the value
284: // 'justify' as 'left' or 'right', depending on
285: // whether the element's default writing direction is
286: // left-to-right or right-to-left, respectively. "
287: // Ah heck, it's easy so let's do it (not doing
288: // character spacing, just word spacing)
289: int n = getBoxCount();
290: setX(0);
291:
292: if (n > 1) {
293: int textWidth = 0;
294:
295: for (int i = 0; i < n; i++) {
296: textWidth += getBox(i).getWidth(); // contentWidth instead?
297: }
298:
299: int space = maxWidth - textWidth;
300:
301: if (space < textWidth) { // don't justify nearly empty lines
302:
303: int portion = space / (n - 1); // separator
304: int x = 0;
305:
306: for (int i = 0; i < (n - 1); i++) {
307: CssBox box = getBox(i);
308: box.setX(x);
309: x += box.getWidth();
310: x += portion;
311: }
312:
313: CssBox lastBox = getBox(n - 1);
314: lastBox.setX(maxWidth - lastBox.getWidth());
315: }
316: }
317: } else {
318: // assert false : al;
319: ErrorManager.getDefault().notify(
320: ErrorManager.INFORMATIONAL,
321: new IllegalStateException(
322: "Unexpected alignment value, cssAl="
323: + cssAl)); // NOI18N
324: }
325: }
326:
327: // Left alignment: only need to adjust for floats
328: setX(leftEdge); // XXX What about containingBlockX?
329: }
330:
331: /**
332: * For each of the inline boxes on this line, adjust
333: * the vertical position of the inline boxes according
334: * to the individual vertical alignment settings.
335: * <p
336: * @todo Implement proper alignment fixing using font alignments
337: * etc.
338: * @return the total height of the line. If the LineBox is
339: * empty, it will return -1.
340: */
341: public int applyVerticalAlignments() {
342: /*
343: if (isEmpty()) {
344: return -1;
345: }
346: */
347: int height = 0;
348:
349: // The above is the MINIMUM line height - however, the spec
350: // (section 10.8.1) actually states that it's the minimum line
351: // height when set on a block-level element; if it's set on an
352: // inline element, it's the exact height of each box generated
353: // by the element (except for inline replaced element, where
354: // the box height is given by the height property.)
355: // This gets a bit tricky since the attribute is inherited.
356: int num = getBoxCount();
357: int maxAbove = 0; // above baseline
358: int maxBelow = 0; // below baseline
359: boolean haveText = false;
360:
361: for (int i = 0; i < num; i++) {
362: // XXX This needs to get better - gotta use font
363: // alignment, images, etc.
364: CssBox box = getBox(i);
365: int h;
366: //10.8
367: //On an inline-level element, 'line-height' specifies the height
368: //that is used in the calculation of the line box height (except for
369: //inline replaced elements, where the height of the box is given
370: //by the 'height' property).
371: CssValue cssBoxHeightValue = CssProvider.getEngineService()
372: .getComputedValueForElement(box.getElement(),
373: XhtmlCss.LINE_HEIGHT_INDEX);
374: if (!box.isReplacedBox()
375: && !CssProvider.getValueService().isNormalValue(
376: cssBoxHeightValue)
377: // XXX #6494312 Faking support for inline tables.
378: && !(box instanceof TableBox)) {
379: h = (int) cssBoxHeightValue.getFloatValue();
380: } else {
381: h = box.getHeight();
382: if (box.getBoxType() == BoxType.TEXT) {
383: haveText = true;
384: }
385:
386: }
387:
388: int above = box.getContributingBaseline();
389: int below = h - above;
390:
391: // Boxes that have no baseline, such as images, also don't force themselves
392: // below the baseline
393: if (above == 0) {
394: below = 0;
395: }
396:
397: if (above > maxAbove) {
398: maxAbove = above;
399: }
400:
401: if (below > maxBelow) {
402: maxBelow = below;
403: }
404:
405: if (!box.isReplacedBox()
406: && !CssProvider.getValueService().isNormalValue(
407: cssBoxHeightValue)
408: // XXX #6494312 Faking support for inline tables.
409: && !(box instanceof TableBox)) {
410: } else {
411: if (maxAbove + maxBelow > h) {
412: h = maxAbove + maxBelow;
413: }
414: }
415:
416: //if the box belongs to some nested container, need to take the line-height
417: //property from that container (in case it has it)
418: CssBox inlineParent = box;
419: while ((inlineParent = inlineParent.originalInlineContainer) != null) {
420: cssBoxHeightValue = CssProvider.getEngineService()
421: .getComputedValueForElement(
422: inlineParent.getElement(),
423: XhtmlCss.LINE_HEIGHT_INDEX);
424: if (!CssProvider.getValueService().isNormalValue(
425: cssBoxHeightValue)
426: && !CssProvider.getEngineService()
427: .isInheritedStyleValueForElement(
428: inlineParent.getElement(),
429: XhtmlCss.LINE_HEIGHT_INDEX)) {
430: //height of the line is exactly the value
431: h = (int) cssBoxHeightValue.getFloatValue();
432: break;
433: }
434: }
435: if (height < h) {
436: height = h;
437: }
438: }
439:
440: Element element = getElement();
441: if (haveText) {
442: if (element != null) { // XXX This is wrong - I should use the element of the child that had text
443: // If not, try setting body font size to 48pt, and have a single <span> with font size 8pt.
444: // The linebox containing the span should have height 8 not 48!
445: // Value heightValue = CssLookup.getValue(element, XhtmlCss.LINE_HEIGHT_INDEX);
446: CssValue cssHeightValue = CssProvider
447: .getEngineService().getComputedValueForElement(
448: element, XhtmlCss.LINE_HEIGHT_INDEX);
449:
450: // if (heightValue != CssValueConstants.NORMAL_VALUE) {
451: if (!CssProvider.getValueService().isNormalValue(
452: cssHeightValue)) {
453: // int lineHeight = (int)heightValue.getFloatValue();
454: int lineHeight = (int) cssHeightValue
455: .getFloatValue();
456: if (lineHeight > height) {
457: height = lineHeight;
458: }
459: }
460: }
461: }
462:
463: // linebox's baseline. This is the "baseline of the parent box"
464: // listed in the CSS2 spec under the vertical-align property.
465: // int baseLine = height - maxBelow;
466: // XXX #109310 The baseline is at the bottom.
467: int baseLine = height;
468:
469: // TODO: implement half-leading!! See CSS2 section 10.8.1!
470: // Vertical alignment:
471: // TODO: implement correct text-top, text-bottom and middle
472: // semantics, plus properly compute offset to be used in sub
473: // and super alignment
474: for (int i = 0; i < num; i++) {
475: // XXX This needs to get better - gotta use font
476: // alignment, images, etc.
477: CssBox box = getBox(i); // XX do nothing for LINEBREAK!!!
478: // Value align = CssLookup.getValue(box.getElement(), XhtmlCss.VERTICAL_ALIGN_INDEX);
479: CssValue cssAlign;
480: if (box.originalInlineContainer != null
481: && !CssProvider.getEngineService()
482: .isInheritedStyleValueForElement(
483: box.getElement(),
484: XhtmlCss.VERTICAL_ALIGN_INDEX)) {
485: cssAlign = CssProvider.getEngineService()
486: .getComputedValueForElement(
487: box.originalInlineContainer
488: .getElement(),
489: XhtmlCss.VERTICAL_ALIGN_INDEX);
490: } else {
491: cssAlign = CssProvider.getEngineService()
492: .getComputedValueForElement(box.getElement(),
493: XhtmlCss.VERTICAL_ALIGN_INDEX);
494: }
495: // if (align == CssValueConstants.BASELINE_VALUE) {
496: if (CssProvider.getValueService().isBaseLineValue(cssAlign)) {
497: // XXX Just have a metrics accessor on CssBox instead and use that?
498: int y = baseLine - box.getBaseline();
499: box.setY(y);
500: // } else if ((align == CssValueConstants.SUPER_VALUE) ||
501: // (align == CssValueConstants.SUB_VALUE)) {
502: } else if (CssProvider.getValueService().isSuperValue(
503: cssAlign)
504: || CssProvider.getValueService().isSubValue(
505: cssAlign)) {
506: int SHIFT;
507:
508: // TODO -- gotta compute from font size! But which font? linebox'!
509: // if (align == CssValueConstants.SUPER_VALUE) {
510: if (CssProvider.getValueService()
511: .isSuperValue(cssAlign)) {
512: SHIFT = -3;
513: } else {
514: SHIFT = 3;
515: }
516:
517: int y = baseLine - box.getBaseline() + SHIFT;
518: box.setY(y);
519: // } else if ((align == CssValueConstants.TOP_VALUE) ||
520: // (align == CssValueConstants.TEXT_TOP_VALUE)) {
521: } else if (CssProvider.getValueService().isTopValue(
522: cssAlign)
523: || CssProvider.getValueService().isTextTopValue(
524: cssAlign)) {
525: // This implements top, not text-top so add separate computation
526: // for that
527: box.setY(0);
528: // } else if ((align == CssValueConstants.BOTTOM_VALUE) ||
529: // (align == CssValueConstants.TEXT_BOTTOM_VALUE)) {
530: } else if (CssProvider.getValueService().isBottomValue(
531: cssAlign)
532: || CssProvider.getValueService().isTextBottomValue(
533: cssAlign)) {
534: // This implements bottom, not text-bottom so add separate
535: // computation for that
536: box.setY(height - box.getHeight());
537: // } else if (align == CssValueConstants.MIDDLE_VALUE) {
538: } else if (CssProvider.getValueService().isMiddleValue(
539: cssAlign)) {
540: // How do we compute the parent x-height called for in
541: // the spec? Hack for now -- just use half of
542: // parent's fontheight! Looks like Batik's
543: // LengthManager is doing a 0.5 factor of the font
544: // height to compute EXS anyway!
545:
546: if (isTextualBox(box)) {
547: // int pex = (int)CssLookup.getFontSize(element, DesignerSetings.getInstance().getDefaultFontSize()) / 2;
548: // int pex = (int)CssProvider.getValueService().getFontSizeForElement(element, DesignerSettings.getInstance().getDefaultFontSize()) / 2;
549: int pex = (int) CssProvider.getValueService()
550: .getFontSizeForElement(element,
551: webform.getDefaultFontSize()) / 2;
552: int y = baseLine - (box.getHeight() / 2) - pex;
553: box.setY(y);
554: } else {
555: // XXX #122162 It seems it doesn't work for all non-textual boxes.
556: //// XXX #6344561 The computation didn't work for images, only for text.
557: //// TODO How it should be done correct way, study the spec (CSS2 10.8.1).
558: int y = (baseLine / 2) - (box.getHeight() / 2);
559: box.setY(y);
560: }
561: } else {
562: // Percentage or length -- not yet handled!
563:
564: /*
565: // If percentage: from http://www.westciv.com/style_master/academy/css_tutorial/properties/text_layout.html
566: "Percentage values
567:
568: Specifying vertical-align as a percentage value gives rise to a quite complicated situation. The baseline of the element is raised above the baseline of its parent element. By how much? By that percentage of the element's line-height.
569:
570: For example, {vertical-align: 20%} with an element that has a line-height of 10pt, the baseline of the element will be raised 2 points above the baseline of its parent element.
571:
572: You can lower the baseline of an element below the baseline of its parent by using negative percentage values."
573: */
574:
575: // XXX This nasty assertion always breaks the designer, revise what is supposed to be here.
576: // assert false : align;
577: // XXX #109310 This should be lenght value now (what to do with percentage?).
578: // For now handle similar way like a baseline.
579: int absoluteAlign = (int) cssAlign.getFloatValue();
580: int newY = baseLine - box.getBaseline() - absoluteAlign;
581: box.setY(newY);
582: }
583: }
584:
585: if (height == 0) {
586: // We've added a space but not text - return default line height
587: if (element != null) {
588: // Value heightValue = CssLookup.getValue(element, XhtmlCss.LINE_HEIGHT_INDEX);
589: CssValue cssHeightValue = CssProvider
590: .getEngineService().getComputedValueForElement(
591: element, XhtmlCss.LINE_HEIGHT_INDEX);
592:
593: // if (heightValue == CssValueConstants.NORMAL_VALUE) {
594: if (CssProvider.getValueService().isNormalValue(
595: cssHeightValue)) {
596: // height = (int)(1.1 * CssLookup.getFontSize(element, DesignerSettings.getInstance().getDefaultFontSize()));
597: // height = (int)(1.1 * CssProvider.getValueService().getFontSizeForElement(element, DesignerSettings.getInstance().getDefaultFontSize()));
598: height = (int) (1.1 * CssProvider.getValueService()
599: .getFontSizeForElement(element,
600: webform.getDefaultFontSize()));
601: } else {
602: // height = (int)heightValue.getFloatValue();
603: height = (int) cssHeightValue.getFloatValue();
604: }
605: }
606: }
607:
608: return height;
609: }
610:
611: private static boolean isTextualBox(CssBox box) {
612: if (box == null) {
613: return false;
614: }
615: BoxType boxType = box.getBoxType();
616: return boxType == BoxType.TEXT || boxType == BoxType.SPACE
617: || boxType == BoxType.LINEBREAK;
618: }
619:
620: protected void paintBackground(Graphics g, int px, int py) {
621: // super.paint(g); // We don't want backgrounds painted, etc.
622: // XXX pass in Rectangle directly since that's what we always want?
623: if (isEmpty()) {
624: return;
625: }
626:
627: px += getX();
628: py += getY();
629:
630: int num = getBoxCount();
631:
632: for (int i = 0; i < num; i++) {
633: CssBox ilb = getBox(i);
634:
635: if (ilb.getBoxType() == BoxType.LINEBREAK) {
636: assert i == (num - 1);
637:
638: return;
639: }
640:
641: CssBox positionParent = ilb.getPositionedBy();
642:
643: if (positionParent != this ) { // XXX Can this happen in a linebox???
644:
645: // Not positioned by us - need to compute the
646: // positioning parent's absolute position
647: ilb.paintBackground(g, positionParent.getAbsoluteX(),
648: positionParent.getAbsoluteY());
649: } else {
650: ilb.paintBackground(g, px, py);
651: }
652: }
653: }
654:
655: public void paint(Graphics g, int px, int py) {
656: // super.paint(g); // We don't want backgrounds painted, etc.
657: // XXX pass in Rectangle directly since that's what we always want?
658: if (isEmpty()) {
659: return;
660: }
661:
662: px += getX();
663: py += getY();
664:
665: int num = getBoxCount();
666:
667: for (int i = 0; i < num; i++) {
668: CssBox ilb = getBox(i);
669:
670: if (ilb.getBoxType() == BoxType.LINEBREAK) {
671: assert i == (num - 1);
672:
673: if (!CssBox.paintSpaces) {
674: continue;
675: }
676: }
677:
678: CssBox positionParent = ilb.getPositionedBy();
679:
680: if (positionParent != this ) {
681: // Not positioned by us - need to compute the
682: // positioning parent's absolute position
683: ilb.paint(g, positionParent.getAbsoluteX(),
684: positionParent.getAbsoluteY());
685: } else {
686: ilb.paint(g, px, py);
687: }
688: }
689:
690: if (CssBox.paintSpaces) {
691: g.setColor(Color.PINK);
692: g.drawRect(getAbsoluteX(), getAbsoluteY(),
693: (width == 0) ? 10 : width, height);
694: }
695: }
696:
697: protected void addBox(CssBox box, CssBox prevBox, CssBox nextBox) {
698: assert box.isInlineBox();
699:
700: // Suppress repeated LineBox.SPACE's.
701: // XXX is this taken care of by the LineBoxGroup?
702: if ((box.getBoxType() == BoxType.SPACE)
703: && (getBoxCount() > 0)
704: && (getBox(getBoxCount() - 1).getBoxType() == BoxType.SPACE)) {
705: return;
706: }
707:
708: //super.addBox(box, prevBox, nextBox);
709: // Don't do a super.addBox because we want this list to be
710: // "anonymous"; the fact that boxes are parented by the linebox
711: // is not known to them - so don't set parent pointers, and don't
712: // maintain parent indices (BoxList.syncParentIndices)
713: if (boxes == null) {
714: int initialSize = 25;
715:
716: // XXX todo: pick an initial size based on the box we're
717: // about to create; e.g. look at our node/element field,
718: // look at the number of children, and do something based
719: // on that. Typically we should do the number of element
720: // nodes in the child (since most text nodes are just
721: // whitespace formatting), but for LineBoxes we should do
722: // something smarter.
723: boxes = new BoxList(initialSize);
724: boxes.setSyncParentIndices(false);
725:
726: if (boxType != BoxType.LINEBOX) {
727: boxes.setKeepSorted(true);
728: }
729: }
730:
731: // This seems to be not needed yet.
732: // // XXX #113899 Ensure the correct order. Due to complicated architecture,
733: // // strange layout processing of LineBoxGroup sometimes leads to wrong order in the line box.
734: // int size = boxes.size();
735: // if (size > 0 && prevBox == null && nextBox == null) {
736: // for (int i = 0; i < size; i++) {
737: // CssBox sibling = boxes.get(i);
738: // if(DesignerUtils.getNextSiblingElement(box.getElement()) == sibling.getElement()) {
739: // nextBox = sibling;
740: // break;
741: // }
742: // }
743: // for (int i = size - 1; i >= 0; i--) {
744: // CssBox sibling = boxes.get(i);
745: // if (DesignerUtils.getPreviousSiblingElement(box.getElement()) == sibling.getElement()) {
746: // prevBox = sibling;
747: // break;
748: // }
749: // }
750: // }
751:
752: boxes.add(box, prevBox, nextBox);
753:
754: //box.setParent(this);
755: box.setPositionedBy(this ); // ????
756:
757: box.setLocation(nextX, 0);
758: nextX += box.getWidth() + box.leftMargin + box.rightMargin;
759: }
760:
761: public void relayout(FormatContext context) {
762: // LineBoxGroup should never delegate layout to us but let's
763: // make doubly sure since inheriting ContainerBox.relayout
764: // would be Bad.
765: throw new RuntimeException();
766: }
767:
768: void computeHorizontalLengths(FormatContext context) {
769: // LineBoxes are anonymous, so they should have no "auto" settings
770: // on them
771: }
772:
773: void computeVerticalLengths(FormatContext context) {
774: // LineBoxes are anonymous, so they should have no "auto" settings
775: // on them
776: }
777:
778: /** Return a position within this line box that the given x coordinate
779: * points to in the document.
780: */
781: // public Position computePosition(int px) {
782: public DomPosition computePosition(int px) {
783: if (getBoxCount() > 0) {
784: // Ensure that we're not pointing to a position further out on
785: // the line than the last box
786: CssBox last = getBox(getBoxCount() - 1);
787: int rightmost = (last.getX() + last.getWidth()) - 1;
788:
789: if (rightmost < px) {
790: px = rightmost;
791: }
792: }
793:
794: for (int i = 0, n = getBoxCount(); i < n; i++) {
795: CssBox box = getBox(i);
796:
797: if (box.getBoxType() == BoxType.LINEBREAK) {
798: // Position pos = Position.create(box.getSourceElement(), false);
799: DomPosition pos = webform.createDomPosition(box
800: .getSourceElement(), false);
801:
802: // if (DesignerUtils.checkPosition(pos, false, /*webform*/webform.getManager().getInlineEditor()) != Position.NONE) {
803: // if (ModelViewMapper.isValidPosition(pos, false, /*webform*/webform.getManager().getInlineEditor())) {
804: if (ModelViewMapper.isValidPosition(webform, pos,
805: false, /*webform*/webform.getManager()
806: .getInlineEditor())) {
807: return pos;
808: }
809: }
810:
811: // TODO -- reformat
812: int left = box.getX();
813: int right = left + box.getWidth();
814:
815: if (left > px) {
816: // We've already passed the spot: it must be over a space
817: // between this inline box and the previous inline box
818: // See which one is closest
819: CssBox closest = null;
820: boolean after = false;
821:
822: if (i > 0) {
823: int currDistance = left - px;
824: CssBox prevBox = getBox(i - 1);
825: int prevDistance = px
826: - (prevBox.getX() + prevBox.getWidth());
827:
828: if (currDistance <= prevDistance) {
829: closest = box;
830: } else {
831: closest = prevBox;
832: after = true;
833: }
834: } else {
835: // No previous box - so this is definitely closest
836: closest = box;
837: }
838:
839: if (closest.getBoxType() == BoxType.TEXT) {
840: // Text boxes: we support positions (offsets)
841: // within the text
842: TextBox tb = (TextBox) closest;
843: // Position pos = tb.getFirstPosition();
844: DomPosition pos = tb.getFirstPosition();
845:
846: // if (DesignerUtils.checkPosition(pos, false, /*webform*/webform.getManager().getInlineEditor()) != Position.NONE) {
847: // if (ModelViewMapper.isValidPosition(pos, false, /*webform*/webform.getManager().getInlineEditor())) {
848: if (ModelViewMapper.isValidPosition(webform, pos,
849: false, /*webform*/webform.getManager()
850: .getInlineEditor())) {
851: return pos;
852: }
853: } else if (closest.getBoxType() == BoxType.SPACE) {
854: // Space boxes: we support positions (offsets)
855: // within the text
856: SpaceBox tb = (SpaceBox) closest;
857: // Position pos = tb.getFirstPosition();
858: DomPosition pos = tb.getFirstPosition();
859:
860: // if (DesignerUtils.checkPosition(pos, false, /*webform*/webform.getManager().getInlineEditor()) != Position.NONE) {
861: // if (ModelViewMapper.isValidPosition(pos, false, /*webform*/webform.getManager().getInlineEditor())) {
862: if (ModelViewMapper.isValidPosition(webform, pos,
863: false, /*webform*/webform.getManager()
864: .getInlineEditor())) {
865: return pos;
866: }
867: } else {
868: // Position pos = Position.create(closest.getSourceElement(), after);
869: DomPosition pos = webform.createDomPosition(closest
870: .getSourceElement(), after);
871:
872: // if (DesignerUtils.checkPosition(pos, false, /*webform*/webform.getManager().getInlineEditor()) != Position.NONE) {
873: // if (ModelViewMapper.isValidPosition(pos, false, /*webform*/webform.getManager().getInlineEditor())) {
874: if (ModelViewMapper.isValidPosition(webform, pos,
875: false, /*webform*/webform.getManager()
876: .getInlineEditor())) {
877: return pos;
878: }
879: }
880: } else if ((px >= left) && (px <= right)) {
881: if (box.getBoxType() == BoxType.TEXT) {
882: // Text boxes: we support positions (offsets)
883: // within the text
884: // Position pos = ((TextBox)box).computePosition(px - left);
885: DomPosition pos = ((TextBox) box)
886: .computePosition(px - left);
887:
888: // if (DesignerUtils.checkPosition(pos, false, /*webform*/webform.getManager().getInlineEditor()) != Position.NONE) {
889: // if (ModelViewMapper.isValidPosition(pos, false, /*webform*/webform.getManager().getInlineEditor())) {
890: if (ModelViewMapper.isValidPosition(webform, pos,
891: false, /*webform*/webform.getManager()
892: .getInlineEditor())) {
893: return pos;
894: }
895: } else if (box.getBoxType() == BoxType.SPACE) {
896: // Text boxes: we support positions (offsets)
897: // within the text
898: // Position pos = ((SpaceBox)box).computePosition(px - left);
899: DomPosition pos = ((SpaceBox) box)
900: .computePosition(px - left);
901:
902: // if (DesignerUtils.checkPosition(pos, false, /*webform*/webform.getManager().getInlineEditor()) != Position.NONE) {
903: // if (ModelViewMapper.isValidPosition(pos, false, /*webform*/webform.getManager().getInlineEditor())) {
904: if (ModelViewMapper.isValidPosition(webform, pos,
905: false, /*webform*/webform.getManager()
906: .getInlineEditor())) {
907: return pos;
908: }
909:
910: /*
911: } else if (DesignerUtils.isCaretTarget(box)) {
912: // It's an "atomic" unit; place the caret
913: // on the outside
914: } else if (box instanceof FormComponentBox) {
915: Position pos = ((FormComponentBox)box).computePosition(x-box.getAbsoluteX());
916: if (pos != null) {
917: Position pos = pos;
918: if (Utilities.checkPosition(pos, false, webformwebform.getManager().getInlineEditor()) != Position.NONE) {
919: return pos;
920: }
921: }
922: */
923: }
924:
925: // Other boxes (iframes, images, etc.): the offset
926: // is either "before" or "after" the element:
927: // offset 0 or 1. We round the x to the nearest
928: // left or right edge to decide which side it goes
929: // on.
930: boolean after = px > ((left + right) / 2);
931: // Position pos = Position.create(box.getSourceElement(), after);
932: DomPosition pos = webform.createDomPosition(box
933: .getSourceElement(), after);
934:
935: // if (DesignerUtils.checkPosition(pos, false, /*webform*/webform.getManager().getInlineEditor()) != Position.NONE) {
936: // if (ModelViewMapper.isValidPosition(pos, false, /*webform*/webform.getManager().getInlineEditor())) {
937: if (ModelViewMapper.isValidPosition(webform, pos,
938: false, /*webform*/webform.getManager()
939: .getInlineEditor())) {
940: return pos;
941: }
942: }
943: }
944:
945: // return Position.NONE; // happens when we have no place to put the caret -- all JSF elements
946: return DomPosition.NONE; // happens when we have no place to put the caret -- all JSF elements
947: }
948: }
|