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 }