Merge pull request #2066 from millmanorama/TL-multiple-images-same-name

distinguish datasource filters by datasource id and not displayname in…
This commit is contained in:
Richard Cordovano 2016-04-18 13:43:39 -04:00
commit ba4ab7e0dd
11 changed files with 186 additions and 95 deletions

View File

@ -68,10 +68,12 @@ public abstract class AbstractFilter implements Filter {
return "[" + (isSelected() ? "x" : " ") + "]"; // NON-NLS return "[" + (isSelected() ? "x" : " ") + "]"; // NON-NLS
} }
@Override
public boolean isActive() { public boolean isActive() {
return activeProperty.get(); return activeProperty().get();
} }
@Override
public BooleanBinding activeProperty() { public BooleanBinding activeProperty() {
return activeProperty; return activeProperty;
} }

View File

@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.timeline.filters;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import javafx.beans.Observable;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
@ -31,12 +30,8 @@ import javafx.collections.ObservableList;
* implementations can decide how to combine the sub-filters. * implementations can decide how to combine the sub-filters.
* *
* a {@link CompoundFilter} uses listeners to enforce the following * a {@link CompoundFilter} uses listeners to enforce the following
* relationships between it and its sub-filters: * relationships between it and its sub-filters: if all of a compound filter's
* <ol> * sub-filters become un-selected, un-select the compound filter.
* <le>if a filter becomes inactive disable all of its subfilters</le>
* <le>if a sub-filter changes active state set the parent filter active if any
* of its sub-filters are active.</le>
* </ol>
*/ */
public abstract class CompoundFilter<SubFilterType extends Filter> extends AbstractFilter { public abstract class CompoundFilter<SubFilterType extends Filter> extends AbstractFilter {
@ -57,30 +52,21 @@ public abstract class CompoundFilter<SubFilterType extends Filter> extends Abstr
public CompoundFilter(List<SubFilterType> subFilters) { public CompoundFilter(List<SubFilterType> subFilters) {
super(); super();
//listen to changes in list of subfilters and add active state listener to newly added filters //listen to changes in list of subfilters
this.subFilters.addListener((ListChangeListener.Change<? extends SubFilterType> c) -> { this.subFilters.addListener((ListChangeListener.Change<? extends SubFilterType> change) -> {
while (c.next()) { while (change.next()) {
addSubFilterListeners(c.getAddedSubList()); //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) { this.subFilters.setAll(subFilters);
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));
});
}
} }
static <SubFilterType extends Filter> boolean areSubFiltersEqual(final CompoundFilter<SubFilterType> oneFilter, final CompoundFilter<SubFilterType> otherFilter) { static <SubFilterType extends Filter> boolean areSubFiltersEqual(final CompoundFilter<SubFilterType> oneFilter, final CompoundFilter<SubFilterType> otherFilter) {

View File

@ -43,7 +43,7 @@ public class DataSourceFilter extends AbstractFilter {
@Override @Override
synchronized public DataSourceFilter copyOf() { synchronized public DataSourceFilter copyOf() {
DataSourceFilter filterCopy = new DataSourceFilter(getDisplayName(), getDataSourceID()); DataSourceFilter filterCopy = new DataSourceFilter(getDataSourceName(), getDataSourceID());
filterCopy.setSelected(isSelected()); filterCopy.setSelected(isSelected());
filterCopy.setDisabled(isDisabled()); filterCopy.setDisabled(isDisabled());
return filterCopy; return filterCopy;
@ -51,7 +51,7 @@ public class DataSourceFilter extends AbstractFilter {
@Override @Override
public String getDisplayName() { public String getDisplayName() {
return dataSourceName; return getDataSourceName();
} }
@Override @Override

View File

@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.timeline.filters;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.value.ObservableBooleanValue; import javafx.beans.value.ObservableBooleanValue;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
@ -29,19 +30,23 @@ import org.openide.util.NbBundle;
*/ */
public class DataSourcesFilter extends UnionFilter<DataSourceFilter> { public class DataSourcesFilter extends UnionFilter<DataSourceFilter> {
//keep references to the overridden properties so they don't get GC'd
private final BooleanBinding activePropertyOverride;
private final BooleanBinding disabledPropertyOverride;
public DataSourcesFilter() { public DataSourcesFilter() {
setSelected(false); disabledPropertyOverride = Bindings.or(super.disabledProperty(), Bindings.size(getSubFilters()).lessThanOrEqualTo(1));
activePropertyOverride = super.activeProperty().and(Bindings.not(disabledPropertyOverride));
} }
@Override @Override
public DataSourcesFilter copyOf() { public DataSourcesFilter copyOf() {
final DataSourcesFilter filterCopy = new DataSourcesFilter(); 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.setSelected(isSelected());
filterCopy.setDisabled(isDisabled()); filterCopy.setDisabled(isDisabled());
//add a copy of each subfilter
getSubFilters().forEach(dataSourceFilter ->
filterCopy.addSubFilter(dataSourceFilter.copyOf())
);
return filterCopy; return filterCopy;
} }
@ -65,13 +70,6 @@ public class DataSourcesFilter extends UnionFilter<DataSourceFilter> {
return string; return string;
} }
public void addSubFilter(DataSourceFilter dataSourceFilter) {
super.addSubFilter(dataSourceFilter);
if (getSubFilters().size() > 1) {
setSelected(Boolean.TRUE);
}
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj == null) { if (obj == null) {
@ -97,7 +95,12 @@ public class DataSourcesFilter extends UnionFilter<DataSourceFilter> {
@Override @Override
public ObservableBooleanValue disabledProperty() { public ObservableBooleanValue disabledProperty() {
return Bindings.or(super.disabledProperty(), Bindings.size(getSubFilters()).lessThanOrEqualTo(1)); return disabledPropertyOverride;
}
@Override
public BooleanBinding activeProperty() {
return activePropertyOverride;
} }
@Override @Override

View File

@ -25,11 +25,16 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList; 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 { public interface Filter {
/** /**
* get a filter that is the intersection of the given filters
*
* @param filters a set of filters to intersect * @param filters a set of filters to intersect
* *
* @return a filter that is the intersection of the given filters * @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 * @param filters a set of filters to intersect
* *
* @return a filter that is the intersection of the given filters * @return a filter that is the intersection of the given filters
*/ */
public static IntersectionFilter<Filter> intersect(Filter[] filters) { public static IntersectionFilter<Filter> 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 * since filters have mutable state (selected/disabled/active) and are
* places, we need a mechanism to copy the current state to keep in history. * 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 * Concrete sub classes should implement this in a way that preserves the
* active state and any subfilters. * state and any sub-filters.
* *
* @return a copy of this filter. * @return a copy of this filter.
*/ */
Filter copyOf(); Filter copyOf();
/**
* get the display name of this filter
*
* @return a name for this filter to show in the UI
*/
String getDisplayName(); 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(); 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(); String getStringCheckBox();
/**
* is this filter selected
*
* @return true if this filter is selected
*/
boolean isSelected(); 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(); SimpleBooleanProperty selectedProperty();
/* /**
* TODO: disabled state only affects the state of the checkboxes in the ui * set the filter disabled
* 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
*/ */
void setDisabled(Boolean act); void setDisabled(Boolean act);
/**
* observable disabled property
*
* @return the observable disabled property for this filter
*/
ObservableBooleanValue disabledProperty(); ObservableBooleanValue disabledProperty();
/**
* is this filter disabled
*
* @return true if this filter is disabled
*/
boolean isDisabled(); boolean isDisabled();
/**
* is this filter active (selected and not disabled)
*
* @return true if this filter is active
*/
boolean isActive(); boolean isActive();
/**
* observable active property
*
* @return the observable active property for this filter
*/
BooleanBinding activeProperty(); BooleanBinding activeProperty();
} }

View File

@ -42,12 +42,12 @@ public class HashHitsFilter extends UnionFilter<HashSetFilter> {
@Override @Override
public HashHitsFilter copyOf() { public HashHitsFilter copyOf() {
HashHitsFilter filterCopy = new HashHitsFilter(); 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.setSelected(isSelected());
filterCopy.setDisabled(isDisabled()); filterCopy.setDisabled(isDisabled());
//add a copy of each subfilter
this.getSubFilters().forEach(hashSetFilter ->
filterCopy.addSubFilter(hashSetFilter.copyOf())
);
return filterCopy; return filterCopy;
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2015 Basis Technology Corp. * Copyright 2015-16 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -122,4 +122,8 @@ public class RootFilter extends IntersectionFilter<Filter> {
} }
}; };
} }
public TypeFilter getTypeFilter() {
return typeFilter;
}
} }

View File

@ -44,12 +44,12 @@ public class TagsFilter extends UnionFilter<TagNameFilter> {
@Override @Override
public TagsFilter copyOf() { public TagsFilter copyOf() {
TagsFilter filterCopy = new TagsFilter(); 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.setSelected(isSelected());
filterCopy.setDisabled(isDisabled()); filterCopy.setDisabled(isDisabled());
//add a copy of each subfilter
getSubFilters().forEach(tagNameFilter ->
filterCopy.addSubFilter(tagNameFilter.copyOf())
);
return filterCopy; return filterCopy;
} }

View File

@ -101,13 +101,11 @@ public class TypeFilter extends UnionFilter<TypeFilter> {
public TypeFilter copyOf() { public TypeFilter copyOf() {
//make a nonrecursive copy of this filter //make a nonrecursive copy of this filter
final TypeFilter filterCopy = new TypeFilter(eventType, false); 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.setSelected(isSelected());
filterCopy.setDisabled(isDisabled()); filterCopy.setDisabled(isDisabled());
//add a copy of each subfilter
getSubFilters().forEach(typeFilter ->
filterCopy.addSubFilter(typeFilter.copyOf(), comparator)
);
return filterCopy; return filterCopy;
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013-15 Basis Technology Corp. * Copyright 2013-16 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * 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.VisualizationMode;
import org.sleuthkit.autopsy.timeline.actions.ResetFilters; import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; 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.AbstractFilter;
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter; import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
import org.sleuthkit.autopsy.timeline.filters.Filter; import org.sleuthkit.autopsy.timeline.filters.Filter;
import org.sleuthkit.autopsy.timeline.filters.RootFilter; import org.sleuthkit.autopsy.timeline.filters.RootFilter;
import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
/** /**
* The FXML controller for the filter ui. * The FXML controller for the filter ui.
@ -86,7 +84,11 @@ final public class FilterSetPanel extends BorderPane {
private final FilteredEventsModel filteredEvents; private final FilteredEventsModel filteredEvents;
private final TimeLineController controller; private final TimeLineController controller;
private final ObservableMap<String, Boolean> 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<Filter, Boolean> expansionMap = FXCollections.observableHashMap();
private double dividerPosition; private double dividerPosition;
@NbBundle.Messages({ @NbBundle.Messages({
@ -114,7 +116,8 @@ final public class FilterSetPanel extends BorderPane {
legendColumn.setCellValueFactory(cellDataFeatures -> cellDataFeatures.getValue().valueProperty()); legendColumn.setCellValueFactory(cellDataFeatures -> cellDataFeatures.getValue().valueProperty());
legendColumn.setCellFactory(col -> new LegendCell(this.controller)); 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.eventTypeZoomProperty().addListener((Observable observable) -> applyFilters());
this.filteredEvents.descriptionLODProperty().addListener((Observable observable1) -> applyFilters()); this.filteredEvents.descriptionLODProperty().addListener((Observable observable1) -> applyFilters());

View File

@ -1,6 +1,23 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014-16 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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; package org.sleuthkit.autopsy.timeline.ui.filtering;
import javafx.beans.Observable;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
import javafx.collections.MapChangeListener; import javafx.collections.MapChangeListener;
import javafx.collections.ObservableMap; import javafx.collections.ObservableMap;
@ -14,46 +31,68 @@ import org.sleuthkit.autopsy.timeline.filters.Filter;
final public class FilterTreeItem extends TreeItem<Filter> { final public class FilterTreeItem extends TreeItem<Filter> {
/** /**
* 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 * the given filter
* *
* *
* @param f the filter for this item. if f has sub-filters, tree items will * @param filter the filter for this item. if f has sub-filters, tree items
* be made for them added added to the children of this * will be made for them added added to the children of this
* FilterTreeItem * FilterTreeItem
*/ */
public FilterTreeItem(Filter f, ObservableMap<String, Boolean> expansionMap) { public FilterTreeItem(Filter filter, ObservableMap<Filter, Boolean> expansionMap) {
super(f); super(filter);
expansionMap.addListener((MapChangeListener.Change<? extends String, ? extends Boolean> change) -> { //listen to changes in the expansion map, and update expansion state of filter object
if (change.getKey() == f.getDisplayName()) { expansionMap.addListener((MapChangeListener.Change<? extends Filter, ? extends Boolean> change) -> {
if (change.getKey().equals(filter)) {
setExpanded(expansionMap.get(change.getKey())); setExpanded(expansionMap.get(change.getKey()));
} }
}); });
if (expansionMap.get(f.getDisplayName()) != null) { if (expansionMap.containsKey(filter)) {
setExpanded(expansionMap.get(f.getDisplayName())); setExpanded(expansionMap.get(filter));
} }
expandedProperty().addListener((Observable observable) -> { //keep expanion map upto date if user expands/collapses filter
expansionMap.put(f.getDisplayName(), isExpanded()); expandedProperty().addListener(expandedProperty -> expansionMap.put(filter, isExpanded()));
});
if (f instanceof CompoundFilter<?>) { //if the filter is a compound filter, add its subfilters to the tree
CompoundFilter<?> compoundFilter = (CompoundFilter<?>) f; if (filter instanceof CompoundFilter<?>) {
final CompoundFilter<?> compoundFilter = (CompoundFilter<?>) filter;
for (Filter subFilter : compoundFilter.getSubFilters()) { //add all sub filters
getChildren().add(new FilterTreeItem(subFilter, expansionMap)); 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) -> { compoundFilter.getSubFilters().addListener((ListChangeListener.Change<? extends Filter> c) -> {
while (c.next()) { while (c.next()) {
for (Filter subfFilter : c.getAddedSubList()) { 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)); 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));
}
} }