diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java
index 51fa6decb6..285e2186d6 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java
@@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.timeline.filters;
import java.util.List;
import java.util.Objects;
-import javafx.beans.Observable;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
@@ -31,12 +30,8 @@ import javafx.collections.ObservableList;
* implementations can decide how to combine the sub-filters.
*
* a {@link CompoundFilter} uses listeners to enforce the following
- * relationships between it and its sub-filters:
- *
- * if a compound filter becomes inactive disable all of its sub-filters
- * if all of a compound filter's sub-filters become un-selected, un-select
- * the compound filter.
- *
+ * relationships between it and its sub-filters: if all of a compound filter's
+ * sub-filters become un-selected, un-select the compound filter.
*/
public abstract class CompoundFilter extends AbstractFilter {
@@ -57,27 +52,23 @@ public abstract class CompoundFilter extends Abstr
public CompoundFilter(List subFilters) {
super();
- //listen to changes in list of subfilters and
- this.subFilters.addListener((ListChangeListener.Change extends SubFilterType> c) -> {
- while (c.next()) { //add active state listener to newly added filters
- addSubFilterListeners(c.getAddedSubList());
+ //listen to changes in list of subfilters
+ this.subFilters.addListener((ListChangeListener.Change extends SubFilterType> change) -> {
+ while (change.next()) {
+ //add a listener to the selected property of each added subfilter
+ change.getAddedSubList().forEach(addedSubFilter -> {
+ //if a subfilter's selected property changes...
+ addedSubFilter.selectedProperty().addListener(selectedProperty -> {
+ //set this compound filter selected af any of the subfilters are selected.
+ setSelected(getSubFilters().parallelStream().anyMatch(Filter::isSelected));
+ });
+ });
}
- setSelected(getSubFilters().parallelStream().anyMatch(Filter::isSelected));
});
this.subFilters.setAll(subFilters);
}
- private void addSubFilterListeners(List extends SubFilterType> newSubfilters) {
- for (SubFilterType sf : newSubfilters) {
- //if a subfilter changes selected state
- sf.selectedProperty().addListener((Observable observable) -> {
- //set this filter selected af any of the subfilters are selected.
- setSelected(getSubFilters().parallelStream().anyMatch(Filter::isSelected));
- });
- }
- }
-
static boolean areSubFiltersEqual(final CompoundFilter oneFilter, final CompoundFilter otherFilter) {
if (oneFilter.getSubFilters().size() != otherFilter.getSubFilters().size()) {
return false;
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourcesFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourcesFilter.java
index c36868a787..3395982ad7 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourcesFilter.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourcesFilter.java
@@ -30,21 +30,21 @@ import org.openide.util.NbBundle;
*/
public class DataSourcesFilter extends UnionFilter {
+ //keep references to the overridden properties so they don't get GC'd
private final BooleanBinding activePropertyOverride;
- private BooleanBinding disabledPropertyOverride;
+ private final BooleanBinding disabledPropertyOverride;
public DataSourcesFilter() {
disabledPropertyOverride = Bindings.or(super.disabledProperty(), Bindings.size(getSubFilters()).lessThanOrEqualTo(1));
- activePropertyOverride = super.activeProperty().and(Bindings.not(disabledProperty()));
+ activePropertyOverride = super.activeProperty().and(Bindings.not(disabledPropertyOverride));
}
@Override
public DataSourcesFilter copyOf() {
final DataSourcesFilter filterCopy = new DataSourcesFilter();
//add a copy of each subfilter
- getSubFilters().forEach(dataSourceFilter ->
- filterCopy.addSubFilter(dataSourceFilter.copyOf())
- );
+ getSubFilters().forEach(dataSourceFilter -> filterCopy.addSubFilter(dataSourceFilter.copyOf()));
+ //these need to happen after the listeners fired by adding the subfilters
filterCopy.setSelected(isSelected());
filterCopy.setDisabled(isDisabled());
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitsFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitsFilter.java
index 13284683cc..ba2791ac1e 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitsFilter.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitsFilter.java
@@ -43,9 +43,8 @@ public class HashHitsFilter extends UnionFilter {
public HashHitsFilter copyOf() {
HashHitsFilter filterCopy = new HashHitsFilter();
//add a copy of each subfilter
- this.getSubFilters().forEach(hashSetFilter ->
- filterCopy.addSubFilter(hashSetFilter.copyOf())
- );
+ this.getSubFilters().forEach(hashSetFilter -> filterCopy.addSubFilter(hashSetFilter.copyOf()));
+ //these need to happen after the listeners fired by adding the subfilters
filterCopy.setSelected(isSelected());
filterCopy.setDisabled(isDisabled());
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/TagsFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/TagsFilter.java
index 52c0b3b559..7d23c49916 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/filters/TagsFilter.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/TagsFilter.java
@@ -45,9 +45,8 @@ public class TagsFilter extends UnionFilter {
public TagsFilter copyOf() {
TagsFilter filterCopy = new TagsFilter();
//add a copy of each subfilter
- getSubFilters().forEach(tagNameFilter ->
- filterCopy.addSubFilter(tagNameFilter.copyOf())
- );
+ getSubFilters().forEach(tagNameFilter -> filterCopy.addSubFilter(tagNameFilter.copyOf()));
+ //these need to happen after the listeners fired by adding the subfilters
filterCopy.setSelected(isSelected());
filterCopy.setDisabled(isDisabled());
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/TypeFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/TypeFilter.java
index 9af52e99fb..b897791985 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/filters/TypeFilter.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/TypeFilter.java
@@ -102,10 +102,8 @@ public class TypeFilter extends UnionFilter {
//make a nonrecursive copy of this filter
final TypeFilter filterCopy = new TypeFilter(eventType, false);
//add a copy of each subfilter
- getSubFilters().forEach(typeFilter ->
- filterCopy.addSubFilter(typeFilter.copyOf(), comparator)
- );
-
+ getSubFilters().forEach(typeFilter -> filterCopy.addSubFilter(typeFilter.copyOf(), comparator));
+ //these need to happen after the listeners fired by adding the subfilters
filterCopy.setSelected(isSelected());
filterCopy.setDisabled(isDisabled());
return filterCopy;
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java
index 53dceb8a6f..8512c3f819 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java
@@ -84,6 +84,10 @@ final public class FilterSetPanel extends BorderPane {
private final FilteredEventsModel filteredEvents;
private final TimeLineController controller;
+ /**
+ * map from filter to its expansion state in the ui, used to restore the
+ * expansion state as we navigate back and forward in the history
+ */
private final ObservableMap expansionMap = FXCollections.observableHashMap();
private double dividerPosition;
@@ -112,6 +116,7 @@ final public class FilterSetPanel extends BorderPane {
legendColumn.setCellValueFactory(cellDataFeatures -> cellDataFeatures.getValue().valueProperty());
legendColumn.setCellFactory(col -> new LegendCell(this.controller));
+ //type is the only filter expanded initialy
expansionMap.put(controller.getEventsModel().getFilter().getTypeFilter(), true);
this.filteredEvents.eventTypeZoomProperty().addListener((Observable observable) -> applyFilters());
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterTreeItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterTreeItem.java
index ba06bfd404..59368eeab6 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterTreeItem.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterTreeItem.java
@@ -31,50 +31,56 @@ import org.sleuthkit.autopsy.timeline.filters.Filter;
final public class FilterTreeItem extends TreeItem {
/**
- * recursively construct a tree of treeitems to parallel the filter tree of
+ * recursively construct a tree of TreeItems to parallel the filter tree of
* the given filter
*
*
- * @param f the filter for this item. if f has sub-filters, tree items will
- * be made for them added added to the children of this
- * FilterTreeItem
+ * @param filter the filter for this item. if f has sub-filters, tree items
+ * will be made for them added added to the children of this
+ * FilterTreeItem
*/
- public FilterTreeItem(Filter f, ObservableMap expansionMap) {
- super(f);
+ public FilterTreeItem(Filter filter, ObservableMap expansionMap) {
+ super(filter);
+ //listen to changes in the expansion map, and update expansion state of filter object
expansionMap.addListener((MapChangeListener.Change extends Filter, ? extends Boolean> change) -> {
- if (change.getKey().equals(f)) {
+ if (change.getKey().equals(filter)) {
setExpanded(expansionMap.get(change.getKey()));
}
});
- if (expansionMap.containsKey(f)) {
- setExpanded(expansionMap.get(f));
+ if (expansionMap.containsKey(filter)) {
+ setExpanded(expansionMap.get(filter));
}
- expandedProperty().addListener(expandedProperty -> expansionMap.put(f, isExpanded()));
+ //keep expanion map upto date if user expands/collapses filter
+ expandedProperty().addListener(expandedProperty -> expansionMap.put(filter, isExpanded()));
- if (f instanceof CompoundFilter>) {
- final CompoundFilter> compoundFilter = (CompoundFilter>) f;
+ //if the filter is a compound filter, add its subfilters to the tree
+ if (filter instanceof CompoundFilter>) {
+ final CompoundFilter> compoundFilter = (CompoundFilter>) filter;
- for (Filter subFilter : compoundFilter.getSubFilters()) {
- getChildren().add(new FilterTreeItem(subFilter, expansionMap));
- }
+ //add all sub filters
+ compoundFilter.getSubFilters().forEach(subFilter -> getChildren().add(new FilterTreeItem(subFilter, expansionMap)));
+ //listen to changes in sub filters and keep tree in sync
compoundFilter.getSubFilters().addListener((ListChangeListener.Change extends Filter> c) -> {
while (c.next()) {
for (Filter subfFilter : c.getAddedSubList()) {
- setExpanded(true);
+ setExpanded(true); //emphasize new filters by expanding parent to make sure they are visible
getChildren().add(new FilterTreeItem(subfFilter, expansionMap));
}
}
});
+ /*
+ * enforce the following relationship between a compound filter and
+ * its subfilters: if a compound filter's active property changes,
+ * disable the subfilters if the compound filter is not active.
+ */
compoundFilter.activeProperty().addListener(activeProperty -> {
compoundFilter.getSubFilters().forEach(subFilter -> subFilter.setDisabled(compoundFilter.isActive() == false));
});
-
}
-
}
}