Skip to content

Commit

Permalink
Launch custom commands directly from Menu Item or Dropdown #59 - Draft
Browse files Browse the repository at this point in the history
Example: A custom action purely configured in layer.xml creates an context sensitive action/shortkey and toolbar icon
  • Loading branch information
markiewb committed Mar 22, 2016
1 parent fcd8b5f commit d37f539
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -43,16 +45,8 @@ public boolean actionPerformed() {
DialogDisplayer.getDefault().notify(d);
return false;
} else {
final Map<String, String> 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);
Expand All @@ -75,6 +69,23 @@ public boolean actionPerformed() {
}
}

/**
* Visible to determine if command could be executed.
* @return
*/
public boolean areAllPlaceHoldersReplaced() {
String command = getCommandWithReplacedPlaceholders();
final Set<String> 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<String, String> createPlaceholders() {
String currentFile = PathFinder.getActivePath(null, false);
String currentFolder = PathFinder.getActivePath(null, true);
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> 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<String> 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();
}
}
Original file line number Diff line number Diff line change
@@ -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.
*
* <pre>
* &lt;file name="action1.instance"&gt;
* &lt;attr methodvalue="me.dsnet.quickopener.actions.layer.SuperclassSensitiveAction.create" name="instanceCreate"/&gt;
* &lt;attr name="imagePath" stringvalue="me/dsnet/quickopener/icons/run.png"/&gt;
* &lt;attr name="displayName" stringvalue="Notepad"/&gt;
* &lt;attr name="custom-command" stringvalue="notepad ${file}"/&gt;
* &lt;/file&gt;
* </pre>
*
* <p>
* 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
* </p>
*
* @author markiewb
*/
public final class LayerXMLConfiguredCustomRunnerAction extends AbstractAction {

private final Lookup.Result<Object> 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();
}

}
16 changes: 16 additions & 0 deletions QuickOpener/src/main/resources/me/dsnet/quickopener/layer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.2//EN" "http://www.netbeans.org/dtds/filesystem-1_2.dtd">
<filesystem>
<folder name="Actions">
<folder name="QuickOpener">
<file name="foo-action1.instance">
<attr methodvalue="me.dsnet.quickopener.actions.layer.LayerXMLConfiguredCustomRunnerAction.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>
</folder>

<folder name="Tools">
<file name="me-dsnet-quickopener-actions-ToolbarPresenter.instance">
<!--me.dsnet.quickopener.actions.CustomCommand-->
Expand Down Expand Up @@ -104,6 +113,11 @@
<attr name="originalFile" stringvalue="Actions/Tools/me-dsnet-quickopener-actions-ToolbarPresenter.instance"/>
<attr intvalue="800" name="position"/>
</file>
<file name="action1.shadow">
<attr name="originalFile" stringvalue="Actions/QuickOpener/foo-action1.instance"/>
<attr intvalue="900" name="position"/>
</file>

</folder>
</folder>
<folder name="Shortcuts">
Expand All @@ -113,5 +127,7 @@
<file name="O-4.shadow"><attr name="originalFile" stringvalue="Actions/Tools/me-dsnet-quickopener-actions-popup-CustomCommandPopupAction.instance"/></file>
<file name="O-5.shadow"><attr name="originalFile" stringvalue="Actions/Tools/me-dsnet-quickopener-actions-popup-CustomFileSystemPopupAction.instance"/></file>
<file name="O-6.shadow"><attr name="originalFile" stringvalue="Actions/Tools/me-dsnet-quickopener-actions-popup-CustomTerminalPopupAction.instance"/></file>

<file name="O-7.shadow"><attr name="originalFile" stringvalue="Actions/QuickOpener/foo-action1.instance"/></file>
</folder>
</filesystem>

0 comments on commit d37f539

Please sign in to comment.