GP-508 fixing program actions enablement with regard to debugger program context not working

This commit is contained in:
ghidravore 2021-10-28 17:51:27 -04:00
parent 569cbc61fc
commit aee233c4d2
9 changed files with 460 additions and 249 deletions

View file

@ -0,0 +1,99 @@
/* ###
* 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 docking.ActionContext;
import docking.action.DockingAction;
import ghidra.app.context.ProgramActionContext;
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.
*/
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) {
super(name, plugin.getName());
this.plugin = plugin;
this.requiresManagedProgram = requiresManagedProgram;
addToWindowWhen(ProgramActionContext.class);
}
@Override
public boolean isValidContext(ActionContext context) {
Program program = getProgram(context);
if (program != lastContextProgram) {
lastContextProgram = program;
programChanged(program);
}
return true;
}
@Override
public final boolean isEnabledForContext(ActionContext context) {
return isEnabledForContext(getProgram(context));
}
protected boolean isEnabledForContext(Program program) {
return program != null;
}
@Override
public void actionPerformed(ActionContext context) {
Program program = getProgram(context);
if (program != null) {
actionPerformed(program);
}
}
protected abstract void actionPerformed(Program program);
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.
* @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) {
return program;
}
// otherwise, just return the global current program.
}
return plugin.getCurrentProgram();
}
}

View file

@ -0,0 +1,56 @@
/* ###
* 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 docking.action.KeyBindingData;
import docking.action.MenuData;
import docking.tool.ToolConstants;
import ghidra.program.model.listing.Program;
import ghidra.util.HTMLUtilities;
/**
* Action class for the "Close Program" action
*/
public class CloseProgramAction extends AbstractProgramNameSwitchingAction {
public CloseProgramAction(ProgramManagerPlugin plugin, String group, int subGroup) {
super(plugin, "Close File", true);
MenuData menuData = new MenuData(new String[] { ToolConstants.MENU_FILE, "&Close" });
menuData.setMenuGroup(group);
menuData.setMenuSubGroup(Integer.toString(subGroup));
setMenuBarData(menuData);
setKeyBindingData(new KeyBindingData("ctrl o"));
}
@Override
protected void programChanged(Program program) {
if (program == null) {
getMenuBarData().setMenuItemName("&Close");
setDescription("Close Program");
}
else {
String programName = "'" + program.getDomainFile().getName() + "'";
getMenuBarData().setMenuItemName("&Close " + programName);
setDescription("<html>Close " + HTMLUtilities.escapeHTML(programName));
}
}
@Override
public void actionPerformed(Program program) {
plugin.closeProgram(program, false);
}
}

View file

@ -64,7 +64,7 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
if (tool == null) {
return; // we have been disposed
}
plugin.updateProgramActions();
plugin.undoStackChanged();
};
}
@ -501,4 +501,32 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
int openVersion = openFile.isReadOnly() ? openFile.getVersion() : -1;
return version == openVersion;
}
/**
* Returns true if this ProgramManager is managing the given program
* @param program the program to check
* @return true if this ProgramManager is managing the given programs
*/
public boolean hasProgram(Program program) {
return programMap.containsKey(program);
}
/**
* Returns true if there is at least one program that has unsaved changes.
* @return true if there is at least one program that has unsaved changes.
*/
public boolean hasUnsavedPrograms() {
// first check the current program as that is the one most likely to have changes
Program currentProgram = getCurrentProgram();
if (currentProgram != null && currentProgram.isChanged()) {
return true;
}
// look at all the open programs to see if any have changes
for (ProgramInfo programInfo : openProgramList) {
if (programInfo.program.isChanged()) {
return true;
}
}
return false;
}
}

View file

