import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import java.nio.file.*;
import java.nio.charset.Charset;
import javax.xml.stream.*;
import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.input.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.scene.canvas.*;
import javafx.beans.property.*;
import javafx.beans.value.*;
import javafx.geometry.*;
import javafx.event.*;
import javafx.collections.*;
public class Edit extends Application {
@Override
public void start(Stage stage) throws Exception {
BorderPane layout = new BorderPane();
Scene scene = new Scene(layout);
stage.setScene(scene);
Scheme scheme = new Scheme(stage);
test(scheme);
scheme.read();
layout.setCenter(scheme.getScrollPane());
stage.setOnCloseRequest(event -> { if (!scheme.saveDialog()) event.consume(); });
stage.show();
}
public static boolean validName(String name) { return name != null && name.matches("[a-zA-Z]|[a-zA-Z_][a-zA-Z0-9_]+"); }
Future<?> future = null;
int angle = 0;
void test(Scheme scheme) {
File file = new File(scheme.getXmlName().concat(".xml"));
if (!file.exists()) {
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\"?>\n");
sb.append("<scheme width=\"500\" height=\"400\">\n");
//sb.append(" <element clss=\"SETest\"/>\n");
sb.append(" <schemeElement clss=\"Edit$SELabel\" name=\"L1\" layoutX=\"120.0\" layoutY=\"100.0\" text=\"Label\" bgFill=\"0xf6f6b6ff\"/>\n");
sb.append(" <schemeElement clss=\"Edit$SEButton\" name=\"B1\" layoutX=\"250.0\" layoutY=\"150.0\" text=\"Button\"/>\n");
sb.append("</scheme>\n");
try { Files.write(file.toPath(), sb.toString().getBytes()); } catch (Exception e) { }
scheme.setModifyed(true);
}
scheme.setOnAction((element, value) -> {
if (!element.equals("B1.button")) return;
if (future != null && !future.isDone()) {
future.cancel(true);
} else {
ExecutorService executorService = Executors.newSingleThreadExecutor((runnable) -> {
Thread thread = new Thread(runnable);
thread.setDaemon(true);
return thread;
});
future = executorService.submit(() -> {
for (;;) {
angle%=360;
Platform.runLater(() -> {
double x = 0, y = 0;
try { x = Double.parseDouble(scheme.get("B1.layoutX")); } catch (Exception e) { }
try { y = Double.parseDouble(scheme.get("B1.layoutY")); } catch (Exception e) { }
scheme.set("L1.layoutX", Double.toString(x + 100 * Math.sin(Math.toRadians(angle))));
scheme.set("L1.layoutY", Double.toString(y + 100 * Math.cos(Math.toRadians(angle))));
scheme.set("L1.rotate", Double.toString(360 - angle));
scheme.set("L1.text", Double.toString(angle));
});
angle++;
try { Thread.sleep(15); } catch (Exception e) { break; }
}
});
executorService.shutdown();
}
});
}
public class Scheme extends Region {
protected ArrayList<String> listSE = new ArrayList<>(Arrays.asList(
"Edit$SELabel",
"Edit$SEButton"
));
protected ObservableList<Label> labelSE;
protected Stage stage;
protected String name = null;
protected Charset charset = Charset.defaultCharset(); //Charset.forName("US-ASCII");
protected boolean modifyed = false;
protected double gridSize = 10;
protected ContextMenu contextMenu = new ContextMenu();
protected MenuItem miNew, miSelect, miDelete, miToFront, miToBack, miProperty;
protected MouseEvent cMouseEvent;
protected SchemeElement editableElement;
protected ScrollPane scrollPane;
protected String xmlName = "scheme";
public Scheme(Stage stage, SchemeElement... children) {
this.stage = Objects.requireNonNull(stage, "Stage cannot be null.");
addSE(children);
stage.setFullScreenExitKeyCombination(KeyCombination.NO_MATCH);
stage.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
if (keyEvent.getCode() == KeyCode.F5) stage.setFullScreen(!stage.isFullScreen());
});
Paint background = Paint.valueOf("linear-gradient(from 0.0% 0.0% to 0.0% 100.0%, 0x90c1eaff 0.0%, 0x5084b0ff 100.0%)");
setBackground(new Background(new BackgroundFill(background, null, null)));
miNew = new MenuItem("New...");
miNew.setOnAction(ae -> { newElement(cMouseEvent.getX(), cMouseEvent.getY()); });
miSelect = new MenuItem("Select");
miSelect.setOnAction(ae -> { setEditableElement(cMouseEvent.getSource()); });
miDelete = new MenuItem("Delete");
miDelete.setOnAction(ae -> { deleteSE((SchemeElement)cMouseEvent.getSource()); modifyed = true; });
miToFront = new MenuItem("To Front");
miToFront.setOnAction(ae -> { ((Node)cMouseEvent.getSource()).toFront(); });
miToBack = new MenuItem("To Back");
miToBack.setOnAction(ae -> { ((Node)cMouseEvent.getSource()).toBack(); });
miProperty = new MenuItem("Property...");
miProperty.setOnAction(ae -> { editProperty((SchemeElement)cMouseEvent.getSource()); });
setOnMousePressed(me -> { mousePressed(me); });
}
public ScrollPane getScrollPane() {
if (scrollPane == null) {
scrollPane = new ScrollPane(this);
scrollPane.viewportBoundsProperty().addListener((v, o, n) -> {
Platform.runLater(() -> requestLayout());
});
}
return scrollPane;
}
@Override protected double computePrefWidth(double width) {
if (scrollPane == null) return super.computePrefWidth(width);
return Math.max(scrollPane.getViewportBounds().getWidth(), super.computePrefWidth(width));
}
@Override protected double computePrefHeight(double height) {
if (scrollPane == null) return super.computePrefHeight(height);
return Math.max(scrollPane.getViewportBounds().getHeight(), super.computePrefHeight(height));
}
public void addSE(SchemeElement... children) {
for (SchemeElement se : children) {
if (se != null) {
try {
ElementProperty ep = se.getProp().get("name");
if (ep != null) {
String nn = ep.toString();
AbstractMap.SimpleEntry<Integer, String> en = checkNameSE(nn);
if (en.getKey().intValue() > 0) throw new RuntimeException(en.getValue());
}
getChildren().add(se);
se.setScheme(this);
} catch (Exception e) { e.printStackTrace(); }
}
}
}
public SchemeElement getSE(String name) {
if (name != null && !name.isEmpty()) {
for (Node node : getChildren()) {
if (name.equals(((SchemeElement)node).getName())) return (SchemeElement)node;
}
}
return null;
}
public boolean containsSE(String name) { return getSE(name) != null; }
public void deleteSE(SchemeElement se) {
getChildren().remove(se);
se.setScheme(null);
}
//public void setNameSE(SchemeElement se, String name) { System.out.println("setNameSE " + name); }
public void setEditableElement(Object element) {
if (editableElement != null) {
editableElement.setEditing(false);
editableElement = null;
}
if (element != null && element instanceof SchemeElement) {
editableElement = (SchemeElement)element;
editableElement.setEditing(true);
editableElement.toFront();
modifyed = true;
}
}
public void mousePressed(MouseEvent me) {
cMouseEvent = me;
if (contextMenu.isShowing()) contextMenu.hide();
setEditableElement(me.isPrimaryButtonDown() ? me.getSource() : null);
if (me.isSecondaryButtonDown()) {
if (me.getSource() instanceof SchemeElement) {
contextMenu.getItems().setAll(miSelect, miToFront, miToBack, miDelete, miProperty);
miProperty.setDisable(((SchemeElement)me.getSource()).getProp().isEmpty());
ArrayList<MenuItem> itemList = ((SchemeElement)me.getSource()).getItems();
if (itemList != null && !itemList.isEmpty()) {
contextMenu.getItems().add(new SeparatorMenuItem());
contextMenu.getItems().addAll(itemList);
}
} else {
contextMenu.getItems().setAll(miNew);
}
contextMenu.show((Node)me.getSource(), me.getScreenX(), me.getScreenY());
}
me.consume();
}
//@Override public ObservableList<Node> getChildren() { return super.getChildren(); }
public Stage getStage() { return stage; }
public String getName() { return name != null ? name : ""; }
public void setName(String name) { if (Edit.validName(name)) this.name = name; }
public ContextMenu getContextMenu() { return contextMenu; }
public double getGridSize() { return gridSize; }
public String getXmlName() { return xmlName; }
public void setModifyed(boolean modifyed) { this.modifyed = modifyed; }
public MouseEvent getCurrentMouseEvent() { return cMouseEvent; }
public void write() {
try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(xmlName.concat(".xml")), charset)) {
XMLStreamWriter xmlw = XMLOutputFactory.newInstance().createXMLStreamWriter(writer);
xmlw.writeStartDocument(); //writeStartDocument(charset.name(), "1.0");
xmlw.writeCharacters("\n");
xmlw.writeStartElement("scheme");
xmlw.writeAttribute("name", getName());
xmlw.writeAttribute("x", Double.toString(getStage().getX()));
xmlw.writeAttribute("y", Double.toString(getStage().getY()));
xmlw.writeAttribute("width", Double.toString(getStage().getWidth()));
xmlw.writeAttribute("height", Double.toString(getStage().getHeight()));
for (String sclss : listSE) {
xmlw.writeCharacters("\n ");
xmlw.writeEmptyElement("element");
xmlw.writeAttribute("clss", sclss);
}
for (Node node : getChildren()) {
xmlw.writeCharacters("\n ");
xmlw.writeEmptyElement("schemeElement");
xmlw.writeAttribute("clss", node.getClass().getName());
Map<String, ElementProperty> prop = ((SchemeElement)node).getProp();
for (ElementProperty ep: prop.values()) xmlw.writeAttribute(ep.getName(), ep.toString());
}
xmlw.writeCharacters("\n");
xmlw.writeEndElement();
xmlw.writeEndDocument();
xmlw.writeCharacters("\n");
xmlw.flush();
xmlw.close();
} catch (Exception e) { e.printStackTrace(); }
}
public void read() {
try (BufferedReader reader = Files.newBufferedReader(Paths.get(xmlName.concat(".xml")), charset)) {
XMLStreamReader xmlr = XMLInputFactory.newInstance().createXMLStreamReader(reader);
Map<String, String> attributeMap = new LinkedHashMap<>();
while (xmlr.hasNext()) {
xmlr.next();
if (xmlr.isStartElement()) {
attributeMap.clear();
for (int i = 0; i < xmlr.getAttributeCount(); i++) attributeMap.put(xmlr.getAttributeLocalName(i), xmlr.getAttributeValue(i));
String elementName = xmlr.getLocalName();
if (elementName.equals("scheme")) {
setName(attributeMap.get("name"));
try { stage.setX(Double.parseDouble(attributeMap.get("x"))); } catch (Exception e) { }
try { stage.setY(Double.parseDouble(attributeMap.get("y"))); } catch (Exception e) { }
try { stage.setWidth(Double.parseDouble(attributeMap.get("width"))); } catch (Exception e) { }
try { stage.setHeight(Double.parseDouble(attributeMap.get("height"))); } catch (Exception e) { }
} else if (elementName.equals("element")) fillListSE(attributeMap);
else if (elementName.equals("schemeElement")) {
SchemeElement se = createSE(attributeMap);
if (se != null) {
se.init(0, 0);
se.setAllProperty(attributeMap);
addSE(se);
fillListSE(attributeMap);
}
}
}
}
xmlr.close();
} catch (Exception e) { e.printStackTrace(); }
}
public void fillListSE(Map<String, String> map) {
String sclss = map.get("clss");
if(sclss != null && !sclss.isEmpty() && !listSE.contains(sclss)) listSE.add(sclss);
}
public void fillLabelSE() {
if (labelSE == null) {
Cursor cursor = getScene().getCursor();
getScene().setCursor(Cursor.WAIT);
labelSE = FXCollections.observableArrayList();
Map<String, String> map = new LinkedHashMap<>();
for (String sclss : listSE) {
map.clear();
map.put("clss", sclss);
SchemeElement se = createSE(map);
labelSE.add(se == null ? new Label(sclss) : new Label(se.getDescription(), se.getPicture(24)));
}
getScene().setCursor(cursor);
}
}
public SchemeElement createSE(Map<String, String> map) {
try {
String className = map.get("clss");
if (className == null || className.isEmpty()) throw new RuntimeException("Missing required attribute clss.");
Class<?> cl = Class.forName(className);
Object inst = className.startsWith("Edit$") ?
cl.getConstructor(Edit.class).newInstance(Edit.this) :
cl.getConstructor().newInstance();
if (!(inst instanceof SchemeElement)) throw new RuntimeException("Is not SchemeElement.");
return (SchemeElement)inst;
} catch (Exception e) { e.printStackTrace(); }
return null;
}
public boolean saveDialog() {
if (!modifyed) return true;
Dialog<ButtonType> dialog = new Dialog<>();
dialog.initOwner(stage);
dialog.initStyle(StageStyle.UTILITY);
dialog.getDialogPane().getButtonTypes().addAll(ButtonType.YES, ButtonType.NO, ButtonType.CANCEL);
dialog.setContentText("Save?");
Optional<ButtonType> result = dialog.showAndWait();
if (result.isPresent()) {
if (result.get() == ButtonType.YES) { write(); return true; }
if (result.get() == ButtonType.NO) return true;
}
return false;
}
public void editProperty(SchemeElement se) {
Dialog<ButtonType> dialog = new Dialog<>();
dialog.initOwner(stage);
dialog.initStyle(StageStyle.UTILITY);
dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
GridPane grid = new GridPane();
grid.setHgap(4);
grid.setVgap(4);
grid.setPadding(new Insets(20, 10, 10, 10));
Map<String, ElementProperty> prop = se.getProp();
int i = 0;
for (ElementProperty ep: prop.values()) {
if (ep.isEditable()) {
grid.add(new Label(ep.getDescription()+":"), 0, i);
Node editor = ep.editIni();
grid.add(editor, 1, i);
if (i == 0) Platform.runLater(() -> editor.requestFocus());
i++;
}
}
dialog.getDialogPane().setContent(grid);
Button buttonOk = (Button)dialog.getDialogPane().lookupButton(ButtonType.OK);
buttonOk.addEventFilter(ActionEvent.ACTION, ef -> {
TextField textField = (TextField)prop.get("name").getEditor();
String nn = textField.getText().trim();
textField.setText(nn);
if (!prop.get("name").toString().equals(nn)) {
AbstractMap.SimpleEntry<Integer, String> en = checkNameSE(nn);
if (en.getKey().intValue() > 0) { alert(en.getValue()); ef.consume(); return; }
}
for (ElementProperty ep: prop.values()) ep.editCommit();
modifyed = true;
});
dialog.showAndWait();
}
public void newElement(double x, double y) {
TextField textField = new TextField("");
ListView<Label> listView = new ListView<>();
listView.setPrefSize(200, 200);
listView.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
listView.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
fillLabelSE();
listView.setItems(labelSE);
//listView.getSelectionModel().selectFirst();
GridPane grid = new GridPane();
grid.add(new Label("Name:"), 0, 0);
grid.add(textField, 1, 0);
grid.add(listView, 0, 1, 2, 1);
grid.setHgap(10);
grid.setVgap(10);
Platform.runLater(() -> textField.requestFocus());
Dialog<ButtonType> dialog = new Dialog<>();
dialog.initOwner(stage);
dialog.initStyle(StageStyle.UTILITY);
dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
dialog.getDialogPane().setContent(grid);
Button buttonOk = (Button)dialog.getDialogPane().lookupButton(ButtonType.OK);
buttonOk.addEventFilter(ActionEvent.ACTION, ef -> {
if (!addEl(textField, listView, x, y)) ef.consume();
});
listView.setOnMouseClicked(me -> {
if (me.getClickCount() == 2 && addEl(textField, listView, x, y)) dialog.close();
});
dialog.showAndWait();
}
public boolean addEl(TextField textField, ListView<Label> listView, double x, double y) {
Map<String, String> map = new LinkedHashMap<>();
int index = listView.getSelectionModel().getSelectedIndex();
if (index < 0) return false;
String nn = textField.getText().trim();
textField.setText(nn);
AbstractMap.SimpleEntry<Integer, String> en = checkNameSE(nn);
if (en.getKey().intValue() > 0) { alert(en.getValue()); return false; }
map.put("clss", listSE.get(index));
map.put("name", nn);
SchemeElement se = createSE(map);
if (se == null) return false;
se.init(x, y);
se.setAllProperty(map);
addSE(se);
modifyed = true;
return true;
}
public AbstractMap.SimpleEntry<Integer, String> checkNameSE(String name) {
if (!name.isEmpty()) {
if (!Edit.validName(name)) { return new AbstractMap.SimpleEntry<>(Integer.valueOf(1), "Element name " + name + " is not valid."); }
if (containsSE(name)) { return new AbstractMap.SimpleEntry<>(Integer.valueOf(2), "Element name " + name + " already defined."); }
}
return new AbstractMap.SimpleEntry<>(Integer.valueOf(0), null);
}
public void alert(String s) {
Alert alert = new Alert(Alert.AlertType.NONE, s, ButtonType.OK);
alert.initOwner(stage);
alert.initStyle(StageStyle.UTILITY);
alert.showAndWait();
}
private BiConsumer<String, String> hAction = null;
public void setOnAction(BiConsumer<String, String> hAction) { this.hAction = hAction; }
public void action(String elementName, String value) { if (hAction != null) hAction.accept(elementName, value); }
private BiConsumer<String, Exception> hMessage= (m, e) -> { System.out.println(m); };
public void setOnMessage(BiConsumer<String, Exception> hMessage) { this.hMessage = hMessage; }
public void message(String message) { message(message, null); }
public void message(String message, Exception exception) { if (hMessage != null) hMessage.accept(message, exception); }
public boolean set(String elementName, String value) {
AbstractMap.SimpleEntry<String, String> en = splitName(elementName);
return set(en.getKey(), en.getValue(), value);
}
public boolean set(String schemeElementName, String propertyName, String value) {
ElementProperty ep = getEP(schemeElementName, propertyName);
if (ep == null) return false;
if (!ep.isChangeable()) { message("Property " + schemeElementName + "." + propertyName +" is not changeable."); return false; }
boolean ret = ep.fromString(value);
if (!ret) message("Property " + schemeElementName + "." + propertyName +" value is not valid.");
return ret;
}
public String get(String elementName) {
AbstractMap.SimpleEntry<String, String> en = splitName(elementName);
return get(en.getKey(), en.getValue());
}
public String get(String schemeElementName, String propertyName) {
ElementProperty ep = getEP(schemeElementName, propertyName);
return ep != null ? ep.toString() : null;
}
public ElementProperty getEP(String schemeElementName, String propertyName) {
if (schemeElementName == null || schemeElementName.isEmpty()) { message("Element name is empty."); return null; }
SchemeElement se = getSE(schemeElementName);
if (se == null) { message("Element " + schemeElementName + " not found."); return null; }
if (propertyName == null || propertyName.isEmpty()) { message("Element " + schemeElementName + " property name is empty."); return null; }
ElementProperty ep = se.getProp().get(propertyName);
if (ep == null) { message("Property " + schemeElementName + "." + propertyName + " not found."); return null; }
return ep;
}
public AbstractMap.SimpleEntry<String, String> splitName(String str) {
int p = 0, c = 0;
for (int i = 0; i < str.length(); i++) if (str.charAt(i) == '.') { p = i; c++; }
if (c != 1) return new AbstractMap.SimpleEntry<>("", "");
return new AbstractMap.SimpleEntry<>(str.substring(0, p), str.substring(p + 1, str.length()));
}
} //end class Scheme
public static class SchemeElement extends Group {
protected StringProperty nameProperty = new SimpleStringProperty(null, "name");
protected Scheme scheme = null;
protected boolean editing = false;
protected Map<String, ElementProperty> properties = new LinkedHashMap<>();
protected ArrayList<Node> anchors;
public SchemeElement() {
setOnMousePressed(me -> { mousePressed(me); });
addProperty(new ElementProperty(nameProperty()).setDescription("Name").setChangeable(false));
}
public void init(double x, double y) {
layoutXProperty().set(x);
layoutYProperty().set(y);
addAllProperty(new ElementProperty(layoutXProperty()).setRestr().setEditable(false),
new ElementProperty(layoutYProperty()).setRestr().setEditable(false)
);
}
public void childrenAddAll(Node... childrens) { getChildren().addAll(childrens); }
public StringProperty nameProperty() { return nameProperty; }
public String getName() { return nameProperty.get() != null ? nameProperty.get() : ""; }
public void mousePressed(MouseEvent me) { if (scheme != null) scheme.mousePressed(me); }
public void setEditing(boolean editing) {
this.editing = editing;
edit(editing);
}
public boolean isEditing() { return editing; }
//public Scheme getScheme() { return scheme; }
public void setScheme(Scheme scheme) {
if (this.scheme != null && scheme != null && this.scheme != scheme) throw new IllegalArgumentException("Scheme already defined.");
this.scheme = scheme;
}
public Map<String, ElementProperty> getProp() { return properties; }
public void addAllProperty(ElementProperty... elements) {
for (ElementProperty ep : elements) addProperty(ep);
}
public void addProperty(ElementProperty ep) {
if (!Edit.validName(ep.getName())) throw new IllegalArgumentException("Property name " + ep.getName() + " is not valid.");
if (properties.containsKey(ep.getName())) throw new IllegalArgumentException("Property name " + ep.getName() + " already defined.");
properties.put(ep.getName(), ep);
ep.setSchemeElement(this);
}
public void setAllProperty(Map<String, String> map) {
for (Map.Entry<String, String> entry : map.entrySet()) setProperty(entry.getKey(), entry.getValue());
}
public void setProperty(String propertyName, String value) {
ElementProperty ep = properties.get(propertyName);
if (ep != null) ep.fromString(value);
}
public String getProperty(String propertyName) {
ElementProperty ep = properties.get(propertyName);
return ep != null ? ep.toString() : "";
}
public void edit(boolean editing) {
//if (scheme == null) throw new NullPointerException("Scheme cannot be null.");
if (editing) {
if (anchors == null) {
anchors = new ArrayList<Node>(Arrays.asList(anchors()));
getChildren().addAll(anchors);
} else anchors.forEach(anchor -> anchor.setVisible(true));
} else if (anchors != null) anchors.forEach(anchor -> anchor.setVisible(false));
}
public Node[] anchors() {
return new Node[] { createAnchor(layoutXProperty(), layoutYProperty(), false) };
}
private double dx, dy;
public Node createAnchor(DoubleProperty x, DoubleProperty y, boolean bind) {
Circle anchor = new Circle(0, 0, 2);
if (bind) {
anchor.centerXProperty().bind(x);
anchor.centerYProperty().bind(y);
}
anchor.setFill(Color.WHITE);
anchor.setStroke(Color.BLACK);
anchor.setStrokeWidth(2);
anchor.setStrokeType(StrokeType.OUTSIDE);
anchor.setOnMousePressed(me -> {
dx = (bind ? x.get() : 0) - me.getX();
dy = (bind ? y.get() : 0) - me.getY();
me.consume();
});
anchor.setOnMouseDragged(me -> {
x.set(me.getX() + dx + (bind ? 0 : x.get()));
y.set(me.getY() + dy + (bind ? 0 : y.get()));
me.consume();
});
anchor.setOnMouseReleased((me) -> { //snap to grid
double gridSize = scheme.getGridSize();
x.set(Math.round(x.get() / gridSize) * gridSize);
y.set(Math.round(y.get() / gridSize) * gridSize);
me.consume();
});
anchor.setOnMouseEntered((mouseEvent) -> { getScene().setCursor(Cursor.HAND); });
anchor.setOnMouseExited((mouseEvent) -> { getScene().setCursor(Cursor.DEFAULT); });
return anchor;
}
public Node createBoundLine(DoubleProperty startX, DoubleProperty startY, DoubleProperty endX, DoubleProperty endY) {
Line boundLine = new Line();
boundLine.startXProperty().bind(startX);
boundLine.startYProperty().bind(startY);
boundLine.endXProperty().bind(endX);
boundLine.endYProperty().bind(endY);
boundLine.setStrokeLineCap(StrokeLineCap.BUTT);
boundLine.getStrokeDashArray().setAll(10.0, 5.0);
return boundLine;
}
public ArrayList<MenuItem> getItems() { return null; }
public String getDescription() { return getClass().getName(); }
public Node getPicture(double size) {
Canvas canvas = new Canvas(size, size);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setStroke(Color.BLUE);
gc.strokeRect(1, 1, size * .67, size * .67);
gc.strokeRect(size * .33, size * .33, size * .6, size * .6);
return canvas;
}
} //end class SchemeElement
public static class ElementProperty {
protected String name = null;
protected Node editor = null;
protected SchemeElement se = null;
protected String description = null;
protected boolean editable = true;
protected boolean changeable = true;
protected Property<?> prop;
protected Class<?> clss;
public ElementProperty(Property<?> prop) { this(prop, prop.getName(), null); }
public ElementProperty(Property<?> prop, String name) { this(prop, name, null); }
public ElementProperty(Property<?> prop, Class<?> clss) { this(prop, prop.getName(), clss); }
public ElementProperty(Property<?> prop, String name, Class<?> clss) {
this.prop = Objects.requireNonNull(prop, "Property cannot be null.");
this.prop = prop;
this.name = name;
this.clss = clss;
}
public String getName() { return name != null ? name : ""; }
public ElementProperty setEditable(boolean editable) { this.editable = editable; return this; }
public boolean isEditable() { return editable; }
public ElementProperty setChangeable(boolean changeable) { this.changeable = changeable; return this; }
public boolean isChangeable() { return changeable; }
public ElementProperty setDescription(String description) { this.description = description; return this; }
public String getDescription() { return description != null ? description : getName(); }
protected ChangeListener<Number> doubleRestr = (v, o, n) -> { if (n.doubleValue() < 0) ((DoubleProperty)v).set(0); };
public ElementProperty setRestr() {
if (prop instanceof DoubleProperty) {
((DoubleProperty)prop).addListener(doubleRestr);
}
return this;
}
//public SchemeElement getSchemeElement() { return se; }
public void setSchemeElement(SchemeElement se) {
if (this.se != null && se != null && this.se != se) throw new IllegalArgumentException("SchemeElement already defined.");
this.se = se;
}
public Property<?> getProperty() { return prop; }
@Override public String toString() { return prop.getValue() == null ? "" : prop.getValue().toString(); }
public Node getEditor() {
if (!editable) throw new RuntimeException("Property is not editable.");
if (editor == null) {
if (prop instanceof StringProperty || prop instanceof DoubleProperty) {
editor = new TextField();
} else if (prop instanceof BooleanProperty) {
editor = new CheckBox();
} else if (prop instanceof ObjectProperty) {
if (clss == null) throw new NullPointerException("Clss cannot be null.");
if (clss.isAssignableFrom(Paint.class)) {
editor = new ColorPicker();
} else if (clss.isEnum()) {
editor = new ComboBox<Object>(FXCollections.observableArrayList(clss.getEnumConstants()));
}
}
if (editor == null) editor = new Label("undefined");
}
return editor;
}
@SuppressWarnings("unchecked")
public Node editIni() {
getEditor();
if (editor instanceof TextField) ((TextField)editor).setText(toString());
else if (editor instanceof CheckBox) ((CheckBox)editor).setSelected(((Boolean)prop.getValue()).booleanValue());
else if (editor instanceof ColorPicker) ((ColorPicker)editor).setValue((Color)prop.getValue());
else if (editor instanceof ComboBox) ((ComboBox<Object>)editor).getSelectionModel().select(prop.getValue());
return editor;
}
@SuppressWarnings("unchecked")
public void editCommit() {
if (!editable || editor == null) return;
if (editor instanceof TextField) fromString(((TextField)editor).getText());
else if (editor instanceof CheckBox) ((BooleanProperty)prop).set(((CheckBox)editor).isSelected());
else if (editor instanceof ColorPicker) ((ObjectProperty<Paint>)prop).set(((ColorPicker)editor).getValue());
else if (editor instanceof ComboBox) ((ObjectProperty)prop).setValue(((ComboBox<Object>)editor).getSelectionModel().getSelectedItem());
}
@SuppressWarnings("unchecked")
public boolean fromString(String s) {
try {
if (prop instanceof StringProperty) ((StringProperty)prop).set(s);
else if (prop instanceof DoubleProperty) {
((DoubleProperty)prop).set(Double.parseDouble(s));
} else if (prop instanceof BooleanProperty) {
((BooleanProperty)prop).set(Boolean.parseBoolean(s));
} else if (prop instanceof ObjectProperty) {
if (clss.isAssignableFrom(Paint.class)) {
((ObjectProperty<Paint>)prop).set(Paint.valueOf(s));
} else if (clss.isEnum()) {
for (Object o : clss.getEnumConstants()) {
if (s.equals(o.toString())) { ((ObjectProperty)prop).setValue(o); break; }
}
} else return false;
} else return false;
} catch (Exception e) { return false; }
return true;
}
} //end class ElementProperty
public class SELabel extends SchemeElement {
public SELabel() {
super();
Label label = new Label("Label");
ObjectProperty<Paint> bgFillProperty = new SimpleObjectProperty<>(null, "bgFill", Color.TRANSPARENT);
bgFillProperty.addListener((v, o, n) -> {
label.setBackground(new Background(new BackgroundFill(n, CornerRadii.EMPTY, Insets.EMPTY)));
});
ObjectProperty<Paint> borderFillProperty = new SimpleObjectProperty<>(null, "borderFill", Color.TRANSPARENT);
borderFillProperty.addListener((v, o, n) -> {
label.setBorder(new Border(new BorderStroke(n, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(1))));
});
label.setPadding(new Insets(2));
label.layoutXProperty().bind(label.widthProperty().divide(-2));
label.layoutYProperty().bind(label.heightProperty().divide(-2));
addAllProperty(new ElementProperty(label.textProperty()),
new ElementProperty(label.textFillProperty(), Paint.class),
new ElementProperty(bgFillProperty, Paint.class),
new ElementProperty(borderFillProperty, Paint.class),
new ElementProperty(label.minWidthProperty()),
new ElementProperty(label.alignmentProperty(), Pos.class),
new ElementProperty(label.rotateProperty())
);
childrenAddAll(label);
}
@Override public String getDescription() { return "Label"; }
} //end class SELabel
public class SEButton extends SchemeElement {
public SEButton() {
super();
Button button = new Button("Button");
addEventFilter(MouseEvent.MOUSE_PRESSED, me -> { if (me.isSecondaryButtonDown()) mousePressed(me); });
button.setOnAction(ae -> { scheme.action(getName().concat(".button"), "pressed"); });
button.setPadding(new Insets(6));
button.layoutXProperty().bind(button.widthProperty().divide(-2));
button.layoutYProperty().bind(button.heightProperty().divide(-2));
addAllProperty(new ElementProperty(button.textProperty()),
new ElementProperty(button.minWidthProperty())
);
childrenAddAll(button);
}
@Override public String getDescription() { return "Button"; }
} //end class SEButton
} //end class Edit