Updated the Column Filter Dialog to not lose focus when picking from the combo boxes

This commit is contained in:
dragonmacher
2025-12-05 18:56:50 -05:00
parent f97d9c1346
commit 3dfe9cfaad
4 changed files with 153 additions and 9 deletions

View File

@@ -469,18 +469,45 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
// TableFilterDialogModelListener methods
//==================================================================================================
void filterRemoved(ColumnBasedTableFilter<R> 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<R> 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);
}
}
}

View File

@@ -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.
* <p>
* 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) {
}

View File

@@ -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<ColumnFilterData<?>> v = new Vector<>(filterEntry.getAllColumnData());
DefaultComboBoxModel<ColumnFilterData<?>> 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);
}
}

View File

@@ -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<ColumnConstraint<?>> {
@Override
@@ -126,4 +167,5 @@ public class ConstraintFilterPanel extends JPanel {
return value.getName();
}
}
}