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.workbench.tree;
35
36 import info.magnolia.ui.vaadin.grid.MagnoliaTreeTable;
37 import info.magnolia.ui.workbench.list.ListViewImpl;
38
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.Collection;
42 import java.util.List;
43 import java.util.Set;
44
45 import com.vaadin.event.Action;
46 import com.vaadin.event.Action.Handler;
47 import com.vaadin.event.FieldEvents.BlurEvent;
48 import com.vaadin.event.FieldEvents.BlurListener;
49 import com.vaadin.event.ShortcutAction;
50 import com.vaadin.event.dd.DropHandler;
51 import com.vaadin.v7.data.Container;
52 import com.vaadin.v7.data.Property;
53 import com.vaadin.v7.event.ItemClickEvent;
54 import com.vaadin.v7.event.ItemClickEvent.ItemClickListener;
55 import com.vaadin.v7.ui.Field;
56 import com.vaadin.v7.ui.Table;
57 import com.vaadin.v7.ui.Table.ColumnGenerator;
58 import com.vaadin.v7.ui.Table.TableDragMode;
59 import com.vaadin.v7.ui.Tree.CollapseEvent;
60 import com.vaadin.v7.ui.Tree.CollapseListener;
61 import com.vaadin.v7.ui.Tree.ExpandEvent;
62 import com.vaadin.v7.ui.Tree.ExpandListener;
63 import com.vaadin.v7.ui.TreeTable;
64
65
66
67
68 public class TreeViewImpl extends ListViewImpl implements TreeView {
69
70 private TreeTable tree;
71
72 private boolean editable;
73 private final List<Object> editableColumns = new ArrayList<Object>();
74 private InplaceEditingFieldFactory fieldFactory;
75 private ExpandListener expandListener;
76 private CollapseListener collapseListener;
77 private Action.Container shortcutActionManager;
78 private EditingKeyboardHandler editingKeyboardHandler;
79 private ColumnGenerator bypassedColumnGenerator;
80 private TreeRowScroller rowScroller;
81
82 @Override
83 protected TreeTable createTable(Container container) {
84 return new MagnoliaTreeTable(container);
85 }
86
87 @Override
88 protected void initializeTable(Table table) {
89 super.initializeTable(table);
90 this.tree = (TreeTable) table;
91 rowScroller = new TreeRowScroller(tree);
92 collapseListener = new CollapsedNodeListener();
93 tree.addCollapseListener(collapseListener);
94 }
95
96 @Override
97 public void select(List<Object> itemIds) {
98 tree.setValue(null);
99
100 Object firstItemId = itemIds == null || itemIds.isEmpty() ? null : itemIds.get(0);
101 if (firstItemId == null) {
102 return;
103 }
104
105 tree.focus();
106
107 for (Object id : itemIds) {
108 tree.select(id);
109 }
110
111 rowScroller.bringRowIntoView(firstItemId);
112 }
113
114 @Override
115 public void expand(Object itemId) {
116 rowScroller.expandTreeToNode(itemId, true);
117 }
118
119 @Override
120 protected TreeView.Listener getListener() {
121 return (TreeView.Listener) super.getListener();
122 }
123
124 @Override
125 public TreeTable asVaadinComponent() {
126 return tree;
127 }
128
129 @Override
130 public void setEditable(boolean editable) {
131 if (editable) {
132
133 fieldFactory = new InplaceEditingFieldFactory();
134 fieldFactory.setFieldBlurListener(new BlurListener() {
135
136 @Override
137 public void blur(BlurEvent event) {
138 Object source = event.getSource();
139 if (source instanceof Field<?>) {
140 saveItemProperty(((Field<?>) source).getPropertyDataSource());
141 }
142 setEditing(null, null);
143 }
144 });
145 tree.setTableFieldFactory(fieldFactory);
146
147
148 expandListener = new ExpandListener() {
149
150 @Override
151 public void nodeExpand(ExpandEvent event) {
152 setEditing(null, null);
153 }
154 };
155
156 tree.addExpandListener(expandListener);
157
158
159 ItemClickListener clickListener = new ItemClickListener() {
160
161 @Override
162 public void itemClick(ItemClickEvent event) {
163 if (event.isDoubleClick()) {
164
165 tree.select(event.getItemId());
166 setEditing(event.getItemId(), event.getPropertyId());
167 }
168 }
169 };
170 tree.addItemClickListener(clickListener);
171
172
173 editingKeyboardHandler = new EditingKeyboardHandler(tree);
174 if (shortcutActionManager != null) {
175 shortcutActionManager.addActionHandler(editingKeyboardHandler);
176 }
177
178 } else {
179 tree.setTableFieldFactory(null);
180 fieldFactory = null;
181 tree.removeExpandListener(expandListener);
182 expandListener = null;
183 if (shortcutActionManager != null) {
184 shortcutActionManager.removeActionHandler(editingKeyboardHandler);
185 }
186 editingKeyboardHandler = null;
187 }
188
189 tree.setEditable(editable);
190 this.editable = editable;
191 }
192
193 @Override
194 public void setEditableColumns(Object... editablePropertyIds) {
195 editableColumns.clear();
196 editableColumns.addAll(Arrays.asList(editablePropertyIds));
197 }
198
199 private void setEditing(Object itemId, Object propertyId) {
200
201
202 if (bypassedColumnGenerator != null) {
203 tree.addGeneratedColumn(fieldFactory.getEditingPropertyId(), bypassedColumnGenerator);
204 bypassedColumnGenerator = null;
205 }
206
207 if (editable && editableColumns.contains(propertyId)) {
208 if (itemId == null || propertyId == null) {
209 tree.focus();
210 fieldFactory.setEditing(null, null);
211 } else {
212
213 if ((bypassedColumnGenerator = tree.getColumnGenerator(propertyId)) != null) {
214 tree.removeGeneratedColumn(propertyId);
215 }
216 fieldFactory.setEditing(itemId, propertyId);
217 }
218 } else {
219 fieldFactory.setEditing(null, null);
220 }
221 tree.refreshRowCache();
222 }
223
224 private void saveItemProperty(Property<?> propertyDataSource) {
225 getListener().onItemEdited(fieldFactory.getEditingItemId(), fieldFactory.getEditingPropertyId(), propertyDataSource);
226 }
227
228 @Override
229 public void setDragAndDropHandler(DropHandler dropHandler) {
230 if (dropHandler != null) {
231 tree.setDragMode(TableDragMode.MULTIROW);
232 tree.setDropHandler(dropHandler);
233 } else {
234 tree.setDragMode(TableDragMode.NONE);
235 tree.setDropHandler(null);
236 }
237 }
238
239
240
241 @Override
242 public void setActionManager(Action.Container shortcutActionManager) {
243 if (editable) {
244 shortcutActionManager.addActionHandler(editingKeyboardHandler);
245 }
246 this.shortcutActionManager = shortcutActionManager;
247 }
248
249 public void setSortable(boolean sortable) {
250 if (tree.getContainerDataSource() instanceof HierarchicalJcrContainer) {
251 tree.setSortEnabled(sortable);
252 ((HierarchicalJcrContainer) tree.getContainerDataSource()).setSortable(sortable);
253 }
254 }
255
256 private final class CollapsedNodeListener implements CollapseListener {
257
258 @Override
259 public void nodeCollapse(CollapseEvent event) {
260
261 if (editable) {
262 setEditing(null, null);
263 }
264 Object collapsedNodeId = event.getItemId();
265
266 unselectDescendants(collapsedNodeId);
267 }
268 }
269
270 private void unselectDescendants(final Object parentId) {
271 if (tree.isMultiSelect()) {
272 Set<Object> selectedIds = (Set<Object>) tree.getValue();
273 for (Object id : selectedIds) {
274 if (isDescendantOf(id, parentId)) {
275 tree.unselect(id);
276 }
277 }
278 }
279 }
280
281
282
283
284 boolean isDescendantOf(final Object itemId, final Object parentId) {
285 Container.Hierarchical container = tree.getContainerDataSource();
286 Object id = itemId;
287 while (!container.isRoot(id)) {
288 id = container.getParent(id);
289 if (id.equals(parentId)) {
290 return true;
291 }
292 }
293 return false;
294 }
295
296
297
298
299 private class EditingKeyboardHandler implements Handler {
300
301 private final ShortcutAction enter = new ShortcutAction("Enter", ShortcutAction.KeyCode.ENTER, null);
302
303 private final ShortcutAction tabNext = new ShortcutAction("Tab", ShortcutAction.KeyCode.TAB, null);
304
305 private final ShortcutAction tabPrev = new ShortcutAction("Shift+Tab", ShortcutAction.KeyCode.TAB, new int[] { ShortcutAction.ModifierKey.SHIFT });
306
307 private final ShortcutAction escape = new ShortcutAction("Esc", ShortcutAction.KeyCode.ESCAPE, null);
308
309 private final TreeTable tree;
310
311 public EditingKeyboardHandler(TreeTable tree) {
312 this.tree = tree;
313 }
314
315 @Override
316 public Action[] getActions(Object target, Object sender) {
317
318 return new Action[] { enter, tabNext, tabPrev, escape };
319 }
320
321 @Override
322 public void handleAction(Action action, Object sender, Object target) {
323
324
325
326
327
328 if (!(action instanceof ShortcutAction)) {
329 return;
330 }
331 ShortcutAction shortcut = (ShortcutAction) action;
332
333
334 if (tree == null || !tree.isAttached()) {
335 return;
336 }
337
338 if (target != tree && target instanceof Field) {
339 Object editingItemId = fieldFactory.getEditingItemId();
340 Object editingPropertyId = fieldFactory.getEditingPropertyId();
341 if (shortcut == enter || shortcut.getKeyCode() == enter.getKeyCode()) {
342 saveItemProperty(fieldFactory.getField().getPropertyDataSource());
343 setEditing(null, null);
344 tree.focus();
345 } else if (action == tabNext) {
346 saveItemProperty(fieldFactory.getField().getPropertyDataSource());
347 editNextCell(getSelectedItemId(editingItemId), editingPropertyId);
348 } else if (action == tabPrev) {
349 saveItemProperty(fieldFactory.getField().getPropertyDataSource());
350 editPreviousCell(getSelectedItemId(editingItemId), editingPropertyId);
351 } else if (action == escape) {
352 setEditing(null, null);
353 tree.focus();
354 }
355 } else if (target == tree) {
356 if (tree.getValue() == null) {
357 return;
358 }
359
360 if (shortcut == enter || shortcut.getKeyCode() == enter.getKeyCode()) {
361 editFirstCell();
362
363 }
364 }
365 }
366
367
368
369
370 private Object getSelectedItemId(Object defaultItemId) {
371 Object selectedItemId = null;
372 if (tree.getValue() instanceof Collection<?>) {
373 Collection<?> values = (Collection<?>) tree.getValue();
374 if (!values.isEmpty()) {
375 selectedItemId = values.iterator().next();
376 }
377 } else {
378 selectedItemId = tree.getValue();
379 }
380 return selectedItemId != null ? selectedItemId: defaultItemId;
381 }
382 }
383
384
385
386 private void editNextCell(Object itemId, Object propertyId) {
387 List<Object> visibleColumns = Arrays.asList(tree.getVisibleColumns());
388 Object newItemId = itemId;
389 Object columnId;
390 int newColumn = visibleColumns.indexOf(propertyId);
391 do {
392 if (newColumn == visibleColumns.size() - 1) {
393 newItemId = tree.nextItemId(newItemId);
394 }
395 newColumn = (newColumn + 1) % visibleColumns.size();
396 columnId = visibleColumns.get(newColumn);
397 } while (newItemId != null && !(editableColumns.contains(columnId) && tree.getItem(newItemId).getItemProperty(columnId) != null));
398
399 updateTreeEditingState(newItemId, columnId);
400 }
401
402 public void editPreviousCell(Object itemId, Object propertyId) {
403 List<Object> visibleColumns = Arrays.asList(tree.getVisibleColumns());
404 Object newItemId = itemId;
405 Object columnId;
406 int newColumn = visibleColumns.indexOf(propertyId);
407 do {
408 if (newColumn == 0) {
409 newItemId = tree.prevItemId(newItemId);
410 }
411 newColumn = (newColumn + visibleColumns.size() - 1) % visibleColumns.size();
412 columnId = visibleColumns.get(newColumn);
413 } while (newItemId != null && !(editableColumns.contains(columnId) && tree.getItem(newItemId).getItemProperty(columnId) != null));
414
415 updateTreeEditingState(newItemId, columnId);
416 }
417
418 public void editFirstCell() {
419
420
421 Object firstSelectedId = tree.getValue();
422 if (firstSelectedId instanceof Collection) {
423 if (((Collection<?>) firstSelectedId).size() > 0) {
424 firstSelectedId = ((Set<?>) firstSelectedId).iterator().next();
425 } else {
426 firstSelectedId = null;
427 }
428 }
429
430
431 Object propertyId = tree.getVisibleColumns()[0];
432 if (!editableColumns.contains(propertyId)) {
433 editNextCell(firstSelectedId, propertyId);
434 } else {
435 setEditing(firstSelectedId, propertyId);
436 }
437 }
438
439 private void updateTreeEditingState(Object itemId, Object columnId) {
440 if (!tree.isSelected(itemId) && itemId != null) {
441 select(Arrays.asList(itemId));
442 }
443 if (itemId == null) {
444 tree.focus();
445 }
446 setEditing(itemId, columnId);
447 }
448 }