1 /* 2 * Copyright (c) 2008, 2016, 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 ensemble.samplepage; 33 34 import ensemble.SampleInfo; 35 import ensemble.playground.PlaygroundProperty; 36 import java.lang.reflect.Field; 37 import java.lang.reflect.InvocationTargetException; 38 import java.text.DecimalFormat; 39 import java.util.logging.Level; 40 import java.util.logging.Logger; 41 import javafx.beans.InvalidationListener; 42 import javafx.beans.Observable; 43 import javafx.beans.property.BooleanProperty; 44 import javafx.beans.property.DoubleProperty; 45 import javafx.beans.property.IntegerProperty; 46 import javafx.beans.property.ObjectProperty; 47 import javafx.beans.property.Property; 48 import javafx.beans.property.StringProperty; 49 import javafx.beans.value.ChangeListener; 50 import javafx.beans.value.ObservableValue; 51 import javafx.collections.FXCollections; 52 import javafx.collections.ObservableList; 53 import javafx.geometry.Insets; 54 import javafx.geometry.Orientation; 55 import javafx.geometry.Pos; 56 import javafx.scene.Node; 57 import javafx.scene.chart.PieChart; 58 import javafx.scene.chart.XYChart; 59 import javafx.scene.control.CheckBox; 60 import javafx.scene.control.ChoiceBox; 61 import javafx.scene.control.ComboBox; 62 import javafx.scene.control.ContentDisplay; 63 import javafx.scene.control.Label; 64 import javafx.scene.control.OverrunStyle; 65 import javafx.scene.control.ScrollPane; 66 import javafx.scene.control.Separator; 67 import javafx.scene.control.Slider; 68 import javafx.scene.control.Tab; 69 import javafx.scene.control.TabPane; 70 import javafx.scene.control.TextField; 71 import javafx.scene.layout.GridPane; 72 import javafx.scene.layout.HBox; 73 import javafx.scene.layout.Priority; 74 import javafx.scene.layout.Region; 75 import javafx.scene.paint.Color; 76 import javafx.scene.paint.Paint; 77 import javafx.scene.shape.Rectangle; 78 import javafx.util.Callback; 79 import javafx.util.StringConverter; 80 81 /** 82 * 83 */ 84 class PlaygroundTabs extends TabPane { 85 private final SamplePage samplePage; 86 private final GridPane grid; 87 private final Tab propertiesTab; 88 private final Tab dataTab; 89 90 PlaygroundTabs(final SamplePage samplePage) { 91 this.samplePage = samplePage; 92 grid = new GridPane(); 93 grid.setHgap(SamplePage.INDENT); 94 grid.setVgap(SamplePage.INDENT); 95 grid.setPadding(new Insets(SamplePage.INDENT)); 96 getStyleClass().add("floating"); 97 ScrollPane scrollPane = new ScrollPane(grid); 98 scrollPane.getStyleClass().clear(); 99 setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE); 100 propertiesTab = new Tab("Properties"); 101 propertiesTab.setContent(scrollPane); 102 dataTab = new Tab("Data"); 103 getTabs().addAll(propertiesTab, dataTab); 104 setMinSize(100, 100); 105 samplePage.registerSampleInfoUpdater(new Callback<SampleInfo, Void>() { 106 107 @Override 108 public Void call(SampleInfo sampleInfo) { 109 update(sampleInfo); 110 return null; 111 } 112 }); 113 } 114 115 private PropertyController newPropertyController(PlaygroundProperty playgroundProperty, Object object, Object property) { 116 if (playgroundProperty.propertyName.equals("getStrokeDashArray")) { 117 return new StrokeDashArrayPropertyController(playgroundProperty, object, (ObservableList<Double>) property); 118 } 119 if (property instanceof DoubleProperty) { 120 DoubleProperty prop = (DoubleProperty) property; 121 return new DoublePropertyController(playgroundProperty, object, prop); 122 } else if (property instanceof IntegerProperty) { 123 IntegerProperty prop = (IntegerProperty) property; 124 return new IntegerPropertyController(playgroundProperty, object, prop); 125 } else if (property instanceof BooleanProperty) { 126 BooleanProperty prop = (BooleanProperty) property; 127 return new BooleanPropertyController(playgroundProperty, object, prop); 128 } else if (property instanceof StringProperty) { 129 StringProperty prop = (StringProperty) property; 130 return new StringPropertyController(playgroundProperty, object, prop); 131 } else if (property instanceof ObjectProperty) { 132 final ObjectProperty prop = (ObjectProperty) property; 133 if (prop.get() instanceof Color) { 134 return new ColorPropertyController(playgroundProperty, object, prop); 135 } 136 if (prop.get() instanceof String) { 137 return new StringPropertyController(playgroundProperty, object, prop); 138 } 139 if (prop.get() != null && prop.get().getClass().isEnum()) { 140 return new EnumPropertyController(playgroundProperty, object, prop, (Enum) prop.get()); 141 } 142 } 143 return null; 144 } 145 146 private void update(SampleInfo sampleInfo) { 147 grid.getChildren().clear(); 148 int rowIndex = 0; 149 boolean needsDataTab = false; 150 for (PlaygroundProperty prop : sampleInfo.playgroundProperties) { 151 try { 152 Object object = samplePage.sampleRuntimeInfoProperty.get().getApp(); 153 if (prop.fieldName != null) { 154 Field declaredField = samplePage.sampleRuntimeInfoProperty.get().getClz().getDeclaredField(prop.fieldName); 155 declaredField.setAccessible(true); 156 object = declaredField.get(object); 157 } 158 Object property = null; 159 if (prop.propertyName.startsWith("-")) { 160 Label sectionLabel = new Label(prop.properties.get("name")); 161 Separator separator1 = new Separator(Orientation.HORIZONTAL); 162 HBox.setHgrow(separator1, Priority.ALWAYS); 163 Separator separator2 = new Separator(Orientation.HORIZONTAL); 164 HBox.setHgrow(separator2, Priority.ALWAYS); 165 HBox separator = new HBox(separator1, sectionLabel, separator2); 166 separator.setAlignment(Pos.CENTER); 167 grid.addRow(rowIndex++, separator); 168 GridPane.setColumnSpan(separator, 3); 169 if (rowIndex > 1) { 170 GridPane.setMargin(separator, new Insets(15, 0, 0, 0)); 171 } 172 continue; 173 } 174 if (prop.propertyName.startsWith("get")) { 175 property = object.getClass().getMethod(prop.propertyName).invoke(object); 176 } else { 177 property = object.getClass().getMethod(prop.propertyName + "Property").invoke(object); 178 } 179 if (object instanceof XYChart && prop.propertyName.equals("data")) { 180 needsDataTab = true; 181 dataTab.setContent(new XYDataVisualizer((XYChart) object)); 182 } else if (object instanceof PieChart && prop.propertyName.equals("data")) { 183 needsDataTab = true; 184 dataTab.setContent(new PieChartDataVisualizer((PieChart) object)); 185 } else { 186 PropertyController controller = newPropertyController(prop, object, property); 187 if (controller != null) { 188 Region controllerNode = controller.getController(); 189 grid.addRow(rowIndex++, controller.getLabel(), controllerNode, controller.getPreview()); 190 controllerNode.maxWidthProperty().bind(widthProperty()); 191 GridPane.setHgrow(controllerNode, Priority.ALWAYS); 192 } else { 193 System.err.println("Warning! The following property doesn't have corresponding controller: " + prop); 194 } 195 } 196 } catch (InvocationTargetException ex) { 197 Logger.getLogger(SamplePage.class.getName()).log(Level.SEVERE, null, ex); 198 } catch (NoSuchMethodException ex) { 199 Logger.getLogger(SamplePage.class.getName()).log(Level.SEVERE, null, ex); 200 } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) { 201 Logger.getLogger(SamplePage.class.getName()).log(Level.SEVERE, null, ex); 202 } 203 } 204 if (needsDataTab && !getTabs().contains(dataTab)) { 205 getTabs().add(dataTab); 206 } 207 if (!needsDataTab) { 208 getTabs().remove(dataTab); 209 } 210 } 211 212 private class PropertyController { 213 214 private PlaygroundProperty playgroundProperty; 215 private String name; 216 private Label label; 217 private Region controller; 218 private Node preview; 219 220 public PropertyController(PlaygroundProperty playgroundProperty) { 221 this(playgroundProperty, playgroundProperty.propertyName); 222 } 223 224 public PropertyController(PlaygroundProperty playgroundProperty, String name) { 225 if (playgroundProperty.properties.containsKey("name")) { 226 this.name = playgroundProperty.properties.get("name"); 227 } else { 228 this.name = name; 229 } 230 this.playgroundProperty = playgroundProperty; 231 } 232 233 public Region getLabel() { 234 if (label == null) { 235 label = new Label(name); 236 label.setAlignment(Pos.BASELINE_RIGHT); 237 label.setLabelFor(getController()); 238 label.setTextOverrun(OverrunStyle.ELLIPSIS); 239 label.setMaxWidth(200); 240 } 241 return label; 242 } 243 244 protected void setController(Region controller) { 245 this.controller = controller; 246 } 247 248 protected void setPreview(Node preview) { 249 this.preview = preview; 250 } 251 252 public Region getController() { 253 if (controller == null) { 254 controller = new Region(); 255 } 256 return controller; 257 } 258 259 public Node getPreview() { 260 if (preview == null) { 261 preview = new Region(); 262 } 263 return preview; 264 } 265 266 protected double getProperty(PlaygroundProperty playgroundProperty, String name, double defaultValue) throws NumberFormatException { 267 String value = playgroundProperty.properties.get(name); 268 if (value == null) { 269 return defaultValue; 270 } else { 271 return Double.parseDouble(value); 272 } 273 } 274 } 275 276 private class DoublePropertyController extends PropertyController { 277 public DoublePropertyController(PlaygroundProperty playgroundProperty, Object object, Property<Number> prop) { 278 super(playgroundProperty); 279 Slider slider = new Slider(); 280 slider.setMin(getProperty(playgroundProperty, "min", 0)); 281 slider.setMax(getProperty(playgroundProperty, "max", 100)); 282 double step = getProperty(playgroundProperty, "step", 0); 283 if (step > 0) { 284 slider.setMajorTickUnit(step); 285 slider.setMinorTickCount(0); 286 slider.setSnapToTicks(true); 287 } 288 slider.valueProperty().bindBidirectional(prop); 289 setController(slider); 290 291 TextField preview = new TextField(); 292 preview.setPrefColumnCount(4); 293 preview.textProperty().bindBidirectional(prop, new StringConverter<Number>() { 294 295 @Override 296 public String toString(Number number) { 297 return DecimalFormat.getInstance().format((Double) number); 298 } 299 300 @Override 301 public Number fromString(String string) { 302 try { 303 Number number = DecimalFormat.getInstance().parse(string); 304 return number; 305 } catch (Exception e) { 306 return 0; 307 } 308 } 309 }); 310 setPreview(preview); 311 } 312 } 313 314 private class IntegerPropertyController extends PropertyController { 315 public IntegerPropertyController(PlaygroundProperty playgroundProperty, Object object, Property<Number> prop) { 316 super(playgroundProperty); 317 Slider slider = new Slider(); 318 slider.setMin(getProperty(playgroundProperty, "min", 0)); 319 slider.setMax(getProperty(playgroundProperty, "max", 100)); 320 slider.setSnapToTicks(true); 321 slider.setMajorTickUnit(1); 322 slider.valueProperty().bindBidirectional(prop); 323 setController(slider); 324 325 TextField preview = new TextField(); 326 preview.setPrefWidth(30); 327 preview.textProperty().bindBidirectional(prop, new StringConverter<Number>() { 328 329 @Override 330 public String toString(Number number) { 331 return DecimalFormat.getInstance().format((Integer) number); 332 } 333 334 @Override 335 public Number fromString(String string) { 336 try { 337 Number number = DecimalFormat.getInstance().parse(string); 338 return number; 339 } catch (Exception e) { 340 return 0; 341 } 342 } 343 }); 344 setPreview(preview); 345 } 346 } 347 348 private class BooleanPropertyController extends PropertyController { 349 public BooleanPropertyController(PlaygroundProperty playgroundProperty, Object object, Property<Boolean> prop) { 350 super(playgroundProperty); 351 CheckBox checkbox = new CheckBox(); 352 checkbox.selectedProperty().bindBidirectional(prop); 353 setController(checkbox); 354 } 355 } 356 357 private class StringPropertyController extends PropertyController { 358 359 public StringPropertyController(PlaygroundProperty playgroundProperty, Object object, Property<String> prop) { 360 super(playgroundProperty); 361 TextField textField = new TextField(); 362 textField.textProperty().bindBidirectional(prop); 363 setController(textField); 364 } 365 } 366 367 private class ColorPropertyController extends PropertyController { 368 369 public ColorPropertyController(PlaygroundProperty playgroundProperty, Object object, final Property<Paint> prop) { 370 super(playgroundProperty); 371 372 final Rectangle colorRect = new Rectangle(20, 20, (Color) prop.getValue()); 373 colorRect.setStroke(Color.GRAY); 374 final Label valueLabel = new Label(formatWebColor((Color) prop.getValue())); 375 valueLabel.setGraphic(colorRect); 376 valueLabel.setContentDisplay(ContentDisplay.LEFT); 377 setPreview(valueLabel); 378 379 final SimpleHSBColorPicker colorPicker = new SimpleHSBColorPicker(); 380 colorPicker.getColor().addListener(new InvalidationListener() { 381 @Override 382 public void invalidated(Observable valueModel) { 383 Color c = colorPicker.getColor().get(); 384 prop.setValue(c); 385 valueLabel.setText(formatWebColor(c)); 386 colorRect.setFill(c); 387 } 388 }); 389 setController(colorPicker); 390 } 391 392 private String formatWebColor(Color c) { 393 String r = Integer.toHexString((int) (c.getRed() * 255)); 394 if (r.length() == 1) { 395 r = "0" + r; 396 } 397 String g = Integer.toHexString((int) (c.getGreen() * 255)); 398 if (g.length() == 1) { 399 g = "0" + g; 400 } 401 String b = Integer.toHexString((int) (c.getBlue() * 255)); 402 if (b.length() == 1) { 403 b = "0" + b; 404 } 405 return "#" + r + g + b; 406 } 407 } 408 409 private class EnumPropertyController extends PropertyController { 410 411 public EnumPropertyController(PlaygroundProperty playgroundProperty, Object object, Property prop, final Enum enumeration) { 412 super(playgroundProperty); 413 414 final ChoiceBox choiceBox = new ChoiceBox(); 415 choiceBox.setItems(FXCollections.observableArrayList(enumeration.getClass().getEnumConstants())); 416 choiceBox.getSelectionModel().select(prop.getValue()); 417 prop.bind(choiceBox.getSelectionModel().selectedItemProperty()); 418 setController(choiceBox); 419 } 420 } 421 422 private class StrokeDashArrayPropertyController extends PropertyController { 423 424 public StrokeDashArrayPropertyController(final PlaygroundProperty playgroundProperty, Object object, final ObservableList<Double> list) { 425 super(playgroundProperty, "strokeDashArray"); 426 427 final ComboBox<ObservableList<Double>> comboBox = new ComboBox<>(); 428 comboBox.setEditable(true); 429 comboBox.setItems(FXCollections.observableArrayList( 430 FXCollections.<Double>observableArrayList(100d, 50d), 431 FXCollections.<Double>observableArrayList(0d, 20d), 432 FXCollections.<Double>observableArrayList(20d, 20d), 433 FXCollections.<Double>observableArrayList(30d, 15d, 0d, 15d) 434 )); 435 comboBox.setConverter(new StringConverter<ObservableList<Double>>() { 436 @Override public String toString(ObservableList<Double> t) { 437 if (t == null || t.isEmpty()) { 438 return null; 439 } 440 StringBuilder sb = new StringBuilder(); 441 for (Double d : t) { 442 String str = String.valueOf(d); 443 if (str.endsWith(".0")) { 444 str = str.substring(0, str.length() - 2); 445 } 446 sb.append(str).append(' '); 447 } 448 return sb.substring(0, sb.length() - 1); 449 } 450 451 @Override public ObservableList<Double> fromString(String string) { 452 String[] values = string.trim().split(" +"); 453 ObservableList<Double> res = FXCollections.observableArrayList(); 454 double sum = 0; 455 for (String value : values) { 456 try { 457 double val = Math.min(Math.max(Double.parseDouble(value), 0), 1000); 458 res.add(val); 459 sum += val; 460 if (sum > 5000) { 461 break; 462 } 463 } catch (Exception ignored) { 464 } 465 } 466 if (sum == 0) { 467 res.clear(); 468 } 469 return res; 470 } 471 }); 472 comboBox.valueProperty().addListener(new ChangeListener() { 473 @Override public void changed(ObservableValue ov, Object t, Object key) { 474 ObservableList<Double> value = comboBox.getValue(); 475 list.setAll(value); 476 477 if (value != null && !value.isEmpty() && comboBox.getItems().indexOf(value) == -1) { 478 comboBox.getItems().add(value); 479 } 480 } 481 }); 482 setController(comboBox); 483 } 484 } 485 }