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.BatchDocumentJob;
  36 import com.oracle.javafx.scenebuilder.kit.editor.job.Job;
  37 import com.oracle.javafx.scenebuilder.kit.editor.job.JobUtils;
  38 import com.oracle.javafx.scenebuilder.kit.editor.job.gridpane.GridPaneJobUtils.Position;
  39 import com.oracle.javafx.scenebuilder.kit.editor.job.atomic.AddPropertyJob;
  40 import com.oracle.javafx.scenebuilder.kit.editor.job.atomic.AddPropertyValueJob;
  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.fxom.FXOMProperty;
  45 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMPropertyC;
  46 import com.oracle.javafx.scenebuilder.kit.metadata.util.DesignHierarchyMask;
  47 import com.oracle.javafx.scenebuilder.kit.metadata.util.PropertyName;
  48 import java.util.ArrayList;
  49 import java.util.LinkedHashSet;
  50 import java.util.List;
  51 import java.util.Map;
  52 import java.util.Set;
  53 import javafx.scene.layout.ColumnConstraints;
  54 
  55 /**
  56  * Job invoked when adding column constraints.
  57  */
  58 public class AddColumnConstraintsJob extends BatchDocumentJob {
  59 
  60     // Key = target GridPane instance
  61     // Value = list of target column indexes for this GridPane
  62     private final Map<FXOMObject, Set<Integer>> targetGridPanes;
  63     private final Position position;
  64     // If the selected column is associated to an existing constraints,
  65     // we duplicate the existing constraints.
  66     // Otherwise, we use the default values below.
  67     private static final double defaultMinWidth = 10.0;
  68     private static final double defaultPrefWidth = 100.0;
  69 
  70     public AddColumnConstraintsJob(
  71             final EditorController editorController,
  72             final Position position,
  73             final Map<FXOMObject, Set<Integer>> targetGridPanes) {
  74         super(editorController);
  75         this.position = position;
  76         this.targetGridPanes = targetGridPanes;
  77     }
  78 
  79     @Override
  80     protected List<Job> makeSubJobs() {
  81 
  82         final List<Job> result = new ArrayList<>();
  83 
  84         // Add column constraints job
  85         assert targetGridPanes.isEmpty() == false;
  86         for (FXOMObject targetGridPane : targetGridPanes.keySet()) {
  87             assert targetGridPane instanceof FXOMInstance;
  88             final Set<Integer> targetIndexes = targetGridPanes.get(targetGridPane);
  89             result.addAll(addColumnConstraints((FXOMInstance) targetGridPane, targetIndexes));
  90         }
  91 
  92         return result;
  93     }
  94 
  95     @Override
  96     protected String makeDescription() {
  97         return "Add Column Constraints"; //NOI18N
  98     }
  99 
 100     private Set<Job> addColumnConstraints(
 101             final FXOMInstance targetGridPane,
 102             final Set<Integer> targetIndexes) {
 103 
 104         final Set<Job> result = new LinkedHashSet<>();
 105         final FXOMDocument fxomDocument = getEditorController().getFxomDocument();
 106 
 107         // Retrieve the constraints property for the specified target GridPane
 108         final PropertyName propertyName = new PropertyName("columnConstraints"); //NOI18N
 109         FXOMProperty constraintsProperty = targetGridPane.getProperties().get(propertyName);
 110         if (constraintsProperty == null) {
 111             constraintsProperty = new FXOMPropertyC(fxomDocument, propertyName);
 112         }
 113         assert constraintsProperty instanceof FXOMPropertyC;
 114 
 115         final DesignHierarchyMask mask = new DesignHierarchyMask(targetGridPane);
 116 
 117         int shiftIndex = 0;
 118         int constraintsSize = mask.getColumnsConstraintsSize();
 119         for (int targetIndex : targetIndexes) {
 120 
 121             // Retrieve the index for the new constraints to be added
 122             int addedIndex = targetIndex + shiftIndex;
 123             if (position == Position.AFTER) {
 124                 addedIndex++;
 125             }
 126 
 127             final FXOMObject targetConstraints
 128                     = mask.getColumnConstraintsAtIndex(targetIndex);
 129             // The target index is associated to an existing constraints value :
 130             // we add a new constraints using the values of the existing one
 131             if (targetConstraints != null) {
 132                 assert targetConstraints instanceof FXOMInstance;
 133                 // Create new constraints instance with same values as the target one
 134                 final FXOMInstance addedConstraints = makeColumnConstraintsInstance(
 135                         (FXOMInstance) targetConstraints);
 136 
 137                 final Job addValueJob = new AddPropertyValueJob(
 138                         addedConstraints,
 139                         (FXOMPropertyC) constraintsProperty,
 140                         addedIndex, getEditorController());
 141                 result.add(addValueJob);
 142             } //
 143             // The target index is not associated to an existing constraints value :
 144             // - we add new empty constraints from the last existing one to the added index (excluded)
 145             // - we add a new constraints with default values for the added index
 146             else {
 147                 for (int index = constraintsSize; index < addedIndex; index++) {
 148                     // Create new empty constraints for the exisiting columns
 149                     final FXOMInstance addedConstraints = makeColumnConstraintsInstance();
 150                     final Job addValueJob = new AddPropertyValueJob(
 151                             addedConstraints,
 152                             (FXOMPropertyC) constraintsProperty,
 153                             index, getEditorController());
 154                     result.add(addValueJob);
 155                 }
 156                 // Create new constraints with default values for the new added column
 157                 final FXOMInstance addedConstraints = makeColumnConstraintsInstance();
 158                 JobUtils.setMinWidth(addedConstraints, ColumnConstraints.class, defaultMinWidth);
 159                 JobUtils.setPrefWidth(addedConstraints, ColumnConstraints.class, defaultPrefWidth);
 160                 final Job addValueJob = new AddPropertyValueJob(
 161                         addedConstraints,
 162                         (FXOMPropertyC) constraintsProperty,
 163                         addedIndex, getEditorController());
 164                 result.add(addValueJob);
 165                 constraintsSize = addedIndex + 1;
 166             }
 167             shiftIndex++;
 168         }
 169 
 170         // Add the constraints property to the target GridPane if not already there.
 171         // IMPORTANT :
 172         // Note that the AddPropertyJob must be called after the AddPropertyValueJob.
 173         if (constraintsProperty.getParentInstance() == null) {
 174             final Job addPropertyJob = new AddPropertyJob(
 175                     constraintsProperty,
 176                     targetGridPane,
 177                     -1, getEditorController());
 178             result.add(addPropertyJob);
 179         }
 180 
 181         return result;
 182     }
 183 
 184     private FXOMInstance makeColumnConstraintsInstance() {
 185 
 186         // Create new constraints instance
 187         final FXOMDocument newDocument = new FXOMDocument();
 188         final FXOMInstance result
 189                 = new FXOMInstance(newDocument, ColumnConstraints.class);
 190         newDocument.setFxomRoot(result);
 191         result.moveToFxomDocument(getEditorController().getFxomDocument());
 192 
 193         return result;
 194     }
 195 
 196     private FXOMInstance makeColumnConstraintsInstance(final FXOMInstance constraints) {
 197 
 198         assert constraints != null;
 199         assert constraints.getDeclaredClass() == ColumnConstraints.class;
 200 
 201         // Create new constraints instance
 202         final FXOMInstance result = makeColumnConstraintsInstance();
 203 
 204         // Set the new column constraints values with the values of the specified instance
 205         final boolean fillWidth = JobUtils.getFillWidth(constraints, ColumnConstraints.class);
 206         final double maxWidth = JobUtils.getMaxWidth(constraints, ColumnConstraints.class);
 207         final double minWidth = JobUtils.getMinWidth(constraints, ColumnConstraints.class);
 208         final double percentWidth = JobUtils.getPercentWidth(constraints, ColumnConstraints.class);
 209         final double prefWidth = JobUtils.getPrefWidth(constraints, ColumnConstraints.class);
 210         final String halignment = JobUtils.getHAlignment(constraints, ColumnConstraints.class);
 211         final String hgrow = JobUtils.getHGrow(constraints, ColumnConstraints.class);
 212 
 213         JobUtils.setFillWidth(result, ColumnConstraints.class, fillWidth);
 214         JobUtils.setMaxWidth(result, ColumnConstraints.class, maxWidth);
 215         // If the existing constraints minWidth is too small, we use the default one
 216         JobUtils.setMinWidth(result, ColumnConstraints.class, Math.max(minWidth, defaultMinWidth));
 217         JobUtils.setPercentWidth(result, ColumnConstraints.class, percentWidth);
 218         // If the existing constraints prefWidth is too small, we use the default one
 219         JobUtils.setPrefWidth(result, ColumnConstraints.class, Math.max(prefWidth, defaultPrefWidth));
 220         JobUtils.setHAlignment(result, ColumnConstraints.class, halignment);
 221         JobUtils.setHGrow(result, ColumnConstraints.class, hgrow);
 222 
 223         return result;
 224     }
 225 }