Skip to content

Commit

Permalink
Add case sensitive toggle to workspace filter
Browse files Browse the repository at this point in the history
Fix filter not expanding parents due to filtering hiding the parent value of tree items
  • Loading branch information
Col-E committed Apr 7, 2024
1 parent 6beee71 commit 89c5920
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.slf4j.Logger;
import software.coley.recaf.analytics.logging.Logging;
import software.coley.recaf.util.ReflectUtil;
import software.coley.recaf.util.Unchecked;

import java.lang.reflect.Field;
import java.util.Collections;
Expand All @@ -33,6 +34,7 @@ public class FilterableTreeItem<T> extends TreeItem<T> {
private static final Field CHILDREN_FIELD;
private static final Logger logger = Logging.get(FilterableTreeItem.class);
private final ObservableList<TreeItem<T>> sourceChildren = FXCollections.observableArrayList();
private final ObjectProperty<TreeItem<T>> sourceParent = new SimpleObjectProperty<>();
private final ObjectProperty<Predicate<TreeItem<T>>> predicate = new SimpleObjectProperty<>();

protected FilterableTreeItem() {
Expand Down Expand Up @@ -62,6 +64,14 @@ public ObservableList<TreeItem<T>> getChildren() {
return super.getChildren();
}

/**
* @return Source parent, ignoring filtering.
*/
@Nonnull
public ObjectProperty<TreeItem<T>> sourceParentProperty() {
return sourceParent;
}

/**
* @return {@code true} when the item MUST be shown.
*/
Expand Down Expand Up @@ -135,6 +145,8 @@ public void addAndSortChild(@Nonnull TreeItem<T> item) {
index = -(index + 1);
sourceChildren.add(index, item);
}
if (item instanceof FilterableTreeItem<?> filterableItem)
filterableItem.sourceParent.set(Unchecked.cast(this));
}
}

