GP-6607: Port Snapshot Table to the (background) Threaded impl.

This commit is contained in:
Dan
2026-04-22 12:20:15 +00:00
parent 0e0a5c1614
commit c0fedc7644
28 changed files with 584 additions and 431 deletions

View File

@@ -25,7 +25,6 @@ import javax.swing.*;
import docking.ReusableDialogComponentProvider;
import docking.widgets.table.*;
import docking.widgets.table.ColumnSortState.SortDirection;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;

View File

@@ -29,7 +29,6 @@ import docking.action.*;
import docking.action.builder.ActionBuilder;
import docking.menu.MultiActionDockingAction;
import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.context.ProgramLocationActionContext;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;

View File

@@ -36,7 +36,7 @@ import docking.action.*;
import docking.actions.PopupActionProvider;
import docking.widgets.table.ColumnSortState.SortDirection;
import docking.widgets.table.CustomToStringCellRenderer;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import docking.widgets.table.EnumeratedTableColumn;
import generic.theme.GIcon;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;

View File

@@ -29,7 +29,6 @@ import javax.swing.table.TableCellEditor;
import db.Transaction;
import docking.ReusableDialogComponentProvider;
import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.copying.DebuggerCopyPlan.Copier;
import ghidra.app.services.*;

View File

@@ -23,7 +23,6 @@ import java.util.function.Function;
import javax.swing.table.TableCellEditor;
import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.gui.AbstractDebuggerMapProposalDialog;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.debug.api.modules.RegionMapProposal.RegionMapEntry;

View File

@@ -22,7 +22,6 @@ import java.util.function.Function;
import javax.swing.table.TableCellEditor;
import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.gui.AbstractDebuggerMapProposalDialog;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.debug.api.modules.ModuleMapProposal.ModuleMapEntry;

View File

@@ -23,7 +23,6 @@ import java.util.function.Function;
import javax.swing.table.*;
import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.gui.AbstractDebuggerMapProposalDialog;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.debug.api.modules.SectionMapProposal.SectionMapEntry;

View File

