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