diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filter/ClearFilterLabel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filter/ClearFilterLabel.java index a6cd265669..b18f9f92b5 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filter/ClearFilterLabel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filter/ClearFilterLabel.java @@ -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. @@ -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(); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java index 87440605f5..8e21f05205 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java @@ -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)); } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeFilterTask.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeFilterTask.java index bd8916e6c9..a481149818 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeFilterTask.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeFilterTask.java @@ -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. @@ -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 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; diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/tasks/GTreeExpandNodeToDepthTask.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/tasks/GTreeExpandNodeToDepthTask.java index a3d69d225a..7721fb315f 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/tasks/GTreeExpandNodeToDepthTask.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/tasks/GTreeExpandNodeToDepthTask.java @@ -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. @@ -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; }