Merge remote-tracking branch 'origin/GP-1441_ghidravore_debugger_window_undo_redo--SQUASHED'

This commit is contained in:
Ryan Kurtz
2021-11-02 15:49:37 -04:00
10 changed files with 228 additions and 141 deletions
@@ -22,28 +22,25 @@ import ghidra.program.model.listing.Program;
/**
* Abstract base class for program actions that change their menu name depending on the the active
* program. There are two types of actions that extend this class; those that only work
* on programs that are managed by Ghidra, and those that can work on any program even those
* whose life cycles are managed by individual plugins.
* program. Note that actions that derived from this class only work on programs that are
* globally managed by Ghidra and not opened and managed by individual plugins. If the action
* context should happen to contain a non-global managed program, the tool's concept of the
* current active program will be used as target of this action instead.
*/
public abstract class AbstractProgramNameSwitchingAction extends DockingAction {
protected ProgramManagerPlugin plugin;
protected Program lastContextProgram;
private boolean requiresManagedProgram;
/**
* Constructor
* @param plugin the ProgramManagerPlugin (i.e. the global Ghidra manager for programs)
* @param name the name of the action
* @param requiresManagedProgram true if the action is only used on globally managed
* programs
*/
public AbstractProgramNameSwitchingAction(ProgramManagerPlugin plugin, String name,
boolean requiresManagedProgram) {
public AbstractProgramNameSwitchingAction(ProgramManagerPlugin plugin, String name) {
super(name, plugin.getName());
this.plugin = plugin;
this.requiresManagedProgram = requiresManagedProgram;
addToWindowWhen(ProgramActionContext.class);
}
@@ -80,16 +77,16 @@ public abstract class AbstractProgramNameSwitchingAction extends DockingAction {
protected abstract void programChanged(Program program);
/**
* Gets the program for the given context. If this actions requires the program
* to be globally managed, then it will only use the context program if it is
* managed; otherwise it will return the global current program.
* Gets the program for the given context. The actions that derive from this class only
* work on programs that are globally managed by Ghidra. So if the program from the context
* is not managed by Ghidra, then just use the current program that is managed.
* @param context the action context from which to get the program
* @return the appropriate program to use for this action.
*/
protected Program getProgram(ActionContext context) {
if (context instanceof ProgramActionContext) {
Program program = ((ProgramActionContext) context).getProgram();
if (plugin.isManaged(program) || !requiresManagedProgram) {
if (plugin.isManaged(program)) {
return program;
}
// otherwise, just return the global current program.
@@ -0,0 +1,185 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.progmgr;
import java.io.IOException;
import javax.swing.Icon;
import docking.ActionContext;
import docking.action.*;
import docking.tool.ToolConstants;
import ghidra.app.context.ProgramActionContext;
import ghidra.app.services.GoToService;
import ghidra.app.services.NavigationHistoryService;
import ghidra.framework.data.DomainObjectAdapterDB;
import ghidra.framework.model.Transaction;
import ghidra.framework.model.TransactionListener;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.util.*;
import resources.ResourceManager;
/**
* Abstract base class for the undo and redo actions. These actions add a listener to the
* current context program in order to know when to update their enabled state and description.
*/
public abstract class AbstractUndoRedoAction extends DockingAction {
private PluginTool tool;
private Program lastProgram;
private ProgramManagerPlugin plugin;
private TransactionListener transactionListener;
public AbstractUndoRedoAction(PluginTool tool, ProgramManagerPlugin plugin, String name,
String iconPath, String keyBinding, String subGroup) {
super(name, plugin.getName());
this.tool = tool;
this.plugin = plugin;
String[] menuPath = { ToolConstants.MENU_EDIT, "&" + name };
Icon icon = ResourceManager.loadImage(iconPath);
String group = "Undo";
MenuData menuData = new MenuData(menuPath, icon, group);
menuData.setMenuSubGroup(subGroup); // make this appear above the redo menu item
setMenuBarData(menuData);
setToolBarData(new ToolBarData(icon, group));
setKeyBindingData(new KeyBindingData(keyBinding));
setHelpLocation(new HelpLocation(ToolConstants.TOOL_HELP_TOPIC, name));
setDescription(name);
addToWindowWhen(ProgramActionContext.class);
transactionListener = new ContextProgramTransactionListener();
}
protected abstract void actionPerformed(Program program) throws IOException;
protected abstract boolean canPerformAction(Program program);
protected abstract String getUndoRedoDescription(Program program);
@Override
public boolean isEnabledForContext(ActionContext context) {
Program program = getProgram(context);
// This method gets called whenever the action context changes. We will insert logic
// here to keep track of the current program context and add a listener
// so that the action's name, description, and enablement is properly updated as the
// user makes changes to the program.
if (program != lastProgram) {
removeTransactionListener(lastProgram);
lastProgram = program;
addTransactionListener(lastProgram);
updateActionNameAndDescription();
}
return canPerformAction(lastProgram);
}
@Override
public void actionPerformed(ActionContext context) {
Program program = getProgram(context);
if (program == null) {
return;
}
saveCurrentLocationToHistory();
try {
actionPerformed(program);
}
catch (IOException e) {
Msg.showError(this, null, null, null, e);
}
}
private Program getProgram(ActionContext context) {
if (context instanceof ProgramActionContext) {
return ((ProgramActionContext) context).getProgram();
}
return plugin.getCurrentProgram();
}
private void removeTransactionListener(Program program) {
if (program != null) {
program.removeTransactionListener(transactionListener);
}
}
private void addTransactionListener(Program program) {
if (program != null) {
program.addTransactionListener(transactionListener);
}
}
private void updateAction() {
updateActionNameAndDescription();
setEnabled(canPerformAction(lastProgram));
}
private void updateActionNameAndDescription() {
String actionName = getName();
String description = actionName;
if (lastProgram != null) {
actionName += " " + lastProgram.getDomainFile().getName();
description = actionName;
}
if (canPerformAction(lastProgram)) {
description = HTMLUtilities.toWrappedHTML(
getName() + " " + HTMLUtilities.escapeHTML(getUndoRedoDescription(lastProgram)));
}
getMenuBarData().setMenuItemName(actionName);
setDescription(description);
}
private void saveCurrentLocationToHistory() {
GoToService goToService = tool.getService(GoToService.class);
NavigationHistoryService historyService = tool.getService(NavigationHistoryService.class);
if (goToService != null && historyService != null) {
historyService.addNewLocation(goToService.getDefaultNavigatable());
}
}
private class ContextProgramTransactionListener implements TransactionListener {
@Override
public void transactionStarted(DomainObjectAdapterDB domainObj, Transaction tx) {
// don't care
}
@Override
public void transactionEnded(DomainObjectAdapterDB domainObj) {
// don't care
}
@Override
public void undoStackChanged(DomainObjectAdapterDB domainObj) {
updateAction();
}
@Override
public void undoRedoOccurred(DomainObjectAdapterDB domainObj) {
// don't care
}
}
}
@@ -27,7 +27,7 @@ import ghidra.util.HTMLUtilities;
public class CloseProgramAction extends AbstractProgramNameSwitchingAction {
public CloseProgramAction(ProgramManagerPlugin plugin, String group, int subGroup) {
super(plugin, "Close File", true);
super(plugin, "Close File");
MenuData menuData = new MenuData(new String[] { ToolConstants.MENU_FILE, "&Close" });
menuData.setMenuGroup(group);
menuData.setMenuSubGroup(Integer.toString(subGroup));
@@ -49,6 +49,8 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
private MyFolderListener folderListener;
private Runnable programChangedRunnable;
private boolean hasUnsavedPrograms;
MultiProgramManager(ProgramManagerPlugin programManagerPlugin) {
this.plugin = programManagerPlugin;
this.tool = programManagerPlugin.getTool();
@@ -64,7 +66,8 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
if (tool == null) {
return; // we have been disposed
}
plugin.undoStackChanged();
hasUnsavedPrograms = checkForUnsavedPrograms();
plugin.contextChanged();
};
}
@@ -516,6 +519,10 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
* @return true if there is at least one program that has unsaved changes.
*/
public boolean hasUnsavedPrograms() {
return hasUnsavedPrograms;
}
private boolean checkForUnsavedPrograms() {
// first check the current program as that is the one most likely to have changes
Program currentProgram = getCurrentProgram();
if (currentProgram != null && currentProgram.isChanged()) {
@@ -528,5 +535,6 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
}
}
return false;
}
}
@@ -613,12 +613,6 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
}
}
void undoStackChanged() {
undoAction.updateActionMenuName();
redoAction.updateActionMenuName();
tool.contextChanged(null);
}
void contextChanged() {
tool.contextChanged(null);
}
@@ -25,7 +25,7 @@ import ghidra.program.model.listing.Program;
public class ProgramOptionsAction extends AbstractProgramNameSwitchingAction {
public ProgramOptionsAction(ProgramManagerPlugin plugin) {
super(plugin, "Program Options", true);
super(plugin, "Program Options");
MenuData menuData =
new MenuData(new String[] { ToolConstants.MENU_EDIT, "P&rogram Options..." });
menuData.setMenuGroup(ToolConstants.TOOL_OPTIONS_MENU_GROUP);
@@ -17,80 +17,31 @@ package ghidra.app.plugin.core.progmgr;
import java.io.IOException;
import javax.swing.Icon;
import docking.action.*;
import docking.tool.ToolConstants;
import ghidra.app.services.GoToService;
import ghidra.app.services.NavigationHistoryService;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.util.*;
import resources.ResourceManager;
/**
* Action class for the "redo" action
*/
public class RedoAction extends AbstractProgramNameSwitchingAction {
private final PluginTool tool;
public class RedoAction extends AbstractUndoRedoAction {
public static final String SUBGROUP = "2Redo";
public RedoAction(ProgramManagerPlugin plugin, PluginTool tool) {
super(plugin, "Redo", true);
this.tool = tool;
setHelpLocation(new HelpLocation(ToolConstants.TOOL_HELP_TOPIC, "Redo"));
String[] menuPath = { ToolConstants.MENU_EDIT, "&Redo" };
String group = "Undo";
Icon icon = ResourceManager.loadImage("images/redo.png");
MenuData menuData = new MenuData(menuPath, icon, group);
menuData.setMenuSubGroup("2Redo"); // make this appear below the undo menu item
setMenuBarData(menuData);
setToolBarData(new ToolBarData(icon, group));
setKeyBindingData(new KeyBindingData("ctrl shift Z"));
setDescription("Redo");
super(tool, plugin, "Redo", "images/redo.png", "ctrl shift Z", SUBGROUP);
}
@Override
protected void actionPerformed(Program program) {
try {
saveCurrentLocationToHistory();
program.redo();
}
catch (IOException e) {
Msg.showError(this, null, null, null, e);
}
}
void updateActionMenuName() {
updateActionMenuName(lastContextProgram);
}
void updateActionMenuName(Program program) {
String actionName = "Redo " + (program == null ? "" : program.getDomainFile().getName());
String description = actionName;
if (program != null && program.canRedo()) {
description = HTMLUtilities
.toWrappedHTML("Redo " + HTMLUtilities.escapeHTML(program.getRedoName()));
}
getMenuBarData().setMenuItemName(actionName);
setDescription(description);
}
protected void programChanged(Program program) {
updateActionMenuName(program);
protected void actionPerformed(Program program) throws IOException {
program.redo();
}
@Override
protected boolean isEnabledForContext(Program program) {
protected boolean canPerformAction(Program program) {
return program != null && program.canRedo();
}
private void saveCurrentLocationToHistory() {
GoToService goToService = tool.getService(GoToService.class);
NavigationHistoryService historyService = tool.getService(NavigationHistoryService.class);
if (goToService != null && historyService != null) {
historyService.addNewLocation(goToService.getDefaultNavigatable());
}
@Override
protected String getUndoRedoDescription(Program program) {
return program.getRedoName();
}
}
@@ -25,7 +25,7 @@ import ghidra.program.model.listing.Program;
public class SaveAsProgramAction extends AbstractProgramNameSwitchingAction {
public SaveAsProgramAction(ProgramManagerPlugin plugin, String group, int subGroup) {
super(plugin, "Save As File", true);
super(plugin, "Save As File");
MenuData menuData = new MenuData(new String[] { ToolConstants.MENU_FILE, "Save &As..." });
menuData.setMenuGroup(group);
menuData.setMenuSubGroup(Integer.toString(subGroup));
@@ -28,7 +28,7 @@ import resources.ResourceManager;
public class SaveProgramAction extends AbstractProgramNameSwitchingAction {
public SaveProgramAction(ProgramManagerPlugin plugin, String group, int subGroup) {
super(plugin, "Save File", true);
super(plugin, "Save File");
MenuData menuData = new MenuData(new String[] { ToolConstants.MENU_FILE, "Save File" });
menuData.setMenuGroup(group);
menuData.setMenuSubGroup(Integer.toString(subGroup));
@@ -17,80 +17,32 @@ package ghidra.app.plugin.core.progmgr;
import java.io.IOException;
import javax.swing.Icon;
import docking.action.*;
import docking.tool.ToolConstants;
import ghidra.app.services.GoToService;
import ghidra.app.services.NavigationHistoryService;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.util.*;
import resources.ResourceManager;
/**
* Action class for the "Undo" action
*/
public class UndoAction extends AbstractProgramNameSwitchingAction {
private final PluginTool tool;
public class UndoAction extends AbstractUndoRedoAction {
public static final String SUBGROUP = "1Undo";
public UndoAction(ProgramManagerPlugin plugin, PluginTool tool) {
super(plugin, "Undo", true);
this.tool = tool;
setHelpLocation(new HelpLocation(ToolConstants.TOOL_HELP_TOPIC, "Undo"));
String[] menuPath = { ToolConstants.MENU_EDIT, "&Undo" };
Icon icon = ResourceManager.loadImage("images/undo.png");
MenuData menuData = new MenuData(menuPath, icon, "Undo");
menuData.setMenuSubGroup("1Undo"); // make this appear above the redo menu item
setMenuBarData(menuData);
setToolBarData(new ToolBarData(icon, "Undo"));
setDescription("Undo");
setKeyBindingData(new KeyBindingData("ctrl Z"));
super(tool, plugin, "Undo", "images/undo.png", "ctrl Z", SUBGROUP);
}
@Override
protected void actionPerformed(Program program) {
try {
saveCurrentLocationToHistory();
program.undo();
}
catch (IOException e) {
Msg.showError(this, null, null, null, e);
}
}
private void saveCurrentLocationToHistory() {
GoToService goToService = tool.getService(GoToService.class);
NavigationHistoryService historyService = tool.getService(NavigationHistoryService.class);
if (goToService != null && historyService != null) {
historyService.addNewLocation(goToService.getDefaultNavigatable());
}
protected void actionPerformed(Program program) throws IOException {
program.undo();
}
@Override
protected void programChanged(Program program) {
updateActionMenuName(program);
}
void updateActionMenuName() {
updateActionMenuName(lastContextProgram);
}
void updateActionMenuName(Program program) {
String actionName = "Undo " + (program == null ? "" : program.getDomainFile().getName());
String description = actionName;
if (program != null && program.canUndo()) {
description = HTMLUtilities
.toWrappedHTML("Undo " + HTMLUtilities.escapeHTML(program.getUndoName()));
}
getMenuBarData().setMenuItemName(actionName);
setDescription(description);
}
@Override
protected boolean isEnabledForContext(Program program) {
protected boolean canPerformAction(Program program) {
return program != null && program.canUndo();
}
@Override
protected String getUndoRedoDescription(Program program) {
return program.getUndoName();
}
}