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.job.atomic.AddPropertyValueJob; 39 import com.oracle.javafx.scenebuilder.kit.editor.job.atomic.RemoveObjectJob; 40 import com.oracle.javafx.scenebuilder.kit.editor.selection.AbstractSelectionGroup; 41 import com.oracle.javafx.scenebuilder.kit.editor.selection.GridSelectionGroup; 42 import com.oracle.javafx.scenebuilder.kit.editor.selection.Selection; 43 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMDocument; 44 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMInstance; 45 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMObject; 46 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMProperty; 47 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMPropertyC; 48 import com.oracle.javafx.scenebuilder.kit.metadata.util.DesignHierarchyMask; 49 import com.oracle.javafx.scenebuilder.kit.metadata.util.PropertyName; 50 import java.util.ArrayList; 51 import java.util.HashSet; 52 import java.util.List; 53 import java.util.Set; 54 import javafx.scene.layout.ColumnConstraints; 55 56 /** 57 * Job invoked when moving columns BEFORE or AFTER. 58 */ 59 public class MoveColumnJob extends BatchSelectionJob { 60 61 private FXOMObject targetGridPane; 62 private final List<Integer> targetIndexes = new ArrayList<>(); 63 private final GridPaneJobUtils.Position position; 64 65 public MoveColumnJob(final EditorController editorController, final Position position) { 66 super(editorController); 67 assert position == Position.BEFORE || position == Position.AFTER; 68 this.position = position; 69 } 70 71 @Override 72 protected List<Job> makeSubJobs() { 73 74 final List<Job> result = new ArrayList<>(); 75 76 if (GridPaneJobUtils.canPerformMove(getEditorController(), position)) { 77 78 // Retrieve the target GridPane 79 final Selection selection = getEditorController().getSelection(); 80 final AbstractSelectionGroup asg = selection.getGroup(); 81 assert asg instanceof GridSelectionGroup; // Because of (1) 82 final GridSelectionGroup gsg = (GridSelectionGroup) asg; 83 84 targetGridPane = gsg.getParentObject(); 85 targetIndexes.addAll(gsg.getIndexes()); 86 87 // Add sub jobs 88 // First move the column constraints 89 result.addAll(moveColumnConstraints()); 90 // Then move the column content 91 result.addAll(moveColumnContent()); 92 } 93 return result; 94 } 95 96 @Override 97 protected String makeDescription() { 98 return "Move Column " + position.name(); //NOI18N 99 } 100 101 @Override 102 protected AbstractSelectionGroup getNewSelectionGroup() { 103 final Set<Integer> movedIndexes = new HashSet<>(); 104 for (int targetIndex : targetIndexes) { 105 int movedIndex = position == Position.BEFORE 106 ? targetIndex - 1 : targetIndex + 1; 107 movedIndexes.add(movedIndex); 108 } 109 return new GridSelectionGroup(targetGridPane, GridSelectionGroup.Type.COLUMN, movedIndexes); 110 } 111 112 private List<Job> moveColumnConstraints() { 113 114 final List<Job> result = new ArrayList<>(); 115 116 // Retrieve the constraints property for the specified target GridPane 117 final PropertyName propertyName = new PropertyName("columnConstraints"); //NOI18N 118 assert targetGridPane instanceof FXOMInstance; 119 FXOMProperty constraintsProperty 120 = ((FXOMInstance) targetGridPane).getProperties().get(propertyName); 121 // GridPane has no constraints property => no constraints to move 122 if (constraintsProperty == null) { 123 return result; 124 } 125 126 final DesignHierarchyMask mask = new DesignHierarchyMask(targetGridPane); 127 for (int targetIndex : targetIndexes) { 128 129 final int positionIndex; 130 switch (position) { 131 case BEFORE: 132 positionIndex = targetIndex - 1; 133 break; 134 case AFTER: 135 positionIndex = targetIndex + 1; 136 break; 137 default: 138 assert false; 139 return result; 140 } 141 142 // Retrieve the target constraints 143 final FXOMObject targetConstraints 144 = mask.getColumnConstraintsAtIndex(targetIndex); 145 146 // The target index is associated to an existing constraints value : 147 // we remove the target constraints and add it back at new position 148 // No need to move the constraints of the column before/after : 149 // indeed, they are automatically shifted while updating the target ones 150 if (targetConstraints != null) { 151 // First remove current target constraints 152 final Job removeValueJob = new RemoveObjectJob( 153 targetConstraints, 154 getEditorController()); 155 result.add(removeValueJob); 156 157 // Then add the target constraints at new positionIndex 158 final Job addValueJob = new AddPropertyValueJob( 159 targetConstraints, 160 (FXOMPropertyC) constraintsProperty, 161 positionIndex, getEditorController()); 162 result.add(addValueJob); 163 }// 164 // The target index is not associated to an existing constraints value : 165 // we may need to move the constraints before the target one if any 166 else if (position == Position.BEFORE) { 167 // Retrieve the constraints before the target one 168 final FXOMObject beforeConstraints 169 = mask.getColumnConstraintsAtIndex(targetIndex - 1); 170 171 // The index before is associated to an existing constraints value : 172 // we insert a new constraints with default values at the position index 173 if (beforeConstraints != null) { 174 // Create new empty constraints for the target column 175 final FXOMInstance addedConstraints = makeColumnConstraintsInstance(); 176 final Job addValueJob = new AddPropertyValueJob( 177 addedConstraints, 178 (FXOMPropertyC) constraintsProperty, 179 positionIndex, getEditorController()); 180 result.add(addValueJob); 181 } 182 } 183 } 184 return result; 185 } 186 187 private List<Job> moveColumnContent() { 188 189 final List<Job> result = new ArrayList<>(); 190 191 for (int targetIndex : targetIndexes) { 192 193 switch (position) { 194 case BEFORE: 195 // First move the target column content 196 result.add(new ReIndexColumnContentJob( 197 getEditorController(), 198 -1, targetGridPane, targetIndex)); 199 int beforeIndex = targetIndex - 1; 200 // Then move the content of the column before the target one 201 // If the index before is not part of the target indexes (selected indexes), 202 // we move the column content as many times as consecutive target indexes 203 if (targetIndexes.contains(beforeIndex) == false) { 204 int shiftIndex = 1; 205 while (targetIndexes.contains(targetIndex + shiftIndex)) { 206 shiftIndex++; 207 } 208 result.add(new ReIndexColumnContentJob( 209 getEditorController(), 210 shiftIndex, targetGridPane, beforeIndex)); 211 } 212 break; 213 case AFTER: 214 // First move the target column content 215 result.add(new ReIndexColumnContentJob( 216 getEditorController(), 217 +1, targetGridPane, targetIndex)); 218 int afterIndex = targetIndex + 1; 219 // Then move the content of the column after the target one 220 // If the index after is not part of the target indexes (selected indexes), 221 // we move the column content as many times as consecutive target indexes 222 if (targetIndexes.contains(afterIndex) == false) { 223 int shiftIndex = -1; 224 while (targetIndexes.contains(targetIndex + shiftIndex)) { 225 shiftIndex--; 226 } 227 result.add(new ReIndexColumnContentJob( 228 getEditorController(), 229 shiftIndex, targetGridPane, afterIndex)); 230 } 231 break; 232 default: 233 assert false; 234 } 235 } 236 return result; 237 } 238 239 private FXOMInstance makeColumnConstraintsInstance() { 240 241 // Create new constraints instance 242 final FXOMDocument newDocument = new FXOMDocument(); 243 final FXOMInstance result 244 = new FXOMInstance(newDocument, ColumnConstraints.class); 245 newDocument.setFxomRoot(result); 246 result.moveToFxomDocument(getEditorController().getFxomDocument()); 247 248 return result; 249 } 250 }