diff --git a/app/logbook/olog/ui/pom.xml b/app/logbook/olog/ui/pom.xml index 9df3fa0dc3..8103b68f94 100644 --- a/app/logbook/olog/ui/pom.xml +++ b/app/logbook/olog/ui/pom.xml @@ -40,6 +40,11 @@ core-security 4.7.4-SNAPSHOT + + org.phoebus + core-ui + 4.7.4-SNAPSHOT + org.jfxtras jfxtras-agenda diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/Messages.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/Messages.java index 7d5d280eaf..bc56349035 100644 --- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/Messages.java +++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/Messages.java @@ -23,6 +23,7 @@ public class Messages AttachmentsDirectoryNotWritable, AttachmentsFileNotDirectory, AttachmentsNoStorage, + AvailableTemplates, Back, CloseRequestHeader, CloseRequestButtonContinue, diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/AttachmentsEditorController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/AttachmentsEditorController.java index 9a7c4f8958..b8e95a4e78 100644 --- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/AttachmentsEditorController.java +++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/AttachmentsEditorController.java @@ -342,10 +342,19 @@ private void addImage(Image image, String id) { } } + /** + * + * @return The {@link ObservableList} of {@link Attachment}s managed in the {@link AttachmentsViewController}. + */ public ObservableList getAttachments() { return attachmentsViewController.getAttachments(); } + /** + * Sets the file upload constraints information in the editor. + * @param maxFileSize Maximum size for a single file. + * @param maxRequestSize Maximum total size of all attachments. + */ public void setSizeLimits(String maxFileSize, String maxRequestSize) { this.maxFileSize = Double.parseDouble(maxFileSize); this.maxRequestSize = Double.parseDouble(maxRequestSize); @@ -387,4 +396,11 @@ private void showFileSizeExceedsLimit(File file) { private void showTotalSizeExceedsLimit() { Platform.runLater(() -> sizesErrorMessage.set(Messages.RequestTooLarge)); } + + /** + * Clears list of {@link Attachment}s. + */ + public void clearAttachments(){ + getAttachments().clear(); + } } diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryEditorController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryEditorController.java index 6702e2854e..078a1d3b6e 100644 --- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryEditorController.java +++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryEditorController.java @@ -23,8 +23,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import javafx.application.Platform; import javafx.beans.binding.Bindings; +import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; @@ -53,6 +55,9 @@ import javafx.scene.paint.Color; import javafx.util.Callback; import javafx.util.StringConverter; +import org.phoebus.framework.autocomplete.Proposal; +import org.phoebus.framework.autocomplete.ProposalProvider; +import org.phoebus.framework.autocomplete.ProposalService; import org.phoebus.framework.jobs.JobManager; import org.phoebus.framework.selection.SelectionService; import org.phoebus.logbook.LogClient; @@ -66,6 +71,7 @@ import org.phoebus.logbook.Tag; import org.phoebus.logbook.olog.ui.HelpViewer; import org.phoebus.logbook.olog.ui.LogbookUIPreferences; +import org.phoebus.logbook.olog.ui.Messages; import org.phoebus.logbook.olog.ui.PreviewViewer; import org.phoebus.olog.es.api.OlogProperties; import org.phoebus.olog.es.api.model.OlogLog; @@ -74,6 +80,7 @@ import org.phoebus.security.tokens.ScopedAuthenticationToken; import org.phoebus.security.tokens.SimpleAuthenticationToken; import org.phoebus.ui.Preferences; +import org.phoebus.ui.autocomplete.AutocompleteMenu; import org.phoebus.ui.dialog.ListSelectionPopOver; import org.phoebus.ui.javafx.ImageCache; import org.phoebus.util.time.TimestampFormats; @@ -237,6 +244,10 @@ public class LogEntryEditorController { */ private Optional logEntryResult = Optional.empty(); + private final ObjectProperty> templatesProperty = + new SimpleObjectProperty<>(FXCollections.observableArrayList()); + //private ObservableList templates = FXCollections.observableArrayList(); + public LogEntryEditorController(LogEntry logEntry, LogEntry inReplyTo, EditMode editMode) { this.replyTo = inReplyTo; this.logFactory = LogService.getInstance().getLogFactories().get(LogbookPreferences.logbook_factory); @@ -456,6 +467,26 @@ public void initialize() { newSelection.forEach(l -> updateDropDown(logbookDropDown, l, true)); }); + AutocompleteMenu autocompleteMenu = new AutocompleteMenu(new ProposalService(new ProposalProvider() { + @Override + public String getName() { + return Messages.AvailableTemplates; + } + + @Override + public List lookup(String text) { + List proposals = new ArrayList<>(); + templatesProperty.get().forEach(template -> { + if (template.name().contains(text)) { + proposals.add(new Proposal(template.name())); + } + }); + return proposals; + } + })); + + autocompleteMenu.attachField(templateSelector.getEditor()); + templateSelector.setCellFactory(new Callback<>() { @Override public ListCell call(ListView logTemplateListView) { @@ -475,28 +506,53 @@ protected void updateItem(LogTemplate item, boolean empty) { templateSelector.valueProperty().addListener((observable, oldValue, newValue) -> { if (newValue != null) { + templateSelector.getEditor().textProperty().set(newValue.name()); if (!newValue.equals(oldValue)) { loadTemplate(newValue); } } + else{ + // E.g. if user specifies an invalid template name + loadTemplate(null); + } }); - templateSelector.setConverter( - new StringConverter<>() { - @Override - public String toString(LogTemplate template) { - if (template == null) { - return ""; - } else { - return template.name(); - } - } + // Hide autocomplete menu when user clicks drop-down button + templateSelector.showingProperty().addListener((obs, wasShowing, isNowShowing) -> autocompleteMenu.hide()); - @Override - public LogTemplate fromString(String s) { + templateSelector.getEditor().textProperty().addListener((obs, o, n) -> { + if(n != null && !n.isEmpty()){ + Optional logTemplate = + templatesProperty.get().stream().filter(t -> t.name().equals(n)).findFirst(); + logTemplate.ifPresent(template -> templateSelector.valueProperty().setValue(template)); + } + }); + + templateSelector.setConverter( + new StringConverter<>() { + @Override + public String toString(LogTemplate template) { + if (template == null) { return null; + } else { + return template.name(); } - }); + } + + /** + * Converts the user specified string to a {@link LogTemplate} + * @param name The name of an (existing) template + * @return A {@link LogTemplate} if name matches an existing one, otherwise null + */ + @Override + public LogTemplate fromString(String name) { + Optional logTemplate = + templatesProperty.get().stream().filter(t -> t.name().equals(name)).findFirst(); + return logTemplate.orElse(null); + } + }); + + templateSelector.itemsProperty().bind(templatesProperty); // Note: logbooks and tags are retrieved asynchronously from service getServerSideStaticData(); @@ -767,8 +823,7 @@ private void getServerSideStaticData() { logger.log(Level.WARNING, "Failed to get or parse response from server info request", e); } - Collection templates = logClient.getTemplates(); - Platform.runLater(() -> templateSelector.getItems().addAll(templates)); + templatesProperty.get().setAll(logClient.getTemplates().stream().toList()); }); } @@ -840,16 +895,30 @@ public Optional getLogEntryResult() { /** * Loads template to configure UI elements. * - * @param logTemplate A {@link LogTemplate} selected by user. + * @param logTemplate A {@link LogTemplate} selected by user. If null, all log entry elements + * will be cleared, except the Level selector, which will be set to the top-most item. */ private void loadTemplate(LogTemplate logTemplate) { - titleProperty.set(logTemplate.title()); - descriptionProperty.set(logTemplate.source()); - logPropertiesEditorController.setProperties(logTemplate.properties()); - selectedTags.setAll(logTemplate.tags().stream().map(Tag::getName).toList()); - selectedLogbooks.setAll(logTemplate.logbooks().stream().map(Logbook::getName).toList()); - levelSelector.getSelectionModel().select(logTemplate.level()); - selectedTags.forEach(t -> updateDropDown(tagDropDown, t, true)); - selectedLogbooks.forEach(l -> updateDropDown(logbookDropDown, l, true)); + if(logTemplate != null){ + titleProperty.set(logTemplate.title()); + descriptionProperty.set(logTemplate.source()); + logPropertiesEditorController.setProperties(logTemplate.properties()); + selectedTags.setAll(logTemplate.tags().stream().map(Tag::getName).toList()); + selectedLogbooks.setAll(logTemplate.logbooks().stream().map(Logbook::getName).toList()); + levelSelector.getSelectionModel().select(logTemplate.level()); + selectedTags.forEach(t -> updateDropDown(tagDropDown, t, true)); + selectedLogbooks.forEach(l -> updateDropDown(logbookDropDown, l, true)); + } + else{ + titleProperty.set(null); + descriptionProperty.set(null); + logPropertiesEditorController.clearSelectedProperties(); + attachmentsEditorController.clearAttachments(); + selectedTags.setAll(Collections.emptyList()); + selectedLogbooks.setAll(Collections.emptyList()); + levelSelector.getSelectionModel().select(0); + selectedTags.forEach(t -> updateDropDown(tagDropDown, t, false)); + selectedLogbooks.forEach(l -> updateDropDown(logbookDropDown, l, false)); + } } } diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogPropertiesEditorController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogPropertiesEditorController.java index 463cbae732..cb520e094e 100644 --- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogPropertiesEditorController.java +++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogPropertiesEditorController.java @@ -173,12 +173,22 @@ public List getProperties() { return selectedProperties; } + /** + * @param properties {@link Collection} of {@link Property}s to set in the editor. + */ public void setProperties(Collection properties){ - if(properties == null){ - return; + if(properties != null){ + selectedProperties.addAll(properties); + availableProperties.removeAll(properties); } - selectedProperties.addAll(properties); - availableProperties.removeAll(properties); + } + + /** + * Moves all selected {@link Property}s back to list of available. + */ + public void clearSelectedProperties(){ + availableProperties.addAll(selectedProperties); + selectedProperties.clear(); } /** diff --git a/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/messages.properties b/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/messages.properties index 7e064c37b8..21bd2841f8 100644 --- a/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/messages.properties +++ b/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/messages.properties @@ -16,6 +16,7 @@ Attachments=Attachments AttachmentsDirectoryFailedCreate=Failed to create directory {0} for attachments AttachmentsFileNotDirectory=File {0} exists but is not a directory AttachmentsSearchProperty=Attachments: +AvailableTemplates=Available Templates Back=Back BrowseButton=Browse Cancel=Cancel diff --git a/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/write/LogEntryEditor.fxml b/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/write/LogEntryEditor.fxml index 6fb8337f15..8bd98cd5d9 100644 --- a/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/write/LogEntryEditor.fxml +++ b/app/logbook/olog/ui/src/main/resources/org/phoebus/logbook/olog/ui/write/LogEntryEditor.fxml @@ -49,7 +49,7 @@ diff --git a/core/logbook/src/main/java/org/phoebus/logbook/LogTemplate.java b/core/logbook/src/main/java/org/phoebus/logbook/LogTemplate.java index 9d7c41fa43..fddae0cde2 100644 --- a/core/logbook/src/main/java/org/phoebus/logbook/LogTemplate.java +++ b/core/logbook/src/main/java/org/phoebus/logbook/LogTemplate.java @@ -32,4 +32,9 @@ public record LogTemplate(String id, Collection logbooks, Collection tags, Collection properties){ + + @Override + public String toString(){ + return name; + } } diff --git a/core/ui/src/main/java/org/phoebus/ui/autocomplete/AutocompleteMenu.java b/core/ui/src/main/java/org/phoebus/ui/autocomplete/AutocompleteMenu.java index 2ee36016bd..c17c21924e 100644 --- a/core/ui/src/main/java/org/phoebus/ui/autocomplete/AutocompleteMenu.java +++ b/core/ui/src/main/java/org/phoebus/ui/autocomplete/AutocompleteMenu.java @@ -320,4 +320,11 @@ private void invokeAction(final TextInputControl field) } menu.hide(); } + + /** + * Explicitly hides the menu. + */ + public void hide(){ + menu.hide(); + } }