Merge remote-tracking branch

'origin/GP-4892-dragonmacher-enum-editor-fix' (Closes #6873)
This commit is contained in:
Ryan Kurtz 2024-09-06 06:14:29 -04:00
commit c5f5da09a8
2 changed files with 230 additions and 63 deletions

View File

@ -17,7 +17,8 @@ package ghidra.app.plugin.core.datamgr.editor;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.Arrays;
import java.util.EventObject;
import javax.swing.*;
import javax.swing.event.*;
@ -99,20 +100,15 @@ class EnumEditorPanel extends JPanel {
// invoke later because the key press on the table causes the selection to change
Swing.runLater(() -> {
try {
if (table.isEditing()) {
return; // don't change the selection if a new edit is in progress
}
int row = tableModel.getRow(name);
if (row >= 0 && row < tableModel.getRowCount()) {
table.setRowSelectionInterval(row, row);
Rectangle rect = table.getCellRect(row, 0, false);
table.scrollRectToVisible(rect);
}
if (table.isEditing()) {
return; // don't change the selection if a new edit is in progress
}
catch (NoSuchElementException e) {
// ignore
int row = tableModel.getRow(name);
if (row >= 0 && row < tableModel.getRowCount()) {
table.setRowSelectionInterval(row, row);
Rectangle rect = table.getCellRect(row, 0, false);
table.scrollRectToVisible(rect);
}
});
}
@ -500,6 +496,26 @@ class EnumEditorPanel extends JPanel {
EnumEntry enumEntry = tableModel.getRowObject(row);
return enumEntry.getName();
}
private void edit(int row, int col) {
scrollToCell(row, col);
table.setRowSelectionInterval(row, row);
table.editCellAt(row, col);
}
private void scrollToCell(int row, int col) {
if (table.getAutoscrolls()) {
Rectangle cellRect = table.getCellRect(row, col, false);
if (cellRect != null) {
table.scrollRectToVisible(cellRect);
}
}
}
private int getRow(EnumEntry entry) {
return tableModel.getRowIndex(entry);
}
//==================================================================================================
// Inner Classes
//==================================================================================================
@ -578,61 +594,113 @@ class EnumEditorPanel extends JPanel {
return;
}
int row = table.getEditingRow();
int col = table.getEditingColumn();
int rowCount = table.getRowCount();
int columnCount = table.getColumnCount();
int keycode = e.getKeyCode();
switch (keycode) {
case KeyEvent.VK_TAB:
if (e.isShiftDown()) {
if (--col < 0) {
col = columnCount - 1;
if (--row < 0) {
row = rowCount - 1;
col = columnCount - 1;
}
}
}
else {
if (++col == columnCount) {
col = 0;
if (++row == rowCount) {
row = 0;
}
}
}
break;
case KeyEvent.VK_DOWN:
if (++row == rowCount) {
row = 0;
}
break;
case KeyEvent.VK_UP:
if (--row < 0) {
row = rowCount - 1;
}
break;
default:
return;
int code = e.getKeyCode();
boolean moveEdit =
code == KeyEvent.VK_TAB || code == KeyEvent.VK_UP || code == KeyEvent.VK_DOWN;
if (!moveEdit) {
return;
}
e.consume();
scrollToCell(row, col);
table.setRowSelectionInterval(row, row);
table.editCellAt(row, col);
int row = table.getEditingRow();
int col = table.getEditingColumn();
//
// The user has attempted to edit a new cell while there is an edit in progress. The
// table may get re-sorted when this happens, as the current edit may get committed,
// which can affect the table's sort. In this case, we need to find where the
// currently edited cell is moved to so that we can correctly move to the user's
// requested cell, which is relative to the current cell being edited.
//
EnumEntry editedEntry = tableModel.getRowObject(row);
TableCellEditor editor = table.getCellEditor();
editor.stopCellEditing();
CellEditRequest cellEditRequest =
new CellEditRequest(EnumEditorPanel.this, editedEntry, col, e);
Swing.runLater(cellEditRequest);
}
};
private void scrollToCell(int row, int col) {
if (table.getAutoscrolls()) {
Rectangle cellRect = table.getCellRect(row, col, false);
if (cellRect != null) {
table.scrollRectToVisible(cellRect);
private record CellEditRequest(EnumEditorPanel editorPanel, EnumEntry editedEntry,
int editCol,
KeyEvent e)
implements Runnable {
@Override
public void run() {
JTable table = editorPanel.table;
// note: this lookup works because equals() is *not* overridden and any edits are
// applied to the object in memory so that the default '==' lookup works.
int row = editorPanel.getRow(editedEntry);
int col = editCol;
int rowCount = table.getRowCount();
switch (e.getKeyCode()) {
case KeyEvent.VK_TAB:
boolean forward = !e.isShiftDown();
editNextCell(table, forward, row, col);
return;
case KeyEvent.VK_DOWN:
if (++row == rowCount) {
row = 0;
}
editorPanel.edit(row, col);
return;
case KeyEvent.VK_UP:
if (--row < 0) {
row = rowCount - 1;
}
editorPanel.edit(row, col);
return;
default:
return;
}
}
private void editNextCell(JTable table, boolean forward, int row, int col) {
int columnCount = table.getColumnCount();
int rowCount = table.getRowCount();
if (forward) {
int nextRow = row;
int nextCol = col + 1;
if (nextCol == columnCount) {
// wrap to the next row
nextCol = 0;
nextRow++;
if (nextRow == rowCount) {
// wrap to the first row
nextRow = 0;
}
}
editorPanel.edit(nextRow, nextCol);
return;
}
// going backward
int nextRow = row;
int nextCol = col - 1;
if (nextCol < 0) {
nextCol = columnCount - 1;
nextRow--;
if (nextRow < 0) {
nextRow = rowCount - 1;
nextCol = columnCount - 1;
}
editorPanel.edit(nextRow, nextCol);
}
}
}
}

View File

@ -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.
@ -18,6 +18,7 @@ package ghidra.app.plugin.core.datamgr.editor;
import static org.junit.Assert.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import javax.swing.*;
import javax.swing.table.*;
@ -27,6 +28,8 @@ import org.junit.*;
import docking.*;
import docking.action.DockingActionIf;
import docking.widgets.OptionDialog;
import docking.widgets.table.*;
import docking.widgets.table.ColumnSortState.SortDirection;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.data.*;
@ -469,8 +472,8 @@ public class EnumEditor2Test extends AbstractGhidraHeadedIntegrationTest {
@Test
public void testEditName() throws Exception {
Enum enummDt = editSampleEnum();
Enum enummDt = editSampleEnum();
EnumEditorPanel panel = findEditorPanel(tool.getToolFrame());
runSwing(() -> {
@ -482,6 +485,37 @@ public class EnumEditor2Test extends AbstractGhidraHeadedIntegrationTest {
assertEquals("MyColors", enummDt.getName());
}
@Test
public void testEditName_RowChanges_NavigationWithTab() throws Exception {
editSampleEnum();
EnumEditorPanel panel = findEditorPanel(tool.getToolFrame());
JTable table = panel.getTable();
sortOnNameColumn();
int row = 0;
int col = 0;
clickTableCell(table, row, col, 2);
assertTrue(runSwing(() -> table.isEditing()));
// note: this new name will cause the enum entry at row 0 to get moved to row 1
String newName = "MyColors";
JTextField editorField = getCellEditorTextField();
assertNotNull(editorField);
setText(editorField, newName);
pressTab(editorField);
// get the row after the table has been sorted
int newRow = findRowByName(newName);
assertNotEquals(row, newRow);
int newColumn = col + 1;
assertEditing(newRow, newColumn);
}
@Test
public void testDuplicateName() throws Exception {
@ -672,6 +706,71 @@ public class EnumEditor2Test extends AbstractGhidraHeadedIntegrationTest {
// Private Methods
//==================================================================================================
private void sortOnNameColumn() {
JTable table = getTable();
SortedTableModel sortedModel = (SortedTableModel) table.getModel();
TableSortState sortState = getSortState(sortedModel);
ColumnSortState primarySortState = sortState.iterator().next();
SortDirection sortDirection = primarySortState.getSortDirection();
if (primarySortState.getColumnModelIndex() == EnumTableModel.NAME_COL) {
if (SortDirection.ASCENDING == sortDirection) {
return; // already sorted
}
}
TableSortState newSortState =
TableSortState.createDefaultSortState(EnumTableModel.NAME_COL, true);
runSwing(() -> sortedModel.setTableSortState(newSortState));
}
private TableSortState getSortState(SortedTableModel sortedModel) {
return runSwing(() -> sortedModel.getTableSortState());
}
private JTable getTable() {
EnumEditorPanel panel = findEditorPanel(tool.getToolFrame());
return panel.getTable();
}
protected JTextField getCellEditorTextField() {
Object editorComponent = getTable().getEditorComponent();
if (editorComponent instanceof JTextField) {
return (JTextField) editorComponent;
}
fail("Either not editing, or editing a field that is a custom editor (not a text field)");
return null;
}
private void assertEditing(int row, int column) {
JTable table = getTable();
assertTrue(runSwing(table::isEditing));
assertEquals(row, (int) runSwing(table::getEditingRow));
assertEquals(row, (int) runSwing(table::getEditingColumn));
}
private int findRowByName(String name) {
JTable table = getTable();
return runSwing(() -> {
int col = 0; // Name column defaults to 0
int n = table.getRowCount();
for (int i = 0; i < n; i++) {
String value = table.getValueAt(i, col).toString();
if (name.equals(value)) {
return i;
}
}
return -1;
});
}
private void pressTab(JComponent component) {
triggerActionKey(component, 0, KeyEvent.VK_TAB);
waitForSwing();
}
private EnumEditorPanel findEditorPanel(Window w) {
Window[] windows = w.getOwnedWindows();
for (Window window : windows) {