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