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 }