From 3270db95d039e1c2bc55962df172779afd644ba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Fri, 24 Oct 2014 17:45:33 +0200 Subject: [PATCH] Table layout library --- libsrc/tablelayout/build.xml | 73 + libsrc/tablelayout/nbproject/build-impl.xml | 1413 ++++++++++++++ .../tablelayout/nbproject/genfiles.properties | 8 + .../tablelayout/nbproject/project.properties | 73 + libsrc/tablelayout/nbproject/project.xml | 15 + .../src/org/xito/dialog/LayoutParser.java | 464 +++++ .../src/org/xito/dialog/TableLayout.java | 1677 +++++++++++++++++ 7 files changed, 3723 insertions(+) create mode 100644 libsrc/tablelayout/build.xml create mode 100644 libsrc/tablelayout/nbproject/build-impl.xml create mode 100644 libsrc/tablelayout/nbproject/genfiles.properties create mode 100644 libsrc/tablelayout/nbproject/project.properties create mode 100644 libsrc/tablelayout/nbproject/project.xml create mode 100644 libsrc/tablelayout/src/org/xito/dialog/LayoutParser.java create mode 100644 libsrc/tablelayout/src/org/xito/dialog/TableLayout.java diff --git a/libsrc/tablelayout/build.xml b/libsrc/tablelayout/build.xml new file mode 100644 index 000000000..ae1a49d7b --- /dev/null +++ b/libsrc/tablelayout/build.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + Builds, tests, and runs the project tablelayout. + + + diff --git a/libsrc/tablelayout/nbproject/build-impl.xml b/libsrc/tablelayout/nbproject/build-impl.xml new file mode 100644 index 000000000..bc8c6ee04 --- /dev/null +++ b/libsrc/tablelayout/nbproject/build-impl.xml @@ -0,0 +1,1413 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.dir + Must set test.src.dir + Must set build.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No tests executed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set JVM to use for profiling in profiler.info.jvm + Must set profiler agent JVM arguments in profiler.info.jvmargs.agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + Must select one file in the IDE or set profile.class + This target only works when run from inside the NetBeans IDE. + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + + + Must select some files in the IDE or set test.includes + + + + + Must select one file in the IDE or set run.class + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + Must select some files in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + Must select one file in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + + + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libsrc/tablelayout/nbproject/genfiles.properties b/libsrc/tablelayout/nbproject/genfiles.properties new file mode 100644 index 000000000..72e23fc4c --- /dev/null +++ b/libsrc/tablelayout/nbproject/genfiles.properties @@ -0,0 +1,8 @@ +build.xml.data.CRC32=e5124a5d +build.xml.script.CRC32=4efcaf5c +build.xml.stylesheet.CRC32=8064a381@1.74.2.48 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=e5124a5d +nbproject/build-impl.xml.script.CRC32=1a6f89d0 +nbproject/build-impl.xml.stylesheet.CRC32=876e7a8f@1.74.2.48 diff --git a/libsrc/tablelayout/nbproject/project.properties b/libsrc/tablelayout/nbproject/project.properties new file mode 100644 index 000000000..4a19a9a31 --- /dev/null +++ b/libsrc/tablelayout/nbproject/project.properties @@ -0,0 +1,73 @@ +annotation.processing.enabled=true +annotation.processing.enabled.in.editor=false +annotation.processing.processors.list= +annotation.processing.run.all.processors=true +annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +application.title=tablelayout +application.vendor=Jindra +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +# Uncomment to specify the preferred debugger connection transport: +#debug.transport=dt_socket +debug.classpath=\ + ${run.classpath} +debug.test.classpath=\ + ${run.test.classpath} +# Files in build.classes.dir which should be excluded from distribution jar +dist.archive.excludes= +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=../../lib/tablelayout.jar +dist.javadoc.dir=${dist.dir}/javadoc +endorsed.classpath= +excludes= +includes=** +jar.compress=false +javac.classpath= +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.processorpath=\ + ${javac.classpath} +javac.source=1.7 +javac.target=1.7 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +javac.test.processorpath=\ + ${javac.test.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +meta.inf.dir=${src.dir}/META-INF +mkdist.disabled=true +platform.active=default_platform +run.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +# Space-separated list of JVM arguments used when running the project. +# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value. +# To set system properties for unit tests define test-sys-prop.name=value: +run.jvmargs= +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +source.encoding=UTF-8 +src.dir=src +test.src.dir=test diff --git a/libsrc/tablelayout/nbproject/project.xml b/libsrc/tablelayout/nbproject/project.xml new file mode 100644 index 000000000..4aca3d912 --- /dev/null +++ b/libsrc/tablelayout/nbproject/project.xml @@ -0,0 +1,15 @@ + + + org.netbeans.modules.java.j2seproject + + + tablelayout + + + + + + + + + diff --git a/libsrc/tablelayout/src/org/xito/dialog/LayoutParser.java b/libsrc/tablelayout/src/org/xito/dialog/LayoutParser.java new file mode 100644 index 000000000..1c93a5cf5 --- /dev/null +++ b/libsrc/tablelayout/src/org/xito/dialog/LayoutParser.java @@ -0,0 +1,464 @@ +// Copyright 2007 Xito.org +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package org.xito.dialog; + +import java.awt.*; +import java.io.*; +import java.net.*; +import java.text.*; +import java.util.Locale; + +import javax.xml.parsers.*; +import org.w3c.dom.*; +import org.xml.sax.SAXException; + +/** + * The Layout Parser will parse well-formed HTML for the first Table declaration and generate a TableLayout + * based on the HTML described Table + * + * @author Deane Richan + */ +public class LayoutParser { + + private static final String TABLE_TAG = "table"; + private static final String TR_TAG = "tr"; + private static final String TD_TAG = "td"; + private static final String WIDTH_ATTR = "width"; + private static final String MIN_WIDTH_ATTR = "min-width"; + private static final String MAX_WIDTH_ATTR = "max-width"; + private static final String HEIGHT_ATTR = "height"; + private static final String MIN_HEIGHT_ATTR = "min-height"; + private static final String MAX_HEIGHT_ATTR = "max-height"; + private static final String ANCHOR_ATTR = "anchor"; + private static final String CELL_SPACING_ATTR = "cellspacing"; + private static final String CELL_PADDING_ATTR = "cellpadding"; + private static final String ALIGN_ATTR = "align"; + private static final String VALIGN_ATTR = "valign"; + private static final String COLSPAN_ATTR = "colspan"; + private static final String ROWSPAN_ATTR = "rowspan"; + private static final String PADDING_ATTR = "padding"; + private static final String ID_ATTR = "id"; + private static final String PREFERRED = "preferred"; + private static final String LEFT = "left"; + private static final String RIGHT = "right"; + private static final String TOP = "top"; + private static final String BOTTOM = "bottom"; + private static final String CENTER = "center"; + private static final String MIDDLE = "middle"; + private static final String FULL = "full"; + private static final String NW = "nw"; + private static final String N = "n"; + private static final String NE = "ne"; + private static final String E = "e"; + private static final String SE = "se"; + private static final String S = "s"; + private static final String SW = "sw"; + private static final String W = "w"; + + //percent values need to be in English style + private static final DecimalFormat percentFormat = new DecimalFormat("###.##%", new DecimalFormatSymbols(Locale.ENGLISH)); + + + /** Creates a new instance of LayoutParser */ + public LayoutParser() { + } + + public TableLayout parse(String htmlTable) throws IOException { + + return parse(new StringBufferInputStream(htmlTable)); + } + + public TableLayout parse(URL url) throws IOException { + + try { + DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = fact.newDocumentBuilder(); + return parse(docBuilder.parse(url.openStream())); + } catch (ParserConfigurationException configExp) { + configExp.printStackTrace(); + throw new IOException(configExp.getMessage()); + } catch (SAXException saxExp) { + saxExp.printStackTrace(); + throw new IOException(saxExp.getMessage()); + } + } + + public TableLayout parse(InputStream in) throws IOException { + + try { + DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = fact.newDocumentBuilder(); + return parse(docBuilder.parse(in)); + } catch (ParserConfigurationException configExp) { + configExp.printStackTrace(); + throw new IOException(configExp.getMessage()); + } catch (SAXException saxExp) { + saxExp.printStackTrace(); + throw new IOException(saxExp.getMessage()); + } + } + + /** + * Parse a Document + * @param doc + * @return + * @throws java.io.IOException + */ + public TableLayout parse(Document doc) throws IOException { + + return parse(doc.getDocumentElement()); + } + + /** + * Parse an Element + * @param element + * @return + * @throws java.io.IOException + */ + public TableLayout parse(Element element) throws IOException { + + if (element.getNodeName().equalsIgnoreCase(TABLE_TAG)) { + return processTableElement(element); + } + else { + NodeList children = element.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + if (children.item(i).getNodeType() == Node.ELEMENT_NODE) { + TableLayout layout = parse((Element) children.item(i)); + if (layout != null) { + return layout; + } + } + } + } + + return null; + } + + /** + * Process the Table Element + * @param element + * @return + * @throws java.io.IOException + */ + private TableLayout processTableElement(Element element) throws IOException { + + TableLayout layout = new TableLayout(); + + //Get Width and Height of Table + float width = getFloat(element.getAttribute(WIDTH_ATTR)); + float height = getFloat(element.getAttribute(HEIGHT_ATTR)); + + //default to 100% relative + if (width == 0) { + width = 1.0f; + } + if (height == 0) { + height = 1.0f; + } + layout.setWidth(width); + layout.setHeight(height); + + //get min, max width and height of Table + int min_width = getInteger(element.getAttribute(MIN_WIDTH_ATTR), 0); + int max_width = getInteger(element.getAttribute(MAX_WIDTH_ATTR), Integer.MAX_VALUE); + int min_height = getInteger(element.getAttribute(MIN_HEIGHT_ATTR), 0); + int max_height = getInteger(element.getAttribute(MAX_HEIGHT_ATTR), Integer.MAX_VALUE); + layout.setMinWidth(min_width); + layout.setMaxWidth(max_width); + layout.setMinHeight(min_height); + layout.setMaxHeight(max_height); + + //Anchor + layout.setAnchor(processAnchor(element.getAttribute(ANCHOR_ATTR))); + + //Cell Spacing and Padding + int cs = getInteger(element.getAttribute(CELL_SPACING_ATTR), 0); + int cp = getInteger(element.getAttribute(CELL_PADDING_ATTR), 0); + int padding = cs + cp; + layout.setPadding(new Insets(padding, padding, padding, padding)); + + //Process Rows + NodeList possibleRows = element.getChildNodes(); + for (int r = 0; r < possibleRows.getLength(); r++) { + Node n = possibleRows.item(r); + if (n.getNodeName().equalsIgnoreCase(TR_TAG) && n.getNodeType() == Node.ELEMENT_NODE) { + Element rowElement = (Element) n; + String hStr = rowElement.getAttribute(HEIGHT_ATTR); + TableLayout.Row row = new TableLayout.Row(getFloatDimensionValue(hStr)); + processRow(row, rowElement); + layout.addRow(row); + } + } + + //Add additional RowSpan columns. + processRowSpan(layout); + + return layout; + } + + /** + * When HTML uses RowSpan it automatically inserts extra columns where the + * row is spanning over so we need to insert these extra empty columns into the layout + * @param layout + */ + private void processRowSpan(TableLayout layout) { + + + for (int r = 0; r < layout.getRowCount(); r++) { + TableLayout.Row row = layout.getRow(r); + + //check for any row spans in the columns + for (int c = 0; c < row.getColumnCount(); c++) { + TableLayout.Column col = row.getColumn(c); + + if (col.rowSpan > 1) { + int span = col.rowSpan - 1; + //loop through this many rows below and insert + //extra columns + for (int s = 1; s <= span; s++) { + TableLayout.Row spanRow = layout.getRow(r + s); + if (spanRow == null) { + continue; + } + spanRow.insertEmptyColumn(c); + } + } + } + + } + + } + + /** + * Process a Row + * @param row + * @param rowElement + */ + private void processRow(TableLayout.Row row, Element rowElement) { + NodeList possibleColumns = rowElement.getChildNodes(); + for (int c = 0; c < possibleColumns.getLength(); c++) { + Node n = possibleColumns.item(c); + if (n.getNodeName().equalsIgnoreCase(TD_TAG) && n.getNodeType() == Node.ELEMENT_NODE) { + Element td = (Element) n; + row.addCol(processCol(td)); + } + } + } + + /** + * Process a Column + * @param colElement + * @return + */ + private TableLayout.Column processCol(Element colElement) { + + TableLayout.Column col = new TableLayout.Column(); + String width = colElement.getAttribute(WIDTH_ATTR); + String hAlign = colElement.getAttribute(ALIGN_ATTR); + String vAlign = colElement.getAttribute(VALIGN_ATTR); + String colSpan = colElement.getAttribute(COLSPAN_ATTR); + String rowSpan = colElement.getAttribute(ROWSPAN_ATTR); + + col.width = getFloatDimensionValue(width); + + //process col and row spans + try { + if (colSpan != null && !colSpan.equals("")) { + col.colSpan = Integer.parseInt(colSpan); + } + } catch (NumberFormatException badNum) { + System.err.println("Error reading colspan:" + colSpan); + } + + try { + if (rowSpan != null && !rowSpan.equals("")) { + col.rowSpan = Integer.parseInt(rowSpan); + } + } catch (NumberFormatException badNum) { + System.err.println("Error reading rowspan:" + rowSpan); + } + + //Horz Align + if (hAlign != null && hAlign.equalsIgnoreCase(LEFT)) { + col.hAlign = TableLayout.LEFT; + } + else if (hAlign != null && hAlign.equalsIgnoreCase(RIGHT)) { + col.hAlign = TableLayout.RIGHT; + } + else if (hAlign != null && (hAlign.equalsIgnoreCase(MIDDLE) || hAlign.equalsIgnoreCase(CENTER))) { + col.hAlign = TableLayout.CENTER; + } + else if (hAlign != null && hAlign.equalsIgnoreCase(FULL)) { + col.hAlign = TableLayout.FULL; + } + + //Vert Align + if (vAlign.equals(TOP)) { + col.vAlign = TableLayout.TOP; + } + else if (vAlign.equals(BOTTOM)) { + col.vAlign = TableLayout.BOTTOM; + } + else if (vAlign.equals(CENTER) || vAlign.equals(MIDDLE)) { + col.vAlign = TableLayout.CENTER; + } + else if (vAlign.equals(FULL)) { + col.vAlign = TableLayout.FULL; + } + + //Process padding + col.padding = processColPadding(colElement.getAttribute(PADDING_ATTR)); + + //First look for name in ID + col.name = colElement.getAttribute(ID_ATTR); + + //Look for name in Text Node if it wasn't in ID + if (col.name == null || col.name.length() == 0) { + try { + NodeList childNodes = colElement.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + if (childNodes.item(i).getNodeType() == Node.TEXT_NODE) { + col.name = childNodes.item(i).getNodeValue(); + break; + } + } + } catch (DOMException badDOM) { + System.err.println("Error reading col name"); + badDOM.printStackTrace(); + } + } + + return col; + } + + public Insets processColPadding(String s) { + if (s == null || s.equals("")) { + return null; + } + Insets insets = new Insets(0, 0, 0, 0); + String values[] = s.split(","); + for (int i = 0; i < values.length; i++) { + if (i == 0) { + insets.top = getInteger(values[i], 0); + } + if (i == 1) { + insets.left = getInteger(values[i], 0); + } + if (i == 2) { + insets.bottom = getInteger(values[i], 0); + } + if (i == 3) { + insets.right = getInteger(values[i], 0); + } + } + + return insets; + } + + /** + * Returns the integer value or 0 + */ + private int getInteger(String s, int defaultValue) { + if (s == null || s.equals("")) { + return defaultValue; + } + try { + return Integer.parseInt(s); + } catch (NumberFormatException badNum) { + return defaultValue; + } + } + + /** + * Returns the integer value or 0 + */ + private float getFloat(String s) { + try { + if (s.endsWith("%")) { + return percentFormat.parse(s).floatValue(); + } + else { + return Float.parseFloat(s); + } + } catch (ParseException parseExp) { + return 0; + } catch (NumberFormatException badNum) { + return 0; + } + } + + /** + * Returns the Anchor int value for the specified String + * defaults to NORTH_WEST + */ + public int processAnchor(String s) { + + if (s == null || s.equals("")) { + return TableLayout.NORTH_WEST; + } + if (s.equalsIgnoreCase(NW)) { + return TableLayout.NORTH_WEST; + } + else if (s.equalsIgnoreCase(N)) { + return TableLayout.NORTH; + } + else if (s.equalsIgnoreCase(NE)) { + return TableLayout.NORTH_EAST; + } + else if (s.equalsIgnoreCase(E)) { + return TableLayout.EAST; + } + else if (s.equalsIgnoreCase(SE)) { + return TableLayout.SOUTH_EAST; + } + else if (s.equalsIgnoreCase(S)) { + return TableLayout.SOUTH; + } + else if (s.equalsIgnoreCase(SW)) { + return TableLayout.SOUTH_WEST; + } + else if (s.equalsIgnoreCase(W)) { + return TableLayout.WEST; + } + else if (s.equalsIgnoreCase(CENTER)) { + return TableLayout.CENTER; + } + + return TableLayout.NORTH_WEST; + } + + private float getFloatDimensionValue(String s) { + if (s == null || s.equals("") || s.equalsIgnoreCase(PREFERRED)) { + return TableLayout.PREFERRED; + } + if (s.equals("100%")) { + s = "99.9999%"; + } + try { + if (s.endsWith("%")) { + return percentFormat.parse(s).floatValue(); + } + else { + return Float.parseFloat(s); + } + } catch (ParseException parseExp) { + System.err.println("Error parsing Dimension value:" + s); + return TableLayout.PREFERRED; + } catch (NumberFormatException badNum) { + System.err.println("Error parsing Dimension value:" + s); + return TableLayout.PREFERRED; + } + } +} diff --git a/libsrc/tablelayout/src/org/xito/dialog/TableLayout.java b/libsrc/tablelayout/src/org/xito/dialog/TableLayout.java new file mode 100644 index 000000000..b57458984 --- /dev/null +++ b/libsrc/tablelayout/src/org/xito/dialog/TableLayout.java @@ -0,0 +1,1677 @@ +// Copyright 2007 Xito.org +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.xito.dialog; + +import java.awt.*; +import java.net.*; +import java.util.*; +import java.io.*; +import javax.swing.*; + +/** + *