@ -81,17 +81,9 @@ import ghidra.util.task.TaskLauncher;
public class ProgramManagerPlugin extends Plugin implements ProgramManager {
private static final String SAVE_GROUP = "DomainObjectSave";
private static final String OPEN_GROUP = "DomainObjectOpen";
static final String OPEN_GROUP = "DomainObjectOpen";
private MultiProgramManager programMgr;
private ProgramSaveManager programSaveMgr;
private DockingAction openAction;
private DockingAction saveAllAction;
private DockingAction closeAction;
private DockingAction saveAction;
private DockingAction saveAsAction;
private DockingAction optionsAction;
private DockingAction closeOthersAction;
private DockingAction closeAllAction;
private int transactionID = -1;
private OpenVersionedFileDialog openDialog;
private boolean locked = false;
@ -174,11 +166,11 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
Program openProgram = programMgr.getOpenProgram(ghidraURL);
if (openProgram != null) {
programMgr.addProgram(openProgram, GhidraURL.getNormalizedURL(ghidraURL), openState);
updateActions();
if (openState == ProgramManager.OPEN_CURRENT) {
gotoProgramRef(openProgram, ghidraURL.getRef());
programMgr.saveLocation();
}
contextChanged();
return openProgram;
}
@ -215,7 +207,7 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
}
programMgr.addProgram(openProgram, GhidraURL.getNormalizedURL(ghidraURL), openState);
updateActions();
contextChanged();
openProgram.release(this);
if (openState == ProgramManager.OPEN_CURRENT) {
gotoProgramRef(openProgram, ghidraURL.getRef());
@ -310,7 +302,7 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
Program program = Swing.runNow(() -> {
Program p = doOpenProgram(domainFile, version, state);
updateActions();
contextChanged();
return p;
});
@ -347,16 +339,16 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
}
@Override
public boolean closeOtherPrograms(final boolean ignoreChanges) {
final Program[] otherPrograms = programMgr.getOtherPrograms();
public boolean closeOtherPrograms(boolean ignoreChanges) {
Program[] otherPrograms = programMgr.getOtherPrograms();
Runnable r = () -> doCloseAllPrograms(otherPrograms, ignoreChanges);
SystemUtilities.runSwingNow(r);
return programMgr.isEmpty();
}
@Override
public boolean closeAllPrograms(final boolean ignoreChanges) {
final Program[] openPrograms = programMgr.getAllPrograms();
public boolean closeAllPrograms(boolean ignoreChanges) {
Program[] openPrograms = programMgr.getAllPrograms();
Runnable r = () -> doCloseAllPrograms(openPrograms, ignoreChanges);
SystemUtilities.runSwingNow(r);
return programMgr.isEmpty();
@ -396,11 +388,11 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
for (Program program : toRemove) {
programMgr.removeProgram(program);
}
updateActions();
contextChanged();
}
@Override
public boolean closeProgram(final Program program, final boolean ignoreChanges) {
public boolean closeProgram(Program program, boolean ignoreChanges) {
if (program == null) {
return false;
}
@ -411,7 +403,7 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
if (ignoreChanges || program.isClosed() || programMgr.isPersistent(program) ||
(tool.canCloseDomainObject(program) && programSaveMgr.canClose(program))) {
programMgr.removeProgram(program);
updateActions();
contextChanged();
}
};
SystemUtilities.runSwingNow(r);
@ -434,16 +426,16 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
if (currentProgram != null) {
programMgr.removeProgram(currentProgram);
}
updateActions();
contextChanged();
tool.setSubTitle("");
tool.clearLastEvents();
}
@Override
public void setCurrentProgram(final Program p) {
public void setCurrentProgram(Program p) {
Runnable r = () -> {
programMgr.setCurrentProgram(p);
updateActions();
contextChanged();
};
SystemUtilities.runSwingNow(r);
}
@ -483,7 +475,7 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
if (state == ProgramManager.OPEN_CURRENT) {
programMgr.saveLocation();
}
updateActions();
contextChanged();
};
SystemUtilities.runSwingNow(r);
}
@ -496,7 +488,7 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
@Override
protected boolean saveData() {
boolean result = programSaveMgr.canCloseAll();
updateActions();
contextChanged();
return result;
}
@ -513,100 +505,57 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
private void createActions() {
int subMenuGroupOrder = 1;
int subMenuGroup = 1;
openAction = new ActionBuilder("Open File", getName())
DockingAction openAction = new ActionBuilder("Open File", getName())
.menuPath(ToolConstants.MENU_FILE, "&Open...")
.menuGroup(OPEN_GROUP, Integer.toString(subMenuGroupOrder++))
.menuGroup(OPEN_GROUP, Integer.toString(subMenuGroup++))
.keyBinding("ctrl O")
.enabledWhen(c -> !locked)
.onAction(c -> open())
.buildAndInstall(tool);
// .withContext(ProgramActionContext.class)
// .inWindow(ActionBuilder.When.CONTEXT_MATCHES)
// openAction doesn't really use a context, but we want it to be in windows that
// have providers that use programs.
openAction.addToWindowWhen(ProgramActionContext.class);
closeAction = new ActionBuilder("Close File", getName())
.menuPath(ToolConstants.MENU_FILE, "&Close")
.menuGroup(OPEN_GROUP, Integer.toString(subMenuGroupOrder++))
.withContext(ProgramActionContext.class)
.supportsDefaultToolContext(true)
.inWindow(ActionBuilder.When.CONTEXT_MATCHES)
.onAction(c -> closeProgram(c.getProgram(), false))
.keyBinding("ctrl W")
.buildAndInstall(tool);
closeOthersAction = new ActionBuilder("Close Others", getName())
tool.addAction(new CloseProgramAction(this, OPEN_GROUP, subMenuGroup++));
new ActionBuilder("Close Others", getName())
.menuPath(ToolConstants.MENU_FILE, "Close &Others")
.menuGroup(OPEN_GROUP, Integer.toString(subMenuGroupOrder++))
.menuGroup(OPEN_GROUP, Integer.toString(subMenuGroup++))
.enabled(false)
.withContext(ProgramActionContext.class)
.inWindow(ActionBuilder.When.CONTEXT_MATCHES)
.enabledWhen(c -> programMgr.hasProgram(c.getProgram()))
.onAction(c -> closeOtherPrograms(false))
.buildAndInstall(tool);
closeAllAction = new ActionBuilder("Close All", getName())
DockingAction closeAllAction = new ActionBuilder("Close All", getName())
.menuPath(ToolConstants.MENU_FILE, "Close &All")
.menuGroup(OPEN_GROUP, Integer.toString(subMenuGroupOrder++))
.withContext(ProgramActionContext.class)
.inWindow(ActionBuilder.When.CONTEXT_MATCHES)
.menuGroup(OPEN_GROUP, Integer.toString(subMenuGroup++))
.enabledWhen(c -> !programMgr.isEmpty())
.onAction(c -> closeAllPrograms(false))
.enabled(false)
.buildAndInstall(tool);
closeAllAction.addToWindowWhen(ProgramActionContext.class);
saveAction = new ActionBuilder("Save File", getName())
.menuPath(ToolConstants.MENU_FILE, "Save File")
.description("Save Program")
.menuGroup(SAVE_GROUP, Integer.toString(subMenuGroupOrder++))
.menuIcon(null)
.toolBarIcon("images/disk.png")
.toolBarGroup(ToolConstants.TOOLBAR_GROUP_ONE)
.keyBinding("ctrl S")
.withContext(ProgramActionContext.class)
.inWindow(ActionBuilder.When.CONTEXT_MATCHES)
.supportsDefaultToolContext(true)
.enabledWhen(c -> c.getProgram() != null && c.getProgram().isChanged())
.onAction(c -> programSaveMgr.saveProgram(c.getProgram()))
.buildAndInstall(tool);
tool.addAction(new SaveProgramAction(this, SAVE_GROUP, subMenuGroup));
tool.addAction(new SaveAsProgramAction(this, SAVE_GROUP, subMenuGroup));
saveAsAction = new ActionBuilder("Save As File", getName())
.menuPath(ToolConstants.MENU_FILE, "Save &As...")
.menuGroup(SAVE_GROUP, Integer.toString(subMenuGroupOrder++))
.withContext(ProgramActionContext.class)
.inWindow(ActionBuilder.When.CONTEXT_MATCHES)
.supportsDefaultToolContext(true)
.onAction(c -> programSaveMgr.saveAs(c.getProgram()))
.buildAndInstall(tool);
saveAllAction = new ActionBuilder("Save All Files", getName())
DockingAction saveAllAction = new ActionBuilder("Save All Files", getName())
.menuPath(ToolConstants.MENU_FILE, "Save All")
.description("Save All Programs")
.menuGroup(SAVE_GROUP, Integer.toString(subMenuGroupOrder++))
.withContext(ProgramActionContext.class)
.inWindow(ActionBuilder.When.CONTEXT_MATCHES)
.menuGroup(SAVE_GROUP, Integer.toString(subMenuGroup++))
.enabledWhen(c -> programMgr.hasUnsavedPrograms())
.onAction(c -> programSaveMgr.saveChangedPrograms())
.buildAndInstall(tool);
saveAllAction.addToWindowWhen(ProgramActionContext.class);
optionsAction = new ActionBuilder("Program Options", getName())
.menuPath(ToolConstants.MENU_EDIT, "P&rogram Options...")
.description("Edit Options for current program")
.menuGroup(ToolConstants.TOOL_OPTIONS_MENU_GROUP,
ToolConstants.TOOL_OPTIONS_MENU_GROUP + "b")
.withContext(ProgramActionContext.class)
.inWindow(ActionBuilder.When.CONTEXT_MATCHES)
.supportsDefaultToolContext(true)
.onAction(c -> showProgramOptions(c.getProgram()))
.buildAndInstall(tool);
tool.addAction(new ProgramOptionsAction(this));
undoAction = new UndoAction(tool, getName());
redoAction = new RedoAction(tool, getName());
undoAction = new UndoAction(this, tool);
redoAction = new RedoAction(this, tool);
tool.addAction(undoAction);
tool.addAction(redoAction);
}
private void showProgramOptions(final Program currentProgram) {
void showProgramOptions(final Program currentProgram) {
List<String> names = currentProgram.getOptionsNames();
Options[] options = new Options[names.size()];
for (int i = 0; i < names.size(); i++) {
@ -664,64 +613,14 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
}
}
private void updateActions() {
Program p = programMgr.getCurrentProgram();
updateCloseAction(p);
updateProgramOptionsAction(p);
updateProgramActions();
closeAllAction.setEnabled(p != null);
optionsAction.setEnabled(p != null);
Program[] programList = programMgr.getAllPrograms();
closeOthersAction.setEnabled(programList.length > 1);
void undoStackChanged() {
undoAction.updateActionMenuName();
redoAction.updateActionMenuName();
tool.contextChanged(null);
}
private void updateSaveAction(Program p) {
if (p == null) {
saveAction.getMenuBarData().setMenuItemName("&Save");
saveAction.setDescription("Save Program");
saveAction.setEnabled(false);
}
else {
String programName = "'" + p.getDomainFile().getName() + "'";
saveAction.getMenuBarData().setMenuItemName("&Save " + programName);
saveAction.setDescription("Save " + programName);
saveAction.setEnabled(p.isChanged());
}
}
private void updateSaveAsAction(Program p) {
if (p == null) {
saveAsAction.getMenuBarData().setMenuItemName("Save &As...");
}
else {
String programName = "'" + p.getDomainFile().getName() + "'";
saveAsAction.getMenuBarData().setMenuItemName("Save " + programName + " &As...");
}
}
private void updateProgramOptionsAction(Program p) {
if (p == null) {
optionsAction.getMenuBarData().setMenuItemName("Program Options");
}
else {
String programName = "'" + p.getDomainFile().getName() + "'";
optionsAction.getMenuBarData().setMenuItemName("Options for " + programName + "...");
}
optionsAction.setEnabled(p != null);
}
private void updateCloseAction(Program p) {
if (p == null) {
closeAction.getMenuBarData().setMenuItemName("&Close");
closeAction.setDescription("Close Program");
}
else {
String programName = "'" + p.getDomainFile().getName() + "'";
closeAction.getMenuBarData().setMenuItemName("&Close " + programName);
closeAction.setDescription("<html>Close " + HTMLUtilities.escapeHTML(programName));
}
closeAction.setEnabled(p != null);
void contextChanged() {
tool.contextChanged(null);
}
private void open() {
@ -746,7 +645,7 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
openDialog.addOkActionListener(listener);
}
tool.showDialog(openDialog);
updateActions();
contextChanged();
}
public void openPrograms(List<DomainFile> filesToOpen) {
@ -792,27 +691,6 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
return openProgram;
}
void updateProgramActions() {
updateSaveAllAction();
Program p = getCurrentProgram();
updateSaveAction(getCurrentProgram());
updateSaveAsAction(getCurrentProgram());
undoAction.update(p);
redoAction.update(p);
}
private void updateSaveAllAction() {
boolean saveAllEnable = false;
Program[] programList = programMgr.getAllPrograms();
for (Program element : programList) {
if (element.isChanged()) {
saveAllEnable = true;
break;
}
}
saveAllAction.setEnabled(saveAllEnable);
}
/**
* Write out my data state.
*/
@ -870,7 +748,7 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
programMgr.setCurrentProgram(programs[0]);
}
}
updateActions();
contextChanged();
}
@Override
@ -1162,7 +1040,19 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
@Override
public void lockDown(boolean state) {
locked = state;
openAction.setEnabled(!state);
contextChanged();
}
public boolean isManaged(Program program) {
return programMgr.contains(program);
}
public void saveProgram(Program program) {
programSaveMgr.saveProgram(program);
}
public void saveProgramAs(Program program) {
programSaveMgr.saveAs(program);
}
}

