/**
* ***************************************************************************
* Copyright (c) 2010 Qcadoo Limited
* Project: Qcadoo Framework
* Version: 1.4
*
* This file is part of Qcadoo.
*
* Qcadoo is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation; either version 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
* ***************************************************************************
*/
package com.qcadoo.view.internal.components.layout;
import java.util.Locale;
import java.util.Map;
import org.json.JSONException;
import org.json.JSONObject;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.google.common.base.Preconditions;
import com.qcadoo.view.internal.ComponentDefinition;
import com.qcadoo.view.internal.api.ComponentPattern;
import com.qcadoo.view.internal.xml.ViewDefinitionParser;
import com.qcadoo.view.internal.xml.ViewDefinitionParserNodeException;
public class GridLayoutPattern extends AbstractLayoutPattern {
private static final String JS_OBJECT = "QCD.components.containers.layout.GridLayout";
private static final String JSP_PATH = "containers/layout/gridLayout.jsp";
private static final String JS_PATH = "/qcadooView/public/js/crud/qcd/components/containers/layout/gridLayout.js";
private GridLayoutCell[][] cells;
private boolean fixedRowHeight = true;
public GridLayoutPattern(final ComponentDefinition componentDefinition) {
super(componentDefinition);
}
@Override
public void parse(final Node componentNode, final ViewDefinitionParser parser) throws ViewDefinitionParserNodeException {
super.parse(componentNode, parser);
Integer columns = getIntAttribute(componentNode, "columns", parser);
Integer rows = getIntAttribute(componentNode, "rows", parser);
fixedRowHeight = parser.getBooleanAttribute(componentNode, "fixedRowHeight", true);
parser.checkState(columns != null, componentNode, "columns not definied");
parser.checkState(rows != null, componentNode, "rows not definied");
// check again - because sonar is stupid
if (rows == null) {
throw new IllegalStateException("rows not definied");
}
cells = new GridLayoutCell[rows][];
for (int row = 0; row < cells.length; row++) {
cells[row] = new GridLayoutCell[columns];
for (int col = 0; col < cells[row].length; col++) {
cells[row][col] = new GridLayoutCell();
}
}
NodeList childNodes = componentNode.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node child = childNodes.item(i);
if (child.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
parser.checkState("layoutElement".equals(child.getNodeName()), child, "gridlayout can contains only layoutElements");
Integer column = getIntAttribute(child, "column", parser);
Integer row = getIntAttribute(child, "row", parser);
GridLayoutCell cell = createGridLayoutCell(child, parser);
try {
insertCell(cell, column, row);
} catch (IllegalStateException e) {
throw new ViewDefinitionParserNodeException(child, e);
}
}
if (parser.getBooleanAttribute(componentNode, "hasBorders", true)) {
updateBorders();
}
}
private void updateBorders() {
int colsNumber = cells[0].length;
boolean[] bordersArray = new boolean[colsNumber];
for (int i = 0; i < colsNumber; i++) {
bordersArray[i] = false;
}
for (int row = 0; row < cells.length; row++) {
for (int col = 0; col < cells[row].length; col++) {
if (cells[row][col].getComponents() != null) {
bordersArray[col + cells[row][col].getColspan() - 1] = true;
}
}
}
// remove last border
for (int i = colsNumber - 1; i >= 0; i--) {
if (bordersArray[i]) {
bordersArray[i] = false;
break;
}
}
for (int row = 0; row < cells.length; row++) {
for (int col = 0; col < cells[row].length; col++) {
if (bordersArray[col + cells[row][col].getColspan() - 1]) {
cells[row][col].setRightBorder(true);
}
}
}
}
private GridLayoutCell createGridLayoutCell(final Node child, final ViewDefinitionParser parser)
throws ViewDefinitionParserNodeException {
Integer colspan = getIntAttribute(child, "width", parser);
Integer rowspan = getIntAttribute(child, "height", parser);
ComponentPattern elementComponent = null;
NodeList elementComponentNodes = child.getChildNodes();
GridLayoutCell cell = new GridLayoutCell();
for (int elementComponentNodesIter = 0; elementComponentNodesIter < elementComponentNodes.getLength(); elementComponentNodesIter++) {
Node elementComponentNode = elementComponentNodes.item(elementComponentNodesIter);
if (elementComponentNode.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
parser.checkState("component".equals(elementComponentNode.getNodeName()), elementComponentNode,
"layoutElement can contains only components");
elementComponent = parser.parseComponent(elementComponentNode, this);
this.addChild(elementComponent);
cell.addComponent(elementComponent);
}
if (colspan != null) {
cell.setColspan(colspan);
}
if (rowspan != null) {
cell.setRowspan(rowspan);
}
return cell;
}
private void insertCell(final GridLayoutCell cell, final int column, final int row) {
Preconditions.checkState(column > 0, "column number less than zero");
Preconditions.checkState(row > 0, "row number less than zero");
Preconditions.checkState(column <= cells[0].length, "column number to large");
Preconditions.checkState(row <= cells.length, "row number to large");
Preconditions.checkState(column + cell.getColspan() - 1 <= cells[0].length, "width number to large");
Preconditions.checkState(row + cell.getRowspan() - 1 <= cells.length, "height number to large");
for (int rowIter = row; rowIter < row + cell.getRowspan(); rowIter++) {
for (int colIter = column; colIter < column + cell.getColspan(); colIter++) {
GridLayoutCell beforeCell = cells[rowIter - 1][colIter - 1];
Preconditions.checkState(beforeCell.isAvailable(), "cell [" + rowIter + "x" + colIter + "] is not available");
beforeCell.setAvailable(false);
}
}
cells[row - 1][column - 1] = cell;
}
private Integer getIntAttribute(final Node node, final String attribute, final ViewDefinitionParser parser)
throws ViewDefinitionParserNodeException {
String valueStr = parser.getStringAttribute(node, attribute);
if (valueStr == null) {
return null;
}
try {
return Integer.parseInt(valueStr);
} catch (NumberFormatException e) {
throw new ViewDefinitionParserNodeException(node, "value of attribute '" + attribute + "' is not a number");
}
}
@Override
public final Map<String, Object> prepareView(final Locale locale) {
Map<String, Object> model = super.prepareView(locale);
model.put("cells", cells);
return model;
}
@Override
protected JSONObject getJsOptions(final Locale locale) throws JSONException {
JSONObject json = new JSONObject();
json.put("colsNumber", cells[0].length);
json.put("fixedRowHeight", fixedRowHeight);
return json;
}
@Override
public String getJspFilePath() {
return JSP_PATH;
}
@Override
public String getJsFilePath() {
return JS_PATH;
}
@Override
public String getJsObjectName() {
return JS_OBJECT;
}
public GridLayoutCell[][] getCells(){
return cells;
}
}