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