diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/AbstractProgramNameSwitchingAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/AbstractProgramNameSwitchingAction.java new file mode 100644 index 0000000000..ccd11cdded --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/AbstractProgramNameSwitchingAction.java @@ -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(); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/CloseProgramAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/CloseProgramAction.java new file mode 100644 index 0000000000..516fe75c0c --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/CloseProgramAction.java @@ -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("Close " + HTMLUtilities.escapeHTML(programName)); + } + } + + @Override + public void actionPerformed(Program program) { + plugin.closeProgram(program, false); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiProgramManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiProgramManager.java index 933e14ca9b..6987a738b8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiProgramManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiProgramManager.java @@ -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; + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramManagerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramManagerPlugin.java index 83f4a9b2cc..8dbb5dbc12 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramManagerPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramManagerPlugin.java @@ -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 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("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 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); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramOptionsAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramOptionsAction.java new file mode 100644 index 0000000000..d22f4b4e29 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramOptionsAction.java @@ -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); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/RedoAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/RedoAction.java index 707ae1448c..8f779906c6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/RedoAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/RedoAction.java @@ -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 - *

- * 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()); } } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/SaveAsProgramAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/SaveAsProgramAction.java new file mode 100644 index 0000000000..aa6fbeb0d3 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/SaveAsProgramAction.java @@ -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); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/SaveProgramAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/SaveProgramAction.java new file mode 100644 index 0000000000..11978c8153 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/SaveProgramAction.java @@ -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); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/UndoAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/UndoAction.java index 777fc870e2..3886c418ed 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/UndoAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/UndoAction.java @@ -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 - *

- * 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(); } }