package org.esa.snap.rcp.scripting; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.util.Exceptions; import org.openide.windows.Mode; import org.openide.windows.TopComponent; import org.openide.windows.WindowManager; import javax.swing.Action; import java.io.IOException; /** * Provides various utility functions allowing scripting clients to register actions and windows * for the SNAP Desktop application. * <p> * The following methods can be used to dynamically add/remove actions and action references to/from * the SNAP Desktop application: * <ul> * <li>{@link SnapUtils#addAction(Action, String)}</li> * <li>{@link SnapUtils#removeAction(FileObject)}</li> * </ul> * The {@code addAction()} methods all return "file objects" which live in the NetBeans * Platform's virtual file system. * These object can be used to create references to the actions they represent in various places * such as menus and tool bars: * <ul> * <li>{@link SnapUtils#addActionReference(FileObject, String, Integer)}</li> * <li>{@link SnapUtils#removeActionReference(FileObject)}</li> * </ul> * <p> * To open a new window in the SNAP Desktop application, the following methods can be used: * <ul> * <li>{@link SnapUtils#openWindow(TopComponent)}</li> * <li>{@link SnapUtils#openWindow(TopComponent, boolean)}</li> * <li>{@link SnapUtils#openWindow(TopComponent, String)}</li> * <li>{@link SnapUtils#openWindow(TopComponent, String, boolean)}</li> * </ul> * * @author Norman Fomferra */ public class SnapUtils { private static final String INSTANCE_PREFIX = "TransientAction-"; private static final String INSTANCE_SUFFIX = ".instance"; private static final String SHADOW_SUFFIX = ".shadow"; /** * Adds an action into the folder {@code Menu/Tools} of the SNAP Desktop / NetBeans file system. * * @param action The action. * @return The file object representing the action. */ public synchronized static FileObject addAction(Action action) { return addAction(action, "Menu/Tools"); } /** * Adds an action into the folder given by {@code path} of the SNAP Desktop / NetBeans file system. * * @param action The action. * @param path The folder path. * @return The file object representing the action. */ public synchronized static FileObject addAction(Action action, String path) { return addAction(action, path, null); } /** * Adds an action into the folder given by {@code path} of the SNAP Desktop / NetBeans file system at the given {@code position}. * * @param action The action. * @param path The folder path. * @param position The position within the folder. May be {@code null}. * @return The file object representing the action. */ public synchronized static FileObject addAction(Action action, String path, Integer position) { FileObject configRoot = FileUtil.getConfigRoot(); try { FileObject actionFile = FileUtil.createData(configRoot, getActionDataPath(path, action)); actionFile.setAttribute("instanceCreate", new TransientAction(action, actionFile.getPath())); if (position != null) { actionFile.setAttribute("position", position); } return actionFile; } catch (IOException ex) { Exceptions.printStackTrace(ex); } return null; } /** * Adds an action references into the folder given by {@code path} of the SNAP Desktop / NetBeans file system at the given {@code position}. * The following are standard folders in SNAP Desktop/NetBeans where actions and action references may be placed to * become visible: * <ol> * <li>{@code Menu/*} - the main menu</li> * <li>{@code Toolbars/*} - the main tool bar</li> * </ol> * * @param instanceFile The file object representing an action instance. * @param path The folder path. * @param position The position within the folder. May be {@code null}. * @return The file object representing the action reference. */ public synchronized static FileObject addActionReference(FileObject instanceFile, String path, Integer position) { Action actualAction = TransientAction.getAction(instanceFile.getPath()); if (actualAction == null) { return null; } String shadowId = instanceFile.getName() + SHADOW_SUFFIX; FileObject configRoot = FileUtil.getConfigRoot(); try { FileObject actionFile = FileUtil.createData(configRoot, path + "/" + shadowId); actionFile.setAttribute("originalFile", instanceFile.getPath()); if (position != null) { actionFile.setAttribute("position", position); } return actionFile; } catch (IOException ex) { Exceptions.printStackTrace(ex); } return null; } public synchronized static boolean removeAction(FileObject actionFile) { if (TransientAction.hasAction(actionFile.getPath())) { try { actionFile.delete(); TransientAction.removeAction(actionFile.getPath()); return true; } catch (IOException ex) { Exceptions.printStackTrace(ex); } } return false; } public synchronized static boolean removeActionReference(FileObject actionReferenceFile) { Object originalFile = actionReferenceFile.getAttribute("originalFile"); if (originalFile != null) { String originalPath = originalFile.toString(); int slashPos = originalPath.lastIndexOf('/'); if (slashPos > 0 && originalPath.substring(slashPos + 1).startsWith(INSTANCE_PREFIX) && originalPath.endsWith(INSTANCE_SUFFIX)) { try { actionReferenceFile.delete(); return true; } catch (IOException ex) { Exceptions.printStackTrace(ex); } } } return false; } /** * Opens a new window in SNAP Desktop in the "explorer" location. * * @param window The window which must must be an instance of {@link TopComponent}. * @see #openWindow(TopComponent, String, boolean) */ public static void openWindow(TopComponent window) { openWindow(window, false); } /** * Opens a new window in SNAP Desktop in the "explorer" location. * * @param window The window which must must be an instance of {@link TopComponent}. * @param requestActive {@code true} if a request will be made to activate the window after opening it. * @see #openWindow(TopComponent, String, boolean) */ public static void openWindow(TopComponent window, boolean requestActive) { openWindow(window, "explorer", requestActive); } /** * Opens a new window in SNAP Desktop at the given location. * * @param window The window which must must be an instance of {@link TopComponent}. * @param location The location where the window should appear when it is first opened. * @see #openWindow(TopComponent, String, boolean) */ public static void openWindow(TopComponent window, String location) { openWindow(window, location, false); } /** * Opens a new window in SNAP Desktop. * * @param window The window which must must be an instance of {@link TopComponent}. * @param location The location where the window should appear when it is first opened. * Possible docking areas are * "explorer" (upper left), "navigator" (lower left), "properties" (upper right), * "output" (bottom). You may choose "floating" to not dock the window at all. Note * that this mode requires explicitly setting the window's position and size. * @param requestActive {@code true} if a request will be made to activate the window after opening it. */ public static void openWindow(TopComponent window, String location, boolean requestActive) { WindowManager.getDefault().invokeWhenUIReady(() -> { Mode mode = WindowManager.getDefault().findMode(location); if (mode != null) { mode.dockInto(window); } window.open(); if (requestActive) { window.requestActive(); } }); } private static String getActionDataPath(String folderPath, Action delegate) { return folderPath + "/" + getActionInstanceName(delegate); } private static String getActionInstanceName(Action delegate) { Object commandKey = delegate.getValue(Action.ACTION_COMMAND_KEY); String id; if (commandKey != null && !commandKey.toString().isEmpty()) { id = commandKey.toString(); } else { id = delegate.getClass().getName(); } id = id.replace('/', '-').replace('.', '-').replace('$', '-'); if (!id.startsWith(INSTANCE_PREFIX)) { id = INSTANCE_PREFIX + id; } if (!id.endsWith(INSTANCE_SUFFIX)) { id += INSTANCE_SUFFIX; } return id; } }