diff --git a/QuickOpener/src/main/java/me/dsnet/quickopener/actions/RunCommand.java b/QuickOpener/src/main/java/me/dsnet/quickopener/actions/RunCommand.java index 3614cb5..75b5c0a 100644 --- a/QuickOpener/src/main/java/me/dsnet/quickopener/actions/RunCommand.java +++ b/QuickOpener/src/main/java/me/dsnet/quickopener/actions/RunCommand.java @@ -10,7 +10,9 @@ import me.dsnet.quickopener.prefs.PrefsUtil; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; import javax.swing.JEditorPane; +import javax.swing.SwingUtilities; import javax.swing.text.JTextComponent; import javax.swing.text.StyledDocument; import org.openide.DialogDisplayer; @@ -43,16 +45,8 @@ public boolean actionPerformed() { DialogDisplayer.getDefault().notify(d); return false; } else { - final Map createPlaceholders = createPlaceholders(); - String command = fillPlaceholders(_command, createPlaceholders); - //Are all placeholders replaced? -> if not then show a message! - boolean foundUnreplacedPlaceholder = false; - for (String placeholder : createPlaceholders.keySet()) { - if (command.contains(placeholder)) { - foundUnreplacedPlaceholder = true; - break; - } - } + boolean foundUnreplacedPlaceholder = !areAllPlaceHoldersReplaced(); + String command = getCommandWithReplacedPlaceholders(); if (foundUnreplacedPlaceholder) { NotifyDescriptor d = new NotifyDescriptor.Message(QuickMessages.NO_DEFAULT_PARAMETERS + " \nCommand was: " + command, NotifyDescriptor.WARNING_MESSAGE); @@ -75,6 +69,23 @@ public boolean actionPerformed() { } } + /** + * Visible to determine if command could be executed. + * @return + */ + public boolean areAllPlaceHoldersReplaced() { + String command = getCommandWithReplacedPlaceholders(); + final Set keys = createPlaceholders().keySet(); + //Are all placeholders replaced? -> if not then show a message! + for (String placeholder : keys) { + if (command.contains(placeholder)) { + return false; + } + } + return true; + } + + private Map createPlaceholders() { String currentFile = PathFinder.getActivePath(null, false); String currentFolder = PathFinder.getActivePath(null, true); @@ -152,6 +163,9 @@ public String getCommandWithReplacedPlaceholders() { } private JTextComponent getCurrentEditor() { + if (!SwingUtilities.isEventDispatchThread()){ + return null; + } Node[] arr = TopComponent.getRegistry().getCurrentNodes(); if (null == arr) { return null; diff --git a/QuickOpener/src/main/java/me/dsnet/quickopener/actions/layer/ActionRegistrationService.java b/QuickOpener/src/main/java/me/dsnet/quickopener/actions/layer/ActionRegistrationService.java new file mode 100644 index 0000000..0733287 --- /dev/null +++ b/QuickOpener/src/main/java/me/dsnet/quickopener/actions/layer/ActionRegistrationService.java @@ -0,0 +1,103 @@ +package me.dsnet.quickopener.actions.layer; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import javax.swing.Action; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; + +/** + * + * Taken from http://wiki.netbeans.org/DevFaqActionsAddAtRuntime + */ +public class ActionRegistrationService { + + /** + * Registers an action with the platform along with optional shortcuts and + * menu items. + * + * @param name Display name of the action. + * @param category Category in the Keymap tool. + * @param shortcut Default shortcut, use an empty string or null for none. + * @param menuPath Menu location starting with "Menu", like "Menu/File" + * @param action an action object to attach to the action entry. + * @throws IOException + */ + public static void registerAction(String name, String category, String shortcut, String menuPath, Action action) throws IOException { + /////////////////////// + // Add/Update Action // + /////////////////////// + String originalFile = "Actions/" + category + "/" + name + ".instance"; + FileObject in = getFolderAt("Actions/" + category); + FileObject obj = in.getFileObject(name, "instance"); + if (obj == null) { + obj = in.createData(name, "instance"); + } + action.putValue(Action.NAME, name); + obj.setAttribute("instanceCreate", action); + obj.setAttribute("instanceClass", action.getClass().getName()); + + ///////////////////// + // Add/Update Menu // + ///////////////////// + in = getFolderAt(menuPath); + obj = in.getFileObject(name, "shadow"); + // Create if missing. + if (obj == null) { + obj = in.createData(name, "shadow"); + obj.setAttribute("originalFile", originalFile); + } + + ///////////////////////// + // Add/Update Shortcut // + ///////////////////////// + in = getFolderAt("Shortcuts"); + obj = in.getFileObject(shortcut, "shadow"); + if (obj == null) { + obj = in.createData(shortcut, "shadow"); + obj.setAttribute("originalFile", originalFile); + } + } + + private static FileObject getFolderAt(String inputPath) throws IOException { + final String[] split = inputPath.split("/"); + if (null == split || split.length == 0) { + return null; + } + List parts = Arrays.asList(split); + FileObject existing = FileUtil.getConfigFile(inputPath); + if (existing != null) { + return existing; + } + + FileObject base = FileUtil.getConfigFile(parts.get(0)); + if (base == null) { + return null; + } + + for (int i = 1; i < parts.size(); i++) { + String path = join("/", parts.subList(0, i + 1)); + FileObject next = FileUtil.getConfigFile(path); + if (next == null) { + next = base.createFolder(parts.get(i)); + } + base = next; + } + + return FileUtil.getConfigFile(inputPath); + } + + private static String join(String separator, List list) { + StringBuilder sb = new StringBuilder(); + final int size = list.size(); + for (int i = 0; i < size; i++) { + String text = list.get(i); + sb.append(text); + if (i != (size - 1)) { + sb.append(separator); + } + } + return sb.toString(); + } +} diff --git a/QuickOpener/src/main/java/me/dsnet/quickopener/actions/layer/LayerXMLConfiguredCustomRunnerAction.java b/QuickOpener/src/main/java/me/dsnet/quickopener/actions/layer/LayerXMLConfiguredCustomRunnerAction.java new file mode 100644 index 0000000..0accd82 --- /dev/null +++ b/QuickOpener/src/main/java/me/dsnet/quickopener/actions/layer/LayerXMLConfiguredCustomRunnerAction.java @@ -0,0 +1,89 @@ +package me.dsnet.quickopener.actions.layer; + +import java.awt.event.ActionEvent; +import java.util.Map; +import javax.swing.AbstractAction; +import javax.swing.Action; +import me.dsnet.quickopener.actions.RunCommand; +import org.openide.awt.DynamicMenuContent; +import org.openide.util.Lookup; +import org.openide.util.LookupEvent; +import org.openide.util.LookupListener; +import org.openide.util.Utilities; + +/** + * Action for running custom commands. The configuration is purely based on + * custom attribute tags of the action registration in the layer.xml file. + * + *
+ * <file name="action1.instance">
+ *     <attr methodvalue="me.dsnet.quickopener.actions.layer.SuperclassSensitiveAction.create" name="instanceCreate"/>
+ *     <attr name="imagePath" stringvalue="me/dsnet/quickopener/icons/run.png"/>
+ *     <attr name="displayName" stringvalue="Notepad"/>
+ *     <attr name="custom-command" stringvalue="notepad ${file}"/>
+ * </file>
+ * 
+ * + *

+ * Based on + * https://blogs.oracle.com/geertjan/entry/enabling_an_action_on_object, + * https://blogs.oracle.com/geertjan/entry/superclass_sensitive_actions and + * http://wiki.netbeans.org/DevFaqActionsAddAtRuntime + *

+ * + * @author markiewb + */ +public final class LayerXMLConfiguredCustomRunnerAction extends AbstractAction { + + private final Lookup.Result lookupResult; + private final RunCommand runCommand; + + /** + * Referenced from layer.xml like this + * + * @param map + * @return + */ + static Action create(Map map) { + final String command = (String) map.get("custom-command"); + final String displayName = (String) map.get("displayName"); + final String iconBase = (String) map.get("imagePath"); + return new LayerXMLConfiguredCustomRunnerAction(iconBase, displayName, command); + } + + /** + * Only to be instanciated by the layer.xml configured based on the + * "instanceCreate"-value. + * + * @param iconBase + * @param displayName + * @param command + */ + private LayerXMLConfiguredCustomRunnerAction(String iconBase, String displayName, final String command) { + super(displayName); + Lookup context = Utilities.actionsGlobalContext(); + runCommand = new RunCommand(command); + //enable action, if all placeholders are replaced + setEnabled(runCommand.areAllPlaceHoldersReplaced()); + //always show + putValue(DynamicMenuContent.HIDE_WHEN_DISABLED, false); + putValue("iconBase", iconBase); + + // make this action context aware using custom code + lookupResult = context.lookupResult(Object.class); + lookupResult.addLookupListener(new LookupListener() { + @Override + public void resultChanged(LookupEvent le) { + //enable action, if all placeholders are replaced + setEnabled(runCommand.areAllPlaceHoldersReplaced()); + } + }); + + } + + @Override + public void actionPerformed(ActionEvent ev) { + runCommand.actionPerformed(); + } + +} diff --git a/QuickOpener/src/main/resources/me/dsnet/quickopener/layer.xml b/QuickOpener/src/main/resources/me/dsnet/quickopener/layer.xml index b5029e4..c10ed42 100644 --- a/QuickOpener/src/main/resources/me/dsnet/quickopener/layer.xml +++ b/QuickOpener/src/main/resources/me/dsnet/quickopener/layer.xml @@ -2,6 +2,15 @@ + + + + + + + + + @@ -104,6 +113,11 @@ + + + + + @@ -113,5 +127,7 @@ + +