+ * The TableLayout provides an easy to use layout manager based on the HTML Table. Layouts can be + * defined programmatically or by suppling a description in a well formed HTML file. This enables Layouts to be + * defined in seperate layout html resource files that can be modified seperately from the codebase. + *

+ *

+ * Layouts that are defined in seperate HTML files should contain just the tags. These files can + * be used to preview the layout behavior in your browser. For the most case, table layout behavior, as displayed + * in the browser will be duplicate with this layout in your component container. + *

+ *

+ * The table layout html should be well formed. The Tidy package is used to tidy any invalidate HTML, and therefore results may very. + * It is always best just to use well formed HTML. + *

+ *

Table Size

+ *

+ * Just as in html if no size of the table is specified as in <table>...</table> the + * contents of the container will layout using their preferred sizes. + * Generally this will mean components will pack into the North-West corner of the container. + *

+ *

+ * If you would like your components to take up the full size of the container use a layout that specifies a percentage + * size such as + * <table min_width="100%" max_height="100%">...</table> + * Of course other percentages will be applied accordingly. + *

+ *

+ * If you would like your components to take up a specific min_width and max_height you can specify an absolute size using:
+ * <table min_width="100" max_height="100">...</table> + *

+ *

Minimum and Maximum Table Sizes

+ *

