2 * Copyright (C) 2011 Openismus GmbH
4 * This file is part of GWT-Glom.
6 * GWT-Glom is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
11 * GWT-Glom is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with GWT-Glom. If not, see <http://www.gnu.org/licenses/>.
20 package org.glom.web.client.ui.details;
22 import java.util.ArrayList;
24 import com.google.gwt.dom.client.Document;
25 import com.google.gwt.dom.client.Style.Float;
26 import com.google.gwt.dom.client.Style.Overflow;
27 import com.google.gwt.dom.client.Style.Unit;
28 import com.google.gwt.user.client.ui.ComplexPanel;
29 import com.google.gwt.user.client.ui.Composite;
30 import com.google.gwt.user.client.ui.FlowPanel;
31 import com.google.gwt.user.client.ui.Panel;
32 import com.google.gwt.user.client.ui.Widget;
35 * A container widget that implements the Glom details view flow table behaviour. Child widgets are arranged using the
36 * least vertical space in the specified number of columns.
38 * This class is currently implemented as a {@link Composite} widget. It would be more efficient to subclass
39 * {@link Panel} or {@link ComplexPanel} and implement this class at a lower level but we'd loose the ability to easily
40 * debug the code. This is something to consider when looking for optimisations.
42 * @author Ben Konrath <ben@bagu.org>
44 public class FlowTable extends Composite {
46 // Represents an item to be inserted into the FlowTable. The primary reason for this class is to cache the vertical
47 // height of the widget being added to the FlowTable.
52 @SuppressWarnings("unused")
53 private FlowTableItem() {
54 // disable default constructor
57 FlowTableItem(Widget widget) {
58 // Get the vertical height with decorations by temporarily adding the widget to the body element of the
59 // document in a transparent container. This is required because the size information is only available when
60 // the widget is attached to the DOM. The size information must be obtained before the widget is added to
61 // column because adding a widget to a container automatically removes it from the previous container.
62 Document doc = Document.get();
63 com.google.gwt.dom.client.Element div = doc.createDivElement();
64 div.getStyle().setOpacity(0.0);
65 div.appendChild(widget.getElement().<com.google.gwt.user.client.Element> cast());
66 doc.getBody().appendChild(div);
67 height = widget.getOffsetHeight();
68 doc.getBody().removeChild(div);
81 private FlowPanel mainPanel = new FlowPanel();
82 private ArrayList<FlowPanel> columns = new ArrayList<FlowPanel>();
83 private ArrayList<FlowTableItem> items = new ArrayList<FlowTableItem>();
85 @SuppressWarnings("unused")
87 // disable default constructor
90 public FlowTable(int columnCount) {
91 // set the overflow properly so that the columns can be arranged properly
92 mainPanel.getElement().getStyle().setOverflow(Overflow.HIDDEN);
93 mainPanel.getElement().getStyle().setWidth(100, Unit.PCT);
96 for (int i = 0; i < columnCount; i++) {
97 FlowPanel column = new FlowPanel();
98 column.getElement().getStyle().setFloat(Float.LEFT);
99 // The columns widths are evenly distributed but this doens't match how the flow table layout works in Glom.
100 // TODO This might need to be fixed to match Glom.
101 column.getElement().getStyle().setWidth(100 / columnCount, Unit.PCT);
103 mainPanel.add(column);
106 initWidget(mainPanel);
110 * Adds a Widget to the FlowTable. The layout of the child widgets is adjusted to minimize the vertical height of
111 * the entire FlowTable.
114 * widget to add to the FlowTable
116 public void add(Widget widget) {
118 // keep track for the child items
119 items.add(new FlowTableItem(widget));
121 // Discover the total amount of minimum space needed by this container widget, by examining its child widgets,
122 // by examining every possible sequential arrangement of the widgets in this fixed number of columns:
123 int minColumnHeight = getMinimumColumnHeight(0, columns.size()); // This calls itself recursively.
125 // Rearrange the widgets taking the newly added widget into account.
126 int currentColumnIndex = 0;
127 int currentColumnHeight = 0;
128 FlowPanel currentColumn = columns.get(currentColumnIndex);
129 for (FlowTableItem item : items) {
130 if (currentColumnHeight + item.getHeight() > minColumnHeight) {
131 // Ensure that we never try to add widgets to an existing column. This shouldn't happen so it's just a
132 // precaution. TODO: log a message if columnNumber is greater than columns.size()
133 if (currentColumnIndex < columns.size() - 1) {
134 currentColumn = columns.get(++currentColumnIndex);
135 currentColumnHeight = 0;
138 currentColumn.add(item.getWidget()); // adding the widget to the column removes it from its current
140 currentColumnHeight += item.getHeight();
145 * Discover how best (least column height) to arrange these widgets in these columns, keeping them in sequence, and
146 * then say how high the columns must be.
148 * This method was ported from the FlowTable class of Glom.
150 private int getMinimumColumnHeight(int startWidget, int columnCount) {
152 if (columnCount == 1) {
153 // Just add the heights together:
154 int widgetsCount = items.size() - startWidget;
155 return getColumnHeight(startWidget, widgetsCount);
158 // Try each combination of widgets in the first column, combined with the the other combinations in the
159 // following columns:
160 int minimumColumnHeight = 0;
161 boolean atLeastOneCombinationChecked = false;
163 int countItemsRemaining = items.size() - startWidget;
165 for (int firstColumnWidgetsCount = 1; firstColumnWidgetsCount <= countItemsRemaining; firstColumnWidgetsCount++) {
166 int firstColumnHeight = getColumnHeight(startWidget, firstColumnWidgetsCount);
167 int minimumColumnHeightSoFar = firstColumnHeight;
168 int othersColumnStartWidget = startWidget + firstColumnWidgetsCount;
170 // Call this function recursively to get the minimum column height in the other columns, when these
171 // widgets are in the first column:
172 int minimumColumnHeightNextColumns = 0;
173 if (othersColumnStartWidget < items.size()) {
174 minimumColumnHeightNextColumns = getMinimumColumnHeight(othersColumnStartWidget, columnCount - 1);
175 minimumColumnHeightSoFar = Math.max(firstColumnHeight, minimumColumnHeightNextColumns);
178 // See whether this is better than the last one:
179 if (atLeastOneCombinationChecked) {
180 if (minimumColumnHeightSoFar < minimumColumnHeight) {
181 minimumColumnHeight = minimumColumnHeightSoFar;
184 minimumColumnHeight = minimumColumnHeightSoFar;
185 atLeastOneCombinationChecked = true;
189 return minimumColumnHeight;
193 private int getColumnHeight(int startWidget, int widgetCount) {
194 // Just add the heights together:
195 int columnHeight = 0;
196 for (int i = startWidget; i < (startWidget + widgetCount); i++) {
197 FlowTableItem item = items.get(i);
198 int itemHeight = item.getHeight();
199 columnHeight += itemHeight;