package src.com.fxexperience.tools.caspianstyler; import java.io.File; import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.StringBufferInputStream; import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; import java.net.URLStreamHandlerFactory; import java.util.Collections; import java.util.ResourceBundle; import java.util.logging.Level; import java.util.logging.Logger; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.ChoiceBox; import javafx.scene.control.ComboBox; import javafx.scene.control.Control; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; import javafx.scene.control.Slider; import javafx.scene.control.TabPane; import javafx.scene.input.Clipboard; import javafx.scene.input.DataFormat; import javafx.scene.layout.GridPane; import javafx.scene.layout.Region; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.stage.FileChooser; import javafx.util.Callback; import src.com.fxexperience.javafx.scene.control.IntegerField; import src.com.fxexperience.javafx.scene.control.colorpicker.ColorPicker; /** * @author Jasper Potts */ @SuppressWarnings("deprecation") public class CaspianStylerMainFrame implements Initializable { private String css; // Content Panel @FXML private GridPane contentPanel; @FXML private ChoiceBox<String> choiceBox; @FXML private ComboBox<String> comboBox; @FXML private ComboBox<String> comboBox2; // Common Properties @FXML private GridPane propertiesPanel; @FXML private ChoiceBox<String> fontChoiceBox; @FXML private CheckBox fontDefaultCheckBox; @FXML private Slider fontSizeSlider; @FXML private Slider paddingSlider; @FXML private Slider borderWidthSlider; @FXML private Slider borderRadiusSlider; // Tabs @FXML private TabPane tabPane1; // Simple Tab @FXML private GridPane simpleGridPane; @FXML private CheckBox textColorAutoComboBox; @FXML private CheckBox fieldTextAutoCheckBox; private ColorPicker baseColorPicker = new ColorPicker(Color.web("#d0d0d0")); private ColorPicker backgroundColorPicker = new ColorPicker( Color.web("#f4f4f4")); private ColorPicker focusColorPicker = new ColorPicker(Color.web("#0093ff")); private ColorPicker textColorPicker = new ColorPicker(Color.web("#000000")); private ColorPicker bkgdTextColorPicker = new ColorPicker( Color.web("#000000")); private ColorPicker fieldBackgroundPicker = new ColorPicker( Color.web("#ffffff")); private ColorPicker fieldTextColorPicker = new ColorPicker( Color.web("#000000")); @FXML private Slider topHighlightSlider; @FXML private Slider bottomHighlightSlider; @FXML private ComboBox<Gradient> gradientComboBox; // Advanced Tab @FXML private GridPane advancedGridPane; @FXML private Slider bodyTopSlider; @FXML private Slider bodyTopMiddleSlider; @FXML private Slider bodyBottomMiddleSlider; @FXML private Slider bodyBottomSlider; @FXML private Slider borderSlider; @FXML private Slider shadowSlider; @FXML private Slider inputBorderSlider; @FXML private Slider inputOuterBorderSlider; @FXML private CheckBox bodyTopMiddleComboBox; @FXML private CheckBox bodyBottomMiddleComboBox; @FXML private CheckBox borderComboBox; @FXML private CheckBox shadowComboBox; @FXML private CheckBox inputBorderComboBox; @FXML private CheckBox inputOuterBorderComboBox; @FXML private CheckBox bkgdTextColorAutoComboBox; // Bottom @FXML private Button copyCSS; @FXML private Button saveCSS; @Override public void initialize(URL url, ResourceBundle rb) { // load stylesheet propertiesPanel.getStylesheets().add( CaspianStylerMainFrame.class.getResource("CaspianStyler.css") .toExternalForm()); // tweek preview content panel choiceBox.getSelectionModel().select(0); comboBox.getSelectionModel().select(0); // populate fonts choicebox fontChoiceBox.getItems().setAll(Font.getFamilies()); fontChoiceBox.getSelectionModel().select("System"); fontDefaultCheckBox.selectedProperty().addListener( new ChangeListener<Boolean>() { @Override public void changed( ObservableValue<? extends Boolean> arg0, Boolean arg1, Boolean newValue) { if (newValue) fontChoiceBox.getSelectionModel().select("System"); } }); fontChoiceBox.getSelectionModel().selectedItemProperty() .addListener(new ChangeListener<String>() { @Override public void changed(ObservableValue<? extends String> arg0, String arg1, String newValue) { if (!"System".equalsIgnoreCase(newValue)) { fontDefaultCheckBox.setSelected(false); } } }); // create listener to call update css ChangeListener<Object> updateCssListener = new ChangeListener<Object>() { @Override public void changed(ObservableValue<?> arg0, Object arg1, Object arg2) { updateCss(); } }; // add listeners to call update css fontChoiceBox.valueProperty().addListener(updateCssListener); fontSizeSlider.valueProperty().addListener(updateCssListener); paddingSlider.valueProperty().addListener(updateCssListener); borderRadiusSlider.valueProperty().addListener(updateCssListener); borderWidthSlider.valueProperty().addListener(updateCssListener); // create Integer Fields createNumberFieldForSlider(fontSizeSlider, propertiesPanel); createNumberFieldForSlider(paddingSlider, propertiesPanel); createNumberFieldForSlider(borderWidthSlider, propertiesPanel); createNumberFieldForSlider(borderRadiusSlider, propertiesPanel); // ------------- SIMPLE TAB -------------------------------------------- simpleGridPane.getChildren().addAll(baseColorPicker, backgroundColorPicker, focusColorPicker, textColorPicker, fieldBackgroundPicker, fieldTextColorPicker, bkgdTextColorPicker); // create color pickers GridPane.setConstraints(baseColorPicker, 1, 1); GridPane.setConstraints(textColorPicker, 1, 2); GridPane.setConstraints(backgroundColorPicker, 1, 3); GridPane.setConstraints(bkgdTextColorPicker, 1, 4); GridPane.setConstraints(fieldBackgroundPicker, 1, 5); GridPane.setConstraints(fieldTextColorPicker, 1, 6); GridPane.setConstraints(focusColorPicker, 1, 7); baseColorPicker.colorProperty().addListener(updateCssListener); backgroundColorPicker.colorProperty().addListener(updateCssListener); focusColorPicker.colorProperty().addListener(updateCssListener); textColorPicker.colorProperty().addListener(updateCssListener); textColorAutoComboBox.selectedProperty().addListener(updateCssListener); textColorPicker.disableProperty().bind( textColorAutoComboBox.selectedProperty()); fieldBackgroundPicker.colorProperty().addListener(updateCssListener); fieldTextColorPicker.colorProperty().addListener(updateCssListener); fieldTextAutoCheckBox.selectedProperty().addListener(updateCssListener); fieldTextColorPicker.disableProperty().bind( fieldTextAutoCheckBox.selectedProperty()); bkgdTextColorPicker.colorProperty().addListener(updateCssListener); bkgdTextColorAutoComboBox.selectedProperty().addListener( updateCssListener); bkgdTextColorPicker.disableProperty().bind( bkgdTextColorAutoComboBox.selectedProperty()); // add listeners to sliders topHighlightSlider.valueProperty().addListener(updateCssListener); bottomHighlightSlider.valueProperty().addListener(updateCssListener); // Populate gradient combo gradientComboBox.getItems().addAll(Gradient.GRADIENTS); gradientComboBox .setCellFactory(new Callback<ListView<Gradient>, ListCell<Gradient>>() { @Override public ListCell<Gradient> call( ListView<Gradient> gradientList) { ListCell<Gradient> cell = new ListCell<Gradient>() { @Override protected void updateItem(Gradient gradient, boolean empty) { super.updateItem(gradient, empty); if (empty || gradient == null) { setText(null); setGraphic(null); } else { setText(gradient.getName()); Region preview = new Region(); preview.setPrefSize(30, 30); preview.setStyle("-fx-border-color: black; -fx-background-color: " + gradient.getCss()); setGraphic(preview); } } }; cell.setStyle("-fx-cell-size: 40;"); return cell; } }); gradientComboBox.getSelectionModel().selectedItemProperty() .addListener(new ChangeListener<Gradient>() { @Override public void changed( ObservableValue<? extends Gradient> arg0, Gradient arg1, Gradient newGradient) { bodyTopSlider.setValue(newGradient.getTopDerivation()); bodyBottomSlider.setValue(newGradient .getBottomDerivation()); if (newGradient.isShinny()) { bodyTopMiddleComboBox.setSelected(true); bodyBottomMiddleComboBox.setSelected(true); bodyTopMiddleSlider.setValue(newGradient .getTopMidDerivation()); bodyBottomMiddleSlider.setValue(newGradient .getBottomMidDerivation()); } else { bodyTopMiddleComboBox.setSelected(false); bodyBottomMiddleComboBox.setSelected(false); } updateCss(); } }); gradientComboBox.getSelectionModel().select(0); // ------------- ADVANCED TAB // -------------------------------------------- bodyTopSlider.valueProperty().addListener(updateCssListener); bodyTopMiddleSlider.valueProperty().addListener(updateCssListener); bodyBottomMiddleSlider.valueProperty().addListener(updateCssListener); bodyBottomSlider.valueProperty().addListener(updateCssListener); borderSlider.valueProperty().addListener(updateCssListener); shadowSlider.valueProperty().addListener(updateCssListener); inputBorderSlider.valueProperty().addListener(updateCssListener); inputOuterBorderSlider.valueProperty().addListener(updateCssListener); bodyTopMiddleSlider.disableProperty().bind( bodyTopMiddleComboBox.selectedProperty().not()); bodyBottomMiddleSlider.disableProperty().bind( bodyBottomMiddleComboBox.selectedProperty().not()); borderSlider.disableProperty().bind( borderComboBox.selectedProperty().not()); shadowSlider.disableProperty().bind( shadowComboBox.selectedProperty().not()); inputBorderSlider.disableProperty().bind( inputBorderComboBox.selectedProperty().not()); inputOuterBorderSlider.disableProperty().bind( inputOuterBorderComboBox.selectedProperty().not()); // ------------- SAVE AND COPY // -------------------------------------------- saveCSS.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent arg0) { FileChooser fileChooser = new FileChooser(); File file = fileChooser.showSaveDialog(propertiesPanel .getScene().getWindow()); if (file != null && !file.exists() && file.getParentFile().isDirectory()) { FileWriter writer = null; try { writer = new FileWriter(file); writer.write(createCSS(true)); writer.flush(); } catch (IOException ex) { Logger.getLogger(CaspianStylerMainFrame.class.getName()) .log(Level.SEVERE, null, ex); } finally { try { writer.close(); } catch (IOException ex) { Logger.getLogger( CaspianStylerMainFrame.class.getName()) .log(Level.SEVERE, null, ex); } } } } }); copyCSS.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent arg0) { Clipboard.getSystemClipboard().setContent( Collections.singletonMap(DataFormat.PLAIN_TEXT, (Object) createCSS(true))); } }); } /** * The SceneBuilder does not work with custom controls yet so create a * IntegerField and add to GridPane * * @param slider * The slider to bind to and sit next to * @param parent * The GridPane to add to */ private void createNumberFieldForSlider(Slider slider, GridPane parent) { IntegerField field = new IntegerField(); field.setMaxHeight(Control.USE_PREF_SIZE); field.setMaxWidth(Control.USE_PREF_SIZE); field.setPrefColumnCount(3); parent.getChildren().add(field); GridPane.setColumnIndex(field, GridPane.getColumnIndex(slider) + 1); GridPane.setRowIndex(field, GridPane.getRowIndex(slider)); field.valueProperty().bindBidirectional(slider.valueProperty()); } private void updateCss() { css = createCSS(false); contentPanel.getStylesheets().setAll("internal:stylesheet.css"); } private String createCSS(boolean isRoot) { int fontSize = (int) fontSizeSlider.getValue(); int borderWidth = (int) borderWidthSlider.getValue(); int borderWidthForPadding = (borderWidth <= 1) ? 0 : borderWidth - 1; int padding = (int) paddingSlider.getValue() + borderWidthForPadding; int borderRadius = (int) borderRadiusSlider.getValue(); double checkPadding = (((0.25 * fontSize) + borderWidthForPadding) / fontSize); double radioPadding = (((0.333333 * fontSize) + borderWidthForPadding) / fontSize); StringBuilder cssBuffer = new StringBuilder(); if (isRoot) { cssBuffer.append(".root {\n"); } else { cssBuffer.append("#ContentPanel {\n"); } cssBuffer.append(" -fx-font-family: \"" + fontChoiceBox.getValue() + "\";\n"); cssBuffer.append(" -fx-font-size: " + fontSizeSlider.getValue() + "px;\n"); cssBuffer.append(" -fx-base: " + baseColorPicker.getWebColor() + ";\n"); cssBuffer.append(" -fx-background: " + backgroundColorPicker.getWebColor() + ";\n"); cssBuffer.append(" -fx-focus-color: " + focusColorPicker.getWebColor() + ";\n"); cssBuffer.append(" -fx-control-inner-background: " + fieldBackgroundPicker.getWebColor() + ";\n"); if (!textColorAutoComboBox.isSelected()) { cssBuffer.append(" -fx-text-base-color: " + textColorPicker.getWebColor() + ";\n"); } if (!bkgdTextColorAutoComboBox.isSelected()) { cssBuffer.append(" -fx-text-background-color: " + bkgdTextColorPicker.getWebColor() + ";\n"); } if (!fieldTextAutoCheckBox.isSelected()) { cssBuffer.append(" -fx-text-inner-color: " + fieldTextColorPicker.getWebColor() + ";\n"); } double innerTopDerivation = bodyTopSlider.getValue() + ((100 - bodyTopSlider.getValue()) * (topHighlightSlider .getValue() / 100)); double innerBottomDerivation = bodyBottomSlider.getValue() + ((100 - bodyBottomSlider.getValue()) * (bottomHighlightSlider .getValue() / 100)); cssBuffer.append(" -fx-inner-border: linear-gradient(to bottom, " + "derive(-fx-color," + innerTopDerivation + "%) 0%, " + "derive(-fx-color," + innerBottomDerivation + "%) 100%);\n"); cssBuffer.append(" -fx-body-color: linear-gradient( to bottom, "); cssBuffer.append("derive(-fx-color, " + bodyTopSlider.getValue() + "%) 0%, "); if (bodyTopMiddleComboBox.isSelected()) cssBuffer.append("derive(-fx-color, " + bodyTopMiddleSlider.getValue() + "%) 50%, "); if (bodyBottomMiddleComboBox.isSelected()) cssBuffer.append("derive(-fx-color, " + bodyBottomMiddleSlider.getValue() + "%) 50.5%, "); cssBuffer.append("derive(-fx-color, " + bodyBottomSlider.getValue() + "%) 100%);\n"); if (borderComboBox.isSelected()) cssBuffer.append(" -fx-outer-border: derive(-fx-color," + borderSlider.getValue() + "%);\n"); if (shadowComboBox.isSelected()) cssBuffer .append(" -fx-shadow-highlight-color: derive(-fx-background," + shadowSlider.getValue() + "%);\n"); if (inputBorderComboBox.isSelected()) cssBuffer.append(" -fx-text-box-border: derive(-fx-background," + inputBorderSlider.getValue() + "%);\n"); cssBuffer.append("}\n"); cssBuffer.append(".button, .toggle-button, .choice-box {\n"); cssBuffer.append(" -fx-background-radius: " + borderRadius + ", " + borderRadius + ", " + (borderRadius - 1) + ", " + (borderRadius - 2) + ";\n"); cssBuffer.append(" -fx-padding: " + padding + "px " + (padding + 7) + "px " + padding + "px " + (padding + 7) + "px;\n"); cssBuffer.append("}\n"); cssBuffer.append(".menu-button {\n"); cssBuffer.append(" -fx-background-radius: " + borderRadius + ", " + borderRadius + ", " + (borderRadius - 1) + ", " + (borderRadius - 2) + ";\n"); cssBuffer.append("}\n"); cssBuffer.append(".menu-button .label {\n"); cssBuffer.append(" -fx-padding: " + padding + "px " + (padding + 15) + "px " + padding + "px " + (padding + 7) + "px;\n"); cssBuffer.append("}\n"); cssBuffer.append(".menu-button .arrow-button {\n"); cssBuffer.append(" -fx-padding: " + padding + "px " + (padding + 3) + "px " + padding + "px 0px;\n"); cssBuffer.append("}\n"); cssBuffer.append(".choice-box {\n"); cssBuffer.append(" -fx-padding: 0 " + (padding + 3) + "px 0 0;\n"); cssBuffer.append("}\n"); cssBuffer.append(".choice-box .label {\n"); cssBuffer.append(" -fx-padding: " + padding + "px " + (padding + 1) + "px " + padding + "px " + (padding + 3) + "px;\n"); cssBuffer.append("}\n"); cssBuffer.append(".choice-box .open-button {\n"); cssBuffer.append(" -fx-padding: 1 0 0 " + (padding + 5) + "px;\n"); cssBuffer.append("}\n"); cssBuffer .append(".combo-box-base:editable .text-field, .combo-box-base .arrow-button, .combo-box .list-cell {\n"); cssBuffer.append(" -fx-padding: " + padding + "px " + (padding + 3) + "px " + padding + "px " + (padding + 3) + "px;\n"); cssBuffer.append("}\n"); cssBuffer.append(".check-box .box {\n"); cssBuffer.append(" -fx-padding: " + checkPadding + "em;\n"); cssBuffer.append("}\n"); cssBuffer.append(".radio-button .radio {\n"); cssBuffer.append(" -fx-padding: " + radioPadding + "em;\n"); cssBuffer.append("}\n"); if (!textColorAutoComboBox.isSelected()) { cssBuffer.append(".hyperlink, {\n"); cssBuffer.append(" -fx-text-fill: -fx-text-background-color;\n"); cssBuffer.append("}\n"); cssBuffer.append(".toggle-button:selected {\n"); cssBuffer.append(" -fx-text-fill: -fx-text-base-color;\n"); cssBuffer.append("}\n"); } cssBuffer.append(".label, .check-box, .radio-button {\n"); cssBuffer.append(" -fx-text-fill: -fx-text-background-color;\n"); cssBuffer.append("}\n"); cssBuffer .append(".button, .toggle-button, .check-box .box, .radio-button .radio, .choice-box, .menu-button, .tab, .combo-box-base {\n"); cssBuffer.append(" -fx-background-insets: 0 0 -1 0, 0, " + borderWidth + ", " + (borderWidth + 1) + ";\n"); cssBuffer.append("}\n"); cssBuffer .append(".button:focused, .toggle-button:focused, .check-box:focused .box, .radio-button:focused .radio, .choice-box:focused, .menu-button:focused, .combo-box-base:focused {\n"); cssBuffer.append(" -fx-background-insets: -1.4, 0, " + borderWidth + ", " + (borderWidth + 1) + ";\n"); cssBuffer.append("}\n"); cssBuffer.append(".combo-box-base .arrow-button {\n"); cssBuffer.append(" -fx-background-insets: 0, " + borderWidth + ", " + (borderWidth + 1) + ";\n"); cssBuffer.append("}\n"); cssBuffer .append(".choice-box .label { /* Workaround for RT-20015 */\n"); cssBuffer.append(" -fx-text-fill: -fx-text-base-color;\n"); // cssBuffer.append(" -fx-text-fill: blue;\n"); cssBuffer.append("}\n"); cssBuffer .append(".menu-button .label { /* Workaround for RT-20015 */\n"); cssBuffer.append(" -fx-text-fill: -fx-text-base-color;\n"); // cssBuffer.append(" -fx-text-fill: green;\n"); cssBuffer.append("}\n"); return cssBuffer.toString(); } // ========================================================================= // URL Handler to create magic "internal:stylesheet.css" url for our css // string buffer { URL.setURLStreamHandlerFactory(new StringURLStreamHandlerFactory()); } /** * Simple URLConnection that always returns the content of the cssBuffer */ private class StringURLConnection extends URLConnection { public StringURLConnection(URL url) { super(url); } @Override public void connect() throws IOException { } @SuppressWarnings("deprecation") @Override public InputStream getInputStream() throws IOException { return new StringBufferInputStream(css); } } private class StringURLStreamHandlerFactory implements URLStreamHandlerFactory { URLStreamHandler streamHandler = new URLStreamHandler() { @Override protected URLConnection openConnection(URL url) throws IOException { if (url.toString().toLowerCase().endsWith(".css")) { return new StringURLConnection(url); } throw new FileNotFoundException(); } }; @Override public URLStreamHandler createURLStreamHandler(String protocol) { if ("internal".equals(protocol)) { return streamHandler; } return null; } } }