diff --git a/Ghidra/Features/Base/src/test.slow/java/docking/widgets/table/GTableDynamicColumnModelTest.java b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/table/GTableDynamicColumnModelTest.java index 75d500758c..b883c4f7a2 100644 --- a/Ghidra/Features/Base/src/test.slow/java/docking/widgets/table/GTableDynamicColumnModelTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/table/GTableDynamicColumnModelTest.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,18 +15,18 @@ */ package docking.widgets.table; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import java.awt.BorderLayout; +import java.util.List; import javax.swing.JFrame; import javax.swing.JScrollPane; +import javax.swing.table.TableColumn; import org.junit.*; -import docking.widgets.table.model.DirData; -import docking.widgets.table.model.TestGDynamicColumnTableModel; +import docking.widgets.table.model.*; import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.util.table.GhidraTable; @@ -73,9 +73,8 @@ public class GTableDynamicColumnModelTest extends AbstractGhidraHeadedIntegratio @Test public void testAddColumn() throws Exception { - // Grab a column in the middle to remove int count = model.getColumnCount(); - DynamicTableColumn column = model.getColumn(2); + DynamicTableColumn column = new DirDataSizeColumn(); int index = 2; // in the middle runSwing(() -> { @@ -86,6 +85,93 @@ public class GTableDynamicColumnModelTest extends AbstractGhidraHeadedIntegratio assertColumnPresent(column); } + @Test + public void testTableColumnDescriptor_ShortcutColumnCreation() throws Exception { + + frame.dispose(); + + model = new TestGDynamicColumnTableModel() { + @Override + protected TableColumnDescriptor createTableColumnDescriptor() { + TableColumnDescriptor 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 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 getColumn(String name) { + int count = model.getColumnCount(); + for (int i = 0; i < count; i++) { + + DynamicTableColumn 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 column = getColumn(expectedName); + assertNotNull("Column not found in model - " + expectedName, column); + } + } + private void assertColumnPresent(DynamicTableColumn column) { int count = model.getColumnCount(); for (int i = 0; i < count; i++) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableColumnDescriptor.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableColumnDescriptor.java index a4e1fb9537..51eaf135f0 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableColumnDescriptor.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableColumnDescriptor.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,7 +16,10 @@ package docking.widgets.table; import java.util.*; +import java.util.function.Function; +import ghidra.docking.settings.Settings; +import ghidra.framework.plugintool.ServiceProvider; import ghidra.util.Msg; public class TableColumnDescriptor { @@ -106,6 +109,99 @@ public class TableColumnDescriptor { columns.add(new TableColumnInfo(column, true, sortOrdinal, ascending)); } + /** + * Adds a column to the descriptor via an anonymous accessor function instead. + *

+ * If you would like to control the sorting behavior of your column, then use + * {@link #addVisibleColumn(String, Class, Function, int, boolean)}. + *

+ * 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 the column type + */ + public void addVisibleColumn(String name, Class columnTypeClass, + Function rowToColumnFunction) { + addVisibleColumn(name, columnTypeClass, rowToColumnFunction, -1, true); + } + + /** + * Adds a column to the descriptor via an anonymous accessor function instead. + *

+ * 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 ordinal (i.e., 1, 2, 3...n), not the index (i.e, 0, 1, 2...n) + * @param ascending true for sort ascending; false for descending + * @param the column type + */ + public void addVisibleColumn(String name, Class columnTypeClass, + Function rowToColumnFunction, int sortOrdinal, + boolean ascending) { + + AbstractDynamicTableColumn 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. + *

+ * 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 the column type + */ + public void addHiddenColumn(String name, Class columnTypeClass, + Function rowToColumnFunction) { + + AbstractDynamicTableColumn column = + createColumnStub(name, columnTypeClass, rowToColumnFunction); + addHiddenColumn(column); + } + + private AbstractDynamicTableColumn createColumnStub( + String name, Class columnTypeClass, + Function 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 getColumnClass() { + return columnTypeClass; + } + + @Override + public Class getSupportedRowType() { + // returning null means this column will not be available to use in other tables + return null; + } + }; + } + private class TableColumnInfo implements Comparable { private DynamicTableColumn column; private boolean isVisible = false;