Merge optional Message popups with child notices carried onto the parent.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -43,10 +43,12 @@ public final class LoadedView {
|
||||
/**
|
||||
* Returns the JavaFX property for an Attribute name.
|
||||
* <p>
|
||||
* The property's class follows the Attribute's declared type.
|
||||
* The property's class follows the Attribute's declared type. The name {@code latestMessage}
|
||||
* is a string property holding the text of the latest Message.
|
||||
*
|
||||
* @param name the Attribute name
|
||||
* @param name the Attribute name, or {@code latestMessage}
|
||||
* @return the property, or {@code null} when that name is not a read-write Attribute
|
||||
* and is not {@code latestMessage}
|
||||
*/
|
||||
public Property<?> property(String name) {
|
||||
return viewModel.property(name);
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package de.neitzel.fx.view;
|
||||
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.Dialog;
|
||||
import javafx.scene.control.Label;
|
||||
|
||||
/**
|
||||
* Shows each newly added {@link Message} in a dialog when the developer asks.
|
||||
* <p>
|
||||
* The library shows nothing until {@link #showNewMessages(ViewModel)} is called. An in-place
|
||||
* update of an existing binding failure is not a new Message, so it does not open another dialog.
|
||||
*/
|
||||
public final class MessagePopup {
|
||||
|
||||
private MessagePopup() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows each Message added to {@code viewModel} after this call in a dialog.
|
||||
* <p>
|
||||
* The dialog is shown with {@link Dialog#show()}, so the caller is not blocked. Messages
|
||||
* already on the ViewModel stay where they are. A later in-place update of a binding failure
|
||||
* does not open another dialog.
|
||||
*
|
||||
* @param viewModel the ViewModel whose new Messages should be shown
|
||||
*/
|
||||
public static void showNewMessages(ViewModel viewModel) {
|
||||
viewModel.whenMessageAdded(MessagePopup::show);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a dialog whose content is the Message text.
|
||||
*
|
||||
* @param message the notice to show
|
||||
*/
|
||||
private static void show(Message message) {
|
||||
Dialog<Void> dialog = new Dialog<>();
|
||||
dialog.setHeaderText(null);
|
||||
dialog.getDialogPane().setContent(new Label(message.getText()));
|
||||
dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,9 @@ public final class ViewLoader {
|
||||
* bindings are not applied during this load. They run when the Component's model property
|
||||
* becomes a non-null Model. A Component that implements {@link HasViewModel} receives that
|
||||
* ViewModel after the property is set and before those bindings run.
|
||||
* Loading does not open a popup or fill a status bar. A Direct binding whose path is
|
||||
* {@code latestMessage} shows the latest Message text. {@link MessagePopup#showNewMessages}
|
||||
* shows each newly added Message in a dialog.
|
||||
*
|
||||
* @param fxml the FXML document
|
||||
* @param model the Model instance
|
||||
|
||||
@@ -13,6 +13,7 @@ import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleLongProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
@@ -40,6 +41,7 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* The object a View binds to, built at runtime for one Model instance.
|
||||
@@ -67,6 +69,10 @@ import java.util.Set;
|
||||
* Info and warn are not. A parent carries each current child message, with the source extended by
|
||||
* the Attribute that leads to that child. Each further parent prepends its own Attribute. The
|
||||
* carried copy is not logged again. When the child drops the message, the parent drops that copy.
|
||||
* The name {@code latestMessage} is a string property holding the text of the latest Message, so a
|
||||
* Direct binding can show that text on a control. The library does not open a popup or fill a
|
||||
* status bar unless the developer asks. {@link MessagePopup#showNewMessages} shows each newly added
|
||||
* Message in a dialog. An in-place update does not.
|
||||
*/
|
||||
public class ViewModel {
|
||||
|
||||
@@ -79,6 +85,8 @@ public class ViewModel {
|
||||
private final Map<String, Message> bindingFailures = new HashMap<>();
|
||||
private final List<MessageListener> messageListeners = new ArrayList<>();
|
||||
private final Map<String, ChildMessageMirror> childMirrors = new HashMap<>();
|
||||
private final StringProperty latestMessageText = new SimpleStringProperty();
|
||||
private final List<Consumer<Message>> messageAdded = new ArrayList<>();
|
||||
private Message latestMessage;
|
||||
|
||||
/**
|
||||
@@ -117,11 +125,19 @@ public class ViewModel {
|
||||
|
||||
/**
|
||||
* Returns the JavaFX property for an Attribute name.
|
||||
* <p>
|
||||
* The name {@code latestMessage} is the text of the latest {@link Message}, beside the
|
||||
* Model attributes. It is a string property. A Direct binding can read it. An in-place
|
||||
* update of that Message refreshes the text and does not replace the Message.
|
||||
*
|
||||
* @param name the Attribute name
|
||||
* @param name the Attribute name, or {@code latestMessage}
|
||||
* @return the property, or {@code null} when that name is not a read-write Attribute
|
||||
* and is not {@code latestMessage}
|
||||
*/
|
||||
public Property<?> property(String name) {
|
||||
if ("latestMessage".equals(name)) {
|
||||
return latestMessageText;
|
||||
}
|
||||
return properties.get(name);
|
||||
}
|
||||
|
||||
@@ -345,8 +361,23 @@ public class ViewModel {
|
||||
public void addMessage(Message message) {
|
||||
messages.add(message);
|
||||
latestMessage = message;
|
||||
publishLatestMessage();
|
||||
logSevere(message);
|
||||
notifyAdded(message);
|
||||
for (Consumer<Message> listener : List.copyOf(messageAdded)) {
|
||||
listener.accept(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@code listener} each time a Message is added.
|
||||
* <p>
|
||||
* An in-place update of a binding failure does not call the listener.
|
||||
*
|
||||
* @param listener the listener
|
||||
*/
|
||||
void whenMessageAdded(Consumer<Message> listener) {
|
||||
messageAdded.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -357,7 +388,11 @@ public class ViewModel {
|
||||
void addCarriedMessage(Message copy) {
|
||||
messages.add(copy);
|
||||
latestMessage = copy;
|
||||
publishLatestMessage();
|
||||
notifyAdded(copy);
|
||||
for (Consumer<Message> listener : List.copyOf(messageAdded)) {
|
||||
listener.accept(copy);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -371,6 +406,7 @@ public class ViewModel {
|
||||
}
|
||||
if (latestMessage == copy) {
|
||||
latestMessage = messages.isEmpty() ? null : messages.get(messages.size() - 1);
|
||||
publishLatestMessage();
|
||||
}
|
||||
notifyRemoved(copy);
|
||||
}
|
||||
@@ -413,6 +449,9 @@ public class ViewModel {
|
||||
* @param message the notice
|
||||
*/
|
||||
void noteMessageText(Message message) {
|
||||
if (message == latestMessage) {
|
||||
publishLatestMessage();
|
||||
}
|
||||
notifyTextChanged(message);
|
||||
}
|
||||
|
||||
@@ -449,6 +488,9 @@ public class ViewModel {
|
||||
Message existing = bindingFailures.get(path);
|
||||
if (existing != null) {
|
||||
existing.setText(text);
|
||||
if (existing == latestMessage) {
|
||||
publishLatestMessage();
|
||||
}
|
||||
logSevere(existing);
|
||||
notifyTextChanged(existing);
|
||||
return;
|
||||
@@ -471,10 +513,18 @@ public class ViewModel {
|
||||
messages.remove(existing);
|
||||
if (latestMessage == existing) {
|
||||
latestMessage = messages.isEmpty() ? null : messages.get(messages.size() - 1);
|
||||
publishLatestMessage();
|
||||
}
|
||||
notifyRemoved(existing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the string property for {@code latestMessage} match {@link #getLatestMessage()}.
|
||||
*/
|
||||
private void publishLatestMessage() {
|
||||
latestMessageText.set(latestMessage == null ? null : latestMessage.getText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an error or fatal notice to the log. Info and warn are left out.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user