Merge remote-tracking branch 'origin/GP-3521_ghidragon_allow_user_to_see_and_pick_older_undos_or_redos--SQUASHED'

This commit is contained in:
Ryan Kurtz 2023-06-14 07:49:31 -04:00
commit d98ae48110
14 changed files with 303 additions and 115 deletions

View File

@ -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<TraceFunctionSymbol> 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<String> getAllUndoNames() {
return trace.getAllUndoNames();
}
@Override
public List<String> 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<TraceMemoryRegion> 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);
}
}

View File

@ -613,6 +613,16 @@ public class DBTraceProgramViewRegisters implements TraceProgramView {
return view.getRedoName();
}
@Override
public List<String> getAllUndoNames() {
return view.getAllUndoNames();
}
@Override
public List<String> getAllRedoNames() {
return view.getAllRedoNames();
}
@Override
public void addTransactionListener(TransactionListener listener) {
view.addTransactionListener(listener);

View File

@ -36,6 +36,11 @@
hovering the mouse over the <IMG src="icon.redo">button will display the name of the edit
operation that would be "redone".</P>
<P><IMG src="help/shared/tip.png"> 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.</P>
<P>&nbsp;</P>
<P>&nbsp;</P>

View File

@ -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<String> getDescriptions(Program program);
@Override
public List<DockingActionIf> getActionList(ActionContext context) {
Program program = getProgram(context);
List<String> descriptions = getDescriptions(program);
List<DockingActionIf> 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

View File

@ -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<String> getDescriptions(Program program) {
return program.getAllRedoNames();
}
}

View File

@ -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<String> getDescriptions(Program program) {
return program.getAllUndoNames();
}
}

View File

@ -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<String> getAllRedoNames() {
return Collections.emptyList();
}
@Override
public List<String> getAllUndoNames() {
return Collections.emptyList();
}
@Override
public void removeTransactionListener(TransactionListener listener) {
// do nothing

View File

@ -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<String> 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<String> getAllRedoNames();
abstract TransactionInfo getCurrentTransactionInfo();
final void redo() throws IOException {

View File

@ -427,6 +427,16 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
return transactionMgr.getUndoName();
}
@Override
public List<String> getAllUndoNames() {
return transactionMgr.getAllUndoNames();
}
@Override
public List<String> getAllRedoNames() {
return transactionMgr.getAllRedoNames();
}
@Override
public TransactionInfo getCurrentTransactionInfo() {
return transactionMgr.getCurrentTransactionInfo();

View File

@ -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<String> getOpenSubTransactions() {
ArrayList<String> subTxList = new ArrayList<String>();

View File

@ -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<DomainObjectDBTransaction> undoList =
new LinkedList<>();
private LinkedList<DomainObjectDBTransaction> redoList =
new LinkedList<>();
private LinkedList<DomainObjectDBTransaction> undoList = new LinkedList<>();
private LinkedList<DomainObjectDBTransaction> redoList = new LinkedList<>();
private WeakSet<TransactionListener> transactionListeners =
WeakDataStructureFactory.createCopyOnWriteWeakSet();
@ -241,6 +239,25 @@ class DomainObjectTransactionManager extends AbstractTransactionManager {
return "";
}
@Override
List<String> getAllUndoNames() {
return getDescriptions(undoList);
}
@Override
List<String> getAllRedoNames() {
return getDescriptions(redoList);
}
private List<String> getDescriptions(List<DomainObjectDBTransaction> list) {
List<String> descriptions = new ArrayList<>();
for (DomainObjectDBTransaction tx : list) {
descriptions.add(tx.getDescription());
}
Collections.reverse(descriptions);
return descriptions;
}
@Override
TransactionInfo getCurrentTransactionInfo() {
return transaction;

View File

@ -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<String> getAllUndoNames() {
List<String> descriptions = new ArrayList<>();
for (SynchronizedTransaction tx : undoList) {
descriptions.add(tx.getDescription());
}
Collections.reverse(descriptions);
return descriptions;
}
@Override
List<String> getAllRedoNames() {
List<String> descriptions = new ArrayList<>();
for (SynchronizedTransaction tx : redoList) {
descriptions.add(tx.getDescription());
}
Collections.reverse(descriptions);
return descriptions;
}
@Override
TransactionInfo getCurrentTransactionInfo() {
return transaction;

View File

@ -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<String> 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<String> getAllRedoNames();
/**
* Adds the given transaction listener to this domain object

View File

@ -303,6 +303,16 @@ public class StubProgram implements Program {
throw new UnsupportedOperationException();
}
@Override
public List<String> getAllUndoNames() {
throw new UnsupportedOperationException();
}
@Override
public List<String> getAllRedoNames() {
throw new UnsupportedOperationException();
}
@Override
public void addTransactionListener(TransactionListener listener) {
throw new UnsupportedOperationException();