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.tree.view;
35
36 import info.magnolia.ui.admincentral.event.ItemEditedEvent;
37 import info.magnolia.ui.vaadin.grid.MagnoliaTreeTable;
38
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.Collection;
42 import java.util.Iterator;
43 import java.util.List;
44 import java.util.Set;
45
46 import com.vaadin.data.Container;
47 import com.vaadin.data.Item;
48 import com.vaadin.data.Property;
49 import com.vaadin.data.util.AbstractProperty;
50 import com.vaadin.event.Action;
51 import com.vaadin.event.Action.Handler;
52 import com.vaadin.event.FieldEvents;
53 import com.vaadin.event.FieldEvents.BlurEvent;
54 import com.vaadin.event.FieldEvents.FocusEvent;
55 import com.vaadin.event.ItemClickEvent;
56 import com.vaadin.event.ShortcutAction;
57 import com.vaadin.ui.AbstractTextField;
58 import com.vaadin.ui.Component;
59 import com.vaadin.ui.DefaultFieldFactory;
60 import com.vaadin.ui.Field;
61
62
63
64
65
66 public class InplaceEditingTreeTable extends MagnoliaTreeTable implements ItemClickEvent.ItemClickListener, ItemEditedEvent.Notifier {
67
68 private Object editingItemId;
69
70 private Object editingPropertyId;
71
72 private List<Object> editableColumns;
73
74 private ColumnGenerator bypassedColumnGenerator;
75
76 private final List<ItemEditedEvent.Handler> listeners = new ArrayList<ItemEditedEvent.Handler>();
77
78 public InplaceEditingTreeTable() {
79 super();
80 setEditable(true);
81 setTableFieldFactory(new InplaceEditingFieldFactory());
82 addListener(asItemClickListener());
83 getActionManager().addActionHandler(new EditingKeyboardHandler());
84 }
85
86
87
88 public void setEditableColumns(Object... editablePropertyIds) {
89 if (editableColumns != null) {
90 this.editableColumns.clear();
91 } else {
92 editableColumns = new ArrayList<Object>();
93 }
94 this.editableColumns.addAll(Arrays.asList(editablePropertyIds));
95 }
96
97
98
99
100
101
102
103 public void setEditing(Object itemId, Object propertyId) {
104 if (itemId != null && propertyId != null) {
105 if ((bypassedColumnGenerator = getColumnGenerator(propertyId)) != null) {
106 removeGeneratedColumn(propertyId);
107 }
108 } else {
109 if (bypassedColumnGenerator != null) {
110 addGeneratedColumn(editingPropertyId, bypassedColumnGenerator);
111 bypassedColumnGenerator = null;
112 }
113 focus();
114 }
115
116 this.editingItemId = itemId;
117 this.editingPropertyId = propertyId;
118 refreshRowCache();
119 requestRepaint();
120 }
121
122
123
124
125
126
127 private class InplaceEditingFieldFactory extends DefaultFieldFactory {
128
129 @Override
130 public Field createField(Container container, final Object itemId, final Object propertyId, Component uiContext) {
131
132
133 if (editableColumns.contains(propertyId) && itemId.equals(editingItemId) && propertyId.equals(editingPropertyId)) {
134
135 Field field = super.createField(container, itemId, propertyId, uiContext);
136
137
138 if (field instanceof AbstractTextField) {
139 final AbstractTextField tf = (AbstractTextField) field;
140 tf.addListener(new FieldEvents.FocusListener() {
141
142 @Override
143 public void focus(FocusEvent event) {
144 tf.setCursorPosition(tf.toString().length());
145 }
146 });
147
148 tf.addListener(new FieldEvents.BlurListener() {
149
150 @Override
151 public void blur(BlurEvent event) {
152 fireItemEditedEvent(getItemFromField(tf));
153 setEditing(null, null);
154 }
155 });
156 tf.focus();
157 }
158
159 return field;
160 }
161 return null;
162 }
163 }
164
165
166
167 @Override
168 public void addListener(ItemEditedEvent.Handler listener) {
169 listeners.add(listener);
170 }
171
172 @Override
173 public void removeListener(ItemEditedEvent.Handler listener) {
174 if (listeners.contains(listener)) {
175 listeners.remove(listener);
176 }
177 }
178
179 private void fireItemEditedEvent(Item item) {
180 if (item != null) {
181 ItemEditedEvent event = new ItemEditedEvent(item);
182 for (ItemEditedEvent.Handler listener : listeners) {
183 listener.onItemEdited(event);
184 }
185 }
186 }
187
188
189
190
191
192
193
194
195
196 private Item getItemFromField(Field source) {
197 if (source != null) {
198 Property property = source.getPropertyDataSource();
199 if (property != null && property instanceof AbstractProperty) {
200 Collection<?> listeners = ((AbstractProperty) property).getListeners(Property.ValueChangeEvent.class);
201 Iterator<?> iterator = listeners.iterator();
202 while (iterator.hasNext()) {
203 Object listener = iterator.next();
204 if (listener instanceof Item) {
205 return (Item) listener;
206 }
207 }
208 }
209 }
210 return null;
211 }
212
213
214
215 @Override
216 public void itemClick(ItemClickEvent event) {
217 if (event.isDoubleClick()) {
218 setEditing(event.getItemId(), event.getPropertyId());
219 }
220 }
221
222 private ItemClickEvent.ItemClickListener asItemClickListener() {
223 return this;
224 }
225
226
227
228
229
230
231 private class EditingKeyboardHandler implements Handler {
232
233 private final ShortcutAction enter = new ShortcutAction("Enter", ShortcutAction.KeyCode.ENTER, null);
234
235 private final ShortcutAction tabNext = new ShortcutAction("Tab", ShortcutAction.KeyCode.TAB, null);
236
237 private final ShortcutAction tabPrev = new ShortcutAction("Shift+Tab", ShortcutAction.KeyCode.TAB, new int[]{ShortcutAction.ModifierKey.SHIFT});
238
239 private final ShortcutAction escape = new ShortcutAction("Esc", ShortcutAction.KeyCode.ESCAPE, null);
240
241 @Override
242 public Action[] getActions(Object target, Object sender) {
243 return new Action[]{enter, tabNext, tabPrev, escape};
244 }
245
246 @Override
247 public void handleAction(Action action, Object sender, Object target) {
248
249
250
251
252
253 if (!(action instanceof ShortcutAction)) {
254 return;
255 }
256 ShortcutAction shortcut = (ShortcutAction) action;
257
258 if (target != InplaceEditingTreeTable.this && target instanceof Field) {
259 Field field = (Field) target;
260
261 if (shortcut == enter || shortcut.getKeyCode() == enter.getKeyCode()) {
262 fireItemEditedEvent(getItemFromField(field));
263 setEditing(null, null);
264
265 } else if (action == tabNext) {
266
267 fireItemEditedEvent(getItemFromField(field));
268
269
270 TableCell nextCell = getNextEditableCandidate(editingItemId, editingPropertyId);
271 setEditing(nextCell.getItemId(), nextCell.getPropertyId());
272
273 } else if (action == tabPrev) {
274
275 fireItemEditedEvent(getItemFromField(field));
276
277
278 TableCell previousCell = getPreviousEditableCandidate(editingItemId, editingPropertyId);
279 setEditing(previousCell.getItemId(), previousCell.getPropertyId());
280
281 } else if (action == escape) {
282 setEditing(null, null);
283 }
284 } else if (target == InplaceEditingTreeTable.this) {
285 if (getValue() == null) {
286 return;
287 }
288
289 if (shortcut == enter || shortcut.getKeyCode() == enter.getKeyCode()) {
290
291 Object firstSelectedId = getValue();
292 if (firstSelectedId instanceof Set && ((Set<?>) firstSelectedId).size() > 0) {
293 firstSelectedId = ((Set<?>) firstSelectedId).iterator().next();
294 }
295
296 Object propertyId = getVisibleColumns()[0];
297 if (!editableColumns.contains(propertyId)) {
298 propertyId = getNextEditableCandidate(firstSelectedId, propertyId).getPropertyId();
299 }
300 setEditing(firstSelectedId, propertyId);
301 }
302 }
303 }
304 }
305
306
307
308 private TableCell getNextEditableCandidate(Object itemId, Object propertyId) {
309
310 List<Object> visibleColumns = Arrays.asList(getVisibleColumns());
311 Object newItemId = itemId;
312 int newColumn = visibleColumns.indexOf(propertyId);
313 do {
314 if (newColumn == visibleColumns.size() - 1) {
315 newItemId = nextItemId(newItemId);
316 }
317 newColumn = (newColumn + 1) % visibleColumns.size();
318 } while (!editableColumns.contains(visibleColumns.get(newColumn)) && newItemId != null);
319
320 return new TableCell(newItemId, visibleColumns.get(newColumn));
321 }
322
323 private TableCell getPreviousEditableCandidate(Object itemId, Object propertyId) {
324
325 List<Object> visibleColumns = Arrays.asList(getVisibleColumns());
326 Object newItemId = itemId;
327 int newColumn = visibleColumns.indexOf(propertyId);
328 do {
329 if (newColumn == 0) {
330 newItemId = prevItemId(newItemId);
331 }
332 newColumn = (newColumn + visibleColumns.size() - 1) % visibleColumns.size();
333 } while (!editableColumns.contains(visibleColumns.get(newColumn)) && newItemId != null);
334
335 return new TableCell(newItemId, visibleColumns.get(newColumn));
336 }
337
338
339
340
341 private class TableCell {
342
343 private final Object itemId;
344
345 private final Object propertyId;
346
347 public TableCell(Object itemId, Object propertyId) {
348 this.itemId = itemId;
349 this.propertyId = propertyId;
350 }
351
352 public Object getItemId() {
353 return itemId;
354 }
355
356 public Object getPropertyId() {
357 return propertyId;
358 }
359 }
360
361 }