View file

@ -0,0 +1,52 @@
/* ###
* 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 docking.action.MenuData;
import docking.tool.ToolConstants;
import ghidra.program.model.listing.Program;
/**
* Action class for the "Edit Program Options" action
*/
public class ProgramOptionsAction extends AbstractProgramNameSwitchingAction {
public ProgramOptionsAction(ProgramManagerPlugin plugin) {
super(plugin, "Program Options", true);
MenuData menuData =
new MenuData(new String[] { ToolConstants.MENU_EDIT, "P&rogram Options..." });
menuData.setMenuGroup(ToolConstants.TOOL_OPTIONS_MENU_GROUP);
menuData.setMenuSubGroup(ToolConstants.TOOL_OPTIONS_MENU_GROUP + "b");
setMenuBarData(menuData);
}
@Override
protected void programChanged(Program program) {
if (program == null) {
getMenuBarData().setMenuItemName("Program Options");
}
else {
String programName = "'" + program.getDomainFile().getName() + "'";
getMenuBarData().setMenuItemName("Options for " + programName + "...");
}
}
@Override
public void actionPerformed(Program program) {
plugin.showProgramOptions(program);
}
}

View file

@ -21,8 +21,6 @@ import javax.swing.Icon;
import docking.action.*;
import docking.tool.ToolConstants;
import ghidra.app.context.ProgramActionContext;
import ghidra.app.context.ProgramContextAction;
import ghidra.app.services.GoToService;
import ghidra.app.services.NavigationHistoryService;
import ghidra.framework.plugintool.PluginTool;
@ -30,11 +28,14 @@ import ghidra.program.model.listing.Program;
import ghidra.util.*;
import resources.ResourceManager;
public class RedoAction extends ProgramContextAction {
/**
* Action class for the "redo" action
*/
public class RedoAction extends AbstractProgramNameSwitchingAction {
private final PluginTool tool;
public RedoAction(PluginTool tool, String owner) {
super("Redo", owner);
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" };
@ -46,15 +47,10 @@ public class RedoAction extends ProgramContextAction {
setToolBarData(new ToolBarData(icon, group));
setKeyBindingData(new KeyBindingData("ctrl shift Z"));
setDescription("Redo");
setSupportsDefaultToolContext(true);
// we want this action to appear in all windows that can produce a program context
addToWindowWhen(ProgramActionContext.class);
}
@Override
protected void actionPerformed(ProgramActionContext programContext) {
Program program = programContext.getProgram();
protected void actionPerformed(Program program) {
try {
saveCurrentLocationToHistory();
program.redo();
@ -64,41 +60,30 @@ public class RedoAction extends ProgramContextAction {
}
}
/**
* updates the menu name of the action as the undo stack changes
* <P>
* NOTE: currently, we must manage the enablement explicitly
* because contextChanged is not called for data changes. Ideally, the enablement
* would be handled by the context, but for now it doesn't work
*
* @param program the program
*/
public void update(Program program) {
void updateActionMenuName() {
updateActionMenuName(lastContextProgram);
}
if (program == null) {
getMenuBarData().setMenuItemName("Redo ");
setDescription("");
setEnabled(false);
}
else if (program.canRedo()) {
String programName = program.getDomainFile().getName();
getMenuBarData().setMenuItemName("Redo " + programName);
String tip = HTMLUtilities.toWrappedHTML(
"Redo " + HTMLUtilities.escapeHTML(program.getRedoName()));
setDescription(tip);
setEnabled(true);
}
else {
setDescription("Redo");
setEnabled(false);
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);
}
@Override
protected boolean isEnabledForContext(ProgramActionContext context) {
Program program = context.getProgram();
return program.canRedo();
protected boolean isEnabledForContext(Program program) {
return program != null && program.canRedo();
}
private void saveCurrentLocationToHistory() {
@ -108,5 +93,4 @@ public class RedoAction extends ProgramContextAction {
historyService.addNewLocation(goToService.getDefaultNavigatable());
}
}
}

View file

@ -0,0 +1,51 @@
/* ###
* 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 docking.action.MenuData;
import docking.tool.ToolConstants;
import ghidra.program.model.listing.Program;
/**
* Action class for the "Save As" action
*/
public class SaveAsProgramAction extends AbstractProgramNameSwitchingAction {
public SaveAsProgramAction(ProgramManagerPlugin plugin, String group, int subGroup) {
super(plugin, "Save As File", true);
MenuData menuData = new MenuData(new String[] { ToolConstants.MENU_FILE, "Save &As..." });
menuData.setMenuGroup(group);
menuData.setMenuSubGroup(Integer.toString(subGroup));
setMenuBarData(menuData);
}
@Override
protected void programChanged(Program program) {
if (program == null) {
getMenuBarData().setMenuItemName("Save &As...");
}
else {
String programName = "'" + program.getDomainFile().getName() + "'";
getMenuBarData().setMenuItemName("Save " + programName + " &As...");
}
}
@Override
public void actionPerformed(Program program) {
plugin.saveProgramAs(program);
}
}

View file

@ -0,0 +1,64 @@
/* ###
* 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 javax.swing.ImageIcon;
import docking.action.*;
import docking.tool.ToolConstants;
import ghidra.program.model.listing.Program;
import resources.ResourceManager;
/**
* Action class for the "Save Program" action
*/
public class SaveProgramAction extends AbstractProgramNameSwitchingAction {
public SaveProgramAction(ProgramManagerPlugin plugin, String group, int subGroup) {
super(plugin, "Save File", true);
MenuData menuData = new MenuData(new String[] { ToolConstants.MENU_FILE, "Save File" });
menuData.setMenuGroup(group);
menuData.setMenuSubGroup(Integer.toString(subGroup));
setMenuBarData(menuData);
ImageIcon icon = ResourceManager.loadImage("images/disk.png");
setToolBarData(new ToolBarData(icon, ToolConstants.TOOLBAR_GROUP_ONE));
setKeyBindingData(new KeyBindingData("ctrl S"));
}
@Override
protected void programChanged(Program program) {
if (program == null) {
getMenuBarData().setMenuItemName("&Save");
setDescription("Save Program");
}
else {
String programName = "'" + program.getDomainFile().getName() + "'";
getMenuBarData().setMenuItemName("&Save " + programName);
setDescription("Save " + programName);
}
}
@Override
public boolean isEnabledForContext(Program program) {
return program != null && program.isChanged();
}
@Override
public void actionPerformed(Program program) {
plugin.saveProgram(program);
}
}

View file

@ -21,8 +21,6 @@ import javax.swing.Icon;
import docking.action.*;
import docking.tool.ToolConstants;
import ghidra.app.context.ProgramActionContext;
import ghidra.app.context.ProgramContextAction;
import ghidra.app.services.GoToService;
import ghidra.app.services.NavigationHistoryService;
import ghidra.framework.plugintool.PluginTool;
@ -30,11 +28,14 @@ import ghidra.program.model.listing.Program;
import ghidra.util.*;
import resources.ResourceManager;
public class UndoAction extends ProgramContextAction {
/**
* Action class for the "Undo" action
*/
public class UndoAction extends AbstractProgramNameSwitchingAction {
private final PluginTool tool;
public UndoAction(PluginTool tool, String owner) {
super("Undo", owner);
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" };
@ -45,15 +46,10 @@ public class UndoAction extends ProgramContextAction {
setToolBarData(new ToolBarData(icon, "Undo"));
setDescription("Undo");
setKeyBindingData(new KeyBindingData("ctrl Z"));
setSupportsDefaultToolContext(true);
// we want this action to appear in all windows that can produce a program context
addToWindowWhen(ProgramActionContext.class);
}
@Override
protected void actionPerformed(ProgramActionContext programContext) {
Program program = programContext.getProgram();
protected void actionPerformed(Program program) {
try {
saveCurrentLocationToHistory();
program.undo();
@ -71,39 +67,30 @@ public class UndoAction extends ProgramContextAction {
}
}
/**
* updates the menu name of the action as the undo stack changes
* <P>
* NOTE: currently, we must manage the enablement explicitly
* because contextChanged is not called for data changes. Ideally, the enablement
* would be handled by the context, but for now it doesn't work
*
* @param program the program
*/
public void update(Program program) {
@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) {
getMenuBarData().setMenuItemName("Undo ");
setDescription("");
setEnabled(false);
}
if (program != null && program.canUndo()) {
String programName = program.getDomainFile().getName();
getMenuBarData().setMenuItemName("Undo " + programName);
String tip = HTMLUtilities.toWrappedHTML(
"Undo " + HTMLUtilities.escapeHTML(program.getUndoName()));
setDescription(tip);
setEnabled(true);
}
else {
setDescription("Undo");
setEnabled(false);
description = HTMLUtilities
.toWrappedHTML("Undo " + HTMLUtilities.escapeHTML(program.getUndoName()));
}
getMenuBarData().setMenuItemName(actionName);
setDescription(description);
}
@Override
protected boolean isEnabledForContext(ProgramActionContext context) {
Program program = context.getProgram();
return program.canUndo();
protected boolean isEnabledForContext(Program program) {
return program != null && program.canUndo();
}
}