/* * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores * CA 94065 USA or visit www.oracle.com if you need additional information or * have any questions. */ package com.sun.lwuit.table; import com.sun.lwuit.layouts.*; import com.sun.lwuit.Component; import com.sun.lwuit.Container; import com.sun.lwuit.geom.Dimension; import com.sun.lwuit.plaf.Style; import java.util.Vector; /** * Layout manager similar in spirit to HTML tables allowing rows and columns * of varying width/height. * * @author Shai Almog */ public class TableLayout extends Layout { private int currentRow; private int currentColumn; private static int minimumSizePerColumn = 10; private Constraint[][] tablePositions; private int[] columnSizes; private int[] columnPositions; private int[] rowPositions; private boolean[] modifableColumnSize; /** * Special case marker SPAN constraint reserving place for other elements */ private static final Constraint H_SPAN_CONSTRAINT = new Constraint(); private static final Constraint V_SPAN_CONSTRAINT = new Constraint(); private static final Constraint VH_SPAN_CONSTRAINT = new Constraint(); private static int defaultColumnWidth = -1; private static int defaultRowHeight = -1; private boolean horizontalSpanningExists; private boolean verticalSpanningExists; /** * A table must declare the amount of rows and columns in advance * * @param rows rows of the table * @param columns columns of the table */ public TableLayout(int rows, int columns) { tablePositions = new Constraint[rows][columns]; } /** * Get the number of rows * @return number of rows */ public int getRows() { return tablePositions.length; } /** * Get the number of columns * @return number of columns */ public int getColumns() { return tablePositions[0].length; } /** * Returns the component at the given row/column * * @param row the row of the component * @param column the column of the component * @return the component instance */ public Component getComponentAt(int row, int column) { return tablePositions[row][column].parent; } /** * @inheritDoc */ public void layoutContainer(Container parent) { verticalSpanningExists = false; horizontalSpanningExists = false; // column and row size in pixels Style s = parent.getStyle(); int top = s.getPadding(false, Component.TOP); int left = s.getPadding(parent.isRTL(), Component.LEFT); int bottom = s.getPadding(false, Component.BOTTOM); int right = s.getPadding(parent.isRTL(), Component.RIGHT); boolean rtl = parent.isRTL(); columnSizes = new int[tablePositions[0].length]; if(modifableColumnSize == null) { modifableColumnSize = new boolean[tablePositions[0].length]; } columnPositions = new int[tablePositions[0].length]; int[] rowSizes = new int[tablePositions.length]; rowPositions = new int[tablePositions.length]; int pWidth = parent.getLayoutWidth() - parent.getSideGap() - left - right; int pHeight = parent.getLayoutHeight() - parent.getBottomGap() - top - bottom; int currentX = left; for(int iter = 0 ; iter < columnSizes.length ; iter++) { columnSizes[iter] = getColumnWidthPixels(iter, pWidth, pWidth); } // try to recalculate the columns for none horizontally scrollable tables // so they are distributed sensibly if no room is available if(!parent.isScrollableX()) { int totalWidth = 0; int totalModifyablePixels = 0; // check how many columns we can modify (the user hasn't requested a specific size for those) for(int iter = 0 ; iter < modifableColumnSize.length ; iter++) { if(modifableColumnSize[iter]) { totalModifyablePixels += columnSizes[iter]; } totalWidth += columnSizes[iter]; } if(pWidth < totalWidth) { int totalPixelsToRemove = totalWidth - pWidth; int totalPixelsNecessary = totalModifyablePixels - totalPixelsToRemove; // Go over the modifyable columns and remove the right pixels according to the ratio for(int iter = 0 ; iter < modifableColumnSize.length ; iter++) { if(modifableColumnSize[iter]) { columnSizes[iter] = (int)(((float)columnSizes[iter]) / ((float)totalModifyablePixels) * totalPixelsNecessary); } } } } for(int iter = 0 ; iter < columnSizes.length ; iter++) { if(rtl) { currentX += columnSizes[iter]; columnPositions[iter] = pWidth - currentX; } else { columnPositions[iter] = currentX; currentX += columnSizes[iter]; } } int currentY = top; for(int iter = 0 ; iter < rowSizes.length ; iter++) { if(parent.isScrollableY()) { rowSizes[iter] = getRowHeightPixels(iter, pHeight, -1); } else { rowSizes[iter] = getRowHeightPixels(iter, pHeight, pHeight - currentY + top); } rowPositions[iter] = currentY; currentY += rowSizes[iter]; } for(int r = 0 ; r < rowSizes.length ; r++) { for(int c = 0 ; c < columnSizes.length ; c++) { Constraint con = tablePositions[r][c]; int conX, conY, conW, conH; if(con != null && con != H_SPAN_CONSTRAINT && con != V_SPAN_CONSTRAINT && con != VH_SPAN_CONSTRAINT) { Style componentStyle = con.parent.getStyle(); int leftMargin = componentStyle.getMargin(parent.isRTL(), Component.LEFT); int topMargin = componentStyle.getMargin(false, Component.TOP); // conX = left + leftMargin + columnPositions[c]; // bugfix table with padding not drawn correctly // conY = top + topMargin + rowPositions[r]; // bugfix table with padding not drawn correctly conX = leftMargin + columnPositions[c]; conY = topMargin + rowPositions[r]; if(con.spanHorizontal > 1) { horizontalSpanningExists = true; int w = columnSizes[c]; for(int sh = 1 ; sh < con.spanHorizontal ; sh++) { w += columnSizes[Math.min(c + sh, columnSizes.length - 1)]; } // for RTL we need to move the component to the side so spanning will work if(rtl) { conX = left + leftMargin + columnPositions[c + con.spanHorizontal - 1]; } conW = w - leftMargin - componentStyle.getMargin(parent.isRTL(), Component.RIGHT); } else { conW = columnSizes[c] - leftMargin - componentStyle.getMargin(parent.isRTL(), Component.RIGHT); } if(con.spanVertical > 1) { verticalSpanningExists = true; int h = rowSizes[r]; for(int sv = 1 ; sv < con.spanVertical ; sv++) { h += rowSizes[Math.min(r + sv, rowSizes.length - 1)]; } conH = h - topMargin - componentStyle.getMargin(false, Component.BOTTOM); } else { conH = rowSizes[r] - topMargin - componentStyle.getMargin(false, Component.BOTTOM); } placeComponent(rtl, con, conX, conY, conW, conH); } } } } /** * Returns the position of the given table row. A valid value is only returned after the * layout occurred. * * @param row the row in the table * @return the Y position in pixels or -1 if layout hasn't occured/row is too large etc. */ public int getRowPosition(int row) { if(rowPositions != null && rowPositions.length > row) { return rowPositions[row]; } return -1; } /** * Returns the position of the given table column. A valid value is only returned after the * layout occurred. * * @param col the column in the table * @return the X position in pixels or -1 if layout hasn't occured/column is too large etc. */ public int getColumnPosition(int col) { if(columnPositions != null && columnPositions.length > col) { return columnPositions[col]; } return -1; } /** * Places the component/constraint in the proper alignment within the cell whose bounds are given */ private void placeComponent(boolean rtl, Constraint con, int x, int y, int width, int height) { con.parent.setX(x); con.parent.setY(y); con.parent.setWidth(width); con.parent.setHeight(height); Dimension pref = con.parent.getPreferredSize(); int pWidth = pref.getWidth(); int pHeight = pref.getHeight(); if(pWidth < width) { int d = (width - pWidth); int a = con.align; if(rtl) { switch(a) { case Component.LEFT: a = Component.RIGHT; break; case Component.RIGHT: a = Component.LEFT; break; } } switch(a) { case Component.LEFT: con.parent.setX(x); con.parent.setWidth(width - d); break; case Component.RIGHT: con.parent.setX(x + d); con.parent.setWidth(width - d); break; case Component.CENTER: con.parent.setX(x + d / 2); con.parent.setWidth(width - d); break; } } if(pHeight < height) { int d = (height - pHeight); switch(con.valign) { case Component.TOP: con.parent.setY(y); con.parent.setHeight(height - d); break; case Component.BOTTOM: con.parent.setY(y + d); con.parent.setHeight(height - d); break; case Component.CENTER: con.parent.setY(y + d / 2); con.parent.setHeight(height - d); break; } } } private int getColumnWidthPixels(int column, int percentageOf, int available) { int current = 0; if(modifableColumnSize == null) { modifableColumnSize = new boolean[tablePositions[0].length]; } for(int iter = 0 ; iter < tablePositions.length ; iter++) { Constraint c = tablePositions[iter][column]; if(c == null || c == H_SPAN_CONSTRAINT || c == V_SPAN_CONSTRAINT || c == VH_SPAN_CONSTRAINT) { continue; } // width in percentage of the parent container if(c.width > 0 && available > -1) { current = Math.max(current, c.width * percentageOf / 100); modifableColumnSize[column] = false; } else { Style s = c.parent.getStyle(); current = Math.max(current, c.parent.getPreferredW() + s.getMargin(false, Component.LEFT) + s.getMargin(false, Component.RIGHT)); modifableColumnSize[column] = true; } if(available > -1) { current = Math.min(available, current); } } return current; } private int getRowHeightPixels(int row, int percentageOf, int available) { int current = 0; for(int iter = 0 ; iter < tablePositions[row].length ; iter++) { Constraint c = tablePositions[row][iter]; if(c == null || c == H_SPAN_CONSTRAINT || c == V_SPAN_CONSTRAINT || c == VH_SPAN_CONSTRAINT) { continue; } // height in percentage of the parent container if(c.height > 0) { current = Math.max(current, c.height * percentageOf / 100); } else { Style s = c.parent.getStyle(); current = Math.max(current, c.parent.getPreferredH() + s.getMargin(false, Component.TOP) + s.getMargin(false, Component.BOTTOM)); } if(available > -1) { current = Math.min(available, current); } } return current; } /** * @inheritDoc */ public Dimension getPreferredSize(Container parent) { Style s = parent.getStyle(); int w = s.getPadding(false, Component.LEFT) + s.getPadding(false, Component.RIGHT); int h = s.getPadding(false, Component.TOP) + s.getPadding(false, Component.BOTTOM); for(int iter = 0 ; iter < tablePositions[0].length ; iter++) { w += getColumnWidthPixels(iter, Integer.MAX_VALUE, -1); } for(int iter = 0 ; iter < tablePositions.length ; iter++) { h += getRowHeightPixels(iter, Integer.MAX_VALUE, -1); } return new Dimension(w, h); } /** * Returns the row where the next operation of add will appear * * @return the row where the next operation of add will appear */ public int getNextRow() { return currentRow; } /** * Returns the column where the next operation of add will appear * * @return the column where the next operation of add will appear */ public int getNextColumn() { return currentColumn; } private void shiftCell(int row, int column) { Constraint currentConstraint = tablePositions[row][column]; for(int iter = column + 1 ; iter < tablePositions[row].length ; iter++) { if(tablePositions[row][iter] != null) { Constraint tmp = tablePositions[row][iter]; tablePositions[row][iter] = currentConstraint; currentConstraint = tmp; } else { tablePositions[row][iter] = currentConstraint; return; } } for(int rowIter = row + 1 ; rowIter < getRows() ; rowIter++) { for(int colIter = 0 ; colIter < getColumns() ; colIter++) { if(tablePositions[rowIter][colIter] != null) { Constraint tmp = tablePositions[rowIter][colIter]; tablePositions[rowIter][colIter] = currentConstraint; currentConstraint = tmp; } else { tablePositions[rowIter][colIter] = currentConstraint; return; } } } // if we reached this point there aren't enough rows addRow(); } private void addRow() { Constraint[][] newArr = new Constraint[tablePositions.length + 1][tablePositions[0].length]; for(int iter = 0 ; iter < tablePositions.length ; iter++) { newArr[iter] = tablePositions[iter]; } tablePositions = newArr; } /** * @inheritDoc */ public void addLayoutComponent(Object value, Component comp, Container c) { Constraint con = (Constraint)value; if(con == null) { con = createConstraint(); } else { if(con.parent != null) { throw new IllegalArgumentException("Constraint already associated with component!"); } } con.actualRow = con.row; con.actualColumn = con.column; if(con.actualRow < 0) { con.actualRow = currentRow; } if(con.actualColumn < 0) { con.actualColumn = currentColumn; } con.parent = comp; if(con.actualRow >= tablePositions.length) { // increase the table row count implicitly addRow(); } if(tablePositions[con.actualRow][con.actualColumn] != null) { if(tablePositions[con.actualRow][con.actualColumn].row != -1 || tablePositions[con.actualRow][con.actualColumn].column != -1) { throw new IllegalArgumentException("Row: " + con.row + " and column: " + con.column + " already occupied"); } // try to reflow the table from this row/column onwards shiftCell(con.actualRow, con.actualColumn); tablePositions[con.actualRow][con.actualColumn] = con; } tablePositions[con.actualRow][con.actualColumn] = con; if(con.spanHorizontal > 1 || con.spanVertical > 1) { for(int sh = 0 ; sh < con.spanHorizontal ; sh++) { for(int sv = 0 ; sv < con.spanVertical ; sv++) { if((sh > 0 || sv > 0) && tablePositions.length > con.actualRow + sv && tablePositions[con.actualRow + sv].length > con.actualColumn + sh) { if(tablePositions[con.actualRow + sv][con.actualColumn + sh] == null) { if(con.spanHorizontal > 1) { if(con.spanVertical > 1) { tablePositions[con.actualRow + sv][con.actualColumn + sh] = VH_SPAN_CONSTRAINT; } else { tablePositions[con.actualRow + sv][con.actualColumn + sh] = V_SPAN_CONSTRAINT; } } else { tablePositions[con.actualRow + sv][con.actualColumn + sh] = H_SPAN_CONSTRAINT; } } } } } } updateRowColumn(); } private void updateRowColumn() { if(currentRow >= tablePositions.length) { return; } while(tablePositions[currentRow][currentColumn] != null) { currentColumn++; if(currentColumn >= tablePositions[0].length) { currentColumn = 0; currentRow++; if(currentRow >= tablePositions.length) { return; } } } } /** * Returns the spanning for the table cell at the given coordinate * * @param row row in the table * @param column column within the table * @return the amount of spanning 1 for no spanning */ public int getCellHorizontalSpan(int row, int column) { return tablePositions[row][column].spanHorizontal; } /** * Returns the spanning for the table cell at the given coordinate * * @param row row in the table * @param column column within the table * @return the amount of spanning 1 for no spanning */ public int getCellVerticalSpan(int row, int column) { return tablePositions[row][column].spanVertical; } /** * Returns true if the cell at the given position is spanned through vertically * * @param row cell row * @param column cell column * @return true if the cell is a part of a span for another cell */ public boolean isCellSpannedThroughVertically(int row, int column) { return tablePositions[row][column] == V_SPAN_CONSTRAINT || tablePositions[row][column] == VH_SPAN_CONSTRAINT; } /** * Returns true if the cell at the given position is spanned through horizontally * * @param row cell row * @param column cell column * @return true if the cell is a part of a span for another cell */ public boolean isCellSpannedThroughHorizontally(int row, int column) { return tablePositions[row][column] == H_SPAN_CONSTRAINT || tablePositions[row][column] == VH_SPAN_CONSTRAINT; } /** * Indicates whether there is spanning within this layout * * @return true if the layout makes use of spanning */ public boolean hasVerticalSpanning() { return verticalSpanningExists; } /** * Indicates whether there is spanning within this layout * * @return true if the layout makes use of spanning */ public boolean hasHorizontalSpanning() { return horizontalSpanningExists; } /** * @inheritDoc */ public void removeLayoutComponent(Component comp) { // reflow the table Vector comps = new Vector(); for(int r = 0 ; r < tablePositions.length ; r++) { for(int c = 0 ; c < tablePositions[r].length ; c++) { if(tablePositions[r][c] != null) { if(tablePositions[r][c].parent != comp) { comps.addElement(tablePositions[r][c]); } else { tablePositions[r][c].parent = null; } } tablePositions[r][c] = null; } } currentRow = 0; currentColumn = 0; int count = comps.size(); for(int iter = 0 ; iter < count ; iter++) { Constraint con = (Constraint)comps.elementAt(iter); if(con == H_SPAN_CONSTRAINT || con == V_SPAN_CONSTRAINT || con == VH_SPAN_CONSTRAINT) { continue; } Component c = con.parent; con.parent = null; addLayoutComponent(con, c, c.getParent()); } } /** * @inheritDoc */ public Object getComponentConstraint(Component comp) { for(int r = 0 ; r < tablePositions.length ; r++) { for(int c = 0 ; c < tablePositions[r].length ; c++) { if(tablePositions[r][c] != null && tablePositions[r][c].parent == comp) { return tablePositions[r][c]; } } } return null; } /** * Creates a new Constraint instance to add to the layout * * @return the default constraint */ public Constraint createConstraint() { return new Constraint(); } /** * Creates a new Constraint instance to add to the layout * * @param row the row for the table starting with 0 * @param column the column for the table starting with 0 * @return the new constraint */ public Constraint createConstraint(int row, int column) { Constraint c = createConstraint(); c.row = row; c.column = column; return c; } /** * Sets the minimum size for a column in the table, this is applicable for tables that are * not scrollable on the X axis. This will force the earlier columns to leave room for * the latter columns. * * @param minimumSize the minimum width of the column */ public static void setMinimumSizePerColumn(int minimumSize) { minimumSizePerColumn = minimumSize; } /** * Indicates the minimum size for a column in the table, this is applicable for tables that are * not scrollable on the X axis. This will force the earlier columns to leave room for * the latter columns. * * @return the minimum width of the column */ public static int getMinimumSizePerColumn() { return minimumSizePerColumn; } /** * Indicates the default (in percentage) for the column width, -1 indicates * automatic sizing * * @param w width in percentage */ public static void setDefaultColumnWidth(int w) { defaultColumnWidth = w; } /** * Indicates the default (in percentage) for the column width, -1 indicates * automatic sizing * * @return width in percentage */ public static int getDefaultColumnWidth() { return defaultColumnWidth; } /** * Indicates the default (in percentage) for the row height, -1 indicates * automatic sizing * * @param h height in percentage */ public static void setDefaultRowHeight(int h) { defaultRowHeight = h; } /** * Indicates the default (in percentage) for the row height, -1 indicates * automatic sizing * * @return height in percentage */ public static int getDefaultRowHeight() { return defaultRowHeight; } /** * Represents the layout constraint for an entry within the table indicating * the desired position/behavior of the component. */ public static class Constraint { private Component parent; private int row = -1; private int column = -1; private int width = defaultColumnWidth; private int height = defaultRowHeight; private int spanHorizontal = 1; private int spanVertical = 1; private int align = -1; private int valign = -1; int actualRow = -1; int actualColumn = -1; /** * Sets the cells to span vertically, this number must never be smaller than 1 * * @param span a number larger than 1 */ public void setVerticalSpan(int span) { if(span < 1) { throw new IllegalArgumentException("Illegal span"); } spanVertical = span; } /** * Sets the cells to span horizontally, this number must never be smaller than 1 * * @param span a number larger than 1 */ public void setHorizontalSpan(int span) { if(span < 1) { throw new IllegalArgumentException("Illegal span"); } spanHorizontal = span; } /** * Sets the column width based on percentage of the parent * * @param width negative number indicates ignoring this member */ public void setWidthPercentage(int width) { this.width = width; } /** * Sets the row height based on percentage of the parent * * @param height negative number indicates ignoring this member */ public void setHeightPercentage(int height) { this.height = height; } /** * Sets the horizontal alignment of the table cell * * @param align Component.LEFT/RIGHT/CENTER */ public void setHorizontalAlign(int align) { this.align = align; } /** * Sets the vertical alignment of the table cell * * @param valign Component.TOP/BOTTOM/CENTER */ public void setVerticalAlign(int valign) { this.valign = valign; } /** * @return the row */ public int getRow() { return row; } /** * @return the column */ public int getColumn() { return column; } /** * @return the width */ public int getWidthPercentage() { return width; } /** * @return the height */ public int getHeightPercentage() { return height; } /** * @return the spanHorizontal */ public int getHorizontalSpan() { return spanHorizontal; } /** * @return the spanVertical */ public int getVerticalSpan() { return spanVertical; } /** * @return the align */ public int getHorizontalAlign() { return align; } /** * @return the valign */ public int getVerticalAlign() { return valign; } } /** * @inheritDoc */ public String toString() { return "TableLayout"; } /** * @inheritDoc */ public boolean equals(Object o) { return super.equals(o) && ((TableLayout)o).getRows() == getRows() && ((TableLayout)o).getColumns() == getColumns(); } }