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