diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java
index 6c9e1b00ed..14394546ba 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java
@@ -68,10 +68,12 @@ public abstract class AbstractFilter implements Filter {
return "[" + (isSelected() ? "x" : " ") + "]"; // NON-NLS
}
+ @Override
public boolean isActive() {
- return activeProperty.get();
+ return activeProperty().get();
}
+ @Override
public BooleanBinding activeProperty() {
return activeProperty;
}
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java
index 055c1c8748..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 filter becomes inactive disable all of its subfilters
- * if a sub-filter changes active state set the parent filter active if any
- * of its sub-filters are active.
- *
+ * 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,30 +52,21 @@ public abstract class CompoundFilter extends Abstr
public CompoundFilter(List subFilters) {
super();
- //listen to changes in list of subfilters and add active state listener to newly added filters
- this.subFilters.addListener((ListChangeListener.Change extends SubFilterType> c) -> {
- while (c.next()) {
- 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));
+ });
+ });
}
});
- this.subFilters.setAll(subFilters);
-
- this.selectedProperty().addListener(activeProperty -> {
- getSubFilters().forEach(subFilter -> subFilter.setDisabled(isActive() == false));
- });
- this.disabledProperty().addListener(activeProperty -> {
- getSubFilters().forEach(subFilter -> subFilter.setDisabled(isActive() == false));
- });
- }
- private void addSubFilterListeners(List extends SubFilterType> newSubfilters) {
- for (SubFilterType sf : newSubfilters) {
- //if a subfilter changes active state
- sf.selectedProperty().addListener((Observable observable) -> {
- //set this filter acttive af any of the subfilters are active.
- setSelected(getSubFilters().parallelStream().anyMatch(Filter::isSelected));
- });
- }
+ this.subFilters.setAll(subFilters);
}
static boolean areSubFiltersEqual(final CompoundFilter oneFilter, final CompoundFilter otherFilter) {
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourceFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourceFilter.java
index 2cd3f93d9e..c713965933 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourceFilter.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourceFilter.java
@@ -43,7 +43,7 @@ public class DataSourceFilter extends AbstractFilter {
@Override
synchronized public DataSourceFilter copyOf() {
- DataSourceFilter filterCopy = new DataSourceFilter(getDisplayName(), getDataSourceID());
+ DataSourceFilter filterCopy = new DataSourceFilter(getDataSourceName(), getDataSourceID());
filterCopy.setSelected(isSelected());
filterCopy.setDisabled(isDisabled());
return filterCopy;
@@ -51,7 +51,7 @@ public class DataSourceFilter extends AbstractFilter {
@Override
public String getDisplayName() {
- return dataSourceName;
+ return getDataSourceName();
}
@Override
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourcesFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourcesFilter.java
index baeb3b7e61..3395982ad7 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourcesFilter.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourcesFilter.java
@@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.timeline.filters;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javafx.beans.binding.Bindings;
+import javafx.beans.binding.BooleanBinding;
import javafx.beans.value.ObservableBooleanValue;
import org.openide.util.NbBundle;
@@ -29,19 +30,23 @@ 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 final BooleanBinding disabledPropertyOverride;
+
public DataSourcesFilter() {
- setSelected(false);
+ disabledPropertyOverride = Bindings.or(super.disabledProperty(), Bindings.size(getSubFilters()).lessThanOrEqualTo(1));
+ 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()));
+ //these need to happen after the listeners fired by adding the subfilters
filterCopy.setSelected(isSelected());
filterCopy.setDisabled(isDisabled());
- //add a copy of each subfilter
- getSubFilters().forEach(dataSourceFilter ->
- filterCopy.addSubFilter(dataSourceFilter.copyOf())
- );
return filterCopy;
}
@@ -65,13 +70,6 @@ public class DataSourcesFilter extends UnionFilter {
return string;
}
- public void addSubFilter(DataSourceFilter dataSourceFilter) {
- super.addSubFilter(dataSourceFilter);
- if (getSubFilters().size() > 1) {
- setSelected(Boolean.TRUE);
- }
- }
-
@Override
public boolean equals(Object obj) {
if (obj == null) {
@@ -97,7 +95,12 @@ public class DataSourcesFilter extends UnionFilter {
@Override
public ObservableBooleanValue disabledProperty() {
- return Bindings.or(super.disabledProperty(), Bindings.size(getSubFilters()).lessThanOrEqualTo(1));
+ return disabledPropertyOverride;
+ }
+
+ @Override
+ public BooleanBinding activeProperty() {
+ return activePropertyOverride;
}
@Override
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java
index 435998a183..92e04dcf81 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java
@@ -25,11 +25,16 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
/**
- * Interface for Filters
+ * Interface for Filters. Filters are given to the EventDB who interpretes them
+ * a appropriately for all db queries. Since the filters are primarily
+ * configured in the UI, this interface provides selected, disabled and active
+ * (selected and not disabled) properties.
*/
public interface Filter {
/**
+ * get a filter that is the intersection of the given filters
+ *
* @param filters a set of filters to intersect
*
* @return a filter that is the intersection of the given filters
@@ -39,50 +44,101 @@ public interface Filter {
}
/**
+ * get a filter that is the intersection of the given filters
+ *
* @param filters a set of filters to intersect
*
* @return a filter that is the intersection of the given filters
*/
public static IntersectionFilter intersect(Filter[] filters) {
- return new IntersectionFilter<>(FXCollections.observableArrayList(filters));
+ return intersect(FXCollections.observableArrayList(filters));
}
/**
- * since filters have mutable state (active) and are observed in various
- * places, we need a mechanism to copy the current state to keep in history.
+ * since filters have mutable state (selected/disabled/active) and are
+ * observed in various places, we need a mechanism to copy the current state
+ * to keep in the history.
*
- * Concrete subtasks should implement this in a way that preserves the
- * active state and any subfilters.
+ * Concrete sub classes should implement this in a way that preserves the
+ * state and any sub-filters.
*
* @return a copy of this filter.
*/
Filter copyOf();
+ /**
+ * get the display name of this filter
+ *
+ * @return a name for this filter to show in the UI
+ */
String getDisplayName();
+ /**
+ * get a representation of this filter (and it's state) as a HTML string
+ *
+ * @return a html representation of this filter
+ */
String getHTMLReportString();
+ /**
+ * get an Ascii representation of this filter's selected state: ie [x] for
+ * selected or [ ] for not selected
+ *
+ * @return an Ascii representation of this filter's selected state
+ */
String getStringCheckBox();
+ /**
+ * is this filter selected
+ *
+ * @return true if this filter is selected
+ */
boolean isSelected();
- void setSelected(Boolean act);
+ /**
+ * set this filter selected
+ *
+ * @param selected true to selecte, false to un-select
+ */
+ void setSelected(Boolean selected);
+ /**
+ * observable selected property
+ *
+ * @return the observable selected property for this filter
+ */
SimpleBooleanProperty selectedProperty();
- /*
- * TODO: disabled state only affects the state of the checkboxes in the ui
- * and not the actual filters and shouldn't be implemented here, but it was
- * too hard to figure out how it should be implemented without intruding on
- * the ui-ignorant filters
+ /**
+ * set the filter disabled
*/
void setDisabled(Boolean act);
+ /**
+ * observable disabled property
+ *
+ * @return the observable disabled property for this filter
+ */
ObservableBooleanValue disabledProperty();
+ /**
+ * is this filter disabled
+ *
+ * @return true if this filter is disabled
+ */
boolean isDisabled();
+ /**
+ * is this filter active (selected and not disabled)
+ *
+ * @return true if this filter is active
+ */
boolean isActive();
+ /**
+ * observable active property
+ *
+ * @return the observable active property for this filter
+ */
BooleanBinding activeProperty();
}
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitsFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitsFilter.java
index c2eb196b79..ba2791ac1e 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitsFilter.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitsFilter.java
@@ -42,12 +42,12 @@ public class HashHitsFilter extends UnionFilter {
@Override
public HashHitsFilter copyOf() {
HashHitsFilter filterCopy = new HashHitsFilter();
+ //add a copy of each subfilter
+ 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());
- //add a copy of each subfilter
- this.getSubFilters().forEach(hashSetFilter ->
- filterCopy.addSubFilter(hashSetFilter.copyOf())
- );
+
return filterCopy;
}
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java
index 2b31978d7b..8b26ed1c71 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2015 Basis Technology Corp.
+ * Copyright 2015-16 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -122,4 +122,8 @@ public class RootFilter extends IntersectionFilter {
}
};
}
+
+ public TypeFilter getTypeFilter() {
+ return typeFilter;
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/TagsFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/TagsFilter.java
index 973b82a19d..7d23c49916 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/filters/TagsFilter.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/TagsFilter.java
@@ -44,12 +44,12 @@ public class TagsFilter extends UnionFilter {
@Override
public TagsFilter copyOf() {
TagsFilter filterCopy = new TagsFilter();
+ //add a copy of each subfilter
+ 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());
- //add a copy of each subfilter
- getSubFilters().forEach(tagNameFilter ->
- filterCopy.addSubFilter(tagNameFilter.copyOf())
- );
+
return filterCopy;
}
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/TypeFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/TypeFilter.java
index d3dc05985e..b897791985 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/filters/TypeFilter.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/TypeFilter.java
@@ -101,13 +101,11 @@ public class TypeFilter extends UnionFilter {
public TypeFilter copyOf() {
//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));
+ //these need to happen after the listeners fired by adding the subfilters
filterCopy.setSelected(isSelected());
filterCopy.setDisabled(isDisabled());
- //add a copy of each subfilter
- getSubFilters().forEach(typeFilter ->
- filterCopy.addSubFilter(typeFilter.copyOf(), comparator)
- );
-
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 760f0a9bc3..8512c3f819 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2013-15 Basis Technology Corp.
+ * Copyright 2013-16 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -44,12 +44,10 @@ import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.VisualizationMode;
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
-import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
import org.sleuthkit.autopsy.timeline.filters.Filter;
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
-import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
/**
* The FXML controller for the filter ui.
@@ -86,7 +84,11 @@ final public class FilterSetPanel extends BorderPane {
private final FilteredEventsModel filteredEvents;
private final TimeLineController controller;
- private final ObservableMap expansionMap = FXCollections.observableHashMap();
+ /**
+ * 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;
@NbBundle.Messages({
@@ -114,7 +116,8 @@ final public class FilterSetPanel extends BorderPane {
legendColumn.setCellValueFactory(cellDataFeatures -> cellDataFeatures.getValue().valueProperty());
legendColumn.setCellFactory(col -> new LegendCell(this.controller));
- expansionMap.put(new TypeFilter(RootEventType.getInstance()).getDisplayName(), true);
+ //type is the only filter expanded initialy
+ expansionMap.put(controller.getEventsModel().getFilter().getTypeFilter(), true);
this.filteredEvents.eventTypeZoomProperty().addListener((Observable observable) -> applyFilters());
this.filteredEvents.descriptionLODProperty().addListener((Observable observable1) -> 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 adc57dc646..58be2a2554 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterTreeItem.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterTreeItem.java
@@ -1,6 +1,23 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2014-16 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package org.sleuthkit.autopsy.timeline.ui.filtering;
-import javafx.beans.Observable;
import javafx.collections.ListChangeListener;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableMap;
@@ -14,46 +31,68 @@ 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);
- expansionMap.addListener((MapChangeListener.Change extends String, ? extends Boolean> change) -> {
- if (change.getKey() == f.getDisplayName()) {
+ //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(filter)) {
setExpanded(expansionMap.get(change.getKey()));
}
});
- if (expansionMap.get(f.getDisplayName()) != null) {
- setExpanded(expansionMap.get(f.getDisplayName()));
+ if (expansionMap.containsKey(filter)) {
+ setExpanded(expansionMap.get(filter));
}
- expandedProperty().addListener((Observable observable) -> {
- expansionMap.put(f.getDisplayName(), isExpanded());
- });
+ //keep expanion map upto date if user expands/collapses filter
+ expandedProperty().addListener(expandedProperty -> expansionMap.put(filter, isExpanded()));
- if (f instanceof CompoundFilter>) {
- 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 -> {
+ disableSubFiltersIfNotActive(compoundFilter);
+ });
+
+ disableSubFiltersIfNotActive(compoundFilter);
}
}
+
+ /**
+ * disable the sub-filters of the given compound filter if it is not active
+ *
+ * @param compoundFilter the compound filter
+ */
+ static private void disableSubFiltersIfNotActive(CompoundFilter> compoundFilter) {
+ boolean inactive = compoundFilter.isActive() == false;
+ compoundFilter.getSubFilters().forEach(subFilter -> subFilter.setDisabled(inactive));
+ }
}