/* GNU GENERAL LICENSE Copyright (C) 2006 The Lobo Project. Copyright (C) 2014 - 2017 Lobo Evolution This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either verion 3 of the License, or (at your option) any later version. This program 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 General License for more details. You should have received a copy of the GNU General Public along with this program. If not, see <http://www.gnu.org/licenses/>. Contact info: lobochief@users.sourceforge.net; ivan.difrancesco@yahoo.it */ /* * 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.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.StringTokenizer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JSplitPane; import org.lobobrowser.html.BrowserFrame; import org.lobobrowser.html.HtmlAttributeProperties; import org.lobobrowser.html.HtmlRendererContext; import org.lobobrowser.html.dombl.FrameNode; import org.lobobrowser.html.domimpl.DOMNodeImpl; import org.lobobrowser.html.domimpl.HTMLElementImpl; 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 { /** The Constant serialVersionUID. */ private static final long serialVersionUID = 1L; /** The Constant logger. */ private static final Logger logger = LogManager.getLogger(FrameSetPanel.class.getName()); /** * Instantiates a new frame set panel. */ public FrameSetPanel() { super(); this.setLayout(WrapperLayout.getInstance()); // TODO: This should be a temporary preferred size this.setPreferredSize(new Dimension(600, 400)); } /** * Gets the lengths. * * @param spec * the spec * @return the lengths */ private HtmlLength[] getLengths(String spec) { if (spec == null) { return new HtmlLength[] { new HtmlLength("1*") }; } StringTokenizer tok = new StringTokenizer(spec, ","); ArrayList<HtmlLength> lengths = new ArrayList<HtmlLength>(); while (tok.hasMoreTokens()) { String token = tok.nextToken().trim(); try { lengths.add(new HtmlLength(token)); } catch (Exception err) { logger.warn("Frame rows or cols value [" + spec + "] is invalid."); } } return lengths.toArray(HtmlLength.EMPTY_ARRAY); } /** * Gets the sub frames. * * @param parent * the parent * @return the sub frames */ private HTMLElementImpl[] getSubFrames(HTMLElementImpl parent) { DOMNodeImpl[] children = parent.getChildrenArray(); ArrayList<DOMNodeImpl> subFrames = new ArrayList<DOMNodeImpl>(); for (int i = 0; i < children.length; i++) { DOMNodeImpl child = children[i]; if (child instanceof HTMLElementImpl) { String nodeName = child.getNodeName(); if ("FRAME".equalsIgnoreCase(nodeName) || "FRAMESET".equalsIgnoreCase(nodeName)) { subFrames.add(child); } } } return subFrames.toArray(new HTMLElementImpl[0]); } /** The root node. */ private HTMLElementImpl rootNode; /** * Sets the FRAMESET node and invalidates the component so it can be * rendered immediately in the GUI thread. * * @param node * the new root node */ @Override public void setRootNode(DOMNodeImpl node) { // Method expected to be called in the GUI thread. if (!(node instanceof HTMLElementImpl)) { throw new IllegalArgumentException("node=" + node); } HTMLElementImpl element = (HTMLElementImpl) node; this.rootNode = element; HtmlRendererContext context = element.getHtmlRendererContext(); this.htmlContext = context; this.domInvalid = true; this.invalidate(); this.validateAll(); this.repaint(); } /** * Validate all. */ protected void validateAll() { Component toValidate = this; for (;;) { Container parent = toValidate.getParent(); if ((parent == null) || parent.isValid()) { break; } toValidate = parent; } toValidate.validate(); } /** * Process document notifications. * * @param notifications * the notifications */ public final void processDocumentNotifications(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(); } } } /** The html context. */ private HtmlRendererContext htmlContext; /** The frame components. */ private Component[] frameComponents; /** The dom invalid. */ private boolean domInvalid = true; /* * (non-Javadoc) * * @see java.awt.Component#setBounds(int, int, int, int) */ @Override public void setBounds(int x, int y, int w, 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(); HtmlRendererContext context = this.htmlContext; if (context != null) { HTMLElementImpl element = this.rootNode; String rows = element.getAttribute(HtmlAttributeProperties.ROWS); String cols = element.getAttribute(HtmlAttributeProperties.COLS); HtmlLength[] rowLengths = this.getLengths(rows); HtmlLength[] colLengths = this.getLengths(cols); HTMLElementImpl[] subframes = this.getSubFrames(element); Component[] frameComponents = new Component[subframes.length]; this.frameComponents = frameComponents; for (int i = 0; i < subframes.length; i++) { HTMLElementImpl frameElement = subframes[i]; if ((frameElement != null) && "FRAMESET".equalsIgnoreCase(frameElement.getTagName())) { FrameSetPanel fsp = new FrameSetPanel(); fsp.setRootNode(frameElement); frameComponents[i] = fsp; } else { if (frameElement instanceof FrameNode) { BrowserFrame frame = context.createBrowserFrame(); ((FrameNode) frameElement).setBrowserFrame(frame); String src = frameElement.getAttribute(HtmlAttributeProperties.SRC); if (src != null) { URL url; try { url = frameElement.getFullURL(src); if (url != null) { frame.loadURL(url); } } catch (MalformedURLException mfu) { logger.warn("Frame URI=[" + src + "] is malformed."); } } frameComponents[i] = frame.getComponent(); } else { frameComponents[i] = new JPanel(); } } } HtmlLength[] rhl = rowLengths; HtmlLength[] chl = colLengths; Component[] fc = this.frameComponents; if ((rhl != null) && (chl != null) && (fc != null)) { Dimension size = this.getSize(); Insets insets = this.getInsets(); int width = size.width - insets.left - insets.right; int height = size.height - insets.left - insets.right; int[] absColLengths = this.getAbsoluteLengths(chl, width); int[] absRowLengths = this.getAbsoluteLengths(rhl, height); this.add(this.getSplitPane(this.htmlContext, absColLengths, 0, absColLengths.length, absRowLengths, 0, absRowLengths.length, fc)); } } } super.doLayout(); } /** * Gets the absolute lengths. * * @param htmlLengths * the html lengths * @param totalSize * the total size * @return the absolute lengths */ private int[] getAbsoluteLengths(HtmlLength[] htmlLengths, int totalSize) { int[] absLengths = new int[htmlLengths.length]; int totalSizeNonMulti = 0; int sumMulti = 0; for (int i = 0; i < htmlLengths.length; i++) { HtmlLength htmlLength = htmlLengths[i]; int lengthType = htmlLength.getLengthType(); if (lengthType == HtmlLength.PIXELS) { int absLength = htmlLength.getRawValue(); totalSizeNonMulti += absLength; absLengths[i] = absLength; } else if (lengthType == HtmlLength.LENGTH) { int absLength = htmlLength.getLength(totalSize); totalSizeNonMulti += absLength; absLengths[i] = absLength; } else { sumMulti += htmlLength.getRawValue(); } } int remaining = totalSize - totalSizeNonMulti; if ((remaining > 0) && (sumMulti > 0)) { for (int i = 0; i < htmlLengths.length; i++) { HtmlLength htmlLength = htmlLengths[i]; if (htmlLength.getLengthType() == HtmlLength.MULTI_LENGTH) { int absLength = (remaining * htmlLength.getRawValue()) / sumMulti; absLengths[i] = absLength; } } } return absLengths; } /** * Gets the split pane. * * @param context * the context * @param colLengths * the col lengths * @param firstCol * the first col * @param numCols * the num cols * @param rowLengths * the row lengths * @param firstRow * the first row * @param numRows * the num rows * @param frameComponents * the frame components * @return the split pane */ private Component getSplitPane(HtmlRendererContext context, int[] colLengths, int firstCol, int numCols, int[] rowLengths, int firstRow, int numRows, Component[] frameComponents) { if (numCols == 1) { int frameindex = (colLengths.length * firstRow) + firstCol; Component topComponent = frameindex < frameComponents.length ? frameComponents[frameindex] : null; if (numRows == 1) { return topComponent; } else { Component bottomComponent = this.getSplitPane(context, colLengths, firstCol, numCols, rowLengths, firstRow + 1, numRows - 1, frameComponents); JSplitPane sp = new JSplitPane(JSplitPane.VERTICAL_SPLIT, topComponent, bottomComponent); sp.setDividerLocation(rowLengths[firstRow]); return sp; } } else { Component rightComponent = this.getSplitPane(context, colLengths, firstCol + 1, numCols - 1, rowLengths, firstRow, numRows, frameComponents); Component leftComponent = this.getSplitPane(context, colLengths, firstCol, 1, rowLengths, firstRow, numRows, frameComponents); JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftComponent, rightComponent); sp.setDividerLocation(colLengths[firstCol]); return sp; } } }