+ * Although not part of the HTML spec, table layouts can define min-min_width, min-max_height, max-min_width, and max-max_height + * attributes. These should not be percent values but instead maximum or minimum size values in pixels. + *

+ *

Anchor

+ *

+ * Just as HTML table in a web page, TableLayout is anchored to the North-West corner of the parent container. + * However an optional anchor attribute can be added to the table element. This attribute will be ignored if min_width + * and max_height are set to 100%. The supported values are:
+ * nw, n, ne, v, e, sw, s, se + *

+ *

Rows

+ *

+ * Rows are defined using the html <tr> element. The max_height attribute has the following behavior: + * (undefined) The table row max_height will take the largest preferred max_height of a component in that row.
+ * max_height="50%" the table row max_height will be 50% of the table's max_height.
+ * max_height="100" the table row will be set to 100 pixels high.
+ *

+ *

Columns

+ *

+ * Columns are defined using the html <td> element. The following attributes are supported:
+ * id Used to specify an id of this table cell. This can be used in the program to place a component at this id.
+ * min_width Can be undefined, which uses the embedded components preferred min_width, a percentage, or fixed min_width in pixels.
+ * padding Used to specify padding space around the component. Best used with an undefined min_width.
+ * align Used to specify the horizontal alignment of the component in the cell. values are left, center, right, and full.
+ * valign Used to specify the vertical alignment of the component in the cell. values are top, bottom, and full.
+ * colspan Used to specify that this cell's component should span this cell and adjacent cells.
+ * rowspan Used to specify that this cell's component should span this row and adjacent rows.
+ * Note: colspan, and rowspan don't always have the same behavior as these attributes in a browser. These attributes should be used + * carefully. + *

