mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 10:49:34 +02:00
GP-2067: Implement header renderer override and add cursor to ModelProvider's Plot columns
This commit is contained in:
parent
45165ea167
commit
cdd6f3d72e
16 changed files with 250 additions and 83 deletions
|
@ -21,6 +21,7 @@ import java.util.stream.Stream;
|
||||||
|
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
|
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||||
import docking.widgets.table.threaded.ThreadedTableModel;
|
import docking.widgets.table.threaded.ThreadedTableModel;
|
||||||
import ghidra.framework.plugintool.Plugin;
|
import ghidra.framework.plugintool.Plugin;
|
||||||
import ghidra.trace.database.DBTraceUtils;
|
import ghidra.trace.database.DBTraceUtils;
|
||||||
|
@ -309,4 +310,6 @@ public abstract class AbstractQueryTableModel<T> extends ThreadedTableModel<T, T
|
||||||
public abstract void setDiffColorSel(Color diffColorSel);
|
public abstract void setDiffColorSel(Color diffColorSel);
|
||||||
|
|
||||||
public abstract T findTraceObject(TraceObject object);
|
public abstract T findTraceObject(TraceObject object);
|
||||||
|
|
||||||
|
public abstract void addSeekListener(SeekListener listener);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import javax.swing.event.ListSelectionListener;
|
||||||
|
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
|
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.framework.plugintool.Plugin;
|
import ghidra.framework.plugintool.Plugin;
|
||||||
import ghidra.trace.model.target.TraceObject;
|
import ghidra.trace.model.target.TraceObject;
|
||||||
|
@ -146,6 +147,10 @@ public abstract class AbstractQueryTablePanel<T> extends JPanel {
|
||||||
table.removeKeyListener(l);
|
table.removeKeyListener(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addSeekListener(SeekListener listener) {
|
||||||
|
tableModel.addSeekListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
public void setSelectionMode(int selectionMode) {
|
public void setSelectionMode(int selectionMode) {
|
||||||
table.setSelectionMode(selectionMode);
|
table.setSelectionMode(selectionMode);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import javax.swing.*;
|
||||||
import docking.*;
|
import docking.*;
|
||||||
import docking.action.DockingAction;
|
import docking.action.DockingAction;
|
||||||
import docking.action.ToggleDockingAction;
|
import docking.action.ToggleDockingAction;
|
||||||
|
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||||
import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin;
|
import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||||
|
@ -111,6 +112,14 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||||
|
|
||||||
DebuggerObjectActionContext myActionContext;
|
DebuggerObjectActionContext myActionContext;
|
||||||
|
|
||||||
|
private SeekListener seekListener = pos -> {
|
||||||
|
long snap = Math.round(pos);
|
||||||
|
if (current.getTrace() == null || snap < 0) {
|
||||||
|
snap = 0;
|
||||||
|
}
|
||||||
|
traceManager.activateSnap(snap);
|
||||||
|
};
|
||||||
|
|
||||||
public DebuggerModelProvider(DebuggerModelPlugin plugin, boolean isClone) {
|
public DebuggerModelProvider(DebuggerModelPlugin plugin, boolean isClone) {
|
||||||
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_MODEL, plugin.getName());
|
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_MODEL, plugin.getName());
|
||||||
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||||
|
@ -349,6 +358,9 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||||
e.consume();
|
e.consume();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
elementsTablePanel.addSeekListener(seekListener);
|
||||||
|
attributesTablePanel.addSeekListener(seekListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -23,6 +23,7 @@ import java.util.stream.Stream;
|
||||||
import com.google.common.collect.*;
|
import com.google.common.collect.*;
|
||||||
|
|
||||||
import docking.widgets.table.DynamicTableColumn;
|
import docking.widgets.table.DynamicTableColumn;
|
||||||
|
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||||
import docking.widgets.table.TableColumnDescriptor;
|
import docking.widgets.table.TableColumnDescriptor;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.columns.*;
|
import ghidra.app.plugin.core.debug.gui.model.columns.*;
|
||||||
|
@ -419,4 +420,15 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||||
column.setDiffColorSel(diffColorSel);
|
column.setDiffColorSel(diffColorSel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void snapChanged() {
|
||||||
|
super.snapChanged();
|
||||||
|
lifePlotColumn.setSnap(getSnap());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addSeekListener(SeekListener listener) {
|
||||||
|
lifePlotColumn.addSeekListener(listener);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.util.stream.Stream;
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
import docking.widgets.table.TableColumnDescriptor;
|
import docking.widgets.table.TableColumnDescriptor;
|
||||||
|
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.columns.*;
|
import ghidra.app.plugin.core.debug.gui.model.columns.*;
|
||||||
import ghidra.framework.plugintool.Plugin;
|
import ghidra.framework.plugintool.Plugin;
|
||||||
|
@ -168,4 +169,15 @@ public class PathTableModel extends AbstractQueryTableModel<PathRow> {
|
||||||
public void setDiffColorSel(Color diffColorSel) {
|
public void setDiffColorSel(Color diffColorSel) {
|
||||||
valueColumn.setDiffColorSel(diffColorSel);
|
valueColumn.setDiffColorSel(diffColorSel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void snapChanged() {
|
||||||
|
super.snapChanged();
|
||||||
|
lifespanPlotColumn.setSnap(getSnap());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addSeekListener(SeekListener listener) {
|
||||||
|
lifespanPlotColumn.addSeekListener(listener);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,8 @@ package ghidra.app.plugin.core.debug.gui.model.columns;
|
||||||
|
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
import docking.widgets.table.AbstractDynamicTableColumn;
|
import docking.widgets.table.*;
|
||||||
import docking.widgets.table.RangeTableCellRenderer;
|
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
||||||
import ghidra.docking.settings.Settings;
|
import ghidra.docking.settings.Settings;
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
|
@ -30,6 +30,8 @@ public class TracePathLastLifespanPlotColumn
|
||||||
extends AbstractDynamicTableColumn<PathRow, Range<Long>, Trace> {
|
extends AbstractDynamicTableColumn<PathRow, Range<Long>, Trace> {
|
||||||
|
|
||||||
private final RangeTableCellRenderer<Long> cellRenderer = new RangeTableCellRenderer<>();
|
private final RangeTableCellRenderer<Long> cellRenderer = new RangeTableCellRenderer<>();
|
||||||
|
private final RangeCursorTableHeaderRenderer<Long> headerRenderer =
|
||||||
|
new RangeCursorTableHeaderRenderer<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
|
@ -51,10 +53,21 @@ public class TracePathLastLifespanPlotColumn
|
||||||
return cellRenderer;
|
return cellRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: header renderer
|
@Override
|
||||||
|
public GTableHeaderRenderer getHeaderRenderer() {
|
||||||
|
return headerRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
public void setFullRange(Range<Long> fullRange) {
|
public void setFullRange(Range<Long> fullRange) {
|
||||||
cellRenderer.setFullRange(fullRange);
|
cellRenderer.setFullRange(fullRange);
|
||||||
// TODO: header, too
|
headerRenderer.setFullRange(fullRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSnap(long snap) {
|
||||||
|
headerRenderer.setCursorPosition(snap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addSeekListener(SeekListener listener) {
|
||||||
|
headerRenderer.addSeekListener(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@ package ghidra.app.plugin.core.debug.gui.model.columns;
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
import com.google.common.collect.RangeSet;
|
import com.google.common.collect.RangeSet;
|
||||||
|
|
||||||
import docking.widgets.table.AbstractDynamicTableColumn;
|
import docking.widgets.table.*;
|
||||||
import docking.widgets.table.RangeSetTableCellRenderer;
|
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||||
import ghidra.docking.settings.Settings;
|
import ghidra.docking.settings.Settings;
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
|
@ -30,6 +30,8 @@ public class TraceValueLifePlotColumn
|
||||||
extends AbstractDynamicTableColumn<ValueRow, RangeSet<Long>, Trace> {
|
extends AbstractDynamicTableColumn<ValueRow, RangeSet<Long>, Trace> {
|
||||||
|
|
||||||
private final RangeSetTableCellRenderer<Long> cellRenderer = new RangeSetTableCellRenderer<>();
|
private final RangeSetTableCellRenderer<Long> cellRenderer = new RangeSetTableCellRenderer<>();
|
||||||
|
private final RangeCursorTableHeaderRenderer<Long> headerRenderer =
|
||||||
|
new RangeCursorTableHeaderRenderer<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
|
@ -47,10 +49,21 @@ public class TraceValueLifePlotColumn
|
||||||
return cellRenderer;
|
return cellRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: The header renderer
|
@Override
|
||||||
|
public GTableHeaderRenderer getHeaderRenderer() {
|
||||||
|
return headerRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
public void setFullRange(Range<Long> fullRange) {
|
public void setFullRange(Range<Long> fullRange) {
|
||||||
cellRenderer.setFullRange(fullRange);
|
cellRenderer.setFullRange(fullRange);
|
||||||
// TODO: set header's full range, too
|
headerRenderer.setFullRange(fullRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSnap(long snap) {
|
||||||
|
headerRenderer.setCursorPosition(snap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addSeekListener(SeekListener listener) {
|
||||||
|
headerRenderer.addSeekListener(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ import java.awt.BorderLayout;
|
||||||
import java.awt.Rectangle;
|
import java.awt.Rectangle;
|
||||||
import java.awt.event.MouseAdapter;
|
import java.awt.event.MouseAdapter;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.util.*;
|
import java.util.Objects;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.event.ListSelectionEvent;
|
import javax.swing.event.ListSelectionEvent;
|
||||||
|
@ -35,6 +35,7 @@ import docking.widgets.HorizontalTabPanel;
|
||||||
import docking.widgets.HorizontalTabPanel.TabListCellRenderer;
|
import docking.widgets.HorizontalTabPanel.TabListCellRenderer;
|
||||||
import docking.widgets.dialogs.InputDialog;
|
import docking.widgets.dialogs.InputDialog;
|
||||||
import docking.widgets.table.*;
|
import docking.widgets.table.*;
|
||||||
|
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
|
@ -206,7 +207,9 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
DockingAction actionCloseDeadTraces;
|
DockingAction actionCloseDeadTraces;
|
||||||
DockingAction actionCloseAllTraces;
|
DockingAction actionCloseAllTraces;
|
||||||
|
|
||||||
Set<Object> strongRefs = new HashSet<>(); // Eww
|
// strong refs
|
||||||
|
ToToggleSelectionListener toToggleSelectionListener;
|
||||||
|
SeekListener seekListener;
|
||||||
|
|
||||||
public DebuggerThreadsProvider(final DebuggerThreadsPlugin plugin) {
|
public DebuggerThreadsProvider(final DebuggerThreadsPlugin plugin) {
|
||||||
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_THREADS, plugin.getName());
|
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_THREADS, plugin.getName());
|
||||||
|
@ -220,8 +223,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
buildMainPanel();
|
buildMainPanel();
|
||||||
|
|
||||||
// TODO: Consider a custom cell renderer in the table instead of a timeline widget?
|
|
||||||
// TODO: Should I receive clicks on that renderer to seek to a given snap?
|
|
||||||
setDefaultWindowPosition(WindowPosition.BOTTOM);
|
setDefaultWindowPosition(WindowPosition.BOTTOM);
|
||||||
|
|
||||||
myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getViewSnap());
|
myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getViewSnap());
|
||||||
|
@ -231,11 +232,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> T strongRef(T t) {
|
|
||||||
strongRefs.add(t);
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
public void setModelService(DebuggerModelService modelService) {
|
public void setModelService(DebuggerModelService modelService) {
|
||||||
if (this.modelService != null) {
|
if (this.modelService != null) {
|
||||||
|
@ -471,7 +467,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
colPlot.setCellRenderer(rangeRenderer);
|
colPlot.setCellRenderer(rangeRenderer);
|
||||||
colPlot.setHeaderRenderer(headerRenderer);
|
colPlot.setHeaderRenderer(headerRenderer);
|
||||||
|
|
||||||
headerRenderer.addSeekListener(threadTable, ThreadTableColumns.PLOT.ordinal(), pos -> {
|
headerRenderer.addSeekListener(seekListener = pos -> {
|
||||||
long snap = Math.round(pos);
|
long snap = Math.round(pos);
|
||||||
if (current.getTrace() == null || snap < 0) {
|
if (current.getTrace() == null || snap < 0) {
|
||||||
snap = 0;
|
snap = 0;
|
||||||
|
@ -483,7 +479,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createActions() {
|
protected void createActions() {
|
||||||
// TODO: Make other actions use builder?
|
|
||||||
actionStepSnapBackward = StepSnapBackwardAction.builder(plugin)
|
actionStepSnapBackward = StepSnapBackwardAction.builder(plugin)
|
||||||
.enabledWhen(this::isStepSnapBackwardEnabled)
|
.enabledWhen(this::isStepSnapBackwardEnabled)
|
||||||
.enabled(false)
|
.enabled(false)
|
||||||
|
@ -521,8 +516,8 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
.enabledWhen(c -> current.getTrace() != null)
|
.enabledWhen(c -> current.getTrace() != null)
|
||||||
.onAction(c -> activatedGoToTime())
|
.onAction(c -> activatedGoToTime())
|
||||||
.buildAndInstallLocal(this);
|
.buildAndInstallLocal(this);
|
||||||
traceManager.addSynchronizeFocusChangeListener(
|
traceManager.addSynchronizeFocusChangeListener(toToggleSelectionListener =
|
||||||
strongRef(new ToToggleSelectionListener(actionSyncFocus)));
|
new ToToggleSelectionListener(actionSyncFocus));
|
||||||
|
|
||||||
actionCloseTrace = CloseTraceAction.builderPopup(plugin)
|
actionCloseTrace = CloseTraceAction.builderPopup(plugin)
|
||||||
.withContext(DebuggerTraceFileActionContext.class)
|
.withContext(DebuggerTraceFileActionContext.class)
|
||||||
|
|
|
@ -25,8 +25,70 @@ import javax.swing.table.*;
|
||||||
|
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
|
import ghidra.util.datastruct.ListenerSet;
|
||||||
|
|
||||||
public class RangeCursorTableHeaderRenderer<N extends Number & Comparable<N>>
|
public class RangeCursorTableHeaderRenderer<N extends Number & Comparable<N>>
|
||||||
extends GTableHeaderRenderer implements RangedRenderer<N> {
|
extends GTableHeaderRenderer implements RangedRenderer<N> {
|
||||||
|
|
||||||
|
public interface SeekListener extends Consumer<Double> {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class ForSeekMouseListener extends MouseAdapter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(MouseEvent e) {
|
||||||
|
if ((e.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((e.getButton() != MouseEvent.BUTTON1)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
doSeek(e);
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseDragged(MouseEvent e) {
|
||||||
|
int onmask = MouseEvent.BUTTON1_DOWN_MASK;
|
||||||
|
int offmask = MouseEvent.SHIFT_DOWN_MASK;
|
||||||
|
if ((e.getModifiersEx() & (onmask | offmask)) != onmask) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
doSeek(e);
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doSeek(MouseEvent e) {
|
||||||
|
TableColumnModel colModel = savedTable.getColumnModel();
|
||||||
|
JTableHeader header = savedTable.getTableHeader();
|
||||||
|
TableColumn myViewCol = colModel.getColumn(savedViewColumn);
|
||||||
|
if (header.getResizingColumn() != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int clickedViewColIdx = colModel.getColumnIndexAtX(e.getX());
|
||||||
|
if (clickedViewColIdx != savedViewColumn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TableColumn draggedViewCol = header.getDraggedColumn();
|
||||||
|
if (draggedViewCol == myViewCol) {
|
||||||
|
header.setDraggedColumn(null);
|
||||||
|
}
|
||||||
|
else if (draggedViewCol != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int colX = 0;
|
||||||
|
for (int i = 0; i < clickedViewColIdx; i++) {
|
||||||
|
colX += colModel.getColumn(i).getWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
double pos =
|
||||||
|
span * (e.getX() - colX) / myViewCol.getWidth() + fullRangeDouble.lowerEndpoint();
|
||||||
|
listeners.fire.accept(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected final static int ARROW_SIZE = 10;
|
protected final static int ARROW_SIZE = 10;
|
||||||
protected final static Polygon ARROW = new Polygon(
|
protected final static Polygon ARROW = new Polygon(
|
||||||
new int[] { 0, -ARROW_SIZE, -ARROW_SIZE },
|
new int[] { 0, -ARROW_SIZE, -ARROW_SIZE },
|
||||||
|
@ -40,6 +102,12 @@ public class RangeCursorTableHeaderRenderer<N extends Number & Comparable<N>>
|
||||||
protected N pos;
|
protected N pos;
|
||||||
protected double doublePos;
|
protected double doublePos;
|
||||||
|
|
||||||
|
private JTable savedTable;
|
||||||
|
private int savedViewColumn;
|
||||||
|
|
||||||
|
private final ForSeekMouseListener forSeekMouseListener = new ForSeekMouseListener();
|
||||||
|
private final ListenerSet<SeekListener> listeners = new ListenerSet<>(SeekListener.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setFullRange(Range<N> fullRange) {
|
public void setFullRange(Range<N> fullRange) {
|
||||||
this.fullRangeDouble = RangedRenderer.validateViewRange(fullRange);
|
this.fullRangeDouble = RangedRenderer.validateViewRange(fullRange);
|
||||||
|
@ -51,6 +119,28 @@ public class RangeCursorTableHeaderRenderer<N extends Number & Comparable<N>>
|
||||||
this.doublePos = pos.doubleValue();
|
this.doublePos = pos.doubleValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void setSavedTable(JTable table) {
|
||||||
|
if (savedTable != null) {
|
||||||
|
JTableHeader header = savedTable.getTableHeader();
|
||||||
|
header.removeMouseListener(forSeekMouseListener);
|
||||||
|
header.removeMouseMotionListener(forSeekMouseListener);
|
||||||
|
}
|
||||||
|
savedTable = table;
|
||||||
|
if (savedTable != null) {
|
||||||
|
JTableHeader header = savedTable.getTableHeader();
|
||||||
|
header.addMouseListener(forSeekMouseListener);
|
||||||
|
header.addMouseMotionListener(forSeekMouseListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
|
||||||
|
boolean hasFocus, int row, int column) {
|
||||||
|
setSavedTable(table);
|
||||||
|
savedViewColumn = column;
|
||||||
|
return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void paintChildren(Graphics g) {
|
protected void paintChildren(Graphics g) {
|
||||||
super.paintChildren(g);
|
super.paintChildren(g);
|
||||||
|
@ -69,65 +159,8 @@ public class RangeCursorTableHeaderRenderer<N extends Number & Comparable<N>>
|
||||||
g.fillPolygon(ARROW);
|
g.fillPolygon(ARROW);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addSeekListener(JTable table, int modelColumn, Consumer<Double> listener) {
|
public void addSeekListener(SeekListener listener) {
|
||||||
TableColumnModel colModel = table.getColumnModel();
|
listeners.add(listener);
|
||||||
JTableHeader header = table.getTableHeader();
|
|
||||||
TableColumn col = colModel.getColumn(modelColumn);
|
|
||||||
MouseAdapter l = new MouseAdapter() {
|
|
||||||
@Override
|
|
||||||
public void mouseClicked(MouseEvent e) {
|
|
||||||
if ((e.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) != 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ((e.getButton() != MouseEvent.BUTTON1)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
doSeek(e);
|
|
||||||
e.consume();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseDragged(MouseEvent e) {
|
|
||||||
int onmask = MouseEvent.BUTTON1_DOWN_MASK;
|
|
||||||
int offmask = MouseEvent.SHIFT_DOWN_MASK;
|
|
||||||
if ((e.getModifiersEx() & (onmask | offmask)) != onmask) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
doSeek(e);
|
|
||||||
e.consume();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void doSeek(MouseEvent e) {
|
|
||||||
if (header.getResizingColumn() != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int viewColIdx = colModel.getColumnIndexAtX(e.getX());
|
|
||||||
int modelColIdx = table.convertColumnIndexToModel(viewColIdx);
|
|
||||||
if (modelColIdx != modelColumn) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TableColumn draggedCol = header.getDraggedColumn();
|
|
||||||
if (draggedCol == col) {
|
|
||||||
header.setDraggedColumn(null);
|
|
||||||
}
|
|
||||||
else if (draggedCol != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int colX = 0;
|
|
||||||
for (int i = 0; i < viewColIdx; i++) {
|
|
||||||
colX += colModel.getColumn(i).getWidth();
|
|
||||||
}
|
|
||||||
TableColumn col = colModel.getColumn(viewColIdx);
|
|
||||||
|
|
||||||
double pos =
|
|
||||||
span * (e.getX() - colX) / col.getWidth() + fullRangeDouble.lowerEndpoint();
|
|
||||||
listener.accept(pos);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
header.addMouseListener(l);
|
|
||||||
header.addMouseMotionListener(l);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public N getCursorPosition() {
|
public N getCursorPosition() {
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.junit.*;
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||||
|
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||||
import ghidra.test.TestEnv;
|
import ghidra.test.TestEnv;
|
||||||
import ghidra.util.SystemUtilities;
|
import ghidra.util.SystemUtilities;
|
||||||
|
@ -117,7 +118,6 @@ public class DemoRangeCellRendererTest extends AbstractGhidraHeadedIntegrationTe
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDemoRangeCellRenderer() throws Throwable {
|
public void testDemoRangeCellRenderer() throws Throwable {
|
||||||
new TestEnv().getTool();
|
|
||||||
JFrame window = new JFrame();
|
JFrame window = new JFrame();
|
||||||
window.setLayout(new BorderLayout());
|
window.setLayout(new BorderLayout());
|
||||||
|
|
||||||
|
@ -141,11 +141,12 @@ public class DemoRangeCellRendererTest extends AbstractGhidraHeadedIntegrationTe
|
||||||
model.add(new MyRow("Bob", Range.atLeast(1956)));
|
model.add(new MyRow("Bob", Range.atLeast(1956)));
|
||||||
model.add(new MyRow("Elvis", Range.closed(1935, 1977)));
|
model.add(new MyRow("Elvis", Range.closed(1935, 1977)));
|
||||||
|
|
||||||
headerRenderer.addSeekListener(table, MyColumns.LIFESPAN.ordinal(), pos -> {
|
SeekListener seekListener = pos -> {
|
||||||
System.out.println("pos: " + pos);
|
System.out.println("pos: " + pos);
|
||||||
headerRenderer.setCursorPosition(pos.intValue());
|
headerRenderer.setCursorPosition(pos.intValue());
|
||||||
table.getTableHeader().repaint();
|
table.getTableHeader().repaint();
|
||||||
});
|
};
|
||||||
|
headerRenderer.addSeekListener(seekListener);
|
||||||
|
|
||||||
window.add(new JScrollPane(table));
|
window.add(new JScrollPane(table));
|
||||||
window.add(filterPanel, BorderLayout.SOUTH);
|
window.add(filterPanel, BorderLayout.SOUTH);
|
||||||
|
|
|
@ -120,6 +120,11 @@ public abstract class AbstractDynamicTableColumn<ROW_TYPE, COLUMN_TYPE, DATA_SOU
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GTableHeaderRenderer getHeaderRenderer() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private void configureDefaultSettings() {
|
private void configureDefaultSettings() {
|
||||||
defaultSettingsDefinitions = NO_SETTINGS_DEFINITIONS;
|
defaultSettingsDefinitions = NO_SETTINGS_DEFINITIONS;
|
||||||
Class<COLUMN_TYPE> columnClass = getColumnClass();
|
Class<COLUMN_TYPE> columnClass = getColumnClass();
|
||||||
|
|
|
@ -66,4 +66,12 @@ public interface ConfigurableColumnTableModel extends TableModel {
|
||||||
* @return the renderer
|
* @return the renderer
|
||||||
*/
|
*/
|
||||||
public TableCellRenderer getRenderer(int columnIndex);
|
public TableCellRenderer getRenderer(int columnIndex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the header cell renderer for the given column
|
||||||
|
* @param columnIndex the index of the column
|
||||||
|
* @return the renderer
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public TableCellRenderer getHeaderRenderer(int columnIndex);
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,6 +97,16 @@ public interface DynamicTableColumn<ROW_TYPE, COLUMN_TYPE, DATA_SOURCE> {
|
||||||
*/
|
*/
|
||||||
public GColumnRenderer<COLUMN_TYPE> getColumnRenderer();
|
public GColumnRenderer<COLUMN_TYPE> getColumnRenderer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the optional header renderer for this column; null if no renderer is used.
|
||||||
|
*
|
||||||
|
* <P>
|
||||||
|
* This method allows columns to define custom header rendering.
|
||||||
|
*
|
||||||
|
* @return the renderer
|
||||||
|
*/
|
||||||
|
public GTableHeaderRenderer getHeaderRenderer();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of settings definitions for this field.
|
* Returns a list of settings definitions for this field.
|
||||||
*
|
*
|
||||||
|
|
|
@ -540,6 +540,19 @@ public abstract class GDynamicColumnTableModel<ROW_TYPE, DATA_SOURCE>
|
||||||
return tableColumns.get(index).getColumnRenderer();
|
return tableColumns.get(index).getColumnRenderer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the special header cell renderer for the specified table field column. A null value
|
||||||
|
* indicates that this column uses a default header renderer.
|
||||||
|
*
|
||||||
|
* @param index the model column index
|
||||||
|
* @return a table cell renderer for this field's header. Otherwise, null if a default renderer
|
||||||
|
* should be used.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public TableCellRenderer getHeaderRenderer(int index) {
|
||||||
|
return tableColumns.get(index).getHeaderRenderer();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the maximum number of text display lines needed for any given cell within the specified
|
* Gets the maximum number of text display lines needed for any given cell within the specified
|
||||||
* column.
|
* column.
|
||||||
|
|
|
@ -692,6 +692,13 @@ public class GTable extends JTable {
|
||||||
addColumn(newColumn);
|
addColumn(newColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < columnCount; i++ ) {
|
||||||
|
TableCellRenderer headerRenderer = getHeaderRendererOverride(i);
|
||||||
|
if (headerRenderer != null) {
|
||||||
|
tableColumnModel.getColumn(i).setHeaderRenderer(headerRenderer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tableColumnModel.setEventsEnabled(wasEnabled);
|
tableColumnModel.setEventsEnabled(wasEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -890,6 +897,26 @@ public class GTable extends JTable {
|
||||||
return super.getCellRenderer(row, col);
|
return super.getCellRenderer(row, col);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs custom work to locate header renderers for special table model types. The headers
|
||||||
|
* are located and installed at the time the table's model is set.
|
||||||
|
*
|
||||||
|
* @param row the row
|
||||||
|
* @param col the column
|
||||||
|
* @return the header cell renderer
|
||||||
|
*/
|
||||||
|
public final TableCellRenderer getHeaderRendererOverride(int col) {
|
||||||
|
ConfigurableColumnTableModel configurableModel = getConfigurableColumnTableModel();
|
||||||
|
if (configurableModel != null) {
|
||||||
|
int modelIndex = convertColumnIndexToModel(col);
|
||||||
|
TableCellRenderer renderer = configurableModel.getHeaderRenderer(modelIndex);
|
||||||
|
if (renderer != null) {
|
||||||
|
return renderer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If you just begin typing into an editable cell in a JTable, then the cell editor will be
|
* If you just begin typing into an editable cell in a JTable, then the cell editor will be
|
||||||
* displayed. However, the editor component will not have a focus. This method has been
|
* displayed. However, the editor component will not have a focus. This method has been
|
||||||
|
|
|
@ -99,6 +99,11 @@ public class MappedTableColumn<ROW_TYPE, EXPECTED_ROW_TYPE, COLUMN_TYPE, DATA_SO
|
||||||
return tableColumn.getColumnRenderer();
|
return tableColumn.getColumnRenderer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GTableHeaderRenderer getHeaderRenderer() {
|
||||||
|
return tableColumn.getHeaderRenderer();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SettingsDefinition[] getSettingsDefinitions() {
|
public SettingsDefinition[] getSettingsDefinitions() {
|
||||||
return tableColumn.getSettingsDefinitions();
|
return tableColumn.getSettingsDefinitions();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue