| 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 | } |