diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTableModel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTableModel.java index 610ef4d783..a027941763 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTableModel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTableModel.java @@ -48,6 +48,7 @@ public abstract class AbstractQueryTableModel extends ThreadedTableModel { long snap = Math.round(pos); - if (current.getTrace() == null || snap < 0) { + if (snap < 0) { snap = 0; } + long max = + current.getTrace() == null ? 0 : current.getTrace().getTimeManager().getMaxSnap(); + if (snap > max) { + snap = max; + } traceManager.activateSnap(snap); }; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TraceValueLifePlotColumn.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TraceValueLifePlotColumn.java index 1a5b9c9151..2021a4365a 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TraceValueLifePlotColumn.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TraceValueLifePlotColumn.java @@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.gui.model.columns; import docking.widgets.table.*; import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener; +import generic.Span; import generic.Span.SpanSet; import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow; import ghidra.docking.settings.Settings; @@ -59,6 +60,10 @@ public class TraceValueLifePlotColumn headerRenderer.setFullRange(fullRange); } + public Span getFullRange() { + return cellRenderer.getFullRange(); + } + public void setSnap(long snap) { headerRenderer.setCursorPosition(snap); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerLegacyThreadsPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerLegacyThreadsPanel.java index 61bdce730d..178836e1eb 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerLegacyThreadsPanel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerLegacyThreadsPanel.java @@ -274,9 +274,14 @@ public class DebuggerLegacyThreadsPanel extends JPanel { headerRenderer.addSeekListener(seekListener = pos -> { long snap = Math.round(pos); - if (current.getTrace() == null || snap < 0) { + if (snap < 0) { snap = 0; } + long max = + current.getTrace() == null ? 0 : current.getTrace().getTimeManager().getMaxSnap(); + if (snap > max) { + snap = max; + } traceManager.activateSnap(snap); myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap); provider.legacyThreadsPanelContextChanged(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsPanel.java index 5c3b4c98ce..d8f159b0d4 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsPanel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsPanel.java @@ -170,9 +170,14 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel { long snap = Math.round(pos); - if (current.getTrace() == null || snap < 0) { + if (snap < 0) { snap = 0; } + long max = + current.getTrace() == null ? 0 : current.getTrace().getTimeManager().getMaxSnap(); + if (snap > max) { + snap = max; + } traceManager.activateSnap(snap); }; diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProviderTest.java index 9c5508ab41..5c5a6b291e 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProviderTest.java @@ -19,7 +19,7 @@ import static org.junit.Assert.*; import java.awt.event.MouseEvent; import java.util.*; -import java.util.stream.Collectors; +import java.util.stream.*; import org.jdom.JDOMException; import org.junit.*; @@ -36,6 +36,7 @@ import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.PrimitiveRow; import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow; import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.AbstractNode; import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow; +import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueLifePlotColumn; import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueValColumn; import ghidra.dbg.target.TargetEventScope; import ghidra.dbg.target.TargetObject; @@ -1162,4 +1163,75 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerTest waitForPass(() -> assertEquals(14, modelProvider.elementsTablePanel.tableModel.getModelData().size())); } + + protected Stream> streamColumns( + GDynamicColumnTableModel model) { + return IntStream.range(0, model.getColumnCount()).mapToObj(model::getColumn); + } + + protected > T findColumnOfType( + GDynamicColumnTableModel model, Class type) { + return streamColumns(model) + .flatMap(c -> type.isInstance(c) ? Stream.of(type.cast(c)) : Stream.of()) + .findAny() + .orElse(null); + } + + @Test + public void testLifePlotColumnFitsSnapshotsOnActivate() throws Throwable { + TraceValueLifePlotColumn plotCol = findColumnOfType( + modelProvider.elementsTablePanel.tableModel, TraceValueLifePlotColumn.class); + createTraceAndPopulateObjects(); + + traceManager.activateTrace(tb.trace); + waitForSwing(); + + // NB. The plot adds a margin of 1 + assertEquals(Lifespan.span(0, 21), plotCol.getFullRange()); + } + + @Test + public void testLifePlotColumnFitsSnapshotsOnAddSnapshot() throws Throwable { + TraceValueLifePlotColumn plotCol = findColumnOfType( + modelProvider.elementsTablePanel.tableModel, TraceValueLifePlotColumn.class); + createTraceAndPopulateObjects(); + + traceManager.activateTrace(tb.trace); + waitForSwing(); + + try (Transaction tx = tb.startTransaction()) { + tb.trace.getTimeManager().getSnapshot(30, true); + } + waitForDomainObject(tb.trace); + + // NB. The plot adds a margin of 1 + assertEquals(Lifespan.span(0, 31), plotCol.getFullRange()); + + try (Transaction tx = tb.startTransaction()) { + tb.trace.getTimeManager().getSnapshot(31, true); + } + waitForDomainObject(tb.trace); + + assertEquals(Lifespan.span(0, 32), plotCol.getFullRange()); + } + + @Test + public void testLifePlotColumnFitsSnapshotsOnAddSnapshotSupressEvents() throws Throwable { + TraceValueLifePlotColumn plotCol = findColumnOfType( + modelProvider.elementsTablePanel.tableModel, TraceValueLifePlotColumn.class); + createTraceAndPopulateObjects(); + + traceManager.activateTrace(tb.trace); + waitForSwing(); + + tb.trace.setEventsEnabled(false); + try (Transaction tx = tb.startTransaction()) { + tb.trace.getTimeManager().getSnapshot(30, true); + } + tb.trace.setEventsEnabled(true); + waitForDomainObject(tb.trace); + + // NB. The plot adds a margin of 1 + assertEquals(Lifespan.span(0, 31), plotCol.getFullRange()); + } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RangeCursorTableHeaderRenderer.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RangeCursorTableHeaderRenderer.java index 60b52ff3aa..6a13383064 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RangeCursorTableHeaderRenderer.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RangeCursorTableHeaderRenderer.java @@ -42,8 +42,8 @@ public class RangeCursorTableHeaderRenderer> if ((e.getButton() != MouseEvent.BUTTON1)) { return; } - doSeek(e); e.consume(); + doSeek(e); } @Override @@ -53,8 +53,8 @@ public class RangeCursorTableHeaderRenderer> if ((e.getModifiersEx() & (onmask | offmask)) != onmask) { return; } - doSeek(e); e.consume(); + doSeek(e); } protected void doSeek(MouseEvent e) {