1 /* 2 * Copyright (c) 2012, 2014, Oracle and/or its affiliates. 3 * All rights reserved. Use is subject to license terms. 4 * 5 * This file is available and licensed under the following license: 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * - Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * - Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the distribution. 16 * - Neither the name of Oracle Corporation nor the names of its 17 * contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package com.oracle.javafx.scenebuilder.kit.editor.panel.hierarchy; 33 34 import com.oracle.javafx.scenebuilder.kit.editor.drag.DragController; 35 import com.oracle.javafx.scenebuilder.kit.editor.drag.source.AbstractDragSource; 36 import com.oracle.javafx.scenebuilder.kit.editor.drag.target.AbstractDropTarget; 37 import com.oracle.javafx.scenebuilder.kit.editor.drag.target.AccessoryDropTarget; 38 import com.oracle.javafx.scenebuilder.kit.editor.drag.target.ContainerZDropTarget; 39 import com.oracle.javafx.scenebuilder.kit.editor.drag.target.RootDropTarget; 40 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMDocument; 41 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMInstance; 42 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMObject; 43 import com.oracle.javafx.scenebuilder.kit.metadata.util.DesignHierarchyMask; 44 import com.oracle.javafx.scenebuilder.kit.metadata.util.DesignHierarchyMask.Accessory; 45 import javafx.scene.control.TreeItem; 46 import javafx.scene.input.DragEvent; 47 48 /** 49 * Controller for all drag and drop gestures in hierarchy panel. This class does 50 * not depend on the TreeView or TreeTableView control and handles only 51 * TreeItems. 52 * 53 * @treatAsPrivate 54 */ 55 public class HierarchyDNDController { 56 57 private final AbstractHierarchyPanelController panelController; 58 private final HierarchyTaskScheduler scheduler; 59 60 /** 61 * Defines the mouse location within the cell when the dropping gesture 62 * occurs. 63 * 64 * @treatAsPrivate 65 */ 66 public enum DroppingMouseLocation { 67 68 BOTTOM, CENTER, TOP 69 } 70 71 public HierarchyDNDController(final AbstractHierarchyPanelController panelController) { 72 this.panelController = panelController; 73 this.scheduler = new HierarchyTaskScheduler(panelController); 74 } 75 76 public HierarchyTaskScheduler getScheduler() { 77 return scheduler; 78 } 79 80 /** 81 * Called by the TreeCell/TreeTableRow event handler. 82 * 83 * @param treeItem the TreeItem 84 * @param event the event 85 */ 86 public void handleOnDragDropped( 87 final TreeItem<HierarchyItem> treeItem, 88 final DragEvent event) { 89 90 // Cancel timer if any 91 scheduler.cancelTimer(); 92 } 93 94 /** 95 * Called by the TreeCell/TreeTableRow event handler. 96 * 97 * @param treeItem the TreeItem 98 * @param event the event 99 */ 100 public void handleOnDragEntered( 101 final TreeItem<HierarchyItem> treeItem, 102 final DragEvent event) { 103 104 // Cancel timer if any 105 scheduler.cancelTimer(); 106 107 if (treeItem == null) { 108 return; 109 } 110 111 // Auto scrolling timeline has been started : 112 // do not schedule any other task 113 if (panelController.isTimelineRunning()) { 114 return; 115 } 116 117 // Schedule expanding job for collapsed TreeItems 118 if (!treeItem.isExpanded() && !treeItem.isLeaf()) { 119 scheduler.scheduleExpandTask(treeItem); 120 } 121 } 122 123 /** 124 * Called by the TreeCell/TreeTableRow event handler. 125 * 126 * @param treeItem the TreeItem 127 * @param event the event 128 * @param location the location 129 */ 130 public void handleOnDragExited( 131 final TreeItem<HierarchyItem> treeItem, 132 final DragEvent event, 133 final DroppingMouseLocation location) { 134 135 // Cancel timer if any 136 scheduler.cancelTimer(); 137 138 // Remove empty tree item graphic if previously added by the scheduler 139 // when we exit the empty graphic TreeItem by the bottom 140 if (treeItem != null) { 141 final HierarchyItem item = treeItem.getValue(); 142 assert item != null; 143 if (item instanceof HierarchyItemGraphic 144 && item.isEmpty() 145 && location == DroppingMouseLocation.BOTTOM) { 146 final TreeItem<HierarchyItem> parentTreeItem = treeItem.getParent(); 147 parentTreeItem.getChildren().remove(treeItem); 148 } 149 } 150 } 151 152 /** 153 * Called by the TreeCell/TreeTableRow event handler. 154 * 155 * @param treeItem the TreeItem 156 * @param event the event 157 * @param location the location 158 */ 159 public void handleOnDragOver( 160 final TreeItem<HierarchyItem> treeItem, 161 final DragEvent event, 162 final DroppingMouseLocation location) { 163 164 // Remove empty tree item graphic if previously added by the scheduler 165 // when we hover the empty graphic owner TreeItem on the top area 166 if (treeItem != null) { 167 final HierarchyItem item = treeItem.getValue(); 168 assert item != null; 169 final TreeItem<HierarchyItem> graphicTreeItem = getEmptyGraphicTreeItemFor(treeItem); 170 if (graphicTreeItem != null && location == DroppingMouseLocation.TOP) { 171 treeItem.getChildren().remove(graphicTreeItem); 172 } 173 } 174 175 // First update drop target 176 final DragController dragController 177 = panelController.getEditorController().getDragController(); 178 final AbstractDropTarget dropTarget = makeDropTarget(treeItem, location); 179 dragController.setDropTarget(dropTarget); 180 181 // Then update transfer mode 182 event.acceptTransferModes(dragController.getAcceptedTransferModes()); 183 184 // Schedule adding empty graphic place holder job : 185 // The drop target must be a GRAPHIC AccessoryDropTarget 186 if (dragController.isDropAccepted() 187 && dropTarget instanceof AccessoryDropTarget 188 && ((AccessoryDropTarget) dropTarget).getAccessory() == Accessory.GRAPHIC) { 189 // Retrieve the GRAPHIC accessory owner 190 final TreeItem<HierarchyItem> graphicOwnerTreeItem; 191 if (treeItem != null) { 192 if (treeItem.getValue().isEmpty() == false) { 193 graphicOwnerTreeItem = treeItem; 194 } else { 195 // Empty graphic place holder 196 // => the graphic owner is the parent 197 graphicOwnerTreeItem = treeItem.getParent(); 198 } 199 } else { 200 // TreeItem is null when dropping below the datas 201 // => the graphic owner is the root 202 graphicOwnerTreeItem = panelController.getRoot(); 203 } 204 assert graphicOwnerTreeItem != null; 205 assert graphicOwnerTreeItem.getValue().isEmpty() == false; 206 // Schedule adding empty graphic place holder if : 207 // - an empty graphic place holder has not yet been added 208 // - an empty graphic place holder has not yet been scheduled 209 if (getEmptyGraphicTreeItemFor(graphicOwnerTreeItem) == null 210 && scheduler.isAddEmptyGraphicTaskScheduled() == false) { 211 scheduler.scheduleAddEmptyGraphicTask(graphicOwnerTreeItem); 212 } 213 } 214 } 215 216 /** 217 * Returns the empty graphic TreeItem (if any) of the specified TreeItem, 218 * null otherwise. 219 * 220 * @param treeItem the TreeItem 221 * @return the empty graphic TreeItem 222 */ 223 public TreeItem<HierarchyItem> getEmptyGraphicTreeItemFor(final TreeItem<HierarchyItem> treeItem) { 224 assert treeItem != null; 225 for (TreeItem<HierarchyItem> childTreeItem : treeItem.getChildren()) { 226 final HierarchyItem child = childTreeItem.getValue(); 227 if (child instanceof HierarchyItemGraphic && child.isEmpty()) { 228 return childTreeItem; 229 } 230 } 231 return null; 232 } 233 234 private AbstractDropTarget makeDropTarget( 235 final TreeItem<HierarchyItem> treeItem, 236 final DroppingMouseLocation location) { 237 238 assert location != null; 239 240 final TreeItem<HierarchyItem> rootTreeItem = panelController.getRoot(); 241 final FXOMObject dropTargetObject; 242 final AbstractDropTarget result; 243 Accessory accessory = null; // Used if we insert as accessory (drop over a place holder) 244 int targetIndex = -1; // Used if we insert as sub components 245 246 final FXOMDocument document = panelController.getEditorController().getFxomDocument(); 247 if (document == null || document.getFxomRoot() == null) { 248 return new RootDropTarget(); 249 } 250 // TreeItem is null when dropping below the datas 251 // => the drop target is the root 252 if (treeItem == null) { 253 dropTargetObject = rootTreeItem.getValue().getFxomObject(); 254 255 } else { 256 final HierarchyItem item = treeItem.getValue(); 257 assert item != null; 258 259 // When the TreeItem is a place holder : 260 // - if the place holder is empty 261 // the drop target is the place holder parent 262 // the accessory is set to the place holder value 263 // whatever the location value is. 264 // - otherwise 265 // the drop target is the place holder item 266 // the accessory is set to null 267 // the target index is set depending on the location value 268 //------------------------------------------------------------------ 269 if (item.isPlaceHolder()) { // (1) 270 271 assert treeItem != rootTreeItem; 272 assert item instanceof HierarchyItemBorderPane 273 || item instanceof HierarchyItemGraphic 274 || item instanceof HierarchyItemDialogPane; 275 276 if (item.isEmpty()) { 277 // Set the drop target 278 final TreeItem<HierarchyItem> parentTreeItem = treeItem.getParent(); 279 assert parentTreeItem != null; // Because of (1) 280 dropTargetObject = parentTreeItem.getValue().getFxomObject(); 281 // Set the accessory 282 if (item instanceof HierarchyItemBorderPane) { 283 accessory = ((HierarchyItemBorderPane) item).getPosition(); 284 } else if (item instanceof HierarchyItemDialogPane) { 285 accessory = ((HierarchyItemDialogPane) item).getAccessory(); 286 } else { 287 accessory = Accessory.GRAPHIC; 288 } 289 } else { 290 // Set the drop target 291 dropTargetObject = item.getFxomObject(); 292 // Set the accessory 293 accessory = null; 294 // Set the target index 295 switch (location) { 296 case CENTER: 297 case TOP: 298 targetIndex = -1; // Insert at last position 299 break; 300 case BOTTOM: 301 if (treeItem.isLeaf() || !treeItem.isExpanded()) { 302 targetIndex = -1; // Insert at last position 303 } else { 304 targetIndex = 0; // Insert at first position 305 } 306 break; 307 default: 308 assert false; 309 break; 310 311 } 312 } 313 } // 314 // TreeItem is not a place holder: 315 // we set the drop target, accessory and target index 316 // depending on the mouse location value 317 //------------------------------------------------------------------ 318 else { 319 switch (location) { 320 321 // REPARENTING 322 case CENTER: 323 dropTargetObject = item.getFxomObject(); 324 targetIndex = -1; // Insert at last position 325 break; 326 327 // REORDERING ABOVE 328 case TOP: 329 // Dropping on TOP of the root TreeItem 330 if (treeItem == rootTreeItem) { // (2) 331 dropTargetObject = item.getFxomObject(); 332 targetIndex = -1; // Insert at last position 333 } else { 334 // If the parent accepts sub components, 335 // this is a reordering gesture and the target is the parent 336 final DragController dragController 337 = panelController.getEditorController().getDragController(); 338 final AbstractDragSource dragSource = dragController.getDragSource(); 339 final TreeItem<HierarchyItem> parentTreeItem = treeItem.getParent(); 340 assert parentTreeItem != null; // Because of (2) 341 final FXOMObject parentObject = parentTreeItem.getValue().getFxomObject(); 342 final DesignHierarchyMask parentMask = new DesignHierarchyMask(parentObject); 343 if (parentMask.isAcceptingSubComponent(dragSource.getDraggedObjects())) { 344 dropTargetObject = parentTreeItem.getValue().getFxomObject(); 345 targetIndex = item.getFxomObject().getIndexInParentProperty(); 346 } // Otherwise, attempt to set an accessory on the current TreeItem 347 else { 348 dropTargetObject = item.getFxomObject(); 349 } 350 } 351 break; 352 353 // REORDERING BELOW 354 case BOTTOM: 355 // Dropping on BOTTOM of the root TreeItem 356 if (treeItem == rootTreeItem) { // (3) 357 dropTargetObject = item.getFxomObject(); 358 targetIndex = 0; // Insert at first position 359 } else { 360 if (treeItem.isLeaf() || !treeItem.isExpanded()) { 361 // If the parent accepts sub components, 362 // this is a reordering gesture and the target is the parent 363 final DragController dragController 364 = panelController.getEditorController().getDragController(); 365 final AbstractDragSource dragSource = dragController.getDragSource(); 366 final TreeItem<HierarchyItem> parentTreeItem = treeItem.getParent(); 367 assert parentTreeItem != null; // Because of (3) 368 final FXOMObject parentObject = parentTreeItem.getValue().getFxomObject(); 369 final DesignHierarchyMask parentMask = new DesignHierarchyMask(parentObject); 370 if (parentMask.isAcceptingSubComponent(dragSource.getDraggedObjects())) { 371 dropTargetObject = parentTreeItem.getValue().getFxomObject(); 372 targetIndex = item.getFxomObject().getIndexInParentProperty() + 1; 373 } // Otherwise, attempt to set an accessory on the current TreeItem 374 else { 375 dropTargetObject = item.getFxomObject(); 376 } 377 } else { 378 dropTargetObject = item.getFxomObject(); 379 targetIndex = 0; // Insert at first position 380 } 381 } 382 break; 383 default: 384 assert false; 385 dropTargetObject = null; 386 break; 387 } 388 } 389 } 390 391 result = makeDropTarget(dropTargetObject, accessory, targetIndex); 392 return result; 393 } 394 395 private AbstractDropTarget makeDropTarget( 396 final FXOMObject dropTargetObject, 397 final Accessory accessory, 398 int targetIndex) { 399 400 AbstractDropTarget result = null; 401 402 if (dropTargetObject instanceof FXOMInstance) { 403 final DragController dragController 404 = panelController.getEditorController().getDragController(); 405 final AbstractDragSource dragSource = dragController.getDragSource(); 406 assert dragSource != null; 407 final FXOMInstance dropTargetInstance = (FXOMInstance) dropTargetObject; 408 if (accessory != null) { 409 result = new AccessoryDropTarget(dropTargetInstance, accessory); 410 } else { 411 final DesignHierarchyMask dropTargetMask 412 = new DesignHierarchyMask(dropTargetInstance); 413 // Check if the drop target accepts sub components 414 if (dropTargetMask.isAcceptingSubComponent(dragSource.getDraggedObjects())) { 415 final FXOMObject beforeChild; 416 if (targetIndex == -1) { 417 beforeChild = null; 418 } else { 419 // targetIndex is the last sub component 420 if (targetIndex == dropTargetMask.getSubComponentCount()) { 421 beforeChild = null; 422 } else { 423 beforeChild = dropTargetMask.getSubComponentAtIndex(targetIndex); 424 } 425 } 426 result = new ContainerZDropTarget(dropTargetInstance, beforeChild); 427 } // 428 // Check if the drop target accepts accessories 429 else { 430 // Check if there is an accessory that can be accepted by the drop target. 431 // First we build the list of accessories that can be set by DND gesture. 432 final Accessory[] accessories = { 433 Accessory.TOP, 434 Accessory.LEFT, 435 Accessory.CENTER, 436 Accessory.RIGHT, 437 Accessory.BOTTOM, 438 Accessory.CONTENT, 439 Accessory.CONTEXT_MENU, 440 Accessory.GRAPHIC, 441 Accessory.TOOLTIP, 442 Accessory.HEADER, 443 Accessory.DP_GRAPHIC, 444 Accessory.DP_CONTENT, 445 Accessory.EXPANDABLE_CONTENT 446 }; 447 for (Accessory a : accessories) { 448 final AccessoryDropTarget dropTarget 449 = new AccessoryDropTarget(dropTargetInstance, a); 450 // If the accessory drop target accepts the dragged objects, 451 // we return this drop target. 452 // Otherwise, we look for the next accessory. 453 if (dropTarget.acceptDragSource(dragSource)) { 454 result = dropTarget; 455 break; 456 } 457 } 458 } 459 } 460 } 461 return result; 462 } 463 }