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: package org.netbeans.modules.visualweb.css2;
042:
043: import org.netbeans.modules.visualweb.api.designer.cssengine.CssProvider;
044: import org.netbeans.modules.visualweb.designer.CssUtilities;
045: import java.awt.Color;
046: import java.awt.Font;
047: import java.awt.Toolkit;
048:
049: import org.w3c.dom.Element;
050: import org.w3c.dom.Node;
051: import org.w3c.dom.NodeList;
052:
053: import org.netbeans.modules.visualweb.designer.DesignerPane;
054: import org.netbeans.modules.visualweb.designer.WebForm;
055: import org.netbeans.modules.visualweb.designer.html.HtmlAttribute;
056: import org.netbeans.modules.visualweb.designer.html.HtmlTag;
057:
058: /**
059: * FrameSetBox represents a <frameset> tag.
060: * Info on how to combine frames and JSP:
061: * http://swforum.sun.com/jive/thread.jspa?threadID=47488&tstart=255
062: * This is not working well yet.
063: *
064: * @author Tor Norbye
065: *
066: */
067: public class FrameSetBox extends PageBox {
068: /*
069: protected String paramString() {
070: return super.paramString() + ", " + markup;
071: }
072: */
073:
074: /** Number of columns in the table. */
075: private int columns = -1;
076:
077: /** Number of rows in the table */
078: private int rows = -1;
079:
080: /** Array of cells holding boxes for each cell. Some cells may be empty
081: * (e.g. when no frame was specified)
082: */
083: private CssBox[][] cells;
084: private int[] colSizes;
085: private byte[] colTypes;
086: private int[] rowSizes;
087: private byte[] rowTypes;
088: private int colRelativeTotals;
089: private int rowRelativeTotals;
090:
091: /** Use the "getFrameSetBox" factory method instead */
092: private FrameSetBox(DesignerPane pane, WebForm webform,
093: Element element, BoxType boxType, boolean inline,
094: boolean replaced) {
095: super (pane, webform, element, boxType, inline, replaced);
096: }
097:
098: /** Create a new FrameSetBox, or provide one from a cache */
099: public static FrameSetBox getFrameSetBox(DesignerPane pane,
100: WebForm webform, Element element, BoxType boxType,
101: HtmlTag tag, boolean inline) {
102: FrameSetBox box = new FrameSetBox(pane, webform, element,
103: boxType, inline, tag.isReplacedTag());
104:
105: return box;
106: }
107:
108: // public String toString() {
109: // return "FrameSetBox[" + paramString() + "]";
110: // }
111:
112: protected void layoutContext(FormatContext context) {
113: relayout(context);
114: }
115:
116: protected void createChildren(CreateContext context) {
117: boolean newContext = false;
118:
119: if (context == null) {
120: newContext = true;
121:
122: // TODO instead of checking on the box count, which could be 0
123: // for valid reasons, have a dedicated flag here which is
124: // invalidated on document edits, etc.
125: context = new CreateContext();
126: context.pushPage(webform);
127:
128: // Font font = CssLookup.getFont(body, DesignerSettings.getInstance().getDefaultFontSize());
129: // Font font = CssProvider.getValueService().getFontForElement(body, DesignerSettings.getInstance().getDefaultFontSize(), Font.PLAIN);
130: // context.metrics = Toolkit.getDefaultToolkit().getFontMetrics(font);
131: // XXX Missing text.
132: context.metrics = CssUtilities
133: .getDesignerFontMetricsForElement(body, null,
134: webform.getDefaultFontSize());
135:
136: //super.createChildren(cc);
137: }
138:
139: Element element = getElement();
140: String colString = element.getAttribute(HtmlAttribute.COLS);
141: String[] split = null;
142:
143: if (colString.length() > 0) {
144: // TODO - precompile the regular expression?
145: split = colString.split(","); // NOI18N
146: }
147:
148: if ((split == null) || (split.length == 0)
149: || (split.length == 1)) {
150: colSizes = new int[] { 1 };
151: colTypes = new byte[] { HtmlAttribute.VALUE_RELATIVE };
152: } else {
153: colSizes = new int[split.length];
154: colTypes = new byte[split.length];
155:
156: int percentTotals = 0;
157:
158: for (int i = 0; i < split.length; i++) {
159: colSizes[i] = HtmlAttribute.parseInt(split[i]);
160: colTypes[i] = HtmlAttribute.getNumberType(split[i]);
161:
162: if ((colSizes[i] == 100)
163: && (colTypes[i] == HtmlAttribute.VALUE_PERCENTAGE)) {
164: // As per the spec, 100% is the same as *
165: // hence the mapping.
166: colSizes[i] = 1;
167: colTypes[i] = HtmlAttribute.VALUE_RELATIVE;
168: } else if ((colSizes[i] == 0)
169: && (colTypes[i] == HtmlAttribute.VALUE_RELATIVE)) {
170: // "*" should be treated as 1*, not 0*
171: colSizes[i] = 1;
172: }
173:
174: if (colTypes[i] == HtmlAttribute.VALUE_PERCENTAGE) {
175: percentTotals += colSizes[i];
176: } else if (colTypes[i] == HtmlAttribute.VALUE_RELATIVE) {
177: colRelativeTotals += colSizes[i];
178: }
179: }
180:
181: if (percentTotals > 100) {
182: for (int i = 0; i < colSizes.length; i++) {
183: // XXX make isPercentage() method
184: if (colTypes[i] == HtmlAttribute.VALUE_PERCENTAGE) {
185: colSizes[i] = (colSizes[i] * 100)
186: / percentTotals;
187: }
188: }
189:
190: percentTotals = 100;
191: } // XXX what if percentTotals < 100 ???
192: }
193:
194: String rowString = element.getAttribute(HtmlAttribute.ROWS);
195:
196: if (rowString.length() > 0) {
197: // TODO - precompile the regular expression?
198: split = rowString.split(","); // NOI18N
199: }
200:
201: if ((split == null) || (split.length == 0)
202: || (split.length == 1)) {
203: colSizes = new int[] { 1 };
204: rowTypes = new byte[] { HtmlAttribute.VALUE_RELATIVE };
205: } else {
206: rowSizes = new int[split.length];
207: rowTypes = new byte[split.length];
208:
209: int percentTotals = 0;
210:
211: for (int i = 0; i < split.length; i++) {
212: rowSizes[i] = HtmlAttribute.parseInt(split[i]);
213: rowTypes[i] = HtmlAttribute.getNumberType(split[i]);
214:
215: if ((rowSizes[i] == 100)
216: && (rowTypes[i] == HtmlAttribute.VALUE_PERCENTAGE)) {
217: // As per the spec, 100% is the same as *
218: // hence the mapping.
219: rowSizes[i] = 1;
220: rowTypes[i] = HtmlAttribute.VALUE_RELATIVE;
221: } else if ((rowSizes[i] == 0)
222: && (rowTypes[i] == HtmlAttribute.VALUE_RELATIVE)) {
223: // "*" should be treated as 1*, not 0*
224: rowSizes[i] = 1;
225: }
226:
227: if (rowTypes[i] == HtmlAttribute.VALUE_PERCENTAGE) {
228: percentTotals += rowSizes[i];
229: } else if (rowTypes[i] == HtmlAttribute.VALUE_RELATIVE) {
230: rowRelativeTotals += rowSizes[i];
231: }
232: }
233:
234: if (percentTotals > 100) {
235: for (int i = 0; i < rowSizes.length; i++) {
236: // XXX make isPercentage() method
237: if (rowTypes[i] == HtmlAttribute.VALUE_PERCENTAGE) {
238: rowSizes[i] = (rowSizes[i] * 100)
239: / percentTotals;
240: }
241: }
242:
243: percentTotals = 100;
244: }
245: }
246:
247: // XXX can framesets contain anything but frame boxes (no)?
248: // Can they be arbitrarily nested (yes)? Can they occur anywhere
249: // EXCEPT as a replacement for body? (no, except as children
250: // of other frames). Do CSS widths and heights on frames have
251: // any effect on the layout? (no)
252: //
253: // (Answers determined empirically from Mozilla and
254: // Safari. Safari accepts a frameset inside a body tag,
255: // mozilla does not.)
256: rows = rowSizes.length;
257: columns = colSizes.length;
258: cells = new CssBox[rows][columns];
259:
260: // The cell boxes should be either FrameSetBoxes or FrameBoxes
261: setProbableChildCount(rows * columns);
262:
263: int currentRow = 0;
264: int currentCol = 0;
265:
266: NodeList list = element.getChildNodes();
267: int len = list.getLength();
268:
269: for (int i = 0; i < len; i++) {
270: Node trn = (Node) list.item(i);
271:
272: if (trn.getNodeType() != Node.ELEMENT_NODE) {
273: continue;
274: }
275:
276: Element e = (Element) trn;
277: String tagName = e.getTagName();
278: HtmlTag tag = HtmlTag.getTag(tagName);
279:
280: if ((tag == HtmlTag.FRAME) || (tag == HtmlTag.FRAMESET)) {
281: boolean inline = false;
282: ContainerBox box = null;
283:
284: // FrameSetBoxes are not created by the BoxFactory since
285: // they are only allowed in place of the body tag (handled
286: // in PageBox' factory method) or as a child of another
287: // FrameSetBox (handled here)
288: if (tag == HtmlTag.FRAMESET) {
289: FrameSetBox fsb = FrameSetBox.getFrameSetBox(
290: webform.getPane(), webform, e, boxType,
291: tag, inline);
292: fsb.tag = HtmlTag.FRAMESET;
293: fsb.isTopLevel = false;
294: box = fsb;
295: } else {
296: box = FrameBox.getFrameBox(context, webform, e,
297: BoxType.STATIC, tag, false);
298: box.tag = HtmlTag.FRAME;
299: }
300:
301: box.clipOverflow = true;
302: box.initialize();
303: addBox(box, null, null);
304: finishLineBox(context); // ensure that cell has its own linebox
305: box.createChildren(context);
306: finishLineBox(context); // ensure that content outside doesn't spill into cell linebox
307:
308: cells[currentRow][currentCol] = box;
309: currentCol++;
310:
311: if (currentCol == columns) {
312: currentCol = 0;
313: currentRow++;
314:
315: if (currentRow == rows) {
316: break; // additional frames are ignored
317: }
318: }
319: }
320: }
321:
322: if (newContext) {
323: fixedBoxes = context.getFixedBoxes();
324: context.popPage();
325: }
326: }
327:
328: public void relayout(FormatContext context) {
329: // Ensure that createChildren has run and has initialized
330: // the row and column fields
331: if ((columns <= 0) || (rows <= 0)) {
332: return;
333: }
334:
335: // Compute column widths
336: // Compute row heights
337: int[] colWidths = new int[columns];
338: int[] rowHeights = new int[rows];
339:
340: layout(colWidths, rowHeights, context);
341:
342: // layout(colWidths, rowHeights, context);
343: effectiveTopMargin = topMargin;
344: effectiveBottomMargin = bottomMargin;
345: }
346:
347: private void layout(int[] columnWidths, int[] rowHeights,
348: FormatContext context) {
349: spread(containingBlockWidth, columnWidths, colSizes, colTypes,
350: colRelativeTotals);
351: spread(containingBlockHeight, rowHeights, rowSizes, rowTypes,
352: rowRelativeTotals);
353:
354: // For debugging only: color frame backgrounds
355: //Color[] colors = { Color.red, Color.blue, Color.green, Color.gray,
356: // Color.pink, Color.yellow, Color.orange,
357: // Color.lightGray, Color.magenta, Color.cyan };
358: //int currentColor = 0;
359: CssBorder border = CssBorder.getBorder(CssBorder.STYLE_INSET,
360: 3, Color.GRAY);
361:
362: // TODO -- I should use cell spacing here, like Safari.
363: // Alternatively, rather than having individual frame borders
364: // I could paint a complete grid, the way Mozilla does (similar
365: // to collapsing border model.)
366: int y = 0;
367:
368: for (int i = 0; i < rows; i++) {
369: int x = 0;
370:
371: for (int j = 0; j < columns; j++) {
372: CssBox box = cells[i][j];
373:
374: if (box != null) {
375: formatCell(i, j, columnWidths, rowHeights, context);
376:
377: box.border = border;
378: box.leftBorderWidth = border.getLeftBorderWidth();
379: box.topBorderWidth = border.getTopBorderWidth();
380: box.bottomBorderWidth = border
381: .getBottomBorderWidth();
382: box.rightBorderWidth = border.getRightBorderWidth();
383:
384: box.width = columnWidths[j];
385: box.contentWidth = box.width
386: - (box.leftBorderWidth + box.leftPadding
387: + box.rightPadding + box.rightBorderWidth);
388: box.height = rowHeights[i];
389: box.contentHeight = box.height
390: - (box.topBorderWidth + box.topPadding
391: + box.bottomPadding + box.bottomBorderWidth);
392:
393: // For debugging only: color frame backgrounds
394: //cells[i][j].bg = colors[currentColor];
395: //currentColor = (currentColor + 1) % colors.length;
396: cells[i][j].setLocation(x, y);
397: }
398:
399: x += columnWidths[j];
400: }
401:
402: y += rowHeights[i];
403: }
404: }
405:
406: private void formatCell(int row, int col, int[] columnWidths,
407: int[] rowHeights, FormatContext context) {
408: CssBox box = cells[row][col];
409: int ac = columnWidths[col];
410: int ah = rowHeights[row];
411: box.setContainingBlock(0, 0, ac, ah);
412: box.contentWidth = ac - box.leftBorderWidth - box.leftPadding
413: - box.rightPadding - box.rightBorderWidth;
414: box.contentHeight = ah - box.topBorderWidth - box.topPadding
415: - box.bottomPadding - box.bottomBorderWidth;
416:
417: if (box instanceof FrameSetBox) {
418: ((FrameSetBox) box).layoutContext(context);
419: } else {
420: box.relayout(context);
421: }
422:
423: box.inline = false;
424: box.replaced = false;
425:
426: box.boxType = BoxType.STATIC; // prevent "bad" docs from screwing things up
427:
428: // by setting position: absolute on <td>'s for example
429: box.computeVerticalLengths(context);
430: box.height = box.topBorderWidth + box.topPadding
431: + box.contentHeight + box.bottomPadding
432: + box.bottomBorderWidth /* XXX + cellSpacing*/;
433: }
434:
435: /**
436: * This method is based on
437: * javax.swing.text.html.FrameSetView.spread().
438: *
439: *
440: * This method is responsible for returning in span[] the
441: * span for each child view along the major axis. it
442: * computes this based on the information that extracted
443: * from the value of the ROW/COL attribute.
444: */
445: private void spread(int targetSpan, int[] span, int[] sizes,
446: byte[] types, int relativeTotals) {
447: if (targetSpan == 0) {
448: return;
449: }
450:
451: int tempSpace = 0;
452: int remainingSpace = targetSpan;
453:
454: // allocate the absolute's first, they have
455: // precedence
456: //
457: for (int i = 0; i < span.length; i++) {
458: if (types[i] == HtmlAttribute.VALUE_ABSOLUTE) {
459: span[i] = sizes[i];
460: remainingSpace -= span[i];
461: }
462: }
463:
464: // then deal with percents.
465: //
466: tempSpace = remainingSpace;
467:
468: for (int i = 0; i < span.length; i++) {
469: if (types[i] == HtmlAttribute.VALUE_PERCENTAGE) {
470: if (tempSpace > 0) {
471: span[i] = (sizes[i] * tempSpace) / 100;
472: remainingSpace -= span[i];
473: } else { // tempSpace <= 0
474: span[i] = targetSpan / span.length;
475: remainingSpace -= span[i];
476: }
477: }
478: }
479:
480: // allocate remainingSpace to relative
481: if ((remainingSpace > 0) && (relativeTotals > 0)) {
482: for (int i = 0; i < span.length; i++) {
483: if (types[i] == HtmlAttribute.VALUE_RELATIVE) {
484: span[i] = (remainingSpace * sizes[i])
485: / relativeTotals;
486: }
487: }
488: } else if (remainingSpace > 0) {
489: // There are no relative columns/rows and the space has been
490: // under- or overallocated. In this case, turn all the
491: // percentage and pixel specified columns/rows to percentage
492: // columns/rows based on the ratio of their pixel count to the
493: // total "virtual" size. (In the case of percentage columns/rows,
494: // the pixel count would equal the specified percentage
495: // of the screen size.
496: // This action is in accordance with the HTML
497: // 4.0 spec (see section 8.3, the end of the discussion of
498: // the FRAMESET tag). The precedence of percentage and pixel
499: // specified columns/rows is unclear (spec seems to indicate that
500: // they share priority, however, unspecified what happens when
501: // overallocation occurs.)
502: // addendum is that we behave similiar to netscape in that specified
503: // widths have precedance over percentage widths...
504: float vTotal = (float) (targetSpan - remainingSpace);
505: float[] tempPercents = new float[span.length];
506: remainingSpace = targetSpan;
507:
508: for (int i = 0; i < span.length; i++) {
509: // ok we know what our total space is, and we know how large each
510: // column/rows should be relative to each other... therefore we can use
511: // that relative information to deduce their percentages of a whole
512: // and then scale them appropriately for the correct size
513: tempPercents[i] = ((float) span[i] / vTotal) * 100.00f;
514: span[i] = (int) (((float) targetSpan * tempPercents[i]) / 100.00f);
515: remainingSpace -= span[i];
516: }
517:
518: // this is for just in case there is something left over.. if there is we just
519: // add it one pixel at a time to the frames in order.. We shouldn't really ever get
520: // here and if we do it shouldn't be with more than 1 pixel, maybe two.
521: int i = 0;
522:
523: while (remainingSpace != 0) {
524: if (remainingSpace < 0) {
525: span[i++]--;
526: remainingSpace++;
527: } else {
528: span[i++]++;
529: remainingSpace--;
530: }
531:
532: // just in case there are more pixels than frames...should never happen..
533: if (i == span.length) {
534: i = 0;
535: }
536: }
537: }
538: }
539:
540: // Because PageBox overrides these
541: // TODO -- should I create a BodyBox which does some of the specific stuff
542: // I don't want inherited into framesetbox?
543: public int getAbsoluteX() {
544: ContainerBox parent = getParent();
545: if (positionedBy != parent) {
546: return positionedBy.getAbsoluteX() + getX() + leftMargin;
547: }
548:
549: if (parent != null) {
550: return parent.getAbsoluteX() + x + leftMargin;
551: } else {
552: return x + leftMargin;
553: }
554: }
555:
556: // Because PageBox overrides these
557: public int getAbsoluteY() {
558: ContainerBox parent = getParent();
559: if (positionedBy != parent) {
560: return positionedBy.getAbsoluteY() + getY()
561: + effectiveTopMargin;
562: }
563:
564: if (parent != null) {
565: return parent.getAbsoluteY() + y + effectiveTopMargin;
566: } else {
567: return y + effectiveTopMargin;
568: }
569: }
570: }
|