package org.eclipse.swt.nebula.widgets.compositetable; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Widget; abstract class AbstractGridRowLayout extends CompositeTableLayout { protected static final int CELL_BORDER_WIDTH = 2; /** * Constructor AbstractGridRowLayout. The default constructor. If you use this * constructor, you must manually specify the column weights, and possibly, * the fittingHorizontally property value. */ public AbstractGridRowLayout() {} /** * Constructor AbstractGridRowLayout. Construct a AbstractGridRowLayout, * specifying the column weights. By default, fittingHorizontally is false. * * @param weights * int[] The amount of weight desired for each column in the * table. If fittingHorizontally is set to true, the sum of all * weights must be 100 and each weight indicates the percentage * of the whole table that each column will occupy. If * fittingHorizontally is set to false, each weight is the * minimum width of the column in pixels. If the table is * narrower than can fit all widths, CompositeTable will display * a horizontal scroll bar. If the table is wider than can fit * all widths, the columns are scaled so that the entire table * fills the desired space and the ratios of the column widths * remains constant. fittingHorizontally defaults to false. */ public AbstractGridRowLayout(int[] weights) { setWeights(weights); } /** * Construct a AbstractGridRowLayout, specifying both the weights and the * fittingHorizontally property. * * @param weights * int[] The amount of weight desired for each column in the * table. If fittingHorizontally is set to true, the sum of all * weights must be 100 and each weight indicates the percentage * of the whole table that each column will occupy. If * fittingHorizontally is set to false, each weight is the * minimum width of the column in pixels. If the table is * narrower than can fit all widths, CompositeTable will display * a horizontal scroll bar. If the table is wider than all * minimum column widths, the columns will be scaled so that the * ratios of the actual widths remains constant and all columns * fit exactly in the available space. fittingHorizontally * defaults to false. * * @param fittingHorizontally * If true, the weights are interpreted as percentages and the * column widths are scaled so that each column occupies the * percentage of the total width indicated by its weight. If * false, the weights are interpreted as minimum column widths. * If the table is narrower than can accommodate those widths, * CompositeTable will display a horizontal scroll bar. If the * table is wider than all minimum column widths, the columns * will be scaled so that the ratios of the actual widths remains * constant and all columns fit exactly in the available space. */ public AbstractGridRowLayout(int[] weights, boolean fittingHorizontally) { setWeights(weights); setFittingHorizontally(fittingHorizontally); } protected Point computeSize(Composite child, int wHint, int hHint, boolean flushCache) { int preferredWidth = computePreferredWidth(child); int preferredHeight = computeMaxHeight(child); return new Point(preferredWidth, preferredHeight); } protected void layout(Composite child, boolean flushCache) { if (isFittingHorizontally() || isWidthWiderThanAllColumns(child)) { layoutWeightedHeaderOrRow(child); } else { layoutAbsoluteWidthHeaderOrRow(child); } } /** * Given the specified header or row, computes if the available width * is wider than the sum of all columns' preferred widths. * * @param headerOrRow * The header or row * @return true if the available width is wider than the sum of all columns' * preferred widths; false otherwise. */ protected boolean isWidthWiderThanAllColumns(Composite headerOrRow) { if (isFittingHorizontally()) { // If we're fitting horizontally, the width is never wider than // all columns return false; } int allColumnsTotalWidth = computePreferredWidth(headerOrRow); return getAvailableWidth(headerOrRow) > allColumnsTotalWidth; } /** * Returns the number of horizontal pixels available for column data. * * @param headerOrRow The header or row object * @return int the number of horizontal pixels available for column data. */ protected int getAvailableWidth(Composite headerOrRow) { return headerOrRow.getParent().getParent().getSize().x; } private int computePreferredWidth(Composite child) { if (isFittingHorizontally()) { return 1; } int allColumnsTotalWidth = 0; int[] colWeights = getWeights(); for (int i = 0; i < colWeights.length; i++) { allColumnsTotalWidth += colWeights[i] + 2*CELL_BORDER_WIDTH; } return allColumnsTotalWidth; } private int layoutWeightedHeaderOrRow(Composite child) { int numChildren = getNumColumns(child); if (numChildren == 0) { return 50; } int maxHeight = computeMaxHeight(child); int[] colWeights = getWeights(); if (isFittingHorizontally()) { colWeights = checkWeights(colWeights, numChildren); } else { colWeights = computeWeights(colWeights, numChildren); } int widthRemaining = child.getParent().getSize().x; int totalSize = widthRemaining; for (int i = 0; i < numChildren - 1; i++) { int left = totalSize - widthRemaining; Widget columnObject = getColumnAt(child, i); int leftPos = left + CELL_BORDER_WIDTH; int width = (int) (((float) colWeights[i]) / 100 * totalSize); int widthIncludingBorderWidth = width - 2*CELL_BORDER_WIDTH; int desiredHeight = computeColumnSize(columnObject, SWT.DEFAULT, SWT.DEFAULT, false).y; int top = computeTop(maxHeight, desiredHeight); setBounds(columnObject, leftPos, top, widthIncludingBorderWidth, desiredHeight - 1); widthRemaining -= width; } Widget lastColumn = getColumnAt(child, numChildren - 1); int left = totalSize - widthRemaining; int desiredHeight = computeColumnSize(lastColumn, SWT.DEFAULT, SWT.DEFAULT, false).y; int top = computeTop(maxHeight, desiredHeight); setBounds(lastColumn, left + CELL_BORDER_WIDTH, top, widthRemaining - 2*CELL_BORDER_WIDTH, desiredHeight); return maxHeight; } private int layoutAbsoluteWidthHeaderOrRow(Composite child) { int numChildren = getNumColumns(child); if (numChildren == 0) { return 50; } int maxHeight = computeMaxHeight(child); int[] colWidths = getWeights(); int left = 0; for (int i = 0; i < numChildren; i++) { Widget column = getColumnAt(child, i); int desiredHeight = computeColumnSize(column, SWT.DEFAULT, SWT.DEFAULT, false).y; int top = computeTop(maxHeight, desiredHeight); setBounds(column, left + 2, top, colWidths[i], desiredHeight); left += colWidths[i] + 2*CELL_BORDER_WIDTH; } return maxHeight; } /** * Return the number of columns in the specified row or header. * * @param rowOrHeader The row or header object. * @return int the number of columns in the specified row or header. */ protected abstract int getNumColumns(Composite rowOrHeader); /** * Return the maximum desired height of each of the row or header's children. * * @param rowOrHeader The row or header Composite * @return int the maximum desired height of each of the row or header's children. */ protected abstract int computeMaxHeight(Composite rowOrHeader); /** * Return the SWT Widget representing the specified column. * @param rowOrHeader The header or row object * @param offset The column's offset. * @return The SWT Widget. */ protected abstract Widget getColumnAt(Composite rowOrHeader, int offset); /** * Compute and return the preferred size of the specified column object, * passing the usual SWT wHint, hHint, and flush parameters. * * @param columnObject The column object * @param wHint SWT.DEFAULT or a preferred width as an int * @param hHint SWT.DEFAULT or a preferred height as an int * @param flush If any cached size should be flushed and recomputed. * @return Point the preferred size. */ protected abstract Point computeColumnSize(Widget columnObject, int wHint, int hHint, boolean flush); /** * Set the bounds of the specified column object. Any of the parameters may * be ignored if necessary (for example, a real Table header will ignore the * top and height parameters). * * @param columnObject The column object to place * @param left The column's left coordinate * @param top The column's top coordinate * @param width The column's width * @param height The column's height */ protected abstract void setBounds(Widget columnObject, int left, int top, int width, int height); private int computeTop(int maxHeight, int desiredHeight) { return ((maxHeight - desiredHeight) / 2); } private int[] computeWeights(int[] weights, int numChildren) { if (weights.length != numChildren) { return checkWeights(weights, numChildren); } int[] realWeights = new int[numChildren]; int total = 100; for (int i = 0; i < realWeights.length; i++) { realWeights[i] = (int) (((double) weights[i]) / getSumOfAllWeights() * 100); total -= realWeights[i]; } int i = 0; while (total > 0) { ++realWeights[i]; --total; ++i; if (i >= realWeights.length) { i = 0; } } return realWeights; } /* * Compute and return a weights array where each weight is the percentage * the corresponding column should occupy of the entire control width. If * the elements in the supplied weights array add up to 100 and the length * of the array is the same as the number of columns, the supplied weights * array is used. Otherwise, this method computes and returns a weights * array that makes each column an equal size. * * @param weights * The default or user-supplied weights array. * @param numChildren * The number of child controls. * @return The weights array that will be used by the layout manager. */ private int[] checkWeights(int[] weights, int numChildren) { if (weights.length == numChildren) { int sum = 0; for (int i = 0; i < weights.length; i++) { sum += weights[i]; } if (sum == 100) { return weights; } } // Either the number of weights doesn't match or they don't add up. // Compute something sane and return that instead. int[] result = new int[numChildren]; int weight = 100 / numChildren; int extra = 100 % numChildren; for (int i = 0; i < result.length - 1; i++) { result[i] = weight; if (extra > 0) { result[i]++; extra--; } } result[numChildren - 1] = weight + extra; return result; } private int[] weights = new int[0]; /** * Method getWeights. If isFittingHorizontally, returns an array * representing the percentage of the total width each column is allocated * or null if no weights have been specified. * <p> * If !isFittingHorizontally, returns an array where each element is the * minimum width in pixels of the corresponding column. * * @return the current weights array or null if no weights have been * specified. */ public int[] getWeights() { return weights; } /** * Method setWeights. If isFittingHorizontally, specifies an array * representing the percentage of the total width each column is allocated * or null if no weights have been specified. * <p> * If !isFittingHorizontally, specifies an array where each element is the * minimum width in pixels of the corresponding column. * <p> * This property is ignored if the programmer has set a layout manager on * the header and/or the row prototype objects. * <p> * The number of elements in the array must match the number of columns and * if isFittingHorizontally, the sum of all elements must equal 100. If * either of these constraints is not true, this property will be ignored * and all columns will be created equal in width. * * @param weights * the weights to use if the CompositeTable is automatically * laying out controls. * @return this */ public AbstractGridRowLayout setWeights(int[] weights) { this.weights = weights; sumOfAllWeights = 0; for (int i = 0; i < weights.length; i++) { sumOfAllWeights += weights[i]; } return this; } private int sumOfAllWeights; /** * Returns the sum of all the weights in the weights property * * @return the sum of all the weights in the weights property */ public int getSumOfAllWeights() { return sumOfAllWeights; } private boolean fittingHorizontally = false; /** * Method isFittingHorizontally. Returns if the CompositeTable control will * scale the widths of all columns so that they all fit into the available * space. The default value is false. * * @return Returns true if the table's actual width is set to equal the * visible width; false otherwise. */ public boolean isFittingHorizontally() { return fittingHorizontally; } /** * Method setFittingHorizontally. Sets if the CompositeTable control will * scale the widths of all columns so that they all fit into the available * space. The default value is false. * * @param fittingHorizontally * true if the table's actual width is set to equal the visible * width; false otherwise. * @return this */ public AbstractGridRowLayout setFittingHorizontally(boolean fittingHorizontally) { this.fittingHorizontally = fittingHorizontally; return this; } }