Merge remote-tracking branch

'origin/GP-5963_dragonmacher_PR-7346_fmagin_fmagin_table_accessor'
(Closes #7346)
This commit is contained in:
Ryan Kurtz 2025-09-02 06:00:03 -04:00
commit 87a32d568c
2 changed files with 192 additions and 10 deletions

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -15,18 +15,18 @@
*/ */
package docking.widgets.table; package docking.widgets.table;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import static org.junit.Assert.fail;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.util.List;
import javax.swing.JFrame; import javax.swing.JFrame;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import javax.swing.table.TableColumn;
import org.junit.*; import org.junit.*;
import docking.widgets.table.model.DirData; import docking.widgets.table.model.*;
import docking.widgets.table.model.TestGDynamicColumnTableModel;
import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.util.table.GhidraTable; import ghidra.util.table.GhidraTable;
@ -73,9 +73,8 @@ public class GTableDynamicColumnModelTest extends AbstractGhidraHeadedIntegratio
@Test @Test
public void testAddColumn() throws Exception { public void testAddColumn() throws Exception {
// Grab a column in the middle to remove
int count = model.getColumnCount(); int count = model.getColumnCount();
DynamicTableColumn<DirData, ?, ?> column = model.getColumn(2); DynamicTableColumn<DirData, ?, ?> column = new DirDataSizeColumn();
int index = 2; // in the middle int index = 2; // in the middle
runSwing(() -> { runSwing(() -> {
@ -86,6 +85,93 @@ public class GTableDynamicColumnModelTest extends AbstractGhidraHeadedIntegratio
assertColumnPresent(column); assertColumnPresent(column);
} }
@Test
public void testTableColumnDescriptor_ShortcutColumnCreation() throws Exception {
frame.dispose();
model = new TestGDynamicColumnTableModel() {
@Override
protected TableColumnDescriptor<DirData> createTableColumnDescriptor() {
TableColumnDescriptor<DirData> descriptor = new TableColumnDescriptor<>();
descriptor.addVisibleColumn("Name", String.class, data -> data.getName());
descriptor.addVisibleColumn("Size", Integer.class, data -> data.getSize());
descriptor.addHiddenColumn("Date", String.class, data -> data.getTime());
return descriptor;
}
};
table = new GhidraTable(model);
frame = new JFrame("Ghidra Table Test");
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(new JScrollPane(table));
frame.pack();
frame.setVisible(true);
// Note: when building the column model, all columns are visible by default, including those
// created as hidden columns. A swing process will run to update the hidden columns. Thus,
// we need to flush the swing processes to get that update to happen.
waitForSwing();
assertEquals(3, model.getColumnCount());
GTableColumnModel columnModel = (GTableColumnModel) table.getColumnModel();
assertEquals(2, columnModel.getColumnCount());
assertColumns("Name", "Size");
showHiddenColumn("Date");
assertColumns("Name", "Size", "Date");
}
private void showHiddenColumn(String name) {
TableColumn tableColumn = getHiddenTableColumn(name);
GTableColumnModel columnModel = (GTableColumnModel) table.getColumnModel();
runSwing(() -> columnModel.setVisible(tableColumn, true));
}
private TableColumn getHiddenTableColumn(String name) {
GTableColumnModel columnModel = (GTableColumnModel) table.getColumnModel();
TableColumn tableColumn = runSwing(() -> {
List<TableColumn> allColumns = columnModel.getAllColumns();
for (TableColumn column : allColumns) {
Object headerValue = column.getHeaderValue();
if (headerValue.equals(name)) {
return column;
}
}
return null;
});
assertNotNull(tableColumn);
boolean isVisible = runSwing(() -> {
int index = tableColumn.getModelIndex();
return columnModel.isVisible(index);
});
assertFalse(isVisible);
return tableColumn;
}
private DynamicTableColumn<DirData, ?, ?> getColumn(String name) {
int count = model.getColumnCount();
for (int i = 0; i < count; i++) {
DynamicTableColumn<DirData, ?, ?> column = model.getColumn(i);
String columnName = column.getColumnName();
if (columnName.equals(name)) {
return column;
}
}
return null;
}
private void assertColumns(String... expectedNames) {
for (String expectedName : expectedNames) {
DynamicTableColumn<DirData, ?, ?> column = getColumn(expectedName);
assertNotNull("Column not found in model - " + expectedName, column);
}
}
private void assertColumnPresent(DynamicTableColumn<DirData, ?, ?> column) { private void assertColumnPresent(DynamicTableColumn<DirData, ?, ?> column) {
int count = model.getColumnCount(); int count = model.getColumnCount();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -16,7 +16,10 @@
package docking.widgets.table; package docking.widgets.table;
import java.util.*; import java.util.*;
import java.util.function.Function;
import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.util.Msg; import ghidra.util.Msg;
public class TableColumnDescriptor<ROW_TYPE> { public class TableColumnDescriptor<ROW_TYPE> {
@ -106,6 +109,99 @@ public class TableColumnDescriptor<ROW_TYPE> {
columns.add(new TableColumnInfo(column, true, sortOrdinal, ascending)); columns.add(new TableColumnInfo(column, true, sortOrdinal, ascending));
} }
/**
* Adds a column to the descriptor via an anonymous accessor function instead.
* <P>
* If you would like to control the sorting behavior of your column, then use
* {@link #addVisibleColumn(String, Class, Function, int, boolean)}.
* <P>
* Note: any columns created via this method will not be discoverable by other tables. To use
* that feature, you must create a separate column class that extends
* {@link DynamicTableColumnExtensionPoint}.
*
* @param name the column name, visible in the UI
* @param columnTypeClass the column class type
* @param rowToColumnFunction a function to convert a row object to the column object
* @param <COLUMN_TYPE> the column type
*/
public <COLUMN_TYPE> void addVisibleColumn(String name, Class<COLUMN_TYPE> columnTypeClass,
Function<ROW_TYPE, COLUMN_TYPE> rowToColumnFunction) {
addVisibleColumn(name, columnTypeClass, rowToColumnFunction, -1, true);
}
/**
* Adds a column to the descriptor via an anonymous accessor function instead.
* <P>
* Note: any columns created via this method will not be discoverable by other tables. To use
* that feature, you must create a separate column class that extends
* {@link DynamicTableColumnExtensionPoint}.
*
* @param name the column name, visible in the UI
* @param columnTypeClass the column class type
* @param rowToColumnFunction a function to convert a row object to the column object
* @param sortOrdinal the <b>ordinal (i.e., 1, 2, 3...n)</b>, not the index (i.e, 0, 1, 2...n)
* @param ascending true for sort ascending; false for descending
* @param <COLUMN_TYPE> the column type
*/
public <COLUMN_TYPE> void addVisibleColumn(String name, Class<COLUMN_TYPE> columnTypeClass,
Function<ROW_TYPE, COLUMN_TYPE> rowToColumnFunction, int sortOrdinal,
boolean ascending) {
AbstractDynamicTableColumn<ROW_TYPE, COLUMN_TYPE, Object> column =
createColumnStub(name, columnTypeClass, rowToColumnFunction);
addVisibleColumn(column, sortOrdinal, ascending);
}
/**
* Adds a column to the descriptor via an anonymous accessor function instead. The column added
* will not be displayed until enabled by the user.
* <P>
* Note: any columns created via this method will not be discoverable by other tables. To use
* that feature, you must create a separate column class that extends
* {@link DynamicTableColumnExtensionPoint}.
*
* @param name the column name, visible in the UI
* @param columnTypeClass the column class type
* @param rowToColumnFunction a function to convert a row object to the column object
* @param <COLUMN_TYPE> the column type
*/
public <COLUMN_TYPE> void addHiddenColumn(String name, Class<COLUMN_TYPE> columnTypeClass,
Function<ROW_TYPE, COLUMN_TYPE> rowToColumnFunction) {
AbstractDynamicTableColumn<ROW_TYPE, COLUMN_TYPE, Object> column =
createColumnStub(name, columnTypeClass, rowToColumnFunction);
addHiddenColumn(column);
}
private <COLUMN_TYPE> AbstractDynamicTableColumn<ROW_TYPE, COLUMN_TYPE, Object> createColumnStub(
String name, Class<COLUMN_TYPE> columnTypeClass,
Function<ROW_TYPE, COLUMN_TYPE> rowToColumnFunction) {
return new AbstractDynamicTableColumn<>() {
@Override
public String getColumnName() {
return name;
}
@Override
public COLUMN_TYPE getValue(ROW_TYPE rowObject, Settings settings, Object data,
ServiceProvider serviceProvider) throws IllegalArgumentException {
return rowToColumnFunction.apply(rowObject);
}
@Override
public Class<COLUMN_TYPE> getColumnClass() {
return columnTypeClass;
}
@Override
public Class<ROW_TYPE> getSupportedRowType() {
// returning null means this column will not be available to use in other tables
return null;
}
};
}
private class TableColumnInfo implements Comparable<TableColumnInfo> { private class TableColumnInfo implements Comparable<TableColumnInfo> {
private DynamicTableColumn<ROW_TYPE, ?, ?> column; private DynamicTableColumn<ROW_TYPE, ?, ?> column;
private boolean isVisible = false; private boolean isVisible = false;