package regexgolf2.ui.subcomponents.editablelabel; import java.util.logging.Logger; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; import com.google.java.contract.Ensures; import com.google.java.contract.Requires; /** * A control that looks like a Label. If clicked, a TextField is shown and the * Text of the Label can be edited. Can show a small icon to indicate that * editing is possible, this has to be set with the * {@link #editIconAppearsProperty()}. Editing can be disabled. EditMode can be * entered and exited. */ public class EditableLabel { private static final Logger _LOG = Logger.getLogger(EditableLabel.class.getName()); // UI properties private final AnchorPane _root = new AnchorPane(); private final TextField _textField = new TextField(); private final Label _label = new Label(); private final ImageView _editIcon = new ImageView(); private final BooleanProperty _editIconAppears = new SimpleBooleanProperty(); // Logic properties private final StringProperty _text = new SimpleStringProperty(); private final BooleanProperty _editable = new SimpleBooleanProperty(); private final BooleanProperty _editMode = new SimpleBooleanProperty(); public EditableLabel() { initBindings(); initLayout(); initListeners(); } private void initLayout() { _textField.prefWidthProperty().bind(_label.widthProperty().add(16)); _root.prefWidthProperty().bind( _textField.prefWidthProperty().add(_textField.heightProperty())); _editIcon.setFitHeight(15.0); _editIcon.setFitWidth(15.0); AnchorPane.setTopAnchor(_label, 4.0); AnchorPane.setLeftAnchor(_label, 8.0); AnchorPane.setTopAnchor(_textField, 0.0); AnchorPane.setLeftAnchor(_textField, 0.0); AnchorPane.setRightAnchor(_editIcon, 2.0); AnchorPane.setTopAnchor(_editIcon, 5.0); AnchorPane.setBottomAnchor(_editIcon, 3.0); _root.getChildren().add(_editIcon); _root.getChildren().add(_label); _root.getChildren().add(_textField); // XXX here the Image(String) constructor could be used Image image = new Image(this.getClass().getResourceAsStream("/regexgolf2/ui/img/edit.png")); _editIcon.setImage(image); } private void initBindings() { // Bind label and textField to textProperty _label.textProperty().bind(_text); _textField.textProperty().bindBidirectional(_text); _editIcon.visibleProperty().bind(_editable.and(_editMode.not().and(_editIconAppears))); _textField.visibleProperty().bind(_editMode.and(_editable)); _label.visibleProperty().bind(_editMode.not()); } /** * Initializes various Listeners: - If the Enter-Key is pressed in the * TextField, editing mode is exited. - If the TextField loses focus, * editing mode is exited. - If editing gets disabled, the editing mode is * exited. - If edit mode is entered, the TextField requests focus. */ private void initListeners() { // TextField enter key pressed -> exit edit mode _textField.setOnAction(e -> exitEditMode()); // TextField focus lost -> exit edit mode _textField.focusedProperty().addListener((o, oV, focused) -> { if (!focused) exitEditMode(); }); _editable.addListener((o, oV, editable) -> { if (!editable && isEditMode()) exitEditMode(); }); _editMode.addListener((o, oV, activated) -> { if (activated) _textField.requestFocus(); }); } // --------------------------- text -------------------------------- @Ensures("result != null") public String getText() { return _text.get(); } @Requires("text != null") private void setText(String text) { _text.set(text); } @Ensures("result != null") public StringProperty textProperty() { return _text; } // --------------------------- editable ---------------------------- public boolean isEditable() { return _editable.get(); } public void setEditable(boolean editable) { _editable.set(editable); } @Ensures("result != null") public BooleanProperty editableProperty() { return _editable; } // --------------------------- editMode ---------------------------- /** * This stops the edit mode. */ public void exitEditMode() { _editMode.set(false); _LOG.info("exit edit mode"); } /** * Tries to enter edit mode. if editing is disabled, this will return false, * else, editing mode will be activated and true is returned. */ public boolean tryEnterEditMode() { if (isEditable()) { _editMode.set(true); // _textField.requestFocus(); _LOG.info("ented edit mode"); return true; } _LOG.info("could not enter edit mode"); return false; } /** * Indicates if this EditableLabel is currently in editing mode. * * @return */ public boolean isEditMode() { return _editMode.get(); } /** * Readonly Property. If you want to set the editmode, use * {@link #tryEnterEditMode()} or {@link #exitEditMode()}. */ @Ensures("result != null") public ReadOnlyBooleanProperty editModeProperty() { return _editMode; } // --------------------------- editIconVisible --------------------- /** * The edit icon will only be visible if three criteria are met: 1. Editing * is enabled. 2. Edit mode is currently disabled. 3. This Property is set * to true. Usually, this is bound to a mouse hover Property to make the * icon show up if the mouse hovers a specific area. */ @Ensures("result != null") public BooleanProperty editIconAppearsProperty() { return _editIconAppears; } /** * Returns the Node to be used inside the visual tree. */ @Ensures("result != null") public Node getUINode() { return _root; } }