1 | /** |
2 | * Copyright 2005-2011 Steve McDuff d-duff@users.sourceforge.net |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | package org.deduced.viewer.web.shared; |
17 | |
18 | import java.util.ArrayList; |
19 | import java.util.List; |
20 | |
21 | import com.google.gwt.event.dom.client.KeyCodes; |
22 | import com.google.gwt.event.dom.client.KeyDownEvent; |
23 | import com.google.gwt.event.dom.client.KeyDownHandler; |
24 | import com.google.gwt.event.logical.shared.CloseEvent; |
25 | import com.google.gwt.event.logical.shared.CloseHandler; |
26 | import com.google.gwt.event.logical.shared.OpenEvent; |
27 | import com.google.gwt.event.logical.shared.OpenHandler; |
28 | import com.google.gwt.event.logical.shared.SelectionEvent; |
29 | import com.google.gwt.event.logical.shared.SelectionHandler; |
30 | import com.google.gwt.user.client.rpc.AsyncCallback; |
31 | import com.google.gwt.user.client.ui.Tree; |
32 | import com.google.gwt.user.client.ui.TreeItem; |
33 | import com.google.gwt.user.client.ui.UIObject; |
34 | |
35 | /** |
36 | * Tree Model |
37 | * |
38 | * @author Steve McDuff |
39 | * |
40 | */ |
41 | public class TreeModel extends WidgetModel implements OpenHandler<TreeItem>, |
42 | CloseHandler<TreeItem>, SelectionHandler<TreeItem>, ModelContainer, |
43 | KeyDownHandler |
44 | { |
45 | |
46 | /** |
47 | * serialVersionUID |
48 | */ |
49 | private static final long serialVersionUID = 4971990822443444267L; |
50 | |
51 | /** |
52 | * the ordered child tree item model list. |
53 | */ |
54 | private OrderedListModel<OrderedTreeItemModel> treeItemList; |
55 | |
56 | /** |
57 | * the list of selected tree item models based on their unique IDs. |
58 | */ |
59 | private StringListModel selectedUserInterfaceElementList; |
60 | |
61 | /** |
62 | * TreeModel constructor |
63 | */ |
64 | public TreeModel() |
65 | { |
66 | |
67 | } |
68 | |
69 | /** |
70 | * set Tree Item List |
71 | * |
72 | * @param newChildItemList the new child item list |
73 | */ |
74 | public void setTreeItemList( |
75 | OrderedListModel<OrderedTreeItemModel> newChildItemList) |
76 | { |
77 | treeItemList = newChildItemList; |
78 | } |
79 | |
80 | /** |
81 | * set Selected User Interface Element List |
82 | * |
83 | * @param newChildItemList the new selection list |
84 | */ |
85 | public void setSelectedUserInterfaceElementList( |
86 | StringListModel newChildItemList) |
87 | { |
88 | selectedUserInterfaceElementList = newChildItemList; |
89 | } |
90 | |
91 | /** |
92 | * get Tree |
93 | * |
94 | * @return the tree widget |
95 | */ |
96 | public Tree getTree() |
97 | { |
98 | return (Tree) getUIObject(); |
99 | } |
100 | |
101 | /** |
102 | * (non-JSDoc) |
103 | * |
104 | * @see org.deduced.viewer.web.shared.WidgetModel#createUIObject() |
105 | */ |
106 | @Override |
107 | public UIObject createUIObject() |
108 | { |
109 | Tree tree = new Tree(); |
110 | tree.addKeyDownHandler(this); |
111 | |
112 | tree.addOpenHandler(this); |
113 | tree.addCloseHandler(this); |
114 | tree.addSelectionHandler(this); |
115 | return tree; |
116 | } |
117 | |
118 | /** |
119 | * (non-JSDoc) |
120 | * |
121 | * @see org.deduced.viewer.web.shared.Model#registerChildModels() |
122 | */ |
123 | @Override |
124 | protected void registerChildModels() |
125 | { |
126 | registerChildModel(getTreeItemList(), this); |
127 | registerChildModel(getSelectedUserInterfaceElementList(), this); |
128 | super.registerChildModels(); |
129 | } |
130 | |
131 | /** |
132 | * (non-JSDoc) |
133 | * |
134 | * @see org.deduced.viewer.web.shared.Model#deleteChildModels() |
135 | */ |
136 | @Override |
137 | protected void deleteChildModels() |
138 | { |
139 | deleteSelectedUserInterfaceElementList(); |
140 | deleteTreeItemList(); |
141 | super.deleteChildModels(); |
142 | } |
143 | |
144 | /** |
145 | * delete selected User Interface Element List |
146 | */ |
147 | private void deleteSelectedUserInterfaceElementList() |
148 | { |
149 | deleteChildModel(getSelectedUserInterfaceElementList()); |
150 | setSelectedUserInterfaceElementList(null); |
151 | } |
152 | |
153 | /** |
154 | * delete Tree Item List |
155 | */ |
156 | private void deleteTreeItemList() |
157 | { |
158 | deleteChildModel(getTreeItemList()); |
159 | setTreeItemList(null); |
160 | } |
161 | |
162 | /** |
163 | * (non-JSDoc) |
164 | * |
165 | * @see org.deduced.viewer.web.shared.UserInterfaceModel#internalSynchronizeWithUI() |
166 | */ |
167 | @Override |
168 | public void internalSynchronizeWithUI() |
169 | { |
170 | synchronizeChildTreeItemList(); |
171 | |
172 | synchronizeSelectedTreeItem(); |
173 | |
174 | super.internalSynchronizeWithUI(); |
175 | } |
176 | |
177 | /** |
178 | * synchronize the Selected Tree Item based on the model |
179 | */ |
180 | private void synchronizeSelectedTreeItem() |
181 | { |
182 | Tree tree = getTree(); |
183 | TreeItem selectedItem = tree.getSelectedItem(); |
184 | TreeItemModel selectedModel = null; |
185 | if (selectedItem != null) |
186 | { |
187 | selectedModel = (TreeItemModel) selectedItem.getUserObject(); |
188 | } |
189 | |
190 | TreeItemModel newSelectedModel = null; |
191 | if (getSelectedUserInterfaceElementList() != null) |
192 | { |
193 | List<String> selectionList = |
194 | getSelectedUserInterfaceElementList().getList(); |
195 | int size = selectionList.size(); |
196 | if (size == 1) |
197 | { |
198 | Model associatedModel = |
199 | getModelRegistry().getModel(selectionList.get(0)); |
200 | if (associatedModel instanceof TreeItemModel) |
201 | { |
202 | newSelectedModel = (TreeItemModel) associatedModel; |
203 | } |
204 | } |
205 | else if (size != 0) |
206 | { |
207 | StringBuilder errorText = new StringBuilder(); |
208 | errorText |
209 | .append("Tree selection list size isn't between 0 and 1 : size : "); |
210 | errorText.append(size); |
211 | errorText.append(". Content : "); |
212 | for (String string : selectionList) |
213 | { |
214 | errorText.append("\""); |
215 | errorText.append(string); |
216 | errorText.append("\""); |
217 | } |
218 | getModelRegistry().showErrorLabel(errorText.toString()); |
219 | } |
220 | } |
221 | |
222 | if (newSelectedModel != selectedModel) |
223 | { |
224 | TreeItem newSelectedTreeItem = null; |
225 | if (newSelectedModel != null) |
226 | { |
227 | newSelectedTreeItem = newSelectedModel.getTreeItem(); |
228 | } |
229 | tree.setSelectedItem(newSelectedTreeItem, false); |
230 | } |
231 | } |
232 | |
233 | /** |
234 | * synchronize Child Tree Item List if necessary |
235 | */ |
236 | private void synchronizeChildTreeItemList() |
237 | { |
238 | if (!isUISynchronized()) |
239 | { |
240 | rebuildChildTreeItemList(); |
241 | } |
242 | } |
243 | |
244 | /** |
245 | * test if the UI is Synchronized based on the child tree item model list |
246 | * |
247 | * @return true if the UI is synchronized |
248 | */ |
249 | private boolean isUISynchronized() |
250 | { |
251 | boolean isUISynchronized = true; |
252 | Tree tree = getTree(); |
253 | int widgetCount = tree.getItemCount(); |
254 | |
255 | if (getTreeItemList() == null) |
256 | { |
257 | return widgetCount == 0; |
258 | } |
259 | |
260 | List<OrderedTreeItemModel> widgetContainerList = |
261 | getTreeItemList().getList(); |
262 | |
263 | int expectedWidgetCount = widgetContainerList.size(); |
264 | |
265 | if (widgetCount == expectedWidgetCount) |
266 | { |
267 | for (int i = 0; i < widgetCount; i++) |
268 | { |
269 | TreeItem currentWidget = tree.getItem(i); |
270 | TreeItem expectedWidget = |
271 | widgetContainerList.get(i).getTreeItem().getTreeItem(); |
272 | |
273 | if (currentWidget != expectedWidget) |
274 | { |
275 | isUISynchronized = false; |
276 | break; |
277 | } |
278 | } |
279 | } |
280 | else |
281 | { |
282 | isUISynchronized = false; |
283 | } |
284 | return isUISynchronized; |
285 | } |
286 | |
287 | /** |
288 | * rebuild Child Tree Item List |
289 | */ |
290 | private void rebuildChildTreeItemList() |
291 | { |
292 | Tree tree = getTree(); |
293 | |
294 | tree.removeItems(); |
295 | |
296 | if (getTreeItemList() != null) |
297 | { |
298 | List<OrderedTreeItemModel> widgetContainerList = |
299 | getTreeItemList().getList(); |
300 | |
301 | for (OrderedTreeItemModel orderedWidgetModel : widgetContainerList) |
302 | { |
303 | TreeItemModel childTreeItem = orderedWidgetModel.getTreeItem(); |
304 | childTreeItem.setParent(this); |
305 | tree.addItem(childTreeItem.getTreeItem()); |
306 | } |
307 | } |
308 | } |
309 | |
310 | /** |
311 | * (non-JSDoc) |
312 | * |
313 | * @see com.google.gwt.event.logical.shared.SelectionHandler#onSelection(com.google.gwt.event.logical.shared.SelectionEvent) |
314 | */ |
315 | @Override |
316 | public void onSelection( |
317 | SelectionEvent<TreeItem> event) |
318 | { |
319 | TreeItem selectedItem = event.getSelectedItem(); |
320 | TreeItemModel userObject = null; |
321 | if (selectedItem != null) |
322 | { |
323 | userObject = (TreeItemModel) selectedItem.getUserObject(); |
324 | } |
325 | onSelection(userObject); |
326 | } |
327 | |
328 | /** |
329 | * handle a selection change based on the tree item model that is selected |
330 | * |
331 | * @param userObject the selected tree item model. |
332 | */ |
333 | public void onSelection( |
334 | TreeItemModel userObject) |
335 | { |
336 | ArrayList<String> selectedObjectIDList = |
337 | selectedUserInterfaceElementList.getList(); |
338 | ArrayList<String> oldSelectedObjectIDList = |
339 | new ArrayList<String>(selectedObjectIDList); |
340 | |
341 | selectedObjectIDList.clear(); |
342 | |
343 | if (userObject != null) |
344 | { |
345 | selectedObjectIDList.add(userObject.getId()); |
346 | } |
347 | |
348 | ModelRegistry currentModelRegistry = getModelRegistry(); |
349 | |
350 | AsyncCallback<Void> defaultVoidCallback = |
351 | currentModelRegistry.getDefaultVoidCallback(); |
352 | |
353 | String selectionListID = selectedUserInterfaceElementList.getId(); |
354 | |
355 | currentModelRegistry.updateReferenceList(selectionListID, |
356 | selectedObjectIDList, oldSelectedObjectIDList, defaultVoidCallback); |
357 | } |
358 | |
359 | /** |
360 | * (non-JSDoc) |
361 | * |
362 | * @see com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google.gwt.event.logical.shared.CloseEvent) |
363 | */ |
364 | @Override |
365 | public void onClose( |
366 | CloseEvent<TreeItem> event) |
367 | { |
368 | TreeItem eventTarget = event.getTarget(); |
369 | TreeItemModel userObject = (TreeItemModel) eventTarget.getUserObject(); |
370 | handleTreeItemIsOpenChange(userObject, false); |
371 | } |
372 | |
373 | /** |
374 | * handle Tree Item Is Open Change by registering the change event to ignore |
375 | * and notifying the server of the change |
376 | * |
377 | * @param userObject the tree item model that changed |
378 | * @param isOpen the new is open flag. |
379 | */ |
380 | private void handleTreeItemIsOpenChange( |
381 | TreeItemModel userObject, boolean isOpen) |
382 | { |
383 | userObject.setIsOpen(isOpen); |
384 | notifyServerOfTreeItemIsOpenChange(userObject, isOpen); |
385 | } |
386 | |
387 | /** |
388 | * notify the server of a tree item node is open change |
389 | * |
390 | * @param userObject the tree item model that changed |
391 | * @param isOpen the new is open flag. |
392 | */ |
393 | private void notifyServerOfTreeItemIsOpenChange( |
394 | TreeItemModel userObject, boolean isOpen) |
395 | { |
396 | ModelRegistry modelRegistry = getModelRegistry(); |
397 | AsyncCallback<Void> defaultVoidCallback = |
398 | modelRegistry.getDefaultVoidCallback(); |
399 | |
400 | String objectID = userObject.getId(); |
401 | Boolean isOpenObject = Boolean.valueOf(isOpen); |
402 | modelRegistry.updateProperty(objectID, "is open", isOpenObject, |
403 | defaultVoidCallback); |
404 | } |
405 | |
406 | /** |
407 | * (non-JSDoc) |
408 | * |
409 | * @see com.google.gwt.event.logical.shared.OpenHandler#onOpen(com.google.gwt.event.logical.shared.OpenEvent) |
410 | */ |
411 | @Override |
412 | public void onOpen( |
413 | OpenEvent<TreeItem> event) |
414 | { |
415 | TreeItem eventTarget = event.getTarget(); |
416 | TreeItemModel userObject = (TreeItemModel) eventTarget.getUserObject(); |
417 | handleTreeItemIsOpenChange(userObject, true); |
418 | } |
419 | |
420 | /** |
421 | * (non-JSDoc) |
422 | * |
423 | * @see org.deduced.viewer.web.shared.UserInterfaceModel#propertyChanged(org.deduced.viewer.web.shared.ChangeEvent) |
424 | */ |
425 | @SuppressWarnings("unchecked") |
426 | @Override |
427 | public void propertyChanged( |
428 | ChangeEvent event) |
429 | { |
430 | if (Utilities.equals(event.getName(), "tree item list")) |
431 | { |
432 | OrderedListModel<OrderedTreeItemModel> newList = |
433 | (OrderedListModel<OrderedTreeItemModel>) event |
434 | .getSerializableValue(); |
435 | |
436 | treeItemListChanged(newList); |
437 | } |
438 | else if (Utilities.equals(event.getName(), |
439 | "selected user interface element list")) |
440 | { |
441 | StringListModel newList = |
442 | (StringListModel) event.getSerializableValue(); |
443 | selectionListUpdated(newList); |
444 | |
445 | } |
446 | else |
447 | { |
448 | super.propertyChanged(event); |
449 | } |
450 | } |
451 | |
452 | /** |
453 | * selection List Updated |
454 | * |
455 | * @param newList the new list |
456 | */ |
457 | public void selectionListUpdated( |
458 | StringListModel newList) |
459 | { |
460 | setSelectedUserInterfaceElementList(updateModel(newList, |
461 | selectedUserInterfaceElementList, this)); |
462 | |
463 | synchronizeWithUI(); |
464 | } |
465 | |
466 | /** |
467 | * tree Item List Changed |
468 | * |
469 | * @param newList the new list |
470 | */ |
471 | public void treeItemListChanged( |
472 | OrderedListModel<OrderedTreeItemModel> newList) |
473 | { |
474 | setTreeItemList(updateModel(newList, getTreeItemList(), this)); |
475 | |
476 | synchronizeWithUI(); |
477 | } |
478 | |
479 | /** |
480 | * @return the selectedUserInterfaceElementList |
481 | */ |
482 | public StringListModel getSelectedUserInterfaceElementList() |
483 | { |
484 | return selectedUserInterfaceElementList; |
485 | } |
486 | |
487 | /** |
488 | * @return the treeItemList |
489 | */ |
490 | public OrderedListModel<OrderedTreeItemModel> getTreeItemList() |
491 | { |
492 | return treeItemList; |
493 | } |
494 | |
495 | /** |
496 | * (non-JSDoc) |
497 | * |
498 | * @see org.deduced.viewer.web.shared.ModelContainer#modelChanged(org.deduced.viewer.web.shared.Model) |
499 | */ |
500 | @Override |
501 | public void modelChanged( |
502 | Model model) |
503 | { |
504 | synchronizeWithUI(); |
505 | } |
506 | |
507 | /** |
508 | * (non-JSDoc) |
509 | * |
510 | * @see com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt.event.dom.client.KeyDownEvent) |
511 | */ |
512 | @Override |
513 | public void onKeyDown( |
514 | KeyDownEvent event) |
515 | { |
516 | int nativeKeyCode = event.getNativeKeyCode(); |
517 | if (event.isControlKeyDown()) |
518 | { |
519 | switch (nativeKeyCode) |
520 | { |
521 | case 'c': |
522 | case 'C': |
523 | triggerCopy(); |
524 | event.stopPropagation(); |
525 | break; |
526 | case 'x': |
527 | case 'X': |
528 | triggerCut(); |
529 | event.stopPropagation(); |
530 | break; |
531 | case 'v': |
532 | case 'V': |
533 | triggerPaste(); |
534 | event.stopPropagation(); |
535 | break; |
536 | default: |
537 | } |
538 | } |
539 | else |
540 | { |
541 | switch (nativeKeyCode) |
542 | { |
543 | case KeyCodes.KEY_DELETE: |
544 | triggerDelete(); |
545 | event.stopPropagation(); |
546 | break; |
547 | default: |
548 | } |
549 | } |
550 | } |
551 | |
552 | /** |
553 | * trigger Copy |
554 | */ |
555 | public void triggerCopy() |
556 | { |
557 | triggerSignal("copy"); |
558 | } |
559 | |
560 | /** |
561 | * trigger Cut |
562 | */ |
563 | private void triggerCut() |
564 | { |
565 | triggerSignal("cut"); |
566 | } |
567 | |
568 | /** |
569 | * trigger Paste |
570 | */ |
571 | private void triggerPaste() |
572 | { |
573 | triggerSignal("paste"); |
574 | } |
575 | |
576 | /** |
577 | * trigger Delete |
578 | */ |
579 | private void triggerDelete() |
580 | { |
581 | triggerSignal("delete"); |
582 | } |
583 | } |