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.FXOMObject; 44 import com.oracle.javafx.scenebuilder.kit.metadata.util.ClipboardDecoder; 45 import com.oracle.javafx.scenebuilder.kit.metadata.util.DesignHierarchyMask; 46 import java.util.ArrayList; 47 import java.util.List; 48 import javafx.scene.Node; 49 import javafx.scene.input.Clipboard; 50 51 /** 52 * 53 */ 54 public class PasteJob extends BatchSelectionJob { 55 56 private FXOMObject targetObject; 57 private List<FXOMObject> newObjects; 58 59 public PasteJob(EditorController editorController) { 60 super(editorController); 61 } 62 63 @Override 64 protected List<Job> makeSubJobs() { 65 final List<Job> result = new ArrayList<>(); 66 67 final FXOMDocument fxomDocument = getEditorController().getFxomDocument(); 68 if (fxomDocument != null) { 69 70 // Retrieve the FXOMObjects from the clipboard 71 final ClipboardDecoder clipboardDecoder 72 = new ClipboardDecoder(Clipboard.getSystemClipboard()); 73 newObjects = clipboardDecoder.decode(fxomDocument); 74 assert newObjects != null; // But possible empty 75 76 if (newObjects.isEmpty() == false) { 77 78 // Retrieve the target FXOMObject : 79 // If the document is empty (root object is null), then the target 80 // object is null. 81 // If the selection is root or is empty, the target object is 82 // the root object. 83 // Otherwise, the target object is the selection common ancestor. 84 if (fxomDocument.getFxomRoot() == null) { 85 targetObject = null; 86 } else { 87 final Selection selection = getEditorController().getSelection(); 88 final FXOMObject rootObject = fxomDocument.getFxomRoot(); 89 if (selection.isEmpty() || selection.isSelected(rootObject)) { 90 targetObject = rootObject; 91 } else { 92 targetObject = selection.getAncestor(); 93 } 94 } 95 assert (targetObject != null) || (fxomDocument.getFxomRoot() == null); 96 97 if (targetObject == null) { 98 // Document is empty : only one object can be inserted 99 if (newObjects.size() == 1) { 100 final FXOMObject newObject0 = newObjects.get(0); 101 final SetDocumentRootJob subJob = new SetDocumentRootJob( 102 newObject0, 103 getEditorController()); 104 result.add(subJob); 105 } 106 } else { 107 // Build InsertAsSubComponent jobs 108 final DesignHierarchyMask targetMask = new DesignHierarchyMask(targetObject); 109 if (targetMask.isAcceptingSubComponent(newObjects)) { 110 111 final double relocateDelta; 112 if (targetMask.isFreeChildPositioning()) { 113 final int pasteJobCount = countPasteJobs(); 114 relocateDelta = 10.0 * (pasteJobCount + 1); 115 } else { 116 relocateDelta = 0.0; 117 } 118 for (FXOMObject newObject : newObjects) { 119 final InsertAsSubComponentJob subJob = new InsertAsSubComponentJob( 120 newObject, 121 targetObject, 122 targetMask.getSubComponentCount(), 123 getEditorController()); 124 result.add(0, subJob); 125 if ((relocateDelta != 0.0) && newObject.isNode()) { 126 final Node sceneGraphNode = (Node) newObject.getSceneGraphObject(); 127 final RelocateNodeJob relocateJob = new RelocateNodeJob( 128 (FXOMInstance) newObject, 129 sceneGraphNode.getLayoutX() + relocateDelta, 130 sceneGraphNode.getLayoutY() + relocateDelta, 131 getEditorController() 132 ); 133 result.add(relocateJob); 134 } 135 } 136 } 137 } 138 } 139 } 140 141 return result; 142 } 143 144 145 @Override 146 protected String makeDescription() { 147 final String result; 148 149 if (newObjects.size() == 1) { 150 result = makeSingleSelectionDescription(); 151 } else { 152 result = makeMultipleSelectionDescription(); 153 } 154 155 return result; 156 } 157 158 @Override 159 protected AbstractSelectionGroup getNewSelectionGroup() { 160 assert newObjects != null; // But possibly empty 161 if (newObjects.isEmpty()) { 162 return null; 163 } else { 164 return new ObjectSelectionGroup(newObjects, newObjects.iterator().next(), null); 165 } 166 } 167 168 /* 169 * Private 170 */ 171 private String makeSingleSelectionDescription() { 172 final String result; 173 174 assert newObjects.size() == 1; 175 final FXOMObject newObject = newObjects.get(0); 176 if (newObject instanceof FXOMInstance) { 177 final Object sceneGraphObject = newObject.getSceneGraphObject(); 178 if (sceneGraphObject != null) { 179 result = I18N.getString("label.action.edit.paste.1", sceneGraphObject.getClass().getSimpleName()); 180 } else { 181 result = I18N.getString("label.action.edit.paste.unresolved"); 182 } 183 } else if (newObject instanceof FXOMCollection) { 184 result = I18N.getString("label.action.edit.paste.collection"); 185 } else { 186 assert false; 187 result = I18N.getString("label.action.edit.paste.1", newObject.getClass().getSimpleName()); 188 } 189 190 return result; 191 } 192 193 private String makeMultipleSelectionDescription() { 194 final int objectCount = newObjects.size(); 195 return I18N.getString("label.action.edit.paste.n", objectCount); 196 } 197 198 private int countPasteJobs() { 199 int result = 0; 200 201 final List<Job> undoStack = getEditorController().getJobManager().getUndoStack(); 202 for (Job job : undoStack) { 203 if (job instanceof PasteJob) { 204 final PasteJob pasteJob = (PasteJob) job; 205 if (this.targetObject == pasteJob.targetObject) { 206 result++; 207 } else { 208 break; 209 } 210 } else { 211 break; 212 } 213 } 214 215 return result; 216 } 217 }