1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.ui.admincentral.shellapp.pulse.message;
35
36 import static info.magnolia.ui.admincentral.shellapp.pulse.message.PulseMessagesPresenter.*;
37
38 import info.magnolia.i18nsystem.SimpleTranslator;
39 import info.magnolia.ui.admincentral.shellapp.pulse.message.PulseMessageCategoryNavigator.CategoryChangedEvent;
40 import info.magnolia.ui.admincentral.shellapp.pulse.message.PulseMessageCategoryNavigator.MessageCategory;
41 import info.magnolia.ui.admincentral.shellapp.pulse.message.PulseMessageCategoryNavigator.MessageCategoryChangedListener;
42 import info.magnolia.ui.api.message.MessageType;
43 import info.magnolia.ui.api.shell.Shell;
44 import info.magnolia.ui.vaadin.grid.MagnoliaTreeTable;
45 import info.magnolia.ui.vaadin.icon.ErrorIcon;
46 import info.magnolia.ui.vaadin.icon.InfoIcon;
47 import info.magnolia.ui.vaadin.icon.WarningIcon;
48 import info.magnolia.ui.workbench.column.DateColumnFormatter;
49
50 import java.util.Collection;
51 import java.util.HashSet;
52 import java.util.Set;
53
54 import javax.inject.Inject;
55
56 import org.apache.commons.lang.StringEscapeUtils;
57
58 import com.vaadin.data.Container;
59 import com.vaadin.data.Container.ItemSetChangeEvent;
60 import com.vaadin.data.Item;
61 import com.vaadin.data.Property;
62 import com.vaadin.data.Property.ValueChangeEvent;
63 import com.vaadin.data.Property.ValueChangeListener;
64 import com.vaadin.event.ItemClickEvent;
65 import com.vaadin.shared.ui.label.ContentMode;
66 import com.vaadin.ui.CustomComponent;
67 import com.vaadin.ui.HasComponents;
68 import com.vaadin.ui.Label;
69 import com.vaadin.ui.Table;
70 import com.vaadin.ui.Table.GeneratedRow;
71 import com.vaadin.ui.TreeTable;
72 import com.vaadin.ui.VerticalLayout;
73
74
75
76
77 public final class PulseMessagesViewImpl extends CustomComponent implements PulseMessagesView {
78
79 private static final String[] order = new String[] { NEW_PROPERTY_ID, TYPE_PROPERTY_ID, TEXT_PROPERTY_ID, SENDER_PROPERTY_ID, DATE_PROPERTY_ID };
80
81 private final String[] headers;
82
83 private final TreeTable messageTable = new MagnoliaTreeTable();
84
85 private final VerticalLayout root = new VerticalLayout();
86
87 private final PulseMessageCategoryNavigator navigator;
88
89 private final SimpleTranslator i18n;
90
91 private PulseMessagesView.Listener listener;
92
93 private Label emptyPlaceHolder;
94
95 private PulseMessagesFooter footer;
96
97 private MessageCategory currentlySelectedCategory = MessageCategory.ALL;
98
99 private boolean categoryFilterAlreadyApplied;
100
101 @Inject
102 public PulseMessagesViewImpl(Shell shell, SimpleTranslator i18n) {
103 this.i18n = i18n;
104 headers = new String[] { i18n.translate("pulse.messages.new"), i18n.translate("pulse.messages.type"), i18n.translate("pulse.messages.text"), i18n.translate("pulse.messages.sender"), i18n.translate("pulse.messages.date") };
105 footer = new PulseMessagesFooter(messageTable, i18n);
106 navigator = new PulseMessageCategoryNavigator(i18n);
107 setSizeFull();
108 root.setSizeFull();
109 setCompositionRoot(root);
110 construct();
111 }
112
113 @Override
114 public void setListener(PulseMessagesView.Listener listener) {
115 this.listener = listener;
116 this.footer.setListener(listener);
117 }
118
119 @Override
120 public void setDataSource(Container dataSource) {
121 messageTable.setContainerDataSource(dataSource);
122 messageTable.setVisibleColumns(order);
123 messageTable.setSortContainerPropertyId(DATE_PROPERTY_ID);
124 messageTable.setSortAscending(false);
125 messageTable.setColumnHeaders(headers);
126 }
127
128 private void construct() {
129 root.addComponent(navigator);
130 navigator.addCategoryChangeListener(new MessageCategoryChangedListener() {
131
132 @Override
133 public void messageCategoryChanged(CategoryChangedEvent event) {
134 final MessageCategory category = event.getCategory();
135 currentlySelectedCategory = category;
136 listener.filterByMessageCategory(category);
137 categoryFilterAlreadyApplied = true;
138
139 for (String id : (Set<String>) messageTable.getValue()) {
140 messageTable.unselect(id);
141 }
142 if (category == MessageCategory.ALL) {
143 navigator.showGroupByType(true);
144 } else {
145 navigator.showGroupByType(false);
146 }
147 refresh();
148 }
149 });
150
151 constructTable();
152 root.addComponent(footer);
153
154 emptyPlaceHolder = new Label();
155 emptyPlaceHolder.setContentMode(ContentMode.HTML);
156 emptyPlaceHolder.setValue(String.format("<span class=\"icon-pulse\"></span><div class=\"message\">%s</div>", i18n.translate("pulse.messages.empty")));
157 emptyPlaceHolder.addStyleName("emptyplaceholder");
158
159 root.addComponent(emptyPlaceHolder);
160 setComponentVisibility(messageTable.getContainerDataSource());
161
162 }
163
164 private void constructTable() {
165 root.addComponent(messageTable);
166 root.setExpandRatio(messageTable, 1f);
167 messageTable.setSizeFull();
168 messageTable.addStyleName("message-table");
169 messageTable.setSelectable(true);
170 messageTable.setMultiSelect(true);
171 messageTable.addGeneratedColumn(NEW_PROPERTY_ID, newMessageColumnGenerator);
172 messageTable.setColumnWidth(NEW_PROPERTY_ID, 100);
173 messageTable.addGeneratedColumn(TYPE_PROPERTY_ID, typeColumnGenerator);
174 messageTable.setColumnWidth(TYPE_PROPERTY_ID, 50);
175 messageTable.addGeneratedColumn(TEXT_PROPERTY_ID, textColumnGenerator);
176 messageTable.setColumnWidth(TEXT_PROPERTY_ID, 450);
177 messageTable.addGeneratedColumn(DATE_PROPERTY_ID, new DateColumnFormatter(null));
178 messageTable.setColumnWidth(DATE_PROPERTY_ID, 150);
179 messageTable.setRowGenerator(groupingRowGenerator);
180
181 navigator.addGroupingListener(groupingListener);
182
183 messageTable.addItemClickListener(new ItemClickEvent.ItemClickListener() {
184 @Override
185 public void itemClick(ItemClickEvent event) {
186 final String itemId = (String) event.getItemId();
187
188 if (itemId.startsWith(GROUP_PLACEHOLDER_ITEMID)) {
189 return;
190 }
191 if (event.isDoubleClick()) {
192 listener.onMessageClicked(itemId);
193 } else {
194 if (messageTable.isSelected(itemId)) {
195 messageTable.unselect(itemId);
196 }
197 }
198 }
199 });
200
201 messageTable.addValueChangeListener(selectionListener);
202 messageTable.addItemSetChangeListener(new Container.ItemSetChangeListener() {
203 @Override
204 public void containerItemSetChange(ItemSetChangeEvent event) {
205 setComponentVisibility(event.getContainer());
206 }
207 });
208 }
209
210 private void setComponentVisibility(Container container) {
211 boolean isEmptyList = container.getItemIds().size() == 0;
212 if (isEmptyList) {
213 root.setExpandRatio(emptyPlaceHolder, 1f);
214
215
216 root.setExpandRatio(messageTable, 0f);
217 root.setExpandRatio(footer, 0f);
218 } else {
219 root.setExpandRatio(emptyPlaceHolder, 0f);
220 root.setExpandRatio(messageTable, 1f);
221 root.setExpandRatio(footer, .1f);
222 }
223
224 messageTable.setVisible(!isEmptyList);
225 footer.setVisible(!isEmptyList);
226 emptyPlaceHolder.setVisible(isEmptyList);
227 }
228
229 private Property.ValueChangeListener selectionListener = new Property.ValueChangeListener() {
230
231 private Set<Object> prevSelected = new HashSet<Object>();
232
233 @Override
234 public void valueChange(ValueChangeEvent event) {
235
236
237
238
239 messageTable.removeValueChangeListener(this);
240
241 @SuppressWarnings("unchecked")
242 Set<Object> currSelected = new HashSet<Object>((Set<Object>) event.getProperty().getValue());
243 Set<Object> added = new HashSet<Object>(currSelected);
244 Set<Object> removed = new HashSet<Object>(prevSelected);
245
246 added.removeAll(prevSelected);
247 removed.removeAll(currSelected);
248
249
250 prevSelected = currSelected;
251
252
253
254
255
256 selectChildren(added, true);
257 selectChildren(removed, false);
258
259
260 for (Object child : removed) {
261 Object parent = listener.getParent(child);
262 if (parent != null) {
263 messageTable.unselect(parent);
264 prevSelected.remove(parent);
265 }
266 }
267
268
269
270
271 for (Object child : added) {
272 Object parent = listener.getParent(child);
273 if (isAllChildrenSelected(parent)) {
274 messageTable.select(parent);
275 prevSelected.add(parent);
276 } else {
277 messageTable.unselect(parent);
278 prevSelected.remove(parent);
279 }
280 }
281
282 messageTable.addValueChangeListener(this);
283 footer.updateStatus();
284 }
285
286 private boolean isAllChildrenSelected(Object parent) {
287 if (parent == null) {
288 return false;
289 }
290
291 Collection<?> siblings = listener.getGroup(parent);
292 boolean allSelected = true;
293
294 if (siblings != null) {
295 for (Object sibling : siblings) {
296 if (!messageTable.isSelected(sibling)) {
297 allSelected = false;
298 }
299 }
300 } else {
301 return false;
302 }
303
304 return allSelected;
305 }
306
307 private void selectChildren(Set<Object> parents, boolean check) {
308 for (Object parent : parents) {
309 Collection<?> group = listener.getGroup(parent);
310 if (group != null) {
311 for (Object child : group) {
312 if (check) {
313 messageTable.select(child);
314 prevSelected.add(child);
315 } else {
316 messageTable.unselect(child);
317 prevSelected.remove(child);
318 }
319 }
320 }
321 }
322 }
323 };
324
325 private ValueChangeListener groupingListener = new ValueChangeListener() {
326
327 @Override
328 public void valueChange(ValueChangeEvent event) {
329 boolean checked = event.getProperty().getValue().equals(Boolean.TRUE);
330 doGrouping(checked);
331 }
332 };
333
334
335
336
337 private Table.RowGenerator groupingRowGenerator = new Table.RowGenerator() {
338
339 @Override
340 public GeneratedRow generateRow(Table table, Object itemId) {
341
342
343
344
345
346
347 if (itemId.toString().startsWith(GROUP_PLACEHOLDER_ITEMID)) {
348 Item item = table.getItem(itemId);
349 Property<MessageType> property = item.getItemProperty(TYPE_PROPERTY_ID);
350 GeneratedRow generated = new GeneratedRow();
351
352 switch (property.getValue()) {
353 case ERROR:
354 generated.setText("", "", i18n.translate("pulse.messages.errors"));
355 break;
356 case WARNING:
357 generated.setText("", "", i18n.translate("pulse.messages.warnings"));
358 break;
359 case INFO:
360 generated.setText("", "", i18n.translate("pulse.messages.info"));
361 break;
362 case WORKITEM:
363 generated.setText("", "", i18n.translate("pulse.messages.workitems"));
364 break;
365 }
366 return generated;
367 }
368
369 return null;
370 }
371 };
372
373 private Table.ColumnGenerator newMessageColumnGenerator = new Table.ColumnGenerator() {
374
375 @Override
376 public Object generateCell(Table source, Object itemId, Object columnId) {
377
378 if (NEW_PROPERTY_ID.equals(columnId)) {
379 final Property<Boolean> newProperty = source.getContainerProperty(itemId, columnId);
380 final Boolean isNew = newProperty != null && newProperty.getValue();
381 if (isNew) {
382 final Label newMessage = new Label();
383 newMessage.setSizeUndefined();
384 newMessage.addStyleName("icon-tick");
385 newMessage.addStyleName("new-message");
386 return newMessage;
387 }
388 }
389 return null;
390 }
391 };
392
393
394
395
396 Table.ColumnGenerator textColumnGenerator = new Table.ColumnGenerator() {
397
398 @Override
399 public Object generateCell(Table source, Object itemId, Object columnId) {
400
401 if (TEXT_PROPERTY_ID.equals(columnId)) {
402 final Property<String> text = source.getContainerProperty(itemId, columnId);
403 final Property<String> subject = source.getContainerProperty(itemId, SUBJECT_PROPERTY_ID);
404
405 final Label textLabel = new Label();
406 textLabel.setSizeUndefined();
407 textLabel.addStyleName("message-subject-text");
408 textLabel.setContentMode(ContentMode.HTML);
409 textLabel.setValue("<strong>" + StringEscapeUtils.escapeXml(subject.getValue()) + "</strong><div>" + StringEscapeUtils.escapeXml(text.getValue()) + "</div>");
410
411 return textLabel;
412
413 }
414 return null;
415 }
416 };
417
418 private Table.ColumnGenerator typeColumnGenerator = new Table.ColumnGenerator() {
419
420 @Override
421 public Object generateCell(Table source, Object itemId, Object columnId) {
422
423 if (TYPE_PROPERTY_ID.equals(columnId)) {
424 final Property<MessageType> typeProperty = source.getContainerProperty(itemId, columnId);
425 final MessageType messageType = typeProperty.getValue();
426
427 switch (messageType) {
428 case INFO:
429 return new InfoIcon();
430
431 case WARNING:
432 return new WarningIcon();
433
434 case ERROR:
435 return new ErrorIcon();
436
437 case WORKITEM:
438
439 final Label messageTypeIcon = new Label();
440 messageTypeIcon.setSizeUndefined();
441 messageTypeIcon.addStyleName("icon");
442 messageTypeIcon.addStyleName("message-type");
443 messageTypeIcon.addStyleName("icon-work-item");
444 return messageTypeIcon;
445
446 }
447 }
448 return null;
449 }
450 };
451
452 @Override
453 public HasComponents asVaadinComponent() {
454 return this;
455 }
456
457 @Override
458 public void refresh() {
459
460 if (currentlySelectedCategory != MessageCategory.ALL && !categoryFilterAlreadyApplied) {
461 listener.filterByMessageCategory(currentlySelectedCategory);
462 }
463
464 categoryFilterAlreadyApplied = false;
465 footer.updateStatus();
466 messageTable.sort();
467 doGrouping(false);
468 }
469
470 @Override
471 public void updateCategoryBadgeCount(MessageCategory category, int count) {
472 navigator.updateCategoryBadgeCount(category, count);
473 }
474
475 private void doGrouping(boolean checked) {
476 listener.setGrouping(checked);
477
478 if (checked) {
479 for (Object itemId : messageTable.getItemIds()) {
480 messageTable.setCollapsed(itemId, false);
481 }
482 }
483 }
484
485 }