diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramView.java
index 5ae5ada266..32f694f636 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramView.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramView.java
@@ -188,8 +188,8 @@ public class DBTraceProgramView implements TraceProgramView {
}
private void bookmarkTypeAdded(TraceBookmarkType type) {
- fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_BOOKMARK_TYPE_ADDED,
- null, null, type, null, null));
+ fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_BOOKMARK_TYPE_ADDED, null,
+ null, type, null, null));
}
private void bookmarkAdded(TraceAddressSpace space, TraceBookmark bm) {
@@ -248,14 +248,14 @@ public class DBTraceProgramView implements TraceProgramView {
}
private void categoryAdded(long id, Category oldIsNull, Category added) {
- fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_CATEGORY_ADDED, null,
- null, null, oldIsNull, added));
+ fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_CATEGORY_ADDED, null, null,
+ null, oldIsNull, added));
}
private void categoryMoved(long id, CategoryPath oldPath, CategoryPath newPath) {
Category category = getDataTypeManager().getCategory(id);
- fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_CATEGORY_MOVED, null,
- null, null, oldPath, category));
+ fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_CATEGORY_MOVED, null, null,
+ null, oldPath, category));
}
private void categoryRenamed(long id, String oldName, String newName) {
@@ -315,8 +315,8 @@ public class DBTraceProgramView implements TraceProgramView {
protected void fireCodeRemoved(DomainObjectEventQueues queues, Address min, Address max,
TraceCodeUnit removed) {
- queues.fireEvent(new ProgramChangeRecord(ChangeManager.DOCR_CODE_REMOVED,
- min, max, null, removed, null));
+ queues.fireEvent(new ProgramChangeRecord(ChangeManager.DOCR_CODE_REMOVED, min, max,
+ null, removed, null));
}
private void codeFragmentChanged(TraceAddressSpace space, TraceAddressSnapRange range,
@@ -352,14 +352,13 @@ public class DBTraceProgramView implements TraceProgramView {
}
private void commentChanged(int docrType, TraceAddressSpace space,
- TraceAddressSnapRange range,
- String oldValue, String newValue) {
+ TraceAddressSnapRange range, String oldValue, String newValue) {
DomainObjectEventQueues queues = isVisible(space, range);
if (queues == null) {
return;
}
- queues.fireEvent(new ProgramChangeRecord(docrType,
- range.getX1(), range.getX2(), null, oldValue, newValue));
+ queues.fireEvent(new ProgramChangeRecord(docrType, range.getX1(), range.getX2(), null,
+ oldValue, newValue));
}
private void commentEolChanged(TraceAddressSpace space, TraceAddressSnapRange range,
@@ -438,14 +437,14 @@ public class DBTraceProgramView implements TraceProgramView {
}
private void dataTypeChanged(long id, DataType oldIsNull, DataType changed) {
- fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_DATA_TYPE_CHANGED,
- null, null, null, oldIsNull, changed));
+ fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_DATA_TYPE_CHANGED, null,
+ null, null, oldIsNull, changed));
}
private void dataTypeReplaced(long id, DataTypePath oldPath, DataTypePath newPath) {
DataType newType = getDataTypeManager().getDataType(id);
- fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_DATA_TYPE_REPLACED,
- null, null, null, newPath, newType));
+ fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_DATA_TYPE_REPLACED, null,
+ null, null, newPath, newType));
}
private void dataTypeMoved(long id, DataTypePath oldPath, DataTypePath newPath) {
@@ -457,13 +456,13 @@ public class DBTraceProgramView implements TraceProgramView {
private void dataTypeRenamed(long id, String oldName, String newName) {
DataType dataType = getDataTypeManager().getDataType(id);
- fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_DATA_TYPE_RENAMED,
- null, null, null, oldName, dataType));
+ fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_DATA_TYPE_RENAMED, null,
+ null, null, oldName, dataType));
}
private void dataTypeDeleted(long id, DataTypePath oldPath, DataTypePath newIsNull) {
- fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_DATA_TYPE_REMOVED,
- null, null, null, oldPath, newIsNull));
+ fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_DATA_TYPE_REMOVED, null,
+ null, null, oldPath, newIsNull));
}
private void gatherThunksTo(Collection into,
@@ -553,18 +552,18 @@ public class DBTraceProgramView implements TraceProgramView {
}
private void functionTagAdded(FunctionTag tag) {
- fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_FUNCTION_TAG_CREATED,
- null, null, tag, null, null));
+ fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_FUNCTION_TAG_CREATED, null,
+ null, tag, null, null));
}
private void functionTagChanged(FunctionTag tag) {
- fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_FUNCTION_TAG_CHANGED,
- null, null, tag, null, null));
+ fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_FUNCTION_TAG_CHANGED, null,
+ null, tag, null, null));
}
private void functionTagDeleted(FunctionTag tag) {
- fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_FUNCTION_TAG_DELETED,
- null, null, tag, null, null));
+ fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_FUNCTION_TAG_DELETED, null,
+ null, tag, null, null));
}
private void instructionFlowOverrideChanged(TraceAddressSpace space,
@@ -629,19 +628,19 @@ public class DBTraceProgramView implements TraceProgramView {
boolean inOld = isRegionVisible(region, oldSpan);
boolean inNew = isRegionVisible(region, newSpan);
if (inOld && !inNew) {
- eventQueues.fireEvent(
- new ProgramChangeRecord(ChangeManager.DOCR_MEMORY_BLOCK_REMOVED,
- region.getMinAddress(), region.getMaxAddress(), null, null, null));
+ eventQueues
+ .fireEvent(new ProgramChangeRecord(ChangeManager.DOCR_MEMORY_BLOCK_REMOVED,
+ region.getMinAddress(), region.getMaxAddress(), null, null, null));
// NOTE: MemoryMapDB does this, too. Otherwise, CodeBrowserPlugin does not hear.
- eventQueues.fireEvent(
- new DomainObjectChangeRecord(DomainObject.DO_OBJECT_RESTORED));
+ eventQueues
+ .fireEvent(new DomainObjectChangeRecord(DomainObject.DO_OBJECT_RESTORED));
}
if (!inOld && inNew) {
eventQueues.fireEvent(new ProgramChangeRecord(ChangeManager.DOCR_MEMORY_BLOCK_ADDED,
region.getMinAddress(), region.getMaxAddress(), null, null, null));
// NOTE: MemoryMapDB does this, too. Otherwise, CodeBrowserPlugin does not hear.
- eventQueues.fireEvent(
- new DomainObjectChangeRecord(DomainObject.DO_OBJECT_RESTORED));
+ eventQueues
+ .fireEvent(new DomainObjectChangeRecord(DomainObject.DO_OBJECT_RESTORED));
}
}
@@ -659,8 +658,8 @@ public class DBTraceProgramView implements TraceProgramView {
}
private void sourceArchiveAdded(UniversalID id) {
- fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_SOURCE_ARCHIVE_ADDED,
- null, null, id, null, null));
+ fireEventAllViews(new ProgramChangeRecord(ChangeManager.DOCR_SOURCE_ARCHIVE_ADDED, null,
+ null, id, null, null));
}
private void sourceArchiveChanged(UniversalID id) {
@@ -764,9 +763,8 @@ public class DBTraceProgramView implements TraceProgramView {
return;
}
// Strange. This is fired as if by the reference rather than the symbol
- queues.fireEvent(
- new ProgramChangeRecord(ChangeManager.DOCR_SYMBOL_ASSOCIATION_ADDED,
- newRef.getFromAddress(), newRef.getFromAddress(), newRef, null, symbol));
+ queues.fireEvent(new ProgramChangeRecord(ChangeManager.DOCR_SYMBOL_ASSOCIATION_ADDED,
+ newRef.getFromAddress(), newRef.getFromAddress(), newRef, null, symbol));
}
private void symbolAssociationRemoved(TraceAddressSpace space, TraceSymbol symbol,
@@ -803,9 +801,9 @@ public class DBTraceProgramView implements TraceProgramView {
fireSymbolRemoved(queues, symbol);
if (symbol instanceof TraceFunctionSymbol) {
TraceFunctionSymbol function = (TraceFunctionSymbol) symbol;
- queues.fireEvent(new ProgramChangeRecord(
- ChangeManager.DOCR_FUNCTION_REMOVED, function.getEntryPoint(),
- function.getEntryPoint(), function, function.getBody(), null));
+ queues.fireEvent(new ProgramChangeRecord(ChangeManager.DOCR_FUNCTION_REMOVED,
+ function.getEntryPoint(), function.getEntryPoint(), function,
+ function.getBody(), null));
}
}
if (!inOld && inNew) {
@@ -834,9 +832,9 @@ public class DBTraceProgramView implements TraceProgramView {
}
protected void fireSymbolRemoved(DomainObjectEventQueues queues, TraceSymbol symbol) {
- queues.fireEvent(new ProgramChangeRecord(ChangeManager.DOCR_SYMBOL_REMOVED,
- symbol.getAddress(), symbol.getAddress(), symbol, symbol.getName(),
- symbol.getID()));
+ queues.fireEvent(
+ new ProgramChangeRecord(ChangeManager.DOCR_SYMBOL_REMOVED, symbol.getAddress(),
+ symbol.getAddress(), symbol, symbol.getName(), symbol.getID()));
}
}
@@ -917,8 +915,7 @@ public class DBTraceProgramView implements TraceProgramView {
this.viewport = trace.createTimeViewport();
this.viewport.setSnap(snap);
- this.eventQueues =
- new DomainObjectEventQueues(this, TIME_INTERVAL, trace.getLock());
+ this.eventQueues = new DomainObjectEventQueues(this, TIME_INTERVAL, trace.getLock());
this.regViewsByThread = new WeakValueHashMap<>();
@@ -1574,6 +1571,16 @@ public class DBTraceProgramView implements TraceProgramView {
return trace.getRedoName();
}
+ @Override
+ public List getAllUndoNames() {
+ return trace.getAllUndoNames();
+ }
+
+ @Override
+ public List getAllRedoNames() {
+ return trace.getAllRedoNames();
+ }
+
@Override
public void addTransactionListener(TransactionListener listener) {
trace.addTransactionListener(listener);
@@ -1605,8 +1612,8 @@ public class DBTraceProgramView implements TraceProgramView {
memory.updateChangeRegionBlockFlags(region);
}
- public void updateMemoryChangeRegionBlockRange(TraceMemoryRegion region,
- AddressRange oldRange, AddressRange newRange) {
+ public void updateMemoryChangeRegionBlockRange(TraceMemoryRegion region, AddressRange oldRange,
+ AddressRange newRange) {
if (!isRegionVisible(region)) {
return;
}
@@ -1706,8 +1713,7 @@ public class DBTraceProgramView implements TraceProgramView {
return RangeQueryOcclusion.super.occluded(cu, range, span);
}
byte[] memBytes = new byte[cu.getLength()];
- memSpace.getBytes(span.lmax(), cu.getMinAddress(),
- ByteBuffer.wrap(memBytes));
+ memSpace.getBytes(span.lmax(), cu.getMinAddress(), ByteBuffer.wrap(memBytes));
byte[] cuBytes;
try {
cuBytes = cu.getBytes();
@@ -1725,8 +1731,7 @@ public class DBTraceProgramView implements TraceProgramView {
@Override
public Iterable extends TraceCodeUnit> query(AddressRange range, Lifespan span) {
- return definedUnits == null
- ? Collections.emptyList()
+ return definedUnits == null ? Collections.emptyList()
: definedUnits.get(span.lmax(), range, true);
}
@@ -1779,8 +1784,7 @@ public class DBTraceProgramView implements TraceProgramView {
public Iterable extends TraceFunctionSymbol> query(AddressRange range,
Lifespan span) {
// NB. No functions in register space!
- return functions.getIntersecting(Lifespan.at(span.lmax()), null, range,
- false);
+ return functions.getIntersecting(Lifespan.at(span.lmax()), null, range, false);
}
public boolean itemOccludes(AddressRange range, TraceFunctionSymbol f) {
@@ -1796,8 +1800,7 @@ public class DBTraceProgramView implements TraceProgramView {
protected boolean isFunctionVisible(TraceFunctionSymbol function, Lifespan lifespan) {
AddressSetView body = function.getBody();
- AddressRange bodySpan =
- new AddressRangeImpl(body.getMinAddress(), body.getMaxAddress());
+ AddressRange bodySpan = new AddressRangeImpl(body.getMinAddress(), body.getMaxAddress());
return viewport.isCompletelyVisible(bodySpan, function.getLifespan(), function,
getFunctionOcclusion(function));
}
@@ -1823,8 +1826,7 @@ public class DBTraceProgramView implements TraceProgramView {
return true;
}
- protected DomainObjectEventQueues isSymbolVisible(TraceAddressSpace space,
- TraceSymbol symbol) {
+ protected DomainObjectEventQueues isSymbolVisible(TraceAddressSpace space, TraceSymbol symbol) {
// NB. Most symbols do not occlude each other
DomainObjectEventQueues queues = getEventQueues(space);
if (queues == null) {
@@ -1861,8 +1863,7 @@ public class DBTraceProgramView implements TraceProgramView {
protected Occlusion regionOcclusion = new RangeQueryOcclusion<>() {
@Override
public Iterable extends TraceMemoryRegion> query(AddressRange range, Lifespan span) {
- return trace.getMemoryManager()
- .getRegionsIntersecting(Lifespan.at(span.lmax()), range);
+ return trace.getMemoryManager().getRegionsIntersecting(Lifespan.at(span.lmax()), range);
}
@Override
@@ -1876,7 +1877,6 @@ public class DBTraceProgramView implements TraceProgramView {
}
protected boolean isRegionVisible(TraceMemoryRegion reg, Lifespan lifespan) {
- return viewport.isCompletelyVisible(reg.getRange(), lifespan, reg,
- regionOcclusion);
+ return viewport.isCompletelyVisible(reg.getRange(), lifespan, reg, regionOcclusion);
}
}
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegisters.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegisters.java
index a9ae222b60..69469b0279 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegisters.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegisters.java
@@ -613,6 +613,16 @@ public class DBTraceProgramViewRegisters implements TraceProgramView {
return view.getRedoName();
}
+ @Override
+ public List getAllUndoNames() {
+ return view.getAllUndoNames();
+ }
+
+ @Override
+ public List getAllRedoNames() {
+ return view.getAllRedoNames();
+ }
+
@Override
public void addTransactionListener(TransactionListener listener) {
view.addTransactionListener(listener);
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Tool/Undo_Redo.htm b/Ghidra/Features/Base/src/main/help/help/topics/Tool/Undo_Redo.htm
index 58bf49d20e..58b694b77a 100644
--- a/Ghidra/Features/Base/src/main/help/help/topics/Tool/Undo_Redo.htm
+++ b/Ghidra/Features/Base/src/main/help/help/topics/Tool/Undo_Redo.htm
@@ -36,6 +36,11 @@
hovering the mouse over the
button will display the name of the edit
operation that would be "redone".
+
Also, pressing the drop-down arrow will show a list
+ of all the undo/redo's that are available. Clicking on one of those will undo/redo that
+ item and all the ones above it.
+
+
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/AbstractUndoRedoAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/AbstractUndoRedoAction.java
index 7017969007..0b098c1c82 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/AbstractUndoRedoAction.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/AbstractUndoRedoAction.java
@@ -16,11 +16,14 @@
package ghidra.app.plugin.core.progmgr;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
import javax.swing.Icon;
import docking.ActionContext;
import docking.action.*;
+import docking.menu.MultiActionDockingAction;
import docking.tool.ToolConstants;
import generic.theme.GIcon;
import ghidra.app.context.ProgramActionContext;
@@ -37,11 +40,12 @@ import ghidra.util.*;
* 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 {
+public abstract class AbstractUndoRedoAction extends MultiActionDockingAction {
private PluginTool tool;
- private Program lastProgram;
+ private Program activeProgram;
private ProgramManagerPlugin plugin;
private TransactionListener transactionListener;
+ private HelpLocation helpLocation;
public AbstractUndoRedoAction(PluginTool tool, ProgramManagerPlugin plugin, String name,
String iconId, String keyBinding, String subGroup) {
@@ -52,6 +56,8 @@ public abstract class AbstractUndoRedoAction extends DockingAction {
String[] menuPath = { ToolConstants.MENU_EDIT, "&" + name };
Icon icon = new GIcon(iconId);
+ helpLocation = new HelpLocation(ToolConstants.TOOL_HELP_TOPIC, name);
+
String group = "Undo";
MenuData menuData = new MenuData(menuPath, icon, group);
@@ -60,7 +66,7 @@ public abstract class AbstractUndoRedoAction extends DockingAction {
setToolBarData(new ToolBarData(icon, group));
setKeyBindingData(new KeyBindingData(keyBinding));
- setHelpLocation(new HelpLocation(ToolConstants.TOOL_HELP_TOPIC, name));
+ setHelpLocation(helpLocation);
setDescription(name);
addToWindowWhen(ProgramActionContext.class);
@@ -68,7 +74,7 @@ public abstract class AbstractUndoRedoAction extends DockingAction {
transactionListener = new ContextProgramTransactionListener();
}
- protected abstract void actionPerformed(Program program) throws IOException;
+ protected abstract void doAction(Program program, int repeatCount) throws IOException;
protected abstract boolean canPerformAction(Program program);
@@ -82,17 +88,21 @@ public abstract class AbstractUndoRedoAction extends DockingAction {
// 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);
+ if (program != activeProgram) {
+ removeTransactionListener(activeProgram);
+ activeProgram = program;
+ addTransactionListener(activeProgram);
updateActionNameAndDescription();
}
- return canPerformAction(lastProgram);
+ return canPerformAction(activeProgram);
}
@Override
public void actionPerformed(ActionContext context) {
+ executeAction(context, 1);
+ }
+
+ private void executeAction(ActionContext context, int repeatCount) {
Program program = getProgram(context);
if (program == null) {
return;
@@ -101,11 +111,30 @@ public abstract class AbstractUndoRedoAction extends DockingAction {
saveCurrentLocationToHistory();
try {
- actionPerformed(program);
+ doAction(program, repeatCount);
}
catch (IOException e) {
- Msg.showError(this, null, null, null, e);
+ Msg.showError(this, null, getName() + " Error",
+ "Error occured while attempting " + getName() + "!", e);
}
+
+ }
+
+ protected abstract List getDescriptions(Program program);
+
+ @Override
+ public List getActionList(ActionContext context) {
+ Program program = getProgram(context);
+ List descriptions = getDescriptions(program);
+ List actions = new ArrayList<>();
+
+ int repeatCount = 1;
+ for (String string : descriptions) {
+ actions.add(new RepeatedAction(string, repeatCount));
+ repeatCount++;
+ }
+
+ return actions;
}
private Program getProgram(ActionContext context) {
@@ -129,24 +158,28 @@ public abstract class AbstractUndoRedoAction extends DockingAction {
private void updateAction() {
updateActionNameAndDescription();
- setEnabled(canPerformAction(lastProgram));
+ setEnabled(canPerformAction(activeProgram));
}
private void updateActionNameAndDescription() {
String actionName = getName();
String description = actionName;
+ String menuName = actionName;
- if (lastProgram != null) {
- actionName += " " + lastProgram.getDomainFile().getName();
+ if (activeProgram != null) {
+ menuName = actionName + " " + activeProgram.getDomainFile().getName();
description = actionName;
}
- if (canPerformAction(lastProgram)) {
- description = HTMLUtilities.toWrappedHTML(
- getName() + " " + HTMLUtilities.escapeHTML(getUndoRedoDescription(lastProgram)));
+ if (canPerformAction(activeProgram)) {
+ String programName = activeProgram.getDomainFile().getName();
+ String undoRedoDescription = getUndoRedoDescription(activeProgram);
+ String text = actionName + " " +
+ HTMLUtilities.escapeHTML(undoRedoDescription + " (" + programName + ")");
+ description = HTMLUtilities.toWrappedHTML(text);
}
- getMenuBarData().setMenuItemNamePlain(actionName);
+ getMenuBarData().setMenuItemNamePlain(menuName);
setDescription(description);
}
@@ -158,6 +191,30 @@ public abstract class AbstractUndoRedoAction extends DockingAction {
}
}
+ /**
+ * Action for repeating the undo/redo action multiple times to effectively undo/redo to
+ * a transaction that is not at the top of the list of undo/redo items.
+ */
+ private class RepeatedAction extends DockingAction {
+
+ private int repeatCount;
+
+ public RepeatedAction(String name, int repeatCount) {
+ super(name, AbstractUndoRedoAction.this.getOwner());
+ this.repeatCount = repeatCount;
+ setHelpLocation(helpLocation);
+ setMenuBarData(new MenuData(new String[] { name }));
+ setEnabled(true);
+
+ }
+
+ @Override
+ public void actionPerformed(ActionContext context) {
+ executeAction(context, repeatCount);
+ }
+
+ }
+
private class ContextProgramTransactionListener implements TransactionListener {
@Override
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 1728e08b7d..d8225eb27d 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
@@ -16,6 +16,7 @@
package ghidra.app.plugin.core.progmgr;
import java.io.IOException;
+import java.util.List;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
@@ -31,8 +32,10 @@ public class RedoAction extends AbstractUndoRedoAction {
}
@Override
- protected void actionPerformed(Program program) throws IOException {
- program.redo();
+ protected void doAction(Program program, int count) throws IOException {
+ for (int i = 0; i < count; i++) {
+ program.redo();
+ }
}
@Override
@@ -44,4 +47,9 @@ public class RedoAction extends AbstractUndoRedoAction {
protected String getUndoRedoDescription(Program program) {
return program.getRedoName();
}
+
+ @Override
+ protected List getDescriptions(Program program) {
+ return program.getAllRedoNames();
+ }
}
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 b01591fd5f..08f2a000ab 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
@@ -16,6 +16,7 @@
package ghidra.app.plugin.core.progmgr;
import java.io.IOException;
+import java.util.List;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
@@ -31,8 +32,10 @@ public class UndoAction extends AbstractUndoRedoAction {
}
@Override
- protected void actionPerformed(Program program) throws IOException {
- program.undo();
+ protected void doAction(Program program, int count) throws IOException {
+ for (int i = 0; i < count; i++) {
+ program.undo();
+ }
}
@Override
@@ -45,4 +48,9 @@ public class UndoAction extends AbstractUndoRedoAction {
return program.getUndoName();
}
+ @Override
+ protected List getDescriptions(Program program) {
+ return program.getAllUndoNames();
+ }
+
}
diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/util/EmptyVTSession.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/util/EmptyVTSession.java
index d28979dfc1..864c4e9e9b 100644
--- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/util/EmptyVTSession.java
+++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/util/EmptyVTSession.java
@@ -307,8 +307,8 @@ public class EmptyVTSession implements VTSession {
}
@Override
- public void saveToPackedFile(File outputFile, TaskMonitor monitor) throws IOException,
- CancelledException {
+ public void saveToPackedFile(File outputFile, TaskMonitor monitor)
+ throws IOException, CancelledException {
// do nothing
}
@@ -372,6 +372,16 @@ public class EmptyVTSession implements VTSession {
// do nothing
}
+ @Override
+ public List getAllRedoNames() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List getAllUndoNames() {
+ return Collections.emptyList();
+ }
+
@Override
public void removeTransactionListener(TransactionListener listener) {
// do nothing
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/AbstractTransactionManager.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/AbstractTransactionManager.java
index 48cdfce8a4..ef49b10027 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/AbstractTransactionManager.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/AbstractTransactionManager.java
@@ -16,6 +16,7 @@
package ghidra.framework.data;
import java.io.IOException;
+import java.util.List;
import db.TerminatedTransactionException;
import ghidra.framework.model.*;
@@ -222,14 +223,44 @@ abstract class AbstractTransactionManager {
*/
abstract int getUndoStackDepth();
+ /**
+ * Returns true if there is at least one redo transaction to be redone.
+ * @return true if there is at least one redo transaction to be redone
+ */
abstract boolean canRedo();
+ /**
+ * Returns true if there is at least one undo transaction to be undone.
+ * @return true if there is at least one undo transaction to be undone
+ */
abstract boolean canUndo();
+ /**
+ * Returns the name of the next undo transaction (The most recent change).
+ * @return the name of the next undo transaction (The most recent change)
+ */
abstract String getRedoName();
+ /**
+ * Returns the name of the next redo transaction (The most recent undo).
+ * @return the name of the next redo transaction (The most recent undo)
+ */
abstract String getUndoName();
+ /**
+ * Returns the names of all undoable transactions in reverse chronological order. In other
+ * words the transaction at the top of the list must be undone first.
+ * @return the names of all undoable transactions in reverse chronological order
+ */
+ abstract List getAllUndoNames();
+
+ /**
+ * Returns the names of all redoable transactions in chronological order. In other words
+ * the transaction at the top of the list must be redone first.
+ * @return the names of all redoable transactions in chronological order
+ */
+ abstract List getAllRedoNames();
+
abstract TransactionInfo getCurrentTransactionInfo();
final void redo() throws IOException {
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java
index 0352134439..7d5d9e9b31 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java
@@ -427,6 +427,16 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
return transactionMgr.getUndoName();
}
+ @Override
+ public List getAllUndoNames() {
+ return transactionMgr.getAllUndoNames();
+ }
+
+ @Override
+ public List getAllRedoNames() {
+ return transactionMgr.getAllRedoNames();
+ }
+
@Override
public TransactionInfo getCurrentTransactionInfo() {
return transactionMgr.getCurrentTransactionInfo();
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectDBTransaction.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectDBTransaction.java
index 7333d8ca47..ff6dd2c560 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectDBTransaction.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectDBTransaction.java
@@ -121,9 +121,6 @@ class DomainObjectDBTransaction implements TransactionInfo {
return hasDBTransaction;
}
- /* (non-Javadoc)
- * @see ghidra.framework.data.XTransaction#getID()
- */
@Override
public long getID() {
return id;
@@ -148,8 +145,8 @@ class DomainObjectDBTransaction implements TransactionInfo {
throw new IllegalStateException("Transaction not found");
}
if (entry.status != Status.NOT_DONE) {
- throw new IllegalStateException("Attempted to end Transaction " + "more that once: " +
- entry.description);
+ throw new IllegalStateException(
+ "Attempted to end Transaction " + "more that once: " + entry.description);
}
entry.status = commit ? Status.COMMITTED : Status.ABORTED;
if (!commit) {
@@ -196,28 +193,20 @@ class DomainObjectDBTransaction implements TransactionInfo {
}
}
- /* (non-Javadoc)
- * @see ghidra.framework.data.XTransaction#getDescription()
- */
@Override
public String getDescription() {
if (list.isEmpty()) {
return "";
}
- String description = "";
for (TransactionEntry entry : list) {
- description = entry.description;
+ String description = entry.description;
if (description != null && description.length() != 0) {
- description = domainObject.getDomainFile().getName() + ": " + description;
- break;
+ return description;
}
}
- return description;
+ return "";
}
- /* (non-Javadoc)
- * @see ghidra.framework.data.XTransaction#getOpenSubTransactions()
- */
@Override
public ArrayList getOpenSubTransactions() {
ArrayList subTxList = new ArrayList();
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectTransactionManager.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectTransactionManager.java
index 2f7ceac99f..e1b240b123 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectTransactionManager.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectTransactionManager.java
@@ -16,7 +16,7 @@
package ghidra.framework.data;
import java.io.IOException;
-import java.util.LinkedList;
+import java.util.*;
import ghidra.framework.model.*;
import ghidra.framework.model.TransactionInfo.Status;
@@ -27,10 +27,8 @@ import ghidra.util.datastruct.WeakSet;
class DomainObjectTransactionManager extends AbstractTransactionManager {
- private LinkedList undoList =
- new LinkedList<>();
- private LinkedList redoList =
- new LinkedList<>();
+ private LinkedList undoList = new LinkedList<>();
+ private LinkedList redoList = new LinkedList<>();
private WeakSet transactionListeners =
WeakDataStructureFactory.createCopyOnWriteWeakSet();
@@ -241,6 +239,25 @@ class DomainObjectTransactionManager extends AbstractTransactionManager {
return "";
}
+ @Override
+ List getAllUndoNames() {
+ return getDescriptions(undoList);
+ }
+
+ @Override
+ List getAllRedoNames() {
+ return getDescriptions(redoList);
+ }
+
+ private List getDescriptions(List list) {
+ List descriptions = new ArrayList<>();
+ for (DomainObjectDBTransaction tx : list) {
+ descriptions.add(tx.getDescription());
+ }
+ Collections.reverse(descriptions);
+ return descriptions;
+ }
+
@Override
TransactionInfo getCurrentTransactionInfo() {
return transaction;
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/SynchronizedTransactionManager.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/SynchronizedTransactionManager.java
index fba782f83f..5665413f7c 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/SynchronizedTransactionManager.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/SynchronizedTransactionManager.java
@@ -16,7 +16,7 @@
package ghidra.framework.data;
import java.io.IOException;
-import java.util.LinkedList;
+import java.util.*;
import ghidra.framework.model.*;
import ghidra.framework.model.TransactionInfo.Status;
@@ -88,8 +88,8 @@ class SynchronizedTransactionManager extends AbstractTransactionManager {
synchronized void removeDomainObject(DomainObjectAdapterDB domainObj) throws LockException {
if (getCurrentTransactionInfo() != null) {
- throw new LockException(
- "domain object has open transaction: " + getCurrentTransactionInfo().getDescription());
+ throw new LockException("domain object has open transaction: " +
+ getCurrentTransactionInfo().getDescription());
}
if (isLocked()) {
throw new LockException("domain object is locked!");
@@ -265,6 +265,26 @@ class SynchronizedTransactionManager extends AbstractTransactionManager {
return "";
}
+ @Override
+ List getAllUndoNames() {
+ List descriptions = new ArrayList<>();
+ for (SynchronizedTransaction tx : undoList) {
+ descriptions.add(tx.getDescription());
+ }
+ Collections.reverse(descriptions);
+ return descriptions;
+ }
+
+ @Override
+ List getAllRedoNames() {
+ List descriptions = new ArrayList<>();
+ for (SynchronizedTransaction tx : redoList) {
+ descriptions.add(tx.getDescription());
+ }
+ Collections.reverse(descriptions);
+ return descriptions;
+ }
+
@Override
TransactionInfo getCurrentTransactionInfo() {
return transaction;
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/Undoable.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/Undoable.java
index 076cbb299f..433ba9e568 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/Undoable.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/Undoable.java
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
- * REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,8 +15,8 @@
*/
package ghidra.framework.model;
-
import java.io.IOException;
+import java.util.List;
/**
* Objects that implement Undoable have the ability to "remember" some number
@@ -59,14 +58,28 @@ public interface Undoable {
void redo() throws IOException;
/**
- * Returns a description of the chanage that would be "undone".
+ * Returns a description of the change that would be "undone".
+ * @return a description of the change that would be "undone".
*/
- String getUndoName();
+ public String getUndoName();
/**
* Returns a description of the change that would be "redone".
+ * @return a description of the change that would be "redone".
*/
- String getRedoName();
+ public String getRedoName();
+
+ /**
+ * Returns a list of the names of all current undo transactions
+ * @return a list of the names of all current undo transactions
+ */
+ public List getAllUndoNames();
+
+ /**
+ * Returns a list of the names of all current redo transactions
+ * @return a list of the names of all current redo transactions
+ */
+ public List getAllRedoNames();
/**
* Adds the given transaction listener to this domain object
diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/StubProgram.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/StubProgram.java
index 23fcf14b04..7d3991b7d7 100644
--- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/StubProgram.java
+++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/StubProgram.java
@@ -303,6 +303,16 @@ public class StubProgram implements Program {
throw new UnsupportedOperationException();
}
+ @Override
+ public List getAllUndoNames() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List getAllRedoNames() {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public void addTransactionListener(TransactionListener listener) {
throw new UnsupportedOperationException();