1 /* 2 * Copyright (c) 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.job; 34 35 import com.oracle.javafx.scenebuilder.kit.editor.job.atomic.RelocateNodeJob; 36 import com.oracle.javafx.scenebuilder.kit.editor.EditorController; 37 import com.oracle.javafx.scenebuilder.kit.editor.i18n.I18N; 38 import com.oracle.javafx.scenebuilder.kit.editor.selection.ObjectSelectionGroup; 39 import com.oracle.javafx.scenebuilder.kit.editor.selection.Selection; 40 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMInstance; 41 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMObject; 42 import com.oracle.javafx.scenebuilder.kit.metadata.util.DesignHierarchyMask; 43 import java.util.ArrayList; 44 import java.util.HashMap; 45 import java.util.HashSet; 46 import java.util.List; 47 import java.util.Map; 48 import java.util.Set; 49 import javafx.geometry.Point2D; 50 51 /** 52 * 53 */ 54 public class RelocateSelectionJob extends BatchDocumentJob { 55 56 private static final long MERGE_PERIOD = 1000; // milliseconds 57 58 private final Map<FXOMObject, Point2D> locationMap = new HashMap<>(); 59 private long time = System.currentTimeMillis(); 60 61 public RelocateSelectionJob(Map<FXOMObject, Point2D> locationMap, 62 EditorController editorController) { 63 super(editorController); 64 this.locationMap.putAll(locationMap); 65 } 66 67 public boolean canBeMergedWith(Job other) { 68 69 /* 70 * This job is collapsible with other if: 71 * 0) other is a RelocateSelectionJob instance 72 * 1) other is younger than this of 1000 ms no more 73 * 2) other and this have the same location map keys 74 */ 75 76 final boolean result; 77 if (other instanceof RelocateSelectionJob) { 78 final RelocateSelectionJob otherRelocate = (RelocateSelectionJob)other; 79 final long timeDifference = otherRelocate.time - this.time; 80 if ((0 <= timeDifference) && (timeDifference < MERGE_PERIOD)) { 81 final Set<FXOMObject> thisKeys = this.locationMap.keySet(); 82 final Set<FXOMObject> otherKeys = otherRelocate.locationMap.keySet(); 83 result = thisKeys.equals(otherKeys); 84 } else { 85 result = false; 86 } 87 } else { 88 result = false; 89 } 90 91 return result; 92 } 93 94 95 public void mergeWith(Job younger) { 96 assert canBeMergedWith(younger); // (1) 97 assert younger instanceof RelocateSelectionJob; // Because (1) 98 99 final RelocateSelectionJob youngerSelection = (RelocateSelectionJob) younger; 100 for (Job subJob : getSubJobs()) { 101 assert subJob instanceof RelocateNodeJob; 102 final RelocateNodeJob thisRelocateJob 103 = (RelocateNodeJob) subJob; 104 final RelocateNodeJob youngerRelocateJob 105 = youngerSelection.lookupSubJob(thisRelocateJob.getFxomInstance()); 106 thisRelocateJob.mergeWith(youngerRelocateJob); 107 } 108 109 this.time = youngerSelection.time; 110 } 111 112 113 public RelocateNodeJob lookupSubJob(FXOMObject fxomObject) { 114 RelocateNodeJob result = null; 115 116 for (Job subJob : getSubJobs()) { 117 assert subJob instanceof RelocateNodeJob; 118 final RelocateNodeJob relocateJob = (RelocateNodeJob) subJob; 119 if (relocateJob.getFxomInstance() == fxomObject) { 120 result = relocateJob; 121 break; 122 } 123 } 124 125 return result; 126 } 127 128 public static boolean isSelectionMovable(EditorController editorController) { 129 /* 130 * Selection can be moved if: 131 * 1) it's an object selection (group instanceof ObjectSelectionGroup) 132 * 2) selected objects have a single parent 133 * 3) single parent supports free child positioning 134 * 135 * => all selected items are Node. 136 */ 137 138 final boolean result; 139 140 final Selection selection = editorController.getSelection(); 141 if (selection.getGroup() instanceof ObjectSelectionGroup) { 142 final ObjectSelectionGroup osg = (ObjectSelectionGroup) selection.getGroup(); 143 if (osg.hasSingleParent()) { 144 final FXOMObject parent = osg.getAncestor(); 145 final DesignHierarchyMask m = new DesignHierarchyMask(parent); 146 result = m.isFreeChildPositioning(); 147 } else { 148 result = false; 149 } 150 } else { 151 result = false; 152 } 153 154 return result; 155 } 156 157 @Override 158 protected List<Job> makeSubJobs() { 159 final List<Job> result = new ArrayList<>(); 160 161 for (Map.Entry<FXOMObject, Point2D> entry : locationMap.entrySet()) { 162 assert entry.getKey() instanceof FXOMInstance; 163 final FXOMInstance fxomInstance = (FXOMInstance) entry.getKey(); 164 final Point2D layoutXY = entry.getValue(); 165 final Job relocateJob = new RelocateNodeJob(fxomInstance, 166 layoutXY.getX(), layoutXY.getY(), getEditorController()); 167 result.add(relocateJob); 168 } 169 170 return result; 171 } 172 173 @Override 174 protected String makeDescription() { 175 final String result; 176 177 final Set<FXOMObject> movedObjects = locationMap.keySet(); 178 if (locationMap.size() == 1) { 179 final FXOMObject movedObject = movedObjects.iterator().next(); 180 final Object sceneGraphObject = movedObject.getSceneGraphObject(); 181 if (sceneGraphObject == null) { 182 result = I18N.getString("drop.job.move.single.unresolved"); 183 } else { 184 result = I18N.getString("drop.job.move.single.resolved", 185 sceneGraphObject.getClass().getSimpleName()); 186 } 187 } else { 188 final Set<Class<?>> classes = new HashSet<>(); 189 int unresolvedCount = 0; 190 for (FXOMObject o : movedObjects) { 191 if (o.getSceneGraphObject() != null) { 192 classes.add(o.getSceneGraphObject().getClass()); 193 } else { 194 unresolvedCount++; 195 } 196 } 197 final boolean homogeneous = (classes.size() == 1) && (unresolvedCount == 0); 198 199 if (homogeneous) { 200 final Class<?> singleClass = classes.iterator().next(); 201 result = I18N.getString("drop.job.move.multiple.homogeneous", 202 movedObjects.size(), 203 singleClass.getSimpleName()); 204 } else { 205 result = I18N.getString("drop.job.move.multiple.heterogeneous", 206 movedObjects.size()); 207 } 208 } 209 210 return result; 211 } 212 213 }