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

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -55,8 +55,9 @@ public class ClearFilterLabel extends GIconLabel {
this.textField = textField; this.textField = textField;
// pad some to offset from the edge of the text field // pad some to offset from the edge of the text field; the border width is a bit larger to
setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); // 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() { textField.getDocument().addDocumentListener(new DocumentListener() {
@ -84,8 +85,12 @@ public class ClearFilterLabel extends GIconLabel {
}); });
addMouseListener(new MouseAdapter() { addMouseListener(new MouseAdapter() {
@Override @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(); clearFilter();
} }
@ -199,9 +204,16 @@ public class ClearFilterLabel extends GIconLabel {
Insets textInsets = textField.getInsets(); Insets textInsets = textField.getInsets();
Point location = textBounds.getLocation(); Point location = textBounds.getLocation();
Dimension size = getPreferredSize(); // For our placement, use the icon size and some padding to keep the icon visually off of
int half = (textBounds.height - size.height) / 2; // the edges of the text field. (We do not want to use our actual preferred size, as we
int y = textBounds.y + half; // 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 end = location.x + textBounds.width;
int x = end - textInsets.right - size.width; int x = end - textInsets.right - size.width;
@ -209,7 +221,8 @@ public class ClearFilterLabel extends GIconLabel {
// hide when text is near // hide when text is near
checkForTouchyText(x); checkForTouchyText(x);
setBounds(x, y, size.width, size.height); Dimension preferredSize = getPreferredSize();
setBounds(x, y, preferredSize.width, preferredSize.height);
myParent.validate(); myParent.validate();
} }

View file

@ -468,13 +468,22 @@ public class GTree extends JPanel implements BusyListener {
public void collapseAll(GTreeNode node) { public void collapseAll(GTreeNode node) {
runSwingNow(() -> { 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(); node.fireNodeStructureChanged();
tree.collapsePath(node.getTreePath()); tree.collapsePath(node.getTreePath());
boolean nodeIsRoot = node.equals(model.getRoot()); boolean nodeIsRoot = node.equals(model.getRoot());
if (nodeIsRoot && !tree.isRootAllowedToCollapse()) { 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 (!allowed) {
if (model != null && model.getRoot() != null) { if (model != null && model.getRoot() != null) {
runTask(new GTreeExpandNodeToDepthTask(GTree.this, getJTree(), runTask(new GTreeExpandNodeToDepthTask(GTree.this,
model.getModelRoot(), 1)); model.getModelRoot(), 1));
} }
} }

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -15,6 +15,11 @@
*/ */
package docking.widgets.tree; 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.support.GTreeFilter;
import docking.widgets.tree.tasks.GTreeClearTreeFilterTask; import docking.widgets.tree.tasks.GTreeClearTreeFilterTask;
import docking.widgets.tree.tasks.GTreeExpandAllTask; import docking.widgets.tree.tasks.GTreeExpandAllTask;
@ -80,10 +85,30 @@ public class GTreeFilterTask extends GTreeTask {
private void restoreInSameTask(TaskMonitor monitor) { private void restoreInSameTask(TaskMonitor monitor) {
GTreeState state = tree.getFilterRestoreState(); 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); GTreeRestoreTreeStateTask restoreTask = new GTreeRestoreTreeStateTask(tree, state);
restoreTask.run(monitor); 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 @Override
public void cancel() { public void cancel() {
cancelledProgramatically = true; cancelledProgramatically = true;

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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 { public class GTreeExpandNodeToDepthTask extends GTreeTask {
private final TreePath[] paths; private final TreePath[] paths;
private final JTree jTree;
private final int depth; private final int depth;
public GTreeExpandNodeToDepthTask(GTree gTree, JTree jTree, GTreeNode node, int depth) { public GTreeExpandNodeToDepthTask(GTree gTree, GTreeNode node, int depth) {
super(gTree); super(gTree);
this.jTree = jTree;
this.paths = new TreePath[] { node.getTreePath() }; this.paths = new TreePath[] { node.getTreePath() };
this.depth = depth; this.depth = depth;
} }