Expand All @@ -146,6 +158,8 @@ public void addAndSortChild(@Nonnull TreeItem<T> item) {
*/
protected void addPreSortedChild(@Nonnull TreeItem<T> item) {
sourceChildren.add(item);
if (item instanceof FilterableTreeItem<?> filterableItem)
filterableItem.sourceParent.set(Unchecked.cast(this));
}

/**
Expand All @@ -159,6 +173,8 @@ protected void addPreSortedChild(@Nonnull TreeItem<T> item) {
*/
public boolean removeSourceChild(@Nonnull TreeItem<T> child) {
synchronized (sourceChildren) {
if (child instanceof FilterableTreeItem<?> filterableItem)
filterableItem.sourceParent.set(null);
return sourceChildren.remove(child);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package software.coley.recaf.ui.control.tree;

import jakarta.annotation.Nonnull;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
Expand All @@ -24,7 +25,7 @@ public class TreeFiltering {
* Assumed that tree contents are {@link FilterableTreeItem}.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public static void install(TextField filter, TreeView<?> tree) {
public static void install(@Nonnull TextField filter, @Nonnull TreeView<?> tree) {
NodeEvents.addKeyPressHandler(filter, e -> {
if (e.getCode() == KeyCode.ESCAPE) {
filter.clear();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package software.coley.recaf.ui.control.tree;

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import javafx.scene.control.MultipleSelectionModel;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
Expand All @@ -16,7 +17,7 @@ public class TreeItems {
* Expand all parents to this item.
*/
public static void expandParents(@Nonnull TreeItem<?> item) {
while ((item = item.getParent()) != null)
while ((item = getParent(item)) != null)
item.setExpanded(true);
}

Expand Down Expand Up @@ -62,4 +63,18 @@ private static void recurseClose(@Nonnull TreeItem<?> item) {
item.getChildren().forEach(TreeItems::recurseClose);
}
}

/**
* @param item
* Tree item to get parent of.
*
* @return Parent tree item.
*/
@Nullable
private static TreeItem<?> getParent(@Nonnull TreeItem<?> item) {
TreeItem<?> parent = item.getParent();
if (parent == null && item instanceof FilterableTreeItem<?> filterableItem)
parent = filterableItem.sourceParentProperty().get();
return parent;
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package software.coley.recaf.ui.control.tree;

import atlantafx.base.controls.CustomTextField;
import atlantafx.base.theme.Styles;
import jakarta.annotation.Nonnull;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import org.kordamp.ikonli.carbonicons.CarbonIcons;
import software.coley.recaf.path.ClassPathNode;
import software.coley.recaf.path.DirectoryPathNode;
import software.coley.recaf.path.FilePathNode;
import software.coley.recaf.path.PathNode;
import software.coley.recaf.util.FxThreadUtil;
import software.coley.recaf.ui.control.BoundToggleIcon;
import software.coley.recaf.ui.control.FontIconView;
import software.coley.recaf.util.Lang;

/**
Expand All @@ -16,45 +21,54 @@
* @author Matt Coley
*/
public class WorkspaceTreeFilterPane extends BorderPane {
private final TextField textField = new TextField();
private final SimpleBooleanProperty caseSensitivity = new SimpleBooleanProperty(false);
private final CustomTextField textField = new CustomTextField();

/**
* @param tree
* Tree to filter.
*/
public WorkspaceTreeFilterPane(@Nonnull WorkspaceTree tree) {
BoundToggleIcon toggleSensitivity = new BoundToggleIcon(new FontIconView(CarbonIcons.LETTER_CC), caseSensitivity).withTooltip("misc.casesensitive");
toggleSensitivity.getStyleClass().addAll(Styles.BUTTON_ICON, Styles.ACCENT, Styles.FLAT, Styles.SMALL);
textField.rightProperty().set(toggleSensitivity);

textField.promptTextProperty().bind(Lang.getBinding("workspace.filter-prompt"));
setCenter(textField);
getStyleClass().add("workspace-filter-pane");
textField.getStyleClass().add("workspace-filter-text");

// TODO:
// - option to hide supporting resources
// - case sensitivity toggle

// Setup tree item predicate property on FX thread.
// The root is assigned on the FX thread, it won't be available if we call it immediately.
FxThreadUtil.run(() -> {
// We're not binding from the root's property since that will trigger immediately.
// That will force-expand the entire workspace, which we do not want to do.
textField.textProperty().addListener((ob, old, cur) -> {
WorkspaceTreeNode root = (WorkspaceTreeNode) tree.getRoot();
root.predicateProperty().set(item -> {
String path;
PathNode<?> node = item.getValue();
if (node instanceof DirectoryPathNode directoryNode) {
path = directoryNode.getValue();
} else if (node instanceof ClassPathNode classPathNode) {
path = classPathNode.getValue().getName();
} else if (node instanceof FilePathNode classPathNode) {
path = classPathNode.getValue().getName();
} else {
path = null;
}
return path == null || path.contains(cur);
});
textField.textProperty().addListener((ob, old, cur) -> update(tree));
caseSensitivity.addListener((ob, old, cur) -> update(tree));
}

private void update(@Nonnull WorkspaceTree tree) {
WorkspaceTreeNode root = (WorkspaceTreeNode) tree.getRoot();
if (root == null) return;

if (textField.getText().isEmpty())
root.predicateProperty().set(null);
else
root.predicateProperty().set(item -> {
String path;
PathNode<?> node = item.getValue();
if (node instanceof DirectoryPathNode directoryNode) {
path = directoryNode.getValue();
} else if (node instanceof ClassPathNode classPathNode) {
path = classPathNode.getValue().getName();
} else if (node instanceof FilePathNode classPathNode) {
path = classPathNode.getValue().getName();
} else {
path = null;
}

if (path == null) return true;

return caseSensitivity.get() ?
path.contains(textField.getText()) :
path.toLowerCase().contains(textField.getText().toLowerCase());
});
});
}

/**
Expand Down

0 comments on commit 89c5920

Please sign in to comment.