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;
}
}
}