From 3dfe9cfaad78e3ebb1a39d02440bf8da3cea6984 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Fri, 5 Dec 2025 18:56:50 -0500 Subject: [PATCH] Updated the Column Filter Dialog to not lose focus when picking from the combo boxes --- .../constraint/dialog/ColumnFilterDialog.java | 39 ++++++++++++--- .../dialog/ColumnFilterGridLocation.java | 32 ++++++++++++ .../constraint/dialog/ColumnFilterPanel.java | 49 +++++++++++++++++-- .../dialog/ConstraintFilterPanel.java | 42 ++++++++++++++++ 4 files changed, 153 insertions(+), 9 deletions(-) create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ColumnFilterGridLocation.java diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ColumnFilterDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ColumnFilterDialog.java index 3a2322ebc4..3428b4e5f0 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ColumnFilterDialog.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ColumnFilterDialog.java @@ -469,18 +469,45 @@ public class ColumnFilterDialog extends ReusableDialogComponentProvider // TableFilterDialogModelListener methods //================================================================================================== + void filterRemoved(ColumnBasedTableFilter filter) { + filterManager.updateSavedFilters(filter, false); + } + @Override public void editorValueChanged(ColumnConstraintEditor editor) { updateStatus(); } - @Override - public void structureChanged() { - loadFilterRows(); - updateStatus(); + private ColumnFilterGridLocation getFocusedGridLocation() { + KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + Component focusOwner = kfm.getFocusOwner(); + + for (int row = 0; row < filterPanels.size(); row++) { + ColumnFilterPanel panel = filterPanels.get(row); + if (SwingUtilities.isDescendingFrom(focusOwner, panel)) { + return panel.getActiveGridLocation(focusOwner, row); + } + } + + return null; } - void filterRemoved(ColumnBasedTableFilter filter) { - filterManager.updateSavedFilters(filter, false); + @Override + public void structureChanged() { + + ColumnFilterGridLocation restoreLocation = getFocusedGridLocation(); + + loadFilterRows(); + updateStatus(); + + if (restoreLocation != null) { + int dialogRow = restoreLocation.dialogRow(); + if (filterPanels.size() <= dialogRow) { + return; // the UI was rebuilt in such a way that the old grid location is not valid + } + + ColumnFilterPanel panel = filterPanels.get(dialogRow); + panel.restoreActiveGridLocation(restoreLocation); + } } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ColumnFilterGridLocation.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ColumnFilterGridLocation.java new file mode 100644 index 0000000000..f1f78debfb --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ColumnFilterGridLocation.java @@ -0,0 +1,32 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package docking.widgets.table.constraint.dialog; + +/** + * A simple object used to track a component's position in the filter dialog. This is used to + * restore focus when the dialog is rebuilt. + *

