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.gridpane; 33 34 import com.oracle.javafx.scenebuilder.kit.editor.EditorController; 35 import com.oracle.javafx.scenebuilder.kit.editor.job.BatchSelectionJob; 36 import com.oracle.javafx.scenebuilder.kit.editor.job.Job; 37 import com.oracle.javafx.scenebuilder.kit.editor.job.gridpane.GridPaneJobUtils.Position; 38 import com.oracle.javafx.scenebuilder.kit.editor.selection.AbstractSelectionGroup; 39 import com.oracle.javafx.scenebuilder.kit.editor.selection.GridSelectionGroup; 40 import com.oracle.javafx.scenebuilder.kit.editor.selection.GridSelectionGroup.Type; 41 import com.oracle.javafx.scenebuilder.kit.editor.selection.ObjectSelectionGroup; 42 import com.oracle.javafx.scenebuilder.kit.editor.selection.Selection; 43 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMInstance; 44 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMObject; 45 import com.oracle.javafx.scenebuilder.kit.metadata.util.DesignHierarchyMask; 46 import java.util.ArrayList; 47 import java.util.HashMap; 48 import java.util.Iterator; 49 import java.util.LinkedHashSet; 50 import java.util.List; 51 import java.util.Map; 52 import java.util.Set; 53 54 /** 55 * Job invoked when adding columns. 56 * 57 * This job handles multi-selection as follows : 58 * - if multiple GridPanes are selected, no column can be selected. 59 * We add the new column to each GP, either at first position (add before) 60 * or last position (add after). 61 * - if multiple columns are selected, a single GridPane is selected. 62 * We add new columns for each selected column, either before or after. 63 * 64 */ 65 public class AddColumnJob extends BatchSelectionJob { 66 67 // Key = target GridPane instance 68 // Value = set of target column indexes for this GridPane 69 private final Map<FXOMObject, Set<Integer>> targetGridPanes = new HashMap<>(); 70 private final Position position; 71 72 public AddColumnJob(EditorController editorController, Position position) { 73 super(editorController); 74 assert position == Position.BEFORE || position == Position.AFTER; 75 this.position = position; 76 } 77 78 @Override 79 protected List<Job> makeSubJobs() { 80 final List<Job> result = new ArrayList<>(); 81 82 if (GridPaneJobUtils.canPerformAdd(getEditorController())) { 83 84 // Populate the target GridPane map 85 assert targetGridPanes.isEmpty() == true; 86 final List<FXOMObject> objectList 87 = GridPaneJobUtils.getTargetGridPanes(getEditorController()); 88 for (FXOMObject object : objectList) { 89 final Set<Integer> indexList 90 = getTargetColumnIndexes(getEditorController(), object); 91 targetGridPanes.put(object, indexList); 92 } 93 94 // Add sub jobs 95 // First add the new column constraints 96 final Job addConstraints = new AddColumnConstraintsJob( 97 getEditorController(), position, targetGridPanes); 98 result.add(addConstraints); 99 // Then move the column content 100 result.addAll(moveColumnContent()); 101 } 102 return result; 103 } 104 105 @Override 106 protected String makeDescription() { 107 return "Add Column " + position.name(); //NOI18N 108 } 109 110 @Override 111 protected AbstractSelectionGroup getNewSelectionGroup() { 112 final AbstractSelectionGroup asg; 113 // Update new selection : 114 // - if there is more than 1 GridPane, we select the GridPane instances 115 // - if there is a single GridPane, we select the added columns 116 if (targetGridPanes.size() > 1) { 117 Set<FXOMObject> objects = targetGridPanes.keySet(); 118 asg = new ObjectSelectionGroup(objects, objects.iterator().next(), null); 119 } else { 120 assert targetGridPanes.size() == 1; 121 final FXOMInstance targetGridPane 122 = (FXOMInstance) targetGridPanes.keySet().iterator().next(); 123 final Set<Integer> targetIndexes = targetGridPanes.get(targetGridPane); 124 assert targetIndexes.size() >= 1; 125 final Set<Integer> addedIndexes 126 = GridPaneJobUtils.getAddedIndexes(targetIndexes, position); 127 128 asg = new GridSelectionGroup(targetGridPane, Type.COLUMN, addedIndexes); 129 } 130 return asg; 131 } 132 133 private List<Job> moveColumnContent() { 134 135 final List<Job> result = new ArrayList<>(); 136 137 for (FXOMObject targetGridPane : targetGridPanes.keySet()) { 138 139 final Set<Integer> targetIndexes = targetGridPanes.get(targetGridPane); 140 141 final DesignHierarchyMask mask = new DesignHierarchyMask(targetGridPane); 142 final int columnsSize = mask.getColumnsSize(); 143 final Iterator<Integer> iterator = targetIndexes.iterator(); 144 145 int shiftIndex = 0; 146 int targetIndex = iterator.next(); 147 while (targetIndex != -1) { 148 // Move the columns content : 149 // - from the target index 150 // - to the next target index if any or the last column index otherwise 151 int fromIndex, toIndex; 152 153 switch (position) { 154 case BEFORE: 155 // fromIndex included 156 // toIndex excluded 157 fromIndex = targetIndex; 158 if (iterator.hasNext()) { 159 targetIndex = iterator.next(); 160 toIndex = targetIndex - 1; 161 } else { 162 targetIndex = -1; 163 toIndex = columnsSize - 1; 164 } 165 break; 166 case AFTER: 167 // fromIndex excluded 168 // toIndex included 169 fromIndex = targetIndex + 1; 170 if (iterator.hasNext()) { 171 targetIndex = iterator.next(); 172 toIndex = targetIndex; 173 } else { 174 targetIndex = -1; 175 toIndex = columnsSize - 1; 176 } 177 break; 178 default: 179 assert false; 180 return result; 181 } 182 183 // If fromIndex >= columnsSize, we are below the last existing column 184 // => no column content to move 185 if (fromIndex < columnsSize) { 186 final int offset = 1 + shiftIndex; 187 final List<Integer> indexes 188 = GridPaneJobUtils.getIndexes(fromIndex, toIndex); 189 final ReIndexColumnContentJob reIndexJob = new ReIndexColumnContentJob( 190 getEditorController(), offset, targetGridPane, indexes); 191 result.add(reIndexJob); 192 } 193 194 shiftIndex++; 195 } 196 } 197 return result; 198 } 199 200 /** 201 * Returns the list of target column indexes for the specified GridPane 202 * instance. 203 * 204 * @return the list of target indexes 205 */ 206 private Set<Integer> getTargetColumnIndexes( 207 final EditorController editorController, 208 final FXOMObject targetGridPane) { 209 210 final Selection selection = editorController.getSelection(); 211 final AbstractSelectionGroup asg = selection.getGroup(); 212 213 final Set<Integer> result = new LinkedHashSet<>(); 214 215 // Selection == GridPane columns 216 // => return the list of selected columns 217 if (asg instanceof GridSelectionGroup 218 && ((GridSelectionGroup) asg).getType() == Type.COLUMN) { 219 final GridSelectionGroup gsg = (GridSelectionGroup) asg; 220 result.addAll(gsg.getIndexes()); 221 } // 222 // Selection == GridPanes or Selection == GridPane rows 223 // => return either the first (BEFORE) or the last (AFTER) column index 224 else { 225 switch (position) { 226 case BEFORE: 227 result.add(0); 228 break; 229 case AFTER: 230 final DesignHierarchyMask mask 231 = new DesignHierarchyMask(targetGridPane); 232 final int size = mask.getColumnsSize(); 233 result.add(size - 1); 234 break; 235 default: 236 assert false; 237 break; 238 } 239 } 240 241 return result; 242 } 243 }