More null checks.
[online-glom:gwt-glom.git] / src / main / java / org / glom / web / client / ui / details / FlowTable.java
1 /*
2  * Copyright (C) 2011 Openismus GmbH
3  *
4  * This file is part of GWT-Glom.
5  *
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.
10  *
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
14  * for more details.
15  *
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/>.
18  */
19
20 package org.glom.web.client.ui.details;
21
22 import java.util.ArrayList;
23
24 import org.glom.web.client.Utils;
25
26 import com.google.gwt.dom.client.Style.Unit;
27 import com.google.gwt.user.client.ui.Composite;
28 import com.google.gwt.user.client.ui.FlexTable;
29 import com.google.gwt.user.client.ui.FlowPanel;
30 import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
31 import com.google.gwt.user.client.ui.HTMLTable.ColumnFormatter;
32 import com.google.gwt.user.client.ui.HTMLTable.RowFormatter;
33 import com.google.gwt.user.client.ui.HasVerticalAlignment;
34 import com.google.gwt.user.client.ui.IsWidget;
35 import com.google.gwt.user.client.ui.Widget;
36
37 /**
38  * A container widget that implements the Glom details view flow table behaviour. Child widgets are arranged using the
39  * least vertical space in the specified number of columns.
40  */
41 public class FlowTable extends Composite {
42
43         // Represents an item to be inserted into the FlowTable. The primary reason for this class is to cache the vertical
44         // height of the widget being added to the FlowTable.
45         class FlowTableItem implements IsWidget {
46
47                 Widget widget;
48                 int height;
49
50                 @SuppressWarnings("unused")
51                 private FlowTableItem() {
52                         // disable default constructor
53                 }
54
55                 FlowTableItem(Widget widget) {
56                         height = Utils.getWidgetHeight(widget);
57                         this.widget = widget;
58                 }
59
60                 int getHeight() {
61                         return height;
62                 }
63
64                 /*
65                  * (non-Javadoc)
66                  * 
67                  * @see com.google.gwt.user.client.ui.IsWidget#asWidget()
68                  */
69                 @Override
70                 public Widget asWidget() {
71                         return widget;
72                 }
73         }
74
75         private FlexTable table = new FlexTable();
76         private ArrayList<FlowPanel> columns = new ArrayList<FlowPanel>();
77         private ArrayList<FlowTableItem> items = new ArrayList<FlowTableItem>();
78
79         @SuppressWarnings("unused")
80         private FlowTable() {
81                 // disable default constructor
82         }
83
84         public FlowTable(int columnCount) {
85                 // get the formatters
86                 CellFormatter cellFormatter = table.getFlexCellFormatter();
87                 ColumnFormatter columnFormatter = table.getColumnFormatter();
88                 RowFormatter rowFormater = table.getRowFormatter();
89
90                 // align the Cells to the top of the row
91                 rowFormater.setVerticalAlign(0, HasVerticalAlignment.ALIGN_TOP);
92
93                 // take up all available horizontal space and remove the border
94                 table.setWidth("100%");
95                 table.getElement().getStyle().setProperty("borderCollapse", "collapse");
96                 table.setBorderWidth(0);
97
98                 if (columnCount < 1) {
99                         columnCount = 1; // Avoid a division by zero.
100                 }
101
102                 // The column widths are evenly distributed amongst the number of columns with 1% padding between the columns.
103                 double columnWidth = (100 - (columnCount - 1)) / columnCount;
104                 for (int i = 0; i < columnCount; i++) {
105                         // create and add a column
106                         FlowPanel column = new FlowPanel();
107                         table.setWidget(0, i, column);
108
109                         // set the column with from the calucation above
110                         columnFormatter.setWidth(i, columnWidth + "%");
111
112                         // Add space between the columns.
113                         // Don't set the left padding on the first column.
114                         if (i != 0)
115                                 cellFormatter.getElement(0, i).getStyle().setPaddingLeft(0.5, Unit.PCT);
116                         // Don't set the right padding on the last column.
117                         if (i != columnCount - 1)
118                                 cellFormatter.getElement(0, i).getStyle().setPaddingRight(0.5, Unit.PCT);
119
120                         // TODO The style name should be placed on the column FlexTable when I add it. - Ben
121                         cellFormatter.addStyleName(0, i, "group-column");
122
123                         // Keep track of the columns so it can be accessed later
124                         columns.add(column);
125                 }
126
127                 initWidget(table);
128         }
129
130         /**
131          * Adds a Widget to the FlowTable. The layout of the child widgets is adjusted to minimize the vertical height of
132          * the entire FlowTable.
133          * 
134          * @param widget
135          *            widget to add to the FlowTable
136          */
137         public void add(Widget widget) {
138
139                 // keep track for the child items
140                 items.add(new FlowTableItem(widget));
141
142                 // Discover the total amount of minimum space needed by this container widget, by examining its child widgets,
143                 // by examining every possible sequential arrangement of the widgets in this fixed number of columns:
144                 int minColumnHeight = getMinimumColumnHeight(0, columns.size()); // This calls itself recursively.
145
146                 // Rearrange the widgets taking the newly added widget into account.
147                 int currentColumnIndex = 0;
148                 int currentColumnHeight = 0;
149                 FlowPanel currentColumn = columns.get(currentColumnIndex);
150                 for (FlowTableItem item : items) {
151                         if (currentColumnHeight + item.getHeight() > minColumnHeight) {
152                                 // Ensure that we never try to add widgets to an existing column. This shouldn't happen so it's just a
153                                 // precaution. TODO: log a message if columnNumber is greater than columns.size()
154                                 if (currentColumnIndex < columns.size() - 1) {
155                                         currentColumn = columns.get(++currentColumnIndex);
156                                         currentColumnHeight = 0;
157                                 }
158                         }
159                         currentColumn.add(item.asWidget()); // adding the widget to the column removes it from its current container
160                         currentColumnHeight += item.getHeight();
161                 }
162         }
163
164         /*
165          * Discover how best (least column height) to arrange these widgets in these columns, keeping them in sequence, and
166          * then say how high the columns must be.
167          * 
168          * This method was ported from the FlowTable class of Glom.
169          */
170         private int getMinimumColumnHeight(int startWidget, int columnCount) {
171
172                 if (columnCount == 1) {
173                         // Just add the heights together:
174                         int widgetsCount = items.size() - startWidget;
175                         return getColumnHeight(startWidget, widgetsCount);
176
177                 } else {
178                         // Try each combination of widgets in the first column, combined with the the other combinations in the
179                         // following columns:
180                         int minimumColumnHeight = 0;
181                         boolean atLeastOneCombinationChecked = false;
182
183                         int countItemsRemaining = items.size() - startWidget;
184
185                         for (int firstColumnWidgetsCount = 1; firstColumnWidgetsCount <= countItemsRemaining; firstColumnWidgetsCount++) {
186                                 int firstColumnHeight = getColumnHeight(startWidget, firstColumnWidgetsCount);
187                                 int minimumColumnHeightSoFar = firstColumnHeight;
188                                 int othersColumnStartWidget = startWidget + firstColumnWidgetsCount;
189
190                                 // Call this function recursively to get the minimum column height in the other columns, when these
191                                 // widgets are in the first column:
192                                 int minimumColumnHeightNextColumns = 0;
193                                 if (othersColumnStartWidget < items.size()) {
194                                         minimumColumnHeightNextColumns = getMinimumColumnHeight(othersColumnStartWidget, columnCount - 1);
195                                         minimumColumnHeightSoFar = Math.max(firstColumnHeight, minimumColumnHeightNextColumns);
196                                 }
197
198                                 // See whether this is better than the last one:
199                                 if (atLeastOneCombinationChecked) {
200                                         if (minimumColumnHeightSoFar < minimumColumnHeight) {
201                                                 minimumColumnHeight = minimumColumnHeightSoFar;
202                                         }
203                                 } else {
204                                         minimumColumnHeight = minimumColumnHeightSoFar;
205                                         atLeastOneCombinationChecked = true;
206                                 }
207                         }
208
209                         return minimumColumnHeight;
210                 }
211         }
212
213         private int getColumnHeight(int startWidget, int widgetCount) {
214                 // Just add the heights together:
215                 int columnHeight = 0;
216                 for (int i = startWidget; i < (startWidget + widgetCount); i++) {
217                         FlowTableItem item = items.get(i);
218                         int itemHeight = item.getHeight();
219                         columnHeight += itemHeight;
220                 }
221                 return columnHeight;
222         }
223
224 }