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.panel.inspector.editors;
  33 
  34 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMInstance;
  35 import com.oracle.javafx.scenebuilder.kit.metadata.property.ValuePropertyMetadata;
  36 import com.oracle.javafx.scenebuilder.kit.metadata.property.value.DoublePropertyMetadata;
  37 
  38 import java.util.ArrayList;
  39 import java.util.List;
  40 import java.util.Set;
  41 
  42 import javafx.beans.value.ChangeListener;
  43 import javafx.event.ActionEvent;
  44 import javafx.event.EventHandler;
  45 import javafx.fxml.FXML;
  46 import javafx.scene.Node;
  47 import javafx.scene.Parent;
  48 import javafx.scene.control.TextField;
  49 import javafx.scene.control.ToggleButton;
  50 import javafx.scene.layout.Region;
  51 
  52 /**
  53  * Editor for AnchorPane constraints.
  54  *
  55  *
  56  */
  57 public class AnchorPaneConstraintsEditor extends PropertiesEditor {
  58 
  59     private static final String ANCHOR_ENABLED_COLOR = "-sb-line-art-accent";
  60     private static final String ANCHOR_DISABLED_COLOR = "-sb-line-art";
  61 
  62     @FXML
  63     private ToggleButton bottomTb;
  64     @FXML
  65     private TextField bottomTf;
  66     @FXML
  67     private Region innerR;
  68     @FXML
  69     private ToggleButton leftTb;
  70     @FXML
  71     private TextField leftTf;
  72     @FXML
  73     private Region outerR;
  74     @FXML
  75     private ToggleButton rightTb;
  76     @FXML
  77     private TextField rightTf;
  78     @FXML
  79     private ToggleButton topTb;
  80     @FXML
  81     private TextField topTf;
  82 
  83     private Parent root = null;
  84     private final ArrayList<ConstraintEditor> contraintEditors = new ArrayList<>();
  85     private ChangeListener<Object> constraintListener;
  86 
  87     public AnchorPaneConstraintsEditor(String name, ValuePropertyMetadata topPropMeta,
  88             ValuePropertyMetadata rightPropMeta,
  89             ValuePropertyMetadata bottomPropMeta,
  90             ValuePropertyMetadata leftPropMeta,
  91             Set<FXOMInstance> selectedInstances) {
  92         super(name);
  93         initialize(topPropMeta, rightPropMeta, bottomPropMeta, leftPropMeta, selectedInstances);
  94         propertyChanged();
  95         styleRegions();
  96     }
  97 
  98     // Method to please findBugs
  99     private void initialize(ValuePropertyMetadata topPropMeta, ValuePropertyMetadata rightPropMeta,
 100             ValuePropertyMetadata bottomPropMeta, ValuePropertyMetadata leftPropMeta, Set<FXOMInstance> selectedInstances) {
 101         root = EditorUtils.loadFxml("AnchorPaneConstraintsEditor.fxml", this);
 102 
 103         constraintListener = (ov, prevValue, newValue) -> {
 104             propertyChanged();
 105             styleRegions();
 106         };
 107 
 108         contraintEditors.add(
 109                 new ConstraintEditor(topTf, topTb, selectedInstances, topPropMeta, constraintListener));
 110         contraintEditors.add(
 111                 new ConstraintEditor(rightTf, rightTb, selectedInstances, rightPropMeta, constraintListener));
 112         contraintEditors.add(
 113                 new ConstraintEditor(bottomTf, bottomTb, selectedInstances, bottomPropMeta, constraintListener));
 114         contraintEditors.add(
 115                 new ConstraintEditor(leftTf, leftTb, selectedInstances, leftPropMeta, constraintListener));
 116     }
 117 
 118     @Override
 119     public List<PropertyEditor> getPropertyEditors() {
 120         List<PropertyEditor> propertyEditors = new ArrayList<>();
 121         for (ConstraintEditor constraintEditor : contraintEditors) {
 122             propertyEditors.add(constraintEditor);
 123         }
 124         return propertyEditors;
 125     }
 126 
 127     public void reset(ValuePropertyMetadata topPropMeta,
 128             ValuePropertyMetadata rightPropMeta,
 129             ValuePropertyMetadata bottomPropMeta,
 130             ValuePropertyMetadata leftPropMeta,
 131             Set<FXOMInstance> selectedInstances) {
 132         contraintEditors.get(0).reset(selectedInstances, topPropMeta);
 133         contraintEditors.get(1).reset(selectedInstances, rightPropMeta);
 134         contraintEditors.get(2).reset(selectedInstances, bottomPropMeta);
 135         contraintEditors.get(3).reset(selectedInstances, leftPropMeta);
 136         for (int ii = 0; ii < 4; ii++) {
 137             contraintEditors.get(ii).addValueListener(constraintListener);
 138         }
 139         styleRegions();
 140     }
 141 
 142     @Override
 143     public Node getValueEditor() {
 144         return root;
 145     }
 146 
 147     private void styleRegions() {
 148         StringBuilder styleString = new StringBuilder();
 149         for (int ii = 0; ii < 4; ii++) {
 150             if (contraintEditors.get(ii).isAnchorEnabled()) {
 151                 styleString.append(ANCHOR_ENABLED_COLOR);
 152                 styleString.append(" ");
 153             } else {
 154                 styleString.append(ANCHOR_DISABLED_COLOR);
 155                 styleString.append(" ");
 156             }
 157         }
 158         String style = "-fx-border-color: " + styleString;
 159         innerR.setStyle(style);
 160         outerR.setStyle(style);
 161     }
 162 
 163     /*
 164      * Editor for a single constraint (e.g. topAnchor)
 165      */
 166     private static class ConstraintEditor extends PropertyEditor {
 167 
 168         private ToggleButton toggleButton;
 169         private TextField textField;
 170         private Set<FXOMInstance> selectedInstances;
 171         private ValuePropertyMetadata propMeta;
 172 
 173         private boolean updateFromTextField = false;
 174 
 175         public ConstraintEditor(TextField textField, ToggleButton toggleButton, Set<FXOMInstance> selectedInstances,
 176                 ValuePropertyMetadata propMeta, ChangeListener<Object> listener) {
 177             super(propMeta, null);
 178             super.addValueListener(listener);
 179             this.textField = textField;
 180             this.toggleButton = toggleButton;
 181             this.selectedInstances = selectedInstances;
 182             this.propMeta = propMeta;
 183 
 184             initialize();
 185         }
 186 
 187         private void initialize() {
 188             //
 189             // Text field
 190             //
 191             // For SQE tests
 192             textField.setId(EditorUtils.toDisplayName(propMeta.getName().getName()) + " Value"); //NOI18N
 193             EventHandler<ActionEvent> valueListener = event -> {
 194                 if (isHandlingError()) {
 195                     // Event received because of focus lost due to error dialog
 196                     return;
 197                 }
 198                 String valStr = textField.getText();
 199                 if (valStr == null || valStr.isEmpty()) {
 200                     if (toggleButton.isSelected()) {
 201                         updateFromTextField = true;
 202                         toggleButton.setSelected(false);
 203                         updateFromTextField = false;
 204                     }
 205                     userUpdateValueProperty(null);
 206                     return;
 207                 }
 208                 textField.selectAll();
 209                 double valDouble;
 210                 try {
 211                     valDouble = Double.parseDouble(valStr);
 212                 } catch (NumberFormatException e) {
 213                     handleInvalidValue(valStr, textField);
 214                     return;
 215                 }
 216                 if (!((DoublePropertyMetadata) getPropertyMeta()).isValidValue(valDouble)) {
 217                     handleInvalidValue(valDouble, textField);
 218                     return;
 219                 }
 220                 if (!toggleButton.isSelected()) {
 221                     updateFromTextField = true;
 222                     toggleButton.setSelected(true);
 223                     updateFromTextField = false;
 224                 }
 225                 userUpdateValueProperty(valDouble);
 226             };
 227             setNumericEditorBehavior(this, textField, valueListener, false);
 228             // Override default promptText
 229             textField.setPromptText(""); //NOI18N
 230 
 231             textField.setOnMouseClicked(t -> ConstraintEditor.this.toggleButton.setSelected(true));
 232 
 233             //
 234             // Toggle button
 235             //
 236             assert propMeta instanceof DoublePropertyMetadata;
 237 
 238             toggleButton.selectedProperty().addListener((ChangeListener<Boolean>) (ov, prevSel, newSel) -> {
 239 //                System.out.println("toggleButton : selectedProperty changed!");
 240                 if (isUpdateFromModel() || updateFromTextField) {
 241                     // nothing to do
 242                     return;
 243                 }
 244 
 245                 // Update comes from toggleButton.
 246                 if (newSel) {
 247                     // Anchor selected : compute its value from the selected node
 248                     double anchor = 0;
 249                     String propName = ConstraintEditor.this.propMeta.getName().toString();
 250                     switch (propName) {
 251                         // For the moment, we don't support multi-selection with different anchors:
 252                         // the first instance anchor only is used.
 253                         case topAnchorPropName:
 254                             anchor = EditorUtils.computeTopAnchor(getFirstInstance());
 255                             break;
 256                         case rightAnchorPropName:
 257                             anchor = EditorUtils.computeRightAnchor(getFirstInstance());
 258                             break;
 259                         case bottomAnchorPropName:
 260                             anchor = EditorUtils.computeBottomAnchor(getFirstInstance());
 261                             break;
 262                         case leftAnchorPropName:
 263                             anchor = EditorUtils.computeLeftAnchor(getFirstInstance());
 264                             break;
 265                         default:
 266                             assert false;
 267                     }
 268                     textField.setText(EditorUtils.valAsStr(anchor));
 269                     userUpdateValueProperty(getValue());
 270                 } else {
 271                     // Anchor unselected
 272                     textField.setText(null);
 273                     userUpdateValueProperty(null);
 274                 }
 275             });
 276         }
 277 
 278         @Override
 279         public Node getValueEditor() {
 280             // Should not be called
 281             assert false;
 282             return null;
 283         }
 284 
 285         @Override
 286         public Object getValue() {
 287             String valStr = textField.getText();
 288             if (valStr == null || valStr.isEmpty()) {
 289                 return null;
 290             }
 291             return Double.valueOf(valStr);
 292         }
 293 
 294         @Override
 295         public void setValue(Object value) {
 296             setValueGeneric(value);
 297             if (isSetValueDone()) {
 298                 return;
 299             }
 300 
 301             if (value == null) {
 302                 toggleButton.setSelected(false);
 303                 textField.setText(null);
 304             } else {
 305                 assert (value instanceof Double);
 306                 toggleButton.setSelected(true);
 307                 textField.setText(EditorUtils.valAsStr(value));
 308                 if (textField.isFocused()) {
 309                     textField.positionCaret(textField.getLength());
 310                 }
 311             }
 312         }
 313 
 314         public void reset(Set<FXOMInstance> selectedInstances, ValuePropertyMetadata propMeta) {
 315             super.reset(propMeta, null);
 316             this.selectedInstances = selectedInstances;
 317             this.propMeta = propMeta;
 318             textField.setPromptText(null);
 319         }
 320 
 321         @Override
 322         protected void valueIsIndeterminate() {
 323             handleIndeterminate(textField);
 324         }
 325 
 326         public boolean isAnchorEnabled() {
 327             return valueProperty().getValue() != null;
 328         }
 329 
 330         @Override
 331         public void requestFocus() {
 332             EditorUtils.doNextFrame(() -> textField.requestFocus());
 333         }
 334 
 335         private FXOMInstance getFirstInstance() {
 336             return (FXOMInstance) selectedInstances.toArray()[0];
 337         }
 338     }
 339 }