From 728151dec12883ddf41e0e81c43e4ec80a79c48d Mon Sep 17 00:00:00 2001 From: georgweiss Date: Mon, 25 Mar 2024 19:43:32 +0100 Subject: [PATCH 01/11] New context menu items for DockItemWithInput tab --- .../filebrowser/FileBrowserController.java | 12 +- .../org/phoebus/ui/application/Messages.java | 4 + .../java/org/phoebus/ui/docking/DockItem.java | 92 +++-- .../phoebus/ui/docking/DockItemWithInput.java | 360 ++++++++++-------- .../ui/application/messages.properties | 4 + 5 files changed, 260 insertions(+), 212 deletions(-) diff --git a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java index 7577f277ac..15da2953cb 100644 --- a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java +++ b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java @@ -5,18 +5,8 @@ import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.collections.ObservableList; import javafx.fxml.FXML; -import javafx.scene.control.Alert; +import javafx.scene.control.*; import javafx.scene.control.Alert.AlertType; -import javafx.scene.control.Button; -import javafx.scene.control.ContextMenu; -import javafx.scene.control.Menu; -import javafx.scene.control.MenuItem; -import javafx.scene.control.SelectionMode; -import javafx.scene.control.SeparatorMenuItem; -import javafx.scene.control.TextField; -import javafx.scene.control.TreeItem; -import javafx.scene.control.TreeTableColumn; -import javafx.scene.control.TreeTableView; import javafx.scene.image.ImageView; import javafx.scene.input.Clipboard; import javafx.scene.input.ContextMenuEvent; diff --git a/core/ui/src/main/java/org/phoebus/ui/application/Messages.java b/core/ui/src/main/java/org/phoebus/ui/application/Messages.java index e9a8cdb981..a16789f778 100644 --- a/core/ui/src/main/java/org/phoebus/ui/application/Messages.java +++ b/core/ui/src/main/java/org/phoebus/ui/application/Messages.java @@ -20,6 +20,7 @@ public class Messages public static String AppRevision; public static String AppVersionHeader; public static String CloseAllTabs; + public static String CopyResourcePath; public static String DeleteLayouts; public static String DeleteLayoutsConfirmFmt; public static String DeleteLayoutsInfo; @@ -130,6 +131,9 @@ public class Messages public static String ScreenshotErrHdr; public static String ScreenshotErrMsg; public static String SelectTab; + public static String ShowIn; + public static String ShowInFileBrowserApp; + public static String ShowInNativeFileBrowser; public static String ShowStatusbar; public static String ShowToolbar; public static String Time12h; diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockItem.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockItem.java index e44a4ee891..8c3629cfdf 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockItem.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockItem.java @@ -137,6 +137,23 @@ public class DockItem extends Tab /** Called after tab was closed */ private List closed_callback = null; + private MenuItem info; + private MenuItem detach; + + private MenuItem split_horiz; + + private MenuItem split_vert; + + private MenuItem save_window; + + private MenuItem close; + + private MenuItem close_other; + + private MenuItem close_all; + + private ContextMenu menu; + /** Create dock item for instance of an application * @param applicationInstance {@link AppInstance} * @param content Content for this application instance @@ -224,19 +241,19 @@ public DockPane getDockPane() private void createContextMenu() { - final MenuItem info = new MenuItem(Messages.DockInfo, new ImageView(info_icon)); + info = new MenuItem(Messages.DockInfo, new ImageView(info_icon)); info.setOnAction(event -> showInfo()); - final MenuItem detach = new MenuItem(Messages.DockDetach, new ImageView(detach_icon)); + detach = new MenuItem(Messages.DockDetach, new ImageView(detach_icon)); detach.setOnAction(event -> detach()); - final MenuItem split_horiz = new MenuItem(Messages.DockSplitH, new ImageView(split_horiz_icon)); + split_horiz = new MenuItem(Messages.DockSplitH, new ImageView(split_horiz_icon)); split_horiz.setOnAction(event -> split(true)); - final MenuItem split_vert = new MenuItem(Messages.DockSplitV, new ImageView(split_vert_icon)); + split_vert = new MenuItem(Messages.DockSplitV, new ImageView(split_vert_icon)); split_vert.setOnAction(event -> split(false)); - final MenuItem save_window = new MenuItem(Messages.SaveLayoutOfContainingWindowAs, new ImageView(save_window_layout_icon)); + save_window = new MenuItem(Messages.SaveLayoutOfContainingWindowAs, new ImageView(save_window_layout_icon)); save_window.setOnAction(event -> { DockPane activeDockPane = getActiveDockPane(); List stagesContainingActiveDockPane = DockStage.getDockStages().stream().filter(stage -> getDockPanes(stage).contains(activeDockPane)).collect(Collectors.toList()); @@ -251,12 +268,12 @@ else if (stagesContainingActiveDockPane.size() == 0) { } }); - final MenuItem close = new MenuItem(Messages.DockClose, new ImageView(DockPane.close_icon)); + close = new MenuItem(Messages.DockClose, new ImageView(DockPane.close_icon)); ArrayList arrayList = new ArrayList(); arrayList.add(this); close.setOnAction(event -> close(arrayList)); - final MenuItem close_other = new MenuItem(Messages.DockCloseOthers, new ImageView(close_many_icon)); + close_other = new MenuItem(Messages.DockCloseOthers, new ImageView(close_many_icon)); close_other.setOnAction(event -> { // Close all other tabs in non-fixed panes of this window @@ -270,7 +287,7 @@ else if (stagesContainingActiveDockPane.size() == 0) { close(tabs); }); - final MenuItem close_all = new MenuItem(Messages.DockCloseAll, new ImageView(close_many_icon)); + close_all = new MenuItem(Messages.DockCloseAll, new ImageView(close_many_icon)); close_all.setOnAction(event -> { // Close all tabs in non-fixed panes of this window @@ -284,41 +301,44 @@ else if (stagesContainingActiveDockPane.size() == 0) { close(tabs); }); - final ContextMenu menu = new ContextMenu(info); - + menu = new ContextMenu(info); menu.setOnShowing(event -> { - menu.getItems().setAll(info); - - final boolean may_lock = AuthorizationService.hasAuthorization("lock_ui"); - final DockPane pane = getDockPane(); - if (pane.isFixed()) - { - if (may_lock) - menu.getItems().addAll(new NamePaneMenuItem(pane), new UnlockMenuItem(pane)); - } - else - { - menu.getItems().addAll(new SeparatorMenuItem(), - detach, - split_horiz, - split_vert); - - if (may_lock) - menu.getItems().addAll(new NamePaneMenuItem(pane), new LockMenuItem(pane)); - - menu.getItems().addAll(new SeparatorMenuItem(), - close, - close_other, - new SeparatorMenuItem(), - close_all); - } - menu.getItems().addAll(new SeparatorMenuItem(), save_window); + configureContextMenu(menu); }); name_tab.setContextMenu(menu); } + protected void configureContextMenu(ContextMenu menu){ + menu.getItems().setAll(info); + + final boolean may_lock = AuthorizationService.hasAuthorization("lock_ui"); + final DockPane pane = getDockPane(); + if (pane.isFixed()) + { + if (may_lock) + menu.getItems().addAll(new NamePaneMenuItem(pane), new UnlockMenuItem(pane)); + } + else + { + menu.getItems().addAll(new SeparatorMenuItem(), + detach, + split_horiz, + split_vert); + + if (may_lock) + menu.getItems().addAll(new NamePaneMenuItem(pane), new LockMenuItem(pane)); + + menu.getItems().addAll(new SeparatorMenuItem(), + close, + close_other, + new SeparatorMenuItem(), + close_all); + } + menu.getItems().addAll(new SeparatorMenuItem(), save_window); + } + /** @param tabs Tabs to prepare and then close */ private void close(final ArrayList tabs) { diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java index fba63d64e6..0c01c67361 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java @@ -7,9 +7,35 @@ *******************************************************************************/ package org.phoebus.ui.docking; -import static org.phoebus.ui.application.PhoebusApplication.logger; +import javafx.application.Platform; +import javafx.scene.Node; +import javafx.scene.control.*; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; +import javafx.scene.layout.Region; +import javafx.stage.FileChooser.ExtensionFilter; +import javafx.stage.Window; +import org.apache.commons.io.FilenameUtils; +import org.phoebus.framework.jobs.JobManager; +import org.phoebus.framework.jobs.JobMonitor; +import org.phoebus.framework.jobs.JobRunnable; +import org.phoebus.framework.spi.AppInstance; +import org.phoebus.framework.util.ResourceParser; +import org.phoebus.framework.workbench.ApplicationService; +import org.phoebus.ui.application.Messages; +import org.phoebus.ui.dialog.DialogHelper; +import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; +import org.phoebus.ui.dialog.SaveAsDialog; +import org.phoebus.ui.javafx.ImageCache; +import java.awt.*; import java.io.File; +import java.io.IOException; import java.net.URI; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; @@ -23,54 +49,36 @@ import java.util.logging.Level; import java.util.stream.Collectors; -import javafx.scene.layout.Region; -import javafx.stage.Window; -import org.apache.commons.io.FilenameUtils; -import org.phoebus.framework.jobs.JobManager; -import org.phoebus.framework.jobs.JobMonitor; -import org.phoebus.framework.jobs.JobRunnable; -import org.phoebus.framework.spi.AppInstance; -import org.phoebus.framework.util.ResourceParser; -import org.phoebus.ui.application.Messages; -import org.phoebus.ui.dialog.DialogHelper; -import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; -import org.phoebus.ui.dialog.SaveAsDialog; - -import javafx.application.Platform; -import javafx.scene.Node; -import javafx.scene.control.Alert; -import javafx.scene.control.Alert.AlertType; -import javafx.scene.control.ButtonType; -import javafx.scene.control.Tab; -import javafx.scene.control.Tooltip; -import javafx.stage.FileChooser.ExtensionFilter; +import static org.phoebus.ui.application.PhoebusApplication.logger; -/** Item for a {@link DockPane} that has an 'input' file or URI. +/** + * Item for a {@link DockPane} that has an 'input' file or URI. * - *

While technically a {@link Tab}, - * only the methods declared in here and - * in {@link DockItem} should be called - * to assert compatibility with future updates. + *

While technically a {@link Tab}, + * only the methods declared in here and + * in {@link DockItem} should be called + * to assert compatibility with future updates. * - *

Tracks the current 'input' and the 'dirty' state. - * When the item becomes 'dirty', 'Save' or 'Save As' - * are supported via the provided list of file extensions - * and a 'save_handler'. - * User will be asked to save a dirty tab when the tab is closed. - * Saving can also be initiated from the 'File' menu. - * If the 'input' is null, 'Save' automatically - * invokes 'Save As' to prompt for a file name. + *

Tracks the current 'input' and the 'dirty' state. + * When the item becomes 'dirty', 'Save' or 'Save As' + * are supported via the provided list of file extensions + * and a 'save_handler'. + * User will be asked to save a dirty tab when the tab is closed. + * Saving can also be initiated from the 'File' menu. + * If the 'input' is null, 'Save' automatically + * invokes 'Save As' to prompt for a file name. * - * @author Kay Kasemir + * @author Kay Kasemir */ @SuppressWarnings("nls") -public class DockItemWithInput extends DockItem -{ +public class DockItemWithInput extends DockItem { private static final String DIRTY = "* "; private AtomicBoolean is_dirty = new AtomicBoolean(false); - /** The one item that should always be included in 'file_extensions' */ + /** + * The one item that should always be included in 'file_extensions' + */ public static final ExtensionFilter ALL_FILES = new ExtensionFilter(Messages.DockAll, "*.*"); private final ExtensionFilter[] file_extensions; @@ -79,37 +87,70 @@ public class DockItemWithInput extends DockItem private volatile URI input; - /** Create dock item + private final static Image copyToClipboardIcon = ImageCache.getImage(DockItem.class, "/icons/copy.png"); + + private final static Image showInIcon = ImageCache.getImage(DockItem.class, "/icons/fldr_obj.png"); + + /** + * Create dock item * - *

The 'save_handler' will be called to save the content. - * It will be called in a background job, because writing files - * might be slow. + *

The 'save_handler' will be called to save the content. + * It will be called in a background job, because writing files + * might be slow. * - *

When 'save_handler' is called, the 'input' will be set to a file-based URI. - * On success, or if for some reason there is nothing to save, - * the 'save_handler' returns. - * On error, the 'save_handler' throws an exception. + *

When 'save_handler' is called, the 'input' will be set to a file-based URI. + * On success, or if for some reason there is nothing to save, + * the 'save_handler' returns. + * On error, the 'save_handler' throws an exception. * - * @param application {@link AppInstance} - * @param content Initial content - * @param input URI for the input. May be null - * @param file_extensions File extensions for "Save As". May be null if never calling setDirty(true) - * @param save_handler Will be called to 'save' the content. May be null if never calling setDirty(true) + * @param application {@link AppInstance} + * @param content Initial content + * @param input URI for the input. May be null + * @param file_extensions File extensions for "Save As". May be null if never calling setDirty(true) + * @param save_handler Will be called to 'save' the content. May be null if never calling setDirty(true) */ public DockItemWithInput(final AppInstance application, final Node content, final URI input, final ExtensionFilter[] file_extensions, - final JobRunnable save_handler) - { + final JobRunnable save_handler) { super(application, content); - this.file_extensions = file_extensions; + this.file_extensions = file_extensions; this.save_handler = save_handler; setInput(input); + name_tab.getContextMenu().setOnShowing(e -> { + this.configureContextMenu(name_tab.getContextMenu()); + }); + } + + protected void configureContextMenu(ContextMenu menu) { + super.configureContextMenu(menu); + final Menu showInMenu = new Menu(Messages.ShowIn, new ImageView(showInIcon)); + final MenuItem showInFileBrowser = new MenuItem(Messages.ShowInFileBrowserApp); + showInFileBrowser.setOnAction(e -> { + ApplicationService.createInstance("file_browser", new File(input.getPath()).getParentFile().toURI()); + }); + final MenuItem showInNativeFileBrowser = new MenuItem(Messages.ShowInNativeFileBrowser); + showInNativeFileBrowser.setOnAction(e -> { + try { + Desktop.getDesktop().open(new File(input.getPath()).getParentFile()); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + }); + final MenuItem copyResourceToClipboard = new MenuItem(Messages.CopyResourcePath, new ImageView(copyToClipboardIcon)); + copyResourceToClipboard.setOnAction(e -> { + final ClipboardContent content = new ClipboardContent(); + content.putString(input.getPath()); + Clipboard.getSystemClipboard().setContent(content); + }); + + showInMenu.getItems().addAll(showInFileBrowser, showInNativeFileBrowser); + name_tab.getContextMenu().getItems().add(1, showInMenu); + name_tab.getContextMenu().getItems().add(2, copyResourceToClipboard); } // Override to include 'dirty' tab @Override - public void setLabel(final String label) - { + public void setLabel(final String label) { name = label; if (isDirty()) name_tab.setText(DIRTY + label); @@ -119,15 +160,13 @@ public void setLabel(final String label) // Add 'input' @Override - protected void fillInformation(final StringBuilder info) - { + protected void fillInformation(final StringBuilder info) { super.fillInformation(info); info.append("\n"); info.append(Messages.DockInput).append(getInput()); } - private static String extract_name(String path) - { + private static String extract_name(String path) { if (path == null) return null; @@ -136,7 +175,7 @@ private static String extract_name(String path) if (sep < 0) sep = path.lastIndexOf('\\'); if (sep >= 0) - path = path.substring(sep+1); + path = path.substring(sep + 1); // Remove extension sep = path.lastIndexOf('.'); @@ -145,19 +184,19 @@ private static String extract_name(String path) return path.substring(0, sep); } - /** Set input + /** + * Set input * - *

Registers the input to be persisted and restored. - * The tab tooltip indicates complete input, - * while tab label will be set to basic name (sans path and extension). - * For custom name, call setLabel after updating input - * in Platform.runLater() + *

Registers the input to be persisted and restored. + * The tab tooltip indicates complete input, + * while tab label will be set to basic name (sans path and extension). + * For custom name, call setLabel after updating input + * in Platform.runLater() * - * @param input Input - * @see DockItemWithInput#setLabel(String) + * @param input Input + * @see DockItemWithInput#setLabel(String) */ - public void setInput(final URI input) - { + public void setInput(final URI input) { this.input = input; final String name = input == null ? null : extract_name(input.getPath()); @@ -166,8 +205,7 @@ public void setInput(final URI input) { if (input == null) name_tab.setTooltip(new Tooltip(Messages.DockNotSaved)); - else - { + else { String decodedInputURI = URLDecoder.decode(input.toString(), StandardCharsets.UTF_8); name_tab.setTooltip(new Tooltip(decodedInputURI)); setLabel(name); @@ -175,43 +213,48 @@ public void setInput(final URI input) }); } - /** @return Input, which may be null (OK to call from any thread) */ - public URI getInput() - { + /** + * @return Input, which may be null (OK to call from any thread) + */ + public URI getInput() { return input; } - /** @return Current 'dirty' state */ - public boolean isDirty() - { + /** + * @return Current 'dirty' state + */ + public boolean isDirty() { return is_dirty.get(); } - /** Update 'dirty' state. + /** + * Update 'dirty' state. + * + *

May be called from any thread * - *

May be called from any thread - * @param dirty Updated 'dirty' state + * @param dirty Updated 'dirty' state */ - public void setDirty(final boolean dirty) - { + public void setDirty(final boolean dirty) { if (is_dirty.getAndSet(dirty) == dirty) return; // Dirty state changed. Update label on UI thread Platform.runLater(() -> setLabel(name)); } - /** @return Is "Save As" supported, i.e. have file extensions and a save handler? */ - public boolean isSaveAsSupported() - { - return file_extensions != null && save_handler != null; + /** + * @return Is "Save As" supported, i.e. have file extensions and a save handler? + */ + public boolean isSaveAsSupported() { + return file_extensions != null && save_handler != null; } - /** Called when user tries to close the tab - * @return Should the tab close? Otherwise it stays open. + /** + * Called when user tries to close the tab + * + * @return Should the tab close? Otherwise it stays open. */ - public Future okToClose() - { - if (! isDirty()) + public Future okToClose() { + if (!isDirty()) return CompletableFuture.completedFuture(true); final FutureTask promptToSave = new FutureTask(() -> { @@ -229,7 +272,7 @@ public Future okToClose() Platform.runLater(promptToSave); try { - ButtonType result = (ButtonType)promptToSave.get(); + ButtonType result = (ButtonType) promptToSave.get(); // Cancel the close request if (result == ButtonType.CANCEL) return CompletableFuture.completedFuture(false); @@ -252,40 +295,38 @@ public Future okToClose() return done; } - /** Save the content of the item to its current 'input' + /** + * Save the content of the item to its current 'input' * - *

Called by the framework when user invokes the 'Save*' - * menu items or when a 'dirty' tab is closed. + *

Called by the framework when user invokes the 'Save*' + * menu items or when a 'dirty' tab is closed. * - *

Will never be called when the item remains clean, - * i.e. never called {@link DockItemWithInput#setDirty(boolean)}. + *

Will never be called when the item remains clean, + * i.e. never called {@link DockItemWithInput#setDirty(boolean)}. * - * @param monitor {@link JobMonitor} for reporting progress - * @return true on success + * @param monitor {@link JobMonitor} for reporting progress + * @return true on success */ - public final boolean save(final JobMonitor monitor, Window parentWindow) - { + public final boolean save(final JobMonitor monitor, Window parentWindow) { // 'final' because any save customization should be possible // inside the save_handler - monitor.beginTask(MessageFormat.format(Messages.Saving , input)); + monitor.beginTask(MessageFormat.format(Messages.Saving, input)); - try - { // If there is no file (input is null or for example http:), + try { // If there is no file (input is null or for example http:), // call save_as to prompt for file File file = ResourceParser.getFile(getInput()); if (file == null) return save_as(monitor, parentWindow); - if (file.exists() && !file.canWrite()) - { // Warn on UI thread that file is read-only + if (file.exists() && !file.canWrite()) { // Warn on UI thread that file is read-only final CompletableFuture response = new CompletableFuture<>(); Platform.runLater(() -> { final Alert prompt = new Alert(AlertType.CONFIRMATION); prompt.setTitle(Messages.SavingAlertTitle); prompt.setResizable(true); - prompt.setHeaderText(MessageFormat.format(Messages.SavingAlert , file.toString())); + prompt.setHeaderText(MessageFormat.format(Messages.SavingAlert, file.toString())); DialogHelper.positionDialog(prompt, getTabPane(), -200, -200); response.complete(prompt.showAndWait().orElse(ButtonType.CANCEL)); @@ -300,13 +341,11 @@ public final boolean save(final JobMonitor monitor, Window parentWindow) if (save_handler == null) throw new Exception("No save_handler provided for 'dirty' " + toString()); save_handler.run(monitor); - } - catch (Exception ex) - { + } catch (Exception ex) { logger.log(Level.WARNING, "Save error", ex); Platform.runLater(() -> - ExceptionDetailsErrorDialog.openError(Messages.SavingHdr, - Messages.SavingErr + getLabel(), ex)); + ExceptionDetailsErrorDialog.openError(Messages.SavingHdr, + Messages.SavingErr + getLabel(), ex)); return false; } @@ -315,43 +354,42 @@ public final boolean save(final JobMonitor monitor, Window parentWindow) return true; } - /** @param file_extensions {@link ExtensionFilter}s - * @return List of valid file extensions, ignoring "*.*" + /** + * @param file_extensions {@link ExtensionFilter}s + * @return List of valid file extensions, ignoring "*.*" */ - private static List getValidExtensions(final ExtensionFilter[] file_extensions) - { + private static List getValidExtensions(final ExtensionFilter[] file_extensions) { final List valid = new ArrayList<>(); for (ExtensionFilter filter : file_extensions) for (String ext : filter.getExtensions()) - if (! ext.equals("*.*")) - { + if (!ext.equals("*.*")) { final int sep = ext.lastIndexOf('.'); if (sep > 0) - valid.add(ext.substring(sep+1)); + valid.add(ext.substring(sep + 1)); } return valid; } - /** @param file File - * @param valid List of valid file extensions - * @return true if file has one of the valid extensions + /** + * @param file File + * @param valid List of valid file extensions + * @return true if file has one of the valid extensions */ - private static boolean checkFileExtension(final File file, final List valid) - { + private static boolean checkFileExtension(final File file, final List valid) { final String path = file.getPath(); final int sep = path.lastIndexOf('.'); if (sep < 0) return false; - final String ext = path.substring(sep+1); + final String ext = path.substring(sep + 1); return valid.contains(ext); } - /** @param file File - * @param valid List of valid file extensions - * @return File updated to the first valid file extension + /** + * @param file File + * @param valid List of valid file extensions + * @return File updated to the first valid file extension */ - private static File setFileExtension(final File file, final List valid) - { + private static File setFileExtension(final File file, final List valid) { String path = file.getPath(); // Remove existing extension final int sep = path.lastIndexOf('.'); @@ -363,27 +401,26 @@ private static File setFileExtension(final File file, final List valid) return new File(path); } - /** Prompt for new file, then save the content of the item that file. + /** + * Prompt for new file, then save the content of the item that file. * - *

Called by the framework when user invokes the 'Save As' - * menu item. + *

Called by the framework when user invokes the 'Save As' + * menu item. * - *

Will never be called when the item does not report - * {@link #isSaveAsSupported()}. + *

Will never be called when the item does not report + * {@link #isSaveAsSupported()}. * - * @param monitor {@link JobMonitor} for reporting progress - * @return true on success + * @param monitor {@link JobMonitor} for reporting progress + * @return true on success */ - public final boolean save_as(final JobMonitor monitor, Window parentWindow) - { + public final boolean save_as(final JobMonitor monitor, Window parentWindow) { // 'final' because any save customization should be possible // inside the save_handler - try - { + try { // Prompt for file final File initial = ResourceParser.getFile(getInput()); final File file = new SaveAsDialog().promptForFile(parentWindow, - Messages.SaveAs, initial, file_extensions); + Messages.SaveAs, initial, file_extensions); if (file == null) return false; @@ -392,16 +429,15 @@ public final boolean save_as(final JobMonitor monitor, Window parentWindow) final CompletableFuture actual_file = new CompletableFuture<>(); if (checkFileExtension(file, valid)) actual_file.complete(file); - else - { + else { // Suggest name with valid extension final File suggestion = setFileExtension(file, valid); // Prompt on UI thread final String prompt = MessageFormat.format(Messages.SaveAsPrompt, - file, - valid.stream().collect(Collectors.joining(", ")), - suggestion); + file, + valid.stream().collect(Collectors.joining(", ")), + suggestion); Runnable confirmFileExtension = () -> { @@ -424,8 +460,7 @@ else if (response == ButtonType.NO) if (Platform.isFxApplicationThread()) { confirmFileExtension.run(); - } - else { + } else { Platform.runLater(confirmFileExtension); } @@ -441,8 +476,7 @@ else if (response == ButtonType.NO) setInput(ResourceParser.getURI(actual_file.get())); // Save in that file return save(monitor, getTabPane().getScene().getWindow()); - } - else { + } else { CompletableFuture waitForDialogToClose = new CompletableFuture<>(); Platform.runLater(() -> { String filename = FilenameUtils.getName(newInput.getPath()); @@ -458,7 +492,7 @@ else if (response == ButtonType.NO) dialog.getDialogPane().setPrefSize(width, height); dialog.getDialogPane().setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); dialog.setResizable(false); - DialogHelper.positionDialog(dialog, getTabPane(), -width/2, -height/2); + DialogHelper.positionDialog(dialog, getTabPane(), -width / 2, -height / 2); dialog.showAndWait(); waitForDialogToClose.complete(true); }); @@ -466,23 +500,20 @@ else if (response == ButtonType.NO) waitForDialogToClose.get(); save_as(monitor, getTabPane().getScene().getWindow()); } - } - catch (Exception ex) - { + } catch (Exception ex) { logger.log(Level.WARNING, "Save-As error", ex); Platform.runLater(() -> - ExceptionDetailsErrorDialog.openError(Messages.SaveAsErrHdr, - Messages.SaveAsErrMsg + getLabel(), ex)); + ExceptionDetailsErrorDialog.openError(Messages.SaveAsErrHdr, + Messages.SaveAsErrMsg + getLabel(), ex)); } return false; } /** * {@inheritDoc} - * */ + */ @Override - final protected void handleClosed() - { + final protected void handleClosed() { // Do the same as in the parent class, DockItem.handleClosed... super.handleClosed(); @@ -492,8 +523,7 @@ final protected void handleClosed() } @Override - public String toString() - { + public String toString() { return "DockItemWithInput(\"" + getLabel() + "\", " + getInput() + ")"; } } \ No newline at end of file diff --git a/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties b/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties index 2c9a3c4882..0d99f3bba3 100644 --- a/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties +++ b/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties @@ -6,6 +6,7 @@ AppVersion=${version} AppRevision=${revision} AppVersionHeader=CS Studio Version CloseAllTabs=Close All Tabs +CopyResourcePath=Copy resource path to clipboard DeleteLayouts=Delete Layouts... DeleteLayoutsConfirmFmt=Delete {0} selected layouts? DeleteLayoutsInfo=Select layouts to delete @@ -116,6 +117,9 @@ SavingHdr=Save error ScreenshotErrHdr=Screenshot error ScreenshotErrMsg=Cannot write screenshot SelectTab=Select Tab +ShowIn=Show in... +ShowInFileBrowserApp=File Browser App +ShowInNativeFileBrowser=Native File Browser ShowStatusbar=Show Status bar ShowToolbar=Show Toolbar Time12h=12 h From 8e61ba6585082159ce04dd04c62d9a60caeff3e1 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Tue, 2 Apr 2024 11:29:07 +0200 Subject: [PATCH 02/11] Update to FileBrowserController to support highlighting of file --- .../filebrowser/FileBrowserController.java | 52 ++++++++++++++++++- .../phoebus/ui/docking/DockItemWithInput.java | 2 +- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java index 15da2953cb..ff3f318235 100644 --- a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java +++ b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java @@ -59,6 +59,13 @@ public class FileBrowserController { private final Menu openWith = new Menu(Messages.OpenWith, ImageCache.getImageView(PhoebusApplication.class, "/icons/fldr_obj.png")); private final ContextMenu contextMenu = new ContextMenu(); + /** + * A {@link File} object representing a file (i.e. not a directory) in case client calls + * {@link #setRoot(File)} using a file. If the {@link #setRoot(File)} call specifies a directory, + * this is set to null. + */ + private File fileToHighlight; + public FileBrowserController() { monitor = new DirectoryMonitor(this::handleFilesystemChanges); @@ -276,6 +283,34 @@ public void initialize() { contextMenu.getItems().addAll(open, openWith); treeView.setOnKeyPressed(this::handleKeys); + + // Listen for changes in expanded item count and then highlight file on change. + // The call to #highlightFile is put here as update of the TreeView is controlled + // by JavaFX whenever a new root is set. + treeView.expandedItemCountProperty().addListener((observableValue, oldValue, newValue) -> { + if(oldValue.intValue() > 0 && !newValue.equals(oldValue)){ + highlightFile(); + } + }); + } + + /** + * Highlights and scrolls to a file if {@link #setRoot(File)} was called with + * a file and not directory object. + */ + private void highlightFile(){ + if(fileToHighlight == null){ + return; + } + TreeItem root = treeView.getRoot(); + List children = root.getChildren(); + for(TreeItem child : children){ + if(((FileInfo)child.getValue()).file.equals(fileToHighlight)){ + treeView.getSelectionModel().select(child); + treeView.scrollTo(treeView.getSelectionModel().getSelectedIndex()); + return; + } + } } TreeTableView getView() @@ -476,9 +511,22 @@ public void setNewRoot() { setRoot(p.toFile()); } - /** @param directory Desired root directory */ - public void setRoot(final File directory) + /** + * Set a new root directory, or set a new root directory and then highlight the file. + * @param file A {@link File} object representing a directory or a file. In the latter case the + * file's parent is used to set the root of the {@link TreeView}. + */ + public void setRoot(final File file) { + File directory; + if(file.isFile()){ + directory = file.getParentFile(); + this.fileToHighlight = file; + } + else{ + directory = file; + this.fileToHighlight = null; + } monitor.setRoot(directory); path.setText(directory.toString()); treeView.setRoot(new FileTreeItem(monitor, directory)); diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java index 0c01c67361..1c29dd9b7b 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java @@ -126,7 +126,7 @@ protected void configureContextMenu(ContextMenu menu) { final Menu showInMenu = new Menu(Messages.ShowIn, new ImageView(showInIcon)); final MenuItem showInFileBrowser = new MenuItem(Messages.ShowInFileBrowserApp); showInFileBrowser.setOnAction(e -> { - ApplicationService.createInstance("file_browser", new File(input.getPath()).getParentFile().toURI()); + ApplicationService.createInstance("file_browser", new File(input.getPath()).toURI()); }); final MenuItem showInNativeFileBrowser = new MenuItem(Messages.ShowInNativeFileBrowser); showInNativeFileBrowser.setOnAction(e -> { From fa27f6ba172e3e657798934464413a4983e73f64 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Tue, 2 Apr 2024 20:21:01 +0200 Subject: [PATCH 03/11] Handle http(s) resources, add null check before configuring context menu --- .../applications/filebrowser/FileBrowser.java | 12 ++++-- .../filebrowser/FileBrowserController.java | 36 +++++++++-------- .../phoebus/ui/docking/DockItemWithInput.java | 39 +++++++++++-------- .../main/resources/static/CSSTUDIO-1316.bob | 31 +++++++++++++++ 4 files changed, 82 insertions(+), 36 deletions(-) create mode 100644 services/save-and-restore/src/main/resources/static/CSSTUDIO-1316.bob diff --git a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowser.java b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowser.java index deadaf19ee..d83506b943 100644 --- a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowser.java +++ b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowser.java @@ -34,7 +34,7 @@ public class FileBrowser implements AppInstance private FileBrowserController controller; - FileBrowser(final AppDescriptor app, final File directory) + FileBrowser(final AppDescriptor app, final File file) { this.app = app; @@ -58,8 +58,14 @@ public class FileBrowser implements AppInstance final DockItem tab = new DockItem(this, content); DockPane.getActiveDockPane().addTab(tab); - if (controller != null && directory != null) - controller.setRoot(directory); + if (controller != null && file != null){ + if(file.isDirectory()){ + controller.setRoot(file); + } + else{ + controller.setRootAndHighlight(file); + } + } tab.addClosedNotification(controller::shutdown); } diff --git a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java index ff3f318235..302ea9d90b 100644 --- a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java +++ b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java @@ -285,10 +285,10 @@ public void initialize() { treeView.setOnKeyPressed(this::handleKeys); // Listen for changes in expanded item count and then highlight file on change. - // The call to #highlightFile is put here as update of the TreeView is controlled - // by JavaFX whenever a new root is set. + // The call to #highlightFile is put here as update of the TreeView is executed + // asynchronously by JavaFX whenever a new root is set. treeView.expandedItemCountProperty().addListener((observableValue, oldValue, newValue) -> { - if(oldValue.intValue() > 0 && !newValue.equals(oldValue)){ + if(newValue.intValue() > 0){ highlightFile(); } }); @@ -511,25 +511,27 @@ public void setNewRoot() { setRoot(p.toFile()); } + /** @param directory Desired root directory */ + public void setRoot(final File directory) + { + monitor.setRoot(directory); + path.setText(directory.toString()); + treeView.setRoot(new FileTreeItem(monitor, directory)); + } + /** - * Set a new root directory, or set a new root directory and then highlight the file. - * @param file A {@link File} object representing a directory or a file. In the latter case the - * file's parent is used to set the root of the {@link TreeView}. + * Set a new root directory and highlight the file provided (unless it is a directory). + * @param file A {@link File} object representing a file (or directory). */ - public void setRoot(final File file) - { - File directory; - if(file.isFile()){ - directory = file.getParentFile(); - this.fileToHighlight = file; + public void setRootAndHighlight(final File file){ + if(file.isDirectory()){ + this.fileToHighlight = null; + setRoot(file); } else{ - directory = file; - this.fileToHighlight = null; + this.fileToHighlight = file; + setRoot(file.getParentFile()); } - monitor.setRoot(directory); - path.setText(directory.toString()); - treeView.setRoot(new FileTreeItem(monitor, directory)); } /** @return Root directory */ diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java index 1c29dd9b7b..e536b0be87 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java @@ -123,28 +123,35 @@ public DockItemWithInput(final AppInstance application, final Node content, fina protected void configureContextMenu(ContextMenu menu) { super.configureContextMenu(menu); - final Menu showInMenu = new Menu(Messages.ShowIn, new ImageView(showInIcon)); - final MenuItem showInFileBrowser = new MenuItem(Messages.ShowInFileBrowserApp); - showInFileBrowser.setOnAction(e -> { - ApplicationService.createInstance("file_browser", new File(input.getPath()).toURI()); - }); - final MenuItem showInNativeFileBrowser = new MenuItem(Messages.ShowInNativeFileBrowser); - showInNativeFileBrowser.setOnAction(e -> { - try { - Desktop.getDesktop().open(new File(input.getPath()).getParentFile()); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - }); + if(input == null){ + return; + } + boolean isHttpResource = input.toString().toLowerCase().startsWith("http"); final MenuItem copyResourceToClipboard = new MenuItem(Messages.CopyResourcePath, new ImageView(copyToClipboardIcon)); copyResourceToClipboard.setOnAction(e -> { final ClipboardContent content = new ClipboardContent(); - content.putString(input.getPath()); + content.putString(isHttpResource ? input.toString() : input.getPath()); Clipboard.getSystemClipboard().setContent(content); }); - showInMenu.getItems().addAll(showInFileBrowser, showInNativeFileBrowser); - name_tab.getContextMenu().getItems().add(1, showInMenu); + if(!isHttpResource){ + final Menu showInMenu = new Menu(Messages.ShowIn, new ImageView(showInIcon)); + final MenuItem showInFileBrowser = new MenuItem(Messages.ShowInFileBrowserApp); + showInFileBrowser.setOnAction(e -> { + ApplicationService.createInstance("file_browser", new File(input.getPath()).toURI()); + }); + final MenuItem showInNativeFileBrowser = new MenuItem(Messages.ShowInNativeFileBrowser); + showInNativeFileBrowser.setOnAction(e -> { + try { + Desktop.getDesktop().open(new File(input.getPath()).getParentFile()); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + }); + showInMenu.getItems().addAll(showInFileBrowser, showInNativeFileBrowser); + name_tab.getContextMenu().getItems().add(1, showInMenu); + } + name_tab.getContextMenu().getItems().add(2, copyResourceToClipboard); } diff --git a/services/save-and-restore/src/main/resources/static/CSSTUDIO-1316.bob b/services/save-and-restore/src/main/resources/static/CSSTUDIO-1316.bob new file mode 100644 index 0000000000..963a73c631 --- /dev/null +++ b/services/save-and-restore/src/main/resources/static/CSSTUDIO-1316.bob @@ -0,0 +1,31 @@ + + + + Display + 920 + 300 + + LinearMeter + loc://x(10) + 200 + 340 + -100.0 + -100.0 + 100.0 + 100.0 + + + Action Button + + + loc://x + 1 + WritePV + + + 780 + 340 + 220 + 100 + + From 1c554e39a14a515c7f088fdabee64ab3c1912c59 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Wed, 3 Apr 2024 11:30:07 +0200 Subject: [PATCH 04/11] Adding some comments/Javadoc --- .../filebrowser/FileBrowserController.java | 8 ++++---- .../org/phoebus/ui/docking/DockItemWithInput.java | 12 ++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java index 302ea9d90b..59fd9e34e9 100644 --- a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java +++ b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java @@ -61,8 +61,8 @@ public class FileBrowserController { /** * A {@link File} object representing a file (i.e. not a directory) in case client calls - * {@link #setRoot(File)} using a file. If the {@link #setRoot(File)} call specifies a directory, - * this is set to null. + * {@link #setRootAndHighlight(File)} using a file. If the {@link #setRootAndHighlight(File)} call + * specifies a directory, this is set to null. */ private File fileToHighlight; @@ -295,8 +295,8 @@ public void initialize() { } /** - * Highlights and scrolls to a file if {@link #setRoot(File)} was called with - * a file and not directory object. + * Highlights and scrolls to a file if {@link #setRootAndHighlight(File)} was called with + * a file and not directory {@link File} object. */ private void highlightFile(){ if(fileToHighlight == null){ diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java index e536b0be87..776bd6bf4e 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java @@ -121,6 +121,18 @@ public DockItemWithInput(final AppInstance application, final Node content, fina }); } + /** + * Configures additional and optional items in the context menu if the resource filed is non-null: + *

    + *
  • Copy the resource to clipboard
  • + *
  • For file resources a sub-menu with items:
  • + *
      + *
    • Open and highlight file in Phoebus File Browser app
    • + *
    • Open file's parent directory in native file browser
    • + *
    + *
+ * @param menu The {@link ContextMenu} to update. + */ protected void configureContextMenu(ContextMenu menu) { super.configureContextMenu(menu); if(input == null){ From ec505859c19f6caf8bde38711a2d73fd3a0f110f Mon Sep 17 00:00:00 2001 From: georgweiss Date: Wed, 3 Apr 2024 11:32:17 +0200 Subject: [PATCH 05/11] Typos... --- .../main/java/org/phoebus/ui/docking/DockItemWithInput.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java index 776bd6bf4e..97434c2a2d 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java @@ -122,12 +122,12 @@ public DockItemWithInput(final AppInstance application, final Node content, fina } /** - * Configures additional and optional items in the context menu if the resource filed is non-null: - *
    + * Configures additional and optional items in the tab header context menu if the resource field is non-null: + * *
  • Copy the resource to clipboard
  • *
  • For file resources a sub-menu with items:
  • *
      - *
    • Open and highlight file in Phoebus File Browser app
    • + *
    • Open and highlight file in new File Browser instance
    • *
    • Open file's parent directory in native file browser
    • *
    *
From f918f3e91c4d5d06757894a03c391f90b86cc96d Mon Sep 17 00:00:00 2001 From: georgweiss Date: Wed, 3 Apr 2024 14:01:01 +0200 Subject: [PATCH 06/11] Desktop.isSupported call needed to avoid UI hand on (at least) Ubunbtu --- .../phoebus/ui/docking/DockItemWithInput.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java index 97434c2a2d..fd25d40a3e 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java @@ -152,15 +152,18 @@ protected void configureContextMenu(ContextMenu menu) { showInFileBrowser.setOnAction(e -> { ApplicationService.createInstance("file_browser", new File(input.getPath()).toURI()); }); - final MenuItem showInNativeFileBrowser = new MenuItem(Messages.ShowInNativeFileBrowser); - showInNativeFileBrowser.setOnAction(e -> { - try { - Desktop.getDesktop().open(new File(input.getPath()).getParentFile()); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - }); - showInMenu.getItems().addAll(showInFileBrowser, showInNativeFileBrowser); + showInMenu.getItems().add(showInFileBrowser); + if(Desktop.isDesktopSupported()){ + final MenuItem showInNativeFileBrowser = new MenuItem(Messages.ShowInNativeFileBrowser); + showInNativeFileBrowser.setOnAction(e -> { + try { + Desktop.getDesktop().open(new File(input.getPath()).getParentFile()); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + }); + showInMenu.getItems().add(showInNativeFileBrowser); + } name_tab.getContextMenu().getItems().add(1, showInMenu); } From e994c419315ad2816d70aa5925daec1b4384e6cf Mon Sep 17 00:00:00 2001 From: georgweiss Date: Wed, 3 Apr 2024 15:30:40 +0200 Subject: [PATCH 07/11] Removed 'Show in native file browser' as this does not work on Linux --- .../org/phoebus/ui/application/Messages.java | 2 -- .../phoebus/ui/docking/DockItemWithInput.java | 19 ++++--------------- .../ui/application/messages.properties | 4 +--- 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/application/Messages.java b/core/ui/src/main/java/org/phoebus/ui/application/Messages.java index a16789f778..85e440852f 100644 --- a/core/ui/src/main/java/org/phoebus/ui/application/Messages.java +++ b/core/ui/src/main/java/org/phoebus/ui/application/Messages.java @@ -131,9 +131,7 @@ public class Messages public static String ScreenshotErrHdr; public static String ScreenshotErrMsg; public static String SelectTab; - public static String ShowIn; public static String ShowInFileBrowserApp; - public static String ShowInNativeFileBrowser; public static String ShowStatusbar; public static String ShowToolbar; public static String Time12h; diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java index fd25d40a3e..34893bed73 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java @@ -89,6 +89,8 @@ public class DockItemWithInput extends DockItem { private final static Image copyToClipboardIcon = ImageCache.getImage(DockItem.class, "/icons/copy.png"); + private final static Image fileBrowserIcon = ImageCache.getImage(DockItem.class, "/icons/filebrowser.png"); + private final static Image showInIcon = ImageCache.getImage(DockItem.class, "/icons/fldr_obj.png"); /** @@ -147,24 +149,11 @@ protected void configureContextMenu(ContextMenu menu) { }); if(!isHttpResource){ - final Menu showInMenu = new Menu(Messages.ShowIn, new ImageView(showInIcon)); - final MenuItem showInFileBrowser = new MenuItem(Messages.ShowInFileBrowserApp); + final MenuItem showInFileBrowser = new MenuItem(Messages.ShowInFileBrowserApp, new ImageView(fileBrowserIcon)); showInFileBrowser.setOnAction(e -> { ApplicationService.createInstance("file_browser", new File(input.getPath()).toURI()); }); - showInMenu.getItems().add(showInFileBrowser); - if(Desktop.isDesktopSupported()){ - final MenuItem showInNativeFileBrowser = new MenuItem(Messages.ShowInNativeFileBrowser); - showInNativeFileBrowser.setOnAction(e -> { - try { - Desktop.getDesktop().open(new File(input.getPath()).getParentFile()); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - }); - showInMenu.getItems().add(showInNativeFileBrowser); - } - name_tab.getContextMenu().getItems().add(1, showInMenu); + name_tab.getContextMenu().getItems().add(1, showInFileBrowser); } name_tab.getContextMenu().getItems().add(2, copyResourceToClipboard); diff --git a/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties b/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties index 0d99f3bba3..66d2cb0609 100644 --- a/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties +++ b/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties @@ -117,9 +117,7 @@ SavingHdr=Save error ScreenshotErrHdr=Screenshot error ScreenshotErrMsg=Cannot write screenshot SelectTab=Select Tab -ShowIn=Show in... -ShowInFileBrowserApp=File Browser App -ShowInNativeFileBrowser=Native File Browser +ShowInFileBrowserApp=Show in File Browser app ShowStatusbar=Show Status bar ShowToolbar=Show Toolbar Time12h=12 h From bb46dccf22267668944e3549a682c582f1a171d4 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Thu, 4 Apr 2024 15:14:36 +0200 Subject: [PATCH 08/11] Changes based on review feed-back --- .../filebrowser/FileBrowserController.java | 372 ++++++++---------- .../phoebus/ui/docking/DockItemWithInput.java | 24 +- .../main/resources/static/CSSTUDIO-1316.bob | 31 -- 3 files changed, 183 insertions(+), 244 deletions(-) delete mode 100644 services/save-and-restore/src/main/resources/static/CSSTUDIO-1316.bob diff --git a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java index 59fd9e34e9..2d0df0164d 100644 --- a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java +++ b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java @@ -3,10 +3,22 @@ import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; import javafx.fxml.FXML; -import javafx.scene.control.*; +import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.Button; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.SeparatorMenuItem; +import javafx.scene.control.TextField; +import javafx.scene.control.TreeItem; +import javafx.scene.control.TreeTableColumn; +import javafx.scene.control.TreeTableView; import javafx.scene.image.ImageView; import javafx.scene.input.Clipboard; import javafx.scene.input.ContextMenuEvent; @@ -59,6 +71,8 @@ public class FileBrowserController { private final Menu openWith = new Menu(Messages.OpenWith, ImageCache.getImageView(PhoebusApplication.class, "/icons/fldr_obj.png")); private final ContextMenu contextMenu = new ContextMenu(); + private ExandedCountChangeListener exandedCountChangeListener; + /** * A {@link File} object representing a file (i.e. not a directory) in case client calls * {@link #setRootAndHighlight(File)} using a file. If the {@link #setRootAndHighlight(File)} call @@ -66,22 +80,17 @@ public class FileBrowserController { */ private File fileToHighlight; - public FileBrowserController() - { + public FileBrowserController() { monitor = new DirectoryMonitor(this::handleFilesystemChanges); } - private void handleFilesystemChanges(final File file, final DirectoryMonitor.Change change) - { + private void handleFilesystemChanges(final File file, final DirectoryMonitor.Change change) { // The notification might address a file that the file browser itself just added/renamed/removed, // and the file browser is already in the process of updating itself. // Wait a little to allow that to happen - try - { + try { Thread.sleep(1000); - } - catch (InterruptedException ex) - { + } catch (InterruptedException ex) { return; } @@ -94,11 +103,9 @@ else if (change == DirectoryMonitor.Change.REMOVED) assertTreeDoesntContain(treeView.getRoot(), file.toPath()); } - private void assertTreeContains(final TreeItem item, final Path file) - { + private void assertTreeContains(final TreeItem item, final Path file) { final Path dir = item.getValue().file.toPath(); - if (! file.startsWith(dir)) - { + if (!file.startsWith(dir)) { logger.log(Level.WARNING, "Cannot check for " + file + " within " + dir); return; } @@ -112,23 +119,20 @@ private void assertTreeContains(final TreeItem item, final Path file) logger.log(Level.FINE, () -> "Looking for " + sub + " in " + dir); for (TreeItem child : item.getChildren()) - if (sub.equals(child.getValue().file)) - { - logger.log(Level.FINE,"Found it!"); + if (sub.equals(child.getValue().file)) { + logger.log(Level.FINE, "Found it!"); if (sub.isDirectory()) assertTreeContains(child, file); return; } logger.log(Level.FINE, () -> "Forcing refresh of " + dir + " to show " + sub); - Platform.runLater(() -> ((FileTreeItem)item).forceRefresh()); + Platform.runLater(() -> ((FileTreeItem) item).forceRefresh()); } - private void refreshTreeItem(final TreeItem item, final Path file) - { + private void refreshTreeItem(final TreeItem item, final Path file) { final Path dir = item.getValue().file.toPath(); - if (dir.equals(file)) - { + if (dir.equals(file)) { logger.log(Level.FINE, () -> "Forcing refresh of " + item); Platform.runLater(() -> { @@ -140,8 +144,7 @@ private void refreshTreeItem(final TreeItem item, final Path file) return; } - if (! file.startsWith(dir)) - { + if (!file.startsWith(dir)) { logger.log(Level.WARNING, "Cannot refresh " + file + " within " + dir); return; } @@ -156,12 +159,10 @@ private void refreshTreeItem(final TreeItem item, final Path file) } - private void assertTreeDoesntContain(final TreeItem item, final Path file) - { + private void assertTreeDoesntContain(final TreeItem item, final Path file) { final Path dir = item.getValue().file.toPath(); logger.log(Level.FINE, () -> "Does " + dir + " still contain " + file + "?"); - if (! file.startsWith(dir)) - { + if (!file.startsWith(dir)) { logger.log(Level.FINE, "Can't!"); return; } @@ -169,16 +170,14 @@ private void assertTreeDoesntContain(final TreeItem item, final Path f final int dir_len = dir.getNameCount(); final File sub = new File(item.getValue().file, file.getName(dir_len).toString()); for (TreeItem child : item.getChildren()) - if (sub.equals(child.getValue().file)) - { + if (sub.equals(child.getValue().file)) { // Found file or sub path to it.. if (sub.isDirectory()) assertTreeDoesntContain(child, file); - else - { // Found the file still listed as a child of 'item', + else { // Found the file still listed as a child of 'item', // so refresh 'item' logger.log(Level.FINE, () -> "Forcing refresh of " + dir + " to hide " + sub); - Platform.runLater(() -> ((FileTreeItem)item).forceRefresh()); + Platform.runLater(() -> ((FileTreeItem) item).forceRefresh()); } return; } @@ -186,15 +185,15 @@ private void assertTreeDoesntContain(final TreeItem item, final Path f logger.log(Level.FINE, "Not found, all good"); } - /** Try to open resource, show error dialog on failure - * @param file Resource to open - * @param stage Stage to use to prompt for specific app. - * Otherwise null to use default app. + /** + * Try to open resource, show error dialog on failure + * + * @param file Resource to open + * @param stage Stage to use to prompt for specific app. + * Otherwise null to use default app. */ - private void openResource(final File file, final Stage stage) - { - if (! ApplicationLauncherService.openFile(file, stage != null, stage)) - { + private void openResource(final File file, final Stage stage) { + if (!ApplicationLauncherService.openFile(file, stage != null, stage)) { final Alert alert = new Alert(AlertType.ERROR); alert.setHeaderText(Messages.OpenAlert1 + file + Messages.OpenAlert2); DialogHelper.positionDialog(alert, treeView, -300, -200); @@ -202,17 +201,18 @@ private void openResource(final File file, final Stage stage) } } - /** Try to open all the currently selected resources */ - private void openSelectedResources() - { + /** + * Try to open all the currently selected resources + */ + private void openSelectedResources() { treeView.selectionModelProperty() .getValue() .getSelectedItems() .forEach(item -> - { - if (item.isLeaf()) - openResource(item.getValue().file, null); - }); + { + if (item.isLeaf()) + openResource(item.getValue().file, null); + }); } @FXML @@ -250,17 +250,15 @@ public void initialize() { // Available with, less space used for the TableMenuButton '+' on the right // so that the up/down column sort markers remain visible double available = treeView.getWidth() - 10; - if (name_col.isVisible()) - { + if (name_col.isVisible()) { // Only name visible? Use the space! if (!size_col.isVisible() && !time_col.isVisible()) name_col.setPrefWidth(available); else available -= name_col.getWidth(); } - if (size_col.isVisible()) - { - if (! time_col.isVisible()) + if (size_col.isVisible()) { + if (!time_col.isVisible()) size_col.setPrefWidth(available); else available -= size_col.getWidth(); @@ -283,128 +281,88 @@ public void initialize() { contextMenu.getItems().addAll(open, openWith); treeView.setOnKeyPressed(this::handleKeys); - - // Listen for changes in expanded item count and then highlight file on change. - // The call to #highlightFile is put here as update of the TreeView is executed - // asynchronously by JavaFX whenever a new root is set. - treeView.expandedItemCountProperty().addListener((observableValue, oldValue, newValue) -> { - if(newValue.intValue() > 0){ - highlightFile(); - } - }); - } - - /** - * Highlights and scrolls to a file if {@link #setRootAndHighlight(File)} was called with - * a file and not directory {@link File} object. - */ - private void highlightFile(){ - if(fileToHighlight == null){ - return; - } - TreeItem root = treeView.getRoot(); - List children = root.getChildren(); - for(TreeItem child : children){ - if(((FileInfo)child.getValue()).file.equals(fileToHighlight)){ - treeView.getSelectionModel().select(child); - treeView.scrollTo(treeView.getSelectionModel().getSelectedIndex()); - return; - } - } } - TreeTableView getView() - { + TreeTableView getView() { return treeView; } - private void handleKeys(final KeyEvent event) - { - switch(event.getCode()) - { - case ENTER: // Open - { - openSelectedResources(); - event.consume(); - break; - } - case F2: // Rename file - { - final ObservableList> items = treeView.selectionModelProperty().getValue().getSelectedItems(); - if (items.size() == 1) + private void handleKeys(final KeyEvent event) { + switch (event.getCode()) { + case ENTER: // Open { - final TreeItem item = items.get(0); - if (item.isLeaf()) - new RenameAction(treeView, item).fire(); + openSelectedResources(); + event.consume(); + break; } - event.consume(); - break; - } - case DELETE: // Delete - { - final ObservableList> items = treeView.selectionModelProperty().getValue().getSelectedItems(); - if (items.size() > 0) - new DeleteAction(treeView, items).fire(); - event.consume(); - break; - } - case C: // Copy - { - if (event.isShortcutDown()) + case F2: // Rename file { final ObservableList> items = treeView.selectionModelProperty().getValue().getSelectedItems(); - new CopyPath(items).fire(); + if (items.size() == 1) { + final TreeItem item = items.get(0); + if (item.isLeaf()) + new RenameAction(treeView, item).fire(); + } event.consume(); + break; } - break; - } - case V: // Paste - { - if (event.isShortcutDown()) + case DELETE: // Delete { - TreeItem item = treeView.selectionModelProperty().getValue().getSelectedItem(); - if (item == null) - item = treeView.getRoot(); - else if (item.isLeaf()) - item = item.getParent(); - new PasteFiles(treeView, item).fire(); + final ObservableList> items = treeView.selectionModelProperty().getValue().getSelectedItems(); + if (items.size() > 0) + new DeleteAction(treeView, items).fire(); event.consume(); + break; } - break; - } - case ESCAPE: // De-select - treeView.selectionModelProperty().get().clearSelection(); - break; - default: - if ((event.getCode().compareTo(KeyCode.A) >= 0 && event.getCode().compareTo(KeyCode.Z) <= 0) || - (event.getCode().compareTo(KeyCode.DIGIT0) >= 0 && event.getCode().compareTo(KeyCode.DIGIT9) <= 0)) + case C: // Copy { - // Move selection to first/next file that starts with that character - final String ch = event.getCode().getChar().toLowerCase(); - - final TreeItem selected = treeView.selectionModelProperty().getValue().getSelectedItem(); - final ObservableList> siblings; - int index; - if (selected != null) - { // Start after the selected item - siblings = selected.getParent().getChildren(); - index = siblings.indexOf(selected); + if (event.isShortcutDown()) { + final ObservableList> items = treeView.selectionModelProperty().getValue().getSelectedItems(); + new CopyPath(items).fire(); + event.consume(); } - else if (!treeView.getRoot().getChildren().isEmpty()) - { // Start at the root - siblings = treeView.getRoot().getChildren(); - index = -1; + break; + } + case V: // Paste + { + if (event.isShortcutDown()) { + TreeItem item = treeView.selectionModelProperty().getValue().getSelectedItem(); + if (item == null) + item = treeView.getRoot(); + else if (item.isLeaf()) + item = item.getParent(); + new PasteFiles(treeView, item).fire(); + event.consume(); } - else - break; - for (++index; index < siblings.size(); ++index) - if (siblings.get(index).getValue().file.getName().toLowerCase().startsWith(ch)) - { - treeView.selectionModelProperty().get().clearSelection(); - treeView.selectionModelProperty().get().select(siblings.get(index)); - break; - } + break; } + case ESCAPE: // De-select + treeView.selectionModelProperty().get().clearSelection(); + break; + default: + if ((event.getCode().compareTo(KeyCode.A) >= 0 && event.getCode().compareTo(KeyCode.Z) <= 0) || + (event.getCode().compareTo(KeyCode.DIGIT0) >= 0 && event.getCode().compareTo(KeyCode.DIGIT9) <= 0)) { + // Move selection to first/next file that starts with that character + final String ch = event.getCode().getChar().toLowerCase(); + + final TreeItem selected = treeView.selectionModelProperty().getValue().getSelectedItem(); + final ObservableList> siblings; + int index; + if (selected != null) { // Start after the selected item + siblings = selected.getParent().getChildren(); + index = siblings.indexOf(selected); + } else if (!treeView.getRoot().getChildren().isEmpty()) { // Start at the root + siblings = treeView.getRoot().getChildren(); + index = -1; + } else + break; + for (++index; index < siblings.size(); ++index) + if (siblings.get(index).getValue().file.getName().toLowerCase().startsWith(ch)) { + treeView.selectionModelProperty().get().clearSelection(); + treeView.selectionModelProperty().get().select(siblings.get(index)); + break; + } + } } } @@ -414,18 +372,15 @@ public void createContextMenu(ContextMenuEvent e) { contextMenu.getItems().clear(); - if (selectedItems.isEmpty()) - { + if (selectedItems.isEmpty()) { // Create directory at root contextMenu.getItems().addAll(new CreateDirectoryAction(treeView, treeView.getRoot())); // Paste files at root if (Clipboard.getSystemClipboard().hasFiles()) contextMenu.getItems().addAll(new PasteFiles(treeView, treeView.getRoot())); - } - else - { + } else { // allMatch() would return true for empty, so only check if there are items - if (selectedItems.stream().allMatch(item -> item.isLeaf())){ + if (selectedItems.stream().allMatch(item -> item.isLeaf())) { contextMenu.getItems().add(open); } @@ -433,7 +388,7 @@ public void createContextMenu(ContextMenuEvent e) { configureOpenWithMenuItem(selectedItems); if (selectedItems.size() == 1) { - if(file.isDirectory()){ + if (file.isDirectory()) { contextMenu.getItems().add(new SetBaseDirectory(file, this::setRoot)); contextMenu.getItems().add(new SeparatorMenuItem()); @@ -459,25 +414,21 @@ public void createContextMenu(ContextMenuEvent e) { contextMenu.getItems().add(new CopyPath(selectedItems)); contextMenu.getItems().add(new SeparatorMenuItem()); } - if (selectedItems.size() >= 1) - { + if (selectedItems.size() >= 1) { final TreeItem item = selectedItems.get(0); final boolean is_file = item.isLeaf(); - if (selectedItems.size() == 1) - { - if (is_file) - { + if (selectedItems.size() == 1) { + if (is_file) { // Create directory within the _parent_ contextMenu.getItems().addAll(new CreateDirectoryAction(treeView, item.getParent())); // Plain file can be duplicated, directory can't contextMenu.getItems().add(new DuplicateAction(treeView, item)); - } - else + } else // Within a directory, a new directory can be created contextMenu.getItems().addAll(new CreateDirectoryAction(treeView, item)); - contextMenu.getItems().addAll(new RenameAction(treeView, selectedItems.get(0))); + contextMenu.getItems().addAll(new RenameAction(treeView, selectedItems.get(0))); if (Clipboard.getSystemClipboard().hasFiles()) contextMenu.getItems().addAll(new PasteFiles(treeView, selectedItems.get(0))); @@ -492,15 +443,14 @@ public void createContextMenu(ContextMenuEvent e) { contextMenu.getItems().add(new RefreshAction(treeView, item)); } - if (selectedItems.size() == 1){ - contextMenu.getItems().addAll(new PropertiesAction(treeView, selectedItems.get(0))); + if (selectedItems.size() == 1) { + contextMenu.getItems().addAll(new PropertiesAction(treeView, selectedItems.get(0))); } contextMenu.show(treeView.getScene().getWindow(), e.getScreenX(), e.getScreenY()); } @FXML - public void handleMouseClickEvents(final MouseEvent event) - { + public void handleMouseClickEvents(final MouseEvent event) { if (event.getClickCount() == 2) openSelectedResources(); } @@ -511,9 +461,10 @@ public void setNewRoot() { setRoot(p.toFile()); } - /** @param directory Desired root directory */ - public void setRoot(final File directory) - { + /** + * @param directory Desired root directory + */ + public void setRoot(final File directory) { monitor.setRoot(directory); path.setText(directory.toString()); treeView.setRoot(new FileTreeItem(monitor, directory)); @@ -521,22 +472,24 @@ public void setRoot(final File directory) /** * Set a new root directory and highlight the file provided (unless it is a directory). + * * @param file A {@link File} object representing a file (or directory). */ - public void setRootAndHighlight(final File file){ - if(file.isDirectory()){ - this.fileToHighlight = null; + public void setRootAndHighlight(final File file) { + if (file.isDirectory()) { setRoot(file); - } - else{ + } else { + this.exandedCountChangeListener = new ExandedCountChangeListener(); this.fileToHighlight = file; + treeView.expandedItemCountProperty().addListener(exandedCountChangeListener); setRoot(file.getParentFile()); } } - /** @return Root directory */ - public File getRoot() - { + /** + * @return Root directory + */ + public File getRoot() { return treeView.getRoot().getValue().file; } @@ -558,9 +511,10 @@ public void browseNewRoot() { setRoot(newRootFile); } - /** Call when no longer needed */ - public void shutdown() - { + /** + * Call when no longer needed + */ + public void shutdown() { monitor.shutdown(); } @@ -572,10 +526,11 @@ public void shutdown() *
  • If all selected items are of same type, the Open With menu item will be added and the * the sub-menu items will open all items. This also covers the case when only one item is selected.
  • * + * * @param selectedItems List of items selected by user in the tree table view. */ - private void configureOpenWithMenuItem(List> selectedItems){ - if(!areSelectedFilesOfSameType(selectedItems)) { + private void configureOpenWithMenuItem(List> selectedItems) { + if (!areSelectedFilesOfSameType(selectedItems)) { openWith.getItems().clear(); return; } @@ -584,17 +539,15 @@ private void configureOpenWithMenuItem(List> selectedItems){ final File file = selectedItems.get(0).getValue().file; final URI resource = ResourceParser.getURI(file); final List applications = ApplicationService.getApplications(resource); - if (applications.size() > 0) - { + if (applications.size() > 0) { openWith.getItems().clear(); - for (AppResourceDescriptor app : applications) - { + for (AppResourceDescriptor app : applications) { final MenuItem open_app = new MenuItem(app.getDisplayName()); final URL icon_url = app.getIconURL(); if (icon_url != null) open_app.setGraphic(new ImageView(icon_url.toExternalForm())); open_app.setOnAction(event -> { - for(TreeItem item : selectedItems){ + for (TreeItem item : selectedItems) { URI u = ResourceParser.getURI(item.getValue().file); app.create(u); } @@ -608,20 +561,37 @@ private void configureOpenWithMenuItem(List> selectedItems){ /** * Examines the file selection to determine whether all files are of the same type. A type is * defined by the file extension (case-insensitive substring after last dot). + * * @param selectedItems Items selected by user in the tree table view * @return true if all selected files have same (case-insensitive) extension. */ - private boolean areSelectedFilesOfSameType(List> selectedItems){ + private boolean areSelectedFilesOfSameType(List> selectedItems) { File file = selectedItems.get(0).getValue().file; String firstExtension = file.getPath().substring(file.getPath().lastIndexOf(".") + 1).toLowerCase(); - for(int i = 1; i < selectedItems.size(); i++){ + for (int i = 1; i < selectedItems.size(); i++) { file = selectedItems.get(i).getValue().file; String nextExtension = file.getPath().substring(file.getPath().lastIndexOf(".") + 1).toLowerCase(); - if(!firstExtension.equals(nextExtension)){ + if (!firstExtension.equals(nextExtension)) { return false; } } return true; } + + private class ExandedCountChangeListener implements ChangeListener { + @Override + public void changed(ObservableValue observable, Object oldValue, Object newValue) { + TreeItem root = treeView.getRoot(); + List children = root.getChildren(); + for (TreeItem child : children) { + if (((FileInfo) child.getValue()).file.equals(fileToHighlight)) { + treeView.getSelectionModel().select(child); + treeView.scrollTo(treeView.getSelectionModel().getSelectedIndex()); + treeView.expandedItemCountProperty().removeListener(exandedCountChangeListener); + return; + } + } + } + } } diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java index 34893bed73..234eea5a07 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java @@ -9,10 +9,13 @@ import javafx.application.Platform; import javafx.scene.Node; -import javafx.scene.control.*; +import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; -import javafx.scene.control.Menu; +import javafx.scene.control.ButtonType; +import javafx.scene.control.ContextMenu; import javafx.scene.control.MenuItem; +import javafx.scene.control.Tab; +import javafx.scene.control.Tooltip; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.Clipboard; @@ -33,9 +36,7 @@ import org.phoebus.ui.dialog.SaveAsDialog; import org.phoebus.ui.javafx.ImageCache; -import java.awt.*; import java.io.File; -import java.io.IOException; import java.net.URI; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; @@ -91,8 +92,6 @@ public class DockItemWithInput extends DockItem { private final static Image fileBrowserIcon = ImageCache.getImage(DockItem.class, "/icons/filebrowser.png"); - private final static Image showInIcon = ImageCache.getImage(DockItem.class, "/icons/fldr_obj.png"); - /** * Create dock item * @@ -126,29 +125,30 @@ public DockItemWithInput(final AppInstance application, final Node content, fina /** * Configures additional and optional items in the tab header context menu if the resource field is non-null: * - *
  • Copy the resource to clipboard
  • - *
  • For file resources a sub-menu with items:
  • + *
  • Copy the resource to clipboard
  • + *
  • For file resources a sub-menu with items:
  • *
      *
    • Open and highlight file in new File Browser instance
    • *
    • Open file's parent directory in native file browser
    • *
    * + * * @param menu The {@link ContextMenu} to update. */ protected void configureContextMenu(ContextMenu menu) { super.configureContextMenu(menu); - if(input == null){ + if (input == null) { return; } - boolean isHttpResource = input.toString().toLowerCase().startsWith("http"); + boolean isFileResource = input.getScheme().toLowerCase().startsWith("file"); final MenuItem copyResourceToClipboard = new MenuItem(Messages.CopyResourcePath, new ImageView(copyToClipboardIcon)); copyResourceToClipboard.setOnAction(e -> { final ClipboardContent content = new ClipboardContent(); - content.putString(isHttpResource ? input.toString() : input.getPath()); + content.putString(isFileResource ? input.toString() : input.getPath()); Clipboard.getSystemClipboard().setContent(content); }); - if(!isHttpResource){ + if (isFileResource) { final MenuItem showInFileBrowser = new MenuItem(Messages.ShowInFileBrowserApp, new ImageView(fileBrowserIcon)); showInFileBrowser.setOnAction(e -> { ApplicationService.createInstance("file_browser", new File(input.getPath()).toURI()); diff --git a/services/save-and-restore/src/main/resources/static/CSSTUDIO-1316.bob b/services/save-and-restore/src/main/resources/static/CSSTUDIO-1316.bob deleted file mode 100644 index 963a73c631..0000000000 --- a/services/save-and-restore/src/main/resources/static/CSSTUDIO-1316.bob +++ /dev/null @@ -1,31 +0,0 @@ - - - - Display - 920 - 300 - - LinearMeter - loc://x(10) - 200 - 340 - -100.0 - -100.0 - 100.0 - 100.0 - - - Action Button - - - loc://x - 1 - WritePV - - - 780 - 340 - 220 - 100 - - From a77936d07c1992d89b909e94084aef59174caac8 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Fri, 5 Apr 2024 11:24:31 +0200 Subject: [PATCH 09/11] Typo --- .../filebrowser/FileBrowserController.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java index 2d0df0164d..171c739395 100644 --- a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java +++ b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java @@ -71,7 +71,7 @@ public class FileBrowserController { private final Menu openWith = new Menu(Messages.OpenWith, ImageCache.getImageView(PhoebusApplication.class, "/icons/fldr_obj.png")); private final ContextMenu contextMenu = new ContextMenu(); - private ExandedCountChangeListener exandedCountChangeListener; + private ExpandedCountChangeListener expandedCountChangeListener; /** * A {@link File} object representing a file (i.e. not a directory) in case client calls @@ -479,9 +479,9 @@ public void setRootAndHighlight(final File file) { if (file.isDirectory()) { setRoot(file); } else { - this.exandedCountChangeListener = new ExandedCountChangeListener(); + this.expandedCountChangeListener = new ExpandedCountChangeListener(); this.fileToHighlight = file; - treeView.expandedItemCountProperty().addListener(exandedCountChangeListener); + treeView.expandedItemCountProperty().addListener(expandedCountChangeListener); setRoot(file.getParentFile()); } } @@ -579,7 +579,7 @@ private boolean areSelectedFilesOfSameType(List> selectedItem return true; } - private class ExandedCountChangeListener implements ChangeListener { + private class ExpandedCountChangeListener implements ChangeListener { @Override public void changed(ObservableValue observable, Object oldValue, Object newValue) { TreeItem root = treeView.getRoot(); @@ -588,7 +588,7 @@ public void changed(ObservableValue observable, Object oldValue, Object newValue if (((FileInfo) child.getValue()).file.equals(fileToHighlight)) { treeView.getSelectionModel().select(child); treeView.scrollTo(treeView.getSelectionModel().getSelectedIndex()); - treeView.expandedItemCountProperty().removeListener(exandedCountChangeListener); + treeView.expandedItemCountProperty().removeListener(expandedCountChangeListener); return; } } From 9829a02b4631c3315b68d632b03dd9f3fedbd7ce Mon Sep 17 00:00:00 2001 From: georgweiss Date: Fri, 5 Apr 2024 11:39:17 +0200 Subject: [PATCH 10/11] Removed need for class variable --- .../filebrowser/FileBrowserController.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java index 171c739395..bbf0d1fa73 100644 --- a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java +++ b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java @@ -73,13 +73,6 @@ public class FileBrowserController { private ExpandedCountChangeListener expandedCountChangeListener; - /** - * A {@link File} object representing a file (i.e. not a directory) in case client calls - * {@link #setRootAndHighlight(File)} using a file. If the {@link #setRootAndHighlight(File)} call - * specifies a directory, this is set to null. - */ - private File fileToHighlight; - public FileBrowserController() { monitor = new DirectoryMonitor(this::handleFilesystemChanges); } @@ -479,8 +472,7 @@ public void setRootAndHighlight(final File file) { if (file.isDirectory()) { setRoot(file); } else { - this.expandedCountChangeListener = new ExpandedCountChangeListener(); - this.fileToHighlight = file; + this.expandedCountChangeListener = new ExpandedCountChangeListener(file); treeView.expandedItemCountProperty().addListener(expandedCountChangeListener); setRoot(file.getParentFile()); } @@ -580,6 +572,17 @@ private boolean areSelectedFilesOfSameType(List> selectedItem } private class ExpandedCountChangeListener implements ChangeListener { + + /** + * A {@link File} object representing a file (i.e. not a directory) in case client calls + * {@link #setRootAndHighlight(File)} using a file. If the {@link #setRootAndHighlight(File)} call + * specifies a directory, this is set to null. + */ + private File fileToHighlight; + + public ExpandedCountChangeListener(File fileToHighlight){ + this.fileToHighlight = fileToHighlight; + } @Override public void changed(ObservableValue observable, Object oldValue, Object newValue) { TreeItem root = treeView.getRoot(); From e15a7a3ea651744eefc21aea7da8961ac1c23f4e Mon Sep 17 00:00:00 2001 From: georgweiss Date: Fri, 5 Apr 2024 12:25:52 +0200 Subject: [PATCH 11/11] Update due to reversed condition --- .../src/main/java/org/phoebus/ui/docking/DockItemWithInput.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java index 234eea5a07..9c91ec9c0a 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java @@ -144,7 +144,7 @@ protected void configureContextMenu(ContextMenu menu) { final MenuItem copyResourceToClipboard = new MenuItem(Messages.CopyResourcePath, new ImageView(copyToClipboardIcon)); copyResourceToClipboard.setOnAction(e -> { final ClipboardContent content = new ClipboardContent(); - content.putString(isFileResource ? input.toString() : input.getPath()); + content.putString(isFileResource ? input.getPath() : input.toString()); Clipboard.getSystemClipboard().setContent(content); });