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 33 package com.oracle.javafx.scenebuilder.kit.editor.drag.target; 34 35 import com.oracle.javafx.scenebuilder.kit.editor.EditorController; 36 import com.oracle.javafx.scenebuilder.kit.editor.drag.source.AbstractDragSource; 37 import com.oracle.javafx.scenebuilder.kit.editor.job.BatchJob; 38 import com.oracle.javafx.scenebuilder.kit.editor.job.InsertAsSubComponentJob; 39 import com.oracle.javafx.scenebuilder.kit.editor.job.Job; 40 import com.oracle.javafx.scenebuilder.kit.editor.job.gridpane.v2.GridSnapshot; 41 import com.oracle.javafx.scenebuilder.kit.editor.job.gridpane.v2.InsertColumnJob; 42 import com.oracle.javafx.scenebuilder.kit.editor.job.gridpane.v2.InsertRowJob; 43 import com.oracle.javafx.scenebuilder.kit.editor.job.gridpane.v2.MoveCellContentJob; 44 import com.oracle.javafx.scenebuilder.kit.editor.job.atomic.ClearSelectionJob; 45 import com.oracle.javafx.scenebuilder.kit.editor.job.atomic.RemoveObjectJob; 46 import com.oracle.javafx.scenebuilder.kit.editor.job.atomic.UpdateSelectionJob; 47 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMInstance; 48 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMObject; 49 import com.oracle.javafx.scenebuilder.kit.metadata.util.DesignHierarchyMask; 50 import com.oracle.javafx.scenebuilder.kit.util.Deprecation; 51 import com.oracle.javafx.scenebuilder.kit.util.GridBounds; 52 import java.util.List; 53 import javafx.scene.Node; 54 import javafx.scene.layout.GridPane; 55 56 /** 57 * 58 */ 59 public class GridPaneDropTarget extends AbstractDropTarget { 60 61 public enum ColumnArea { 62 LEFT, CENTER, RIGHT 63 } 64 65 public enum RowArea { 66 TOP, CENTER, BOTTOM 67 } 68 69 private final FXOMObject targetGridPane; 70 private final int targetIndex; 71 private final int targetColumnIndex; 72 private final int targetRowIndex; 73 private final ColumnArea targetColumnArea; 74 private final RowArea targetRowArea; 75 76 public GridPaneDropTarget(FXOMObject targetGridPane, 77 int columnIndex, int rowIndex, 78 ColumnArea targetColumnArea, RowArea targetRowArea) { 79 assert targetGridPane != null; 80 assert targetGridPane.getSceneGraphObject() instanceof GridPane; 81 assert columnIndex >= 0; 82 assert rowIndex >= 0; 83 84 this.targetGridPane = targetGridPane; 85 this.targetIndex = -1; 86 this.targetColumnIndex = columnIndex; 87 this.targetRowIndex = rowIndex; 88 this.targetColumnArea = targetColumnArea; 89 this.targetRowArea = targetRowArea; 90 } 91 92 public GridPaneDropTarget(FXOMObject targetGridPane, int targetIndex) { 93 assert targetGridPane != null; 94 assert targetGridPane.getSceneGraphObject() instanceof GridPane; 95 assert targetIndex >= -1; 96 97 this.targetGridPane = targetGridPane; 98 this.targetIndex = targetIndex; 99 this.targetColumnIndex = 0; 100 this.targetRowIndex = 0; 101 102 final GridPane gridPane = (GridPane) targetGridPane.getSceneGraphObject(); 103 if (Deprecation.getGridPaneColumnCount(gridPane) == 0) { 104 this.targetColumnArea = ColumnArea.LEFT; 105 } else { 106 this.targetColumnArea = ColumnArea.CENTER; 107 } 108 if (Deprecation.getGridPaneRowCount(gridPane) == 0) { 109 this.targetRowArea = RowArea.TOP; 110 } else { 111 this.targetRowArea = RowArea.CENTER; 112 } 113 } 114 115 public int getTargetColumnIndex() { 116 return targetColumnIndex; 117 } 118 119 public int getTargetRowIndex() { 120 return targetRowIndex; 121 } 122 123 public ColumnArea getTargetColumnArea() { 124 return targetColumnArea; 125 } 126 127 public RowArea getTargetRowArea() { 128 return targetRowArea; 129 } 130 131 /* 132 * AbstractDropTarget 133 */ 134 @Override 135 public FXOMObject getTargetObject() { 136 return targetGridPane; 137 } 138 139 @Override 140 public boolean acceptDragSource(AbstractDragSource dragSource) { 141 assert dragSource != null; 142 143 final boolean result; 144 if (dragSource.getDraggedObjects().isEmpty()) { 145 result = false; 146 } else { 147 final DesignHierarchyMask m = new DesignHierarchyMask(targetGridPane); 148 if (m.isAcceptingSubComponent(dragSource.getDraggedObjects())) { 149 final FXOMObject draggedObject0 = dragSource.getDraggedObjects().get(0); 150 assert draggedObject0.getSceneGraphObject() instanceof Node; 151 152 final Node draggedNode0 = (Node) draggedObject0.getSceneGraphObject(); 153 final Integer columIndexObj = GridPane.getColumnIndex(draggedNode0); 154 final Integer rowIndexObj = GridPane.getRowIndex(draggedNode0); 155 final int currentColumnIndex = (columIndexObj == null) ? 0 : columIndexObj; 156 final int currentRowIndex = (rowIndexObj == null) ? 0 : rowIndexObj; 157 158 final boolean sameContainer 159 = targetGridPane == draggedObject0.getParentObject(); 160 final boolean sameColumnIndex 161 = targetColumnIndex == currentColumnIndex; 162 final boolean sameRowIndex 163 = targetRowIndex == currentRowIndex; 164 final boolean sameArea 165 = (targetColumnArea == ColumnArea.CENTER) 166 && (targetRowArea == RowArea.CENTER); 167 168 result = (sameContainer == false) 169 || (sameColumnIndex == false) 170 || (sameRowIndex == false) 171 || (sameArea == false); 172 } else { 173 result = false; 174 } 175 } 176 177 return result; 178 } 179 180 @Override 181 public Job makeDropJob(AbstractDragSource dragSource, EditorController editorController) { 182 assert acceptDragSource(dragSource); // (1) 183 assert editorController != null; 184 185 final boolean shouldRefreshSceneGraph = true; 186 final BatchJob result = new BatchJob(editorController, 187 shouldRefreshSceneGraph, dragSource.makeDropJobDescription()); 188 189 final List<FXOMObject> draggedObjects = dragSource.getDraggedObjects(); 190 final FXOMObject hitObject = dragSource.getHitObject(); 191 final FXOMObject currentParent = hitObject.getParentObject(); 192 final boolean reparenting = (currentParent != targetGridPane); 193 final GridPane gridPane = (GridPane) targetGridPane.getSceneGraphObject(); 194 195 // Steps: 196 // 197 // 1) snapshot grid related properties of dragged objects 198 // => this must be done here because they will be lost by #1 199 // 2) clear the selection 200 // 3) remove drag source objects from their current parent (if any) 201 // 4) add new columns/rows in target grip pane as needed 202 // 5) add drag source objects to this drop target 203 // 6) restore grid related properties 204 // 7) select the dragged objects 205 // 206 // Note: if source and target parents are the same, skip #2,#3,#5,#7 and #8 207 208 // Step #1 209 final GridSnapshot gridSnapshot; 210 if ((currentParent != null) 211 && (currentParent.getSceneGraphObject() instanceof GridPane)) { 212 gridSnapshot = new GridSnapshot(draggedObjects); 213 } else { 214 gridSnapshot = new GridSnapshot(draggedObjects, 1); 215 } 216 217 if (reparenting) { 218 219 // Step #2 220 result.addSubJob(new ClearSelectionJob(editorController)); 221 222 // Step #3 223 if (currentParent != null) { 224 for (FXOMObject draggedObject : draggedObjects) { 225 result.addSubJob(new RemoveObjectJob(draggedObject, 226 editorController)); 227 } 228 } 229 } 230 231 // Step #4 232 final GridBounds snapshotBounds = gridSnapshot.getBounds(); 233 final int hitColumnIndex = gridSnapshot.getColumnIndex(hitObject); 234 final int hitRowIndex = gridSnapshot.getRowIndex(hitObject); 235 final int destColumnIndex = (targetColumnArea == ColumnArea.RIGHT) ? targetColumnIndex+1 : targetColumnIndex; 236 final int destRowIndex = (targetRowArea == RowArea.BOTTOM) ? targetRowIndex+1 : targetRowIndex; 237 final int columnDelta = destColumnIndex - hitColumnIndex; 238 final int rowDelta = destRowIndex - hitRowIndex; 239 final GridBounds adjustedBounds = snapshotBounds.move(columnDelta, rowDelta); 240 241 // Step #4.1 : columns 242 switch(targetColumnArea) { 243 case LEFT: 244 case RIGHT: { // Insert columns at destColumnIndex 245 final int insertCount = snapshotBounds.getColumnSpan(); 246 result.addSubJob(new InsertColumnJob(targetGridPane, 247 destColumnIndex, insertCount, editorController)); 248 break; 249 } 250 case CENTER: {// Insert columns at right (first) and left ends if needed 251 final int targetColumnCount = Deprecation.getGridPaneColumnCount(gridPane); 252 if (adjustedBounds.getMaxColumnIndex() > targetColumnCount) { 253 final int insertCount = adjustedBounds.getMaxColumnIndex() - targetColumnCount; 254 result.addSubJob(new InsertColumnJob(targetGridPane, 255 targetColumnCount, insertCount, editorController)); 256 } 257 if (adjustedBounds.getMinColumnIndex() < 0) { 258 final int insertCount = -adjustedBounds.getMinColumnIndex(); 259 result.addSubJob(new InsertColumnJob(targetGridPane, 260 0, insertCount, editorController)); 261 } 262 break; 263 } 264 } 265 266 // Step #4.2 : rows 267 switch(targetRowArea) { 268 case TOP: 269 case BOTTOM: { // Insert rows at destRowIndex 270 final int insertCount = snapshotBounds.getRowSpan(); 271 result.addSubJob(new InsertRowJob(targetGridPane, 272 destRowIndex, insertCount, editorController)); 273 break; 274 } 275 case CENTER: { // Insert rows at bottom (first) and top ends if needed 276 final int targetRowCount = Deprecation.getGridPaneRowCount(gridPane); 277 if (adjustedBounds.getMaxRowIndex() > targetRowCount) { 278 final int insertCount = adjustedBounds.getMaxRowIndex() - targetRowCount; 279 result.addSubJob(new InsertRowJob(targetGridPane, 280 targetRowCount, insertCount, editorController)); 281 } 282 if (adjustedBounds.getMinRowIndex() < 0) { 283 final int insertCount = -adjustedBounds.getMinRowIndex(); 284 result.addSubJob(new InsertRowJob(targetGridPane, 285 0, insertCount, editorController)); 286 } 287 break; 288 } 289 } 290 291 if (reparenting) { 292 293 // Step #5 294 for (FXOMObject draggedObject : draggedObjects) { 295 final Job j = new InsertAsSubComponentJob(draggedObject, 296 targetGridPane, targetIndex, editorController); 297 result.addSubJob(j); 298 } 299 } 300 301 // Step #6 302 for (FXOMObject draggedObject : draggedObjects) { 303 assert draggedObject instanceof FXOMInstance; // Because (1) 304 result.addSubJob(new MoveCellContentJob((FXOMInstance) draggedObject, 305 columnDelta, rowDelta, editorController)); 306 } 307 308 if (reparenting) { 309 310 // Step #7 311 result.addSubJob(new UpdateSelectionJob(draggedObjects, editorController)); 312 } 313 314 assert result.isExecutable(); 315 316 return result; 317 } 318 319 @Override 320 public boolean isSelectRequiredAfterDrop() { 321 return true; 322 } 323 324 }