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 }