+ * This class models the filter dialog as grid. The dialog has a set of rows, each of which can + * have sub-rows within the dialog's row. The columns values are the same for the dialog as they + * are for sub-components. + * + * @param dialogRow the row number in the dialog's set of compound rows + * @param subRow the row number within a given dialog row + * @param col the column + */ +record ColumnFilterGridLocation(int dialogRow, int subRow, int col) { + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ColumnFilterPanel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ColumnFilterPanel.java index 6efb5add02..6693c740fa 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ColumnFilterPanel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ColumnFilterPanel.java @@ -23,10 +23,11 @@ import javax.swing.*; import javax.swing.border.BevelBorder; import docking.widgets.EmptyBorderButton; -import docking.widgets.combobox.GhidraComboBox; +import docking.widgets.combobox.GComboBox; import docking.widgets.label.GDLabel; import docking.widgets.list.GComboBoxCellRenderer; import generic.theme.GThemeDefaults.Colors.Messages; +import ghidra.util.Swing; import ghidra.util.layout.VerticalLayout; import resources.Icons; import resources.ResourceManager; @@ -71,7 +72,8 @@ class ColumnFilterPanel extends JPanel { Vector> v = new Vector<>(filterEntry.getAllColumnData()); DefaultComboBoxModel> model = new DefaultComboBoxModel<>(v); - columnFilterComboBox = new GhidraComboBox<>(model); + columnFilterComboBox = new GComboBox<>(model); + columnFilterComboBox.getAccessibleContext().setAccessibleName("Table Column"); columnFilterComboBox.setRenderer(new GComboBoxCellRenderer<>() { @Override protected String getItemText(ColumnFilterData value) { @@ -81,7 +83,6 @@ class ColumnFilterPanel extends JPanel { columnFilterComboBox.setSelectedItem(filterEntry.getColumnFilterData()); - columnFilterComboBox.addItemListener(e -> columnChanged()); columnFilterComboBox.addActionListener(e -> columnChanged()); return columnFilterComboBox; } @@ -127,4 +128,46 @@ class ColumnFilterPanel extends JPanel { DialogFilterRow getColumnFilterEntry() { return filterEntry; } + + ColumnFilterGridLocation getActiveGridLocation(Component component, int dialogRow) { + + if (columnFilterComboBox == component) { + return new ColumnFilterGridLocation(dialogRow, 0, 0); + } + + for (int subRow = 0; subRow < filterPanels.size(); subRow++) { + ConstraintFilterPanel panel = filterPanels.get(subRow); + int col = panel.getActiveComponentColumn(component); + if (col != -1) { + return new ColumnFilterGridLocation(dialogRow, subRow, col); + } + } + + return null; + } + + void restoreActiveGridLocation(ColumnFilterGridLocation location) { + + Component component = getComponent(location); + if (component != null) { + Swing.runLater(() -> { + component.requestFocusInWindow(); + }); + } + } + + private Component getComponent(ColumnFilterGridLocation location) { + int subRow = location.subRow(); + int col = location.col(); + if (subRow == 0 && col == 0) { + return columnFilterComboBox; + } + + if (filterPanels.size() <= subRow) { + return null; // the UI was rebuilt in such a way that the old grid location is not valid + } + + ConstraintFilterPanel panel = filterPanels.get(subRow); + return panel.getActiveComponent(col); + } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ConstraintFilterPanel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ConstraintFilterPanel.java index 3779f65944..e992036f29 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ConstraintFilterPanel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ConstraintFilterPanel.java @@ -84,6 +84,7 @@ public class ConstraintFilterPanel extends JPanel { private Component buildConstraintCombo() { JPanel panel = new JPanel(new BorderLayout()); constraintComboBox = new GComboBox<>(); + constraintComboBox.getAccessibleContext().setAccessibleName("Filter"); constraintComboBox.setRenderer(new ConstraintComboBoxCellRenderer()); constraintComboBox.addActionListener(constraintComboBoxListener); panel.add(constraintComboBox, BorderLayout.CENTER); @@ -119,6 +120,46 @@ public class ConstraintFilterPanel extends JPanel { constraintComboBox.addActionListener(constraintComboBoxListener); } + /** + * Gets the column value for the given component. The column value is relative to the dialog's + * grid of components. This returns -1 if the given component is not inside the component + * hierarchy of this filter panel. + * + * @param component the component + * @return the column + * @see #getActiveComponent(int) + */ + int getActiveComponentColumn(Component component) { + + // Note: we provide a combo for choosing a condition and a field for entering a value. + // The client of this class has a combo that is first in the row. We will allow that parent + // widget to be column 0, so we will use column 1 and 2 for our widgets. + if (constraintComboBox == component) { + return 1; + } + else if (SwingUtilities.isDescendingFrom(component, inlineEditorPanel)) { + return 2; + } + + return -1; + } + + /** + * Gets the component for the given column value. + * @param col the column value + * @return the component + * @see #getActiveComponentColumn(Component) + */ + Component getActiveComponent(int col) { + if (col == 1) { + return constraintComboBox; + } + else if (col == 2) { + return inlineEditorPanel; + } + return null; + } + private class ConstraintComboBoxCellRenderer extends GComboBoxCellRenderer> { @Override @@ -126,4 +167,5 @@ public class ConstraintFilterPanel extends JPanel { return value.getName(); } } + }