diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPluginTest.java index db24326341..3632d94575 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPluginTest.java @@ -15,11 +15,17 @@ */ package ghidra.app.plugin.core.functionwindow; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.table.*; import org.junit.*; import docking.ComponentProvider; +import docking.widgets.combobox.GComboBox; +import docking.widgets.dialogs.SettingsDialog; import docking.widgets.table.GTable; import docking.widgets.table.threaded.ThreadedTableModel; import ghidra.app.plugin.core.clear.ClearCmd; @@ -99,6 +105,84 @@ public class FunctionWindowPluginTest extends AbstractGhidraHeadedIntegrationTes loadProgram("notepad"); } + @Test + public void testChangeSettings() throws Exception { + // + // This test is for a regression bug. There were multiple exceptions happening when + // executing the code paths below. + // + + int row = 0; + int column = getColumnIndex("Function Size"); + String startValue = getRenderedTableCellValue(functionTable, row, column); + + JPopupMenu menu = functionTable.getTableColumnPopupMenu(column); + JMenuItem item = (JMenuItem) menu.getComponent(1); + assertEquals("Column Settings...", item.getText()); + + pressButton(item, false); + + SettingsDialog dialog = waitForDialogComponent(SettingsDialog.class); + int editRow = getFormatRow(dialog); + //triggerEdit(dialog, editRow, 1); + editCell(dialog.getTable(), editRow, 1); + setComboValue(dialog, "hex"); + endEdit(dialog); + pressButtonByText(dialog, "Dismiss"); + + String endValue = getRenderedTableCellValue(functionTable, row, column); + assertNotEquals("Changing the format did not change the view", startValue, endValue); + } + + private int getFormatRow(SettingsDialog dialog) { + GTable table = dialog.getTable(); + int column = getColumnIndex(table, "Name"); + int n = table.getRowCount(); + for (int i = 0; i < n; i++) { + int row = i; + Object name = runSwing(() -> table.getValueAt(row, column)); + if ("Format".equals(name)) { + return i; + } + } + + fail("Unable to find the 'Format' row in the Settings Dialog"); + return -1; + } + + private int getColumnIndex(String text) { + return getColumnIndex(functionTable, text); + } + + private int getColumnIndex(JTable table, String text) { + TableColumnModel columnModel = table.getColumnModel(); + int n = columnModel.getColumnCount(); + for (int i = 0; i < n; i++) { + TableColumn column = columnModel.getColumn(i); + if (text.equals(column.getIdentifier().toString())) { + return i; + } + } + fail("Could not find column '" + text + "'"); + return -1; + } + + private void setComboValue(SettingsDialog d, String string) { + GTable table = d.getTable(); + TableCellEditor activeEditor = runSwing(() -> table.getCellEditor()); + assertNotNull("Table should be editing, but is not", activeEditor); + + assertTrue(activeEditor.getClass().getSimpleName().contains("SettingsEditor")); + @SuppressWarnings("unchecked") + GComboBox combo = (GComboBox) getInstanceField("comboBox", activeEditor); + setComboBoxSelection(combo, string); + } + + private void endEdit(SettingsDialog d) { + GTable table = d.getTable(); + runSwing(() -> table.editingStopped(new ChangeEvent(table))); + } + private void waitForNotBusy(GTable table) { waitForTableModel((ThreadedTableModel) table.getModel()); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/SettingsDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/SettingsDialog.java index 3ccc06e013..94e907ebf9 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/SettingsDialog.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/SettingsDialog.java @@ -63,12 +63,14 @@ public class SettingsDialog extends DialogComponentProvider { this.settingsDefs = newSettingsDefs; this.settings = newSettings; setTitle(title); - settingsTableModel.fireTableDataChanged(); + + settingsTableModel.setSettingsDefinitions(settingsDefs); DockingWindowManager.showDialog(parent, this); } public void dispose() { settingsTable.editingStopped(null); + settingsTable.dispose(); close(); settingsDefs = null; @@ -79,7 +81,7 @@ public class SettingsDialog extends DialogComponentProvider { JPanel workPanel = new JPanel(new BorderLayout()); workPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); - settingsTableModel = new SettingsTableModel(settingsDefs); + settingsTableModel = new SettingsTableModel(); settingsTable = new GTable(settingsTableModel); settingsTable.setAutoscrolls(true); settingsTable.setRowSelectionAllowed(false); @@ -106,6 +108,10 @@ public class SettingsDialog extends DialogComponentProvider { dispose(); } + public GTable getTable() { + return settingsTable; + } + //================================================================================================== // Private Methods //================================================================================================== @@ -161,10 +167,12 @@ public class SettingsDialog extends DialogComponentProvider { private List rows = new ArrayList<>(); - SettingsTableModel(SettingsDefinition[] settingsDefs) { + void setSettingsDefinitions(SettingsDefinition[] settingsDefs) { for (SettingsDefinition sd : settingsDefs) { rows.add(new SettingsRowObject(sd)); } + + settingsTableModel.fireTableDataChanged(); } @Override @@ -203,6 +211,17 @@ public class SettingsDialog extends DialogComponentProvider { return null; } + @Override + public Class getColumnClass(int col) { + switch (col) { + case 0: + return String.class; + case 1: + return Settings.class; + } + return null; + } + @Override public Object getColumnValueForRow(SettingsRowObject t, int columnIndex) { switch (columnIndex) { diff --git a/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java b/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java index 0bf9268cae..3534153bb5 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java @@ -32,6 +32,7 @@ import java.util.regex.Pattern; import javax.swing.*; import javax.swing.table.TableCellEditor; +import javax.swing.table.TableCellRenderer; import javax.swing.text.JTextComponent; import javax.swing.tree.*; @@ -1307,6 +1308,36 @@ public abstract class AbstractGenericTest extends AbstractGTest { return editor; } + /** + * Gets the rendered value for the specified table cell. The actual value at the cell may + * not be a String. This method will get the String display value, as created by the table. + * + * @param table the table to query + * @param row the row to query + * @param column the column to query + * @return the String value + * @throws IllegalArgumentException if there is no renderer or the rendered component is + * something from which this method can get a String (such as a JLabel) + */ + public static String getRenderedTableCellValue(JTable table, int row, int column) { + + return runSwing(() -> { + + TableCellRenderer renderer = table.getCellRenderer(row, column); + if (renderer == null) { + throw new IllegalArgumentException( + "No renderer registered for row/col: " + row + '/' + column); + } + Component component = table.prepareRenderer(renderer, row, column); + if (!(component instanceof JLabel)) { + throw new IllegalArgumentException( + "Do not know how to get text from a renderer " + "that is not a JLabel"); + } + + return ((JLabel) component).getText(); + }); + } + public static void setComboBoxSelection(final JComboBox comboField, final T selection) { runSwing(() -> comboField.setSelectedItem(selection)); waitForSwing();