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 }