@@ -30,7 +30,6 @@ import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.DockingActionIf;
import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerProvider;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
@@ -132,7 +131,7 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
}
}
protected static class MappingTableModel extends DebouncedRowWrappedEnumeratedColumnTableModel< //
protected static class MappingTableModel extends DebouncedRowWrappedEnumeratedColumnTableModel<
StaticMappingTableColumns, ObjectKey, StaticMappingRow, TraceStaticMapping> {
public MappingTableModel(PluginTool tool) {

View File

@@ -31,7 +31,6 @@ import javax.swing.table.*;
import db.Transaction;
import docking.action.DockingAction;
import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import generic.theme.GColor;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;

View File

@@ -26,7 +26,6 @@ import javax.swing.*;
import docking.DialogComponentProvider;
import docking.widgets.table.*;
import docking.widgets.table.ColumnSortState.SortDirection;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOffer;
import ghidra.framework.plugintool.PluginTool;

View File

@@ -26,9 +26,7 @@ import javax.swing.*;
import docking.ActionContext;
import docking.ReusableDialogComponentProvider;
import docking.action.DockingAction;
import docking.widgets.table.DefaultEnumeratedColumnTableModel;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import docking.widgets.table.GTable;
import docking.widgets.table.*;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.lang.Language;

View File

@@ -38,7 +38,6 @@ import docking.actions.PopupActionProvider;
import docking.widgets.AbstractGCellRenderer;
import docking.widgets.table.*;
import docking.widgets.table.ColumnSortState.SortDirection;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import generic.theme.GColor;
import ghidra.app.plugin.core.data.DataSettingsDialog;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;

View File

@@ -16,31 +16,24 @@
package ghidra.app.plugin.core.debug.gui.time;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.Date;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.swing.*;
import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import docking.widgets.table.threaded.GThreadedTablePanel;
import generic.theme.GColor;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.docking.settings.Settings;
import ghidra.framework.model.DomainObjectEvent;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.target.TraceObjectValue;
import ghidra.trace.model.target.path.KeyPath;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
import ghidra.trace.util.TraceEvents;
import ghidra.util.DateUtils;
import ghidra.util.table.GhidraTableFilterPanel;
import ghidra.util.table.column.AbstractGColumnRenderer;
@@ -178,77 +171,6 @@ public class DebuggerSnapshotTablePanel extends JPanel {
}
}
protected class SnapshotTableModel
extends DefaultEnumeratedColumnTableModel<SnapshotTableColumns, SnapshotRow> {
public SnapshotTableModel(PluginTool tool) {
super(tool, "Snapshots", SnapshotTableColumns.class);
}
@Override
public List<SnapshotTableColumns> defaultSortOrder() {
return List.of(SnapshotTableColumns.TIME);
}
Trace getTrace() {
return currentTrace;
}
DebuggerCoordinates getCurrent() {
return current;
}
}
private class SnapshotListener extends TraceDomainObjectListener {
public SnapshotListener() {
listenForUntyped(DomainObjectEvent.RESTORED, e -> objectRestored());
listenFor(TraceEvents.SNAPSHOT_ADDED, this::snapAdded);
listenFor(TraceEvents.SNAPSHOT_CHANGED, this::snapChanged);
listenFor(TraceEvents.SNAPSHOT_DELETED, this::snapDeleted);
listenFor(TraceEvents.VALUE_CREATED, this::valueCreated);
listenFor(TraceEvents.VALUE_DELETED, this::valueDeleted);
}
private void objectRestored() {
loadSnapshots();
}
private void snapAdded(TraceSnapshot snapshot) {
if (snapshot.getKey() < 0 && hideScratch) {
return;
}
SnapshotRow row = new SnapshotRow(snapshot, tool);
snapshotTableModel.add(row);
}
private void snapChanged(TraceSnapshot snapshot) {
if (snapshot.getKey() < 0 && hideScratch) {
return;
}
snapshotTableModel.notifyUpdatedWith(row -> row.getSnapshot() == snapshot);
}
private void snapDeleted(TraceSnapshot snapshot) {
if (snapshot.getKey() < 0 && hideScratch) {
return;
}
snapshotTableModel.deleteWith(row -> row.getSnapshot() == snapshot);
}
private void valueCreated(TraceObjectValue value) {
if (value.getCanonicalPath().equals(KeyPath.of(TraceTimeManager.KEY_TIME_RADIX))) {
snapshotTableModel.fireTableDataChanged();
}
}
private void valueDeleted(TraceObjectValue value) {
if (value.getCanonicalPath().equals(KeyPath.of(TraceTimeManager.KEY_TIME_RADIX))) {
snapshotTableModel.fireTableDataChanged();
}
}
}
static class StyleCurrentSnapRenderer extends AbstractGColumnRenderer<Object>
implements GTableAccess {
@@ -363,102 +285,34 @@ public class DebuggerSnapshotTablePanel extends JPanel {
protected final PluginTool tool;
protected final SnapshotTableModel snapshotTableModel;
protected final GThreadedTablePanel<SnapshotRow> snapshotTablePanel;
protected final GTable snapshotTable;
protected final GhidraTableFilterPanel<SnapshotRow> snapshotFilterPanel;
protected boolean hideScratch = false;
private Trace currentTrace;
private volatile DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
protected final SnapshotListener listener = new SnapshotListener();
public DebuggerSnapshotTablePanel(PluginTool tool) {
super(new BorderLayout());
this.tool = tool;
snapshotTableModel = new SnapshotTableModel(tool);
snapshotTable = new GTable(snapshotTableModel);
snapshotTablePanel = new GThreadedTablePanel<>(snapshotTableModel);
snapshotTable = snapshotTablePanel.getTable();
snapshotTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
add(new JScrollPane(snapshotTable));
add(snapshotTablePanel);
snapshotFilterPanel = new GhidraTableFilterPanel<>(snapshotTable, snapshotTableModel);
add(snapshotFilterPanel, BorderLayout.SOUTH);
}
private void addNewListeners() {
if (currentTrace == null) {
return;
}
currentTrace.addListener(listener);
}
private void removeOldListeners() {
if (currentTrace == null) {
return;
}
currentTrace.removeListener(listener);
}
public void setTrace(Trace trace) {
if (currentTrace == trace) {
return;
}
removeOldListeners();
currentTrace = trace;
addNewListeners();
loadSnapshots();
snapshotTableModel.setTrace(trace);
}
public Trace getTrace() {
return currentTrace;
return snapshotTableModel.getTrace();
}
public void setHideScratchSnapshots(boolean hideScratch) {
if (this.hideScratch == hideScratch) {
return;
}
this.hideScratch = hideScratch;
if (hideScratch) {
deleteScratchSnapshots();
}
else {
loadScratchSnapshots();
}
}
protected void loadSnapshots() {
snapshotTableModel.clear();
if (currentTrace == null) {
return;
}
TraceTimeManager manager = currentTrace.getTimeManager();
List<SnapshotRow> toAdd = new ArrayList<>();
for (TraceSnapshot snapshot : hideScratch
? manager.getSnapshots(0, true, Long.MAX_VALUE, true)
: manager.getAllSnapshots()) {
SnapshotRow row = new SnapshotRow(snapshot, tool);
toAdd.add(row);
if (current != DebuggerCoordinates.NOWHERE &&
snapshot.getKey() == current.getViewSnap()) {
}
}
snapshotTableModel.addAll(toAdd);
}
protected void deleteScratchSnapshots() {
snapshotTableModel.deleteWith(s -> s.getSnap() < 0);
}
protected void loadScratchSnapshots() {
if (currentTrace == null) {
return;
}
TraceTimeManager manager = currentTrace.getTimeManager();
Collection<? extends TraceSnapshot> sratch =
manager.getSnapshots(Long.MIN_VALUE, true, 0, false);
snapshotTableModel.addAll(sratch.stream()
.map(s -> new SnapshotRow(s, tool))
.collect(Collectors.toList()));
snapshotTableModel.setHideScratch(hideScratch);
}
public ListSelectionModel getSelectionModel() {
@@ -471,9 +325,8 @@ public class DebuggerSnapshotTablePanel extends JPanel {
}
public void setCurrent(DebuggerCoordinates coords) {
assert coords.getTrace() == currentTrace;
boolean fire = coords.getViewSnap() != current.getViewSnap();
current = coords;
boolean fire = coords.getViewSnap() != snapshotTableModel.getCurrent().getViewSnap();
snapshotTableModel.setCurrent(coords);
if (fire) {
snapshotTable.repaint();
}
@@ -490,7 +343,13 @@ public class DebuggerSnapshotTablePanel extends JPanel {
if (Objects.equals(curSnap, snap)) {
return;
}
SnapshotRow row = snapshotTableModel.findFirst(r -> r.getSnap() == snap);
if (snapshotTableModel.getTrace() == null) {
return;
}
TraceSnapshot snapshot =
snapshotTableModel.getTrace().getTimeManager().getSnapshot(snap, false);
SnapshotRow row = snapshotTableModel.rowMap.get(snapshot);
if (row == null) {
snapshotTable.clearSelection();
return;

View File

@@ -0,0 +1,193 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.time;
import java.util.*;
import docking.widgets.table.ThreadedEnumeratedColumnTableModel;
import ghidra.app.plugin.core.debug.gui.time.DebuggerSnapshotTablePanel.SnapshotTableColumns;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.model.DomainObjectEvent;
import ghidra.framework.plugintool.PluginTool;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.target.TraceObjectValue;
import ghidra.trace.model.target.path.KeyPath;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.trace.util.TraceEvents;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
class SnapshotTableModel
extends ThreadedEnumeratedColumnTableModel<SnapshotTableColumns, SnapshotRow> {
private class SnapshotListener extends TraceDomainObjectListener {
public SnapshotListener() {
listenForUntyped(DomainObjectEvent.RESTORED, e -> objectRestored());
listenFor(TraceEvents.SNAPSHOT_ADDED, this::snapAdded);
listenFor(TraceEvents.SNAPSHOT_CHANGED, this::snapChanged);
listenFor(TraceEvents.SNAPSHOT_DELETED, this::snapDeleted);
listenFor(TraceEvents.VALUE_CREATED, this::valueCreated);
listenFor(TraceEvents.VALUE_DELETED, this::valueDeleted);
}
private void objectRestored() {
reload();
}
private void snapAdded(TraceSnapshot snapshot) {
addSnapshot(snapshot);
}
private void snapChanged(TraceSnapshot snapshot) {
updateSnapshot(snapshot);
}
private void snapDeleted(TraceSnapshot snapshot) {
removeSnapshot(snapshot);
}
private void valueCreated(TraceObjectValue value) {
if (value.getCanonicalPath().equals(KeyPath.of(TraceTimeManager.KEY_TIME_RADIX))) {
fireTableDataChanged();
}
}
private void valueDeleted(TraceObjectValue value) {
if (value.getCanonicalPath().equals(KeyPath.of(TraceTimeManager.KEY_TIME_RADIX))) {
fireTableDataChanged();
}
}
}
protected final SnapshotListener listener = new SnapshotListener();
protected final Map<TraceSnapshot, SnapshotRow> rowMap = new HashMap<>();
private volatile Trace currentTrace; // Because it gets set before current
private volatile DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
private boolean hideScratch;
public SnapshotTableModel(PluginTool tool) {
super(tool, "Snapshots", SnapshotTableColumns.class, null, true);
}
private void addNewListeners() {
if (currentTrace == null) {
return;
}
currentTrace.addListener(listener);
}
private void removeOldListeners() {
if (currentTrace == null) {
return;
}
currentTrace.removeListener(listener);
}
public void setTrace(Trace trace) {
if (this.currentTrace == trace) {
return;
}
removeOldListeners();
this.currentTrace = trace;
addNewListeners();
reload();
}
public Trace getTrace() {
return currentTrace;
}
public void setCurrent(DebuggerCoordinates coords) {
assert coords.getTrace() == currentTrace;
this.current = coords;
}
public DebuggerCoordinates getCurrent() {
return current;
}
public void setHideScratch(boolean hideScratch) {
if (this.hideScratch == hideScratch) {
return;
}
this.hideScratch = hideScratch;
reload();
}
@Override
public List<SnapshotTableColumns> defaultSortOrder() {
return List.of(SnapshotTableColumns.TIME);
}
@Override
protected void doLoad(Accumulator<SnapshotRow> accumulator, TaskMonitor monitor)
throws CancelledException {
rowMap.clear();
if (currentTrace == null) {
return;
}
TraceTimeManager manager = currentTrace.getTimeManager();
Long maxSnap = manager.getMaxSnap();
monitor.initialize(maxSnap == null ? 0 : maxSnap.longValue(), "Reading Snapshots");
for (TraceSnapshot snapshot : hideScratch
? manager.getSnapshots(0, true, Long.MAX_VALUE, true)
: manager.getAllSnapshots()) {
SnapshotRow row = new SnapshotRow(snapshot, serviceProvider);
rowMap.put(snapshot, row);
accumulator.add(row);
monitor.setProgress(Math.max(0, snapshot.getKey()));
monitor.checkCancelled();
}
}
public void addSnapshot(TraceSnapshot snapshot) {
if (snapshot.getKey() < 0 && hideScratch) {
return;
}
SnapshotRow row =
rowMap.computeIfAbsent(snapshot, s -> new SnapshotRow(s, serviceProvider));
addObject(row);
}
public void updateSnapshot(TraceSnapshot snapshot) {
if (snapshot.getKey() < 0 && hideScratch) {
return;
}
SnapshotRow row = rowMap.get(snapshot);
if (row == null) {
return;
}
updateObject(row);
}
public void removeSnapshot(TraceSnapshot snapshot) {
if (snapshot.getKey() < 0 && hideScratch) {
return;
}
SnapshotRow row = rowMap.remove(snapshot);
if (row == null) {
return;
}
removeObject(row);
}
}

View File

@@ -38,7 +38,6 @@ import docking.action.DockingAction;
import docking.action.ToggleDockingAction;
import docking.action.builder.ActionBuilder;
import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import generic.theme.GColor;
import ghidra.app.context.ListingActionContext;
import ghidra.app.context.ProgramLocationActionContext;

View File

@@ -17,14 +17,15 @@ package ghidra.app.plugin.core.debug.utils;
import java.util.function.Function;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import docking.widgets.table.EnumeratedTableColumn;
import docking.widgets.table.RowWrappedEnumeratedColumnTableModel;
import ghidra.async.AsyncDebouncer;
import ghidra.async.AsyncTimer;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.Swing;
public class DebouncedRowWrappedEnumeratedColumnTableModel<C extends Enum<C> & EnumeratedTableColumn<C, R>, K, R, T>
public class DebouncedRowWrappedEnumeratedColumnTableModel<
C extends Enum<C> & EnumeratedTableColumn<C, R>, K, R, T>
extends RowWrappedEnumeratedColumnTableModel<C, K, R, T> {
AsyncDebouncer<Void> debouncer = new AsyncDebouncer<Void>(AsyncTimer.DEFAULT_TIMER, 100);

View File

@@ -102,25 +102,26 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerTest {
createSnaplessTrace();
addSnapshots();
waitForBusyTool(tool);
assertDisabled(listingProvider, timePlugin.actionRenameSnapshot);
traceManager.openTrace(tb.trace);
waitForSwing();
waitForBusyTool(tool);
assertDisabled(listingProvider, timePlugin.actionRenameSnapshot);
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForBusyTool(tool);
assertEnabled(listingProvider, timePlugin.actionRenameSnapshot);
traceManager.activateSnap(10);
waitForSwing();
waitForBusyTool(tool);
performEnabledAction(listingProvider, timePlugin.actionRenameSnapshot, false);
InputDialog dialog = waitForDialogComponent(InputDialog.class);
assertEquals("Snap 10", dialog.getValue());
dialog.setValue("My Snapshot");
dialog.close(); // isCancelled (private) defaults to false
waitForSwing();
waitForBusyTool(tool);
DBTraceSnapshot snapshot = tb.trace.getTimeManager().getSnapshot(10, false);
assertEquals("My Snapshot", snapshot.getDescription());
@@ -130,6 +131,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerTest {
@Test
public void testEmpty() {
waitForBusyTool(tool);
assertProviderEmpty();
}
@@ -138,12 +140,13 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerTest {
createSnaplessTrace();
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForBusyTool(tool);
assertProviderEmpty();
addSnapshots();
waitForDomainObject(tb.trace);
waitForBusyTool(tool);
assertProviderPopulated();
}
@@ -158,12 +161,13 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerTest {
}
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
waitForBusyTool(tool);
assertProviderEmpty();
addSnapshots();
waitForDomainObject(tb.trace);
waitForBusyTool(tool);
assertProviderPopulated();
}
@@ -174,11 +178,12 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerTest {
traceManager.openTrace(tb.trace);
addSnapshots();
waitForDomainObject(tb.trace);
waitForBusyTool(tool);
assertProviderEmpty();
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForBusyTool(tool);
assertProviderPopulated();
}
@@ -189,11 +194,12 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerTest {
traceManager.openTrace(tb.trace);
addSnapshots();
waitForDomainObject(tb.trace);
waitForBusyTool(tool);
assertProviderEmpty();
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForBusyTool(tool);
assertProviderPopulated();
@@ -201,6 +207,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerTest {
tb.trace.getTimeManager().getSnapshot(10, false).delete();
}
waitForDomainObject(tb.trace);
waitForBusyTool(tool);
assertEquals(1, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
}
@@ -210,20 +217,23 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerTest {
createSnaplessTrace();
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForBusyTool(tool);
assertProviderEmpty();
addSnapshots();
waitForDomainObject(tb.trace);
waitForBusyTool(tool);
assertProviderPopulated();
undo(tb.trace);
waitForBusyTool(tool);
assertProviderEmpty();
redo(tb.trace);
waitForBusyTool(tool);
assertProviderPopulated();
}
@@ -233,19 +243,21 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerTest {
createSnaplessTrace();
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForBusyTool(tool);
assertProviderEmpty();
try (Transaction tx = tb.startTransaction()) {
addSnapshots();
waitForDomainObject(tb.trace);
waitForBusyTool(tool);
assertProviderPopulated();
tx.abort();
}
waitForDomainObject(tb.trace);
waitForBusyTool(tool);
assertProviderEmpty();
}
@@ -260,7 +272,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerTest {
assertProviderEmpty();
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForBusyTool(tool);
assertProviderPopulated();
@@ -276,15 +288,17 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerTest {
traceManager.openTrace(tb.trace);
addSnapshots();
waitForDomainObject(tb.trace);
waitForBusyTool(tool);
assertProviderEmpty();
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForBusyTool(tool);
SnapshotRow row = timeProvider.mainPanel.snapshotTableModel.getModelData().get(0);
runSwing(() -> row.setDescription("Custom Description"));
waitForDomainObject(tb.trace);
waitForBusyTool(tool);
assertEquals("Custom Description",
tb.trace.getTimeManager().getSnapshot(0, false).getDescription());
@@ -298,11 +312,12 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerTest {
traceManager.openTrace(tb.trace);
addSnapshots();
waitForDomainObject(tb.trace);
waitForBusyTool(tool);
assertProviderEmpty();
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForBusyTool(tool);
clickTableCell(timeProvider.mainPanel.snapshotTable, 0, 0, 2);
assertEquals(0, traceManager.getCurrentSnap());
@@ -318,9 +333,10 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerTest {
addSnapshots();
addScratchSnapshot();
waitForDomainObject(tb.trace);
waitForBusyTool(tool);
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForBusyTool(tool);
assertEquals(3, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
}
@@ -331,14 +347,16 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerTest {
traceManager.openTrace(tb.trace);
addSnapshots();
waitForDomainObject(tb.trace);
waitForBusyTool(tool);
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForBusyTool(tool);
assertEquals(2, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
addScratchSnapshot();
waitForDomainObject(tb.trace);
waitForBusyTool(tool);
assertEquals(3, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
}
@@ -350,19 +368,22 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerTest {
addSnapshots();
addScratchSnapshot();
waitForDomainObject(tb.trace);
waitForBusyTool(tool);
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForBusyTool(tool);
assertEquals(false, timeProvider.hideScratch);
assertEquals(3, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
performAction(timeProvider.actionHideScratch);
waitForBusyTool(tool);
assertEquals(true, timeProvider.hideScratch);
assertEquals(2, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
performAction(timeProvider.actionHideScratch);
waitForBusyTool(tool);
assertEquals(false, timeProvider.hideScratch);
assertEquals(3, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
@@ -377,9 +398,10 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerTest {
addSnapshots();
addScratchSnapshot();
waitForDomainObject(tb.trace);
waitForBusyTool(tool);
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForBusyTool(tool);
assertEquals(true, timeProvider.hideScratch);
List<SnapshotRow> data = timeProvider.mainPanel.snapshotTableModel.getModelData();

View File

@@ -15,7 +15,6 @@
*/
package docking.widgets.table;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;

View File

@@ -18,15 +18,7 @@ package docking.widgets.table;
import java.util.*;
import java.util.function.Predicate;
import javax.swing.table.TableCellEditor;
import docking.widgets.table.ColumnSortState.SortDirection;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.docking.settings.Settings;
import ghidra.docking.settings.SettingsDefinition;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.util.table.column.GColumnRenderer;
/**
* A table model whose columns are described using an {@link Enum}.
@@ -39,128 +31,16 @@ import ghidra.util.table.column.GColumnRenderer;
*/
public class DefaultEnumeratedColumnTableModel<C extends Enum<C> & EnumeratedTableColumn<C, R>, R>
extends GDynamicColumnTableModel<R, Void> implements EnumeratedColumnTableModel<R> {
// NOTE: If I need to track indices, addSortListener
/**
* An interface on enums used to describe table columns
*
* @param <C> the type of the enum
* @param <R> the type of rows
*/
public static interface EnumeratedTableColumn<C extends Enum<C>, R> {
/**
* Get the value class of cells in this column
*
* @return the class
*/
public Class<?> getValueClass();
/**
* Get the value of this column for the given row
*
* @param row the row
* @return the value
*/
public Object getValueOf(R row);
/**
* Get the name of this column
*
* @return the name
*/
public String getHeader();
/**
* Get the value of this column for the given row
*
* @param row the row
* @param value the new value
*/
default public void setValueOf(R row, Object value) {
throw new UnsupportedOperationException("Cell is not editable");
}
/**
* Check if this column can be modified for the given row
*
* @param row the row
* @return true if editable
*/
default public boolean isEditable(R row) {
return false;
}
/**
* Check if this column can be sorted
*
* <p>
* TODO: Either this should be implemented as ported to {@link GDynamicColumnTableModel}, or
* removed.
*
* @return true if sortable
*/
default public boolean isSortable() {
return true;
}
/**
* Check if this column should be visible by default
*
* @return true if visible
*/
default public boolean isVisible() {
return true;
}
/**
* Get the default sort direction for this column
*
* @return the sort direction
*/
default public SortDirection defaultSortDirection() {
return SortDirection.ASCENDING;
}
default public int getPreferredWidth() {
return AbstractGTableModel.WIDTH_UNDEFINED;
}
default public int getMinWidth() {
return AbstractGTableModel.WIDTH_UNDEFINED;
}
default public int getMaxWidth() {
return AbstractGTableModel.WIDTH_UNDEFINED;
}
/**
* Because of limitations with Java generics and Enumerations, type checking cannot be
* guaranteed here. The user must ensure that any returned by {@link #getValueOf(Object)}
* can be accepted by the renderer returned here. The framework will perform an unchecked
* cast of the renderer.
*
* @return the renderer
*/
default public GColumnRenderer<?> getRenderer() {
return null;
}
default public TableCellEditor getEditor() {
return null;
}
default public SettingsDefinition[] getSettingsDefinitions() {
return null;
}
}
private final List<R> modelData = new ArrayList<>();
private final String name;
private final C[] cols;
private final List<C> cols;
public DefaultEnumeratedColumnTableModel(PluginTool tool, String name, Class<C> colType) {
super(tool);
public DefaultEnumeratedColumnTableModel(ServiceProvider serviceProvider, String name,
Class<C> colType) {
super(serviceProvider);
this.name = name;
this.cols = colType.getEnumConstants();
this.cols = List.of(colType.getEnumConstants());
reloadColumns(); // Smell
}
@@ -181,98 +61,9 @@ public class DefaultEnumeratedColumnTableModel<C extends Enum<C> & EnumeratedTab
}
}
static class EnumeratedDynamicTableColumn<R>
extends AbstractDynamicTableColumn<R, Object, Void>
implements EditableDynamicTableColumn<R, Object, Void> {
private final EnumeratedTableColumn<?, R> col;
public EnumeratedDynamicTableColumn(EnumeratedTableColumn<?, R> col) {
this.col = col;
}
@Override
public String getColumnName() {
return col.getHeader();
}
@Override
public Object getValue(R rowObject, Settings settings, Void data,
ServiceProvider serviceProvider) throws IllegalArgumentException {
return col.getValueOf(rowObject);
}
@Override
@SuppressWarnings("unchecked")
public Class<Object> getColumnClass() {
return (Class<Object>) col.getValueClass();
}
@Override
public boolean isEditable(R row, Settings settings, Void dataSource,
ServiceProvider serviceProvider) {
return col.isEditable(row);
}
@Override
public void setValueOf(R row, Object value, Settings settings, Void dataSource,
ServiceProvider serviceProvider) {
col.setValueOf(row, value);
}
@Override
@SuppressWarnings("unchecked")
public GColumnRenderer<Object> getColumnRenderer() {
return (GColumnRenderer<Object>) col.getRenderer();
}
@Override
public TableCellEditor getColumnEditor() {
return col.getEditor();
}
@Override
public int getColumnPreferredWidth() {
return col.getPreferredWidth();
}
@Override
public int getColumnMaxWidth() {
return col.getMaxWidth();
}
@Override
public int getColumnMinWidth() {
return col.getMinWidth();
}
@Override
public SettingsDefinition[] getSettingsDefinitions() {
SettingsDefinition[] defs = col.getSettingsDefinitions();
if (defs != null) {
return defs;
}
return super.getSettingsDefinitions();
}
}
@Override
protected TableColumnDescriptor<R> createTableColumnDescriptor() {
TableColumnDescriptor<R> descriptor = new TableColumnDescriptor<>();
if (cols != null) { // Smells
List<C> defaultOrder = defaultSortOrder();
for (C col : cols) {
EnumeratedDynamicTableColumn<R> ecol = new EnumeratedDynamicTableColumn<R>(col);
if (col.isVisible()) {
descriptor.addVisibleColumn(ecol,
defaultOrder.indexOf(col), // -1 means not found, not sorted
col.defaultSortDirection().isAscending());
}
else {
descriptor.addHiddenColumn(ecol);
}
}
}
return descriptor;
return EnumeratedColumnTableModel.createTableColumnDescriptor(cols, defaultSortOrder());
}
@Override

View File

@@ -54,4 +54,23 @@ public interface EnumeratedColumnTableModel<R> extends RowObjectTableModel<R> {
R findFirst(Predicate<R> predicate);
public void clear();
static <C extends Enum<C> & EnumeratedTableColumn<C, R>, R> TableColumnDescriptor<R>
createTableColumnDescriptor(Collection<C> cols, List<C> defaultOrder) {
TableColumnDescriptor<R> descriptor = new TableColumnDescriptor<>();
if (cols != null) { // Smells
for (C col : cols) {
EnumeratedDynamicTableColumn<R> ecol = new EnumeratedDynamicTableColumn<R>(col);
if (col.isVisible()) {
descriptor.addVisibleColumn(ecol,
defaultOrder.indexOf(col), // -1 means not found, not sorted
col.defaultSortDirection().isAscending());
}
else {
descriptor.addHiddenColumn(ecol);
}
}
}
return descriptor;
}
}

View File

@@ -0,0 +1,98 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets.table;
import javax.swing.table.TableCellEditor;
import docking.widgets.table.EnumeratedColumnTableModel.EditableDynamicTableColumn;
import ghidra.docking.settings.Settings;
import ghidra.docking.settings.SettingsDefinition;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.util.table.column.GColumnRenderer;
class EnumeratedDynamicTableColumn<R>
extends AbstractDynamicTableColumn<R, Object, Void>
implements EditableDynamicTableColumn<R, Object, Void> {
private final EnumeratedTableColumn<?, R> col;
public EnumeratedDynamicTableColumn(EnumeratedTableColumn<?, R> col) {
this.col = col;
}
@Override
public String getColumnName() {
return col.getHeader();
}
@Override
public Object getValue(R rowObject, Settings settings, Void data,
ServiceProvider serviceProvider) throws IllegalArgumentException {
return col.getValueOf(rowObject);
}
@Override
@SuppressWarnings("unchecked")
public Class<Object> getColumnClass() {
return (Class<Object>) col.getValueClass();
}
@Override
public boolean isEditable(R row, Settings settings, Void dataSource,
ServiceProvider serviceProvider) {
return col.isEditable(row);
}
@Override
public void setValueOf(R row, Object value, Settings settings, Void dataSource,
ServiceProvider serviceProvider) {
col.setValueOf(row, value);
}
@Override
@SuppressWarnings("unchecked")
public GColumnRenderer<Object> getColumnRenderer() {
return (GColumnRenderer<Object>) col.getRenderer();
}
@Override
public TableCellEditor getColumnEditor() {
return col.getEditor();
}
@Override
public int getColumnPreferredWidth() {
return col.getPreferredWidth();
}
@Override
public int getColumnMaxWidth() {
return col.getMaxWidth();
}
@Override
public int getColumnMinWidth() {
return col.getMinWidth();
}
@Override
public SettingsDefinition[] getSettingsDefinitions() {
SettingsDefinition[] defs = col.getSettingsDefinitions();
if (defs != null) {
return defs;
}
return super.getSettingsDefinitions();
}
}

View File

@@ -0,0 +1,136 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets.table;
import javax.swing.table.TableCellEditor;
import docking.widgets.table.ColumnSortState.SortDirection;
import ghidra.docking.settings.SettingsDefinition;
import ghidra.util.table.column.GColumnRenderer;
// NOTE: If I need to track indices, addSortListener
/**
* An interface on enums used to describe table columns
*
* @param <C> the type of the enum
* @param <R> the type of rows
*/
public interface EnumeratedTableColumn<C extends Enum<C>, R> {
/**
* Get the value class of cells in this column
*
* @return the class
*/
public Class<?> getValueClass();
/**
* Get the value of this column for the given row
*
* @param row the row
* @return the value
*/
public Object getValueOf(R row);
/**
* Get the name of this column
*
* @return the name
*/
public String getHeader();
/**
* Get the value of this column for the given row
*
* @param row the row
* @param value the new value
*/
default public void setValueOf(R row, Object value) {
throw new UnsupportedOperationException("Cell is not editable");
}
/**
* Check if this column can be modified for the given row
*
* @param row the row
* @return true if editable
*/
default public boolean isEditable(R row) {
return false;
}
/**
* Check if this column can be sorted
*
* <p>
* TODO: Either this should be implemented as ported to {@link GDynamicColumnTableModel}, or
* removed.
*
* @return true if sortable
*/
default public boolean isSortable() {
return true;
}
/**
* Check if this column should be visible by default
*
* @return true if visible
*/
default public boolean isVisible() {
return true;
}
/**
* Get the default sort direction for this column
*
* @return the sort direction
*/
default public SortDirection defaultSortDirection() {
return SortDirection.ASCENDING;
}
default public int getPreferredWidth() {
return AbstractGTableModel.WIDTH_UNDEFINED;
}
default public int getMinWidth() {
return AbstractGTableModel.WIDTH_UNDEFINED;
}
default public int getMaxWidth() {
return AbstractGTableModel.WIDTH_UNDEFINED;
}
/**
* Because of limitations with Java generics and Enumerations, type checking cannot be
* guaranteed here. The user must ensure that any returned by {@link #getValueOf(Object)} can be
* accepted by the renderer returned here. The framework will perform an unchecked cast of the
* renderer.
*
* @return the renderer
*/
default public GColumnRenderer<?> getRenderer() {
return null;
}
default public TableCellEditor getEditor() {
return null;
}
default public SettingsDefinition[] getSettingsDefinitions() {
return null;
}
}

View File

@@ -21,7 +21,6 @@ import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.Msg;

View File

@@ -0,0 +1,56 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets.table;
import java.util.Collections;
import java.util.List;
import docking.widgets.table.threaded.ThreadedTableModel;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.util.task.TaskMonitor;
public abstract class ThreadedEnumeratedColumnTableModel<
C extends Enum<C> & EnumeratedTableColumn<C, R>, R> extends ThreadedTableModel<R, Void> {
private final List<C> cols;
protected ThreadedEnumeratedColumnTableModel(ServiceProvider serviceProvider, String name,
Class<C> colType, TaskMonitor monitor, boolean loadIncrementally) {
super(name, serviceProvider, monitor, loadIncrementally);
this.cols = List.of(colType.getEnumConstants());
reloadColumns();
}
/**
* Get the default sort order of the table
*
* @return the list of columns in order of descending priority
*/
public List<C> defaultSortOrder() {
return Collections.emptyList();
}
@Override
protected TableColumnDescriptor<R> createTableColumnDescriptor() {
return EnumeratedColumnTableModel.createTableColumnDescriptor(cols, defaultSortOrder());
}
@Override
public Void getDataSource() {
return null;
}
}

View File

@@ -29,7 +29,6 @@ import javax.swing.table.TableColumn;
import org.junit.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
import generic.Span;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;

View File

@@ -24,9 +24,7 @@ import java.util.stream.Stream;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import docking.widgets.table.DefaultEnumeratedColumnTableModel;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import docking.widgets.table.GTable;
import docking.widgets.table.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.table.GhidraTableFilterPanel;
import ghidra.util.table.column.GColumnRenderer;

View File

@@ -23,9 +23,7 @@ import java.util.function.Function;
import javax.swing.*;
import docking.widgets.table.DefaultEnumeratedColumnProgramTableModel;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import docking.widgets.table.GTable;
import docking.widgets.table.*;
import generic.theme.GColor;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.emu.symz3.SymZ3RecordsExecution.RecInstruction;

View File

@@ -23,9 +23,7 @@ import java.util.function.Function;
import javax.swing.*;
import docking.widgets.table.DefaultEnumeratedColumnProgramTableModel;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import docking.widgets.table.GTable;
import docking.widgets.table.*;
import generic.theme.GColor;
import ghidra.app.plugin.processors.sleigh.template.OpTpl;
import ghidra.app.util.pcode.*;