GP-3080 revised default tool launch to allow for optional running tool re-use

This commit is contained in:
ghidra1
2023-02-09 18:33:57 -05:00
parent 1de4b32e51
commit a5f2c9d55b
12 changed files with 117 additions and 108 deletions
@@ -359,7 +359,11 @@
<LI>To open a file in the tool that was <A href= <LI>To open a file in the tool that was <A href=
"help/topics/Tool/Ghidra_Tool_Administration.htm#Set_Tool_Associations">specified as the "help/topics/Tool/Ghidra_Tool_Administration.htm#Set_Tool_Associations">specified as the
"default,"</A> double click on the Program that you want to open, OR right mouse click on "default,"</A> double click on the Program that you want to open, OR right mouse click on
the file and choose <B>Open in Default Tool.</B></LI> the file and choose <B>Open in Default Tool.</B> Either a new tool will be launched
or an existing running tool will be reused based upon the Tool option setting (see
<A href="help/topics/Tool/ToolOptions_Dialog.htm#Front_End_Tool_Options">
Front-End Tool Options</A>.
</LI>
</UL> </UL>
<H4><A name="Open_File_With"></A>Open a File With a Specific Tool</H4> <H4><A name="Open_File_With"></A>Open a File With a Specific Tool</H4>
@@ -412,7 +412,16 @@
whether or not Ghidra automatically opens the previously loaded project on whether or not Ghidra automatically opens the previously loaded project on
startup.</TD> startup.</TD>
</TR> </TR>
<TR>
<TD valign="top" width="200" align="left">Default Tool Launch Mode</TD>
<TD valign="top" align="left">This controls
if a new or already running tool should be used during default launch.
Tool "reuse" mode will open selected file within a suitable running tool
if one can be identified, otherwise a new tool will be launched.
</TD>
</TR>
<TR> <TR>
<TD valign="top" width="200" align="left"><A name="Use_Inverted_Colors"></A>Use <TD valign="top" width="200" align="left"><A name="Use_Inverted_Colors"></A>Use
@@ -316,18 +316,7 @@ public final class LanguageProviderPlugin extends Plugin implements ApplicationL
try { try {
SwingUtilities.invokeAndWait(() -> { SwingUtilities.invokeAndWait(() -> {
ToolServices toolServices = tool.getToolServices(); ToolServices toolServices = tool.getToolServices();
ToolTemplate defaultToolTemplate = toolServices.getDefaultToolTemplate(file); if (toolServices.launchDefaultTool(domainFile) == null) {
if (defaultToolTemplate != null) {
String defaultToolName = defaultToolTemplate.getName();
for (PluginTool t : toolServices.getRunningTools()) {
if (t.getName().equals(defaultToolName)) {
openTool = t;
break;
}
}
}
if (openTool == null ||
!openTool.acceptDomainFiles(new DomainFile[] { file })) {
Msg.showError(this, tool.getToolFrame(), "Failed to Open Program", Msg.showError(this, tool.getToolFrame(), "Failed to Open Program",
"A suitable default tool could not found!"); "A suitable default tool could not found!");
} }
@@ -185,8 +185,8 @@ public interface ContentHandler<T extends DomainObjectAdapter> extends Extension
Icon getIcon(); Icon getIcon();
/** /**
* Returns the name of the default tool that should be used to open this content type. * Returns the name of the default tool/template that should be used to open this content type.
* @return associated default tool for this content type * @return associated default tool name for this content type
*/ */
String getDefaultToolName(); String getDefaultToolName();
@@ -1102,14 +1102,9 @@ public class FrontEndPlugin extends Plugin
} }
Project project = tool.getProject(); Project project = tool.getProject();
final ToolServices toolServices = project.getToolServices(); ToolServices toolServices = project.getToolServices();
ToolTemplate defaultToolTemplate = toolServices.getDefaultToolTemplate(domainFile); if (toolServices.launchDefaultTool(domainFile) != null) {
if (defaultToolTemplate != null) { return;
ToolButton button = toolBar.getToolButtonForToolConfig(defaultToolTemplate);
if (button != null) {
button.launchTool(domainFile);
return;
}
} }
Msg.showInfo(this, tool.getToolFrame(), "Cannot Find Tool", Msg.showInfo(this, tool.getToolFrame(), "Cannot Find Tool",
@@ -87,6 +87,7 @@ import help.HelpService;
* manner. * manner.
*/ */
public class FrontEndTool extends PluginTool implements OptionsChangeListener { public class FrontEndTool extends PluginTool implements OptionsChangeListener {
public static final String DEFAULT_TOOL_LAUNCH_MODE = "Default Tool Launch Mode";
public static final String AUTOMATICALLY_SAVE_TOOLS = "Automatically Save Tools"; public static final String AUTOMATICALLY_SAVE_TOOLS = "Automatically Save Tools";
private static final String USE_ALERT_ANIMATION_OPTION_NAME = "Use Notification Animation"; private static final String USE_ALERT_ANIMATION_OPTION_NAME = "Use Notification Animation";
@@ -122,6 +123,8 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
private WeakSet<ProjectListener> listeners; private WeakSet<ProjectListener> listeners;
private FrontEndPlugin plugin; private FrontEndPlugin plugin;
private DefaultLaunchMode defaultLaunchMode = DefaultLaunchMode.DEFAULT;
private ComponentProvider compProvider; private ComponentProvider compProvider;
private LogComponentProvider logProvider; private LogComponentProvider logProvider;
@@ -315,11 +318,21 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
showComponentHeader(compProvider, false); showComponentHeader(compProvider, false);
} }
/**
* Get the preferred default tool launch mode
* @return default tool launch mode
*/
public DefaultLaunchMode getDefaultLaunchMode() {
return defaultLaunchMode;
}
private void initFrontEndOptions() { private void initFrontEndOptions() {
ToolOptions options = getOptions(ToolConstants.TOOL_OPTIONS); ToolOptions options = getOptions(ToolConstants.TOOL_OPTIONS);
HelpLocation help = HelpLocation help =
new HelpLocation(ToolConstants.TOOL_HELP_TOPIC, "Front_End_Tool_Options"); new HelpLocation(ToolConstants.TOOL_HELP_TOPIC, "Front_End_Tool_Options");
options.registerOption(DEFAULT_TOOL_LAUNCH_MODE, DefaultLaunchMode.DEFAULT, help,
"Indicates if a new or already running tool should be used during default launch.");
options.registerOption(AUTOMATICALLY_SAVE_TOOLS, true, help, options.registerOption(AUTOMATICALLY_SAVE_TOOLS, true, help,
"When enabled tools will be saved when they are closed"); "When enabled tools will be saved when they are closed");
options.registerOption(USE_ALERT_ANIMATION_OPTION_NAME, true, help, options.registerOption(USE_ALERT_ANIMATION_OPTION_NAME, true, help,
@@ -332,6 +345,8 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
options.registerOption(RESTORE_PREVIOUS_PROJECT_NAME, Boolean.TRUE, help, options.registerOption(RESTORE_PREVIOUS_PROJECT_NAME, Boolean.TRUE, help,
"Restore the previous project when Ghidra starts."); "Restore the previous project when Ghidra starts.");
defaultLaunchMode = options.getEnum(DEFAULT_TOOL_LAUNCH_MODE, defaultLaunchMode);
boolean autoSave = options.getBoolean(AUTOMATICALLY_SAVE_TOOLS, true); boolean autoSave = options.getBoolean(AUTOMATICALLY_SAVE_TOOLS, true);
GhidraTool.autoSave = autoSave; GhidraTool.autoSave = autoSave;
@@ -350,6 +365,9 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
@Override @Override
public void optionsChanged(ToolOptions options, String optionName, Object oldValue, public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) { Object newValue) {
if (DEFAULT_TOOL_LAUNCH_MODE.equals(optionName)) {
defaultLaunchMode = (DefaultLaunchMode) newValue;
}
if (AUTOMATICALLY_SAVE_TOOLS.equals(optionName)) { if (AUTOMATICALLY_SAVE_TOOLS.equals(optionName)) {
GhidraTool.autoSave = (Boolean) newValue; GhidraTool.autoSave = (Boolean) newValue;
} }
@@ -67,7 +67,6 @@ class ToolButton extends EmptyBorderButton implements Draggable, Droppable {
private ToolTemplate template; private ToolTemplate template;
private PluginTool associatedRunningTool; private PluginTool associatedRunningTool;
private DefaultToolChangeListener toolChangeListener;
private ToolServices toolServices; private ToolServices toolServices;
/** /**
@@ -132,8 +131,6 @@ class ToolButton extends EmptyBorderButton implements Draggable, Droppable {
} }
if (!isRunningTool()) { if (!isRunningTool()) {
toolChangeListener = new ToolChangeListener(template);
toolServices.addDefaultToolChangeListener(toolChangeListener);
setIcon(generateIcon()); setIcon(generateIcon());
} }
} }
@@ -503,8 +500,6 @@ class ToolButton extends EmptyBorderButton implements Draggable, Droppable {
} }
void dispose() { void dispose() {
toolServices.removeDefaultToolChangeListener(toolChangeListener);
plugin = null; plugin = null;
template = null; template = null;
associatedRunningTool = null; associatedRunningTool = null;
@@ -679,22 +674,6 @@ class ToolButton extends EmptyBorderButton implements Draggable, Droppable {
// Inner Classes // Inner Classes
//================================================================================================== //==================================================================================================
private class ToolChangeListener implements DefaultToolChangeListener {
private final ToolTemplate toolTemplate;
public ToolChangeListener(ToolTemplate toolTemplate) {
this.toolTemplate = toolTemplate;
}
@Override
public void defaultToolChanged(String oldName, String newName) {
String myName = toolTemplate.getName();
if (myName.equals(oldName) || myName.equals(newName)) {
setIcon(generateIcon());
}
}
}
private class ToolButtonDropTgtAdapter extends DropTgtAdapter { private class ToolButtonDropTgtAdapter extends DropTgtAdapter {
private boolean draggingOverValidDropTarget = false; private boolean draggingOverValidDropTarget = false;
@@ -21,8 +21,8 @@ import java.util.List;
import docking.action.KeyBindingData; import docking.action.KeyBindingData;
import docking.action.MenuData; import docking.action.MenuData;
import ghidra.framework.main.AppInfo; import ghidra.framework.main.AppInfo;
import ghidra.framework.main.datatable.ProjectDataContext;
import ghidra.framework.main.datatable.FrontendProjectTreeAction; import ghidra.framework.main.datatable.FrontendProjectTreeAction;
import ghidra.framework.main.datatable.ProjectDataContext;
import ghidra.framework.model.DomainFile; import ghidra.framework.model.DomainFile;
public class ProjectDataOpenDefaultToolAction extends FrontendProjectTreeAction { public class ProjectDataOpenDefaultToolAction extends FrontendProjectTreeAction {
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,15 +15,28 @@
*/ */
package ghidra.framework.model; package ghidra.framework.model;
/** import ghidra.framework.options.Options;
* Listener that is notified when the default tool specification changes.
*/ /**
public interface DefaultToolChangeListener { * {@link DefaultLaunchMode} provides an {@link Options} value which indicates how a default tool
* launch should be performed.
*/
public enum DefaultLaunchMode {
REUSE_TOOL("Reuse acceptable running tool"),
NEW_TOOL("Launch new default tool");
public static DefaultLaunchMode DEFAULT = NEW_TOOL;
private String str;
private DefaultLaunchMode(String str) {
this.str = str;
}
@Override
public String toString() {
return str;
}
/**
* Notification that the default tool specification changed
* @param oldName name of the old default tool
* @param newName name of the new default tool
*/
void defaultToolChanged(String oldName, String newName);
} }
@@ -119,6 +119,7 @@ public interface ToolServices {
/** /**
* Launch the default tool and open the specified domainFile. * Launch the default tool and open the specified domainFile.
* NOTE: running tool re-use is implementation dependent
* @param domainFile the file to open * @param domainFile the file to open
* @return the launched tool. Null returned if a suitable default tool * @return the launched tool. Null returned if a suitable default tool
* for the file content type was not found. * for the file content type was not found.
@@ -136,7 +137,8 @@ public interface ToolServices {
/** /**
* Launch the default tool and open the specified Ghidra URL resource. * Launch the default tool and open the specified Ghidra URL resource.
* The tool choosen well be based upon the content type of the specified resource. * The tool choosen will be based upon the content type of the specified resource.
* NOTE: running tool re-use is implementation dependent
* @param ghidraUrl resource to be opened (see {@link GhidraURL}) * @param ghidraUrl resource to be opened (see {@link GhidraURL})
* @return the launched tool. Null returned if a failure occurs while accessing the specified * @return the launched tool. Null returned if a failure occurs while accessing the specified
* resource or a suitable default tool for the file content type was not found. * resource or a suitable default tool for the file content type was not found.
@@ -155,18 +157,6 @@ public interface ToolServices {
*/ */
public PluginTool launchToolWithURL(String toolName, URL ghidraUrl); public PluginTool launchToolWithURL(String toolName, URL ghidraUrl);
/**
* Add a listener that will be notified when the default tool specification changes
* @param listener the listener
*/
public void addDefaultToolChangeListener(DefaultToolChangeListener listener);
/**
* Remove the listener
* @param listener the listener
*/
public void removeDefaultToolChangeListener(DefaultToolChangeListener listener);
/** /**
* Return array of running tools * Return array of running tools
* @return array of Tools * @return array of Tools
@@ -23,10 +23,6 @@ import ghidra.framework.model.*;
public class ToolServicesAdapter implements ToolServices { public class ToolServicesAdapter implements ToolServices {
@Override
public void addDefaultToolChangeListener(DefaultToolChangeListener listener) {
}
@Override @Override
public boolean canAutoSave(PluginTool tool) { public boolean canAutoSave(PluginTool tool) {
return true; return true;
@@ -97,11 +93,6 @@ public class ToolServicesAdapter implements ToolServices {
return null; return null;
} }
@Override
public void removeDefaultToolChangeListener(DefaultToolChangeListener listener) {
// override
}
@Override @Override
public void saveTool(PluginTool tool) { public void saveTool(PluginTool tool) {
// override // override
@@ -18,6 +18,7 @@ package ghidra.framework.project.tool;
import java.io.*; import java.io.*;
import java.net.URL; import java.net.URL;
import java.util.*; import java.util.*;
import java.util.function.Function;
import org.jdom.Document; import org.jdom.Document;
import org.jdom.output.XMLOutputter; import org.jdom.output.XMLOutputter;
@@ -26,6 +27,8 @@ import docking.widgets.OptionDialog;
import docking.widgets.filechooser.GhidraFileChooser; import docking.widgets.filechooser.GhidraFileChooser;
import ghidra.framework.ToolUtils; import ghidra.framework.ToolUtils;
import ghidra.framework.data.*; import ghidra.framework.data.*;
import ghidra.framework.main.AppInfo;
import ghidra.framework.main.FrontEndTool;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginEvent; import ghidra.framework.plugintool.PluginEvent;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
@@ -49,7 +52,6 @@ class ToolServicesImpl implements ToolServices {
private ToolChest toolChest; private ToolChest toolChest;
private ToolManagerImpl toolManager; private ToolManagerImpl toolManager;
private List<DefaultToolChangeListener> listeners = new ArrayList<>();
private ToolChestChangeListener toolChestChangeListener; private ToolChestChangeListener toolChestChangeListener;
private Set<ContentHandler<?>> contentHandlers; private Set<ContentHandler<?>> contentHandlers;
@@ -187,22 +189,60 @@ class ToolServicesImpl implements ToolServices {
matchingTool.firePluginEvent(event); matchingTool.firePluginEvent(event);
} }
@Override private static DefaultLaunchMode getDefaultLaunchMode() {
public PluginTool launchDefaultTool(DomainFile domainFile) { DefaultLaunchMode defaultLaunchMode = DefaultLaunchMode.DEFAULT;
ToolTemplate template = getDefaultToolTemplate(domainFile); FrontEndTool frontEndTool = AppInfo.getFrontEndTool();
if (template == null) { if (frontEndTool != null) {
return null; defaultLaunchMode = frontEndTool.getDefaultLaunchMode();
} }
return defaultLaunchMode;
}
private PluginTool defaultLaunch(ToolTemplate template,
Function<PluginTool, Boolean> openFunction) {
DefaultLaunchMode defaultLaunchMode = getDefaultLaunchMode();
if (defaultLaunchMode == DefaultLaunchMode.REUSE_TOOL) {
if (template != null) {
// attempt to reuse running tool with default name
String defaultToolName = template.getName();
for (PluginTool tool : getRunningTools()) {
if (tool.getName().equals(defaultToolName) && openFunction.apply(tool)) {
return tool;
}
}
}
// attempt to reuse any running tool
for (PluginTool tool : getRunningTools()) {
if (openFunction.apply(tool)) {
return tool;
}
}
}
if (template == null) {
return null; // unable to launch new tool
}
Workspace workspace = toolManager.getActiveWorkspace(); Workspace workspace = toolManager.getActiveWorkspace();
PluginTool tool = workspace.runTool(template); PluginTool tool = workspace.runTool(template);
if (tool == null) { if (tool == null) {
return null; return null; // tool launch failed
} }
tool.setVisible(true); tool.setVisible(true);
tool.acceptDomainFiles(new DomainFile[] { domainFile }); openFunction.apply(tool);
return tool; return tool;
} }
@Override
public PluginTool launchDefaultTool(DomainFile domainFile) {
ToolTemplate template = getDefaultToolTemplate(domainFile);
return defaultLaunch(template, t -> {
return t.acceptDomainFiles(new DomainFile[] { domainFile });
});
}
@Override @Override
public PluginTool launchTool(String toolName, DomainFile domainFile) { public PluginTool launchTool(String toolName, DomainFile domainFile) {
ToolTemplate template = findToolChestToolTemplate(toolName); ToolTemplate template = findToolChestToolTemplate(toolName);
@@ -228,17 +268,9 @@ class ToolServicesImpl implements ToolServices {
return null; return null;
} }
ToolTemplate template = getDefaultToolTemplate(contentType); ToolTemplate template = getDefaultToolTemplate(contentType);
if (template == null) { return defaultLaunch(template, t -> {
return null; return t.accept(ghidraUrl);
} });
Workspace workspace = toolManager.getActiveWorkspace();
PluginTool tool = workspace.runTool(template);
if (tool == null) {
return null;
}
tool.setVisible(true);
tool.accept(ghidraUrl);
return tool;
} }
@Override @Override
@@ -475,16 +507,6 @@ class ToolServicesImpl implements ToolServices {
return contentHandlers; return contentHandlers;
} }
@Override
public void addDefaultToolChangeListener(DefaultToolChangeListener listener) {
listeners.add(listener);
}
@Override
public void removeDefaultToolChangeListener(DefaultToolChangeListener listener) {
listeners.remove(listener);
}
private GhidraToolTemplate findToolChestToolTemplate(String toolName) { private GhidraToolTemplate findToolChestToolTemplate(String toolName) {
if (toolName != null) { if (toolName != null) {
return (GhidraToolTemplate) toolChest.getToolTemplate(toolName); return (GhidraToolTemplate) toolChest.getToolTemplate(toolName);