From ab5c8000309d5a216351ce14b1905d6ac6dba7b3 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Tue, 27 Feb 2024 10:50:07 -0500 Subject: [PATCH] GP-4363: Various fixes and improvements for Elements tables. --- .../src/main/py/src/ghidradbg/commands.py | 2 +- .../src/main/py/src/ghidradbg/schema.xml | 2 +- .../src/main/py/src/ghidragdb/commands.py | 2 +- .../src/main/py/src/ghidragdb/schema.xml | 3 +- .../src/main/py/src/ghidralldb/commands.py | 2 +- .../src/main/py/src/ghidralldb/schema.xml | 2 +- .../gui/model/AbstractQueryTablePanel.java | 16 +- .../core/debug/gui/model/ModelQuery.java | 3 + .../debug/gui/model/ObjectTableModel.java | 208 ++++++++---------- .../debug/gui/model/ObjectsTablePanel.java | 75 +++++++ .../TracePathLastLifespanPlotColumn.java | 2 +- .../columns/TraceValueLifePlotColumn.java | 2 +- .../thread/DebuggerLegacyThreadsPanel.java | 5 +- .../gui/thread/DebuggerThreadsPanel.java | 7 +- .../gui/model/DebuggerModelProviderTest.java | 19 +- .../table/RangeCursorTableHeaderRenderer.java | 53 ++++- .../table/DemoSpanCellRendererTest.java | 3 +- 17 files changed, 258 insertions(+), 148 deletions(-) diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py index edd48caac2..82f37f592e 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py @@ -209,7 +209,7 @@ def start_trace(name): with open(schema_fn, 'r') as schema_file: schema_xml = schema_file.read() with STATE.trace.open_tx("Create Root Object"): - root = STATE.trace.create_root_object(schema_xml, 'Session') + root = STATE.trace.create_root_object(schema_xml, 'DbgengSession') root.set_value('_display', util.DBG_VERSION.full + ' via pybag') util.set_convenience_variable('_ghidra_tracing', "true") diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema.xml b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema.xml index bb8e16f4ed..48b0ea017f 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema.xml +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema.xml @@ -1,5 +1,5 @@ - + diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/commands.py b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/commands.py index 0ea042cb55..c958acb6db 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/commands.py +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/commands.py @@ -275,7 +275,7 @@ def start_trace(name): with open(schema_fn, 'r') as schema_file: schema_xml = schema_file.read() with STATE.trace.open_tx("Create Root Object"): - root = STATE.trace.create_root_object(schema_xml, 'Session') + root = STATE.trace.create_root_object(schema_xml, 'GdbSession') root.set_value('_display', 'GNU gdb ' + util.GDB_VERSION.full) STATE.trace.create_object(AVAILABLES_PATH).insert() STATE.trace.create_object(BREAKPOINTS_PATH).insert() diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/schema.xml b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/schema.xml index 9f9c092b74..8738e2adf3 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/schema.xml +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/schema.xml @@ -1,5 +1,5 @@ - + @@ -257,7 +257,6 @@ diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/commands.py b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/commands.py index 48213a432f..0396ad97cb 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/commands.py +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/commands.py @@ -261,7 +261,7 @@ def start_trace(name): with open(schema_fn, 'r') as schema_file: schema_xml = schema_file.read() with STATE.trace.open_tx("Create Root Object"): - root = STATE.trace.create_root_object(schema_xml, 'Session') + root = STATE.trace.create_root_object(schema_xml, 'LldbSession') root.set_value('_display', 'GNU lldb ' + util.LLDB_VERSION.full) util.set_convenience_variable('_ghidra_tracing', "true") diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/schema.xml b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/schema.xml index 15d22213ef..b3e28bb963 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/schema.xml +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/schema.xml @@ -1,5 +1,5 @@ - + diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTablePanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTablePanel.java index e2c5f95d99..bc1eb784e1 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTablePanel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTablePanel.java @@ -82,6 +82,10 @@ public abstract class AbstractQueryTablePanel computeSchemas(Trace trace) { TargetObjectSchema rootSchema = trace.getObjectManager().getRootSchema(); + if (rootSchema == null) { + return List.of(); + } return predicates.getPatterns() .stream() .map(p -> rootSchema.getSuccessorSchema(p.asPath())) diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectTableModel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectTableModel.java index 282f111540..f9bb8d6624 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectTableModel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectTableModel.java @@ -21,8 +21,9 @@ import java.util.stream.*; import org.apache.commons.lang3.ArrayUtils; -import docking.widgets.table.*; +import docking.widgets.table.DynamicTableColumn; import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener; +import docking.widgets.table.TableColumnDescriptor; import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow; import ghidra.app.plugin.core.debug.gui.model.columns.*; import ghidra.dbg.target.schema.SchemaContext; @@ -39,6 +40,7 @@ import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObjectValue; import ghidra.util.HTMLUtilities; import ghidra.util.NumericUtilities; +import ghidra.util.datastruct.ListenerSet; public class ObjectTableModel extends AbstractQueryTableModel { @@ -442,59 +444,85 @@ public class ObjectTableModel extends AbstractQueryTableModel { AttributeSchema attributeSchema) { String name = attributeSchema.getName(); Class type = computeAttributeType(ctx, attributeSchema); - return new AutoAttributeColumn<>(name, type); + return new AutoAttributeColumn<>(name, type, attributeSchema.isHidden()); } - public AutoAttributeColumn(String attributeName, Class attributeType) { + final boolean hidden; + + public AutoAttributeColumn(String attributeName, Class attributeType, boolean hidden) { super(attributeName, attributeType); + this.hidden = hidden; + } + + public boolean isHidden() { + return hidden; } } - // TODO: Save and restore these between sessions, esp., their settings + private TraceValueLifePlotColumn plotColumn; + private ListenerSet seekListeners; + private SeekListener mySeekListener; private Map> columnCache = new HashMap<>(); protected ObjectTableModel(Plugin plugin) { super("Object Model", plugin); } + protected TraceValueLifePlotColumn newPlotColumn() { + return new TraceValueLifePlotColumn(); + } + + protected TraceValueLifePlotColumn getPlotColumn() { + if (plotColumn == null) { + plotColumn = newPlotColumn(); + plotColumn.setFullRange(computeFullRange()); + getSeekListeners(); + plotColumn.addSeekListener(mySeekListener); + } + return plotColumn; + } + + protected ListenerSet getSeekListeners() { + if (seekListeners == null) { + seekListeners = new ListenerSet<>(SeekListener.class, true); + // Can't use the proxy directly, as Set will invoke hashCode/equals + mySeekListener = seekListeners.invoke()::accept; + } + return seekListeners; + } + @Override protected void traceChanged() { - reloadAttributeColumns(); + reloadColumns(); + fireTableStructureChanged(); updateTimelineMax(); super.traceChanged(); } @Override protected void queryChanged() { - reloadAttributeColumns(); + reloadColumns(); + fireTableStructureChanged(); super.queryChanged(); } - @Override - protected void showHiddenChanged() { - reloadAttributeColumns(); - super.showHiddenChanged(); - } - @Override protected void maxSnapChanged() { updateTimelineMax(); refresh(); } - protected void updateTimelineMax() { + protected Lifespan computeFullRange() { Long max = getTrace() == null ? null : getTrace().getTimeManager().getMaxSnap(); - Lifespan fullRange = Lifespan.span(0L, max == null ? 1 : max + 1); - int count = getColumnCount(); - for (int i = 0; i < count; i++) { - DynamicTableColumn column = getColumn(i); - if (column instanceof TraceValueLifePlotColumn plotCol) { - plotCol.setFullRange(fullRange); - } - } + return Lifespan.span(0L, max == null ? 1 : max + 1); } - protected List computeAttributeSchemas() { + protected void updateTimelineMax() { + Lifespan fullRange = computeFullRange(); + getPlotColumn().setFullRange(fullRange); + } + + protected List computeAttributes() { Trace trace = getTrace(); ModelQuery query = getQuery(); if (trace == null || query == null) { @@ -506,91 +534,10 @@ public class ObjectTableModel extends AbstractQueryTableModel { } SchemaContext ctx = rootSchema.getContext(); return query.computeAttributes(trace) - .filter(a -> isShowHidden() || !a.isHidden()) .filter(a -> !ctx.getSchema(a.getSchema()).isCanonicalContainer()) .collect(Collectors.toList()); } - protected void reloadAttributeColumns() { - List attributes; - Trace trace = getTrace(); - ModelQuery query = getQuery(); - if (trace == null || query == null || trace.getObjectManager().getRootSchema() == null) { - attributes = List.of(); - } - else { - SchemaContext ctx = trace.getObjectManager().getRootSchema().getContext(); - attributes = query.computeAttributes(trace) - .filter(a -> !ctx.getSchema(a.getSchema()).isCanonicalContainer()) - .collect(Collectors.toList()); - } - resyncAttributeColumns(attributes); - } - - protected Set> computeAttributeColumns( - Collection attributes) { - if (attributes == null) { - return Set.of(); - } - Trace trace = getTrace(); - if (trace == null) { - return Set.of(); - } - TargetObjectSchema rootSchema = trace.getObjectManager().getRootSchema(); - if (rootSchema == null) { - return Set.of(); - } - SchemaContext ctx = rootSchema.getContext(); - return attributes.stream() - .map(as -> columnCache.computeIfAbsent(ColKey.fromSchema(ctx, as), - ck -> AutoAttributeColumn.fromSchema(ctx, as))) - .collect(Collectors.toSet()); - } - - protected void resyncAttributeColumns(Collection attributes) { - Map> byVisible = attributes == null ? Map.of() - : attributes.stream() - .collect(Collectors.groupingBy(a -> !a.isHidden() || isShowHidden())); - Set> visibleColumns = - new HashSet<>(computeAttributeColumns(byVisible.get(true))); - Set> hiddenColumns = - new HashSet<>(computeAttributeColumns(byVisible.get(false))); - Set> toRemove = new HashSet<>(); - boolean[] removedIndices = new boolean[getColumnCount()]; - for (int i = 0; i < getColumnCount(); i++) { - DynamicTableColumn exists = getColumn(i); - if (!(exists instanceof AutoAttributeColumn)) { - continue; - } - if (!visibleColumns.remove(exists) && !hiddenColumns.remove(exists)) { - toRemove.add(exists); - removedIndices[i] = true; - } - } - TableSortState curSortState = getTableSortState(); - TableSortStateEditor newStateEditor = new TableSortStateEditor(); - for (ColumnSortState css : curSortState.getAllSortStates()) { - int index = css.getColumnModelIndex(); - if (removedIndices[index]) { - continue; // Don't add too new - } - int precedingRemoved = 0; - for (int i = 0; i < index; i++) { - if (removedIndices[index]) { - precedingRemoved++; - } - } - newStateEditor.addSortedColumn(index - precedingRemoved, css.getSortDirection()); - } - TableSortState newSortState = newStateEditor.createTableSortState(); - - setTableSortState(TableSortState.createUnsortedSortState()); - removeTableColumns(toRemove); - addTableColumns(visibleColumns, true); - addTableColumns(hiddenColumns, false); - setTableSortState(newSortState); - } - @Override protected Stream streamRows(Trace trace, ModelQuery query, Lifespan span) { return distinctCanonical(query.streamValues(trace, span) @@ -604,10 +551,37 @@ public class ObjectTableModel extends AbstractQueryTableModel { descriptor.addVisibleColumn(new TraceValueKeyColumn(), 1, true); descriptor.addVisibleColumn(new TraceValueValColumn()); descriptor.addVisibleColumn(new TraceValueLifeColumn(), 2, true); - descriptor.addHiddenColumn(new TraceValueLifePlotColumn()); + descriptor.addHiddenColumn(getPlotColumn()); + + appendAttributeColumns(descriptor); + return descriptor; } + protected void appendAttributeColumns(TableColumnDescriptor descriptor) { + Trace trace = getTrace(); + if (trace == null) { + return; + } + TargetObjectSchema rootSchema = trace.getObjectManager().getRootSchema(); + if (rootSchema == null) { + return; + } + SchemaContext ctx = rootSchema.getContext(); + List attributes = computeAttributes(); + for (AttributeSchema as : attributes) { + TraceValueObjectAttributeColumn column = + columnCache.computeIfAbsent(ColKey.fromSchema(ctx, as), + ck -> AutoAttributeColumn.fromSchema(ctx, as)); + if (as.isHidden()) { + descriptor.addHiddenColumn(column); + } + else { + descriptor.addVisibleColumn(column); + } + } + } + @Override public ValueRow findTraceObject(TraceObject object) { for (ValueRow row : getModelData()) { @@ -675,25 +649,12 @@ public class ObjectTableModel extends AbstractQueryTableModel { @Override protected void snapChanged() { super.snapChanged(); - long snap = getSnap(); - int count = getColumnCount(); - for (int i = 0; i < count; i++) { - DynamicTableColumn column = getColumn(i); - if (column instanceof TraceValueLifePlotColumn plotCol) { - plotCol.setSnap(snap); - } - } + getPlotColumn().setSnap(getSnap()); } @Override public void addSeekListener(SeekListener listener) { - int count = getColumnCount(); - for (int i = 0; i < count; i++) { - DynamicTableColumn column = getColumn(i); - if (column instanceof TraceValueLifePlotColumn plotCol) { - plotCol.addSeekListener(listener); - } - } + getSeekListeners().add(listener); } @Override @@ -765,4 +726,13 @@ public class ObjectTableModel extends AbstractQueryTableModel { ServiceProvider serviceProvider) { editable.setValue(t, (COLUMN_TYPE) aValue, settings, dataSource, serviceProvider); } + + @Override + public boolean isVisibleByDefault(int modelIndex) { + DynamicTableColumn column = tableColumns.get(modelIndex); + if (column instanceof AutoAttributeColumn && isShowHidden()) { + return true; + } + return super.isVisibleByDefault(modelIndex); + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectsTablePanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectsTablePanel.java index 8e691e40b8..18321eda17 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectsTablePanel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectsTablePanel.java @@ -16,17 +16,25 @@ package ghidra.app.plugin.core.debug.gui.model; import java.awt.Component; +import java.util.List; +import java.util.stream.Collectors; import javax.swing.JTable; import javax.swing.JTextField; +import javax.swing.table.TableColumn; +import docking.widgets.table.GTableColumnModel; import docking.widgets.table.GTableTextCellEditor; import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.*; +import ghidra.dbg.target.schema.TargetObjectSchema; import ghidra.framework.plugintool.Plugin; +import ghidra.trace.model.Trace; import ghidra.trace.model.target.TraceObject; public class ObjectsTablePanel extends AbstractQueryTablePanel { + private static final String DEFAULT_PREF_KEY = "DEFAULT"; + private static class PropertyEditor extends GTableTextCellEditor { private final JTextField textField; @@ -73,4 +81,71 @@ public class ObjectsTablePanel extends AbstractQueryTablePanel schemas = query.computeSchemas(trace); + if (schemas.isEmpty()) { + return DEFAULT_PREF_KEY; + } + TargetObjectSchema rootSchema = trace.getObjectManager().getRootSchema(); + if (rootSchema == null) { + return DEFAULT_PREF_KEY; + } + return rootSchema + ":" + schemas + .stream() + .map(s -> s.getName().toString()) + .collect(Collectors.joining(",")) + + ":" + (isShowHidden() ? "show" : "hide"); + } + + protected void showHiddenColumns(boolean show) { + if (table.getColumnModel() instanceof GTableColumnModel columnModel) { + for (TableColumn tCol : columnModel.getAllColumns()) { + int modelIndex = tCol.getModelIndex(); + if (tableModel + .getColumn(modelIndex) instanceof AutoAttributeColumn attrCol) { + if (attrCol.isHidden()) { + columnModel.setVisible(tCol, show); + } + } + } + } + } + + protected void reloadPreferences() { + String prefKey = computePreferenceKey(); + if (!prefKey.equals(table.getPreferenceKey())) { + table.setPreferenceKey(prefKey); + } + } + + protected void resyncAttributeVisibility() { + showHiddenColumns(isShowHidden()); + } + + @Override + protected void coordinatesChanged() { + super.coordinatesChanged(); + reloadPreferences(); + } + + @Override + protected void queryChanged() { + super.queryChanged(); + reloadPreferences(); + } + + @Override + protected void showHiddenChanged() { + super.showHiddenChanged(); + resyncAttributeVisibility(); + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TracePathLastLifespanPlotColumn.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TracePathLastLifespanPlotColumn.java index 07bc79ffb5..57cbcaf026 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TracePathLastLifespanPlotColumn.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TracePathLastLifespanPlotColumn.java @@ -31,7 +31,7 @@ public class TracePathLastLifespanPlotColumn private final SpanTableCellRenderer cellRenderer = new SpanTableCellRenderer<>(); private final RangeCursorTableHeaderRenderer headerRenderer = - new RangeCursorTableHeaderRenderer<>(0L); + new RangeCursorTableHeaderRenderer<>(0L, this); @Override public String getColumnName() { 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 2021a4365a..2e1c018f05 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 @@ -32,7 +32,7 @@ public class TraceValueLifePlotColumn private final SpanSetTableCellRenderer cellRenderer = new SpanSetTableCellRenderer<>(); private final RangeCursorTableHeaderRenderer headerRenderer = - new RangeCursorTableHeaderRenderer<>(0L); + new RangeCursorTableHeaderRenderer<>(0L, this); @Override public String getColumnName() { 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 b84f361f42..664c6969f7 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 @@ -189,8 +189,7 @@ public class DebuggerLegacyThreadsPanel extends JPanel { /* package access for testing */ final SpanTableCellRenderer spanRenderer = new SpanTableCellRenderer<>(); - final RangeCursorTableHeaderRenderer headerRenderer = - new RangeCursorTableHeaderRenderer<>(0L); + final RangeCursorTableHeaderRenderer headerRenderer; final TableCellRenderer boldCurrentRenderer = new AbstractGColumnRenderer() { @Override @@ -226,6 +225,8 @@ public class DebuggerLegacyThreadsPanel extends JPanel { this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this); threadTableModel = new ThreadTableModel(provider); + headerRenderer = new RangeCursorTableHeaderRenderer<>(0L, + threadTableModel.getColumn(ThreadTableColumns.PLOT.ordinal())); threadTable = new GhidraTable(threadTableModel); threadTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); add(new JScrollPane(threadTable)); 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 a40c0cfd53..c398fa740f 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 @@ -195,6 +195,11 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel createTableColumnDescriptor() { TableColumnDescriptor descriptor = new TableColumnDescriptor<>(); @@ -206,7 +211,7 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel> protected class ForSeekMouseListener extends MouseAdapter { + private boolean checkRemove() { + if (savedTable == null) { + return false; + } + TableModel unwrapped = RowObjectTableModel.unwrap(savedTable.getModel()); + if (!(unwrapped instanceof DynamicColumnTableModel model)) { + setSavedTable(null); + return true; + } + int count = model.getColumnCount(); + for (int i = 0; i < count; i++) { + if (model.getColumn(i) == col) { + return false; + } + } + setSavedTable(null); + return true; + } + @Override public void mouseClicked(MouseEvent e) { + if (checkRemove()) { + return; + } if ((e.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) != 0) { return; } if ((e.getButton() != MouseEvent.BUTTON1)) { return; } - e.consume(); doSeek(e); } @Override public void mouseDragged(MouseEvent e) { + if (checkRemove()) { + return; + } int onmask = MouseEvent.BUTTON1_DOWN_MASK; int offmask = MouseEvent.SHIFT_DOWN_MASK; if ((e.getModifiersEx() & (onmask | offmask)) != onmask) { return; } - e.consume(); doSeek(e); } @@ -84,6 +106,7 @@ public class RangeCursorTableHeaderRenderer> double pos = span * (e.getX() - colX) / myViewCol.getWidth() + fullRangeDouble.min(); + e.consume(); listeners.invoke().accept(pos); } } @@ -99,6 +122,7 @@ public class RangeCursorTableHeaderRenderer> protected Span fullRange; protected N pos; + protected final DynamicTableColumn col; protected double doublePos; private JTable savedTable; @@ -107,8 +131,9 @@ public class RangeCursorTableHeaderRenderer> private final ForSeekMouseListener forSeekMouseListener = new ForSeekMouseListener(); private final ListenerSet listeners = new ListenerSet<>(SeekListener.class, true); - public RangeCursorTableHeaderRenderer(N pos) { + public RangeCursorTableHeaderRenderer(N pos, DynamicTableColumn col) { this.pos = pos; + this.col = col; } @Override @@ -123,6 +148,9 @@ public class RangeCursorTableHeaderRenderer> } protected void setSavedTable(JTable table) { + if (savedTable == table) { + return; + } if (savedTable != null) { JTableHeader header = savedTable.getTableHeader(); header.removeMouseListener(forSeekMouseListener); @@ -131,8 +159,23 @@ public class RangeCursorTableHeaderRenderer> savedTable = table; if (savedTable != null) { JTableHeader header = savedTable.getTableHeader(); + // I need firstsies. SHIFT key will pass event down the chain. + MouseListener[] curMouseListeners = header.getMouseListeners(); + MouseMotionListener[] curMotionListeners = header.getMouseMotionListeners(); + for (MouseListener l : curMouseListeners) { + header.removeMouseListener(l); + } + for (MouseMotionListener l : curMotionListeners) { + header.removeMouseMotionListener(l); + } header.addMouseListener(forSeekMouseListener); header.addMouseMotionListener(forSeekMouseListener); + for (MouseListener l : curMouseListeners) { + header.addMouseListener(l); + } + for (MouseMotionListener l : curMotionListeners) { + header.addMouseMotionListener(l); + } } } diff --git a/Ghidra/Debug/ProposedUtils/src/test/java/docking/widgets/table/DemoSpanCellRendererTest.java b/Ghidra/Debug/ProposedUtils/src/test/java/docking/widgets/table/DemoSpanCellRendererTest.java index fed523a4cb..78c6e90868 100644 --- a/Ghidra/Debug/ProposedUtils/src/test/java/docking/widgets/table/DemoSpanCellRendererTest.java +++ b/Ghidra/Debug/ProposedUtils/src/test/java/docking/widgets/table/DemoSpanCellRendererTest.java @@ -214,8 +214,9 @@ public class DemoSpanCellRendererTest extends AbstractGhidraHeadedIntegrationTes TableColumn column = table.getColumnModel().getColumn(MyColumns.LIFESPAN.ordinal()); SpanTableCellRenderer rangeRenderer = new SpanTableCellRenderer<>(); + DynamicTableColumn col = model.getColumn(MyColumns.LIFESPAN.ordinal()); RangeCursorTableHeaderRenderer headerRenderer = - new RangeCursorTableHeaderRenderer<>(0); + new RangeCursorTableHeaderRenderer<>(0, col); column.setCellRenderer(rangeRenderer); column.setHeaderRenderer(headerRenderer);