+ *

+ * Once the Table layout has been processed and added to a container you can use the following to added components to the + * container: + *

+ * JPanel panel = new JPanel();
+ * panel.setLayout(new TableLayout(html or url));
+ * panel.add("id_1", new JButton()); //where id_1 is the id for the table cell you want to place the component in. The border attribute
+ * is ignored by the TableLayout.
+ * 
+ *

+ *

+ * Tip: In order to help test layouts in a browser set the table border="1". + *

+ * @author Deane Richan + */ +public class TableLayout implements LayoutManager2 { + + public final static int PREFERRED = -1; + public final static int LEFT = SwingConstants.LEFT; + public final static int RIGHT = SwingConstants.RIGHT; + public final static int CENTER = SwingConstants.CENTER; + public final static int TOP = SwingConstants.TOP; + public final static int BOTTOM = SwingConstants.BOTTOM; + public final static int FULL = 999; + public final static int NORTH_WEST = SwingConstants.NORTH_WEST; + public final static int NORTH = SwingConstants.NORTH; + public final static int NORTH_EAST = SwingConstants.NORTH_EAST; + public final static int EAST = SwingConstants.EAST; + public final static int SOUTH_EAST = SwingConstants.SOUTH_EAST; + public final static int SOUTH = SwingConstants.SOUTH; + public final static int SOUTH_WEST = SwingConstants.SOUTH_WEST; + public final static int WEST = SwingConstants.WEST; + public final static float PERCENT_100 = 0.9999f; + private ArrayList rows = new ArrayList(); + private float rowH[]; + private int rowY[]; + private int colCount = 0; + private float colW[]; + private int colX[]; + private int preferredWidth; + private int preferredHeight; + private int maxCalculatedWidth, maxCalculatedHeight; + private float width = 1.0f; + private float height = 1.0f; + private int anchor = NORTH_WEST; + private Insets padding = null; + private Dimension lastTargetDim; + private int minWidth = 0; + private int maxWidth = Integer.MAX_VALUE; + private int minHeight = 0; + private int maxHeight = Integer.MAX_VALUE; + private URL htmlResourceURL; + + public static final String BORDER_LAYOUT = "border_layout"; + public static final String TITLE_LAYOUT = "title_layout"; + + /** + * Create a layout for the given resource name. Layouts include: + * border_layout + * @param name + * @return + */ + public static TableLayout createLayout(String name) { + return new TableLayout(TableLayout.class.getResource("layouts/" + name + ".html")); + } + + /** + * Creates a new instance of TableLayout + * With min_width of 100%, max_height of 100% and anchor of NORTH_WEST + */ + public TableLayout() { + this(PERCENT_100, PERCENT_100, NORTH_WEST); + } + + /** + * Creates a new instance of TableLayout + * @param width either fixed or percentage of container's min_width + * @param height either fixed or percentage of container's max_height + * @param anchor either NORTH_WEST, NORTH, NORTH_EAST, EAST, SOUTH_EAST, SOUTH, SOUTH_WEST, WEST, or CENTER + */ + public TableLayout(float width, float height, int anchor) { + this.width = width; + this.height = height; + this.anchor = anchor; + } + + /** Creates a new instance of TableLayout */ + public TableLayout(String html) { + try { + LayoutParser parser = new LayoutParser(); + TableLayout layout = parser.parse(html); + copy(layout); + } catch (IOException ioExp) { + ioExp.printStackTrace(); + } + } + + /** Creates a new instance of TableLayout */ + public TableLayout(URL htmlURL) { + + htmlResourceURL = htmlURL; + + try { + LayoutParser parser = new LayoutParser(); + TableLayout layout = parser.parse(htmlURL); + copy(layout); + } catch (IOException ioExp) { + ioExp.printStackTrace(); + } + } + + /** Creates a new instance of TableLayout */ + public TableLayout(ArrayList rows) { + this.rows = rows; + if (this.rows == null) { + rows = new ArrayList(); + } + } + + /** + * Copy settings from a layout to this layout + */ + private void copy(TableLayout layout) { + + this.padding = layout.getPadding(); + this.width = layout.getWidth(); + this.minWidth = layout.getMinWidth(); + this.maxWidth = layout.getMaxWidth(); + this.height = layout.getHeight(); + this.minHeight = layout.getMinHeight(); + this.maxHeight = layout.getMaxHeight(); + + this.anchor = layout.anchor; + + //return early if there are no rows in the layout + if (layout.rows == null) { + return; //copy rows + } + for (int i = 0; i < layout.rows.size(); i++) { + Row r = (Row) ((Row) layout.rows.get(i)).clone(); + rows.add(r); + } + } + + public void setWidth(float w) { + width = w; + } + + public float getWidth() { + return width; + } + + public void setHeight(float h) { + height = h; + } + + public float getHeight() { + return height; + } + + public int getMaxHeight() { + return maxHeight; + } + + public void setMaxHeight(int maxHeight) { + this.maxHeight = maxHeight; + } + + public int getMaxWidth() { + return maxWidth; + } + + public void setMaxWidth(int maxWidth) { + this.maxWidth = maxWidth; + } + + public int getMinHeight() { + return minHeight; + } + + public void setMinHeight(int minHeight) { + this.minHeight = minHeight; + } + + public int getMinWidth() { + return minWidth; + } + + public void setMinWidth(int minWidth) { + this.minWidth = minWidth; + } + + public void setAnchor(int a) { + anchor = a; + } + + public int getAnchor() { + return anchor; + } + + public void setPadding(Insets p) { + padding = p; + } + + public Insets getPadding() { + return padding; + } + + /** + * Add a Row to the end of Current Rows + */ + public void addRow(Row r) { + + rows.add(r); + lastTargetDim = null; + } + + /** + * Add a Row + */ + public void addRow(int i, Row r) { + + if (i > rows.size() - 1) { + i = rows.size() - 1; + } + rows.add(i, r); + lastTargetDim = null; + } + + /** + * Remove a Row + */ + public void removeRow(int i) { + + if (i > rows.size() - 1) { + return; + } + rows.remove(i); + lastTargetDim = null; + } + + /** + * Get Row Count + */ + public int getRowCount() { + + return rows.size(); + } + + /** + * Get a Row or null if it doesn't exist + */ + public Row getRow(int r) { + try { + return (Row) rows.get(r); + } catch (IndexOutOfBoundsException e) { + return null; + } + } + + /** + * Adds the specified component to the layout, using the specified + * constraint object. + * @param comp the component to be added + * @param constraints where/how the component is added to the layout. + */ + @Override + public void addLayoutComponent(Component comp, Object constraints) { + addLayoutComponent((String) constraints, comp); + } + + /** + * If the layout manager uses a per-component string, + * adds the component comp to the layout, + * associating it + * with the string specified by name. + * + * @param name the string to be associated with the component + * @param comp the component to be added + */ + @Override + public void addLayoutComponent(String name, Component comp) { + Column c = this.getColumn(name); + if (c != null) { + c.component = comp; + } + } + + /** + * Calculates the preferred size dimensions for the specified + * container, given the components it contains. + * @param target the container to be laid out + * + * @see #minimumLayoutSize + */ + @Override + public Dimension preferredLayoutSize(Container target) { + + if (rows.size() == 0) { + return new Dimension(0, 0); + } + + Insets is=target.getInsets(); + calculatePositions(target); + return new Dimension(preferredWidth+is.left+is.right, preferredHeight+is.top+is.bottom); + } + + /** + * + * Calculates the minimum size dimensions for the specified + * container, given the components it contains. + * @param target the component to be laid out + * @see #preferredLayoutSize + */ + public Dimension minimumLayoutSize(Container target) { + + int min_height = 0; + int min_width = 0; + for (int i = 0; i < rows.size(); i++) { + Row row = (Row) rows.get(i); + float ph = row.getMinimumHeight(); + if (ph < 1) { + min_height = min_height + (int) row.getMinimumHeight(); + } + else { + min_height = min_height + (int) ph; + } + + float pw = row.getMinimumWidth(); + + if (min_width < pw) { + min_width = (int) pw; + } + } + + Insets is=target.getInsets(); + + return new Dimension(min_width+is.left+is.right, min_height+is.top+is.bottom); + } + + /** + * + * Calculates the maximum size dimensions for the specified container, + * given the components it contains. + * @see java.awt.Component#getMaximumSize + * @see LayoutManager + */ + @Override + public Dimension maximumLayoutSize(Container target) { + + if(rows.size() == 0) { + return new Dimension(0,0); + } + + calculatePositions(target); + Insets is = target.getInsets(); + return new Dimension(maxCalculatedWidth+is.left+is.right, maxCalculatedHeight+is.top+is.bottom); + } + + /** + * + * Lays out the specified container. + * @param parent the container to be laid out + */ + public void layoutContainer(Container parent) { + + if (rows.size() == 0) { + return; //Recalculate the Positions + } + calculatePositions(parent); + + synchronized (parent.getTreeLock()) { + Iterator row_it = rows.iterator(); + int r = 0; + while (row_it.hasNext()) { + Row row = (Row) row_it.next(); + if (row.cols != null) { + + Iterator col_it = row.cols.iterator(); + int c = 0; + while (col_it.hasNext()) { + layoutColumn(row, (Column)col_it.next(), r, c); + + //iterate col index + c++; + } + } + + //iterate row index + r++; + } + + }//End of Synchronize Block + } + + /** + * Layout a Column's component + * @param row + * @param col + * @param r num + * @param c num + */ + private void layoutColumn(Row row, Column col, int r, int c) { + + if (col.component == null) { + return; //no component so we don't care + } + + //get the new x index based on colspan + int x = getColXforColSpan(row, c); + if(x == -1) { + return; //colspan pushed the x past the end of the table + } + + //get the w based on colspan + int w = getColWidthforColSpan(row, c); + + int y = (int) rowY[r]; + int h = (int) rowH[r]; + + //Compute RowSpan + //TODO take this out when new RowSpan is done + if (col.rowSpan > 1) { + int lastRow = r + (col.rowSpan - 1); + if (lastRow >= rowH.length) { + lastRow = rowH.length - 1; + } + + h = (((int) rowY[lastRow] + (int) rowH[lastRow])) - y; + } + + //Compute column Padding + if (col.padding != null) { + x = x + col.padding.left; + y = y + col.padding.top; + w = w - col.padding.right; + h = h - col.padding.bottom; + } + + //TODO this max and min calc has a problem of messing up alignment need to rewrite + //Apply Max or Min Width + //if(vcol.getMaximumWidth()) v = col.getMaximumWidth(); + + //if(hcol.component.getMaximumSize().max_height) h = col.component.getMaximumSize().max_height; + + //Compute Table level Padding + if (padding != null) { + x = x + padding.left; + y = y + padding.top; + w = w - padding.right; + h = h - padding.bottom; + } + + int pw = col.component.getPreferredSize().width; + int ph = col.component.getPreferredSize().height; + + //Compute hAlign + if (col.hAlign != FULL) { + + if (col.hAlign == RIGHT) { + if ((colX[c] + w - pw) >= x) { + x = colX[c] + w - pw; + } + } + else if (col.hAlign == CENTER) { + int col_center = x + (w / 2); + if ((col_center - (pw / 2)) >= x) { + x = col_center - (pw / 2); + } + } + if (pw < w) { + w = pw; + } + } + + //Compute vAlign + if (col.vAlign != FULL) { + + if (col.vAlign == BOTTOM) { + if ((rowY[r] + h - ph) >= y) { + y = rowY[r] + h - ph; + } + } + else if (col.vAlign == CENTER) { + int row_center = y + (h / 2); + if ((row_center - (ph / 2)) >= y) { + y = row_center - (ph / 2); + } + } + if (ph < h) { + h = ph; + } + } + + col.component.setLocation(x, y); + col.component.setSize(w, h); + } + + /** + * return -1 if can't determine columns y location + * @param r + * @param c + * @return + */ + private int getRowYforRowSpan(int r, int c) { + int rowIndex = getRowIndexforRowSpan(r, c); + if(rowIndex > rows.size()-1) { + return -1; + } + else { + return rowY[rowIndex]; + } + } + + /** + * return -1 if can't determine columns X location + * @param row + * @param c + * @return + */ + private int getColXforColSpan(Row row, int c) { + int index = getColIndexforColSpan(row, c); + if(index>colX.length-1) { + return -1; + } + else { + return (int) colX[index]; + } + } + + /** + * Get the column width using colspan + * @param row + * @param c + * @return + */ + private int getColWidthforColSpan(Row row, int c) { + int index = getColIndexforColSpan(row, c); + int colspan = row.cols.get(c).colSpan; + + float w = 0; + for(int i=index;icolW.length-1) { + break; //we moved past the end of the table so we just return now + } + w = w + colW[i]; + } + + return (int)w; + } + + private int getColIndexforColSpan(Row row, int c) { + int index = 0; + for(int i=0;irow.cols.size()-1) { + break; + } + Column col = row.cols.get(colIndex); + index = index + col.rowSpan; + } + + return index; + } + + + + /** + * Invalidates the layout, indicating that if the layout manager + * has cached information it should be discarded. + */ + public void invalidateLayout(Container target) { + + rowH = null; + rowY = null; + + colCount = 0; + colW = null; + colX = null; + } + + /** + * Returns the alignment along the y axis. This specifies how + * the component would like to be aligned relative to other + * components. The value should be a number between 0 and 1 + * where 0 represents alignment along the origin, 1 is aligned + * the furthest away from the origin, 0.5 is centered, etc. + */ + public float getLayoutAlignmentY(Container target) { + return 0f; + } + + /** + * Returns the alignment along the x axis. This specifies how + * the component would like to be aligned relative to other + * components. The value should be a number between 0 and 1 + * where 0 represents alignment along the origin, 1 is aligned + * the furthest away from the origin, 0.5 is centered, etc. + */ + public float getLayoutAlignmentX(Container target) { + return 0f; + } + + /** + * Removes the specified component from the layout. + * @param comp the component to be removed + */ + public void removeLayoutComponent(Component comp) { + Iterator it = rows.iterator(); + while (it.hasNext()) { + Row r = (Row) it.next(); + if (r.cols == null) { + continue; + } + Iterator col_it = r.cols.iterator(); + while (col_it.hasNext()) { + Column c = (Column) col_it.next(); + if (c.component == comp) { + c.component = null; + } + } + } + + lastTargetDim = null; + } + + /** + * Get a Column by a specified name + * The column is a specific Column instance in a specific Row + * @return the Column found or null + */ + public Column getColumn(String name) { + + for (int i = 0; i < rows.size(); i++) { + Row r = (Row) rows.get(i); + Column c = r.getColumn(name); + if (c != null) { + return c; + } + } + + return null; + } + + /** + * Get a Column by row number and col number. 0,0 is the Upper-Left component + * @return the Column or null + */ + public Column getColumn(int r, int c) { + try { + return (Column) ((Row) rows.get(r)).cols.get(c); + } catch (IndexOutOfBoundsException badIndex) { + return null; + } catch (NullPointerException noCols) { + return null; + } + } + + /** + * Paint the Layouts Tablelines using the specified graphics context + */ + public void paintTableLines(Container target, Graphics g) { + + if (rowY == null) { + calculatePositions(target); + } + + Graphics2D g2d = (Graphics2D) g; + Dimension size = getSize(target); + Point origin = getOrigin(size, target); + + //Draw outside border + g2d.setColor(Color.RED); + g2d.drawRect(origin.x, origin.y, size.width, size.height); + + //Draw rows + for (int i = 0; i < rowY.length; i++) { + g2d.drawLine(origin.x, rowY[i], size.width + origin.x, rowY[i]); + } + //draw columns + for (int i = 0; i < colX.length; i++) { + int x = colX[i]; + g2d.drawLine(colX[i], origin.y, colX[i], size.height + origin.y); + x = colX[i] + (int) colW[i]; + g2d.drawLine(x, origin.y, x, size.height + origin.y); + } + + } + + /** + * Get the Size that this layout wants based on its settings and the container + */ + private Dimension getSize(Container target) { + Dimension dim = new Dimension(); + Insets is=target.getInsets(); + int twid = target.getWidth() - is.left - is.right; + int thei = target.getHeight() - is.top - is.bottom; + + //min_width + if (width > 1) { + dim.width = (int) width; + } + else { + dim.width = (int) (width * twid); + } + + if (dim.width < minWidth) { + dim.width = minWidth; + } + if (dim.width > maxWidth) { + dim.width = maxWidth; //max_height + } + if (height > 1) { + dim.height = (int) height; + } + else { + dim.height = (int) (height * thei); + } + + if (dim.height < minHeight) { + dim.height = minHeight; + } + if (dim.height > maxHeight) { + dim.height = maxHeight; + } + return dim; + } + + private Point getOrigin(Dimension size, Container target) { + Point origin = new Point(); + Insets is=target.getInsets(); + + int twid = target.getWidth() - is.left - is.right; + int thei = target.getHeight() - is.top - is.bottom; + + origin.x = is.left; + origin.y = is.top; + + if (anchor == NORTH_WEST) { + origin.x = is.left; + origin.y = is.top; + } + else if (anchor == NORTH) { + origin.x = is.left + ((twid - size.width) / 2); + origin.y = is.top; + } + else if (anchor == NORTH_EAST) { + origin.x = is.left + twid - size.width; + origin.y = is.top; + } + else if (anchor == EAST) { + origin.x = is.left + twid - size.width; + origin.y = is.top + ((thei - size.height) / 2); + } + else if (anchor == SOUTH_EAST) { + origin.x = is.left + twid - size.width; + origin.y = is.top + thei - size.height; + } + else if (anchor == SOUTH) { + origin.x = is.left + ((twid - size.width) / 2); + origin.y = is.top + thei - size.height; + } + else if (anchor == SOUTH_WEST) { + origin.x = is.left; + origin.y = is.top + thei - size.height; + } + else if (anchor == WEST) { + origin.x = is.left; + origin.y = is.top + (thei - size.height) / 2; + } + else if (anchor == CENTER) { + origin.x = is.left + ((twid - size.width) / 2); + origin.y = is.top + (thei- size.height) / 2; + } + + return origin; + } + + /** + * Calculate the Positions of each Cell + */ + public void calculatePositions(Container target) { + + if (rows.size() == 0) { + return; + } + + //If we already calculated based on this target size then just return + if (lastTargetDim != null && target.getWidth() > 0 && target.getHeight() > 0) { + if (target.getWidth() == lastTargetDim.width && target.getHeight() == lastTargetDim.height && rowH != null) { + return; + } + } + + Dimension size = getSize(target); + + Point origin = getOrigin(size, target); + + //Calculate Row Heights + calculateRowHeightsAndLocations(size.height, origin.y); + + //Calculate Col Widths + calculateColWidthsAndLocations(size.width, origin.x); + + lastTargetDim = target.getSize(); + } + + private boolean isPreferredValue(float v) { + return (v == PREFERRED); + } + + private boolean isRelativeValue(float v) { + return (v > 0 && v < 1.0); + } + + private boolean isFixedValue(float v) { + return (v >= 1.0); + } + + /** + * Calculate the row heights and the row locations + * @param totalHeight + */ + private void calculateRowHeightsAndLocations(int totalHeight, int y) { + + rowH = new float[rows.size()]; + rowY = new int[rows.size()]; + + //we calculate column count while we are processing rows + colCount = 0; + + int[] maxHeights = new int[rows.size()]; + int[] preferredHeights = new int[rows.size()]; + for (int r = 0; r < rows.size(); r++) { + + Row row = getRow(r); + + //Get column counts while we are at it + if (row.cols != null && row.cols.size() > colCount) { + colCount = row.cols.size(); + } + + //check to see if the row height is fixed or relative + if (isFixedValue(row.height)) { + maxHeights[r] = (int) row.height; + preferredHeights[r] = (int) row.height; + rowH[r] = row.height; + } + else if (isRelativeValue(row.height)) { + maxHeights[r] = (int)row.getMaximumHeight(); + preferredHeights[r] = (int)row.getPreferredHeight(); + rowH[r] = row.height; + } + //use the rows preferred height to determine + else { + + rowH[r] = row.getPreferredHeight(); + preferredHeights[r] = (int)rowH[r]; + + maxHeights[r] = (int)row.getMaximumHeight(); + + /* + for (int c = 0; c < row.cols.size(); c++) { + Column col = row.getColumn(c); + float ph = col.getPreferredHeight(); + if (ph > rowH[r]) { + rowH[r] = ph; + } + + //update preferred, max_height + if (ph > preferredHeights[r]) { + preferredHeights[r] = (int) ph; + } + } + */ + } + } + + preferredHeight = total(preferredHeights); + maxCalculatedHeight = total(maxHeights); + + //our preferred Height shouldn't be lower then our min height + if(preferredHeight < minHeight) { + preferredHeight = minHeight; + } + + if(maxCalculatedHeight < maxHeight) { + maxCalculatedHeight = maxHeight; + } + + //convert relative heights to fixed heights + //add up all the fixed heights + int fixedHeight = 0; + for (int r = 0; r < rowH.length; r++) { + if (!isRelativeValue(rowH[r])) { + fixedHeight += (int) rowH[r]; + } + } + + //first we fix the percentages to make sure that they add up to 1.0 + float totalPercentage = 0; + for (int r = 0; r < rowH.length; r++) { + if (isRelativeValue(rowH[r])) { + totalPercentage += rowH[r]; + } + } + + //the fixed relative widths get ratios of the total percentage + if (totalPercentage > 1.0) { + for (int r = 0; r < rowH.length; r++) { + if (isRelativeValue(rowH[r])) { + rowH[r] = totalPercentage / rowH[r]; + } + } + } + + int remainingHeight = totalHeight - fixedHeight; + //convert the relative widths to fixed widths + for (int r = 0; r < rowH.length; r++) { + if (isRelativeValue(rowH[r])) { + int requestedHeight = (int) (remainingHeight * rowH[r]); + if (requestedHeight < remainingHeight) { + rowH[r] = requestedHeight; + remainingHeight -= requestedHeight; + } + else { + rowH[r] = remainingHeight; + remainingHeight = 0; + } + } + } + + //Calculate Row Y locations + rowY[0] = y; + for (int i = 1; i < rowY.length; i++) { + y = y + (int) rowH[i - 1]; + rowY[i] = y; + } + } + + /** + * Get the Column Widths + */ + private void calculateColWidthsAndLocations(int totalWidth, int x) { + + colW = new float[colCount]; + colX = new int[colCount]; + + if (colCount == 0) { + return; + } + if (rows.size() == 0) { + return; + } + + int[] maxWidths = new int[colW.length]; + int[] preferredWidths = new int[colW.length]; + for (int c = 0; c < colW.length; c++) { + for (int r = 0; r < rows.size(); r++) { + Row row = getRow(r); + if (row.cols.size() < (c + 1)) { + continue; + } + Column col = row.getColumn(c); + float pw = col.getPreferredWidth(); + + int mw = col.getMaximumWidth(); + if(maxWidths[c] 1.0) { + for (int c = 0; c < colW.length; c++) { + if (isRelativeValue(colW[c])) { + colW[c] = 1 / (totalPercentage / colW[c]); + } + } + } + + int remainingWidth = totalWidth - fixedWidth; + //convert the relative widths to fixed widths + for (int c = 0; c < colW.length; c++) { + if (isRelativeValue(colW[c])) { + //if relative min_width is .9999 then they really mean 100% + int requestedWidth = (int) (remainingWidth * (colW[c] == PERCENT_100 ? 1.0 : colW[c])); + if (requestedWidth < remainingWidth) { + colW[c] = requestedWidth; + } + else { + colW[c] = remainingWidth; + remainingWidth = 0; + } + } + } + + //Calculate Col X locations + colX[0] = x; + for (int i = 1; i < colX.length; i++) { + x = x + (int) colW[i - 1]; + colX[i] = x; + } + + } + + private float total(float[] values) { + if (values == null) { + return 0; + } + float totalValue = 0; + + for (int i = 0; i < values.length; i++) { + totalValue += values[i]; + } + + return totalValue; + } + + private int total(int[] values) { + if (values == null) { + return 0; + } + int totalValue = 0; + + for (int i = 0; i < values.length; i++) { + totalValue += values[i]; + } + + return totalValue; + } + + /** + * Get the Column Widths + */ + private void calculateColWidthsOLD(int totalWidth, int colCount) { + + colW = new float[colCount]; + + //Get fixed and preferred Widths + for (int r = 0; r < rows.size(); r++) { + Row row = (Row) rows.get(r); + row.updateColWidth(colW); + } + + //calculate fixed Height + int fixedWidth = 0; + for (int i = 0; i < colCount; i++) { + if (colW[i] >= 1) { + fixedWidth = fixedWidth + (int) colW[i]; + } + } + + //calculate relative Widths + int remainingW = totalWidth - fixedWidth; + for (int i = 0; i < colCount; i++) { + if (remainingW == 0) { + continue; + } + else if (colW[i] > 0 && colW[i] < 1 && remainingW > 0) { + //calc relative min_width + int w = 0; + if (colW[i] > PERCENT_100) { + w = (int) (totalWidth - fixedWidth); + } + else { + w = (int) (colW[i] * (totalWidth - fixedWidth)); + } + + if (w > remainingW) { + w = remainingW; + } + remainingW = remainingW - w; + colW[i] = w; + } + } + } + + /******************************************************************* + * ROW Class represents Table elements + *******************************************************************/ + public static class Row implements Cloneable { + + public float height = PREFERRED; + private ArrayList cols = new ArrayList(); + + /** + * Create a row with PREFERRED Height + */ + public Row() { + height = PREFERRED; + } + + /** + * Create a row with a specified max_height + */ + public Row(float h) { + height = h; + } + + public int getColumnCount() { + return cols.size(); + } + + /** + * Add a Column to this Row + */ + public void addCol(Column c) { + cols.add(c); + } + + /** + * insert empty column at index. Columns will be added to fill into i + * @param index + */ + public void insertEmptyColumn(int index) { + if (index > cols.size()) { + int count = index - cols.size(); + for (int i = 0; i < count + 1; i++) { + cols.add(new Column()); + } + } + else { + cols.add(index, new Column()); + } + } + + /** + * Copy this row + * @return a copy of this row including a copy of all columns + */ + @Override + public Object clone() { + + Row rowCopy = new Row(); + rowCopy.height = this.height; + if (this.cols != null) { + for (int i = 0; i < this.cols.size(); i++) { + Column colCopy = (Column) ((Column) this.cols.get(i)).clone(); + rowCopy.addCol(colCopy); + } + } + + return rowCopy; + } + + /** + * Returns a Rows Preferred Height. Based on the content components of the row + */ + public int getPreferredHeight() { + + //Must be Preferred Height so check the components + Iterator it = cols.iterator(); + int h = 0; + while (it.hasNext()) { + Column col = it.next(); + Component comp = col.component; + if (comp != null) { + int ph = comp.getPreferredSize().height; + if (col.padding != null) { + ph = ph + col.padding.top + col.padding.bottom; + } + if (ph > h) { + h = ph; + } + } + } + + return h; + } + + /** + * Returns a Rows Maximum Height based on calculating the maximum components height in this row + */ + public int getMaximumHeight() { + + Iterator it = cols.iterator(); + int h = 0; + while (it.hasNext()) { + Column col = it.next(); + Component comp = col.component; + if (comp != null) { + int mh = comp.getMaximumSize().height; + if (col.padding != null) { + mh = mh + col.padding.top + col.padding.bottom; + } + if (mh > h) { + h = mh; + } + } + } + + return h; + } + + /** + * Returns a Rows Maximum Width. If row is PREFERRED then returns the max Width column components + * If row is relative Percentage or fixed then returns 1 + */ + public int getMaximumWidth() { + + if (cols == null) { + return 0; + } + Iterator it = cols.iterator(); + int w = 0; + while (it.hasNext()) { + Column col = (Column) it.next(); + w = w + col.getMaximumWidth(); + } + + return w; + } + + /** + * Returns a Rows Minimum Width. If row is PREFERRED then returns the min Width column components + * If row is relative Percentage or fixed then returns 1 + */ + public int getMinimumWidth() { + + if (cols == null) { + return 0; + } + Iterator it = cols.iterator(); + int w = 0; + while (it.hasNext()) { + Column col = (Column) it.next(); + float pw = col.getMinimumWidth(); + + if (pw > w) { + w = (int) pw; + } + } + + return w; + } + + /** + * Returns a Rows Preferred Width by getting the sum of all columns widths. + */ + public int getPreferredWidth() { + + if (cols == null) { + return 0; + } + Iterator it = cols.iterator(); + int w = 0; + while (it.hasNext()) { + Column col = (Column) it.next(); + float pw = col.getPreferredWidth(); + if (pw < 1) { + pw = col.getMinimumWidth(); + } + + w = w + (int) pw; + + } + + return w; + } + + /** + * Returns a Rows Minimum Height. + * IF row is fixed then returns the fixed row max_height + * If row is PREFERRED or relative Percentage + * then returns the max minimum Height of all column components + */ + public int getMinimumHeight() { + + //If a fixed max_height + if (height >= 1) { + return (int) height; //must be preferred or relative in which case we just return the min max_height + } + if (cols == null) { + return 0; + } + Iterator it = cols.iterator(); + int h = 0; + while (it.hasNext()) { + Column col = (Column) it.next(); + Component comp = col.component; + if (comp != null) { + int mh = comp.getMinimumSize().height; + if (col.padding != null) { + mh = mh + col.padding.top + col.padding.bottom; + } + if (mh > h) { + h = mh; + } + } + } + + return h; + } + + /** + * Calculate the Column Widths that this Row wants. Existing column widths are passed in and + * if this row's columns want widths that are larger then it replaces just those widths with its + * own columns widths + */ + protected float[] updateColWidth(float colW[]) { + + //If we don't have any columns then we can't figure it out + //so just return what we got + if (cols == null) { + return colW; //Walk through each col + } + float relativeWidth = 0; + for (int i = 0; i < colW.length; i++) { + try { + Column col = (Column) cols.get(i); + + //The preferred min_width is either a fixed min_width or a relative min_width + float pw = col.getPreferredWidth(); + + //If a percentage min_width then make sure we have room for it + if (pw > 0 && pw < 1) { + if ((relativeWidth + pw) > 1) { + pw = col.getMinimumWidth(); + relativeWidth = 1.0f; + } + else { + relativeWidth = relativeWidth + pw; + } + } + + //If fixed min_width was based on preferred component min_width then + //use it if its greater then what we have and what we have is not a relative min_width + if ((col.width == PREFERRED) && (pw > colW[i]) && (colW[i] >= 1)) { + colW[i] = pw; + } //Else use the fixed or percentage min_width if we don't have a setting yet + else if (colW[i] == 0) { + colW[i] = pw; + } + } //Cols that we don't have just get skipped + catch (IndexOutOfBoundsException noCol) { + } + } + + return colW; + } + + /** + * Get a Column for a specific Name + */ + public Column getColumn(String name) { + if (cols == null) { + return null; + } + Iterator it = cols.iterator(); + while (it.hasNext()) { + Column c = (Column) it.next(); + if (c.name != null && c.name.equals(name)) { + return c; + } + } + + return null; + } + + public Column getColumn(int index) { + if (index >= cols.size()) { + return null; + } + return (Column) cols.get(index); + } + } + + /******************************************************************* + * Column Class represents Table
elements or Columns in Rows + *******************************************************************/ + public static class Column implements Cloneable { + + public float width = PREFERRED; + public String name; + public Component component; + public int colSpan = 1; + public int rowSpan = 1; + public int hAlign = LEFT; + public int vAlign = CENTER; + Insets padding = null; + + /** + * Create a Column + */ + public Column() { + } + + /** + * Create a Column with a Name + */ + public Column(String n) { + name = n; + } + + /** + * Create a Column with a Width + */ + public Column(float w) { + width = w; + } + + /** + * Create a Column with name and min_width + */ + public Column(String n, float w) { + name = n; + width = w; + } + + /** + * Create a Column with a specific column and row span + */ + public Column(String n, float w, int cspan, int rspan) { + name = n; + width = w; + colSpan = cspan; + rowSpan = rspan; + } + + /** + * Get a copy of this column information + * @return + */ + @Override + public Object clone() { + Column colCopy = new Column(); + + colCopy.width = this.width; + colCopy.name = this.name; + colCopy.component = null; //we are copying the layout not the components in it + colCopy.colSpan = this.colSpan; + colCopy.rowSpan = this.rowSpan; + colCopy.hAlign = this.hAlign; + colCopy.vAlign = this.vAlign; + if (this.padding != null) { + colCopy.padding = (Insets) this.padding.clone(); + } + + return colCopy; + } + + /** + * Returns a Columns Preferred Width. If the column is PREFERRED then returns this column's component preferred min_width + * If the column is a relative percentage then returns that percentage + */ + public float getPreferredWidth() { + float pw = 0; + if (width == PREFERRED && component != null && colSpan <= 1) { + pw = component.getPreferredSize().width; + return pw; + } + else if (width == PREFERRED && (component == null || colSpan > 1)) { + return 0; + } + else if (width >= 1 || width == 0) { + + pw = width; + if (padding != null) { + pw = pw + padding.left + padding.right; + } + + return pw; + } + else { + return width; + } + } + + public int getPreferredHeight() { + if (component == null) { + return 0; + } + else { + return component.getPreferredSize().height; + } + } + + public int getComponentPreferredWidth() { + if (component == null) { + return 0; + } + else { + return component.getPreferredSize().width; + } + } + + /** + * Gets this Columns Components Minimum Width + */ + public int getMinimumWidth() { + + //if its fixed then just return it + if (width > 1) { + return (int) width; //if no component then just 0 + } + if (component == null) { + return 0; + } + return component.getMinimumSize().width; + } + + /** + * Gets this Columns Components Maximum Width + */ + public int getMaximumWidth() { + + //if its fixed then just return it + if (width > 1) { + return (int) width; //if no component then just 0 + } + if (component == null) { + return 0; + } + return component.getMaximumSize().width; + } + } +} +