mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +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 docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||
import docking.widgets.table.threaded.ThreadedTableModel;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
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 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 docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
|
@ -146,6 +147,10 @@ public abstract class AbstractQueryTablePanel<T> extends JPanel {
|
|||
table.removeKeyListener(l);
|
||||
}
|
||||
|
||||
public void addSeekListener(SeekListener listener) {
|
||||
tableModel.addSeekListener(listener);
|
||||
}
|
||||
|
||||
public void setSelectionMode(int selectionMode) {
|
||||
table.setSelectionMode(selectionMode);
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import javax.swing.*;
|
|||
import docking.*;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.ToggleDockingAction;
|
||||
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||
import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
|
@ -111,6 +112,14 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||
|
||||
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) {
|
||||
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_MODEL, plugin.getName());
|
||||
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||
|
@ -349,6 +358,9 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||
e.consume();
|
||||
}
|
||||
});
|
||||
|
||||
elementsTablePanel.addSeekListener(seekListener);
|
||||
attributesTablePanel.addSeekListener(seekListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.util.stream.Stream;
|
|||
import com.google.common.collect.*;
|
||||
|
||||
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.*;
|
||||
|
@ -419,4 +420,15 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
|||
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 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.columns.*;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
|
@ -168,4 +169,15 @@ public class PathTableModel extends AbstractQueryTableModel<PathRow> {
|
|||
public void setDiffColorSel(Color 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 docking.widgets.table.AbstractDynamicTableColumn;
|
||||
import docking.widgets.table.RangeTableCellRenderer;
|
||||
import docking.widgets.table.*;
|
||||
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
|
@ -30,6 +30,8 @@ public class TracePathLastLifespanPlotColumn
|
|||
extends AbstractDynamicTableColumn<PathRow, Range<Long>, Trace> {
|
||||
|
||||
private final RangeTableCellRenderer<Long> cellRenderer = new RangeTableCellRenderer<>();
|
||||
private final RangeCursorTableHeaderRenderer<Long> headerRenderer =
|
||||
new RangeCursorTableHeaderRenderer<>();
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
|
@ -51,10 +53,21 @@ public class TracePathLastLifespanPlotColumn
|
|||
return cellRenderer;
|
||||
}
|
||||
|
||||
// TODO: header renderer
|
||||
@Override
|
||||
public GTableHeaderRenderer getHeaderRenderer() {
|
||||
return headerRenderer;
|
||||
}
|
||||
|
||||
public void setFullRange(Range<Long> 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.RangeSet;
|
||||
|
||||
import docking.widgets.table.AbstractDynamicTableColumn;
|
||||
import docking.widgets.table.RangeSetTableCellRenderer;
|
||||
import docking.widgets.table.*;
|
||||
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
|
@ -30,6 +30,8 @@ public class TraceValueLifePlotColumn
|
|||
extends AbstractDynamicTableColumn<ValueRow, RangeSet<Long>, Trace> {
|
||||
|
||||
private final RangeSetTableCellRenderer<Long> cellRenderer = new RangeSetTableCellRenderer<>();
|
||||
private final RangeCursorTableHeaderRenderer<Long> headerRenderer =
|
||||
new RangeCursorTableHeaderRenderer<>();
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
|
@ -47,10 +49,21 @@ public class TraceValueLifePlotColumn
|
|||
return cellRenderer;
|
||||
}
|
||||
|
||||
// TODO: The header renderer
|
||||
@Override
|
||||
public GTableHeaderRenderer getHeaderRenderer() {
|
||||
return headerRenderer;
|
||||
}
|
||||
|
||||
public void setFullRange(Range<Long> 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.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.*;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
|
@ -35,6 +35,7 @@ import docking.widgets.HorizontalTabPanel;
|
|||
import docking.widgets.HorizontalTabPanel.TabListCellRenderer;
|
||||
import docking.widgets.dialogs.InputDialog;
|
||||
import docking.widgets.table.*;
|
||||
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
|
@ -206,7 +207,9 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
|||
DockingAction actionCloseDeadTraces;
|
||||
DockingAction actionCloseAllTraces;
|
||||
|
||||
Set<Object> strongRefs = new HashSet<>(); // Eww
|
||||
// strong refs
|
||||
ToToggleSelectionListener toToggleSelectionListener;
|
||||
SeekListener seekListener;
|
||||
|
||||
public DebuggerThreadsProvider(final DebuggerThreadsPlugin plugin) {
|
||||
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_THREADS, plugin.getName());
|
||||
|
@ -220,8 +223,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
|||
|
||||
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);
|
||||
|
||||
myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getViewSnap());
|
||||
|
@ -231,11 +232,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
|||
setVisible(true);
|
||||
}
|
||||
|
||||
private <T> T strongRef(T t) {
|
||||
strongRefs.add(t);
|
||||
return t;
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
public void setModelService(DebuggerModelService modelService) {
|
||||
if (this.modelService != null) {
|
||||
|
@ -471,7 +467,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
|||
colPlot.setCellRenderer(rangeRenderer);
|
||||
colPlot.setHeaderRenderer(headerRenderer);
|
||||
|
||||
headerRenderer.addSeekListener(threadTable, ThreadTableColumns.PLOT.ordinal(), pos -> {
|
||||
headerRenderer.addSeekListener(seekListener = pos -> {
|
||||
long snap = Math.round(pos);
|
||||
if (current.getTrace() == null || snap < 0) {
|
||||
snap = 0;
|
||||
|
@ -483,7 +479,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
|||
}
|
||||
|
||||
protected void createActions() {
|
||||
// TODO: Make other actions use builder?
|
||||
actionStepSnapBackward = StepSnapBackwardAction.builder(plugin)
|
||||
.enabledWhen(this::isStepSnapBackwardEnabled)
|
||||
.enabled(false)
|
||||
|
@ -521,8 +516,8 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
|||
.enabledWhen(c -> current.getTrace() != null)
|
||||
.onAction(c -> activatedGoToTime())
|
||||
.buildAndInstallLocal(this);
|
||||
traceManager.addSynchronizeFocusChangeListener(
|
||||
strongRef(new ToToggleSelectionListener(actionSyncFocus)));
|
||||
traceManager.addSynchronizeFocusChangeListener(toToggleSelectionListener =
|
||||
new ToToggleSelectionListener(actionSyncFocus));
|
||||
|
||||
actionCloseTrace = CloseTraceAction.builderPopup(plugin)
|
||||
.withContext(DebuggerTraceFileActionContext.class)
|
||||
|
|
|
@ -25,55 +25,16 @@ import javax.swing.table.*;
|
|||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
public class RangeCursorTableHeaderRenderer<N extends Number & Comparable<N>>
|
||||
extends GTableHeaderRenderer implements RangedRenderer<N> {
|
||||
protected final static int ARROW_SIZE = 10;
|
||||
protected final static Polygon ARROW = new Polygon(
|
||||
new int[] { 0, -ARROW_SIZE, -ARROW_SIZE },
|
||||
new int[] { 0, ARROW_SIZE, -ARROW_SIZE }, 3);
|
||||
|
||||
protected Range<Double> fullRangeDouble = Range.closed(0d, 1d);
|
||||
protected double span = 1;
|
||||
|
||||
protected Range<N> fullRange;
|
||||
|
||||
protected N pos;
|
||||
protected double doublePos;
|
||||
|
||||
@Override
|
||||
public void setFullRange(Range<N> fullRange) {
|
||||
this.fullRangeDouble = RangedRenderer.validateViewRange(fullRange);
|
||||
this.span = this.fullRangeDouble.upperEndpoint() - this.fullRangeDouble.lowerEndpoint();
|
||||
public interface SeekListener extends Consumer<Double> {
|
||||
}
|
||||
|
||||
public void setCursorPosition(N pos) {
|
||||
this.pos = pos;
|
||||
this.doublePos = pos.doubleValue();
|
||||
}
|
||||
protected class ForSeekMouseListener extends MouseAdapter {
|
||||
|
||||
@Override
|
||||
protected void paintChildren(Graphics g) {
|
||||
super.paintChildren(g);
|
||||
// The cursor should occlude the children
|
||||
paintCursor(g);
|
||||
}
|
||||
|
||||
protected void paintCursor(Graphics parentG) {
|
||||
Graphics2D g = (Graphics2D) parentG.create();
|
||||
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
double x = (doublePos - fullRangeDouble.lowerEndpoint()) / span * getWidth();
|
||||
g.translate(x, getHeight());
|
||||
g.rotate(Math.PI / 2);
|
||||
g.setColor(getForeground());
|
||||
g.fillPolygon(ARROW);
|
||||
}
|
||||
|
||||
public void addSeekListener(JTable table, int modelColumn, Consumer<Double> listener) {
|
||||
TableColumnModel colModel = table.getColumnModel();
|
||||
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) {
|
||||
|
@ -98,36 +59,108 @@ public class RangeCursorTableHeaderRenderer<N extends Number & Comparable<N>>
|
|||
}
|
||||
|
||||
protected void doSeek(MouseEvent e) {
|
||||
TableColumnModel colModel = savedTable.getColumnModel();
|
||||
JTableHeader header = savedTable.getTableHeader();
|
||||
TableColumn myViewCol = colModel.getColumn(savedViewColumn);
|
||||
if (header.getResizingColumn() != null) {
|
||||
return;
|
||||
}
|
||||
int viewColIdx = colModel.getColumnIndexAtX(e.getX());
|
||||
int modelColIdx = table.convertColumnIndexToModel(viewColIdx);
|
||||
if (modelColIdx != modelColumn) {
|
||||
int clickedViewColIdx = colModel.getColumnIndexAtX(e.getX());
|
||||
if (clickedViewColIdx != savedViewColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
TableColumn draggedCol = header.getDraggedColumn();
|
||||
if (draggedCol == col) {
|
||||
TableColumn draggedViewCol = header.getDraggedColumn();
|
||||
if (draggedViewCol == myViewCol) {
|
||||
header.setDraggedColumn(null);
|
||||
}
|
||||
else if (draggedCol != null) {
|
||||
else if (draggedViewCol != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int colX = 0;
|
||||
for (int i = 0; i < viewColIdx; i++) {
|
||||
for (int i = 0; i < clickedViewColIdx; i++) {
|
||||
colX += colModel.getColumn(i).getWidth();
|
||||
}
|
||||
TableColumn col = colModel.getColumn(viewColIdx);
|
||||
|
||||
double pos =
|
||||
span * (e.getX() - colX) / col.getWidth() + fullRangeDouble.lowerEndpoint();
|
||||
listener.accept(pos);
|
||||
span * (e.getX() - colX) / myViewCol.getWidth() + fullRangeDouble.lowerEndpoint();
|
||||
listeners.fire.accept(pos);
|
||||
}
|
||||
};
|
||||
header.addMouseListener(l);
|
||||
header.addMouseMotionListener(l);
|
||||
}
|
||||
|
||||
protected final static int ARROW_SIZE = 10;
|
||||
protected final static Polygon ARROW = new Polygon(
|
||||
new int[] { 0, -ARROW_SIZE, -ARROW_SIZE },
|
||||
new int[] { 0, ARROW_SIZE, -ARROW_SIZE }, 3);
|
||||
|
||||
protected Range<Double> fullRangeDouble = Range.closed(0d, 1d);
|
||||
protected double span = 1;
|
||||
|
||||
protected Range<N> fullRange;
|
||||
|
||||
protected N pos;
|
||||
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
|
||||
public void setFullRange(Range<N> fullRange) {
|
||||
this.fullRangeDouble = RangedRenderer.validateViewRange(fullRange);
|
||||
this.span = this.fullRangeDouble.upperEndpoint() - this.fullRangeDouble.lowerEndpoint();
|
||||
}
|
||||
|
||||
public void setCursorPosition(N pos) {
|
||||
this.pos = pos;
|
||||
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
|
||||
protected void paintChildren(Graphics g) {
|
||||
super.paintChildren(g);
|
||||
// The cursor should occlude the children
|
||||
paintCursor(g);
|
||||
}
|
||||
|
||||
protected void paintCursor(Graphics parentG) {
|
||||
Graphics2D g = (Graphics2D) parentG.create();
|
||||
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
double x = (doublePos - fullRangeDouble.lowerEndpoint()) / span * getWidth();
|
||||
g.translate(x, getHeight());
|
||||
g.rotate(Math.PI / 2);
|
||||
g.setColor(getForeground());
|
||||
g.fillPolygon(ARROW);
|
||||
}
|
||||
|
||||
public void addSeekListener(SeekListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public N getCursorPosition() {
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.junit.*;
|
|||
import com.google.common.collect.Range;
|
||||
|
||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.test.TestEnv;
|
||||
import ghidra.util.SystemUtilities;
|
||||
|
@ -117,7 +118,6 @@ public class DemoRangeCellRendererTest extends AbstractGhidraHeadedIntegrationTe
|
|||
|
||||
@Test
|
||||
public void testDemoRangeCellRenderer() throws Throwable {
|
||||
new TestEnv().getTool();
|
||||
JFrame window = new JFrame();
|
||||
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("Elvis", Range.closed(1935, 1977)));
|
||||
|
||||
headerRenderer.addSeekListener(table, MyColumns.LIFESPAN.ordinal(), pos -> {
|
||||
SeekListener seekListener = pos -> {
|
||||
System.out.println("pos: " + pos);
|
||||
headerRenderer.setCursorPosition(pos.intValue());
|
||||
table.getTableHeader().repaint();
|
||||
});
|
||||
};
|
||||
headerRenderer.addSeekListener(seekListener);
|
||||
|
||||
window.add(new JScrollPane(table));
|
||||
window.add(filterPanel, BorderLayout.SOUTH);
|
||||
|
|
|
@ -120,6 +120,11 @@ public abstract class AbstractDynamicTableColumn<ROW_TYPE, COLUMN_TYPE, DATA_SOU
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GTableHeaderRenderer getHeaderRenderer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void configureDefaultSettings() {
|
||||
defaultSettingsDefinitions = NO_SETTINGS_DEFINITIONS;
|
||||
Class<COLUMN_TYPE> columnClass = getColumnClass();
|
||||
|
|
|
@ -66,4 +66,12 @@ public interface ConfigurableColumnTableModel extends TableModel {
|
|||
* @return the renderer
|
||||
*/
|
||||
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();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
|
|
@ -540,6 +540,19 @@ public abstract class GDynamicColumnTableModel<ROW_TYPE, DATA_SOURCE>
|
|||
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
|
||||
* column.
|
||||
|
|
|
@ -692,6 +692,13 @@ public class GTable extends JTable {
|
|||
addColumn(newColumn);
|
||||
}
|
||||
|
||||
for (int i = 0; i < columnCount; i++ ) {
|
||||
TableCellRenderer headerRenderer = getHeaderRendererOverride(i);
|
||||
if (headerRenderer != null) {
|
||||
tableColumnModel.getColumn(i).setHeaderRenderer(headerRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
tableColumnModel.setEventsEnabled(wasEnabled);
|
||||
}
|
||||
|
||||
|
@ -890,6 +897,26 @@ public class GTable extends JTable {
|
|||
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
|
||||
* 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GTableHeaderRenderer getHeaderRenderer() {
|
||||
return tableColumn.getHeaderRenderer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SettingsDefinition[] getSettingsDefinitions() {
|
||||
return tableColumn.getSettingsDefinitions();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue