/* GNU LESSER GENERAL PUBLIC LICENSE Copyright (C) 2006 The Lobo Project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Contact info: lobochief@users.sourceforge.net */ /* * Created on Jan 29, 2006 */ package org.lobobrowser.html.gui; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Insets; import java.util.ArrayList; import java.util.StringTokenizer; import java.util.logging.Logger; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JSplitPane; import org.lobobrowser.html.BrowserFrame; import org.lobobrowser.html.HtmlRendererContext; import org.lobobrowser.html.domimpl.FrameNode; import org.lobobrowser.html.domimpl.HTMLElementImpl; import org.lobobrowser.html.domimpl.NodeImpl; import org.lobobrowser.html.renderer.NodeRenderer; import org.lobobrowser.html.style.HtmlLength; import org.lobobrowser.util.gui.WrapperLayout; /** * A Swing panel used to render FRAMESETs only. It is used by {@link HtmlPanel} * when a document is determined to be a FRAMESET. * * @see HtmlPanel * @see HtmlBlockPanel */ public class FrameSetPanel extends JComponent implements NodeRenderer { private static final long serialVersionUID = 5048031593959987324L; private static final Logger logger = Logger.getLogger(FrameSetPanel.class.getName()); public FrameSetPanel() { super(); this.setLayout(WrapperLayout.getInstance()); // TODO: This should be a temporary preferred size this.setPreferredSize(new Dimension(600, 400)); } private static HtmlLength[] getLengths(final String spec) { if (spec == null) { return new HtmlLength[] { new HtmlLength("1*") }; } final StringTokenizer tok = new StringTokenizer(spec, ","); final ArrayList<HtmlLength> lengths = new ArrayList<>(); while (tok.hasMoreTokens()) { final String token = tok.nextToken().trim(); try { lengths.add(new HtmlLength(token)); } catch (final Exception err) { logger.warning("Frame rows or cols value [" + spec + "] is invalid."); } } return lengths.toArray(HtmlLength.EMPTY_ARRAY); } private static HTMLElementImpl[] getSubFrames(final HTMLElementImpl parent) { final NodeImpl[] children = parent.getChildrenArray(); final ArrayList<NodeImpl> subFrames = new ArrayList<>(); for (final NodeImpl child : children) { if (child instanceof HTMLElementImpl) { final String nodeName = child.getNodeName(); if ("FRAME".equalsIgnoreCase(nodeName) || "FRAMESET".equalsIgnoreCase(nodeName)) { subFrames.add(child); } } } return subFrames.toArray(new HTMLElementImpl[0]); } private HTMLElementImpl rootNode; /** * Sets the FRAMESET node and invalidates the component so it can be rendered * immediately in the GUI thread. */ public void setRootNode(final NodeImpl node) { // Method expected to be called in the GUI thread. if (!(node instanceof HTMLElementImpl)) { throw new IllegalArgumentException("node=" + node); } final HTMLElementImpl element = (HTMLElementImpl) node; this.rootNode = element; final HtmlRendererContext context = element.getHtmlRendererContext(); this.htmlContext = context; this.domInvalid = true; this.invalidate(); this.validateAll(); this.repaint(); } protected void validateAll() { Component toValidate = this; for (;;) { final Container parent = toValidate.getParent(); if ((parent == null) || parent.isValid()) { break; } toValidate = parent; } toValidate.validate(); } public final void processDocumentNotifications(final DocumentNotification[] notifications) { // Called in the GUI thread. if (notifications.length > 0) { // Not very efficient, but it will do. this.domInvalid = true; this.invalidate(); if (this.isVisible()) { this.validate(); this.repaint(); } } } private HtmlRendererContext htmlContext; private Component[] frameComponents; private boolean domInvalid = true; @Override public void setBounds(final int x, final int y, final int w, final int h) { super.setBounds(x, y, w, h); } /** * This method is invoked by AWT in the GUI thread to lay out the component. * This implementation is an override. */ @Override public void doLayout() { if (this.domInvalid) { this.domInvalid = false; this.removeAll(); final HtmlRendererContext context = this.htmlContext; if (context != null) { final HTMLElementImpl element = this.rootNode; final String rows = element.getAttribute("rows"); final String cols = element.getAttribute("cols"); final HtmlLength[] rowLengths = FrameSetPanel.getLengths(rows); final HtmlLength[] colLengths = FrameSetPanel.getLengths(cols); final HTMLElementImpl[] subframes = FrameSetPanel.getSubFrames(element); final Component[] frameComponents = new Component[subframes.length]; this.frameComponents = frameComponents; for (int i = 0; i < subframes.length; i++) { final HTMLElementImpl frameElement = subframes[i]; if ((frameElement != null) && "FRAMESET".equalsIgnoreCase(frameElement.getTagName())) { final FrameSetPanel fsp = new FrameSetPanel(); fsp.setRootNode(frameElement); frameComponents[i] = fsp; } else { if (frameElement instanceof FrameNode) { final FrameNode frameNode = (FrameNode) frameElement; if (frameNode.getBrowserFrame() == null) { final BrowserFrame frame = context.createBrowserFrame(); frameNode.setBrowserFrame(frame); frameComponents[i] = frame.getComponent(); } else { frameComponents[i] = frameNode.getBrowserFrame().getComponent(); } } else { frameComponents[i] = new JPanel(); } } } final HtmlLength[] rhl = rowLengths; final HtmlLength[] chl = colLengths; final Component[] fc = this.frameComponents; if ((rhl != null) && (chl != null) && (fc != null)) { final Dimension size = this.getSize(); final Insets insets = this.getInsets(); final int width = size.width - insets.left - insets.right; final int height = size.height - insets.left - insets.right; final int[] absColLengths = getAbsoluteLengths(chl, width); final int[] absRowLengths = getAbsoluteLengths(rhl, height); this.add(this.getSplitPane(this.htmlContext, absColLengths, 0, absColLengths.length, absRowLengths, 0, absRowLengths.length, fc)); } } } super.doLayout(); } private static int[] getAbsoluteLengths(final HtmlLength[] htmlLengths, final int totalSize) { final int[] absLengths = new int[htmlLengths.length]; int totalSizeNonMulti = 0; int sumMulti = 0; for (int i = 0; i < htmlLengths.length; i++) { final HtmlLength htmlLength = htmlLengths[i]; final int lengthType = htmlLength.getLengthType(); if (lengthType == HtmlLength.PIXELS) { final int absLength = htmlLength.getRawValue(); totalSizeNonMulti += absLength; absLengths[i] = absLength; } else if (lengthType == HtmlLength.LENGTH) { final int absLength = htmlLength.getLength(totalSize); totalSizeNonMulti += absLength; absLengths[i] = absLength; } else { sumMulti += htmlLength.getRawValue(); } } final int remaining = totalSize - totalSizeNonMulti; if ((remaining > 0) && (sumMulti > 0)) { for (int i = 0; i < htmlLengths.length; i++) { final HtmlLength htmlLength = htmlLengths[i]; if (htmlLength.getLengthType() == HtmlLength.MULTI_LENGTH) { final int absLength = (remaining * htmlLength.getRawValue()) / sumMulti; absLengths[i] = absLength; } } } return absLengths; } private Component getSplitPane(final HtmlRendererContext context, final int[] colLengths, final int firstCol, final int numCols, final int[] rowLengths, final int firstRow, final int numRows, final Component[] frameComponents) { if (numCols == 1) { final int frameindex = (colLengths.length * firstRow) + firstCol; final Component topComponent = frameindex < frameComponents.length ? frameComponents[frameindex] : null; if (numRows == 1) { return topComponent; } else { final Component bottomComponent = this.getSplitPane(context, colLengths, firstCol, numCols, rowLengths, firstRow + 1, numRows - 1, frameComponents); final JSplitPane sp = new JSplitPane(JSplitPane.VERTICAL_SPLIT, topComponent, bottomComponent); sp.setDividerLocation(rowLengths[firstRow]); return sp; } } else { final Component rightComponent = this.getSplitPane(context, colLengths, firstCol + 1, numCols - 1, rowLengths, firstRow, numRows, frameComponents); final Component leftComponent = this.getSplitPane(context, colLengths, firstCol, 1, rowLengths, firstRow, numRows, frameComponents); final JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftComponent, rightComponent); sp.setDividerLocation(colLengths[firstCol]); return sp; } } }