1 /*
   2  * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.javafx.scene.control;
  27 
  28 import com.sun.javafx.scene.control.IntegerField;
  29 import com.sun.javafx.scene.control.WebColorField;
  30 import com.sun.javafx.scene.control.skin.IntegerFieldSkin;
  31 import com.sun.javafx.scene.control.skin.WebColorFieldSkin;
  32 import javafx.beans.InvalidationListener;
  33 import javafx.beans.Observable;
  34 import javafx.beans.property.*;
  35 import javafx.event.EventHandler;
  36 import javafx.scene.Scene;
  37 import javafx.scene.control.*;
  38 import javafx.scene.input.MouseEvent;
  39 import javafx.scene.layout.*;
  40 import javafx.scene.paint.*;
  41 import javafx.stage.Modality;
  42 import javafx.stage.Stage;
  43 import javafx.stage.StageStyle;
  44 import javafx.stage.Window;
  45 import javafx.beans.binding.Bindings;
  46 import javafx.beans.binding.ObjectBinding;
  47 import javafx.geometry.Insets;
  48 import javafx.geometry.Orientation;
  49 import javafx.geometry.Pos;
  50 import javafx.geometry.Rectangle2D;
  51 import javafx.scene.input.KeyEvent;
  52 import javafx.stage.Screen;
  53 import javafx.stage.WindowEvent;
  54 
  55 /**
  56  *
  57  */
  58 public class CustomColorDialog extends HBox {
  59     
  60     private final Stage dialog = new Stage();
  61     private ColorRectPane colorRectPane;
  62     private ControlsPane controlsPane;
  63 
  64     private ObjectProperty<Color> currentColorProperty = new SimpleObjectProperty<>(Color.WHITE);
  65     private ObjectProperty<Color> customColorProperty = new SimpleObjectProperty<>(Color.TRANSPARENT);
  66     private Runnable onSave;
  67     private Runnable onUse;
  68     private Runnable onCancel;
  69     
  70     private WebColorField webField = null;
  71     private Scene customScene;
  72     
  73     public CustomColorDialog(Window owner) {
  74         getStyleClass().add("custom-color-dialog");
  75         if (owner != null) dialog.initOwner(owner);
  76         dialog.setTitle(Properties.getColorPickerString("customColorDialogTitle"));
  77         dialog.initModality(Modality.APPLICATION_MODAL);
  78         dialog.initStyle(StageStyle.UTILITY);
  79         dialog.setResizable(false);
  80         colorRectPane = new ColorRectPane();
  81         controlsPane = new ControlsPane();
  82         setHgrow(controlsPane, Priority.ALWAYS);
  83         
  84         customScene = new Scene(this);
  85         final Scene ownerScene = owner.getScene();
  86         if (ownerScene != null) {
  87             if (ownerScene.getUserAgentStylesheet() != null) {
  88                 customScene.setUserAgentStylesheet(ownerScene.getUserAgentStylesheet());
  89             }
  90             customScene.getStylesheets().addAll(ownerScene.getStylesheets());
  91         }
  92         getChildren().addAll(colorRectPane, controlsPane);
  93         
  94         dialog.setScene(customScene);
  95         dialog.addEventHandler(KeyEvent.ANY, keyEventListener);
  96     }
  97 
  98     private final EventHandler<KeyEvent> keyEventListener = e -> {
  99         switch (e.getCode()) {
 100             case ESCAPE :
 101                 dialog.setScene(null);
 102                 dialog.close();
 103         default:
 104             break;
 105         }
 106     };
 107     
 108     public void setCurrentColor(Color currentColor) {
 109         this.currentColorProperty.set(currentColor);
 110     }
 111 
 112     public final Color getCurrentColor() {
 113         return currentColorProperty.get();
 114     }
 115     
 116     public final ObjectProperty<Color> customColorProperty() {
 117         return customColorProperty;
 118     }
 119 
 120     public final void setCustomColor(Color color) {
 121         customColorProperty.set(color);
 122     }
 123 
 124     public final Color getCustomColor() {
 125         return customColorProperty.get();
 126     }
 127     
 128     public Runnable getOnSave() {
 129         return onSave;
 130     }
 131 
 132     public void setOnSave(Runnable onSave) {
 133         this.onSave = onSave;
 134     }
 135 
 136     public Runnable getOnUse() {
 137         return onUse;
 138     }
 139 
 140     public void setOnUse(Runnable onUse) {
 141         this.onUse = onUse;
 142     }
 143 
 144     public Runnable getOnCancel() {
 145         return onCancel;
 146     }
 147 
 148     public void setOnCancel(Runnable onCancel) {
 149         this.onCancel = onCancel;
 150     }
 151 
 152     public void setOnHidden(EventHandler<WindowEvent> onHidden) {
 153         dialog.setOnHidden(onHidden);
 154     }
 155 
 156     public Stage getDialog() {
 157         return dialog;
 158     }
 159     
 160     public void show() {
 161         if (dialog.getOwner() != null) {
 162             // Workaround of RT-29871: Instead of just invoking fixPosition() 
 163             // here need to use listener that fixes dialog position once both
 164             // width and height are determined
 165             dialog.widthProperty().addListener(positionAdjuster);
 166             dialog.heightProperty().addListener(positionAdjuster);
 167             positionAdjuster.invalidated(null);
 168         }
 169         if (dialog.getScene() == null) dialog.setScene(customScene);
 170         colorRectPane.updateValues();
 171         dialog.show();
 172     }
 173     
 174     private InvalidationListener positionAdjuster = new InvalidationListener() {
 175 
 176         @Override
 177         public void invalidated(Observable ignored) {
 178             if (Double.isNaN(dialog.getWidth()) || Double.isNaN(dialog.getHeight())) {
 179                 return;
 180             }
 181             dialog.widthProperty().removeListener(positionAdjuster);
 182             dialog.heightProperty().removeListener(positionAdjuster);
 183             fixPosition();
 184         }
 185 
 186     };
 187     
 188     private void fixPosition() {
 189         Window w = dialog.getOwner();
 190         Screen s = com.sun.javafx.util.Utils.getScreen(w);
 191         Rectangle2D sb = s.getBounds();
 192         double xR = w.getX() + w.getWidth();
 193         double xL = w.getX() - dialog.getWidth();
 194         double x, y;
 195         if (sb.getMaxX() >= xR + dialog.getWidth()) {
 196             x = xR;
 197         } else if (sb.getMinX() <= xL) {
 198             x = xL;
 199         } else {
 200             x = Math.max(sb.getMinX(), sb.getMaxX() - dialog.getWidth());
 201         }
 202         y = Math.max(sb.getMinY(), Math.min(sb.getMaxY() - dialog.getHeight(), w.getY()));
 203         dialog.setX(x);
 204         dialog.setY(y);
 205     }
 206     
 207     @Override public void layoutChildren() {
 208         super.layoutChildren();
 209         if (dialog.getMinWidth() > 0 && dialog.getMinHeight() > 0) {
 210             // don't recalculate min size once it's set
 211             return;
 212         }
 213 
 214         // Math.max(0, ...) added for RT-34704 to ensure the dialog is at least 0 x 0
 215         double minWidth = Math.max(0, computeMinWidth(getHeight()) + (dialog.getWidth() - customScene.getWidth()));
 216         double minHeight = Math.max(0, computeMinHeight(getWidth()) + (dialog.getHeight() - customScene.getHeight()));
 217         dialog.setMinWidth(minWidth);
 218         dialog.setMinHeight(minHeight);
 219     }
 220        
 221     /* ------------------------------------------------------------------------*/
 222     
 223     private class ColorRectPane extends HBox {
 224 
 225         private Pane colorRect;
 226         private Pane colorBar;
 227         private Pane colorRectOverlayOne;
 228         private Pane colorRectOverlayTwo;
 229         private Region colorRectIndicator;
 230         private Region colorBarIndicator;
 231         
 232         private boolean changeIsLocal = false;
 233         private DoubleProperty hue = new SimpleDoubleProperty(-1) {
 234             @Override protected void invalidated() {
 235                 if (!changeIsLocal) {
 236                     changeIsLocal = true;
 237                     updateHSBColor();
 238                     changeIsLocal = false;
 239                 }
 240             }
 241         };
 242         private DoubleProperty sat = new SimpleDoubleProperty(-1) {
 243             @Override protected void invalidated() {
 244                 if (!changeIsLocal) {
 245                     changeIsLocal = true;
 246                     updateHSBColor();
 247                     changeIsLocal = false;
 248                 }
 249             }
 250         };
 251         private DoubleProperty bright = new SimpleDoubleProperty(-1) {
 252             @Override protected void invalidated() {
 253                 if (!changeIsLocal) {
 254                     changeIsLocal = true;
 255                     updateHSBColor();
 256                     changeIsLocal = false;
 257                 }
 258             }
 259         };
 260         private IntegerProperty red = new SimpleIntegerProperty(-1) {
 261             @Override protected void invalidated() {
 262                 if (!changeIsLocal) {
 263                     changeIsLocal = true;
 264                     updateRGBColor();
 265                     changeIsLocal = false;
 266                 }
 267             }
 268         };
 269         
 270         private IntegerProperty green = new SimpleIntegerProperty(-1) {
 271             @Override protected void invalidated() {
 272                 if (!changeIsLocal) {
 273                     changeIsLocal = true;
 274                     updateRGBColor();
 275                     changeIsLocal = false;
 276                 }
 277             }
 278         };
 279         
 280         private IntegerProperty blue = new SimpleIntegerProperty(-1) {
 281             @Override protected void invalidated() {
 282                 if (!changeIsLocal) {
 283                     changeIsLocal = true;
 284                     updateRGBColor();
 285                     changeIsLocal = false;
 286                 }
 287             }
 288         };
 289         
 290         private DoubleProperty alpha = new SimpleDoubleProperty(100) {
 291             @Override protected void invalidated() {
 292                 if (!changeIsLocal) {
 293                     changeIsLocal = true;
 294                     setCustomColor(new Color(
 295                             getCustomColor().getRed(), 
 296                             getCustomColor().getGreen(), 
 297                             getCustomColor().getBlue(), 
 298                             clamp(alpha.get() / 100)));
 299                     changeIsLocal = false;
 300                 }
 301             }
 302         };
 303          
 304         private void updateRGBColor() {
 305             Color newColor = Color.rgb(red.get(), green.get(), blue.get(), clamp(alpha.get() / 100));
 306             hue.set(newColor.getHue());
 307             sat.set(newColor.getSaturation() * 100);
 308             bright.set(newColor.getBrightness() * 100);
 309             setCustomColor(newColor);
 310         }
 311         
 312         private void updateHSBColor() {
 313             Color newColor = Color.hsb(hue.get(), clamp(sat.get() / 100), 
 314                             clamp(bright.get() / 100), clamp(alpha.get() / 100));
 315             red.set(doubleToInt(newColor.getRed()));
 316             green.set(doubleToInt(newColor.getGreen()));
 317             blue.set(doubleToInt(newColor.getBlue()));
 318             setCustomColor(newColor);
 319         }
 320        
 321         private void colorChanged() {
 322             if (!changeIsLocal) {
 323                 changeIsLocal = true;
 324                 hue.set(getCustomColor().getHue());
 325                 sat.set(getCustomColor().getSaturation() * 100);
 326                 bright.set(getCustomColor().getBrightness() * 100);
 327                 red.set(doubleToInt(getCustomColor().getRed()));
 328                 green.set(doubleToInt(getCustomColor().getGreen()));
 329                 blue.set(doubleToInt(getCustomColor().getBlue()));
 330                 changeIsLocal = false;
 331             }
 332         }
 333         
 334         public ColorRectPane() {
 335             
 336             getStyleClass().add("color-rect-pane");
 337             
 338             customColorProperty().addListener((ov, t, t1) -> {
 339                 colorChanged();
 340             });
 341             
 342             colorRectIndicator = new Region();
 343             colorRectIndicator.setId("color-rect-indicator");
 344             colorRectIndicator.setManaged(false);
 345             colorRectIndicator.setMouseTransparent(true);
 346             colorRectIndicator.setCache(true);
 347         
 348             final Pane colorRectOpacityContainer = new StackPane();
 349             
 350             colorRect = new StackPane() {
 351                 // This is an implementation of square control that chooses its
 352                 // size to fill the available height
 353                 @Override
 354                 public Orientation getContentBias() {
 355                     return Orientation.VERTICAL;
 356                 }
 357                 @Override
 358                 protected double computePrefWidth(double height) {
 359                     return height;
 360                 }
 361                 @Override
 362                 protected double computeMaxWidth(double height) {
 363                     return height;
 364                 }
 365             };
 366             colorRect.getStyleClass().addAll("color-rect", "transparent-pattern");
 367             
 368             Pane colorRectHue = new Pane();
 369             colorRectHue.backgroundProperty().bind(new ObjectBinding<Background>() {
 370                 
 371                 {
 372                     bind(hue);
 373                 }
 374 
 375                 @Override protected Background computeValue() {
 376                     return new Background(new BackgroundFill(
 377                             Color.hsb(hue.getValue(), 1.0, 1.0), 
 378                             CornerRadii.EMPTY, Insets.EMPTY));
 379                 }
 380             });            
 381             
 382             colorRectOverlayOne = new Pane();
 383             colorRectOverlayOne.getStyleClass().add("color-rect");
 384             colorRectOverlayOne.setBackground(new Background(new BackgroundFill(
 385                     new LinearGradient(0, 0, 1, 0, true, CycleMethod.NO_CYCLE, 
 386                     new Stop(0, Color.rgb(255, 255, 255, 1)), 
 387                     new Stop(1, Color.rgb(255, 255, 255, 0))), 
 388                     CornerRadii.EMPTY, Insets.EMPTY)));
 389         
 390             EventHandler<MouseEvent> rectMouseHandler = event -> {
 391                 final double x = event.getX();
 392                 final double y = event.getY();
 393                 sat.set(clamp(x / colorRect.getWidth()) * 100);
 394                 bright.set(100 - (clamp(y / colorRect.getHeight()) * 100));
 395             };
 396         
 397             colorRectOverlayTwo = new Pane();
 398             colorRectOverlayTwo.getStyleClass().addAll("color-rect");
 399             colorRectOverlayTwo.setBackground(new Background(new BackgroundFill(
 400                     new LinearGradient(0, 0, 0, 1, true, CycleMethod.NO_CYCLE, 
 401                     new Stop(0, Color.rgb(0, 0, 0, 0)), new Stop(1, Color.rgb(0, 0, 0, 1))), 
 402                     CornerRadii.EMPTY, Insets.EMPTY)));
 403             colorRectOverlayTwo.setOnMouseDragged(rectMouseHandler);
 404             colorRectOverlayTwo.setOnMousePressed(rectMouseHandler);
 405             
 406             Pane colorRectBlackBorder = new Pane();
 407             colorRectBlackBorder.setMouseTransparent(true);
 408             colorRectBlackBorder.getStyleClass().addAll("color-rect", "color-rect-border");
 409             
 410             colorBar = new Pane();
 411             colorBar.getStyleClass().add("color-bar");
 412             colorBar.setBackground(new Background(new BackgroundFill(createHueGradient(), 
 413                     CornerRadii.EMPTY, Insets.EMPTY)));
 414 
 415             colorBarIndicator = new Region();
 416             colorBarIndicator.setId("color-bar-indicator");
 417             colorBarIndicator.setMouseTransparent(true);
 418             colorBarIndicator.setCache(true);
 419             
 420             colorRectIndicator.layoutXProperty().bind(sat.divide(100).multiply(colorRect.widthProperty()));
 421             colorRectIndicator.layoutYProperty().bind(Bindings.subtract(1, bright.divide(100)).multiply(colorRect.heightProperty()));
 422             colorBarIndicator.layoutYProperty().bind(hue.divide(360).multiply(colorBar.heightProperty()));
 423             colorRectOpacityContainer.opacityProperty().bind(alpha.divide(100));
 424                     
 425             EventHandler<MouseEvent> barMouseHandler = event -> {
 426                 final double y = event.getY();
 427                 hue.set(clamp(y / colorRect.getHeight()) * 360);
 428             };
 429             
 430             colorBar.setOnMouseDragged(barMouseHandler);
 431             colorBar.setOnMousePressed(barMouseHandler);
 432         
 433             colorBar.getChildren().setAll(colorBarIndicator);
 434             colorRectOpacityContainer.getChildren().setAll(colorRectHue, colorRectOverlayOne, colorRectOverlayTwo);
 435             colorRect.getChildren().setAll(colorRectOpacityContainer, colorRectBlackBorder, colorRectIndicator);
 436             HBox.setHgrow(colorRect, Priority.SOMETIMES);
 437             getChildren().addAll(colorRect, colorBar);
 438         }
 439         
 440         private void updateValues() {
 441             if (getCurrentColor() == null) {
 442                 setCurrentColor(Color.TRANSPARENT);
 443             }
 444             changeIsLocal = true;
 445             //Initialize hue, sat, bright, color, red, green and blue
 446             hue.set(getCurrentColor().getHue());
 447             sat.set(getCurrentColor().getSaturation()*100);
 448             bright.set(getCurrentColor().getBrightness()*100);
 449             alpha.set(getCurrentColor().getOpacity()*100);
 450             setCustomColor(Color.hsb(hue.get(), clamp(sat.get() / 100), clamp(bright.get() / 100), 
 451                     clamp(alpha.get()/100)));
 452             red.set(doubleToInt(getCustomColor().getRed()));
 453             green.set(doubleToInt(getCustomColor().getGreen()));
 454             blue.set(doubleToInt(getCustomColor().getBlue()));
 455             changeIsLocal = false;
 456         }
 457         
 458         @Override protected void layoutChildren() {
 459             super.layoutChildren();
 460             
 461             // to maintain default size
 462             colorRectIndicator.autosize();
 463             // to maintain square size
 464             double size = Math.min(colorRect.getWidth(), colorRect.getHeight());
 465             colorRect.resize(size, size);
 466             colorBar.resize(colorBar.getWidth(), size);
 467         }
 468     }
 469     
 470     /* ------------------------------------------------------------------------*/
 471     
 472     private class ControlsPane extends VBox {
 473         
 474         private Label currentColorLabel;
 475         private Label newColorLabel;
 476         private Region currentColorRect;
 477         private Region newColorRect;
 478         private Region currentTransparent; // for opacity
 479         private GridPane currentAndNewColor;
 480         private Region currentNewColorBorder;
 481         private ToggleButton hsbButton;
 482         private ToggleButton rgbButton;
 483         private ToggleButton webButton;
 484         private HBox hBox;
 485         
 486         private Label labels[] = new Label[4];
 487         private Slider sliders[] = new Slider[4];
 488         private IntegerField fields[] = new IntegerField[4];
 489         private Label units[] = new Label[4];
 490         private HBox buttonBox;
 491         private Region whiteBox;
 492         
 493         private GridPane settingsPane = new GridPane();
 494         
 495         public ControlsPane() {
 496             getStyleClass().add("controls-pane");
 497             
 498             currentNewColorBorder = new Region();
 499             currentNewColorBorder.setId("current-new-color-border");
 500             
 501             currentTransparent = new Region();
 502             currentTransparent.getStyleClass().addAll("transparent-pattern");
 503             
 504             currentColorRect = new Region();
 505             currentColorRect.getStyleClass().add("color-rect");
 506             currentColorRect.setId("current-color");
 507             currentColorRect.backgroundProperty().bind(new ObjectBinding<Background>() {
 508                 {
 509                     bind(currentColorProperty);
 510                 }
 511                 @Override protected Background computeValue() {
 512                     return new Background(new BackgroundFill(currentColorProperty.get(), CornerRadii.EMPTY, Insets.EMPTY));
 513                 }
 514             });
 515 
 516             newColorRect = new Region();
 517             newColorRect.getStyleClass().add("color-rect");
 518             newColorRect.setId("new-color");
 519             newColorRect.backgroundProperty().bind(new ObjectBinding<Background>() {
 520                 {
 521                     bind(customColorProperty);
 522                 }
 523                 @Override protected Background computeValue() {
 524                     return new Background(new BackgroundFill(customColorProperty.get(), CornerRadii.EMPTY, Insets.EMPTY));
 525                 }
 526             });
 527 
 528             currentColorLabel = new Label(Properties.getColorPickerString("currentColor"));
 529             newColorLabel = new Label(Properties.getColorPickerString("newColor"));
 530             
 531             whiteBox = new Region();
 532             whiteBox.getStyleClass().add("customcolor-controls-background");
 533             
 534             hsbButton = new ToggleButton(Properties.getColorPickerString("colorType.hsb"));
 535             hsbButton.getStyleClass().add("left-pill");
 536             rgbButton = new ToggleButton(Properties.getColorPickerString("colorType.rgb"));
 537             rgbButton.getStyleClass().add("center-pill");
 538             webButton = new ToggleButton(Properties.getColorPickerString("colorType.web"));
 539             webButton.getStyleClass().add("right-pill");
 540             final ToggleGroup group = new ToggleGroup();
 541             
 542             hBox = new HBox();
 543             hBox.setAlignment(Pos.CENTER);
 544             hBox.getChildren().addAll(hsbButton, rgbButton, webButton);
 545             
 546             Region spacer1 = new Region();
 547             spacer1.setId("spacer1");
 548             Region spacer2 = new Region();
 549             spacer2.setId("spacer2");            
 550             Region leftSpacer = new Region();
 551             leftSpacer.setId("spacer-side");
 552             Region rightSpacer = new Region();
 553             rightSpacer.setId("spacer-side");
 554             Region bottomSpacer = new Region();
 555             bottomSpacer.setId("spacer-bottom");
 556             
 557             currentAndNewColor = new GridPane();
 558             currentAndNewColor.getColumnConstraints().addAll(new ColumnConstraints(), new ColumnConstraints());
 559             currentAndNewColor.getColumnConstraints().get(0).setHgrow(Priority.ALWAYS);
 560             currentAndNewColor.getColumnConstraints().get(1).setHgrow(Priority.ALWAYS);
 561             currentAndNewColor.getRowConstraints().addAll(new RowConstraints(), new RowConstraints(), new RowConstraints());
 562             currentAndNewColor.getRowConstraints().get(2).setVgrow(Priority.ALWAYS);
 563             VBox.setVgrow(currentAndNewColor, Priority.ALWAYS);
 564             
 565             currentAndNewColor.getStyleClass().add("current-new-color-grid");
 566             currentAndNewColor.add(currentColorLabel, 0, 0);
 567             currentAndNewColor.add(newColorLabel, 1, 0);
 568             currentAndNewColor.add(spacer1, 0, 1, 2, 1);
 569             currentAndNewColor.add(currentTransparent, 0, 2, 2, 1);
 570             currentAndNewColor.add(currentColorRect, 0, 2);
 571             currentAndNewColor.add(newColorRect, 1, 2);
 572             currentAndNewColor.add(currentNewColorBorder, 0, 2, 2, 1);
 573             currentAndNewColor.add(spacer2, 0, 3, 2, 1);
 574             
 575             settingsPane = new GridPane();
 576             settingsPane.setId("settings-pane");
 577             settingsPane.getColumnConstraints().addAll(new ColumnConstraints(), 
 578                     new ColumnConstraints(), new ColumnConstraints(), 
 579                     new ColumnConstraints(), new ColumnConstraints(), 
 580                     new ColumnConstraints());
 581             settingsPane.getColumnConstraints().get(0).setHgrow(Priority.NEVER);
 582             settingsPane.getColumnConstraints().get(2).setHgrow(Priority.ALWAYS);
 583             settingsPane.getColumnConstraints().get(3).setHgrow(Priority.NEVER);
 584             settingsPane.getColumnConstraints().get(4).setHgrow(Priority.NEVER);
 585             settingsPane.getColumnConstraints().get(5).setHgrow(Priority.NEVER);
 586             settingsPane.add(whiteBox, 0, 0, 6, 5);
 587             settingsPane.add(hBox, 0, 0, 6, 1);
 588             settingsPane.add(leftSpacer, 0, 0);
 589             settingsPane.add(rightSpacer, 5, 0);
 590             settingsPane.add(bottomSpacer, 0, 4);   
 591             
 592             webField = new WebColorField();
 593             webField.getStyleClass().add("web-field");
 594             webField.setSkin(new WebColorFieldSkin(webField));
 595             webField.valueProperty().bindBidirectional(customColorProperty);
 596             webField.visibleProperty().bind(group.selectedToggleProperty().isEqualTo(webButton));
 597             settingsPane.add(webField, 2, 1);
 598             
 599             // Color settings Grid Pane
 600             for (int i = 0; i < 4; i++) {
 601                 labels[i] = new Label();
 602                 labels[i].getStyleClass().add("settings-label");
 603 
 604                 sliders[i] = new Slider();
 605 
 606                 fields[i] = new IntegerField();
 607                 fields[i].getStyleClass().add("color-input-field");
 608                 fields[i].setSkin(new IntegerFieldSkin(fields[i]));
 609                 
 610                 units[i] = new Label(i == 0 ? "\u00B0" : "%");                
 611                 units[i].getStyleClass().add("settings-unit");
 612 
 613                 if (i > 0 && i < 3) {
 614                     // first row and opacity labels are always visible
 615                     // second and third row labels are not visible in Web page
 616                     labels[i].visibleProperty().bind(group.selectedToggleProperty().isNotEqualTo(webButton));
 617                 }
 618                 if (i < 3) {
 619                     // sliders and fields shouldn't be visible in Web page
 620                     sliders[i].visibleProperty().bind(group.selectedToggleProperty().isNotEqualTo(webButton));
 621                     fields[i].visibleProperty().bind(group.selectedToggleProperty().isNotEqualTo(webButton));
 622                     units[i].visibleProperty().bind(group.selectedToggleProperty().isEqualTo(hsbButton));
 623                 }
 624                 int row = 1 + i;
 625                 if (i == 3) {
 626                     // opacity row is shifted one gridPane row down
 627                     row++;
 628                 }
 629                 
 630                 settingsPane.add(labels[i], 1, row);
 631                 settingsPane.add(sliders[i], 2, row);
 632                 settingsPane.add(fields[i], 3, row);
 633                 settingsPane.add(units[i], 4, row);
 634             }
 635             
 636             set(3, Properties.getColorPickerString("opacity_colon"), 100, colorRectPane.alpha);
 637             
 638             hsbButton.setToggleGroup(group);
 639             rgbButton.setToggleGroup(group);
 640             webButton.setToggleGroup(group);
 641             group.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
 642                 if (newValue == null) {
 643                     group.selectToggle(oldValue);
 644                 } else {
 645                     if (newValue == hsbButton) {
 646                         showHSBSettings();
 647                     } else if (newValue == rgbButton) {
 648                         showRGBSettings();
 649                     } else {
 650                         showWebSettings();
 651                     }
 652                 }
 653             });
 654             group.selectToggle(hsbButton);            
 655             
 656             buttonBox = new HBox();
 657             buttonBox.setId("buttons-hbox");
 658             
 659             Button saveButton = new Button(Properties.getColorPickerString("Save"));
 660             saveButton.setDefaultButton(true);
 661             saveButton.setOnAction(t -> {
 662                 if (onSave != null) {
 663                     onSave.run();
 664                 }
 665                 dialog.hide();
 666             });
 667             
 668             Button useButton = new Button(Properties.getColorPickerString("Use"));
 669             useButton.setOnAction(t -> {
 670                 if (onUse != null) {
 671                     onUse.run();
 672                 }
 673                 dialog.hide();
 674             });
 675             
 676             Button cancelButton = new Button(Properties.getColorPickerString("Cancel"));
 677             cancelButton.setCancelButton(true);
 678             cancelButton.setOnAction(e -> {
 679                 customColorProperty.set(getCurrentColor());
 680                 if (onCancel != null) {
 681                     onCancel.run();
 682                 }
 683                 dialog.hide();
 684             });
 685             buttonBox.getChildren().addAll(saveButton, useButton, cancelButton);
 686             
 687             getChildren().addAll(currentAndNewColor, settingsPane, buttonBox);
 688         }
 689         
 690         private void showHSBSettings() {
 691             set(0, Properties.getColorPickerString("hue_colon"), 360, colorRectPane.hue);
 692             set(1, Properties.getColorPickerString("saturation_colon"), 100, colorRectPane.sat);
 693             set(2, Properties.getColorPickerString("brightness_colon"), 100, colorRectPane.bright);
 694         }
 695         
 696         private void showRGBSettings() {
 697             set(0, Properties.getColorPickerString("red_colon"), 255, colorRectPane.red);
 698             set(1, Properties.getColorPickerString("green_colon"), 255, colorRectPane.green);
 699             set(2, Properties.getColorPickerString("blue_colon"), 255, colorRectPane.blue);
 700         }
 701         
 702         private void showWebSettings() {
 703             labels[0].setText(Properties.getColorPickerString("web_colon"));
 704         }
 705                 
 706         private Property<Number>[] bindedProperties = new Property[4];
 707             
 708         private void set(int row, String caption, int maxValue, Property<Number> prop) {
 709             labels[row].setText(caption);
 710             if (bindedProperties[row] != null) {
 711                 sliders[row].valueProperty().unbindBidirectional(bindedProperties[row]);
 712                 fields[row].valueProperty().unbindBidirectional(bindedProperties[row]);
 713             } 
 714             sliders[row].setMax(maxValue);
 715             sliders[row].valueProperty().bindBidirectional(prop);
 716             labels[row].setLabelFor(sliders[row]);
 717             fields[row].setMaxValue(maxValue);
 718             fields[row].valueProperty().bindBidirectional(prop);
 719             bindedProperties[row] = prop;
 720         }
 721     }
 722     
 723     static double clamp(double value) {
 724         return value < 0 ? 0 : value > 1 ? 1 : value;
 725     }
 726     
 727     private static LinearGradient createHueGradient() {
 728         double offset;
 729         Stop[] stops = new Stop[255];
 730         for (int y = 0; y < 255; y++) {
 731             offset = (double)(1 - (1.0 / 255) * y);
 732             int h = (int)((y / 255.0) * 360);
 733             stops[y] = new Stop(offset, Color.hsb(h, 1.0, 1.0));
 734         }
 735         return new LinearGradient(0f, 1f, 0f, 0f, true, CycleMethod.NO_CYCLE, stops);
 736     }
 737     
 738     private static int doubleToInt(double value) {
 739         return (int) (value * 255 + 0.5); // Adding 0.5 for rounding only
 740     }
 741 }