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.job; 33 34 import com.oracle.javafx.scenebuilder.kit.editor.job.atomic.RelocateNodeJob; 35 import com.oracle.javafx.scenebuilder.kit.editor.EditorController; 36 import com.oracle.javafx.scenebuilder.kit.editor.i18n.I18N; 37 import com.oracle.javafx.scenebuilder.kit.editor.selection.AbstractSelectionGroup; 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.FXOMCollection; 41 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMDocument; 42 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMInstance; 43 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMNodes; 44 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMObject; 45 import com.oracle.javafx.scenebuilder.kit.metadata.util.DesignHierarchyMask; 46 import java.util.LinkedHashMap; 47 import java.util.LinkedList; 48 import java.util.List; 49 import java.util.Map; 50 import javafx.scene.Node; 51 52 /** 53 * 54 */ 55 public class DuplicateSelectionJob extends BatchSelectionJob { 56 57 private final static double offset = 10; 58 final Map<FXOMObject, FXOMObject> newFxomObjects = new LinkedHashMap<>(); 59 60 public DuplicateSelectionJob(EditorController editorController) { 61 super(editorController); 62 } 63 64 @Override 65 protected List<Job> makeSubJobs() { 66 final List<Job> result = new LinkedList<>(); 67 68 if (canDuplicate()) { // (1) 69 70 final Selection selection = getEditorController().getSelection(); 71 final AbstractSelectionGroup asg = selection.getGroup(); 72 assert asg instanceof ObjectSelectionGroup; // Because of (1) 73 final ObjectSelectionGroup osg = (ObjectSelectionGroup) asg; 74 assert osg.hasSingleParent() == true; // Because of (1) 75 final FXOMObject targetObject = osg.getAncestor(); 76 assert targetObject != null; // Because of (1) 77 final FXOMDocument targetDocument = getEditorController().getFxomDocument(); 78 for (FXOMObject selectedObject : osg.getSortedItems()) { 79 final FXOMDocument newDocument = FXOMNodes.newDocument(selectedObject); 80 final FXOMObject newObject = newDocument.getFxomRoot(); 81 newObject.moveToFxomDocument(targetDocument); 82 assert newDocument.getFxomRoot() == null; 83 newFxomObjects.put(selectedObject, newObject); 84 } 85 assert newFxomObjects.isEmpty() == false; // Because of (1) 86 87 // Build InsertAsSubComponent jobs 88 final DesignHierarchyMask targetMask = new DesignHierarchyMask(targetObject); 89 if (targetMask.isAcceptingSubComponent(newFxomObjects.keySet())) { 90 int index = 0; 91 for (Map.Entry<FXOMObject, FXOMObject> entry : newFxomObjects.entrySet()) { 92 final FXOMObject selectedFxomObject = entry.getKey(); 93 final FXOMObject newFxomObject = entry.getValue(); 94 final InsertAsSubComponentJob insertSubJob = new InsertAsSubComponentJob( 95 newFxomObject, 96 targetObject, 97 targetMask.getSubComponentCount() + index++, 98 getEditorController()); 99 result.add(insertSubJob); 100 final Object selectedSceneGraphObject = selectedFxomObject.getSceneGraphObject(); 101 // Relocate duplicated objects if needed 102 if (selectedSceneGraphObject instanceof Node) { 103 final Node selectedNode = (Node) selectedSceneGraphObject; 104 final double newLayoutX = Math.round(selectedNode.getLayoutX() + offset); 105 final double newLayoutY = Math.round(selectedNode.getLayoutY() + offset); 106 assert newFxomObject instanceof FXOMInstance; 107 final RelocateNodeJob relocateSubJob = new RelocateNodeJob( 108 (FXOMInstance) newFxomObject, 109 newLayoutX, 110 newLayoutY, 111 getEditorController()); 112 result.add(relocateSubJob); 113 } 114 } 115 } 116 } 117 return result; 118 } 119 120 @Override 121 protected String makeDescription() { 122 final String result; 123 assert newFxomObjects.values().isEmpty() == false; 124 if (newFxomObjects.values().size() == 1) { 125 result = makeSingleSelectionDescription(); 126 } else { 127 result = makeMultipleSelectionDescription(); 128 } 129 130 return result; 131 } 132 133 @Override 134 protected AbstractSelectionGroup getNewSelectionGroup() { 135 assert newFxomObjects != null; // But possibly empty 136 if (newFxomObjects.isEmpty()) { 137 return null; 138 } else { 139 return new ObjectSelectionGroup(newFxomObjects.values(), newFxomObjects.values().iterator().next(), null); 140 } 141 } 142 143 private boolean canDuplicate() { 144 final FXOMDocument fxomDocument = getEditorController().getFxomDocument(); 145 if (fxomDocument == null) { 146 return false; 147 } 148 final Selection selection = getEditorController().getSelection(); 149 if (selection.isEmpty()) { 150 return false; 151 } 152 final FXOMObject rootObject = fxomDocument.getFxomRoot(); 153 if (selection.isSelected(rootObject)) { 154 return false; 155 } 156 final AbstractSelectionGroup asg = selection.getGroup(); 157 if ((asg instanceof ObjectSelectionGroup) == false) { 158 return false; 159 } 160 final ObjectSelectionGroup osg = (ObjectSelectionGroup) asg; 161 for (FXOMObject fxomObject : osg.getItems()) { 162 if (fxomObject.getSceneGraphObject() == null) { // Unresolved custom type 163 return false; 164 } 165 } 166 return osg.hasSingleParent() == true; 167 } 168 169 private String makeSingleSelectionDescription() { 170 final String result; 171 172 final FXOMObject newObject = newFxomObjects.values().iterator().next(); 173 if (newObject instanceof FXOMInstance) { 174 final Object sceneGraphObject = newObject.getSceneGraphObject(); 175 if (sceneGraphObject != null) { 176 result = I18N.getString("label.action.edit.duplicate.1", sceneGraphObject.getClass().getSimpleName()); 177 } else { 178 result = I18N.getString("label.action.edit.duplicate.unresolved"); 179 } 180 } else if (newObject instanceof FXOMCollection) { 181 result = I18N.getString("label.action.edit.duplicate.collection"); 182 } else { 183 assert false; 184 result = I18N.getString("label.action.edit.duplicate.1", newObject.getClass().getSimpleName()); 185 } 186 187 return result; 188 } 189 190 private String makeMultipleSelectionDescription() { 191 return I18N.getString("label.action.edit.duplicate.n", newFxomObjects.values().size()); 192 } 193 }