GP-5761 - Data Type Manager - Updated the Collapse button to stop any post-filter state restoring

This commit is contained in:
dragonmacher 2025-06-16 14:10:43 -04:00
parent c03417b3f6
commit b07256a114
4 changed files with 63 additions and 18 deletions

View file

@ -55,8 +55,9 @@ public class ClearFilterLabel extends GIconLabel {
this.textField = textField;
// pad some to offset from the edge of the text field
setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
// pad some to offset from the edge of the text field; the border width is a bit larger to
// make it easier to hover over this label. Values were picked through trial-and-error.
setBorder(BorderFactory.createEmptyBorder(2, 6, 4, 2));
textField.getDocument().addDocumentListener(new DocumentListener() {
@ -84,8 +85,12 @@ public class ClearFilterLabel extends GIconLabel {
});
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
public void mouseReleased(MouseEvent e) {
// Clear on released instead of clicked to allow for slight movement between the
// press and release. If the user moves the most while holding down, the drag
// prevents the clicked callback.
clearFilter();
}
@ -199,9 +204,16 @@ public class ClearFilterLabel extends GIconLabel {
Insets textInsets = textField.getInsets();
Point location = textBounds.getLocation();
Dimension size = getPreferredSize();
int half = (textBounds.height - size.height) / 2;
int y = textBounds.y + half;
// For our placement, use the icon size and some padding to keep the icon visually off of
// the edges of the text field. (We do not want to use our actual preferred size, as we
// have made our size larger than this padding so it is easier to click us.)
int iconHeight = ICON.getIconHeight();
int iconWidth = ICON.getIconWidth();
int padding = 4;
Dimension size = new Dimension(iconWidth + padding, iconHeight + padding);
int halfHeight = (textBounds.height - size.height) / 2;
int y = textBounds.y + halfHeight;
int end = location.x + textBounds.width;
int x = end - textInsets.right - size.width;
@ -209,7 +221,8 @@ public class ClearFilterLabel extends GIconLabel {
// hide when text is near
checkForTouchyText(x);
setBounds(x, y, size.width, size.height);
Dimension preferredSize = getPreferredSize();
setBounds(x, y, preferredSize.width, preferredSize.height);
myParent.validate();
}

View file

@ -468,13 +468,22 @@ public class GTree extends JPanel implements BusyListener {
public void collapseAll(GTreeNode node) {
runSwingNow(() -> {
if (!isFiltered() && lastFilterTask != null) {
// When the user clears the filter, the filter task may be running to restore state.
// If the user wishes to collapse nodes, it does not make sense to keep restoring
// expanded/selected state. This call allows users to cancel any long running tree
// state restoring by executing a collapse action.
lastFilterTask.cancel();
}
node.fireNodeStructureChanged();
tree.collapsePath(node.getTreePath());
boolean nodeIsRoot = node.equals(model.getRoot());
if (nodeIsRoot && !tree.isRootAllowedToCollapse()) {
runTask(new GTreeExpandNodeToDepthTask(this, getJTree(), node, 1));
runTask(new GTreeExpandNodeToDepthTask(this, node, 1));
}
});
@ -1686,7 +1695,7 @@ public class GTree extends JPanel implements BusyListener {
if (!allowed) {
if (model != null && model.getRoot() != null) {
runTask(new GTreeExpandNodeToDepthTask(GTree.this, getJTree(),
runTask(new GTreeExpandNodeToDepthTask(GTree.this,
model.getModelRoot(), 1));
}
}

View file

@ -15,6 +15,11 @@
*/
package docking.widgets.tree;
import java.util.List;
import java.util.Objects;
import javax.swing.tree.TreePath;
import docking.widgets.tree.support.GTreeFilter;
import docking.widgets.tree.tasks.GTreeClearTreeFilterTask;
import docking.widgets.tree.tasks.GTreeExpandAllTask;
@ -80,10 +85,30 @@ public class GTreeFilterTask extends GTreeTask {
private void restoreInSameTask(TaskMonitor monitor) {
GTreeState state = tree.getFilterRestoreState();
if (isOnlyRootSelected(state)) {
// This is a special case that allows the user to signal to not restore the tree state
// when the filter is cleared. The tree will normally restore the state to either 1)
// the state prior to the filter, or 2) the state the user chose when filtered by
// selecting one or more nodes. If the user selects the root, we will use that as a
// signal from the user to say they do not want any state to be restored when the filter
// is cleared.
return;
}
GTreeRestoreTreeStateTask restoreTask = new GTreeRestoreTreeStateTask(tree, state);
restoreTask.run(monitor);
}
private boolean isOnlyRootSelected(GTreeState state) {
List<TreePath> paths = state.getSelectedPaths();
if (paths.size() == 1) {
TreePath path = paths.get(0);
GTreeNode node = (GTreeNode) path.getLastPathComponent();
GTreeNode viewRoot = tree.getViewRoot();
return Objects.equals(node, viewRoot);
}
return false;
}
@Override
public void cancel() {
cancelledProgramatically = true;

View file

@ -29,12 +29,10 @@ import ghidra.util.task.TaskMonitor;
public class GTreeExpandNodeToDepthTask extends GTreeTask {
private final TreePath[] paths;
private final JTree jTree;
private final int depth;
public GTreeExpandNodeToDepthTask(GTree gTree, JTree jTree, GTreeNode node, int depth) {
public GTreeExpandNodeToDepthTask(GTree gTree, GTreeNode node, int depth) {
super(gTree);
this.jTree = jTree;
this.paths = new TreePath[] { node.getTreePath() };
this.depth = depth;
}