/*******************************************************************************
* Copyright (c) 2007-2009 Exadel, Inc. and Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Exadel, Inc. and Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.jsf.vpe.richfaces.template;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import org.jboss.tools.jsf.vpe.richfaces.ComponentUtil;
import org.jboss.tools.vpe.editor.context.VpePageContext;
import org.jboss.tools.vpe.editor.template.VpeAbstractTemplate;
import org.jboss.tools.vpe.editor.template.VpeChildrenInfo;
import org.jboss.tools.vpe.editor.template.VpeCreationData;
import org.jboss.tools.vpe.editor.util.Constants;
import org.jboss.tools.vpe.editor.util.HTML;
import org.mozilla.interfaces.nsIDOMDocument;
import org.mozilla.interfaces.nsIDOMElement;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class RichFacesLayoutTemplate extends VpeAbstractTemplate {
private static final String FLOAT_LEFT_STYLE = ";float: left;"; //$NON-NLS-1$
private static final String FLOAT_RIGHT_STYLE = ";float: right;"; //$NON-NLS-1$
private static final String BOTTOM_SECONDARY_DIV_STYLE = ";display: block; height: 0; clear: both; visibility: hidden;"; //$NON-NLS-1$
private static final String BOTTOM_SECONDARY_DIV_TEXT = "."; //$NON-NLS-1$
private static final String LAYOUT_PANEL_NAME = ":layoutPanel"; //$NON-NLS-1$
/**
* Constructor
*/
public RichFacesLayoutTemplate() {
super();
}
public VpeCreationData create(VpePageContext pageContext, Node sourceNode,
nsIDOMDocument visualDocument) {
VpeCreationData creationData = null;
Element sourceElement = (Element)sourceNode;
nsIDOMElement mainDiv = visualDocument.createElement(HTML.TAG_DIV);
String style = sourceElement.getAttribute(HTML.ATTR_STYLE);
if (ComponentUtil.isNotBlank(style)) {
mainDiv.setAttribute(HTML.ATTR_STYLE, style);
}
nsIDOMElement topDiv = visualDocument.createElement(HTML.TAG_DIV);
nsIDOMElement centerDiv = visualDocument.createElement(HTML.TAG_DIV);
nsIDOMElement leftDiv = visualDocument.createElement(HTML.TAG_DIV);
nsIDOMElement rightDiv = visualDocument.createElement(HTML.TAG_DIV);
nsIDOMElement bottomDiv = visualDocument.createElement(HTML.TAG_DIV);
nsIDOMElement bottomSecondaryDiv = visualDocument.createElement(HTML.TAG_DIV);
creationData = new VpeCreationData(mainDiv);
bottomSecondaryDiv.setAttribute(HTML.ATTR_STYLE,
BOTTOM_SECONDARY_DIV_STYLE);
bottomSecondaryDiv.appendChild(visualDocument
.createTextNode(BOTTOM_SECONDARY_DIV_TEXT));
bottomDiv.appendChild(bottomSecondaryDiv);
NodeList children = sourceNode.getChildNodes();
Map<String, Element> panels = new HashMap<String, Element>();
/*
* Array of columns weights
* 0 - for left panel
* 1 - for center panel
* 2 - for right panel
*/
String[] widthStrings = {Constants.EMPTY, Constants.EMPTY, Constants.EMPTY};
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node instanceof Element && node.getNodeName() != null
&& node.getNodeName().indexOf(LAYOUT_PANEL_NAME) > 0) {
Element element = (Element) node;
String position = element.getAttribute(RichFaces.ATTR_POSITION);
/*
* Adding several panel with the same position is forbidden.
* During adding to the map only the last panel
* with repeating position name will be displayed.
* Counting columns quantity and reading 'width attribute'
* at the same time.
*/
if (ComponentUtil.isNotBlank(position)) {
if (RichFaces.VALUE_TOP.equalsIgnoreCase(position)) {
panels.put(RichFaces.VALUE_TOP, element);
} else if (RichFaces.VALUE_LEFT.equalsIgnoreCase(position)) {
panels.put(RichFaces.VALUE_LEFT, element);
widthStrings[0] = element.getAttribute(HTML.ATTR_WIDTH);
} else if (RichFaces.VALUE_CENTER.equalsIgnoreCase(position)) {
panels.put(RichFaces.VALUE_CENTER, element);
widthStrings[1] = element.getAttribute(HTML.ATTR_WIDTH);
} else if (RichFaces.VALUE_RIGHT.equalsIgnoreCase(position)) {
panels.put(RichFaces.VALUE_RIGHT, element);
widthStrings[2] = element.getAttribute(HTML.ATTR_WIDTH);
} else if (RichFaces.VALUE_BOTTOM.equalsIgnoreCase(position)) {
panels.put(RichFaces.VALUE_BOTTOM, element);
}
}
}
}
/*
* Columns weights processing:
* 1) If column has no weight specified
* a) it should have a percent weight - when other weights are less than 100% summary,
* if there are some columns without weight - they should share
* total free weight between each other equally.
* b) if other columns has weights specified in percents
* and in summary it's more than 100%
* than no weight should be set.
* 2) If there are some columns has weight in '%' and their weights'
* sum is more or less 100% then new weight should be set in range of 100%
* in proportion to specified weights.
* 3) If column has weight set in '%' and the value is greater than 100%
* the value is added to style without changes.
* 4) Weights in 'px' and 'em' are set in style without changes.
*/
/*
* Array of columns weights
* 0 - for left panel
* 1 - for center panel
* 2 - for right panel
*/
double[] widths = {-1, -1, -1};
for (int i = 0; i < widthStrings.length; i++) {
widths[i] = parseWidthFromPercents(widthStrings[i]);
}
/*
* A) Find any >100% weight.
* Leave it as is.
*/
boolean widthOverflow = false;
for (double w : widths) {
if (w > 100) {
widthOverflow = true;
}
}
if (!widthOverflow){
/*
* B) When weights are less than 100
* Count total weight in '%' (<100 || >100)
*/
double totalWidth = 0;
for (double w : widths) {
if (w > 0) {
totalWidth += w;
}
}
/*
* Count columns with no width specified.
*/
int noWeightColumns = 0;
for (String ws : widthStrings) {
if ((null == ws)
|| Constants.EMPTY.equalsIgnoreCase(ws)) {
noWeightColumns++;
}
}
/*
* Free width to add to total width to 100.
*/
double totalFreeWidth = 100 - totalWidth;
/*
* Total available width should always be less or equal 100.
*/
double availableWidth = 100;
if ((totalWidth < 100) && (noWeightColumns > 0)){
/*
* Set specified width, free space will be filled
* with columns without width attribute.
*/
availableWidth = totalWidth;
}
/*
* C) Adjust existed weights in '%'
*/
double[] coeffs = {-1, -1, -1};
for (int i = 0; i < widths.length; i++) {
if (widths[i] > 0) {
coeffs[i] = widths[i] / totalWidth;
BigDecimal b = new BigDecimal(availableWidth*coeffs[i]).setScale(2,
BigDecimal.ROUND_HALF_UP);
widthStrings[i] = b.doubleValue() + Constants.PERCENT;
}
}
/*
* D) Adjust empty weight
* When there is some free space to adjust -
* divide it equally between width free columns.
*/
if ((totalFreeWidth > 0) && (noWeightColumns > 0)) {
BigDecimal b = new BigDecimal(totalFreeWidth/noWeightColumns).setScale(2,
BigDecimal.ROUND_HALF_UP);
for (int i = 0; i < widthStrings.length; i++) {
if ((null == widthStrings[i])
|| Constants.EMPTY.equalsIgnoreCase(widthStrings[i])) {
widthStrings[i] = b.doubleValue() + Constants.PERCENT;
}
}
}
}
/*
* E) Leave 'px' and 'em' without changes
*/
/*
* Adding panels' divs.
* Order is important!
*/
addPanelFromMap(RichFaces.VALUE_TOP, panels, mainDiv, topDiv,
Constants.EMPTY, null, creationData);
addPanelFromMap(RichFaces.VALUE_LEFT, panels, mainDiv, leftDiv,
FLOAT_LEFT_STYLE, widthStrings[0], creationData);
addPanelFromMap(RichFaces.VALUE_CENTER, panels, mainDiv, centerDiv,
FLOAT_LEFT_STYLE, widthStrings[1], creationData);
addPanelFromMap(RichFaces.VALUE_RIGHT, panels, mainDiv, rightDiv,
FLOAT_RIGHT_STYLE, widthStrings[2], creationData);
addPanelFromMap(RichFaces.VALUE_BOTTOM, panels, mainDiv, bottomDiv,
Constants.EMPTY, null, creationData);
return creationData;
}
/**
* Parse width string from percents form to a number.
* @param widthStr panel's width string
* @return panel's width number or -1 when parsing failed
*/
private double parseWidthFromPercents(String widthStr) {
double result = -1;
if ((null != widthStr) && widthStr.endsWith(Constants.PERCENT)) {
try {
result = Double.parseDouble(widthStr.substring(0, widthStr.length()-1));
} catch (NumberFormatException e) {
/*
* Cannot parse - skip.
*/
}
}
return result;
}
/**
* Adds rich:layoutPanel to current rich:layout.
* Styles and width will be set.
* Panel will be added to children info.
*
* @param panelPositionMapName panel name, also used to get the panel from the map
* @param panelsMap map with panels elements from source
* @param mainDiv rich:layout's div
* @param panelDiv div to render layoutPanel
* @param style panel's css style
* @param panelWidth panel's width
* @param creationData VpeCreationData
*/
private void addPanelFromMap(String panelPositionMapName, Map<String, Element> panelsMap,
nsIDOMElement mainDiv, nsIDOMElement panelDiv, String style, String panelWidth, VpeCreationData creationData) {
Element panel = panelsMap.get(panelPositionMapName);
if (null != panel) {
String widthStr = panel.getAttribute(HTML.ATTR_WIDTH);
/*
* Apply column width for left, center and right panels only.
*/
if (!RichFaces.VALUE_TOP.equalsIgnoreCase(panelPositionMapName)
&& !RichFaces.VALUE_BOTTOM.equalsIgnoreCase(panelPositionMapName)
&& (null != panelWidth)) {
style += "; width: " + panelWidth + ";"; //$NON-NLS-1$ //$NON-NLS-2$
} else {
}
/*
* Set the original width to 'width' attribute
* as richfaces do.
*/
if (ComponentUtil.isNotBlank(widthStr)) {
panelDiv.setAttribute(HTML.ATTR_WIDTH, widthStr);
}
panelDiv.setAttribute(HTML.ATTR_STYLE, style);
mainDiv.appendChild(panelDiv);
VpeChildrenInfo panelInfo = new VpeChildrenInfo(panelDiv);
panelInfo.addSourceChild(panel);
creationData.addChildrenInfo(panelInfo);
}
}
}