mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-23 12:08:33 +08:00
GP-1441 fixing undo/redo action for debugger dynamic programs
This commit is contained in:
+9
-12
@@ -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.
|
||||
|
||||
+185
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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));
|
||||
|
||||
+9
-1
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
-6
@@ -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);
|
||||
}
|
||||
|
||||
+1
-1
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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));
|
||||
|
||||
+1
-1
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user