More null checks.
[online-glom:gwt-glom.git] / src / main / java / org / glom / web / client / ui / details / DetailsCell.java
1 /*
2  * Copyright (C) 2011 Openismus GmbH
3  * Copyright (C) 2011 Ben Konrath <ben@bagu.org>
4  *
5  * This file is part of GWT-Glom.
6  *
7  * GWT-Glom is free software: you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as published by the
9  * Free Software Foundation, either version 3 of the License, or (at your
10  * option) any later version.
11  *
12  * GWT-Glom is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
15  * for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with GWT-Glom.  If not, see <http://www.gnu.org/licenses/>.
19  */
20
21 package org.glom.web.client.ui.details;
22
23 import java.io.UnsupportedEncodingException;
24
25 import org.glom.web.client.StringUtils;
26 import org.glom.web.client.Utils;
27 import org.glom.web.client.ui.OnlineGlomConstants;
28 import org.glom.web.shared.DataItem;
29 import org.glom.web.shared.libglom.NumericFormat;
30 import org.glom.web.shared.libglom.layout.Formatting;
31 import org.glom.web.shared.libglom.layout.LayoutItemField;
32
33 import com.google.gwt.core.client.GWT;
34 import com.google.gwt.dom.client.DivElement;
35 import com.google.gwt.dom.client.Document;
36 import com.google.gwt.dom.client.Style.Overflow;
37 import com.google.gwt.event.dom.client.ClickEvent;
38 import com.google.gwt.event.dom.client.ClickHandler;
39 import com.google.gwt.event.shared.HandlerRegistration;
40 import com.google.gwt.i18n.client.NumberFormat;
41 import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
42 import com.google.gwt.safehtml.shared.SafeHtmlUtils;
43 import com.google.gwt.user.client.ui.Button;
44 import com.google.gwt.user.client.ui.CheckBox;
45 import com.google.gwt.user.client.ui.Composite;
46 import com.google.gwt.user.client.ui.FlowPanel;
47 import com.google.gwt.user.client.ui.HasHorizontalAlignment;
48 import com.google.gwt.user.client.ui.Label;
49
50 /**
51  * Holds a label, data and a navigation button.
52  */
53 public class DetailsCell extends Composite {
54
55         // OnlineGlomConstants.java is generated in the target/ directory,
56         // from OnlineGlomConstants.properties
57         // by the gwt-maven-plugin's i18n (mvn:i18n) goal.
58         private OnlineGlomConstants constants = GWT.create(OnlineGlomConstants.class);
59
60         private LayoutItemField layoutItemField;
61         private FlowPanel detailsData = new FlowPanel();
62         private Label detailsLabel = new Label();
63         private DataItem dataItem;
64
65         private Button openButton = null;
66         private HandlerRegistration openButtonHandlerReg = null;
67
68         public DetailsCell(LayoutItemField layoutItemField) {
69                 // Labels (text in div element) are being used so that the height of the details-data element can be set for
70                 // the multiline height of LayoutItemFeilds. This allows the the data element to display the correct height
71                 // if style is applied that shows the height. This has the added benefit of allowing the order of the label and
72                 // data elements to be changed for right-to-left languages.
73
74                 Label detailsLabel = new Label(layoutItemField.getTitle() + ":");
75                 detailsLabel.setStyleName("details-label");
76
77                 detailsData.setStyleName("details-data");
78                 Formatting formatting = layoutItemField.getFormatting();
79                 if (formatting == null) {
80                         GWT.log("setData(): formatting is null");
81                         formatting = new Formatting(); // To avoid checks later.
82                 }
83
84                 // set the height based on the number of lines
85                 detailsData.setHeight(formatting.getTextFormatMultilineHeightLines() + "em");
86
87                 // set the alignment
88                 switch (formatting.getHorizontalAlignment()) {
89                 case HORIZONTAL_ALIGNMENT_LEFT:
90                         detailsLabel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LEFT);
91                         break;
92                 case HORIZONTAL_ALIGNMENT_RIGHT:
93                         detailsLabel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
94                         break;
95                 case HORIZONTAL_ALIGNMENT_AUTO:
96                 default:
97                         detailsLabel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_DEFAULT);
98                         break;
99                 }
100
101                 // set the text foreground and background colours
102                 String foregroundColour = formatting.getTextFormatColourForeground();
103                 if (!StringUtils.isEmpty(foregroundColour))
104                         detailsData.getElement().getStyle().setColor(foregroundColour);
105                 String backgroundColour = formatting.getTextFormatColourBackground();
106                 if (!StringUtils.isEmpty(backgroundColour))
107                         detailsData.getElement().getStyle().setBackgroundColor(backgroundColour);
108
109                 FlowPanel mainPanel = new FlowPanel();
110                 mainPanel.setStyleName("details-cell");
111
112                 mainPanel.add(detailsLabel);
113                 mainPanel.add(detailsData);
114
115                 if (layoutItemField.getAddNavigation()) {
116                         openButton = new Button(constants.open());
117                         openButton.setStyleName("details-navigation");
118                         openButton.setEnabled(false);
119                         mainPanel.add(openButton);
120                 }
121
122                 this.layoutItemField = layoutItemField;
123
124                 initWidget(mainPanel);
125         }
126
127         public DataItem getData() {
128                 return dataItem;
129         }
130
131         public void setData(final DataItem dataItem) {
132                 detailsData.clear();
133
134                 if (dataItem == null)
135                         return;
136
137                 Formatting formatting = layoutItemField.getFormatting();
138
139                 // FIXME use the cell renderers from the list view to render the inforamtion here
140                 switch (layoutItemField.getGlomType()) {
141                 case TYPE_BOOLEAN:
142                         final CheckBox checkBox = new CheckBox();
143                         checkBox.setValue(dataItem.getBoolean());
144                         checkBox.addClickHandler(new ClickHandler() {
145
146                                 @Override
147                                 public void onClick(ClickEvent event) {
148                                         // don't let users change the checkbox
149                                         checkBox.setValue(dataItem.getBoolean());
150                                 }
151                         });
152                         detailsData.add(checkBox);
153                         break;
154                 case TYPE_NUMERIC:
155                         if (formatting == null) {
156                                 GWT.log("setData(): formatting is null");
157                                 formatting = new Formatting(); // To avoid checks later.
158                         }
159                         NumericFormat numericFormat = formatting.getNumericFormat();
160                         NumberFormat gwtNumberFormat = Utils.getNumberFormat(numericFormat);
161
162                         // set the foreground color to red if the number is negative and this is requested
163                         if (numericFormat.getUseAltForegroundColorForNegatives() && dataItem.getNumber() < 0) {
164                                 // The default alternative color in libglom is red.
165                                 detailsData.getElement().getStyle().setColor(NumericFormat.getAlternativeColorForNegatives());
166                         }
167
168                         final String currencyCode = StringUtils.isEmpty(numericFormat.getCurrencySymbol()) ? "" : numericFormat
169                                         .getCurrencySymbol().trim() + " ";
170                         detailsLabel.setText(currencyCode + gwtNumberFormat.format(dataItem.getNumber()));
171                         detailsData.add(detailsLabel);
172                         break;
173                 case TYPE_DATE:
174                 case TYPE_TIME:
175                 case TYPE_TEXT:
176                         final String text = StringUtils.defaultString(dataItem.getText());
177
178                         // Deal with multiline text differently than single line text.
179                         if ((formatting != null) && (formatting.getTextFormatMultilineHeightLines() > 1)) {
180                                 detailsData.getElement().getStyle().setOverflow(Overflow.AUTO);
181                                 // Convert '\n' to <br/> escaping the data so that it won't be rendered as HTML.
182                                 try {
183                                         // JavaScript requires the charsetName to be "UTF-8". CharsetName values that work in Java (such as
184                                         // "UTF8") will not work when compiled to JavaScript.
185                                         String utf8NewLine = new String(new byte[] { 0x0A }, "UTF-8");
186                                         String[] lines = text.split(utf8NewLine);
187                                         SafeHtmlBuilder sb = new SafeHtmlBuilder();
188                                         for (String line : lines) {
189                                                 sb.append(SafeHtmlUtils.fromString(line));
190                                                 sb.append(SafeHtmlUtils.fromSafeConstant("<br/>"));
191                                         }
192
193                                         // Manually add the HTML to the detailsData container.
194                                         DivElement div = Document.get().createDivElement();
195                                         div.setInnerHTML(sb.toSafeHtml().asString());
196                                         detailsData.getElement().appendChild(div);
197
198                                         // Expand the width of detailsData if a vertical scrollbar has been placed on the inside of the
199                                         // detailsData container.
200                                         int scrollBarWidth = detailsData.getOffsetWidth() - div.getOffsetWidth();
201                                         if (scrollBarWidth > 0) {
202                                                 // A vertical scrollbar is on the inside.
203                                                 detailsData.setWidth((detailsData.getOffsetWidth() + scrollBarWidth + 4) + "px");
204                                         }
205
206                                         // TODO Add horizontal scroll bars when detailsData expands beyond its container.
207
208                                 } catch (UnsupportedEncodingException e) {
209                                         // If the new String() line throws an exception, don't try to add the <br/> tags. This is unlikely
210                                         // to happen but we should do something if it does.
211                                         detailsLabel.setText(text);
212                                         detailsData.add(detailsLabel);
213                                 }
214
215                         } else {
216                                 SingleLineText textPanel = new SingleLineText(text);
217                                 detailsData.add(textPanel);
218                         }
219                 default:
220                         break;
221                 }
222
223                 this.dataItem = dataItem;
224
225                 // enable the navigation button if it's safe
226                 if (openButton != null && openButtonHandlerReg != null && this.dataItem != null) {
227                         openButton.setEnabled(true);
228                 }
229
230         }
231
232         public LayoutItemField getLayoutItemField() {
233                 return layoutItemField;
234         }
235
236         public HandlerRegistration setOpenButtonClickHandler(ClickHandler clickHandler) {
237                 if (openButton != null) {
238                         openButtonHandlerReg = openButton.addClickHandler(clickHandler);
239                 }
240                 return openButtonHandlerReg;
241         }
242
243 }