From 874703030d30f84bc2f80d19784accca4697bbc3 Mon Sep 17 00:00:00 2001 From: jmillman Date: Tue, 1 Sep 2015 17:39:10 -0400 Subject: [PATCH 01/29] single cluster WIP --- .../timeline/datamodel/EventBundle.java | 33 ++ ...{AggregateEvent.java => EventCluster.java} | 28 +- .../timeline/datamodel/EventStripe.java | 134 ++++++ .../datamodel/FilteredEventsModel.java | 4 +- .../autopsy/timeline/db/EventDB.java | 30 +- .../autopsy/timeline/db/EventsRepository.java | 6 +- .../timeline/ui/AbstractVisualization.java | 6 +- .../ui/detailview/DetailViewNode.java | 41 ++ .../ui/detailview/DetailViewPane.java | 56 +-- .../ui/detailview/DetailViewSettingsPane.fxml | 1 + .../timeline/ui/detailview/EventAxis.java | 18 +- ...teEventNode.java => EventClusterNode.java} | 94 ++-- .../ui/detailview/EventDetailChart.java | 295 +++++++----- .../ui/detailview/EventStripeNode.java | 425 ++++++++++++++++++ .../tree/EventDescriptionTreeItem.java | 9 +- .../ui/detailview/tree/EventTypeTreeItem.java | 9 +- .../timeline/ui/detailview/tree/NavPanel.java | 12 +- .../ui/detailview/tree/NavTreeItem.java | 7 +- .../timeline/ui/detailview/tree/RootItem.java | 7 +- CoreLibs/ivy.xml | 3 +- CoreLibs/nbproject/project.properties | 6 +- CoreLibs/nbproject/project.xml | 14 +- 22 files changed, 1004 insertions(+), 234 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java rename Core/src/org/sleuthkit/autopsy/timeline/datamodel/{AggregateEvent.java => EventCluster.java} (79%) create mode 100644 Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java create mode 100644 Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java rename Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/{AggregateEventNode.java => EventClusterNode.java} (87%) create mode 100644 Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java new file mode 100644 index 0000000000..0677f86c61 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java @@ -0,0 +1,33 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.timeline.datamodel; + +import java.util.Set; +import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; + +/** + * + */ +public interface EventBundle { + + String getDescription(); + + DescriptionLOD getDescriptionLOD(); + + long getEndMillis(); + + Set getEventIDs(); + + Set getEventIDsWithHashHits(); + + Set getEventIDsWithTags(); + + long getStartMillis(); + + EventType getType(); + +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/AggregateEvent.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java similarity index 79% rename from Core/src/org/sleuthkit/autopsy/timeline/datamodel/AggregateEvent.java rename to Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java index 6ca47c55be..9a26affa7e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/AggregateEvent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.timeline.datamodel; +import com.google.common.collect.Range; import com.google.common.collect.Sets; import java.util.Collections; import java.util.Set; @@ -33,7 +34,7 @@ import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; * designated 'zoom level'. */ @Immutable -public class AggregateEvent { +public class EventCluster implements EventBundle { /** * the smallest time interval containing all the aggregated events @@ -72,7 +73,7 @@ public class AggregateEvent { */ private final Set hashHits; - public AggregateEvent(Interval spanningInterval, EventType type, Set eventIDs, Set hashHits, Set tagged, String description, DescriptionLOD lod) { + public EventCluster(Interval spanningInterval, EventType type, Set eventIDs, Set hashHits, Set tagged, String description, DescriptionLOD lod) { this.span = spanningInterval; this.type = type; @@ -90,6 +91,14 @@ public class AggregateEvent { return span; } + public long getStartMillis() { + return span.getStartMillis(); + } + + public long getEndMillis() { + return span.getEndMillis(); + } + public Set getEventIDs() { return Collections.unmodifiableSet(eventIDs); } @@ -110,7 +119,8 @@ public class AggregateEvent { return type; } - public DescriptionLOD getLOD() { + @Override + public DescriptionLOD getDescriptionLOD() { return lod; } @@ -123,7 +133,7 @@ public class AggregateEvent { * @return a new aggregate event that is the result of merging the given * events */ - public static AggregateEvent merge(AggregateEvent aggEvent1, AggregateEvent ag2) { + public static EventCluster merge(EventCluster aggEvent1, EventCluster ag2) { if (aggEvent1.getType() != ag2.getType()) { throw new IllegalArgumentException("aggregate events are not compatible they have different types"); @@ -136,6 +146,14 @@ public class AggregateEvent { Sets.SetView hashHitsUnion = Sets.union(aggEvent1.getEventIDsWithHashHits(), ag2.getEventIDsWithHashHits()); Sets.SetView taggedUnion = Sets.union(aggEvent1.getEventIDsWithTags(), ag2.getEventIDsWithTags()); - return new AggregateEvent(IntervalUtils.span(aggEvent1.span, ag2.span), aggEvent1.getType(), idsUnion, hashHitsUnion, taggedUnion, aggEvent1.getDescription(), aggEvent1.lod); + return new EventCluster(IntervalUtils.span(aggEvent1.span, ag2.span), aggEvent1.getType(), idsUnion, hashHitsUnion, taggedUnion, aggEvent1.getDescription(), aggEvent1.lod); + } + + Range getRange() { + if (getEndMillis() > getStartMillis()) { + return Range.closedOpen(getSpan().getStartMillis(), getSpan().getEndMillis()); + } else { + return Range.singleton(getStartMillis()); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java new file mode 100644 index 0000000000..49dd736611 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java @@ -0,0 +1,134 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.timeline.datamodel; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Range; +import com.google.common.collect.RangeMap; +import com.google.common.collect.RangeSet; +import com.google.common.collect.TreeRangeMap; +import com.google.common.collect.TreeRangeSet; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import javax.annotation.concurrent.Immutable; +import org.python.google.common.base.Objects; +import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; + +/** + * + */ +@Immutable +public final class EventStripe implements EventBundle { + + private final RangeSet spans = TreeRangeSet.create(); + private final RangeMap spanMap = TreeRangeMap.create(); + + /** + * the type of all the aggregted events + */ + private final EventType type; + + /** + * the common description of all the aggregated events + */ + private final String description; + + /** + * the description level of detail that the events were aggregated at. + */ + private final DescriptionLOD lod; + + /** + * the set of ids of the aggregated events + */ + private final Set eventIDs = new HashSet<>(); + + /** + * the ids of the subset of aggregated events that have at least one tag + * applied to them + */ + private final Set tagged = new HashSet<>(); + + /** + * the ids of the subset of aggregated events that have at least one hash + * set hit + */ + private final Set hashHits = new HashSet<>(); + + public EventStripe(EventCluster aggEvent) { + spans.add(aggEvent.getRange()); + spanMap.put(aggEvent.getRange(), aggEvent); + type = aggEvent.getType(); + description = aggEvent.getDescription(); + lod = aggEvent.getDescriptionLOD(); + eventIDs.addAll(aggEvent.getEventIDs()); + tagged.addAll(aggEvent.getEventIDsWithTags()); + hashHits.addAll(aggEvent.getEventIDsWithHashHits()); + } + + private EventStripe(EventStripe u, EventStripe v) { + spans.addAll(u.spans); + spans.addAll(v.spans); + spanMap.putAll(u.spanMap); + spanMap.putAll(v.spanMap); + type = u.getType(); + description = u.getDescription(); + lod = u.getDescriptionLOD(); + eventIDs.addAll(u.getEventIDs()); + eventIDs.addAll(v.getEventIDs()); + tagged.addAll(u.getEventIDsWithTags()); + tagged.addAll(v.getEventIDsWithTags()); + hashHits.addAll(u.getEventIDsWithHashHits()); + hashHits.addAll(v.getEventIDsWithHashHits()); + } + + public static EventStripe merge(EventStripe u, EventStripe v) { + Preconditions.checkNotNull(u); + Preconditions.checkNotNull(v); + Preconditions.checkArgument(Objects.equal(u.description, v.description)); + Preconditions.checkArgument(Objects.equal(u.lod, v.lod)); + Preconditions.checkArgument(Objects.equal(u.type, v.type)); + return new EventStripe(u, v); + } + + public String getDescription() { + return description; + } + + public EventType getType() { + return type; + } + + public DescriptionLOD getDescriptionLOD() { + return lod; + } + + public Set getEventIDs() { + return Collections.unmodifiableSet(eventIDs); + } + + public Set getEventIDsWithHashHits() { + return Collections.unmodifiableSet(hashHits); + } + + public Set getEventIDsWithTags() { + return Collections.unmodifiableSet(tagged); + } + + public long getStartMillis() { + return spans.span().lowerEndpoint(); + } + + public long getEndMillis() { + return spans.span().upperEndpoint(); + } + + public Iterable> getRanges() { + return spans.asRanges(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java index 3de6dacc61..80aa51d571 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java @@ -326,7 +326,7 @@ public final class FilteredEventsModel { * range and pass the requested filter, using the given aggregation * to control the grouping of events */ - public List getAggregatedEvents() { + public List getAggregatedEvents() { final Interval range; final RootFilter filter; final EventTypeZoomLevel zoom; @@ -347,7 +347,7 @@ public final class FilteredEventsModel { * range and pass the requested filter, using the given aggregation * to control the grouping of events */ - public List getAggregatedEvents(ZoomParams params) { + public List getAggregatedEvents(ZoomParams params) { return repo.getAggregatedEvents(params); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java index 1b6c5943fb..ea653dadee 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java @@ -54,7 +54,7 @@ import org.joda.time.Period; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.timeline.TimeLineController; -import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent; +import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.BaseTypes; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; @@ -1031,7 +1031,7 @@ public class EventDB { } /** - * get a list of {@link AggregateEvent}s, clustered according to the given + * get a list of {@link EventCluster}s, clustered according to the given * zoom paramaters. * * @param params the zoom params that determine the zooming, filtering and @@ -1041,7 +1041,7 @@ public class EventDB { * the supplied filter, aggregated according to the given event type * and description zoom levels */ - List getAggregatedEvents(ZoomParams params) { + List getAggregatedEvents(ZoomParams params) { //unpack params Interval timeRange = params.getTimeRange(); RootFilter filter = params.getFilter(); @@ -1073,7 +1073,7 @@ public class EventDB { + "\n ORDER BY min(time)"; // NON-NLS // perform query and map results to AggregateEvent objects - List events = new ArrayList<>(); + List events = new ArrayList<>(); DBLock.lock(); try (Statement createStatement = con.createStatement(); @@ -1103,7 +1103,7 @@ public class EventDB { * * @throws SQLException */ - private AggregateEvent aggregateEventHelper(ResultSet rs, boolean useSubTypes, DescriptionLOD descriptionLOD, TagsFilter filter) throws SQLException { + private EventCluster aggregateEventHelper(ResultSet rs, boolean useSubTypes, DescriptionLOD descriptionLOD, TagsFilter filter) throws SQLException { Interval interval = new Interval(rs.getLong("min(time)") * 1000, rs.getLong("max(time)") * 1000, TimeLineController.getJodaTimeZone());// NON-NLS String eventIDsString = rs.getString("event_ids");// NON-NLS Set eventIDs = SQLHelper.unGroupConcat(eventIDsString, Long::valueOf); @@ -1128,7 +1128,7 @@ public class EventDB { } } - return new AggregateEvent(interval, type, eventIDs, hashHits, tagged, + return new EventCluster(interval, type, eventIDs, hashHits, tagged, description, descriptionLOD); } @@ -1145,36 +1145,36 @@ public class EventDB { * * @return */ - static private List mergeAggregateEvents(Period timeUnitLength, List preMergedEvents) { + static private List mergeAggregateEvents(Period timeUnitLength, List preMergedEvents) { //effectively map from type to (map from description to events) - Map> typeMap = new HashMap<>(); + Map> typeMap = new HashMap<>(); - for (AggregateEvent aggregateEvent : preMergedEvents) { + for (EventCluster aggregateEvent : preMergedEvents) { typeMap.computeIfAbsent(aggregateEvent.getType(), eventType -> HashMultimap.create()) .put(aggregateEvent.getDescription(), aggregateEvent); } //result list to return - ArrayList aggEvents = new ArrayList<>(); + ArrayList aggEvents = new ArrayList<>(); //For each (type, description) key, merge agg events - for (SetMultimap descrMap : typeMap.values()) { + for (SetMultimap descrMap : typeMap.values()) { //for each description ... for (String descr : descrMap.keySet()) { //run through the sorted events, merging together adjacent events - Iterator iterator = descrMap.get(descr).stream() + Iterator iterator = descrMap.get(descr).stream() .sorted(Comparator.comparing(event -> event.getSpan().getStartMillis())) .iterator(); - AggregateEvent current = iterator.next(); + EventCluster current = iterator.next(); while (iterator.hasNext()) { - AggregateEvent next = iterator.next(); + EventCluster next = iterator.next(); Interval gap = current.getSpan().gap(next.getSpan()); //if they overlap or gap is less one quarter timeUnitLength //TODO: 1/4 factor is arbitrary. review! -jm if (gap == null || gap.toDuration().getMillis() <= timeUnitLength.toDurationFrom(gap.getStart()).getMillis() / 4) { //merge them - current = AggregateEvent.merge(current, next); + current = EventCluster.merge(current, next); } else { //done merging into current, set next as new current aggEvents.add(current); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java index 7a537626fd..1a9e61c2d1 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java @@ -45,7 +45,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.timeline.ProgressWindow; -import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent; +import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.ArtifactEventType; @@ -98,7 +98,7 @@ public class EventsRepository { private final LoadingCache idToEventCache; private final LoadingCache> eventCountsCache; - private final LoadingCache> aggregateEventsCache; + private final LoadingCache> aggregateEventsCache; private final ObservableMap datasourcesMap = FXCollections.observableHashMap(); private final ObservableMap hashSetMap = FXCollections.observableHashMap(); @@ -206,7 +206,7 @@ public class EventsRepository { } - synchronized public List getAggregatedEvents(ZoomParams params) { + synchronized public List getAggregatedEvents(ZoomParams params) { return aggregateEventsCache.getUnchecked(params); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualization.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualization.java index 1f8a57de48..6d6fb8fb2e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualization.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualization.java @@ -73,7 +73,7 @@ import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent; * {@link XYChart} doing the rendering. Is this a good idea? -jm TODO: pull up * common history context menu items out of derived classes? -jm */ -public abstract class AbstractVisualization & TimeLineChart> extends BorderPane implements TimeLineView { +public abstract class AbstractVisualization & TimeLineChart> extends BorderPane implements TimeLineView { protected final SimpleBooleanProperty hasEvents = new SimpleBooleanProperty(true); @@ -173,8 +173,8 @@ public abstract class AbstractVisualization getYAxis(); /** - * update this visualization based on current state of zoom / - * filters. Primarily this invokes the background {@link Task} returned by + * update this visualization based on current state of zoom / filters. + * Primarily this invokes the background {@link Task} returned by * {@link #getUpdateTask()} which derived classes must implement. */ synchronized public void update() { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java new file mode 100644 index 0000000000..4378eed8b3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java @@ -0,0 +1,41 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.timeline.ui.detailview; + +import java.util.List; +import java.util.Set; +import javafx.scene.layout.Pane; +import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; +import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; + +/** + * + */ +public interface DetailViewNode { + + long getStartMillis(); + + long getEndMillis(); + + public void setDescriptionVisibility(DescriptionVisibility get); + + public Pane getSubNodePane(); + + public void setSpanWidths(List spanWidths); + + public void setDescriptionWidth(double max); + + public EventType getType(); + + public Set getEventIDs(); + + public void applySelectionEffect(boolean applied); + + public String getDescription(); + + public EventBundle getBundleDescriptor(); + +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java index e1a0ec1867..773ba836f7 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java @@ -46,6 +46,7 @@ import javafx.scene.control.RadioButton; import javafx.scene.control.ScrollBar; import javafx.scene.control.SeparatorMenuItem; import javafx.scene.control.Slider; +import javafx.scene.control.ToggleButton; import javafx.scene.control.ToggleGroup; import javafx.scene.control.TreeItem; import javafx.scene.effect.Effect; @@ -70,7 +71,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.TimeLineController; -import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent; +import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization; @@ -100,7 +101,7 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo; * TODO: refactor common code out of this class and CountsChartPane into * {@link AbstractVisualization} */ -public class DetailViewPane extends AbstractVisualization { +public class DetailViewPane extends AbstractVisualization { private final static Logger LOGGER = Logger.getLogger(CountsViewPane.class.getName()); @@ -109,20 +110,20 @@ public class DetailViewPane extends AbstractVisualization verticalAxis = new EventAxis(); + private final Axis verticalAxis = new EventAxis(); //private access to barchart data - private final Map> eventTypeToSeriesMap = new ConcurrentHashMap<>(); + private final Map> eventTypeToSeriesMap = new ConcurrentHashMap<>(); private final ScrollBar vertScrollBar = new ScrollBar(); private final Region region = new Region(); - private final ObservableList aggregatedEvents = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); + private final ObservableList aggregatedEvents = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); - private final ObservableList highlightedNodes = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); + private final ObservableList highlightedNodes = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); - public ObservableList getAggregatedEvents() { + public ObservableList getAggregatedEvents() { return aggregatedEvents; } @@ -149,7 +150,7 @@ public class DetailViewPane extends AbstractVisualization change) -> { + highlightedNodes.addListener((ListChangeListener.Change change) -> { while (change.next()) { change.getAddedSubList().forEach(aeNode -> { aeNode.applyHighlightEffect(true); @@ -212,8 +213,8 @@ public class DetailViewPane extends AbstractVisualization { highlightedNodes.clear(); selectedNodes.stream().forEach((tn) -> { - for (AggregateEventNode n : chart.getNodes((AggregateEventNode t) - -> t.getEvent().getDescription().equals(tn.getEvent().getDescription()))) { + for (EventClusterNode n : chart.getNodes((EventClusterNode t) + -> t.getEvent().getDescription().equals(tn.getDescription()))) { highlightedNodes.add(n); } }); @@ -236,7 +237,7 @@ public class DetailViewPane extends AbstractVisualization { highlightedNodes.clear(); for (TreeItem tn : treeSelectionModel.getSelectedItems()) { - for (AggregateEventNode n : chart.getNodes((AggregateEventNode t) + for (EventClusterNode n : chart.getNodes((EventClusterNode t) -> t.getEvent().getDescription().equals(tn.getValue().getDescription()))) { highlightedNodes.add(n); } @@ -250,7 +251,7 @@ public class DetailViewPane extends AbstractVisualization getYAxis() { + protected Axis getYAxis() { return verticalAxis; } @@ -279,15 +280,13 @@ public class DetailViewPane extends AbstractVisualization getSeries(final EventType et) { - XYChart.Series series = eventTypeToSeriesMap.get(et); - if (series == null) { - series = new XYChart.Series<>(); + private XYChart.Series getSeries(final EventType et) { + return eventTypeToSeriesMap.computeIfAbsent(et, (EventType t) -> { + XYChart.Series series = new XYChart.Series<>(); series.setName(et.getDisplayName()); - eventTypeToSeriesMap.put(et, series); dataSets.add(series); - } - return series; + return series; + }); } @Override @@ -328,13 +327,13 @@ public class DetailViewPane extends AbstractVisualization xyData = new BarChart.Data<>(new DateTime(e.getSpan().getStartMillis()), e); + final XYChart.Data xyData = new BarChart.Data<>(new DateTime(e.getSpan().getStartMillis()), e); Platform.runLater(() -> { if (isCancelled() == false) { @@ -359,12 +358,15 @@ public class DetailViewPane extends AbstractVisualization { + filteredEvents.refresh(); + }); + bandByTypeBox.selectedProperty().bindBidirectional(chart.bandByTypeProperty()); + truncateAllBox.selectedProperty().bindBidirectional(chart.truncateAllProperty()); + oneEventPerRowBox.selectedProperty().bindBidirectional(chart.oneEventPerRowProperty()); truncateSliderLabel.disableProperty().bind(truncateAllBox.selectedProperty().not()); truncateSliderLabel.setText(NbBundle.getMessage(this.getClass(), "DetailViewPane.truncateSliderLabel.text")); final InvalidationListener sliderListener = o -> { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewSettingsPane.fxml b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewSettingsPane.fxml index 08e3964711..051067ce67 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewSettingsPane.fxml +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewSettingsPane.fxml @@ -74,6 +74,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventAxis.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventAxis.java index 486f9f290e..89b4de0ec7 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventAxis.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventAxis.java @@ -22,21 +22,21 @@ import java.util.Collections; import java.util.List; import javafx.scene.chart.Axis; import javafx.scene.chart.XYChart; -import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent; +import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; /** * No-Op axis that doesn't do anything usefull but is necessary to pass * AggregateEvent as the second member of {@link XYChart.Data} objects */ -class EventAxis extends Axis { +class EventAxis extends Axis { @Override - public double getDisplayPosition(AggregateEvent value) { + public double getDisplayPosition(EventCluster value) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override - public AggregateEvent getValueForDisplay(double displayPosition) { + public EventCluster getValueForDisplay(double displayPosition) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @@ -46,17 +46,17 @@ class EventAxis extends Axis { } @Override - public boolean isValueOnAxis(AggregateEvent value) { + public boolean isValueOnAxis(EventCluster value) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override - public double toNumericValue(AggregateEvent value) { + public double toNumericValue(EventCluster value) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override - public AggregateEvent toRealValue(double value) { + public EventCluster toRealValue(double value) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @@ -66,7 +66,7 @@ class EventAxis extends Axis { } @Override - protected List calculateTickValues(double length, Object range) { + protected List calculateTickValues(double length, Object range) { return Collections.emptyList(); } @@ -76,7 +76,7 @@ class EventAxis extends Axis { } @Override - protected String getTickMarkLabel(AggregateEvent value) { + protected String getTickMarkLabel(EventCluster value) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AggregateEventNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java similarity index 87% rename from Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AggregateEventNode.java rename to Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java index f28ec9269d..e5e38d8375 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AggregateEventNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java @@ -64,9 +64,11 @@ import org.sleuthkit.autopsy.coreutils.LoggedTask; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.timeline.TimeLineController; -import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent; +import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; +import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent; +import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.filters.RootFilter; import org.sleuthkit.autopsy.timeline.filters.TextFilter; import org.sleuthkit.autopsy.timeline.filters.TypeFilter; @@ -76,11 +78,11 @@ import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** - * Represents an {@link AggregateEvent} in a {@link EventDetailChart}. + * Represents an {@link EventCluster} in a {@link EventDetailChart}. */ -public class AggregateEventNode extends StackPane { +public class EventClusterNode extends StackPane implements DetailViewNode { - private static final Logger LOGGER = Logger.getLogger(AggregateEventNode.class.getName()); + private static final Logger LOGGER = Logger.getLogger(EventClusterNode.class.getName()); private static final Image HASH_PIN = new Image("/org/sleuthkit/autopsy/images/hashset_hits.png"); private final static Image PLUS = new Image("/org/sleuthkit/autopsy/timeline/images/plus-button.png"); // NON-NLS @@ -97,9 +99,9 @@ public class AggregateEventNode extends StackPane { /** * The event this AggregateEventNode represents visually */ - private AggregateEvent aggEvent; + private EventCluster aggEvent; - private final AggregateEventNode parentEventNode; + private final EventClusterNode parentEventNode; /** * the region that represents the time span of this node's event @@ -169,9 +171,9 @@ public class AggregateEventNode extends StackPane { private final ImageView hashIV = new ImageView(HASH_PIN); private final ImageView tagIV = new ImageView(TAG); - public AggregateEventNode(final AggregateEvent aggEvent, AggregateEventNode parentEventNode, EventDetailChart chart) { + public EventClusterNode(final EventCluster aggEvent, EventClusterNode parentEventNode, EventDetailChart chart) { this.aggEvent = aggEvent; - descLOD.set(aggEvent.getLOD()); + descLOD.set(aggEvent.getDescriptionLOD()); this.parentEventNode = parentEventNode; this.chart = chart; sleuthkitCase = chart.getController().getAutopsyCase().getSleuthkitCase(); @@ -230,7 +232,7 @@ public class AggregateEventNode extends StackPane { //setup backgrounds final Color evtColor = aggEvent.getType().getColor(); spanFill = new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY)); - setBackground(new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY))); + setBackground(spanFill); setCursor(Cursor.HAND); spanRegion.setStyle("-fx-border-width:2 0 2 2; -fx-border-radius: 2; -fx-border-color: " + ColorUtilities.getRGBCode(evtColor) + ";"); // NON-NLS spanRegion.setBackground(spanFill); @@ -258,7 +260,7 @@ public class AggregateEventNode extends StackPane { setOnMouseClicked(new EventMouseHandler()); plusButton.disableProperty().bind(descLOD.isEqualTo(DescriptionLOD.FULL)); - minusButton.disableProperty().bind(descLOD.isEqualTo(aggEvent.getLOD())); + minusButton.disableProperty().bind(descLOD.isEqualTo(aggEvent.getDescriptionLOD())); plusButton.setOnMouseClicked(e -> { final DescriptionLOD next = descLOD.get().next(); @@ -296,7 +298,7 @@ public class AggregateEventNode extends StackPane { Map tagCounts = new HashMap<>(); if (!aggEvent.getEventIDsWithTags().isEmpty()) { - tagCounts.putAll( eventsModel.getTagCountsByTagName(aggEvent.getEventIDsWithTags())); + tagCounts.putAll(eventsModel.getTagCountsByTagName(aggEvent.getEventIDsWithTags())); } @@ -315,15 +317,16 @@ public class AggregateEventNode extends StackPane { + (hashSetCountsString.isEmpty() ? "" : "\n\nHash Set Hits\n" + hashSetCountsString) + (tagCountsString.isEmpty() ? "" : "\n\nTags\n" + tagCountsString) ); - Tooltip.install(AggregateEventNode.this, tooltip); + Tooltip.install(EventClusterNode.this, tooltip); } } + @Override public Pane getSubNodePane() { return subNodePane; } - synchronized public AggregateEvent getEvent() { + synchronized public EventCluster getEvent() { return aggEvent; } @@ -333,16 +336,23 @@ public class AggregateEventNode extends StackPane { * * @param w */ - public void setSpanWidth(double w) { + private void setSpanWidth(double w) { spanRegion.setPrefWidth(w); spanRegion.setMaxWidth(w); spanRegion.setMinWidth(Math.max(2, w)); } + @Override + public void setSpanWidths(List spanWidths) { + setSpanWidth(spanWidths.get(0)); + + } + /** * * @param w the maximum width the description label should have */ + @Override public void setDescriptionWidth(double w) { descrLabel.setMaxWidth(w); } @@ -351,7 +361,8 @@ public class AggregateEventNode extends StackPane { * @param descrVis the level of description that should be displayed */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - synchronized final void setDescriptionVisibility(DescriptionVisibility descrVis) { + @Override + public final synchronized void setDescriptionVisibility(DescriptionVisibility descrVis) { this.descrVis = descrVis; final int size = aggEvent.getEventIDs().size(); @@ -381,7 +392,8 @@ public class AggregateEventNode extends StackPane { * * @param applied true to apply the selection 'effect', false to remove it */ - void applySelectionEffect(final boolean applied) { + @Override + public void applySelectionEffect(boolean applied) { Platform.runLater(() -> { if (applied) { setBorder(selectionBorder); @@ -391,6 +403,11 @@ public class AggregateEventNode extends StackPane { }); } + @Override + public String getDescription() { + return aggEvent.getDescription(); + } + /** * apply the 'effect' to visually indicate highlighted nodes * @@ -441,7 +458,7 @@ public class AggregateEventNode extends StackPane { */ synchronized private void loadSubClusters(DescriptionLOD newDescriptionLOD) { getSubNodePane().getChildren().clear(); - if (newDescriptionLOD == aggEvent.getLOD()) { + if (newDescriptionLOD == aggEvent.getDescriptionLOD()) { chart.setRequiresLayout(true); chart.requestChartLayout(); } else { @@ -454,19 +471,19 @@ public class AggregateEventNode extends StackPane { final Interval span = aggEvent.getSpan().withEndMillis(aggEvent.getSpan().getEndMillis() + 1000); //make a task to load the subnodes - LoggedTask> loggedTask = new LoggedTask>( + LoggedTask> loggedTask = new LoggedTask>( NbBundle.getMessage(this.getClass(), "AggregateEventNode.loggedTask.name"), true) { @Override - protected List call() throws Exception { + protected List call() throws Exception { //query for the sub-clusters - List aggregatedEvents = eventsModel.getAggregatedEvents(new ZoomParams(span, + List aggregatedEvents = eventsModel.getAggregatedEvents(new ZoomParams(span, eventsModel.eventTypeZoomProperty().get(), combinedFilter, newDescriptionLOD)); //for each sub cluster make an AggregateEventNode to visually represent it, and set x-position return aggregatedEvents.stream().map(aggEvent -> { - AggregateEventNode subNode = new AggregateEventNode(aggEvent, AggregateEventNode.this, chart); + EventClusterNode subNode = new EventClusterNode(aggEvent, EventClusterNode.this, chart); subNode.setLayoutX(chart.getXAxis().getDisplayPosition(new DateTime(aggEvent.getSpan().getStartMillis())) - getLayoutXCompensation()); return subNode; }).collect(Collectors.toList()); // return list of AggregateEventNodes representing subclusters @@ -494,7 +511,7 @@ public class AggregateEventNode extends StackPane { } /** - * event handler used for mouse events on {@link AggregateEventNode}s + * event handler used for mouse events on {@link EventClusterNode}s */ private class EventMouseHandler implements EventHandler { @@ -503,11 +520,11 @@ public class AggregateEventNode extends StackPane { if (t.getButton() == MouseButton.PRIMARY) { t.consume(); if (t.isShiftDown()) { - if (chart.selectedNodes.contains(AggregateEventNode.this) == false) { - chart.selectedNodes.add(AggregateEventNode.this); + if (chart.selectedNodes.contains(EventClusterNode.this) == false) { + chart.selectedNodes.add(EventClusterNode.this); } } else if (t.isShortcutDown()) { - chart.selectedNodes.removeAll(AggregateEventNode.this); + chart.selectedNodes.removeAll(EventClusterNode.this); } else if (t.getClickCount() > 1) { final DescriptionLOD next = descLOD.get().next(); if (next != null) { @@ -515,9 +532,34 @@ public class AggregateEventNode extends StackPane { descLOD.set(next); } } else { - chart.selectedNodes.setAll(AggregateEventNode.this); + chart.selectedNodes.setAll(EventClusterNode.this); } } } } + + @Override + public long getStartMillis() { + return getEvent().getStartMillis(); + } + + @Override + public long getEndMillis() { + return getEvent().getStartMillis(); + } + + @Override + public EventType getType() { + return getEvent().getType(); + } + + @Override + public Set getEventIDs() { + return getEvent().getEventIDs(); + } + + @Override + public EventBundle getBundleDescriptor() { + return aggEvent; + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java index 9d8ba43e70..b5266f8207 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java @@ -18,15 +18,17 @@ */ package org.sleuthkit.autopsy.timeline.ui.detailview; -import com.google.common.collect.Collections2; +import com.google.common.collect.Range; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.TreeMap; +import java.util.Objects; import java.util.function.Predicate; import java.util.stream.Collectors; import javafx.animation.KeyFrame; @@ -34,6 +36,7 @@ import javafx.animation.KeyValue; import javafx.animation.Timeline; import javafx.beans.InvalidationListener; import javafx.beans.Observable; +import javafx.beans.property.Property; import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyDoubleWrapper; import javafx.beans.property.SimpleBooleanProperty; @@ -58,10 +61,12 @@ import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; +import javafx.scene.layout.Region; import javafx.scene.shape.Line; import javafx.scene.shape.StrokeLineCap; import javafx.util.Duration; import javax.annotation.concurrent.GuardedBy; +import org.apache.commons.lang3.tuple.ImmutablePair; import org.controlsfx.control.action.Action; import org.controlsfx.control.action.ActionGroup; import org.controlsfx.control.action.ActionUtils; @@ -71,7 +76,8 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.actions.Back; import org.sleuthkit.autopsy.timeline.actions.Forward; -import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent; +import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; +import org.sleuthkit.autopsy.timeline.datamodel.EventStripe; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.ui.TimeLineChart; @@ -90,7 +96,7 @@ import org.sleuthkit.autopsy.timeline.ui.TimeLineChart; * * //TODO: refactor the projected lines to a separate class. -jm */ -public final class EventDetailChart extends XYChart implements TimeLineChart { +public final class EventDetailChart extends XYChart implements TimeLineChart { private static final int PROJECTED_LINE_Y_OFFSET = 5; @@ -151,17 +157,9 @@ public final class EventDetailChart extends XYChart im /** * map from event to node */ - private final Map nodeMap = new TreeMap<>(( - AggregateEvent o1, - AggregateEvent o2) -> { - int comp = Long.compare(o1.getSpan().getStartMillis(), o2.getSpan().getStartMillis()); - if (comp != 0) { - return comp; - } else { - return Comparator.comparing(AggregateEvent::hashCode).compare(o1, o2); - } - }); - + private final Map nodeMap = new HashMap<>(); + private final Map, EventStripe> stripeDescMap = new HashMap<>(); + private final Map stripeNodeMap = new HashMap<>(); /** * true == enforce that no two events can share the same 'row', leading to * sparser but possibly clearer layout. false == put unrelated events in the @@ -169,7 +167,7 @@ public final class EventDetailChart extends XYChart im */ private final SimpleBooleanProperty oneEventPerRow = new SimpleBooleanProperty(false); - private final ObservableMap projectionMap = FXCollections.observableHashMap(); + private final ObservableMap projectionMap = FXCollections.observableHashMap(); /** * flag indicating whether this chart actually needs a layout pass @@ -177,16 +175,16 @@ public final class EventDetailChart extends XYChart im @GuardedBy(value = "this") private boolean requiresLayout = true; - final ObservableList selectedNodes; + final ObservableList selectedNodes; /** * list of series of data added to this chart TODO: replace this with a map * from name to series? -jm */ - private final ObservableList> seriesList - = FXCollections.>observableArrayList(); + private final ObservableList> seriesList + = FXCollections.>observableArrayList(); - private final ObservableList> sortedSeriesList = seriesList + private final ObservableList> sortedSeriesList = seriesList .sorted((s1, s2) -> { final List collect = EventType.allTypes.stream().map(EventType::getDisplayName).collect(Collectors.toList()); return Integer.compare(collect.indexOf(s1.getName()), collect.indexOf(s2.getName())); @@ -205,8 +203,9 @@ public final class EventDetailChart extends XYChart im * via slider if truncateAll is true */ private final SimpleDoubleProperty truncateWidth = new SimpleDoubleProperty(200.0); + private final SimpleBooleanProperty alternateLayout = new SimpleBooleanProperty(true); - EventDetailChart(DateAxis dateAxis, final Axis verticalAxis, ObservableList selectedNodes) { + EventDetailChart(DateAxis dateAxis, final Axis verticalAxis, ObservableList selectedNodes) { super(dateAxis, verticalAxis); dateAxis.setAutoRanging(false); @@ -225,6 +224,7 @@ public final class EventDetailChart extends XYChart im widthProperty().addListener(layoutInvalidationListener); heightProperty().addListener(layoutInvalidationListener); // boundsInLocalProperty().addListener(layoutInvalidationListener); + bandByType.addListener(layoutInvalidationListener); oneEventPerRow.addListener(layoutInvalidationListener); truncateAll.addListener(layoutInvalidationListener); @@ -285,7 +285,7 @@ public final class EventDetailChart extends XYChart im setOnMouseReleased(dragHandler); setOnMouseDragged(dragHandler); - projectionMap.addListener((MapChangeListener.Change change) -> { + projectionMap.addListener((MapChangeListener.Change change) -> { final Line valueRemoved = change.getValueRemoved(); if (valueRemoved != null) { getChartChildren().removeAll(valueRemoved); @@ -298,16 +298,16 @@ public final class EventDetailChart extends XYChart im this.selectedNodes = selectedNodes; this.selectedNodes.addListener(( - ListChangeListener.Change c) -> { + ListChangeListener.Change c) -> { while (c.next()) { - c.getRemoved().forEach((AggregateEventNode t) -> { + c.getRemoved().forEach((DetailViewNode t) -> { projectionMap.remove(t); }); - c.getAddedSubList().forEach((AggregateEventNode t) -> { - Line line = new Line(dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(t.getEvent().getSpan().getStartMillis(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET, - dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(t.getEvent().getSpan().getEndMillis(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET + c.getAddedSubList().forEach((DetailViewNode t) -> { + Line line = new Line(dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(t.getStartMillis(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET, + dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(t.getEndMillis(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET ); - line.setStroke(t.getEvent().getType().getColor().deriveColor(0, 1, 1, .5)); + line.setStroke(t.getType().getColor().deriveColor(0, 1, 1, .5)); line.setStrokeWidth(PROJECTED_LINE_STROKE_WIDTH); line.setStrokeLineCap(StrokeLineCap.ROUND); projectionMap.put(t, line); @@ -316,7 +316,7 @@ public final class EventDetailChart extends XYChart im } this.controller.selectEventIDs(selectedNodes.stream() - .flatMap((AggregateEventNode aggNode) -> aggNode.getEvent().getEventIDs().stream()) + .flatMap((DetailViewNode aggNode) -> aggNode.getEventIDs().stream()) .collect(Collectors.toList())); }); @@ -329,7 +329,7 @@ public final class EventDetailChart extends XYChart im intervalSelector = null; } - public synchronized SimpleBooleanProperty getBandByType() { + public synchronized SimpleBooleanProperty bandByTypeProperty() { return bandByType; } @@ -389,11 +389,11 @@ public final class EventDetailChart extends XYChart im getChartChildren().add(getIntervalSelector()); } - public synchronized SimpleBooleanProperty getOneEventPerRow() { + public synchronized SimpleBooleanProperty oneEventPerRowProperty() { return oneEventPerRow; } - public synchronized SimpleBooleanProperty getTruncateAll() { + public synchronized SimpleBooleanProperty truncateAllProperty() { return truncateAll; } @@ -407,30 +407,49 @@ public final class EventDetailChart extends XYChart im } @Override - protected synchronized void dataItemAdded(Series series, int i, Data data) { - final AggregateEvent aggEvent = data.getYValue(); - AggregateEventNode eventNode = nodeMap.get(aggEvent); - if (eventNode == null) { - eventNode = new AggregateEventNode(aggEvent, null, this); - - eventNode.setLayoutX(getXAxis().getDisplayPosition(new DateTime(aggEvent.getSpan().getStartMillis()))); - data.setNode(eventNode); - nodeMap.put(aggEvent, eventNode); - nodeGroup.getChildren().add(eventNode); - requiresLayout = true; + protected synchronized void dataItemAdded(Series series, int i, Data data) { + final EventCluster aggEvent = data.getYValue(); + if (alternateLayout.get()) { + EventStripe eventCluster = stripeDescMap.merge(ImmutablePair.of(aggEvent.getType(), aggEvent.getDescription()), + new EventStripe(aggEvent), + (EventStripe u, EventStripe v) -> { + EventStripeNode remove = stripeNodeMap.remove(u); + nodeGroup.getChildren().remove(remove); + remove = stripeNodeMap.remove(v); + nodeGroup.getChildren().remove(remove); + return EventStripe.merge(u, v); + } + ); + EventStripeNode clusterNode = new EventStripeNode(eventCluster,null, EventDetailChart.this); + stripeNodeMap.put(eventCluster, clusterNode); + nodeGroup.getChildren().add(clusterNode); + } else { + nodeMap.computeIfAbsent(aggEvent, (EventCluster t) -> { + EventClusterNode eventNode = new EventClusterNode(aggEvent, null, EventDetailChart.this); + eventNode.setLayoutX(getXAxis().getDisplayPosition(new DateTime(aggEvent.getSpan().getStartMillis()))); + nodeMap.put(aggEvent, eventNode); + nodeGroup.getChildren().add(eventNode); + return eventNode; + }); } } @Override - protected synchronized void dataItemChanged(Data data) { + protected synchronized void dataItemChanged(Data data) { //TODO: can we use this to help with local detail level adjustment -jm throw new UnsupportedOperationException("Not supported yet."); // NON-NLS //To change body of generated methods, choose Tools | Templates. } @Override - protected synchronized void dataItemRemoved(Data data, Series series) { - nodeMap.remove(data.getYValue()); - nodeGroup.getChildren().remove(data.getNode()); + protected synchronized void dataItemRemoved(Data data, Series series) { + EventCluster aggEvent = data.getYValue(); + Node removedNode = nodeMap.remove(aggEvent); + nodeGroup.getChildren().remove(removedNode); + + EventStripe removedCluster = stripeDescMap.remove(ImmutablePair.of(aggEvent.getType(), aggEvent.getDescription())); + removedNode = stripeNodeMap.remove(removedCluster); + nodeGroup.getChildren().remove(removedNode); + data.setNode(null); } @@ -465,17 +484,36 @@ public final class EventDetailChart extends XYChart im maxY.set(0.0); if (bandByType.get() == false) { + if (alternateLayout.get() == true) { + List nodes = new ArrayList<>(stripeNodeMap.values()); + Collections.sort(nodes, Comparator.comparing(DetailViewNode::getStartMillis)); + layoutNodes(nodes, minY, 0); + } else { + List nodes = new ArrayList<>(nodeMap.values()); + Collections.sort(nodes, Comparator.comparing(DetailViewNode::getStartMillis)); + layoutNodes(nodes, minY, 0); + } - ObservableList nodes = FXCollections.observableArrayList(nodeMap.values()); - FXCollections.sort(nodes, new StartTimeComparator()); - layoutNodes(nodes, minY, 0); -// layoutNodes(new ArrayList<>(nodeMap.values()), minY, 0); } else { - for (Series s : sortedSeriesList) { - ObservableList nodes = FXCollections.observableArrayList(Collections2.transform(s.getData(), Data::getNode)); - - FXCollections.sort(nodes, new StartTimeComparator()); - layoutNodes(nodes.filtered((Node n) -> n != null), minY, 0); + for (Series s : sortedSeriesList) { + if (alternateLayout.get() == true) { + List nodes = s.getData().stream() + .map(Data::getYValue) + .map(cluster -> stripeDescMap.get(ImmutablePair.of(cluster.getType(), cluster.getDescription()))) + .distinct() + .sorted(Comparator.comparing(EventStripe::getStartMillis)) + .map(stripeNodeMap::get) + .collect(Collectors.toList()); + layoutNodes(nodes, minY, 0); + } else { + List nodes = s.getData().stream() + .map(Data::getYValue) + .map(nodeMap::get) + .filter(Objects::nonNull) + .sorted(Comparator.comparing(EventClusterNode::getStartMillis)) + .collect(Collectors.toList()); + layoutNodes(nodes, minY, 0); + } minY = maxY.get(); } } @@ -486,7 +524,7 @@ public final class EventDetailChart extends XYChart im } @Override - protected synchronized void seriesAdded(Series series, int i) { + protected synchronized void seriesAdded(Series series, int i) { for (int j = 0; j < series.getData().size(); j++) { dataItemAdded(series, j, series.getData().get(j)); } @@ -495,7 +533,7 @@ public final class EventDetailChart extends XYChart im } @Override - protected synchronized void seriesRemoved(Series series) { + protected synchronized void seriesRemoved(Series series) { for (int j = 0; j < series.getData().size(); j++) { dataItemRemoved(series.getData().get(j), series); } @@ -503,44 +541,48 @@ public final class EventDetailChart extends XYChart im requiresLayout = true; } - synchronized SimpleObjectProperty getDescrVisibility() { + synchronized SimpleObjectProperty< DescriptionVisibility> getDescrVisibility() { return descrVisibility; } - synchronized ReadOnlyDoubleProperty getMaxVScroll() { + synchronized ReadOnlyDoubleProperty + getMaxVScroll() { return maxY.getReadOnlyProperty(); } - Iterable getNodes(Predicate p) { - List nodes = new ArrayList<>(); + Iterable getNodes(Predicate p) { + List nodes = new ArrayList<>(); - for (AggregateEventNode node : nodeMap.values()) { + for (EventClusterNode node : nodeMap.values()) { checkNode(node, p, nodes); } return nodes; } - Iterable getAllNodes() { + Iterable getAllNodes() { return getNodes(x -> true); } - synchronized SimpleDoubleProperty getTruncateWidth() { + synchronized SimpleDoubleProperty + getTruncateWidth() { return truncateWidth; } - synchronized void setVScroll(double d) { + synchronized void + setVScroll(double d + ) { final double h = maxY.get() - (getHeight() * .9); nodeGroup.setTranslateY(-d * h); } - private static void checkNode(AggregateEventNode node, Predicate p, List nodes) { + private static void checkNode(EventClusterNode node, Predicate p, List nodes) { if (node != null) { if (p.test(node)) { nodes.add(node); } for (Node n : node.getSubNodePane().getChildrenUnmodifiable()) { - checkNode((AggregateEventNode) n, p, nodes); + checkNode((EventClusterNode) n, p, nodes); } } } @@ -557,41 +599,77 @@ public final class EventDetailChart extends XYChart im * @param nodes * @param minY */ - private synchronized double layoutNodes(final List nodes, final double minY, final double xOffset) { + private synchronized double layoutNodes(final Collection nodes, final double minY, final double xOffset) { //hash map from y value to right most occupied x value. This tells you for a given 'row' what is the first avaialable slot Map maxXatY = new HashMap<>(); double localMax = minY; //for each node lay size it and position it in first available slot - for (Node n : nodes) { - final AggregateEventNode tlNode = (AggregateEventNode) n; - tlNode.setDescriptionVisibility(descrVisibility.get()); + for (D n : nodes) { + n.setDescriptionVisibility(descrVisibility.get()); + double rawDisplayPosition = getXAxis().getDisplayPosition(new DateTime(n.getStartMillis())); - AggregateEvent ie = tlNode.getEvent(); - final double rawDisplayPosition = getXAxis().getDisplayPosition(new DateTime(ie.getSpan().getStartMillis())); //position of start and end according to range of axis - double xPos = rawDisplayPosition - xOffset; + double startX = rawDisplayPosition - xOffset; double layoutNodesResultHeight = 0; - if (tlNode.getSubNodePane().getChildren().isEmpty() == false) { - FXCollections.sort(tlNode.getSubNodePane().getChildren(), new StartTimeComparator()); - layoutNodesResultHeight = layoutNodes(tlNode.getSubNodePane().getChildren(), 0, rawDisplayPosition); - } - double xPos2 = getXAxis().getDisplayPosition(new DateTime(ie.getSpan().getEndMillis())) - xOffset; - double span = xPos2 - xPos; - //size timespan border - tlNode.setSpanWidth(span); - if (truncateAll.get()) { //if truncate option is selected limit width of description label - tlNode.setDescriptionWidth(Math.max(span, truncateWidth.get())); - } else { //else set it unbounded - tlNode.setDescriptionWidth(USE_PREF_SIZE);//20 + new Text(tlNode.getDisplayedDescription()).getLayoutBounds().getWidth()); + double span = 0; + if (n instanceof EventClusterNode) { + if (n.getSubNodePane().getChildren().isEmpty() == false) { + List children = n.getSubNodePane().getChildren().stream() + .map(EventClusterNode.class::cast) + .sorted(Comparator.comparing(DetailViewNode::getStartMillis)) + .collect(Collectors.toList()); + layoutNodesResultHeight = layoutNodes(children, 0, rawDisplayPosition); + } + double endX = getXAxis().getDisplayPosition(new DateTime(n.getEndMillis())) - xOffset; + span = endX - startX; + + //size timespan border + n.setSpanWidths(Arrays.asList(span)); + } else { + if (n.getSubNodePane().getChildren().isEmpty() == false) { + List children = n.getSubNodePane().getChildren().stream() + .map(EventStripeNode.class::cast) + .sorted(Comparator.comparing(DetailViewNode::getStartMillis)) + .collect(Collectors.toList()); + layoutNodesResultHeight = layoutNodes(children, 0, rawDisplayPosition); + } + EventStripeNode cn = (EventStripeNode) n; + List spanWidths = new ArrayList<>(); + double x = getXAxis().getDisplayPosition(new DateTime(cn.getStartMillis()));; + double x2; + Iterator> ranges = cn.getCluster().getRanges().iterator(); + Range range = ranges.next(); + do { + x2 = getXAxis().getDisplayPosition(new DateTime(range.upperEndpoint())); + double clusterSpan = x2 - x; + span += clusterSpan; + spanWidths.add(clusterSpan); + if (ranges.hasNext()) { + range = ranges.next(); + x = getXAxis().getDisplayPosition(new DateTime(range.lowerEndpoint())); + double gapSpan = x - x2; + span += gapSpan; + spanWidths.add(gapSpan); + } + + } while (ranges.hasNext()); + + cn.setSpanWidths(spanWidths); } - tlNode.autosize(); //compute size of tlNode based on constraints and event data + if (truncateAll.get()) { //if truncate option is selected limit width of description label + n.setDescriptionWidth(Math.max(span, truncateWidth.get())); + } else { //else set it unbounded + n.setDescriptionWidth(USE_PREF_SIZE);//20 + new Text(tlNode.getDisplayedDescription()).getLayoutBounds().getWidth()); + } + + n.autosize(); //compute size of tlNode based on constraints and event data //get position of right edge of node ( influenced by description label) - double xRight = xPos + tlNode.getWidth(); + double xRight = startX + n.getWidth(); //get the height of the node - final double h = layoutNodesResultHeight == 0 ? tlNode.getHeight() : layoutNodesResultHeight + DEFAULT_ROW_HEIGHT; + final double h = layoutNodesResultHeight == 0 ? n.getHeight() : layoutNodesResultHeight + DEFAULT_ROW_HEIGHT; //initial test position double yPos = minY; @@ -612,7 +690,7 @@ public final class EventDetailChart extends XYChart im //check each pixel from bottom to top. for (double y = yPos2; y >= yPos; y--) { final Double maxX = maxXatY.get((int) y); - if (maxX != null && maxX >= xPos - 4) { + if (maxX != null && maxX >= startX - 4) { //if that pixel is already used //jump top to this y value and repeat until free slot is found. overlapping = true; @@ -630,24 +708,24 @@ public final class EventDetailChart extends XYChart im localMax = Math.max(yPos2, localMax); Timeline tm = new Timeline(new KeyFrame(Duration.seconds(1.0), - new KeyValue(tlNode.layoutXProperty(), xPos), - new KeyValue(tlNode.layoutYProperty(), yPos))); + new KeyValue(n.layoutXProperty(), startX), + new KeyValue(n.layoutYProperty(), yPos))); tm.play(); -// tlNode.relocate(xPos, yPos); } maxY.set(Math.max(maxY.get(), localMax)); return localMax - minY; } + private static final int DEFAULT_ROW_HEIGHT = 24; private void layoutProjectionMap() { - for (final Map.Entry entry : projectionMap.entrySet()) { - final AggregateEventNode aggNode = entry.getKey(); + for (final Map.Entry entry : projectionMap.entrySet()) { + final DetailViewNode aggNode = entry.getKey(); final Line line = entry.getValue(); - line.setStartX(getParentXForValue(new DateTime(aggNode.getEvent().getSpan().getStartMillis(), TimeLineController.getJodaTimeZone()))); - line.setEndX(getParentXForValue(new DateTime(aggNode.getEvent().getSpan().getEndMillis(), TimeLineController.getJodaTimeZone()))); + line.setStartX(getParentXForValue(new DateTime(aggNode.getStartMillis(), TimeLineController.getJodaTimeZone()))); + line.setEndX(getParentXForValue(new DateTime(aggNode.getEndMillis(), TimeLineController.getJodaTimeZone()))); line.setStartY(getXAxis().getLayoutY() + PROJECTED_LINE_Y_OFFSET); line.setEndY(getXAxis().getLayoutY() + PROJECTED_LINE_Y_OFFSET); } @@ -678,22 +756,17 @@ public final class EventDetailChart extends XYChart im return chartContextMenu; } - private static class StartTimeComparator implements Comparator { + Property alternateLayoutProperty() { + return alternateLayout; + } + + private static class StartTimeComparator implements Comparator { @Override - public int compare(Node n1, Node n2) { - - if (n1 == null) { - return 1; - } else if (n2 == null) { - return -1; - } else { - - return Long.compare(((AggregateEventNode) n1).getEvent().getSpan().getStartMillis(), - (((AggregateEventNode) n2).getEvent().getSpan().getStartMillis())); - } + public int compare(T n1, T n2) { + return Long.compare(n1.getStartMillis(), n2.getStartMillis() + ); } - } private class DetailIntervalSelector extends IntervalSelector { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java new file mode 100644 index 0000000000..aab34c242a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java @@ -0,0 +1,425 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.timeline.ui.detailview; + +import com.google.common.collect.Range; +import java.util.HashMap; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.stream.Collectors; +import javafx.application.Platform; +import javafx.beans.property.SimpleObjectProperty; +import javafx.event.EventHandler; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Cursor; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.OverrunStyle; +import javafx.scene.effect.DropShadow; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.Border; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.BorderStroke; +import javafx.scene.layout.BorderStrokeStyle; +import javafx.scene.layout.BorderWidths; +import javafx.scene.layout.CornerRadii; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import static javafx.scene.layout.Region.USE_COMPUTED_SIZE; +import static javafx.scene.layout.Region.USE_PREF_SIZE; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; +import org.joda.time.Interval; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.ColorUtilities; +import org.sleuthkit.autopsy.coreutils.LoggedTask; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; +import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; +import org.sleuthkit.autopsy.timeline.datamodel.EventStripe; +import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; +import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; +import org.sleuthkit.autopsy.timeline.filters.RootFilter; +import org.sleuthkit.autopsy.timeline.filters.TextFilter; +import org.sleuthkit.autopsy.timeline.filters.TypeFilter; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; +import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; +import org.sleuthkit.datamodel.SleuthkitCase; + +/** + * + */ +public class EventStripeNode extends StackPane implements DetailViewNode { + + private static final Logger LOGGER = Logger.getLogger(EventClusterNode.class.getName()); + + private static final Image HASH_PIN = new Image("/org/sleuthkit/autopsy/images/hashset_hits.png"); + private final static Image PLUS = new Image("/org/sleuthkit/autopsy/timeline/images/plus-button.png"); // NON-NLS + private final static Image MINUS = new Image("/org/sleuthkit/autopsy/timeline/images/minus-button.png"); // NON-NLS + private final static Image TAG = new Image("/org/sleuthkit/autopsy/images/green-tag-icon-16.png"); // NON-NLS + + private final Pane subNodePane = new Pane(); + private final EventStripe cluster; + private final EventStripeNode parentNode; + private final EventDetailChart chart; + private SimpleObjectProperty descLOD = new SimpleObjectProperty<>(); + private final SleuthkitCase sleuthkitCase; + private final FilteredEventsModel eventsModel; + /** + * The label used to display this node's event's description + */ + private final Label descrLabel = new Label(); + + /** + * The label used to display this node's event count + */ + private final Label countLabel = new Label(); + + private final ImageView hashIV = new ImageView(HASH_PIN); + private final ImageView tagIV = new ImageView(TAG); + private final Button plusButton = new Button(null, new ImageView(PLUS)) { + { + setMinSize(16, 16); + setMaxSize(16, 16); + setPrefSize(16, 16); + } + }; + private final Button minusButton = new Button(null, new ImageView(MINUS)) { + { + setMinSize(16, 16); + setMaxSize(16, 16); + setPrefSize(16, 16); + } + }; + private DescriptionVisibility descrVis; + private final HBox spanRegion = new HBox(); + /** + * The IamgeView used to show the icon for this node's event's type + */ + private final ImageView eventTypeImageView = new ImageView(); + private Background spanFill; + private static final CornerRadii CORNER_RADII = new CornerRadii(3); + + EventStripeNode(EventStripe cluster, EventStripeNode parentNode, EventDetailChart chart) { + this.chart = chart; + sleuthkitCase = chart.getController().getAutopsyCase().getSleuthkitCase(); + eventsModel = chart.getController().getEventsModel(); + + this.parentNode = parentNode; + this.cluster = cluster; + descLOD.set(cluster.getDescriptionLOD()); + + final Region spacer = new Region(); + HBox.setHgrow(spacer, Priority.ALWAYS); + + final HBox hBox = new HBox(descrLabel, countLabel, spacer, hashIV, tagIV, minusButton, plusButton); + if (cluster.getEventIDsWithHashHits().isEmpty()) { + hashIV.setManaged(false); + hashIV.setVisible(false); + } + if (cluster.getEventIDsWithTags().isEmpty()) { + tagIV.setManaged(false); + tagIV.setVisible(false); + } + hBox.setPrefWidth(USE_COMPUTED_SIZE); + hBox.setMinWidth(USE_PREF_SIZE); + hBox.setPadding(new Insets(2, 5, 2, 5)); + hBox.setAlignment(Pos.CENTER_LEFT); + + minusButton.setVisible(false); + plusButton.setVisible(false); + minusButton.setManaged(false); + plusButton.setManaged(false); + final BorderPane borderPane = new BorderPane(subNodePane, hBox, null, null, null); + BorderPane.setAlignment(subNodePane, Pos.TOP_LEFT); + borderPane.setPrefWidth(USE_COMPUTED_SIZE); + final Color evtColor = cluster.getType().getColor(); + + spanFill = new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .2), CORNER_RADII, Insets.EMPTY)); + for (Range r : cluster.getRanges()) { + Region region = new Region(); + region.setStyle("-fx-border-width:2 1 2 1; -fx-border-radius: 1; -fx-border-color: " + ColorUtilities.getRGBCode(evtColor.deriveColor(0, 1, 1, .3)) + ";"); // NON-NLS + region.setBackground(spanFill); + spanRegion.getChildren().addAll(region, new Region()); + } + spanRegion.getChildren().remove(spanRegion.getChildren().size() - 1); + + getChildren().addAll(spanRegion, borderPane); + setBackground(new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY))); + setAlignment(Pos.TOP_LEFT); + setMinHeight(24); + minWidthProperty().bind(spanRegion.widthProperty()); + setPrefHeight(USE_COMPUTED_SIZE); + setMaxHeight(USE_PREF_SIZE); + + //set up subnode pane sizing contraints + subNodePane.setPrefHeight(USE_COMPUTED_SIZE); + subNodePane.setMinHeight(USE_PREF_SIZE); + subNodePane.setMinWidth(USE_PREF_SIZE); + subNodePane.setMaxHeight(USE_PREF_SIZE); + subNodePane.setMaxWidth(USE_PREF_SIZE); + subNodePane.setPickOnBounds(false); + + //setup description label + eventTypeImageView.setImage(cluster.getType().getFXImage()); + descrLabel.setGraphic(eventTypeImageView); + descrLabel.setPrefWidth(USE_COMPUTED_SIZE); + descrLabel.setTextOverrun(OverrunStyle.CENTER_ELLIPSIS); + + descrLabel.setMouseTransparent(true); + setDescriptionVisibility(chart.getDescrVisibility().get()); + setOnMouseClicked(new EventMouseHandler()); + + //set up mouse hover effect and tooltip + setOnMouseEntered((MouseEvent e) -> { + //defer tooltip creation till needed, this had a surprisingly large impact on speed of loading the chart +// installTooltip(); + spanRegion.setEffect(new DropShadow(10, evtColor)); + minusButton.setVisible(true); + plusButton.setVisible(true); + minusButton.setManaged(true); + plusButton.setManaged(true); + toFront(); + }); + + setOnMouseExited((MouseEvent e) -> { + spanRegion.setEffect(null); + minusButton.setVisible(false); + plusButton.setVisible(false); + minusButton.setManaged(false); + plusButton.setManaged(false); + }); + + plusButton.disableProperty().bind(descLOD.isEqualTo(DescriptionLOD.FULL)); + minusButton.disableProperty().bind(descLOD.isEqualTo(cluster.getDescriptionLOD())); + + plusButton.setOnMouseClicked(e -> { + final DescriptionLOD next = descLOD.get().next(); + if (next != null) { + loadSubClusters(next); + descLOD.set(next); + } + }); + minusButton.setOnMouseClicked(e -> { + final DescriptionLOD previous = descLOD.get().previous(); + if (previous != null) { + loadSubClusters(previous); + descLOD.set(previous); + } + }); + } + + @Override + public long getStartMillis() { + return cluster.getStartMillis(); + } + + @Override + public void setSpanWidths(List spanWidths) { + for (int i = 0; i < spanWidths.size(); i++) { + Region get = (Region) spanRegion.getChildren().get(i); + Double w = spanWidths.get(i); + get.setPrefWidth(w); + get.setMaxWidth(w); + get.setMinWidth(Math.max(2, w)); + } + } + + public void setDescriptionVisibility(DescriptionVisibility descrVis) { + this.descrVis = descrVis; + final int size = cluster.getEventIDs().size(); + + switch (descrVis) { + case COUNT_ONLY: + descrLabel.setText(""); + countLabel.setText(String.valueOf(size)); + break; + case HIDDEN: + countLabel.setText(""); + descrLabel.setText(""); + break; + default: + case SHOWN: + String description = cluster.getDescription(); + description = parentNode != null + ? " ..." + StringUtils.substringAfter(description, parentNode.getDescription()) + : description; + descrLabel.setText(description); + countLabel.setText(((size == 1) ? "" : " (" + size + ")")); // NON-NLS + break; + } + } + + EventStripe getCluster() { + return cluster; + } + + @Override + public void setDescriptionWidth(double w) { + descrLabel.setMaxWidth(w); + } + + @Override + public long getEndMillis() { + return cluster.getEndMillis(); + } + + @Override + public Pane getSubNodePane() { + return subNodePane; + } + + /** + * event handler used for mouse events on {@link AggregateEventNode}s + */ + private class EventMouseHandler implements EventHandler { + + @Override + public void handle(MouseEvent t) { + if (t.getButton() == MouseButton.PRIMARY) { + t.consume(); + if (t.isShiftDown()) { + if (chart.selectedNodes.contains(EventStripeNode.this) == false) { + chart.selectedNodes.add(EventStripeNode.this); + } + } else if (t.isShortcutDown()) { + chart.selectedNodes.removeAll(EventStripeNode.this); + } else if (t.getClickCount() > 1) { + final DescriptionLOD next = descLOD.get().next(); + if (next != null) { + loadSubClusters(next); + descLOD.set(next); + } + } else { + chart.selectedNodes.setAll(EventStripeNode.this); + } + } + } + } + + @Override + public EventType getType() { + return cluster.getType(); + } + + @Override + public Set getEventIDs() { + return cluster.getEventIDs(); + } + private static final Border selectionBorder = new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CORNER_RADII, new BorderWidths(2))); + + /** + * apply the 'effect' to visually indicate selection + * + * @param applied true to apply the selection 'effect', false to remove it + */ + @Override + public void applySelectionEffect(boolean applied) { + Platform.runLater(() -> { + if (applied) { + setBorder(selectionBorder); + } else { + setBorder(null); + } + }); + } + + @Override + public String getDescription() { + return cluster.getDescription(); + } + + @Override + public EventBundle getBundleDescriptor() { + return getCluster(); + } + + /** + * loads sub-clusters at the given Description LOD + * + * @param newDescriptionLOD + */ + synchronized private void loadSubClusters(DescriptionLOD newDescriptionLOD) { + getSubNodePane().getChildren().clear(); + if (newDescriptionLOD == cluster.getDescriptionLOD()) { + chart.setRequiresLayout(true); + chart.requestChartLayout(); + } else { + RootFilter combinedFilter = eventsModel.filterProperty().get().copyOf(); + //make a new filter intersecting the global filter with text(description) and type filters to restrict sub-clusters + combinedFilter.getSubFilters().addAll(new TextFilter(cluster.getDescription()), + new TypeFilter(cluster.getType())); + + //make a new end inclusive span (to 'filter' with) + final Interval span = new Interval(cluster.getStartMillis(), cluster.getEndMillis() + 1000); + + //make a task to load the subnodes + LoggedTask> loggedTask = new LoggedTask>( + NbBundle.getMessage(this.getClass(), "AggregateEventNode.loggedTask.name"), true) { + + @Override + protected List call() throws Exception { + //query for the sub-clusters + List aggregatedEvents = eventsModel.getAggregatedEvents(new ZoomParams(span, + eventsModel.eventTypeZoomProperty().get(), + combinedFilter, + newDescriptionLOD)); + //for each sub cluster make an AggregateEventNode to visually represent it, and set x-position + HashMap stripeDescMap = new HashMap<>(); + for (EventCluster subCluster : aggregatedEvents) { + stripeDescMap.merge(subCluster.getDescription(), + new EventStripe(subCluster), + (EventStripe u, EventStripe v) -> { + return EventStripe.merge(u, v); + } + ); + } + + return stripeDescMap.values().stream().map(subStripe -> { + EventStripeNode subNode = new EventStripeNode(subStripe, EventStripeNode.this, chart); + subNode.setLayoutX(chart.getXAxis().getDisplayPosition(new DateTime(subStripe.getStartMillis())) - getLayoutXCompensation()); + return subNode; + }).collect(Collectors.toList()); // return list of AggregateEventNodes representing subclusters + } + + @Override + protected void succeeded() { + try { + chart.setCursor(Cursor.WAIT); + //assign subNodes and request chart layout + getSubNodePane().getChildren().setAll(get()); + setDescriptionVisibility(descrVis); + chart.setRequiresLayout(true); + chart.requestChartLayout(); + chart.setCursor(null); + } catch (InterruptedException | ExecutionException ex) { + LOGGER.log(Level.SEVERE, "Error loading subnodes", ex); + } + } + }; + + //start task + chart.getController().monitorTask(loggedTask); + } + } + + double getLayoutXCompensation() { + return (parentNode != null ? parentNode.getLayoutXCompensation() : 0) + + getBoundsInParent().getMinX(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java index 04ec644a25..f2f5a7b1da 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java @@ -20,14 +20,15 @@ package org.sleuthkit.autopsy.timeline.ui.detailview.tree; import java.util.Comparator; import javafx.scene.control.TreeItem; -import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent; +import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; +import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; /** * */ class EventDescriptionTreeItem extends NavTreeItem { - public EventDescriptionTreeItem(AggregateEvent g) { + public EventDescriptionTreeItem(EventCluster g) { setValue(new NavTreeNode(g.getType().getBaseType(), g.getDescription(), g.getEventIDs().size())); } @@ -37,7 +38,7 @@ class EventDescriptionTreeItem extends NavTreeItem { } @Override - public void insert(AggregateEvent g) { + public void insert(EventCluster g) { NavTreeNode value = getValue(); if ((value.getType().getBaseType().equals(g.getType().getBaseType()) == false) || ((value.getDescription().equals(g.getDescription()) == false))) { throw new IllegalArgumentException(); @@ -52,7 +53,7 @@ class EventDescriptionTreeItem extends NavTreeItem { } @Override - public TreeItem findTreeItemForEvent(AggregateEvent t) { + public TreeItem findTreeItemForEvent(EventBundle t) { if (getValue().getType().getBaseType() == t.getType().getBaseType() && getValue().getDescription().equals(t.getDescription())) { return this; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java index ac16b31949..d9508cfd4d 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java @@ -24,7 +24,8 @@ import java.util.concurrent.ConcurrentHashMap; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.scene.control.TreeItem; -import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent; +import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; +import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; class EventTypeTreeItem extends NavTreeItem { @@ -35,7 +36,7 @@ class EventTypeTreeItem extends NavTreeItem { private final Comparator> comparator = TreeComparator.Description; - EventTypeTreeItem(AggregateEvent g) { + EventTypeTreeItem(EventCluster g) { setValue(new NavTreeNode(g.getType().getBaseType(), g.getType().getBaseType().getDisplayName(), 0)); } @@ -52,7 +53,7 @@ class EventTypeTreeItem extends NavTreeItem { * @param tree True if it is part of a tree (versus a list) */ @Override - public void insert(AggregateEvent g) { + public void insert(EventCluster g) { EventDescriptionTreeItem treeItem = childMap.get(g.getDescription()); if (treeItem == null) { @@ -77,7 +78,7 @@ class EventTypeTreeItem extends NavTreeItem { } @Override - public TreeItem findTreeItemForEvent(AggregateEvent t) { + public TreeItem findTreeItemForEvent(EventBundle t) { if (t.getType().getBaseType() == getValue().getType().getBaseType()) { for (TreeItem child : getChildren()) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java index 361c8b3250..8718b6f90c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java @@ -41,9 +41,9 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.TimeLineView; -import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent; +import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; -import org.sleuthkit.autopsy.timeline.ui.detailview.AggregateEventNode; +import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewNode; import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane; /** @@ -91,8 +91,8 @@ public class NavPanel extends BorderPane implements TimeLineView { }); detailViewPane.getSelectedNodes().addListener((Observable observable) -> { eventsTree.getSelectionModel().clearSelection(); - detailViewPane.getSelectedNodes().forEach((AggregateEventNode t) -> { - eventsTree.getSelectionModel().select(((NavTreeItem) eventsTree.getRoot()).findTreeItemForEvent(t.getEvent())); + detailViewPane.getSelectedNodes().forEach((DetailViewNode t) -> { + eventsTree.getSelectionModel().select(((NavTreeItem) eventsTree.getRoot()).findTreeItemForEvent(t.getBundleDescriptor())); }); }); @@ -100,10 +100,10 @@ public class NavPanel extends BorderPane implements TimeLineView { private void setRoot() { RootItem root = new RootItem(); - final ObservableList aggregatedEvents = detailViewPane.getAggregatedEvents(); + final ObservableList aggregatedEvents = detailViewPane.getAggregatedEvents(); synchronized (aggregatedEvents) { - for (AggregateEvent agg : aggregatedEvents) { + for (EventCluster agg : aggregatedEvents) { root.insert(agg); } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java index 3f96c57a71..e87b10cc17 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java @@ -20,7 +20,8 @@ package org.sleuthkit.autopsy.timeline.ui.detailview.tree; import java.util.Comparator; import javafx.scene.control.TreeItem; -import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent; +import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; +import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; /** * A node in the nav tree. Manages inserts and resorts. Has parents and @@ -30,12 +31,12 @@ import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent; */ abstract class NavTreeItem extends TreeItem { - abstract void insert(AggregateEvent g); + abstract void insert(EventCluster g); abstract int getCount(); abstract void resort(Comparator> comp); - abstract TreeItem findTreeItemForEvent(AggregateEvent t); + abstract TreeItem findTreeItemForEvent(EventBundle t); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java index 95ec59287b..38a9547cba 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java @@ -24,8 +24,9 @@ import java.util.Map; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.scene.control.TreeItem; +import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; +import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; -import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent; /** * @@ -56,7 +57,7 @@ class RootItem extends NavTreeItem { * @param g Group to add */ @Override - public void insert(AggregateEvent g) { + public void insert(EventCluster g) { EventTypeTreeItem treeItem = childMap.get(g.getType().getBaseType()); if (treeItem == null) { @@ -85,7 +86,7 @@ class RootItem extends NavTreeItem { } @Override - public TreeItem findTreeItemForEvent(AggregateEvent t) { + public TreeItem findTreeItemForEvent(EventBundle t) { for (TreeItem child : getChildren()) { final TreeItem findTreeItemForEvent = ((NavTreeItem) child).findTreeItemForEvent(t); if (findTreeItemForEvent != null) { diff --git a/CoreLibs/ivy.xml b/CoreLibs/ivy.xml index 5a6cd9dd17..7e247684b4 100644 --- a/CoreLibs/ivy.xml +++ b/CoreLibs/ivy.xml @@ -1,5 +1,3 @@ - - @@ -18,6 +16,7 @@ + diff --git a/CoreLibs/nbproject/project.properties b/CoreLibs/nbproject/project.properties index bb6e8ee7c5..fac6eca1cc 100644 --- a/CoreLibs/nbproject/project.properties +++ b/CoreLibs/nbproject/project.properties @@ -20,7 +20,7 @@ file.reference.dom4j-1.6.1.jar=release/modules/ext/dom4j-1.6.1.jar file.reference.geronimo-jms_1.1_spec-1.0.jar=release/modules/ext/geronimo-jms_1.1_spec-1.0.jar file.reference.gson-1.4.jar=release/modules/ext/gson-1.4.jar file.reference.gstreamer-java-1.5.jar=release/modules/ext/gstreamer-java-1.5.jar -file.reference.guava-11.0.2.jar=release/modules/ext/guava-11.0.2.jar +file.reference.guava-18.0.jar=C:\\dev\\autopsy\\CoreLibs\\release\\modules\\ext\\guava-18.0.jar file.reference.imageio-bmp-3.1.1.jar=release/modules/ext/imageio-bmp-3.1.1.jar file.reference.imageio-core-3.1.1.jar=release/modules/ext/imageio-core-3.1.1.jar file.reference.imageio-icns-3.1.1.jar=release/modules/ext/imageio-icns-3.1.1.jar @@ -71,13 +71,13 @@ file.reference.xmlbeans-2.3.0.jar=release/modules/ext/xmlbeans-2.3.0.jar javac.source=1.7 javac.compilerargs=-Xlint -Xlint:-serial javadoc.reference.controlsfx-8.40.9.jar=release/modules/ext/controlsfx-8.40.9-javadoc.jar -javadoc.reference.guava-11.0.2.jar=release/modules/ext/guava-11.0.2-javadoc.jar +javadoc.reference.guava-18.0.jar=release/modules/ext/guava-18.0-javadoc.jar javadoc.reference.jfxtras-common-8.0-r4.jar=release/modules/ext/jfxtras-common-8.0-r4-javadoc.jar javadoc.reference.jfxtras-controls-8.0-r4.jar=release/modules/ext/jfxtras-controls-8.0-r4-javadoc.jar javadoc.reference.jfxtras-fxml-8.0-r4.jar=release/modules/ext/jfxtras-fxml-8.0-r4-javadoc.jar nbm.needs.restart=true source.reference.controlsfx-8.40.9.jar=release/modules/ext/controlsfx-8.40.9-sources.jar -source.reference.guava-11.0.2.jar=release/modules/ext/guava-11.0.2-sources.jar +source.reference.guava-18.0.jar=release/modules/ext/guava-18.0-sources.jar source.reference.jfxtras-common-8.0-r4.jar=release/modules/ext/jfxtras-common-8.0-r4-sources.jar source.reference.jfxtras-controls-8.0-r4.jar=release/modules/ext/jfxtras-controls-8.0-r4-sources.jar source.reference.jfxtras-fxml-8.0-r4.jar=release/modules/ext/jfxtras-fxml-8.0-r4-sources.jar diff --git a/CoreLibs/nbproject/project.xml b/CoreLibs/nbproject/project.xml index 5eb250b40c..09020ac24e 100644 --- a/CoreLibs/nbproject/project.xml +++ b/CoreLibs/nbproject/project.xml @@ -29,17 +29,11 @@ com.apple.eawt com.apple.eawt.event com.apple.eio - com.google.common.annotations com.google.common.base - com.google.common.base.internal com.google.common.cache com.google.common.collect com.google.common.eventbus - com.google.common.hash com.google.common.io - com.google.common.math - com.google.common.net - com.google.common.primitives com.google.common.util.concurrent com.google.gson com.google.gson.annotations @@ -708,10 +702,6 @@ ext/commons-lang3-3.0-sources.jar release/modules/ext/commons-lang3-3.0-sources.jar - - ext/guava-11.0.2.jar - release/modules/ext/guava-11.0.2.jar - ext/mail-1.4.3.jar release/modules/ext/mail-1.4.3.jar @@ -724,6 +714,10 @@ ext/imageio-pnm-3.1.1.jar release/modules/ext/imageio-pnm-3.1.1.jar + + ext/guava-18.0.jar + C:\dev\autopsy\CoreLibs\release\modules\ext\guava-18.0.jar + ext/common-lang-3.1.1.jar release/modules/ext/common-lang-3.1.1.jar From 2250b01a56bf8dc9ad6dd1fce13f9637820bc3e7 Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 2 Sep 2015 15:56:46 -0400 Subject: [PATCH 02/29] extend DetailViewNode interface to support more features of old visualization in new mode. --- .../ui/detailview/DescriptionVisibility.java | 7 +- .../ui/detailview/DetailViewNode.java | 19 ++- .../ui/detailview/DetailViewPane.java | 19 ++- .../ui/detailview/EventClusterNode.java | 35 ++--- .../ui/detailview/EventDetailChart.java | 92 ++++++------ .../ui/detailview/EventStripeNode.java | 131 +++++++++++------- 6 files changed, 163 insertions(+), 140 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DescriptionVisibility.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DescriptionVisibility.java index 6689a87ed4..3f32941725 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DescriptionVisibility.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DescriptionVisibility.java @@ -22,8 +22,9 @@ package org.sleuthkit.autopsy.timeline.ui.detailview; * Level of description shown in UI NOTE: this is a separate concept form * {@link DescriptionLOD} */ -enum DescriptionVisibility { - - HIDDEN, COUNT_ONLY, SHOWN; +public enum DescriptionVisibility { + HIDDEN, + COUNT_ONLY, + SHOWN; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java index 4378eed8b3..e8a5bb8dcb 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java @@ -7,22 +7,21 @@ package org.sleuthkit.autopsy.timeline.ui.detailview; import java.util.List; import java.util.Set; -import javafx.scene.layout.Pane; import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; /** * */ -public interface DetailViewNode { +public interface DetailViewNode> { - long getStartMillis(); + public long getStartMillis(); - long getEndMillis(); + public long getEndMillis(); public void setDescriptionVisibility(DescriptionVisibility get); - public Pane getSubNodePane(); + public List getSubNodes(); public void setSpanWidths(List spanWidths); @@ -32,10 +31,16 @@ public interface DetailViewNode { public Set getEventIDs(); - public void applySelectionEffect(boolean applied); - public String getDescription(); public EventBundle getBundleDescriptor(); + /** + * apply the 'effect' to visually indicate highlighted nodes + * + * @param applied true to apply the highlight 'effect', false to remove it + */ + void applyHighlightEffect(boolean applied); + + public void applySelectionEffect(boolean applied); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java index 773ba836f7..9118e6872a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java @@ -30,7 +30,6 @@ import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.concurrent.Task; -import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.geometry.Orientation; import javafx.scene.Cursor; @@ -101,7 +100,7 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo; * TODO: refactor common code out of this class and CountsChartPane into * {@link AbstractVisualization} */ -public class DetailViewPane extends AbstractVisualization { +public class DetailViewPane extends AbstractVisualization, EventDetailChart> { private final static Logger LOGGER = Logger.getLogger(CountsViewPane.class.getName()); @@ -121,7 +120,7 @@ public class DetailViewPane extends AbstractVisualization aggregatedEvents = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); - private final ObservableList highlightedNodes = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); + private final ObservableList> highlightedNodes = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); public ObservableList getAggregatedEvents() { return aggregatedEvents; @@ -150,7 +149,7 @@ public class DetailViewPane extends AbstractVisualization change) -> { + highlightedNodes.addListener((ListChangeListener.Change> change) -> { while (change.next()) { change.getAddedSubList().forEach(aeNode -> { aeNode.applyHighlightEffect(true); @@ -167,7 +166,7 @@ public class DetailViewPane extends AbstractVisualization) (ScrollEvent t) -> { + this.onScrollProperty().set((ScrollEvent t) -> { vertScrollBar.valueProperty().set(Math.max(0, Math.min(100, vertScrollBar.getValue() - t.getDeltaY() / 200.0))); }); @@ -213,8 +212,8 @@ public class DetailViewPane extends AbstractVisualization { highlightedNodes.clear(); selectedNodes.stream().forEach((tn) -> { - for (EventClusterNode n : chart.getNodes((EventClusterNode t) - -> t.getEvent().getDescription().equals(tn.getDescription()))) { + for (DetailViewNode n : chart.getNodes((DetailViewNode t) + -> t.getDescription().equals(tn.getDescription()))) { highlightedNodes.add(n); } }); @@ -237,8 +236,8 @@ public class DetailViewPane extends AbstractVisualization { highlightedNodes.clear(); for (TreeItem tn : treeSelectionModel.getSelectedItems()) { - for (EventClusterNode n : chart.getNodes((EventClusterNode t) - -> t.getEvent().getDescription().equals(tn.getValue().getDescription()))) { + for (DetailViewNode n : chart.getNodes((DetailViewNode t) + -> t.getDescription().equals(tn.getValue().getDescription()))) { highlightedNodes.add(n); } } @@ -358,7 +357,7 @@ public class DetailViewPane extends AbstractVisualization c1, Boolean applied) { c1.applySelectionEffect(applied); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java index e5e38d8375..133ab3e511 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java @@ -80,7 +80,7 @@ import org.sleuthkit.datamodel.TskCoreException; /** * Represents an {@link EventCluster} in a {@link EventDetailChart}. */ -public class EventClusterNode extends StackPane implements DetailViewNode { +public class EventClusterNode extends StackPane implements DetailViewNode { private static final Logger LOGGER = Logger.getLogger(EventClusterNode.class.getName()); @@ -148,18 +148,23 @@ public class EventClusterNode extends StackPane implements DetailViewNode { private final Button plusButton = new Button(null, new ImageView(PLUS)) { { - setMinSize(16, 16); - setMaxSize(16, 16); - setPrefSize(16, 16); + configureLODButton(this); } }; private final Button minusButton = new Button(null, new ImageView(MINUS)) { { - setMinSize(16, 16); - setMaxSize(16, 16); - setPrefSize(16, 16); + configureLODButton(this); } }; + + private static void configureLODButton(Button b) { + b.setMinSize(16, 16); + b.setMaxSize(16, 16); + b.setPrefSize(16, 16); + b.setVisible(false); + b.setManaged(false); + } + private final EventDetailChart chart; private SimpleObjectProperty descLOD = new SimpleObjectProperty<>(); @@ -196,10 +201,6 @@ public class EventClusterNode extends StackPane implements DetailViewNode { hBox.setPadding(new Insets(2, 5, 2, 5)); hBox.setAlignment(Pos.CENTER_LEFT); - minusButton.setVisible(false); - plusButton.setVisible(false); - minusButton.setManaged(false); - plusButton.setManaged(false); final BorderPane borderPane = new BorderPane(subNodePane, hBox, null, null, null); BorderPane.setAlignment(subNodePane, Pos.TOP_LEFT); borderPane.setPrefWidth(USE_COMPUTED_SIZE); @@ -322,8 +323,10 @@ public class EventClusterNode extends StackPane implements DetailViewNode { } @Override - public Pane getSubNodePane() { - return subNodePane; + public List getSubNodes() { + return subNodePane.getChildrenUnmodifiable().stream() + .map(EventClusterNode.class::cast) + .collect(Collectors.toList()); } synchronized public EventCluster getEvent() { @@ -413,7 +416,7 @@ public class EventClusterNode extends StackPane implements DetailViewNode { * * @param applied true to apply the highlight 'effect', false to remove it */ - synchronized void applyHighlightEffect(boolean applied) { + public synchronized void applyHighlightEffect(boolean applied) { if (applied) { descrLabel.setStyle("-fx-font-weight: bold;"); // NON-NLS @@ -457,7 +460,7 @@ public class EventClusterNode extends StackPane implements DetailViewNode { * @param newDescriptionLOD */ synchronized private void loadSubClusters(DescriptionLOD newDescriptionLOD) { - getSubNodePane().getChildren().clear(); + subNodePane.getChildren().clear(); if (newDescriptionLOD == aggEvent.getDescriptionLOD()) { chart.setRequiresLayout(true); chart.requestChartLayout(); @@ -494,7 +497,7 @@ public class EventClusterNode extends StackPane implements DetailViewNode { try { chart.setCursor(Cursor.WAIT); //assign subNodes and request chart layout - getSubNodePane().getChildren().setAll(get()); + subNodePane.getChildren().setAll(get()); setDescriptionVisibility(descrVis); chart.setRequiresLayout(true); chart.requestChartLayout(); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java index b5266f8207..8dadf6937a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java @@ -31,6 +31,7 @@ import java.util.Map; import java.util.Objects; import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.Stream; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; @@ -167,7 +168,7 @@ public final class EventDetailChart extends XYChart impl */ private final SimpleBooleanProperty oneEventPerRow = new SimpleBooleanProperty(false); - private final ObservableMap projectionMap = FXCollections.observableHashMap(); + private final ObservableMap, Line> projectionMap = FXCollections.observableHashMap(); /** * flag indicating whether this chart actually needs a layout pass @@ -175,7 +176,7 @@ public final class EventDetailChart extends XYChart impl @GuardedBy(value = "this") private boolean requiresLayout = true; - final ObservableList selectedNodes; + final ObservableList> selectedNodes; /** * list of series of data added to this chart TODO: replace this with a map @@ -205,7 +206,7 @@ public final class EventDetailChart extends XYChart impl private final SimpleDoubleProperty truncateWidth = new SimpleDoubleProperty(200.0); private final SimpleBooleanProperty alternateLayout = new SimpleBooleanProperty(true); - EventDetailChart(DateAxis dateAxis, final Axis verticalAxis, ObservableList selectedNodes) { + EventDetailChart(DateAxis dateAxis, final Axis verticalAxis, ObservableList> selectedNodes) { super(dateAxis, verticalAxis); dateAxis.setAutoRanging(false); @@ -285,7 +286,7 @@ public final class EventDetailChart extends XYChart impl setOnMouseReleased(dragHandler); setOnMouseDragged(dragHandler); - projectionMap.addListener((MapChangeListener.Change change) -> { + projectionMap.addListener((MapChangeListener.Change, ? extends Line> change) -> { final Line valueRemoved = change.getValueRemoved(); if (valueRemoved != null) { getChartChildren().removeAll(valueRemoved); @@ -298,12 +299,12 @@ public final class EventDetailChart extends XYChart impl this.selectedNodes = selectedNodes; this.selectedNodes.addListener(( - ListChangeListener.Change c) -> { + ListChangeListener.Change> c) -> { while (c.next()) { - c.getRemoved().forEach((DetailViewNode t) -> { + c.getRemoved().forEach((DetailViewNode t) -> { projectionMap.remove(t); }); - c.getAddedSubList().forEach((DetailViewNode t) -> { + c.getAddedSubList().forEach((DetailViewNode t) -> { Line line = new Line(dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(t.getStartMillis(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET, dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(t.getEndMillis(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET ); @@ -316,7 +317,7 @@ public final class EventDetailChart extends XYChart impl } this.controller.selectEventIDs(selectedNodes.stream() - .flatMap((DetailViewNode aggNode) -> aggNode.getEventIDs().stream()) + .flatMap(detailNode -> detailNode.getEventIDs().stream()) .collect(Collectors.toList())); }); @@ -420,7 +421,7 @@ public final class EventDetailChart extends XYChart impl return EventStripe.merge(u, v); } ); - EventStripeNode clusterNode = new EventStripeNode(eventCluster,null, EventDetailChart.this); + EventStripeNode clusterNode = new EventStripeNode(eventCluster, null, EventDetailChart.this); stripeNodeMap.put(eventCluster, clusterNode); nodeGroup.getChildren().add(clusterNode); } else { @@ -486,11 +487,11 @@ public final class EventDetailChart extends XYChart impl if (bandByType.get() == false) { if (alternateLayout.get() == true) { List nodes = new ArrayList<>(stripeNodeMap.values()); - Collections.sort(nodes, Comparator.comparing(DetailViewNode::getStartMillis)); + nodes.sort(Comparator.comparing(DetailViewNode::getStartMillis)); layoutNodes(nodes, minY, 0); } else { List nodes = new ArrayList<>(nodeMap.values()); - Collections.sort(nodes, Comparator.comparing(DetailViewNode::getStartMillis)); + nodes.sort(Comparator.comparing(DetailViewNode::getStartMillis)); layoutNodes(nodes, minY, 0); } @@ -545,22 +546,28 @@ public final class EventDetailChart extends XYChart impl return descrVisibility; } - synchronized ReadOnlyDoubleProperty - getMaxVScroll() { + synchronized ReadOnlyDoubleProperty getMaxVScroll() { return maxY.getReadOnlyProperty(); } - Iterable getNodes(Predicate p) { - List nodes = new ArrayList<>(); + Iterable> getNodes(Predicate> p) { + Collection> values = alternateLayout.get() + ? stripeNodeMap.values() + : nodeMap.values(); - for (EventClusterNode node : nodeMap.values()) { - checkNode(node, p, nodes); - } - - return nodes; + //collapse tree of DetailViewNoeds to list and then filter on given predicate + return values.stream() + .flatMap(EventDetailChart::flatten) + .filter(p).collect(Collectors.toList()); } - Iterable getAllNodes() { + public static Stream> flatten(DetailViewNode node) { + return Stream.concat( + Stream.of(node), + node.getSubNodes().stream().flatMap(EventDetailChart::flatten)); + } + + Iterable> getAllNodes() { return getNodes(x -> true); } @@ -576,17 +583,6 @@ public final class EventDetailChart extends XYChart impl nodeGroup.setTranslateY(-d * h); } - private static void checkNode(EventClusterNode node, Predicate p, List nodes) { - if (node != null) { - if (p.test(node)) { - nodes.add(node); - } - for (Node n : node.getSubNodePane().getChildrenUnmodifiable()) { - checkNode((EventClusterNode) n, p, nodes); - } - } - } - private void clearGuideLine() { getChartChildren().remove(guideLine); guideLine = null; @@ -599,12 +595,12 @@ public final class EventDetailChart extends XYChart impl * @param nodes * @param minY */ - private synchronized double layoutNodes(final Collection nodes, final double minY, final double xOffset) { + private synchronized > double layoutNodes(final Collection nodes, final double minY, final double xOffset) { //hash map from y value to right most occupied x value. This tells you for a given 'row' what is the first avaialable slot Map maxXatY = new HashMap<>(); double localMax = minY; //for each node lay size it and position it in first available slot - for (D n : nodes) { + for (DVRegion n : nodes) { n.setDescriptionVisibility(descrVisibility.get()); double rawDisplayPosition = getXAxis().getDisplayPosition(new DateTime(n.getStartMillis())); @@ -613,27 +609,19 @@ public final class EventDetailChart extends XYChart impl double layoutNodesResultHeight = 0; double span = 0; + List subNodes = n.getSubNodes(); + if (subNodes.isEmpty() == false) { + subNodes.sort(Comparator.comparing((DVRegion t) -> t.getStartMillis())); + layoutNodesResultHeight = layoutNodes(subNodes, 0, rawDisplayPosition); + } + if (n instanceof EventClusterNode) { - if (n.getSubNodePane().getChildren().isEmpty() == false) { - List children = n.getSubNodePane().getChildren().stream() - .map(EventClusterNode.class::cast) - .sorted(Comparator.comparing(DetailViewNode::getStartMillis)) - .collect(Collectors.toList()); - layoutNodesResultHeight = layoutNodes(children, 0, rawDisplayPosition); - } double endX = getXAxis().getDisplayPosition(new DateTime(n.getEndMillis())) - xOffset; span = endX - startX; - //size timespan border n.setSpanWidths(Arrays.asList(span)); } else { - if (n.getSubNodePane().getChildren().isEmpty() == false) { - List children = n.getSubNodePane().getChildren().stream() - .map(EventStripeNode.class::cast) - .sorted(Comparator.comparing(DetailViewNode::getStartMillis)) - .collect(Collectors.toList()); - layoutNodesResultHeight = layoutNodes(children, 0, rawDisplayPosition); - } + EventStripeNode cn = (EventStripeNode) n; List spanWidths = new ArrayList<>(); double x = getXAxis().getDisplayPosition(new DateTime(cn.getStartMillis()));; @@ -720,8 +708,8 @@ public final class EventDetailChart extends XYChart impl private static final int DEFAULT_ROW_HEIGHT = 24; private void layoutProjectionMap() { - for (final Map.Entry entry : projectionMap.entrySet()) { - final DetailViewNode aggNode = entry.getKey(); + for (final Map.Entry, Line> entry : projectionMap.entrySet()) { + final DetailViewNode aggNode = entry.getKey(); final Line line = entry.getValue(); line.setStartX(getParentXForValue(new DateTime(aggNode.getStartMillis(), TimeLineController.getJodaTimeZone()))); @@ -760,7 +748,7 @@ public final class EventDetailChart extends XYChart impl return alternateLayout; } - private static class StartTimeComparator implements Comparator { + private static class StartTimeComparator> implements Comparator { @Override public int compare(T n1, T n2) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java index aab34c242a..3953b2baba 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java @@ -18,6 +18,7 @@ import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Cursor; +import javafx.scene.Node; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.OverrunStyle; @@ -29,7 +30,6 @@ import javafx.scene.input.MouseEvent; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.Border; -import javafx.scene.layout.BorderPane; import javafx.scene.layout.BorderStroke; import javafx.scene.layout.BorderStrokeStyle; import javafx.scene.layout.BorderWidths; @@ -41,6 +41,7 @@ import javafx.scene.layout.Region; import static javafx.scene.layout.Region.USE_COMPUTED_SIZE; import static javafx.scene.layout.Region.USE_PREF_SIZE; import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; @@ -64,7 +65,7 @@ import org.sleuthkit.datamodel.SleuthkitCase; /** * */ -public class EventStripeNode extends StackPane implements DetailViewNode { +public class EventStripeNode extends StackPane implements DetailViewNode { private static final Logger LOGGER = Logger.getLogger(EventClusterNode.class.getName()); @@ -94,20 +95,28 @@ public class EventStripeNode extends StackPane implements DetailViewNode { private final ImageView tagIV = new ImageView(TAG); private final Button plusButton = new Button(null, new ImageView(PLUS)) { { - setMinSize(16, 16); - setMaxSize(16, 16); - setPrefSize(16, 16); + configureLODButton(this); } }; private final Button minusButton = new Button(null, new ImageView(MINUS)) { { - setMinSize(16, 16); - setMaxSize(16, 16); - setPrefSize(16, 16); + configureLODButton(this); } }; + + private static void configureLODButton(Button b) { + b.setMinSize(16, 16); + b.setMaxSize(16, 16); + b.setPrefSize(16, 16); + show(b, false); + } + + private static void show(Node b, boolean show) { + b.setVisible(show); + b.setManaged(show); + } private DescriptionVisibility descrVis; - private final HBox spanRegion = new HBox(); + private final HBox spansHBox = new HBox(); /** * The IamgeView used to show the icon for this node's event's type */ @@ -127,43 +136,36 @@ public class EventStripeNode extends StackPane implements DetailViewNode { final Region spacer = new Region(); HBox.setHgrow(spacer, Priority.ALWAYS); - final HBox hBox = new HBox(descrLabel, countLabel, spacer, hashIV, tagIV, minusButton, plusButton); + final HBox header = new HBox(descrLabel, countLabel, hashIV, tagIV, spacer, minusButton, plusButton); if (cluster.getEventIDsWithHashHits().isEmpty()) { - hashIV.setManaged(false); - hashIV.setVisible(false); + show(hashIV, false); } if (cluster.getEventIDsWithTags().isEmpty()) { - tagIV.setManaged(false); - tagIV.setVisible(false); + show(tagIV, false); } - hBox.setPrefWidth(USE_COMPUTED_SIZE); - hBox.setMinWidth(USE_PREF_SIZE); - hBox.setPadding(new Insets(2, 5, 2, 5)); - hBox.setAlignment(Pos.CENTER_LEFT); + header.setMinWidth(USE_PREF_SIZE); + header.setPadding(new Insets(2, 5, 2, 5)); + header.setAlignment(Pos.CENTER_LEFT); - minusButton.setVisible(false); - plusButton.setVisible(false); - minusButton.setManaged(false); - plusButton.setManaged(false); - final BorderPane borderPane = new BorderPane(subNodePane, hBox, null, null, null); - BorderPane.setAlignment(subNodePane, Pos.TOP_LEFT); - borderPane.setPrefWidth(USE_COMPUTED_SIZE); + final VBox internalVBox = new VBox(header, subNodePane); + internalVBox.setAlignment(Pos.CENTER_LEFT); final Color evtColor = cluster.getType().getColor(); spanFill = new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .2), CORNER_RADII, Insets.EMPTY)); for (Range r : cluster.getRanges()) { - Region region = new Region(); - region.setStyle("-fx-border-width:2 1 2 1; -fx-border-radius: 1; -fx-border-color: " + ColorUtilities.getRGBCode(evtColor.deriveColor(0, 1, 1, .3)) + ";"); // NON-NLS - region.setBackground(spanFill); - spanRegion.getChildren().addAll(region, new Region()); + Region spanRegion = new Region(); + spanRegion.setStyle("-fx-border-width:2 1 2 1; -fx-border-radius: 1; -fx-border-color: " + ColorUtilities.getRGBCode(evtColor.deriveColor(0, 1, 1, .3)) + ";"); // NON-NLS + spanRegion.setBackground(spanFill); + spansHBox.getChildren().addAll(spanRegion, new Region()); } - spanRegion.getChildren().remove(spanRegion.getChildren().size() - 1); - - getChildren().addAll(spanRegion, borderPane); - setBackground(new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY))); + spansHBox.getChildren().remove(spansHBox.getChildren().size() - 1); + spansHBox.setMaxWidth(USE_PREF_SIZE); + setMaxWidth(USE_PREF_SIZE); + getChildren().addAll(spansHBox, internalVBox); + setBackground(new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .05), CORNER_RADII, Insets.EMPTY))); setAlignment(Pos.TOP_LEFT); setMinHeight(24); - minWidthProperty().bind(spanRegion.widthProperty()); + minWidthProperty().bind(spansHBox.widthProperty()); setPrefHeight(USE_COMPUTED_SIZE); setMaxHeight(USE_PREF_SIZE); @@ -189,20 +191,20 @@ public class EventStripeNode extends StackPane implements DetailViewNode { setOnMouseEntered((MouseEvent e) -> { //defer tooltip creation till needed, this had a surprisingly large impact on speed of loading the chart // installTooltip(); - spanRegion.setEffect(new DropShadow(10, evtColor)); - minusButton.setVisible(true); - plusButton.setVisible(true); - minusButton.setManaged(true); - plusButton.setManaged(true); + spansHBox.setEffect(new DropShadow(10, evtColor)); + show(spacer, true); + show(minusButton, true); + show(plusButton, true); + toFront(); }); setOnMouseExited((MouseEvent e) -> { - spanRegion.setEffect(null); - minusButton.setVisible(false); - plusButton.setVisible(false); - minusButton.setManaged(false); - plusButton.setManaged(false); + spansHBox.setEffect(null); + show(spacer, false); + show(minusButton, false); + show(plusButton, false); + }); plusButton.disableProperty().bind(descLOD.isEqualTo(DescriptionLOD.FULL)); @@ -232,11 +234,11 @@ public class EventStripeNode extends StackPane implements DetailViewNode { @Override public void setSpanWidths(List spanWidths) { for (int i = 0; i < spanWidths.size(); i++) { - Region get = (Region) spanRegion.getChildren().get(i); + Region spanRegion = (Region) spansHBox.getChildren().get(i); Double w = spanWidths.get(i); - get.setPrefWidth(w); - get.setMaxWidth(w); - get.setMinWidth(Math.max(2, w)); + spanRegion.setPrefWidth(w); + spanRegion.setMaxWidth(w); + spanRegion.setMinWidth(Math.max(2, w)); } } @@ -280,8 +282,10 @@ public class EventStripeNode extends StackPane implements DetailViewNode { } @Override - public Pane getSubNodePane() { - return subNodePane; + public List getSubNodes() { + return subNodePane.getChildrenUnmodifiable().stream() + .map(EventStripeNode.class::cast) + .collect(Collectors.toList()); } /** @@ -339,6 +343,26 @@ public class EventStripeNode extends StackPane implements DetailViewNode { }); } + /** + * apply the 'effect' to visually indicate highlighted nodes + * + * @param applied true to apply the highlight 'effect', false to remove it + */ + @Override + public synchronized void applyHighlightEffect(boolean applied) { + if (applied) { + descrLabel.setStyle("-fx-font-weight: bold;"); // NON-NLS + spanFill = new Background(new BackgroundFill(cluster.getType().getColor().deriveColor(0, 1, 1, .3), CORNER_RADII, Insets.EMPTY)); + spansHBox.setBackground(spanFill); + setBackground(new Background(new BackgroundFill(cluster.getType().getColor().deriveColor(0, 1, 1, .2), CORNER_RADII, Insets.EMPTY))); + } else { + descrLabel.setStyle("-fx-font-weight: normal;"); // NON-NLS + spanFill = new Background(new BackgroundFill(cluster.getType().getColor().deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY)); + spansHBox.setBackground(spanFill); + setBackground(new Background(new BackgroundFill(cluster.getType().getColor().deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY))); + } + } + @Override public String getDescription() { return cluster.getDescription(); @@ -355,11 +379,14 @@ public class EventStripeNode extends StackPane implements DetailViewNode { * @param newDescriptionLOD */ synchronized private void loadSubClusters(DescriptionLOD newDescriptionLOD) { - getSubNodePane().getChildren().clear(); + subNodePane.getChildren().clear(); + if (newDescriptionLOD == cluster.getDescriptionLOD()) { + spansHBox.setVisible(true); chart.setRequiresLayout(true); chart.requestChartLayout(); } else { + spansHBox.setVisible(false); RootFilter combinedFilter = eventsModel.filterProperty().get().copyOf(); //make a new filter intersecting the global filter with text(description) and type filters to restrict sub-clusters combinedFilter.getSubFilters().addAll(new TextFilter(cluster.getDescription()), @@ -402,7 +429,7 @@ public class EventStripeNode extends StackPane implements DetailViewNode { try { chart.setCursor(Cursor.WAIT); //assign subNodes and request chart layout - getSubNodePane().getChildren().setAll(get()); + subNodePane.getChildren().setAll(get()); setDescriptionVisibility(descrVis); chart.setRequiresLayout(true); chart.requestChartLayout(); From 2161812bd473d6c1806f05eb1acaa6588ae34509 Mon Sep 17 00:00:00 2001 From: jmillman Date: Fri, 11 Sep 2015 15:13:26 -0400 Subject: [PATCH 03/29] WIP Cleanup, verification that all existing features till work in new alternate layout mode. --- .../timeline/datamodel/EventBundle.java | 11 +- .../timeline/datamodel/EventCluster.java | 12 +- .../timeline/datamodel/EventStripe.java | 15 ++- .../autopsy/timeline/db/EventDB.java | 2 +- .../ui/detailview/DetailViewNode.java | 4 +- .../ui/detailview/DetailViewPane.java | 10 +- .../ui/detailview/EventClusterNode.java | 58 ++++----- .../ui/detailview/EventDetailChart.java | 112 ++++++++++-------- .../ui/detailview/EventStripeNode.java | 70 +++++------ .../tree/EventDescriptionTreeItem.java | 6 +- .../ui/detailview/tree/EventTypeTreeItem.java | 4 +- .../timeline/ui/detailview/tree/NavPanel.java | 6 +- .../timeline/ui/detailview/tree/RootItem.java | 4 +- 13 files changed, 174 insertions(+), 140 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java index 0677f86c61..17d5a01e7b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java @@ -5,6 +5,7 @@ */ package org.sleuthkit.autopsy.timeline.datamodel; +import com.google.common.collect.Range; import java.util.Set; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; @@ -18,16 +19,18 @@ public interface EventBundle { DescriptionLOD getDescriptionLOD(); - long getEndMillis(); - Set getEventIDs(); Set getEventIDsWithHashHits(); Set getEventIDsWithTags(); + EventType getEventType(); + + long getEndMillis(); + long getStartMillis(); - EventType getType(); - + Iterable> getRanges(); + } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java index 9a26affa7e..9f5cc5428c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java @@ -115,7 +115,7 @@ public class EventCluster implements EventBundle { return description; } - public EventType getType() { + public EventType getEventType() { return type; } @@ -135,7 +135,7 @@ public class EventCluster implements EventBundle { */ public static EventCluster merge(EventCluster aggEvent1, EventCluster ag2) { - if (aggEvent1.getType() != ag2.getType()) { + if (aggEvent1.getEventType() != ag2.getEventType()) { throw new IllegalArgumentException("aggregate events are not compatible they have different types"); } @@ -146,7 +146,7 @@ public class EventCluster implements EventBundle { Sets.SetView hashHitsUnion = Sets.union(aggEvent1.getEventIDsWithHashHits(), ag2.getEventIDsWithHashHits()); Sets.SetView taggedUnion = Sets.union(aggEvent1.getEventIDsWithTags(), ag2.getEventIDsWithTags()); - return new EventCluster(IntervalUtils.span(aggEvent1.span, ag2.span), aggEvent1.getType(), idsUnion, hashHitsUnion, taggedUnion, aggEvent1.getDescription(), aggEvent1.lod); + return new EventCluster(IntervalUtils.span(aggEvent1.span, ag2.span), aggEvent1.getEventType(), idsUnion, hashHitsUnion, taggedUnion, aggEvent1.getDescription(), aggEvent1.lod); } Range getRange() { @@ -156,4 +156,10 @@ public class EventCluster implements EventBundle { return Range.singleton(getStartMillis()); } } + + @Override + public Iterable> getRanges() { + return Collections.singletonList(getRange()); + } + } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java index 49dd736611..9bd45ce278 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java @@ -63,7 +63,7 @@ public final class EventStripe implements EventBundle { public EventStripe(EventCluster aggEvent) { spans.add(aggEvent.getRange()); spanMap.put(aggEvent.getRange(), aggEvent); - type = aggEvent.getType(); + type = aggEvent.getEventType(); description = aggEvent.getDescription(); lod = aggEvent.getDescriptionLOD(); eventIDs.addAll(aggEvent.getEventIDs()); @@ -76,7 +76,7 @@ public final class EventStripe implements EventBundle { spans.addAll(v.spans); spanMap.putAll(u.spanMap); spanMap.putAll(v.spanMap); - type = u.getType(); + type = u.getEventType(); description = u.getDescription(); lod = u.getDescriptionLOD(); eventIDs.addAll(u.getEventIDs()); @@ -96,38 +96,47 @@ public final class EventStripe implements EventBundle { return new EventStripe(u, v); } + @Override public String getDescription() { return description; } - public EventType getType() { + @Override + public EventType getEventType() { return type; } + @Override public DescriptionLOD getDescriptionLOD() { return lod; } + @Override public Set getEventIDs() { return Collections.unmodifiableSet(eventIDs); } + @Override public Set getEventIDsWithHashHits() { return Collections.unmodifiableSet(hashHits); } + @Override public Set getEventIDsWithTags() { return Collections.unmodifiableSet(tagged); } + @Override public long getStartMillis() { return spans.span().lowerEndpoint(); } + @Override public long getEndMillis() { return spans.span().upperEndpoint(); } + @Override public Iterable> getRanges() { return spans.asRanges(); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java index ea653dadee..1beacbbf41 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java @@ -1151,7 +1151,7 @@ public class EventDB { Map> typeMap = new HashMap<>(); for (EventCluster aggregateEvent : preMergedEvents) { - typeMap.computeIfAbsent(aggregateEvent.getType(), eventType -> HashMultimap.create()) + typeMap.computeIfAbsent(aggregateEvent.getEventType(), eventType -> HashMultimap.create()) .put(aggregateEvent.getDescription(), aggregateEvent); } //result list to return diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java index e8a5bb8dcb..efd3a47586 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java @@ -27,13 +27,13 @@ public interface DetailViewNode> { public void setDescriptionWidth(double max); - public EventType getType(); + public EventType getEventType(); public Set getEventIDs(); public String getDescription(); - public EventBundle getBundleDescriptor(); + public EventBundle getEventBundle(); /** * apply the 'effect' to visually indicate highlighted nodes diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java index 9118e6872a..5f1d32b083 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java @@ -70,6 +70,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.TimeLineController; +import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; @@ -100,7 +101,7 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo; * TODO: refactor common code out of this class and CountsChartPane into * {@link AbstractVisualization} */ -public class DetailViewPane extends AbstractVisualization, EventDetailChart> { +public class DetailViewPane extends AbstractVisualization { private final static Logger LOGGER = Logger.getLogger(CountsViewPane.class.getName()); @@ -336,7 +337,7 @@ public class DetailViewPane extends AbstractVisualization { if (isCancelled() == false) { - getSeries(e.getType()).getData().add(xyData); + getSeries(e.getEventType()).getData().add(xyData); } }); } @@ -357,8 +358,9 @@ public class DetailViewPane extends AbstractVisualization c1, Boolean applied) { - c1.applySelectionEffect(applied); + protected void applySelectionEffect(EventBundle c1, Boolean selected) { + chart.applySelectionEffect(c1, selected); + } private class DetailViewSettingsPane extends HBox { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java index 133ab3e511..20baf039b1 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java @@ -99,7 +99,7 @@ public class EventClusterNode extends StackPane implements DetailViewNode hashSetCounts = new HashMap<>(); - if (!aggEvent.getEventIDsWithHashHits().isEmpty()) { + if (!eventCluster.getEventIDsWithHashHits().isEmpty()) { hashSetCounts = new HashMap<>(); try { - for (TimeLineEvent tle : eventsModel.getEventsById(aggEvent.getEventIDsWithHashHits())) { + for (TimeLineEvent tle : eventsModel.getEventsById(eventCluster.getEventIDsWithHashHits())) { Set hashSetNames = sleuthkitCase.getAbstractFileById(tle.getFileID()).getHashSetNames(); for (String hashSetName : hashSetNames) { hashSetCounts.merge(hashSetName, 1L, Long::sum); @@ -298,8 +298,8 @@ public class EventClusterNode extends StackPane implements DetailViewNode tagCounts = new HashMap<>(); - if (!aggEvent.getEventIDsWithTags().isEmpty()) { - tagCounts.putAll(eventsModel.getTagCountsByTagName(aggEvent.getEventIDsWithTags())); + if (!eventCluster.getEventIDsWithTags().isEmpty()) { + tagCounts.putAll(eventsModel.getTagCountsByTagName(eventCluster.getEventIDsWithTags())); } @@ -312,7 +312,7 @@ public class EventClusterNode extends StackPane implements DetailViewNode> loggedTask = new LoggedTask>( @@ -523,11 +523,11 @@ public class EventClusterNode extends StackPane implements DetailViewNode 1) { final DescriptionLOD next = descLOD.get().next(); if (next != null) { @@ -535,7 +535,7 @@ public class EventClusterNode extends StackPane implements DetailViewNode impl /** * map from event to node */ - private final Map nodeMap = new HashMap<>(); + private final Map clusterNodeMap = new HashMap<>(); private final Map, EventStripe> stripeDescMap = new HashMap<>(); private final Map stripeNodeMap = new HashMap<>(); /** @@ -168,7 +167,7 @@ public final class EventDetailChart extends XYChart impl */ private final SimpleBooleanProperty oneEventPerRow = new SimpleBooleanProperty(false); - private final ObservableMap, Line> projectionMap = FXCollections.observableHashMap(); + private final Map, Line> projectionMap = new HashMap<>(); /** * flag indicating whether this chart actually needs a layout pass @@ -176,7 +175,7 @@ public final class EventDetailChart extends XYChart impl @GuardedBy(value = "this") private boolean requiresLayout = true; - final ObservableList> selectedNodes; + final ObservableList selectedBundles; /** * list of series of data added to this chart TODO: replace this with a map @@ -206,11 +205,11 @@ public final class EventDetailChart extends XYChart impl private final SimpleDoubleProperty truncateWidth = new SimpleDoubleProperty(200.0); private final SimpleBooleanProperty alternateLayout = new SimpleBooleanProperty(true); - EventDetailChart(DateAxis dateAxis, final Axis verticalAxis, ObservableList> selectedNodes) { + EventDetailChart(DateAxis dateAxis, final Axis verticalAxis, ObservableList selectedNodes) { super(dateAxis, verticalAxis); dateAxis.setAutoRanging(false); - //yAxis.setVisible(false);//TODO: why doesn't this hide the vertical axis, instead we have to turn off all parts individually? -jm + //verticalAxis.setVisible(false);//TODO: why doesn't this hide the vertical axis, instead we have to turn off all parts individually? -jm verticalAxis.setTickLabelsVisible(false); verticalAxis.setTickMarkVisible(false); @@ -224,7 +223,6 @@ public final class EventDetailChart extends XYChart impl //bind listener to events that should trigger layout widthProperty().addListener(layoutInvalidationListener); heightProperty().addListener(layoutInvalidationListener); -// boundsInLocalProperty().addListener(layoutInvalidationListener); bandByType.addListener(layoutInvalidationListener); oneEventPerRow.addListener(layoutInvalidationListener); @@ -286,34 +284,41 @@ public final class EventDetailChart extends XYChart impl setOnMouseReleased(dragHandler); setOnMouseDragged(dragHandler); - projectionMap.addListener((MapChangeListener.Change, ? extends Line> change) -> { - final Line valueRemoved = change.getValueRemoved(); - if (valueRemoved != null) { - getChartChildren().removeAll(valueRemoved); - } - final Line valueAdded = change.getValueAdded(); - if (valueAdded != null) { - getChartChildren().add(valueAdded); - } - }); - - this.selectedNodes = selectedNodes; - this.selectedNodes.addListener(( - ListChangeListener.Change> c) -> { +// projectionMap.addListener((MapChangeListener.Change, ? extends Line> change) -> { +// final Line valueRemoved = change.getValueRemoved(); +// if (valueRemoved != null) { +// getChartChildren().removeAll(valueRemoved); +// } +// final Line valueAdded = change.getValueAdded(); +// if (valueAdded != null) { +// getChartChildren().add(valueAdded); +// } +// }); + this.selectedBundles = selectedNodes; + this.selectedBundles.addListener(( + ListChangeListener.Change c) -> { while (c.next()) { - c.getRemoved().forEach((DetailViewNode t) -> { - projectionMap.remove(t); - }); - c.getAddedSubList().forEach((DetailViewNode t) -> { - Line line = new Line(dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(t.getStartMillis(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET, - dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(t.getEndMillis(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET - ); - line.setStroke(t.getType().getColor().deriveColor(0, 1, 1, .5)); - line.setStrokeWidth(PROJECTED_LINE_STROKE_WIDTH); - line.setStrokeLineCap(StrokeLineCap.ROUND); - projectionMap.put(t, line); - }); + c.getRemoved().forEach((EventBundle t) -> { + t.getRanges().forEach((Range t1) -> { + Line removeAll = projectionMap.remove(t1); + getChartChildren().removeAll(removeAll); + }); + }); + c.getAddedSubList().forEach((EventBundle t) -> { + + for (Range r : t.getRanges()) { + + Line line = new Line(dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(r.lowerEndpoint(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET, + dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(r.upperEndpoint(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET + ); + line.setStroke(t.getEventType().getColor().deriveColor(0, 1, 1, .5)); + line.setStrokeWidth(PROJECTED_LINE_STROKE_WIDTH); + line.setStrokeLineCap(StrokeLineCap.ROUND); + projectionMap.put(r, line); + getChartChildren().add(line); + } + }); } this.controller.selectEventIDs(selectedNodes.stream() @@ -348,7 +353,7 @@ public final class EventDetailChart extends XYChart impl clearGuideLine(); clearIntervalSelector(); - selectedNodes.clear(); + selectedBundles.clear(); projectionMap.clear(); controller.selectEventIDs(Collections.emptyList()); }); @@ -411,7 +416,7 @@ public final class EventDetailChart extends XYChart impl protected synchronized void dataItemAdded(Series series, int i, Data data) { final EventCluster aggEvent = data.getYValue(); if (alternateLayout.get()) { - EventStripe eventCluster = stripeDescMap.merge(ImmutablePair.of(aggEvent.getType(), aggEvent.getDescription()), + EventStripe eventCluster = stripeDescMap.merge(ImmutablePair.of(aggEvent.getEventType(), aggEvent.getDescription()), new EventStripe(aggEvent), (EventStripe u, EventStripe v) -> { EventStripeNode remove = stripeNodeMap.remove(u); @@ -425,10 +430,10 @@ public final class EventDetailChart extends XYChart impl stripeNodeMap.put(eventCluster, clusterNode); nodeGroup.getChildren().add(clusterNode); } else { - nodeMap.computeIfAbsent(aggEvent, (EventCluster t) -> { + clusterNodeMap.computeIfAbsent(aggEvent, (EventCluster t) -> { EventClusterNode eventNode = new EventClusterNode(aggEvent, null, EventDetailChart.this); eventNode.setLayoutX(getXAxis().getDisplayPosition(new DateTime(aggEvent.getSpan().getStartMillis()))); - nodeMap.put(aggEvent, eventNode); + clusterNodeMap.put(aggEvent, eventNode); nodeGroup.getChildren().add(eventNode); return eventNode; }); @@ -444,10 +449,10 @@ public final class EventDetailChart extends XYChart impl @Override protected synchronized void dataItemRemoved(Data data, Series series) { EventCluster aggEvent = data.getYValue(); - Node removedNode = nodeMap.remove(aggEvent); + Node removedNode = clusterNodeMap.remove(aggEvent); nodeGroup.getChildren().remove(removedNode); - EventStripe removedCluster = stripeDescMap.remove(ImmutablePair.of(aggEvent.getType(), aggEvent.getDescription())); + EventStripe removedCluster = stripeDescMap.remove(ImmutablePair.of(aggEvent.getEventType(), aggEvent.getDescription())); removedNode = stripeNodeMap.remove(removedCluster); nodeGroup.getChildren().remove(removedNode); @@ -490,7 +495,7 @@ public final class EventDetailChart extends XYChart impl nodes.sort(Comparator.comparing(DetailViewNode::getStartMillis)); layoutNodes(nodes, minY, 0); } else { - List nodes = new ArrayList<>(nodeMap.values()); + List nodes = new ArrayList<>(clusterNodeMap.values()); nodes.sort(Comparator.comparing(DetailViewNode::getStartMillis)); layoutNodes(nodes, minY, 0); } @@ -500,7 +505,7 @@ public final class EventDetailChart extends XYChart impl if (alternateLayout.get() == true) { List nodes = s.getData().stream() .map(Data::getYValue) - .map(cluster -> stripeDescMap.get(ImmutablePair.of(cluster.getType(), cluster.getDescription()))) + .map(cluster -> stripeDescMap.get(ImmutablePair.of(cluster.getEventType(), cluster.getDescription()))) .distinct() .sorted(Comparator.comparing(EventStripe::getStartMillis)) .map(stripeNodeMap::get) @@ -509,7 +514,7 @@ public final class EventDetailChart extends XYChart impl } else { List nodes = s.getData().stream() .map(Data::getYValue) - .map(nodeMap::get) + .map(clusterNodeMap::get) .filter(Objects::nonNull) .sorted(Comparator.comparing(EventClusterNode::getStartMillis)) .collect(Collectors.toList()); @@ -553,7 +558,7 @@ public final class EventDetailChart extends XYChart impl Iterable> getNodes(Predicate> p) { Collection> values = alternateLayout.get() ? stripeNodeMap.values() - : nodeMap.values(); + : clusterNodeMap.values(); //collapse tree of DetailViewNoeds to list and then filter on given predicate return values.stream() @@ -626,7 +631,7 @@ public final class EventDetailChart extends XYChart impl List spanWidths = new ArrayList<>(); double x = getXAxis().getDisplayPosition(new DateTime(cn.getStartMillis()));; double x2; - Iterator> ranges = cn.getCluster().getRanges().iterator(); + Iterator> ranges = cn.getStripe().getRanges().iterator(); Range range = ranges.next(); do { x2 = getXAxis().getDisplayPosition(new DateTime(range.upperEndpoint())); @@ -708,12 +713,12 @@ public final class EventDetailChart extends XYChart impl private static final int DEFAULT_ROW_HEIGHT = 24; private void layoutProjectionMap() { - for (final Map.Entry, Line> entry : projectionMap.entrySet()) { - final DetailViewNode aggNode = entry.getKey(); + for (final Map.Entry, Line> entry : projectionMap.entrySet()) { + final Range eventBundle = entry.getKey(); final Line line = entry.getValue(); - line.setStartX(getParentXForValue(new DateTime(aggNode.getStartMillis(), TimeLineController.getJodaTimeZone()))); - line.setEndX(getParentXForValue(new DateTime(aggNode.getEndMillis(), TimeLineController.getJodaTimeZone()))); + line.setStartX(getParentXForValue(new DateTime(eventBundle.lowerEndpoint(), TimeLineController.getJodaTimeZone()))); + line.setEndX(getParentXForValue(new DateTime(eventBundle.upperEndpoint(), TimeLineController.getJodaTimeZone()))); line.setStartY(getXAxis().getLayoutY() + PROJECTED_LINE_Y_OFFSET); line.setEndY(getXAxis().getLayoutY() + PROJECTED_LINE_Y_OFFSET); } @@ -791,4 +796,13 @@ public final class EventDetailChart extends XYChart impl protected void requestChartLayout() { super.requestChartLayout(); } + + void applySelectionEffect(EventBundle c1, Boolean selected) { + if (alternateLayout.get()) { + EventStripe eventStripe = stripeDescMap.get(ImmutablePair.of(c1.getEventType(), c1.getDescription())); + stripeNodeMap.get(eventStripe).applySelectionEffect(selected); + } else { + clusterNodeMap.get(c1).applySelectionEffect(selected); + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java index 3953b2baba..4e03ff342d 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java @@ -75,7 +75,7 @@ public class EventStripeNode extends StackPane implements DetailViewNode descLOD = new SimpleObjectProperty<>(); @@ -124,23 +124,23 @@ public class EventStripeNode extends StackPane implements DetailViewNode r : cluster.getRanges()) { + for (Range r : eventStripe.getRanges()) { Region spanRegion = new Region(); spanRegion.setStyle("-fx-border-width:2 1 2 1; -fx-border-radius: 1; -fx-border-color: " + ColorUtilities.getRGBCode(evtColor.deriveColor(0, 1, 1, .3)) + ";"); // NON-NLS spanRegion.setBackground(spanFill); @@ -162,7 +162,7 @@ public class EventStripeNode extends StackPane implements DetailViewNode { final DescriptionLOD next = descLOD.get().next(); @@ -228,7 +228,7 @@ public class EventStripeNode extends StackPane implements DetailViewNode 1) { final DescriptionLOD next = descLOD.get().next(); if (next != null) { @@ -310,20 +310,20 @@ public class EventStripeNode extends StackPane implements DetailViewNode getEventIDs() { - return cluster.getEventIDs(); + return eventStripe.getEventIDs(); } private static final Border selectionBorder = new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CORNER_RADII, new BorderWidths(2))); @@ -352,25 +352,25 @@ public class EventStripeNode extends StackPane implements DetailViewNode> loggedTask = new LoggedTask>( diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java index f2f5a7b1da..69fdd1fe43 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java @@ -29,7 +29,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; class EventDescriptionTreeItem extends NavTreeItem { public EventDescriptionTreeItem(EventCluster g) { - setValue(new NavTreeNode(g.getType().getBaseType(), g.getDescription(), g.getEventIDs().size())); + setValue(new NavTreeNode(g.getEventType().getBaseType(), g.getDescription(), g.getEventIDs().size())); } @Override @@ -40,7 +40,7 @@ class EventDescriptionTreeItem extends NavTreeItem { @Override public void insert(EventCluster g) { NavTreeNode value = getValue(); - if ((value.getType().getBaseType().equals(g.getType().getBaseType()) == false) || ((value.getDescription().equals(g.getDescription()) == false))) { + if ((value.getType().getBaseType().equals(g.getEventType().getBaseType()) == false) || ((value.getDescription().equals(g.getDescription()) == false))) { throw new IllegalArgumentException(); } @@ -54,7 +54,7 @@ class EventDescriptionTreeItem extends NavTreeItem { @Override public TreeItem findTreeItemForEvent(EventBundle t) { - if (getValue().getType().getBaseType() == t.getType().getBaseType() && getValue().getDescription().equals(t.getDescription())) { + if (getValue().getType().getBaseType() == t.getEventType().getBaseType() && getValue().getDescription().equals(t.getDescription())) { return this; } return null; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java index d9508cfd4d..fb2947bc42 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java @@ -37,7 +37,7 @@ class EventTypeTreeItem extends NavTreeItem { private final Comparator> comparator = TreeComparator.Description; EventTypeTreeItem(EventCluster g) { - setValue(new NavTreeNode(g.getType().getBaseType(), g.getType().getBaseType().getDisplayName(), 0)); + setValue(new NavTreeNode(g.getEventType().getBaseType(), g.getEventType().getBaseType().getDisplayName(), 0)); } @Override @@ -79,7 +79,7 @@ class EventTypeTreeItem extends NavTreeItem { @Override public TreeItem findTreeItemForEvent(EventBundle t) { - if (t.getType().getBaseType() == getValue().getType().getBaseType()) { + if (t.getEventType().getBaseType() == getValue().getType().getBaseType()) { for (TreeItem child : getChildren()) { final TreeItem findTreeItemForEvent = ((NavTreeItem) child).findTreeItemForEvent(t); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java index 8718b6f90c..898917ae15 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java @@ -41,9 +41,9 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.TimeLineView; +import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; -import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewNode; import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane; /** @@ -91,8 +91,8 @@ public class NavPanel extends BorderPane implements TimeLineView { }); detailViewPane.getSelectedNodes().addListener((Observable observable) -> { eventsTree.getSelectionModel().clearSelection(); - detailViewPane.getSelectedNodes().forEach((DetailViewNode t) -> { - eventsTree.getSelectionModel().select(((NavTreeItem) eventsTree.getRoot()).findTreeItemForEvent(t.getBundleDescriptor())); + detailViewPane.getSelectedNodes().forEach((EventBundle t) -> { + eventsTree.getSelectionModel().select(((NavTreeItem) eventsTree.getRoot()).findTreeItemForEvent(t)); }); }); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java index 38a9547cba..cb15a0b622 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java @@ -59,11 +59,11 @@ class RootItem extends NavTreeItem { @Override public void insert(EventCluster g) { - EventTypeTreeItem treeItem = childMap.get(g.getType().getBaseType()); + EventTypeTreeItem treeItem = childMap.get(g.getEventType().getBaseType()); if (treeItem == null) { final EventTypeTreeItem newTreeItem = new EventTypeTreeItem(g); newTreeItem.setExpanded(true); - childMap.put(g.getType().getBaseType(), newTreeItem); + childMap.put(g.getEventType().getBaseType(), newTreeItem); newTreeItem.insert(g); Platform.runLater(() -> { From 7a85c14b2d6104a84169a9913c3ef39efcf22ea3 Mon Sep 17 00:00:00 2001 From: jmillman Date: Mon, 14 Sep 2015 15:53:41 -0400 Subject: [PATCH 04/29] refactor common code out of clustering modes --- .../timeline/datamodel/EventCluster.java | 16 +- .../timeline/datamodel/EventStripe.java | 18 +- .../ui/detailview/AbstractDetailViewNode.java | 452 ++++++++++++++++++ .../ui/detailview/DetailViewNode.java | 43 +- .../ui/detailview/DetailViewPane.java | 6 +- .../ui/detailview/EventClusterNode.java | 443 ++--------------- .../ui/detailview/EventDetailChart.java | 106 ++-- .../ui/detailview/EventStripeNode.java | 418 ++-------------- .../timeline/ui/detailview/tree/NavPanel.java | 8 +- .../ui/detailview/tree/NavTreeItem.java | 2 +- 10 files changed, 618 insertions(+), 894 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java index 9f5cc5428c..7533c486e1 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java @@ -127,26 +127,26 @@ public class EventCluster implements EventBundle { /** * merge two aggregate events into one new aggregate event. * - * @param aggEvent1 + * @param cluster1 * @param aggEVent2 * * @return a new aggregate event that is the result of merging the given * events */ - public static EventCluster merge(EventCluster aggEvent1, EventCluster ag2) { + public static EventCluster merge(EventCluster cluster1, EventCluster cluster2) { - if (aggEvent1.getEventType() != ag2.getEventType()) { + if (cluster1.getEventType() != cluster2.getEventType()) { throw new IllegalArgumentException("aggregate events are not compatible they have different types"); } - if (!aggEvent1.getDescription().equals(ag2.getDescription())) { + if (!cluster1.getDescription().equals(cluster2.getDescription())) { throw new IllegalArgumentException("aggregate events are not compatible they have different descriptions"); } - Sets.SetView idsUnion = Sets.union(aggEvent1.getEventIDs(), ag2.getEventIDs()); - Sets.SetView hashHitsUnion = Sets.union(aggEvent1.getEventIDsWithHashHits(), ag2.getEventIDsWithHashHits()); - Sets.SetView taggedUnion = Sets.union(aggEvent1.getEventIDsWithTags(), ag2.getEventIDsWithTags()); + Sets.SetView idsUnion = Sets.union(cluster1.getEventIDs(), cluster2.getEventIDs()); + Sets.SetView hashHitsUnion = Sets.union(cluster1.getEventIDsWithHashHits(), cluster2.getEventIDsWithHashHits()); + Sets.SetView taggedUnion = Sets.union(cluster1.getEventIDsWithTags(), cluster2.getEventIDsWithTags()); - return new EventCluster(IntervalUtils.span(aggEvent1.span, ag2.span), aggEvent1.getEventType(), idsUnion, hashHitsUnion, taggedUnion, aggEvent1.getDescription(), aggEvent1.lod); + return new EventCluster(IntervalUtils.span(cluster1.span, cluster2.span), cluster1.getEventType(), idsUnion, hashHitsUnion, taggedUnion, cluster1.getDescription(), cluster1.lod); } Range getRange() { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java index 9bd45ce278..e88e8ff623 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java @@ -60,15 +60,15 @@ public final class EventStripe implements EventBundle { */ private final Set hashHits = new HashSet<>(); - public EventStripe(EventCluster aggEvent) { - spans.add(aggEvent.getRange()); - spanMap.put(aggEvent.getRange(), aggEvent); - type = aggEvent.getEventType(); - description = aggEvent.getDescription(); - lod = aggEvent.getDescriptionLOD(); - eventIDs.addAll(aggEvent.getEventIDs()); - tagged.addAll(aggEvent.getEventIDsWithTags()); - hashHits.addAll(aggEvent.getEventIDsWithHashHits()); + public EventStripe(EventCluster cluster) { + spans.add(cluster.getRange()); + spanMap.put(cluster.getRange(), cluster); + type = cluster.getEventType(); + description = cluster.getDescription(); + lod = cluster.getDescriptionLOD(); + eventIDs.addAll(cluster.getEventIDs()); + tagged.addAll(cluster.getEventIDsWithTags()); + hashHits.addAll(cluster.getEventIDsWithHashHits()); } private EventStripe(EventStripe u, EventStripe v) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java new file mode 100644 index 0000000000..743e49b476 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java @@ -0,0 +1,452 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.timeline.ui.detailview; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.stream.Collectors; +import javafx.application.Platform; +import javafx.beans.property.SimpleObjectProperty; +import javafx.event.EventHandler; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Cursor; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.OverrunStyle; +import javafx.scene.effect.DropShadow; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.Border; +import javafx.scene.layout.BorderStroke; +import javafx.scene.layout.BorderStrokeStyle; +import javafx.scene.layout.BorderWidths; +import javafx.scene.layout.CornerRadii; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import static javafx.scene.layout.Region.USE_COMPUTED_SIZE; +import static javafx.scene.layout.Region.USE_PREF_SIZE; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; +import org.joda.time.Interval; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.LoggedTask; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; +import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; +import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; +import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; +import org.sleuthkit.autopsy.timeline.filters.RootFilter; +import org.sleuthkit.autopsy.timeline.filters.TextFilter; +import org.sleuthkit.autopsy.timeline.filters.TypeFilter; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; +import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; +import org.sleuthkit.datamodel.SleuthkitCase; + +public abstract class AbstractDetailViewNode< T extends EventBundle, S extends AbstractDetailViewNode> extends StackPane implements DetailViewNode> { + + static final Image HASH_PIN = new Image("/org/sleuthkit/autopsy/images/hashset_hits.png"); + static final Image PLUS = new Image("/org/sleuthkit/autopsy/timeline/images/plus-button.png"); // NON-NLS + static final Image MINUS = new Image("/org/sleuthkit/autopsy/timeline/images/minus-button.png"); // NON-NLS + static final Image TAG = new Image("/org/sleuthkit/autopsy/images/green-tag-icon-16.png"); // NON-NLS + static final CornerRadii CORNER_RADII = new CornerRadii(3); + Map dropShadowMap = new HashMap<>(); + + static void configureLODButton(Button b) { + b.setMinSize(16, 16); + b.setMaxSize(16, 16); + b.setPrefSize(16, 16); + show(b, false); + } + /** + * the border to apply when this node is 'selected' + */ + static final Border selectionBorder = new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CORNER_RADII, new BorderWidths(2))); + final Color evtColor; + + @Override + @SuppressWarnings("unchecked") + public List getSubNodes() { + return subNodePane.getChildrenUnmodifiable().stream() + .map(t -> (S) t) + .collect(Collectors.toList()); + } + + /** + * apply the 'effect' to visually indicate selection + * + * @param applied true to apply the selection 'effect', false to remove it + */ + @Override + public void applySelectionEffect(boolean applied) { + Platform.runLater(() -> { + if (applied) { + setBorder(selectionBorder); + } else { + setBorder(null); + } + }); + } + + static void show(Node b, boolean show) { + b.setVisible(show); + b.setManaged(show); + } + final ImageView hashIV = new ImageView(HASH_PIN); + final ImageView tagIV = new ImageView(TAG); + private final S parentNode; + DescriptionVisibility descrVis; + + /** + * Pane that contains AggregateEventNodes of any 'subevents' if they are + * displayed + * + * //TODO: move more of the control of subnodes/events here and out of + * EventDetail Chart + */ + final Pane subNodePane = new Pane(); + + /** + * The ImageView used to show the icon for this node's event's type + */ + private final ImageView eventTypeImageView = new ImageView(); + + /** + * The label used to display this node's event's description + */ + final Label descrLabel = new Label(); + + /** + * The label used to display this node's event count + */ + final Label countLabel = new Label(); + + private final T eventBundle; + private final EventDetailChart chart; + final SleuthkitCase sleuthkitCase; + final FilteredEventsModel eventsModel; + + final Button plusButton = new Button(null, new ImageView(PLUS)) { + { + configureLODButton(this); + } + + }; + + final Button minusButton = new Button(null, new ImageView(MINUS)) { + { + configureLODButton(this); + } + }; + SimpleObjectProperty descLOD = new SimpleObjectProperty<>(); + final HBox header; + + /** + * + * @param showControls the value of par + */ + void showDescriptionLoDControls(final boolean showControls) { + DropShadow dropShadow = dropShadowMap.computeIfAbsent(getEventType(), + eventType -> new DropShadow(10, eventType.getColor())); + getSpanFillNode().setEffect(showControls ? dropShadow : null); + show(minusButton, showControls); + show(plusButton, showControls); + } + final Region spacer = new Region(); + + RootFilter getSubClusterFilter() { + RootFilter combinedFilter = eventsModel.filterProperty().get().copyOf(); + //make a new filter intersecting the global filter with text(description) and type filters to restrict sub-clusters + combinedFilter.getSubFilters().addAll(new TextFilter(getEventBundle().getDescription()), + new TypeFilter(getEventType())); + return combinedFilter; + } + + abstract Collection makeBundlesFromClusters(List eventClusters); + + abstract void showSpans(final boolean showSpans); + + public AbstractDetailViewNode(EventDetailChart chart, T bundle, S parentEventNode) { + this.eventBundle = bundle; + this.parentNode = parentEventNode; + this.chart = chart; + descLOD.set(bundle.getDescriptionLOD()); + sleuthkitCase = chart.getController().getAutopsyCase().getSleuthkitCase(); + eventsModel = chart.getController().getEventsModel(); + + if (eventBundle.getEventIDsWithHashHits().isEmpty()) { + show(hashIV, false); + } + if (eventBundle.getEventIDsWithTags().isEmpty()) { + show(tagIV, false); + } + HBox.setHgrow(spacer, Priority.ALWAYS); + header = new HBox(getDescrLabel(), getCountLabel(), hashIV, tagIV, /*spacer,*/ minusButton, plusButton); + + header.setMinWidth(USE_PREF_SIZE); + header.setPadding(new Insets(2, 5, 2, 5)); + header.setAlignment(Pos.CENTER_LEFT); + //setup description label + evtColor = getEventType().getColor(); + eventTypeImageView.setImage(getEventType().getFXImage()); + descrLabel.setGraphic(eventTypeImageView); + descrLabel.setPrefWidth(USE_COMPUTED_SIZE); + descrLabel.setTextOverrun(OverrunStyle.CENTER_ELLIPSIS); + descrLabel.setMouseTransparent(true); + + //set up subnode pane sizing contraints + subNodePane.setPrefHeight(USE_COMPUTED_SIZE); + subNodePane.setMinHeight(USE_PREF_SIZE); + subNodePane.setMinWidth(USE_PREF_SIZE); + subNodePane.setMaxHeight(USE_PREF_SIZE); + subNodePane.setMaxWidth(USE_PREF_SIZE); + subNodePane.setPickOnBounds(false); + + setAlignment(Pos.TOP_LEFT); + setMinHeight(24); + + setPrefHeight(USE_COMPUTED_SIZE); + setMaxHeight(USE_PREF_SIZE); + setOnMouseClicked(new EventMouseHandler()); + + //set up mouse hover effect and tooltip + setOnMouseEntered((MouseEvent e) -> { + //defer tooltip creation till needed, this had a surprisingly large impact on speed of loading the chart + installTooltip(); + showDescriptionLoDControls(true); + toFront(); + }); + + setOnMouseExited((MouseEvent e) -> { + showDescriptionLoDControls(false); + }); + setCursor(Cursor.HAND); + + plusButton.disableProperty().bind(descLOD.isEqualTo(DescriptionLOD.FULL)); + minusButton.disableProperty().bind(descLOD.isEqualTo(getEventBundle().getDescriptionLOD())); + + plusButton.setOnMouseClicked(e -> { + final DescriptionLOD next = descLOD.get().next(); + if (next != null) { + loadSubClusters(next); + descLOD.set(next); + } + }); + minusButton.setOnMouseClicked(e -> { + final DescriptionLOD previous = descLOD.get().previous(); + if (previous != null) { + loadSubClusters(previous); + descLOD.set(previous); + } + }); + + setBackground(new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY))); + + setLayoutX(getChart().getXAxis().getDisplayPosition(new DateTime(eventBundle.getStartMillis())) - getLayoutXCompensation()); + } + + /** + * @param w the maximum width the description label should have + */ + @Override + public void setDescriptionWidth(double w) { + getDescrLabel().setMaxWidth(w); + } + + abstract void installTooltip(); + + /** + * apply the 'effect' to visually indicate highlighted nodes + * + * @param applied true to apply the highlight 'effect', false to remove it + */ + @Override + public synchronized void applyHighlightEffect(boolean applied) { + if (applied) { + getDescrLabel().setStyle("-fx-font-weight: bold;"); // NON-NLS + getSpanFillNode().setBackground(new Background(new BackgroundFill(getEventType().getColor().deriveColor(0, 1, 1, .3), CORNER_RADII, Insets.EMPTY))); + setBackground(new Background(new BackgroundFill(getEventType().getColor().deriveColor(0, 1, 1, .2), CORNER_RADII, Insets.EMPTY))); + } else { + getDescrLabel().setStyle("-fx-font-weight: normal;"); // NON-NLS + getSpanFillNode().setBackground(new Background(new BackgroundFill(getEventType().getColor().deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY))); + setBackground(new Background(new BackgroundFill(getEventType().getColor().deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY))); + } + } + + String getDisplayedDescription() { + return getDescrLabel().getText(); + } + + abstract Region getSpanFillNode(); + + Button getPlusButton() { + return plusButton; + } + + Button getMinusButton() { + return minusButton; + } + + public final Label getDescrLabel() { + return descrLabel; + } + + public final Label getCountLabel() { + return countLabel; + } + + public S getParentNode() { + return parentNode; + } + + @Override + public final T getEventBundle() { + return eventBundle; + } + + public final EventDetailChart getChart() { + return chart; + } + + public DescriptionLOD getDescLOD() { + return descLOD.get(); + } + private static final Logger LOGGER = Logger.getLogger(AbstractDetailViewNode.class.getName()); + + /** + * loads sub-clusters at the given Description LOD + * + * @param newDescriptionLOD + */ + final synchronized void loadSubClusters(DescriptionLOD newDescriptionLOD) { + subNodePane.getChildren().clear(); + if (newDescriptionLOD == getEventBundle().getDescriptionLOD()) { + showSpans(true); + getChart().setRequiresLayout(true); + getChart().requestChartLayout(); + } else { + showSpans(false); + RootFilter combinedFilter = getSubClusterFilter(); + + //make a new end inclusive span (to 'filter' with) + final Interval span = new Interval(getEventBundle().getStartMillis(), getEventBundle().getEndMillis() + 1000); + + //make a task to load the subnodes + LoggedTask> loggedTask = new LoggedTask>( + NbBundle.getMessage(this.getClass(), "AggregateEventNode.loggedTask.name"), true) { + + @Override + protected List call() throws Exception { + //query for the sub-clusters + List aggregatedEvents = eventsModel.getAggregatedEvents(new ZoomParams(span, + eventsModel.eventTypeZoomProperty().get(), + combinedFilter, + newDescriptionLOD)); + + return makeBundlesFromClusters(aggregatedEvents).stream() + .map(aggEvent -> { + return getNodeForCluser(aggEvent); + }).collect(Collectors.toList()); // return list of AggregateEventNodes representing subclusters + } + + @Override + protected void succeeded() { + try { + getChart().setCursor(Cursor.WAIT); + //assign subNodes and request chart layout + subNodePane.getChildren().setAll(get()); + setDescriptionVisibility(descrVis); + getChart().setRequiresLayout(true); + getChart().requestChartLayout(); + getChart().setCursor(null); + } catch (InterruptedException | ExecutionException ex) { + LOGGER.log(Level.SEVERE, "Error loading subnodes", ex); + } + } + }; + + //start task + getChart().getController().monitorTask(loggedTask); + } + } + + final double getLayoutXCompensation() { + return (getParentNode() != null ? getParentNode().getLayoutXCompensation() : 0) + + getBoundsInParent().getMinX(); + } + + @Override + final public void setDescriptionVisibility(DescriptionVisibility descrVis) { + this.descrVis = descrVis; + final int size = getEventBundle().getEventIDs().size(); + + switch (descrVis) { + case COUNT_ONLY: + descrLabel.setText(""); + countLabel.setText(String.valueOf(size)); + break; + case HIDDEN: + countLabel.setText(""); + descrLabel.setText(""); + break; + default: + case SHOWN: + String description = getEventBundle().getDescription(); + description = getParentNode() != null + ? " ..." + StringUtils.substringAfter(description, getParentNode().getDescription()) + : description; + descrLabel.setText(description); + countLabel.setText(((size == 1) ? "" : " (" + size + ")")); // NON-NLS + break; + } + } + + /** + * event handler used for mouse events on {@link AggregateEventNode}s + */ + private class EventMouseHandler implements EventHandler { + + @Override + public void handle(MouseEvent t) { + + if (t.getButton() == MouseButton.PRIMARY) { + t.consume(); + if (t.isShiftDown()) { + if (chart.selectedNodes.contains(AbstractDetailViewNode.this) == false) { + chart.selectedNodes.add(AbstractDetailViewNode.this); + } + } else if (t.isShortcutDown()) { + chart.selectedNodes.removeAll(AbstractDetailViewNode.this); + } else if (t.getClickCount() > 1) { + final DescriptionLOD next = descLOD.get().next(); + if (next != null) { + loadSubClusters(next); + descLOD.set(next); + } + } else { + chart.selectedNodes.setAll(AbstractDetailViewNode.this); + } + + } + } + } + + abstract S getNodeForCluser(T cluster); + +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java index efd3a47586..5ae2ab2e0b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java @@ -5,6 +5,7 @@ */ package org.sleuthkit.autopsy.timeline.ui.detailview; +import java.util.Comparator; import java.util.List; import java.util.Set; import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; @@ -15,24 +16,14 @@ import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; */ public interface DetailViewNode> { - public long getStartMillis(); - - public long getEndMillis(); - public void setDescriptionVisibility(DescriptionVisibility get); - public List getSubNodes(); + public List getSubNodes(); public void setSpanWidths(List spanWidths); public void setDescriptionWidth(double max); - public EventType getEventType(); - - public Set getEventIDs(); - - public String getDescription(); - public EventBundle getEventBundle(); /** @@ -43,4 +34,34 @@ public interface DetailViewNode> { void applyHighlightEffect(boolean applied); public void applySelectionEffect(boolean applied); + + default String getDescription() { + return getEventBundle().getDescription(); + } + + default EventType getEventType() { + return getEventBundle().getEventType(); + } + + default Set getEventIDs() { + return getEventBundle().getEventIDs(); + } + + default public long getStartMillis() { + return getEventBundle().getStartMillis(); + } + + default long getEndMillis() { + return getEventBundle().getEndMillis(); + } + + + + static class StartTimeComparator implements Comparator> { + + @Override + public int compare(DetailViewNode o1, DetailViewNode o2) { + return Long.compare(o1.getStartMillis(), o2.getStartMillis()); + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java index 5f1d32b083..f40948c20b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java @@ -70,7 +70,6 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.TimeLineController; -import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; @@ -101,7 +100,7 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo; * TODO: refactor common code out of this class and CountsChartPane into * {@link AbstractVisualization} */ -public class DetailViewPane extends AbstractVisualization { +public class DetailViewPane extends AbstractVisualization, EventDetailChart> { private final static Logger LOGGER = Logger.getLogger(CountsViewPane.class.getName()); @@ -358,9 +357,8 @@ public class DetailViewPane extends AbstractVisualization c1, Boolean selected) { chart.applySelectionEffect(c1, selected); - } private class DetailViewSettingsPane extends HBox { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java index 20baf039b1..b07adac60b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java @@ -18,120 +18,39 @@ */ package org.sleuthkit.autopsy.timeline.ui.detailview; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.stream.Collectors; -import javafx.application.Platform; import javafx.beans.property.SimpleObjectProperty; -import javafx.event.EventHandler; -import javafx.geometry.Insets; import javafx.geometry.Pos; -import javafx.scene.Cursor; -import javafx.scene.control.Button; import javafx.scene.control.ContextMenu; -import javafx.scene.control.Label; -import javafx.scene.control.OverrunStyle; import javafx.scene.control.Tooltip; -import javafx.scene.effect.DropShadow; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; -import javafx.scene.input.MouseButton; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.Background; -import javafx.scene.layout.BackgroundFill; -import javafx.scene.layout.Border; import javafx.scene.layout.BorderPane; -import javafx.scene.layout.BorderStroke; -import javafx.scene.layout.BorderStrokeStyle; -import javafx.scene.layout.BorderWidths; -import javafx.scene.layout.CornerRadii; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Pane; -import javafx.scene.layout.Priority; import javafx.scene.layout.Region; -import javafx.scene.layout.StackPane; -import javafx.scene.paint.Color; -import org.apache.commons.lang3.StringUtils; -import org.joda.time.DateTime; -import org.joda.time.Interval; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.ColorUtilities; -import org.sleuthkit.autopsy.coreutils.LoggedTask; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.timeline.TimeLineController; -import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; -import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent; -import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; -import org.sleuthkit.autopsy.timeline.filters.RootFilter; -import org.sleuthkit.autopsy.timeline.filters.TextFilter; -import org.sleuthkit.autopsy.timeline.filters.TypeFilter; -import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; -import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; -import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** * Represents an {@link EventCluster} in a {@link EventDetailChart}. */ -public class EventClusterNode extends StackPane implements DetailViewNode { +public class EventClusterNode extends AbstractDetailViewNode { private static final Logger LOGGER = Logger.getLogger(EventClusterNode.class.getName()); - private static final Image HASH_PIN = new Image("/org/sleuthkit/autopsy/images/hashset_hits.png"); - private final static Image PLUS = new Image("/org/sleuthkit/autopsy/timeline/images/plus-button.png"); // NON-NLS - private final static Image MINUS = new Image("/org/sleuthkit/autopsy/timeline/images/minus-button.png"); // NON-NLS - private final static Image TAG = new Image("/org/sleuthkit/autopsy/images/green-tag-icon-16.png"); // NON-NLS - - private static final CornerRadii CORNER_RADII = new CornerRadii(3); - - /** - * the border to apply when this node is 'selected' - */ - private static final Border selectionBorder = new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CORNER_RADII, new BorderWidths(2))); - - /** - * The event this AggregateEventNode represents visually - */ - private EventCluster eventCluster; - - private final EventClusterNode parentEventNode; - /** * the region that represents the time span of this node's event */ private final Region spanRegion = new Region(); - /** - * The label used to display this node's event's description - */ - private final Label descrLabel = new Label(); - - /** - * The label used to display this node's event count - */ - private final Label countLabel = new Label(); - - /** - * The IamgeView used to show the icon for this node's event's type - */ - private final ImageView eventTypeImageView = new ImageView(); - - /** - * Pane that contains AggregateEventNodes of any 'subevents' if they are - * displayed - * - * //TODO: move more of the control of subnodes/events here and out of - * EventDetail Chart - */ - private final Pane subNodePane = new Pane(); - /** * the context menu that with the slider that controls subnode/event display * @@ -140,153 +59,34 @@ public class EventClusterNode extends StackPane implements DetailViewNode contextMenu = new SimpleObjectProperty<>(); - /** - * the Background used to fill the spanRegion, this varies epending on the - * selected/highlighted state of this node in its parent EventDetailChart - */ - private Background spanFill; - - private final Button plusButton = new Button(null, new ImageView(PLUS)) { - { - configureLODButton(this); - } - }; - private final Button minusButton = new Button(null, new ImageView(MINUS)) { - { - configureLODButton(this); - } - }; - - private static void configureLODButton(Button b) { - b.setMinSize(16, 16); - b.setMaxSize(16, 16); - b.setPrefSize(16, 16); - b.setVisible(false); - b.setManaged(false); - } - - private final EventDetailChart chart; - - private SimpleObjectProperty descLOD = new SimpleObjectProperty<>(); - private DescriptionVisibility descrVis; - private final SleuthkitCase sleuthkitCase; - private final FilteredEventsModel eventsModel; - private Tooltip tooltip; - private final ImageView hashIV = new ImageView(HASH_PIN); - private final ImageView tagIV = new ImageView(TAG); - public EventClusterNode(final EventCluster aggEvent, EventClusterNode parentEventNode, EventDetailChart chart) { - this.eventCluster = aggEvent; - descLOD.set(aggEvent.getDescriptionLOD()); - this.parentEventNode = parentEventNode; - this.chart = chart; - sleuthkitCase = chart.getController().getAutopsyCase().getSleuthkitCase(); - eventsModel = chart.getController().getEventsModel(); + public EventClusterNode(final EventCluster eventCluster, EventClusterNode parentEventNode, EventDetailChart chart) { + super(chart, eventCluster, parentEventNode); + minWidthProperty().bind(spanRegion.widthProperty()); + header.setPrefWidth(USE_COMPUTED_SIZE); - final Region region = new Region(); - HBox.setHgrow(region, Priority.ALWAYS); - - final HBox hBox = new HBox(descrLabel, countLabel, region, hashIV, tagIV, minusButton, plusButton); - if (aggEvent.getEventIDsWithHashHits().isEmpty()) { - hashIV.setManaged(false); - hashIV.setVisible(false); - } - if (aggEvent.getEventIDsWithTags().isEmpty()) { - tagIV.setManaged(false); - tagIV.setVisible(false); - } - hBox.setPrefWidth(USE_COMPUTED_SIZE); - hBox.setMinWidth(USE_PREF_SIZE); - hBox.setPadding(new Insets(2, 5, 2, 5)); - hBox.setAlignment(Pos.CENTER_LEFT); - - final BorderPane borderPane = new BorderPane(subNodePane, hBox, null, null, null); + final BorderPane borderPane = new BorderPane(subNodePane, header, null, null, null); BorderPane.setAlignment(subNodePane, Pos.TOP_LEFT); borderPane.setPrefWidth(USE_COMPUTED_SIZE); getChildren().addAll(spanRegion, borderPane); - setAlignment(Pos.TOP_LEFT); - setMinHeight(24); - minWidthProperty().bind(spanRegion.widthProperty()); - setPrefHeight(USE_COMPUTED_SIZE); - setMaxHeight(USE_PREF_SIZE); - - //set up subnode pane sizing contraints - subNodePane.setPrefHeight(USE_COMPUTED_SIZE); - subNodePane.setMinHeight(USE_PREF_SIZE); - subNodePane.setMinWidth(USE_PREF_SIZE); - subNodePane.setMaxHeight(USE_PREF_SIZE); - subNodePane.setMaxWidth(USE_PREF_SIZE); - subNodePane.setPickOnBounds(false); - - //setup description label - eventTypeImageView.setImage(aggEvent.getEventType().getFXImage()); - descrLabel.setGraphic(eventTypeImageView); - descrLabel.setPrefWidth(USE_COMPUTED_SIZE); - descrLabel.setTextOverrun(OverrunStyle.CENTER_ELLIPSIS); - - descrLabel.setMouseTransparent(true); - setDescriptionVisibility(chart.getDescrVisibility().get()); - //setup backgrounds - final Color evtColor = aggEvent.getEventType().getColor(); - spanFill = new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY)); - setBackground(spanFill); - setCursor(Cursor.HAND); spanRegion.setStyle("-fx-border-width:2 0 2 2; -fx-border-radius: 2; -fx-border-color: " + ColorUtilities.getRGBCode(evtColor) + ";"); // NON-NLS - spanRegion.setBackground(spanFill); + spanRegion.setBackground(getBackground()); - //set up mouse hover effect and tooltip - setOnMouseEntered((MouseEvent e) -> { - //defer tooltip creation till needed, this had a surprisingly large impact on speed of loading the chart - installTooltip(); - spanRegion.setEffect(new DropShadow(10, evtColor)); - minusButton.setVisible(true); - plusButton.setVisible(true); - minusButton.setManaged(true); - plusButton.setManaged(true); - toFront(); - }); - - setOnMouseExited((MouseEvent e) -> { - spanRegion.setEffect(null); - minusButton.setVisible(false); - plusButton.setVisible(false); - minusButton.setManaged(false); - plusButton.setManaged(false); - }); - - setOnMouseClicked(new EventMouseHandler()); - - plusButton.disableProperty().bind(descLOD.isEqualTo(DescriptionLOD.FULL)); - minusButton.disableProperty().bind(descLOD.isEqualTo(aggEvent.getDescriptionLOD())); - - plusButton.setOnMouseClicked(e -> { - final DescriptionLOD next = descLOD.get().next(); - if (next != null) { - loadSubClusters(next); - descLOD.set(next); - } - }); - minusButton.setOnMouseClicked(e -> { - final DescriptionLOD previous = descLOD.get().previous(); - if (previous != null) { - loadSubClusters(previous); - descLOD.set(previous); - } - }); } - synchronized private void installTooltip() { + @Override + synchronized void installTooltip() { //TODO: all this work should probably go on a background thread... if (tooltip == null) { HashMap hashSetCounts = new HashMap<>(); - if (!eventCluster.getEventIDsWithHashHits().isEmpty()) { + if (!getEventCluster().getEventIDsWithHashHits().isEmpty()) { hashSetCounts = new HashMap<>(); try { - for (TimeLineEvent tle : eventsModel.getEventsById(eventCluster.getEventIDsWithHashHits())) { + for (TimeLineEvent tle : eventsModel.getEventsById(getEventCluster().getEventIDsWithHashHits())) { Set hashSetNames = sleuthkitCase.getAbstractFileById(tle.getFileID()).getHashSetNames(); for (String hashSetName : hashSetNames) { hashSetCounts.merge(hashSetName, 1L, Long::sum); @@ -298,8 +98,8 @@ public class EventClusterNode extends StackPane implements DetailViewNode tagCounts = new HashMap<>(); - if (!eventCluster.getEventIDsWithTags().isEmpty()) { - tagCounts.putAll(eventsModel.getTagCountsByTagName(eventCluster.getEventIDsWithTags())); + if (!getEventCluster().getEventIDsWithTags().isEmpty()) { + tagCounts.putAll(eventsModel.getTagCountsByTagName(getEventCluster().getEventIDsWithTags())); } @@ -312,9 +112,9 @@ public class EventClusterNode extends StackPane implements DetailViewNode getSubNodes() { - return subNodePane.getChildrenUnmodifiable().stream() - .map(EventClusterNode.class::cast) - .collect(Collectors.toList()); - } - - synchronized public EventCluster getEvent() { - return eventCluster; + synchronized public EventCluster getEventCluster() { + return getEventBundle(); } /** @@ -351,93 +144,9 @@ public class EventClusterNode extends StackPane implements DetailViewNode { - if (applied) { - setBorder(selectionBorder); - } else { - setBorder(null); - } - }); - } - - @Override - public String getDescription() { - return eventCluster.getDescription(); - } - - /** - * apply the 'effect' to visually indicate highlighted nodes - * - * @param applied true to apply the highlight 'effect', false to remove it - */ - public synchronized void applyHighlightEffect(boolean applied) { - - if (applied) { - descrLabel.setStyle("-fx-font-weight: bold;"); // NON-NLS - spanFill = new Background(new BackgroundFill(eventCluster.getEventType().getColor().deriveColor(0, 1, 1, .3), CORNER_RADII, Insets.EMPTY)); - spanRegion.setBackground(spanFill); - setBackground(new Background(new BackgroundFill(eventCluster.getEventType().getColor().deriveColor(0, 1, 1, .2), CORNER_RADII, Insets.EMPTY))); - } else { - descrLabel.setStyle("-fx-font-weight: normal;"); // NON-NLS - spanFill = new Background(new BackgroundFill(eventCluster.getEventType().getColor().deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY)); - spanRegion.setBackground(spanFill); - setBackground(new Background(new BackgroundFill(eventCluster.getEventType().getColor().deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY))); - } - } - - String getDisplayedDescription() { - return descrLabel.getText(); - } - - double getLayoutXCompensation() { - return (parentEventNode != null ? parentEventNode.getLayoutXCompensation() : 0) - + getBoundsInParent().getMinX(); + Region getSpanFillNode() { + return spanRegion; } /** @@ -454,115 +163,19 @@ public class EventClusterNode extends StackPane implements DetailViewNode> loggedTask = new LoggedTask>( - NbBundle.getMessage(this.getClass(), "AggregateEventNode.loggedTask.name"), true) { - - @Override - protected List call() throws Exception { - //query for the sub-clusters - List aggregatedEvents = eventsModel.getAggregatedEvents(new ZoomParams(span, - eventsModel.eventTypeZoomProperty().get(), - combinedFilter, - newDescriptionLOD)); - //for each sub cluster make an AggregateEventNode to visually represent it, and set x-position - return aggregatedEvents.stream().map(aggEvent -> { - EventClusterNode subNode = new EventClusterNode(aggEvent, EventClusterNode.this, chart); - subNode.setLayoutX(chart.getXAxis().getDisplayPosition(new DateTime(aggEvent.getSpan().getStartMillis())) - getLayoutXCompensation()); - return subNode; - }).collect(Collectors.toList()); // return list of AggregateEventNodes representing subclusters - } - - @Override - protected void succeeded() { - try { - chart.setCursor(Cursor.WAIT); - //assign subNodes and request chart layout - subNodePane.getChildren().setAll(get()); - setDescriptionVisibility(descrVis); - chart.setRequiresLayout(true); - chart.requestChartLayout(); - chart.setCursor(null); - } catch (InterruptedException | ExecutionException ex) { - LOGGER.log(Level.SEVERE, "Error loading subnodes", ex); - } - } - }; - - //start task - chart.getController().monitorTask(loggedTask); - } - } - - /** - * event handler used for mouse events on {@link EventClusterNode}s - */ - private class EventMouseHandler implements EventHandler { - - @Override - public void handle(MouseEvent t) { - if (t.getButton() == MouseButton.PRIMARY) { - t.consume(); - if (t.isShiftDown()) { - if (chart.selectedBundles.contains(eventCluster) == false) { - chart.selectedBundles.add(eventCluster); - } - } else if (t.isShortcutDown()) { - chart.selectedBundles.removeAll(eventCluster); - } else if (t.getClickCount() > 1) { - final DescriptionLOD next = descLOD.get().next(); - if (next != null) { - loadSubClusters(next); - descLOD.set(next); - } - } else { - chart.selectedBundles.setAll(eventCluster); - } - } - } + @Override + void showSpans(boolean showSpans) { + //no-op for now } @Override - public long getStartMillis() { - return getEvent().getStartMillis(); + Collection makeBundlesFromClusters(List eventClusters) { + return eventClusters; } @Override - public long getEndMillis() { - return getEvent().getStartMillis(); + EventClusterNode getNodeForCluser(EventCluster cluster) { + return new EventClusterNode(cluster, this, getChart()); } - @Override - public EventType getEventType() { - return getEvent().getEventType(); - } - - @Override - public Set getEventIDs() { - return getEvent().getEventIDs(); - } - - @Override - public EventBundle getEventBundle() { - return eventCluster; - } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java index 17b38e8e71..5d9bd03eb4 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java @@ -60,7 +60,6 @@ import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; -import javafx.scene.layout.Region; import javafx.scene.shape.Line; import javafx.scene.shape.StrokeLineCap; import javafx.util.Duration; @@ -75,7 +74,6 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.actions.Back; import org.sleuthkit.autopsy.timeline.actions.Forward; -import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.EventStripe; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; @@ -175,7 +173,7 @@ public final class EventDetailChart extends XYChart impl @GuardedBy(value = "this") private boolean requiresLayout = true; - final ObservableList selectedBundles; + final ObservableList> selectedNodes; /** * list of series of data added to this chart TODO: replace this with a map @@ -205,7 +203,7 @@ public final class EventDetailChart extends XYChart impl private final SimpleDoubleProperty truncateWidth = new SimpleDoubleProperty(200.0); private final SimpleBooleanProperty alternateLayout = new SimpleBooleanProperty(true); - EventDetailChart(DateAxis dateAxis, final Axis verticalAxis, ObservableList selectedNodes) { + EventDetailChart(DateAxis dateAxis, final Axis verticalAxis, ObservableList> selectedNodes) { super(dateAxis, verticalAxis); dateAxis.setAutoRanging(false); @@ -284,38 +282,28 @@ public final class EventDetailChart extends XYChart impl setOnMouseReleased(dragHandler); setOnMouseDragged(dragHandler); -// projectionMap.addListener((MapChangeListener.Change, ? extends Line> change) -> { -// final Line valueRemoved = change.getValueRemoved(); -// if (valueRemoved != null) { -// getChartChildren().removeAll(valueRemoved); -// } -// final Line valueAdded = change.getValueAdded(); -// if (valueAdded != null) { -// getChartChildren().add(valueAdded); -// } -// }); - this.selectedBundles = selectedNodes; - this.selectedBundles.addListener(( - ListChangeListener.Change c) -> { + this.selectedNodes = selectedNodes; + this.selectedNodes.addListener(( + ListChangeListener.Change> c) -> { while (c.next()) { - c.getRemoved().forEach((EventBundle t) -> { - t.getRanges().forEach((Range t1) -> { - Line removeAll = projectionMap.remove(t1); - getChartChildren().removeAll(removeAll); + c.getRemoved().forEach((DetailViewNode t) -> { + t.getEventBundle().getRanges().forEach((Range t1) -> { + Line removedLine = projectionMap.remove(t1); + getChartChildren().removeAll(removedLine); }); }); - c.getAddedSubList().forEach((EventBundle t) -> { + c.getAddedSubList().forEach((DetailViewNode t) -> { - for (Range r : t.getRanges()) { + for (Range range : t.getEventBundle().getRanges()) { - Line line = new Line(dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(r.lowerEndpoint(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET, - dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(r.upperEndpoint(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET + Line line = new Line(dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(range.lowerEndpoint(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET, + dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(range.upperEndpoint(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET ); line.setStroke(t.getEventType().getColor().deriveColor(0, 1, 1, .5)); line.setStrokeWidth(PROJECTED_LINE_STROKE_WIDTH); line.setStrokeLineCap(StrokeLineCap.ROUND); - projectionMap.put(r, line); + projectionMap.put(range, line); getChartChildren().add(line); } }); @@ -353,7 +341,7 @@ public final class EventDetailChart extends XYChart impl clearGuideLine(); clearIntervalSelector(); - selectedBundles.clear(); + selectedNodes.clear(); projectionMap.clear(); controller.selectEventIDs(Collections.emptyList()); }); @@ -600,38 +588,38 @@ public final class EventDetailChart extends XYChart impl * @param nodes * @param minY */ - private synchronized > double layoutNodes(final Collection nodes, final double minY, final double xOffset) { + private synchronized double layoutNodes(final Collection> nodes, final double minY, final double xOffset) { //hash map from y value to right most occupied x value. This tells you for a given 'row' what is the first avaialable slot Map maxXatY = new HashMap<>(); double localMax = minY; //for each node lay size it and position it in first available slot - for (DVRegion n : nodes) { - n.setDescriptionVisibility(descrVisibility.get()); - double rawDisplayPosition = getXAxis().getDisplayPosition(new DateTime(n.getStartMillis())); + for (AbstractDetailViewNode node : nodes) { + node.setDescriptionVisibility(descrVisibility.get()); + double rawDisplayPosition = getXAxis().getDisplayPosition(new DateTime(node.getStartMillis())); //position of start and end according to range of axis double startX = rawDisplayPosition - xOffset; double layoutNodesResultHeight = 0; double span = 0; - List subNodes = n.getSubNodes(); + List> subNodes = node.getSubNodes(); if (subNodes.isEmpty() == false) { - subNodes.sort(Comparator.comparing((DVRegion t) -> t.getStartMillis())); + subNodes.sort(new DetailViewNode.StartTimeComparator()); layoutNodesResultHeight = layoutNodes(subNodes, 0, rawDisplayPosition); } - if (n instanceof EventClusterNode) { - double endX = getXAxis().getDisplayPosition(new DateTime(n.getEndMillis())) - xOffset; + if (alternateLayout.get() == false) { + double endX = getXAxis().getDisplayPosition(new DateTime(node.getEndMillis())) - xOffset; span = endX - startX; //size timespan border - n.setSpanWidths(Arrays.asList(span)); + node.setSpanWidths(Arrays.asList(span)); } else { - EventStripeNode cn = (EventStripeNode) n; + EventStripeNode stripeNode = (EventStripeNode) node; List spanWidths = new ArrayList<>(); - double x = getXAxis().getDisplayPosition(new DateTime(cn.getStartMillis()));; + double x = getXAxis().getDisplayPosition(new DateTime(stripeNode.getStartMillis()));; double x2; - Iterator> ranges = cn.getStripe().getRanges().iterator(); + Iterator> ranges = stripeNode.getStripe().getRanges().iterator(); Range range = ranges.next(); do { x2 = getXAxis().getDisplayPosition(new DateTime(range.upperEndpoint())); @@ -644,25 +632,31 @@ public final class EventDetailChart extends XYChart impl double gapSpan = x - x2; span += gapSpan; spanWidths.add(gapSpan); + if (ranges.hasNext() == false) { + x2 = getXAxis().getDisplayPosition(new DateTime(range.upperEndpoint())); + clusterSpan = x2 - x; + span += clusterSpan; + spanWidths.add(clusterSpan); + } } } while (ranges.hasNext()); - cn.setSpanWidths(spanWidths); + stripeNode.setSpanWidths(spanWidths); } if (truncateAll.get()) { //if truncate option is selected limit width of description label - n.setDescriptionWidth(Math.max(span, truncateWidth.get())); + node.setDescriptionWidth(Math.max(span, truncateWidth.get())); } else { //else set it unbounded - n.setDescriptionWidth(USE_PREF_SIZE);//20 + new Text(tlNode.getDisplayedDescription()).getLayoutBounds().getWidth()); + node.setDescriptionWidth(USE_PREF_SIZE);//20 + new Text(tlNode.getDisplayedDescription()).getLayoutBounds().getWidth()); } - n.autosize(); //compute size of tlNode based on constraints and event data + node.autosize(); //compute size of tlNode based on constraints and event data //get position of right edge of node ( influenced by description label) - double xRight = startX + n.getWidth(); + double xRight = startX + node.getWidth(); //get the height of the node - final double h = layoutNodesResultHeight == 0 ? n.getHeight() : layoutNodesResultHeight + DEFAULT_ROW_HEIGHT; + final double h = layoutNodesResultHeight == 0 ? node.getHeight() : layoutNodesResultHeight + DEFAULT_ROW_HEIGHT; //initial test position double yPos = minY; @@ -701,8 +695,8 @@ public final class EventDetailChart extends XYChart impl localMax = Math.max(yPos2, localMax); Timeline tm = new Timeline(new KeyFrame(Duration.seconds(1.0), - new KeyValue(n.layoutXProperty(), startX), - new KeyValue(n.layoutYProperty(), yPos))); + new KeyValue(node.layoutXProperty(), startX), + new KeyValue(node.layoutYProperty(), yPos))); tm.play(); } @@ -753,15 +747,6 @@ public final class EventDetailChart extends XYChart impl return alternateLayout; } - private static class StartTimeComparator> implements Comparator { - - @Override - public int compare(T n1, T n2) { - return Long.compare(n1.getStartMillis(), n2.getStartMillis() - ); - } - } - private class DetailIntervalSelector extends IntervalSelector { public DetailIntervalSelector(double x, double height, Axis axis, TimeLineController controller) { @@ -797,12 +782,7 @@ public final class EventDetailChart extends XYChart impl super.requestChartLayout(); } - void applySelectionEffect(EventBundle c1, Boolean selected) { - if (alternateLayout.get()) { - EventStripe eventStripe = stripeDescMap.get(ImmutablePair.of(c1.getEventType(), c1.getDescription())); - stripeNodeMap.get(eventStripe).applySelectionEffect(selected); - } else { - clusterNodeMap.get(c1).applySelectionEffect(selected); - } + void applySelectionEffect(DetailViewNode c1, Boolean selected) { + c1.applySelectionEffect(selected); } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java index 4e03ff342d..ac79c021a3 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java @@ -6,235 +6,64 @@ package org.sleuthkit.autopsy.timeline.ui.detailview; import com.google.common.collect.Range; -import java.util.HashMap; +import java.util.Collection; import java.util.List; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.logging.Level; import java.util.stream.Collectors; -import javafx.application.Platform; -import javafx.beans.property.SimpleObjectProperty; -import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.geometry.Pos; -import javafx.scene.Cursor; -import javafx.scene.Node; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.control.OverrunStyle; -import javafx.scene.effect.DropShadow; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; -import javafx.scene.input.MouseButton; -import javafx.scene.input.MouseEvent; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; -import javafx.scene.layout.Border; -import javafx.scene.layout.BorderStroke; -import javafx.scene.layout.BorderStrokeStyle; -import javafx.scene.layout.BorderWidths; -import javafx.scene.layout.CornerRadii; import javafx.scene.layout.HBox; -import javafx.scene.layout.Pane; -import javafx.scene.layout.Priority; import javafx.scene.layout.Region; -import static javafx.scene.layout.Region.USE_COMPUTED_SIZE; import static javafx.scene.layout.Region.USE_PREF_SIZE; -import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; -import javafx.scene.paint.Color; -import org.apache.commons.lang3.StringUtils; -import org.joda.time.DateTime; -import org.joda.time.Interval; -import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.ColorUtilities; -import org.sleuthkit.autopsy.coreutils.LoggedTask; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.EventStripe; -import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; -import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; -import org.sleuthkit.autopsy.timeline.filters.RootFilter; -import org.sleuthkit.autopsy.timeline.filters.TextFilter; -import org.sleuthkit.autopsy.timeline.filters.TypeFilter; -import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; -import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; -import org.sleuthkit.datamodel.SleuthkitCase; +import static org.sleuthkit.autopsy.timeline.ui.detailview.AbstractDetailViewNode.show; /** * */ -public class EventStripeNode extends StackPane implements DetailViewNode { +public class EventStripeNode extends AbstractDetailViewNode { private static final Logger LOGGER = Logger.getLogger(EventClusterNode.class.getName()); - private static final Image HASH_PIN = new Image("/org/sleuthkit/autopsy/images/hashset_hits.png"); - private final static Image PLUS = new Image("/org/sleuthkit/autopsy/timeline/images/plus-button.png"); // NON-NLS - private final static Image MINUS = new Image("/org/sleuthkit/autopsy/timeline/images/minus-button.png"); // NON-NLS - private final static Image TAG = new Image("/org/sleuthkit/autopsy/images/green-tag-icon-16.png"); // NON-NLS - - private final Pane subNodePane = new Pane(); - private final EventStripe eventStripe; - private final EventStripeNode parentNode; - private final EventDetailChart chart; - private SimpleObjectProperty descLOD = new SimpleObjectProperty<>(); - private final SleuthkitCase sleuthkitCase; - private final FilteredEventsModel eventsModel; - /** - * The label used to display this node's event's description - */ - private final Label descrLabel = new Label(); - - /** - * The label used to display this node's event count - */ - private final Label countLabel = new Label(); - - private final ImageView hashIV = new ImageView(HASH_PIN); - private final ImageView tagIV = new ImageView(TAG); - private final Button plusButton = new Button(null, new ImageView(PLUS)) { - { - configureLODButton(this); - } - }; - private final Button minusButton = new Button(null, new ImageView(MINUS)) { - { - configureLODButton(this); - } - }; - - private static void configureLODButton(Button b) { - b.setMinSize(16, 16); - b.setMaxSize(16, 16); - b.setPrefSize(16, 16); - show(b, false); - } - - private static void show(Node b, boolean show) { - b.setVisible(show); - b.setManaged(show); - } - private DescriptionVisibility descrVis; - private final HBox spansHBox = new HBox(); - /** - * The IamgeView used to show the icon for this node's event's type - */ - private final ImageView eventTypeImageView = new ImageView(); - private Background spanFill; - private static final CornerRadii CORNER_RADII = new CornerRadii(3); + private final HBox rangesHBox = new HBox(); EventStripeNode(EventStripe eventStripe, EventStripeNode parentNode, EventDetailChart chart) { - this.chart = chart; - sleuthkitCase = chart.getController().getAutopsyCase().getSleuthkitCase(); - eventsModel = chart.getController().getEventsModel(); - - this.parentNode = parentNode; - this.eventStripe = eventStripe; - descLOD.set(eventStripe.getDescriptionLOD()); - - final Region spacer = new Region(); - HBox.setHgrow(spacer, Priority.ALWAYS); - - final HBox header = new HBox(descrLabel, countLabel, hashIV, tagIV, spacer, minusButton, plusButton); - if (eventStripe.getEventIDsWithHashHits().isEmpty()) { - show(hashIV, false); - } - if (eventStripe.getEventIDsWithTags().isEmpty()) { - show(tagIV, false); - } - header.setMinWidth(USE_PREF_SIZE); - header.setPadding(new Insets(2, 5, 2, 5)); - header.setAlignment(Pos.CENTER_LEFT); - + super(chart, eventStripe, parentNode); + minWidthProperty().bind(rangesHBox.widthProperty()); final VBox internalVBox = new VBox(header, subNodePane); internalVBox.setAlignment(Pos.CENTER_LEFT); - final Color evtColor = eventStripe.getEventType().getColor(); - spanFill = new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .2), CORNER_RADII, Insets.EMPTY)); - for (Range r : eventStripe.getRanges()) { - Region spanRegion = new Region(); - spanRegion.setStyle("-fx-border-width:2 1 2 1; -fx-border-radius: 1; -fx-border-color: " + ColorUtilities.getRGBCode(evtColor.deriveColor(0, 1, 1, .3)) + ";"); // NON-NLS - spanRegion.setBackground(spanFill); - spansHBox.getChildren().addAll(spanRegion, new Region()); + for (Range range : eventStripe.getRanges()) { + Region rangeRegion = new Region(); + rangeRegion.setStyle("-fx-border-width:2 1 2 1; -fx-border-radius: 1; -fx-border-color: " + ColorUtilities.getRGBCode(evtColor.deriveColor(0, 1, 1, .3)) + ";"); // NON-NLS + rangeRegion.setBackground(new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .2), CORNER_RADII, Insets.EMPTY))); + rangesHBox.getChildren().addAll(rangeRegion, new Region()); } - spansHBox.getChildren().remove(spansHBox.getChildren().size() - 1); - spansHBox.setMaxWidth(USE_PREF_SIZE); + rangesHBox.getChildren().remove(rangesHBox.getChildren().size() - 1); + rangesHBox.setMaxWidth(USE_PREF_SIZE); setMaxWidth(USE_PREF_SIZE); - getChildren().addAll(spansHBox, internalVBox); - setBackground(new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY))); - setAlignment(Pos.TOP_LEFT); - setMinHeight(24); - minWidthProperty().bind(spansHBox.widthProperty()); - setPrefHeight(USE_COMPUTED_SIZE); - setMaxHeight(USE_PREF_SIZE); - - //set up subnode pane sizing contraints - subNodePane.setPrefHeight(USE_COMPUTED_SIZE); - subNodePane.setMinHeight(USE_PREF_SIZE); - subNodePane.setMinWidth(USE_PREF_SIZE); - subNodePane.setMaxHeight(USE_PREF_SIZE); - subNodePane.setMaxWidth(USE_PREF_SIZE); - subNodePane.setPickOnBounds(false); - - //setup description label - eventTypeImageView.setImage(eventStripe.getEventType().getFXImage()); - descrLabel.setGraphic(eventTypeImageView); - descrLabel.setPrefWidth(USE_COMPUTED_SIZE); - descrLabel.setTextOverrun(OverrunStyle.CENTER_ELLIPSIS); - - descrLabel.setMouseTransparent(true); - setDescriptionVisibility(chart.getDescrVisibility().get()); - setOnMouseClicked(new EventMouseHandler()); - - //set up mouse hover effect and tooltip - setOnMouseEntered((MouseEvent e) -> { - //defer tooltip creation till needed, this had a surprisingly large impact on speed of loading the chart -// installTooltip(); - spansHBox.setEffect(new DropShadow(10, evtColor)); - show(spacer, true); - show(minusButton, true); - show(plusButton, true); - - toFront(); - }); - - setOnMouseExited((MouseEvent e) -> { - spansHBox.setEffect(null); - show(spacer, false); - show(minusButton, false); - show(plusButton, false); - - }); - - plusButton.disableProperty().bind(descLOD.isEqualTo(DescriptionLOD.FULL)); - minusButton.disableProperty().bind(descLOD.isEqualTo(eventStripe.getDescriptionLOD())); - - plusButton.setOnMouseClicked(e -> { - final DescriptionLOD next = descLOD.get().next(); - if (next != null) { - loadSubClusters(next); - descLOD.set(next); - } - }); - minusButton.setOnMouseClicked(e -> { - final DescriptionLOD previous = descLOD.get().previous(); - if (previous != null) { - loadSubClusters(previous); - descLOD.set(previous); - } - }); + getChildren().addAll(rangesHBox, internalVBox); } + /** + * + * @param showControls the value of par + */ @Override - public long getStartMillis() { - return eventStripe.getStartMillis(); + void showDescriptionLoDControls(final boolean showControls) { + super.showDescriptionLoDControls(showControls); + show(spacer, showControls); } @Override public void setSpanWidths(List spanWidths) { for (int i = 0; i < spanWidths.size(); i++) { - Region spanRegion = (Region) spansHBox.getChildren().get(i); + Region spanRegion = (Region) rangesHBox.getChildren().get(i); Double w = spanWidths.get(i); spanRegion.setPrefWidth(w); spanRegion.setMaxWidth(w); @@ -242,211 +71,42 @@ public class EventStripeNode extends StackPane implements DetailViewNode getSubNodes() { - return subNodePane.getChildrenUnmodifiable().stream() - .map(EventStripeNode.class::cast) - .collect(Collectors.toList()); + Collection makeBundlesFromClusters(List eventClusters) { + return eventClusters.stream().collect( + Collectors.toMap( + EventCluster::getDescription, //key + EventStripe::new, //value + EventStripe::merge)//merge method + ).values(); } /** - * event handler used for mouse events on {@link AggregateEventNode}s - */ - private class EventMouseHandler implements EventHandler { - - @Override - public void handle(MouseEvent t) { - if (t.getButton() == MouseButton.PRIMARY) { - t.consume(); - if (t.isShiftDown()) { - if (chart.selectedBundles.contains(eventStripe) == false) { - chart.selectedBundles.add(eventStripe); - } - } else if (t.isShortcutDown()) { - chart.selectedBundles.removeAll(eventStripe); - } else if (t.getClickCount() > 1) { - final DescriptionLOD next = descLOD.get().next(); - if (next != null) { - loadSubClusters(next); - descLOD.set(next); - } - } else { - chart.selectedBundles.setAll(eventStripe); - } - } - } - } - - @Override - public EventType getEventType() { - return eventStripe.getEventType(); - } - - @Override - public Set getEventIDs() { - return eventStripe.getEventIDs(); - } - private static final Border selectionBorder = new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CORNER_RADII, new BorderWidths(2))); - - /** - * apply the 'effect' to visually indicate selection * - * @param applied true to apply the selection 'effect', false to remove it + * @param showSpans the value of showSpans */ @Override - public void applySelectionEffect(boolean applied) { - Platform.runLater(() -> { - if (applied) { - setBorder(selectionBorder); - } else { - setBorder(null); - } - }); - } - - /** - * apply the 'effect' to visually indicate highlighted nodes - * - * @param applied true to apply the highlight 'effect', false to remove it - */ - @Override - public synchronized void applyHighlightEffect(boolean applied) { - if (applied) { - descrLabel.setStyle("-fx-font-weight: bold;"); // NON-NLS - spanFill = new Background(new BackgroundFill(getEventType().getColor().deriveColor(0, 1, 1, .3), CORNER_RADII, Insets.EMPTY)); - spansHBox.setBackground(spanFill); - setBackground(new Background(new BackgroundFill(getEventType().getColor().deriveColor(0, 1, 1, .2), CORNER_RADII, Insets.EMPTY))); - } else { - descrLabel.setStyle("-fx-font-weight: normal;"); // NON-NLS - spanFill = new Background(new BackgroundFill(getEventType().getColor().deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY)); - spansHBox.setBackground(spanFill); - setBackground(new Background(new BackgroundFill(getEventType().getColor().deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY))); - } + void showSpans(final boolean showSpans) { + rangesHBox.setVisible(showSpans); } @Override - public String getDescription() { - return eventStripe.getDescription(); + void installTooltip() { +// throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override - public EventBundle getEventBundle() { - return getStripe(); + EventStripeNode getNodeForCluser(EventStripe cluster) { + return new EventStripeNode(cluster, this, getChart()); } - /** - * loads sub-clusters at the given Description LOD - * - * @param newDescriptionLOD - */ - synchronized private void loadSubClusters(DescriptionLOD newDescriptionLOD) { - subNodePane.getChildren().clear(); - - if (newDescriptionLOD == eventStripe.getDescriptionLOD()) { - spansHBox.setVisible(true); - chart.setRequiresLayout(true); - chart.requestChartLayout(); - } else { - spansHBox.setVisible(false); - RootFilter combinedFilter = eventsModel.filterProperty().get().copyOf(); - //make a new filter intersecting the global filter with text(description) and type filters to restrict sub-clusters - combinedFilter.getSubFilters().addAll(new TextFilter(eventStripe.getDescription()), - new TypeFilter(eventStripe.getEventType())); - - //make a new end inclusive span (to 'filter' with) - final Interval span = new Interval(eventStripe.getStartMillis(), eventStripe.getEndMillis() + 1000); - - //make a task to load the subnodes - LoggedTask> loggedTask = new LoggedTask>( - NbBundle.getMessage(this.getClass(), "AggregateEventNode.loggedTask.name"), true) { - - @Override - protected List call() throws Exception { - //query for the sub-clusters - List aggregatedEvents = eventsModel.getAggregatedEvents(new ZoomParams(span, - eventsModel.eventTypeZoomProperty().get(), - combinedFilter, - newDescriptionLOD)); - //for each sub cluster make an AggregateEventNode to visually represent it, and set x-position - HashMap stripeDescMap = new HashMap<>(); - for (EventCluster subCluster : aggregatedEvents) { - stripeDescMap.merge(subCluster.getDescription(), - new EventStripe(subCluster), - (EventStripe u, EventStripe v) -> { - return EventStripe.merge(u, v); - } - ); - } - - return stripeDescMap.values().stream().map(subStripe -> { - EventStripeNode subNode = new EventStripeNode(subStripe, EventStripeNode.this, chart); - subNode.setLayoutX(chart.getXAxis().getDisplayPosition(new DateTime(subStripe.getStartMillis())) - getLayoutXCompensation()); - return subNode; - }).collect(Collectors.toList()); // return list of AggregateEventNodes representing subclusters - } - - @Override - protected void succeeded() { - try { - chart.setCursor(Cursor.WAIT); - //assign subNodes and request chart layout - subNodePane.getChildren().setAll(get()); - setDescriptionVisibility(descrVis); - chart.setRequiresLayout(true); - chart.requestChartLayout(); - chart.setCursor(null); - } catch (InterruptedException | ExecutionException ex) { - LOGGER.log(Level.SEVERE, "Error loading subnodes", ex); - } - } - }; - - //start task - chart.getController().monitorTask(loggedTask); - } - } - - double getLayoutXCompensation() { - return (parentNode != null ? parentNode.getLayoutXCompensation() : 0) - + getBoundsInParent().getMinX(); - } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java index 898917ae15..9971291863 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java @@ -41,9 +41,9 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.TimeLineView; -import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; +import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewNode; import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane; /** @@ -91,8 +91,8 @@ public class NavPanel extends BorderPane implements TimeLineView { }); detailViewPane.getSelectedNodes().addListener((Observable observable) -> { eventsTree.getSelectionModel().clearSelection(); - detailViewPane.getSelectedNodes().forEach((EventBundle t) -> { - eventsTree.getSelectionModel().select(((NavTreeItem) eventsTree.getRoot()).findTreeItemForEvent(t)); + detailViewPane.getSelectedNodes().forEach((DetailViewNode t) -> { + eventsTree.getSelectionModel().select(((NavTreeItem) eventsTree.getRoot()).findTreeItemForEvent(t.getEventBundle())); }); }); @@ -131,7 +131,7 @@ public class NavPanel extends BorderPane implements TimeLineView { sortByBox.getItems().setAll(Arrays.asList(TreeComparator.Description, TreeComparator.Count)); sortByBox.getSelectionModel().select(TreeComparator.Description); sortByBox.getSelectionModel().selectedItemProperty().addListener((Observable o) -> { - ((RootItem) eventsTree.getRoot()).resort(sortByBox.getSelectionModel().getSelectedItem()); + ((NavTreeItem) eventsTree.getRoot()).resort(sortByBox.getSelectionModel().getSelectedItem()); }); eventsTree.setShowRoot(false); eventsTree.setCellFactory((TreeView p) -> new EventTreeCell()); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java index e87b10cc17..cfd582433e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java @@ -20,8 +20,8 @@ package org.sleuthkit.autopsy.timeline.ui.detailview.tree; import java.util.Comparator; import javafx.scene.control.TreeItem; -import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; +import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; /** * A node in the nav tree. Manages inserts and resorts. Has parents and From cedeed1a9eee5221c1a8ef92d824a858f0c6cbb3 Mon Sep 17 00:00:00 2001 From: jmillman Date: Tue, 15 Sep 2015 13:14:07 -0400 Subject: [PATCH 05/29] create some additional indexes; don't use group_concat uselessly --- .../autopsy/timeline/db/EventDB.java | 31 ++++++++++--------- .../autopsy/timeline/db/EventsRepository.java | 2 +- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java index 1beacbbf41..e939428dce 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java @@ -413,10 +413,7 @@ public class EventDB { try (ResultSet rs = getDataSourceIDsStmt.executeQuery()) { while (rs.next()) { long datasourceID = rs.getLong("datasource_id"); - //this relies on the fact that no tskObj has ID 0 but 0 is the default value for the datasource_id column in the events table. - if (datasourceID != 0) { - hashSet.add(datasourceID); - } + hashSet.add(datasourceID); } } catch (SQLException ex) { LOGGER.log(Level.SEVERE, "Failed to get MAX time.", ex); // NON-NLS @@ -583,6 +580,10 @@ public class EventDB { initializeTagsTable(); + createIndex("events", Arrays.asList("datasource_id")); + createIndex("events", Arrays.asList("event_id", "hash_hit")); + createIndex("events", Arrays.asList("event_id", "tagged")); + createIndex("events", Arrays.asList("file_id")); createIndex("events", Arrays.asList("file_id")); createIndex("events", Arrays.asList("artifact_id")); createIndex("events", Arrays.asList("time")); @@ -595,7 +596,7 @@ public class EventDB { "INSERT INTO events (datasource_id,file_id ,artifact_id, time, sub_type, base_type, full_description, med_description, short_description, known_state, hash_hit, tagged) " // NON-NLS + "VALUES (?,?,?,?,?,?,?,?,?,?,?,?)"); // NON-NLS getHashSetNamesStmt = prepareStatement("SELECT hash_set_id, hash_set_name FROM hash_sets"); // NON-NLS - getDataSourceIDsStmt = prepareStatement("SELECT DISTINCT datasource_id FROM events"); // NON-NLS + getDataSourceIDsStmt = prepareStatement("SELECT DISTINCT datasource_id FROM events WHERE datasource_id != 0"); // NON-NLS getMaxTimeStmt = prepareStatement("SELECT Max(time) AS max FROM events"); // NON-NLS getMinTimeStmt = prepareStatement("SELECT Min(time) AS min FROM events"); // NON-NLS getEventByIDStmt = prepareStatement("SELECT * FROM events WHERE event_id = ?"); // NON-NLS @@ -1041,7 +1042,7 @@ public class EventDB { * the supplied filter, aggregated according to the given event type * and description zoom levels */ - List getAggregatedEvents(ZoomParams params) { + List getClusteredEvents(ZoomParams params) { //unpack params Interval timeRange = params.getTimeRange(); RootFilter filter = params.getFilter(); @@ -1079,7 +1080,7 @@ public class EventDB { try (Statement createStatement = con.createStatement(); ResultSet rs = createStatement.executeQuery(query)) { while (rs.next()) { - events.add(aggregateEventHelper(rs, useSubTypes, descriptionLOD, filter.getTagsFilter())); + events.add(eventClusterHelper(rs, useSubTypes, descriptionLOD, filter.getTagsFilter())); } } catch (SQLException ex) { LOGGER.log(Level.SEVERE, "Failed to get aggregate events with query: " + query, ex); // NON-NLS @@ -1087,11 +1088,11 @@ public class EventDB { DBLock.unlock(); } - return mergeAggregateEvents(rangeInfo.getPeriodSize().getPeriod(), events); + return mergeEventClusters(rangeInfo.getPeriodSize().getPeriod(), events); } /** - * map a single row in a ResultSet to an AggregateEvent + * map a single row in a ResultSet to an EventCluster * * @param rs the result set whose current row should be mapped * @param useSubTypes use the sub_type column if true, else use the @@ -1103,7 +1104,7 @@ public class EventDB { * * @throws SQLException */ - private EventCluster aggregateEventHelper(ResultSet rs, boolean useSubTypes, DescriptionLOD descriptionLOD, TagsFilter filter) throws SQLException { + private EventCluster eventClusterHelper(ResultSet rs, boolean useSubTypes, DescriptionLOD descriptionLOD, TagsFilter filter) throws SQLException { Interval interval = new Interval(rs.getLong("min(time)") * 1000, rs.getLong("max(time)") * 1000, TimeLineController.getJodaTimeZone());// NON-NLS String eventIDsString = rs.getString("event_ids");// NON-NLS Set eventIDs = SQLHelper.unGroupConcat(eventIDsString, Long::valueOf); @@ -1111,20 +1112,20 @@ public class EventDB { EventType type = useSubTypes ? RootEventType.allTypes.get(rs.getInt("sub_type")) : BaseTypes.values()[rs.getInt("base_type")];// NON-NLS Set hashHits = new HashSet<>(); - String hashHitQuery = "SELECT group_concat(event_id) FROM events WHERE event_id IN (" + eventIDsString + ") AND hash_hit = 1";// NON-NLS + String hashHitQuery = "SELECT event_id FROM events WHERE event_id IN (" + eventIDsString + ") AND hash_hit = 1";// NON-NLS try (Statement stmt = con.createStatement(); ResultSet hashHitsRS = stmt.executeQuery(hashHitQuery)) { while (hashHitsRS.next()) { - hashHits = SQLHelper.unGroupConcat(hashHitsRS.getString("group_concat(event_id)"), Long::valueOf);// NON-NLS + hashHits.add(hashHitsRS.getLong("event_id")); } } Set tagged = new HashSet<>(); - String taggedQuery = "SELECT group_concat(event_id) FROM events WHERE event_id IN (" + eventIDsString + ") AND tagged = 1";// NON-NLS + String taggedQuery = "SELECT event_id FROM events WHERE event_id IN (" + eventIDsString + ") AND tagged = 1";// NON-NLS try (Statement stmt = con.createStatement(); ResultSet taggedRS = stmt.executeQuery(taggedQuery)) { while (taggedRS.next()) { - tagged = SQLHelper.unGroupConcat(taggedRS.getString("group_concat(event_id)"), Long::valueOf);// NON-NLS + tagged.add(taggedRS.getLong("event_id")); } } @@ -1145,7 +1146,7 @@ public class EventDB { * * @return */ - static private List mergeAggregateEvents(Period timeUnitLength, List preMergedEvents) { + static private List mergeEventClusters(Period timeUnitLength, List preMergedEvents) { //effectively map from type to (map from description to events) Map> typeMap = new HashMap<>(); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java index 1a9e61c2d1..bae051b66e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java @@ -149,7 +149,7 @@ public class EventsRepository { aggregateEventsCache = CacheBuilder.newBuilder() .maximumSize(1000L) .expireAfterAccess(10, TimeUnit.MINUTES - ).build(CacheLoader.from(eventDB::getAggregatedEvents)); + ).build(CacheLoader.from(eventDB::getClusteredEvents)); maxCache = CacheBuilder.newBuilder().build(CacheLoader.from(eventDB::getMaxTime)); minCache = CacheBuilder.newBuilder().build(CacheLoader.from(eventDB::getMinTime)); this.modelInstance = new FilteredEventsModel(this, currentStateProperty); From 22313fca8c375c6d52210c952d49dd21114f2391 Mon Sep 17 00:00:00 2001 From: jmillman Date: Tue, 15 Sep 2015 14:32:56 -0400 Subject: [PATCH 06/29] replace separate queries for events with tags and hash hits with additional group_concat columns --- .../autopsy/timeline/db/EventDB.java | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java index e939428dce..0aa63bfd27 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java @@ -1065,9 +1065,12 @@ public class EventDB { String timeZone = TimeLineController.getTimeZone().get().equals(TimeZone.getDefault()) ? ", 'localtime'" : ""; // NON-NLS String typeColumn = typeColumnHelper(useSubTypes); - //compose query string + //compose query string, /n only for nicer formatting if printing the entire query String query = "SELECT strftime('" + strfTimeFormat + "',time , 'unixepoch'" + timeZone + ") AS interval," // NON-NLS - + " group_concat(events.event_id) as event_ids, min(time), max(time), " + typeColumn + ", " + descriptionColumn // NON-NLS + + "\n group_concat(events.event_id) as event_ids," + + "\n group_concat(CASE WHEN hash_hit = 1 THEN event_id ELSE NULL END) as hash_hits," + + "\n group_concat(CASE WHEN tagged = 1 THEN event_id ELSE NULL END) as taggeds," + + "\n min(time), max(time), " + typeColumn + ", " + descriptionColumn // NON-NLS + "\n FROM events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) // NON-NLS + "\n WHERE time >= " + start + " AND time < " + end + " AND " + SQLHelper.getSQLWhere(filter) // NON-NLS + "\n GROUP BY interval, " + typeColumn + " , " + descriptionColumn // NON-NLS @@ -1075,6 +1078,7 @@ public class EventDB { // perform query and map results to AggregateEvent objects List events = new ArrayList<>(); + DBLock.lock(); try (Statement createStatement = con.createStatement(); @@ -1111,23 +1115,8 @@ public class EventDB { String description = rs.getString(SQLHelper.getDescriptionColumn(descriptionLOD)); EventType type = useSubTypes ? RootEventType.allTypes.get(rs.getInt("sub_type")) : BaseTypes.values()[rs.getInt("base_type")];// NON-NLS - Set hashHits = new HashSet<>(); - String hashHitQuery = "SELECT event_id FROM events WHERE event_id IN (" + eventIDsString + ") AND hash_hit = 1";// NON-NLS - try (Statement stmt = con.createStatement(); - ResultSet hashHitsRS = stmt.executeQuery(hashHitQuery)) { - while (hashHitsRS.next()) { - hashHits.add(hashHitsRS.getLong("event_id")); - } - } - - Set tagged = new HashSet<>(); - String taggedQuery = "SELECT event_id FROM events WHERE event_id IN (" + eventIDsString + ") AND tagged = 1";// NON-NLS - try (Statement stmt = con.createStatement(); - ResultSet taggedRS = stmt.executeQuery(taggedQuery)) { - while (taggedRS.next()) { - tagged.add(taggedRS.getLong("event_id")); - } - } + Set hashHits = SQLHelper.unGroupConcat(rs.getString("hash_hits"), Long::valueOf); + Set tagged = SQLHelper.unGroupConcat(rs.getString("taggeds"), Long::valueOf); return new EventCluster(interval, type, eventIDs, hashHits, tagged, description, descriptionLOD); From 634e12ef5ce7c383eb0d33ee8e4da492052b5362 Mon Sep 17 00:00:00 2001 From: jmillman Date: Tue, 15 Sep 2015 16:11:30 -0400 Subject: [PATCH 07/29] use ControlsFX Actions to add expand/collapse menu items to AbstractDetailViewNode --- .../ui/detailview/AbstractDetailViewNode.java | 226 +++++++++++------- .../ui/detailview/EventDetailChart.java | 76 +++--- .../timeline/ui/detailview/GuideLine.java | 5 +- 3 files changed, 175 insertions(+), 132 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java index 743e49b476..56a0eca60c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java @@ -14,14 +14,17 @@ import java.util.logging.Level; import java.util.stream.Collectors; import javafx.application.Platform; import javafx.beans.property.SimpleObjectProperty; +import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Cursor; import javafx.scene.Node; import javafx.scene.control.Button; +import javafx.scene.control.ContextMenu; import javafx.scene.control.Label; import javafx.scene.control.OverrunStyle; +import javafx.scene.control.SeparatorMenuItem; import javafx.scene.effect.DropShadow; import javafx.scene.image.Image; import javafx.scene.image.ImageView; @@ -43,6 +46,8 @@ import static javafx.scene.layout.Region.USE_PREF_SIZE; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import org.apache.commons.lang3.StringUtils; +import org.controlsfx.control.action.Action; +import org.controlsfx.control.action.ActionUtils; import org.joda.time.DateTime; import org.joda.time.Interval; import org.openide.util.NbBundle; @@ -66,7 +71,12 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A static final Image MINUS = new Image("/org/sleuthkit/autopsy/timeline/images/minus-button.png"); // NON-NLS static final Image TAG = new Image("/org/sleuthkit/autopsy/images/green-tag-icon-16.png"); // NON-NLS static final CornerRadii CORNER_RADII = new CornerRadii(3); - Map dropShadowMap = new HashMap<>(); + /** + * the border to apply when this node is 'selected' + */ + static final Border selectionBorder = new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CORNER_RADII, new BorderWidths(2))); + private static final Logger LOGGER = Logger.getLogger(AbstractDetailViewNode.class + .getName()); static void configureLODButton(Button b) { b.setMinSize(16, 16); @@ -74,42 +84,14 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A b.setPrefSize(16, 16); show(b, false); } - /** - * the border to apply when this node is 'selected' - */ - static final Border selectionBorder = new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CORNER_RADII, new BorderWidths(2))); - final Color evtColor; - - @Override - @SuppressWarnings("unchecked") - public List getSubNodes() { - return subNodePane.getChildrenUnmodifiable().stream() - .map(t -> (S) t) - .collect(Collectors.toList()); - } - - /** - * apply the 'effect' to visually indicate selection - * - * @param applied true to apply the selection 'effect', false to remove it - */ - @Override - public void applySelectionEffect(boolean applied) { - Platform.runLater(() -> { - if (applied) { - setBorder(selectionBorder); - } else { - setBorder(null); - } - }); - } static void show(Node b, boolean show) { b.setVisible(show); b.setManaged(show); } - final ImageView hashIV = new ImageView(HASH_PIN); - final ImageView tagIV = new ImageView(TAG); + Map dropShadowMap = new HashMap<>(); + final Color evtColor; + private final S parentNode; DescriptionVisibility descrVis; @@ -142,45 +124,16 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A final SleuthkitCase sleuthkitCase; final FilteredEventsModel eventsModel; - final Button plusButton = new Button(null, new ImageView(PLUS)) { - { - configureLODButton(this); - } + final Button plusButton; + final Button minusButton; - }; - - final Button minusButton = new Button(null, new ImageView(MINUS)) { - { - configureLODButton(this); - } - }; - SimpleObjectProperty descLOD = new SimpleObjectProperty<>(); + final SimpleObjectProperty descLOD = new SimpleObjectProperty<>(); final HBox header; - /** - * - * @param showControls the value of par - */ - void showDescriptionLoDControls(final boolean showControls) { - DropShadow dropShadow = dropShadowMap.computeIfAbsent(getEventType(), - eventType -> new DropShadow(10, eventType.getColor())); - getSpanFillNode().setEffect(showControls ? dropShadow : null); - show(minusButton, showControls); - show(plusButton, showControls); - } final Region spacer = new Region(); - RootFilter getSubClusterFilter() { - RootFilter combinedFilter = eventsModel.filterProperty().get().copyOf(); - //make a new filter intersecting the global filter with text(description) and type filters to restrict sub-clusters - combinedFilter.getSubFilters().addAll(new TextFilter(getEventBundle().getDescription()), - new TypeFilter(getEventType())); - return combinedFilter; - } - - abstract Collection makeBundlesFromClusters(List eventClusters); - - abstract void showSpans(final boolean showSpans); + private final CollapseClusterAction collapseClusterAction; + private final ExpandClusterAction expandClusterAction; public AbstractDetailViewNode(EventDetailChart chart, T bundle, S parentEventNode) { this.eventBundle = bundle; @@ -189,21 +142,34 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A descLOD.set(bundle.getDescriptionLOD()); sleuthkitCase = chart.getController().getAutopsyCase().getSleuthkitCase(); eventsModel = chart.getController().getEventsModel(); - + ImageView hashIV = new ImageView(HASH_PIN); + ImageView tagIV = new ImageView(TAG); if (eventBundle.getEventIDsWithHashHits().isEmpty()) { show(hashIV, false); } if (eventBundle.getEventIDsWithTags().isEmpty()) { show(tagIV, false); } + + expandClusterAction = new ExpandClusterAction(); + plusButton = ActionUtils.createButton(expandClusterAction, ActionUtils.ActionTextBehavior.HIDE); + configureLODButton(plusButton); + + collapseClusterAction = new CollapseClusterAction(); + minusButton = ActionUtils.createButton(collapseClusterAction, ActionUtils.ActionTextBehavior.HIDE); + configureLODButton(minusButton); + HBox.setHgrow(spacer, Priority.ALWAYS); - header = new HBox(getDescrLabel(), getCountLabel(), hashIV, tagIV, /*spacer,*/ minusButton, plusButton); + header = new HBox(getDescrLabel(), getCountLabel(), hashIV, tagIV, /* + * spacer, + */ minusButton, plusButton); header.setMinWidth(USE_PREF_SIZE); header.setPadding(new Insets(2, 5, 2, 5)); header.setAlignment(Pos.CENTER_LEFT); //setup description label evtColor = getEventType().getColor(); + eventTypeImageView.setImage(getEventType().getFXImage()); descrLabel.setGraphic(eventTypeImageView); descrLabel.setPrefWidth(USE_COMPUTED_SIZE); @@ -220,7 +186,6 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A setAlignment(Pos.TOP_LEFT); setMinHeight(24); - setPrefHeight(USE_COMPUTED_SIZE); setMaxHeight(USE_PREF_SIZE); setOnMouseClicked(new EventMouseHandler()); @@ -238,29 +203,59 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A }); setCursor(Cursor.HAND); - plusButton.disableProperty().bind(descLOD.isEqualTo(DescriptionLOD.FULL)); - minusButton.disableProperty().bind(descLOD.isEqualTo(getEventBundle().getDescriptionLOD())); - - plusButton.setOnMouseClicked(e -> { - final DescriptionLOD next = descLOD.get().next(); - if (next != null) { - loadSubClusters(next); - descLOD.set(next); - } - }); - minusButton.setOnMouseClicked(e -> { - final DescriptionLOD previous = descLOD.get().previous(); - if (previous != null) { - loadSubClusters(previous); - descLOD.set(previous); - } - }); - setBackground(new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY))); setLayoutX(getChart().getXAxis().getDisplayPosition(new DateTime(eventBundle.getStartMillis())) - getLayoutXCompensation()); } + @Override + @SuppressWarnings("unchecked") + public List getSubNodes() { + return subNodePane.getChildrenUnmodifiable().stream() + .map(t -> (S) t) + .collect(Collectors.toList()); + } + + /** + * apply the 'effect' to visually indicate selection + * + * @param applied true to apply the selection 'effect', false to remove it + */ + @Override + public void applySelectionEffect(boolean applied) { + Platform.runLater(() -> { + if (applied) { + setBorder(selectionBorder); + } else { + setBorder(null); + } + }); + } + + /** + * + * @param showControls the value of par + */ + void showDescriptionLoDControls(final boolean showControls) { + DropShadow dropShadow = dropShadowMap.computeIfAbsent(getEventType(), + eventType -> new DropShadow(10, eventType.getColor())); + getSpanFillNode().setEffect(showControls ? dropShadow : null); + show(minusButton, showControls); + show(plusButton, showControls); + } + + RootFilter getSubClusterFilter() { + RootFilter combinedFilter = eventsModel.filterProperty().get().copyOf(); + //make a new filter intersecting the global filter with text(description) and type filters to restrict sub-clusters + combinedFilter.getSubFilters().addAll(new TextFilter(getEventBundle().getDescription()), + new TypeFilter(getEventType())); + return combinedFilter; + } + + abstract Collection makeBundlesFromClusters(List eventClusters); + + abstract void showSpans(final boolean showSpans); + /** * @param w the maximum width the description label should have */ @@ -307,7 +302,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A return descrLabel; } - public final Label getCountLabel() { + final public Label getCountLabel() { return countLabel; } @@ -327,7 +322,6 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A public DescriptionLOD getDescLOD() { return descLOD.get(); } - private static final Logger LOGGER = Logger.getLogger(AbstractDetailViewNode.class.getName()); /** * loads sub-clusters at the given Description LOD @@ -358,7 +352,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A eventsModel.eventTypeZoomProperty().get(), combinedFilter, newDescriptionLOD)); - + return makeBundlesFromClusters(aggregatedEvents).stream() .map(aggEvent -> { return getNodeForCluser(aggEvent); @@ -392,7 +386,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A } @Override - final public void setDescriptionVisibility(DescriptionVisibility descrVis) { + public final void setDescriptionVisibility(DescriptionVisibility descrVis) { this.descrVis = descrVis; final int size = getEventBundle().getEventIDs().size(); @@ -417,11 +411,15 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A } } + abstract S getNodeForCluser(T cluster); + /** * event handler used for mouse events on {@link AggregateEventNode}s */ private class EventMouseHandler implements EventHandler { + private ContextMenu contextMenu; + @Override public void handle(MouseEvent t) { @@ -442,11 +440,55 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A } else { chart.selectedNodes.setAll(AbstractDetailViewNode.this); } + t.consume(); + } else if (t.getButton() == MouseButton.SECONDARY) { + ContextMenu chartContextMenu = chart.getChartContextMenu(t); + if (contextMenu == null) { + contextMenu = new ContextMenu(); + contextMenu.setAutoHide(true); + contextMenu.getItems().add(ActionUtils.createMenuItem(expandClusterAction)); + contextMenu.getItems().add(ActionUtils.createMenuItem(collapseClusterAction)); + + contextMenu.getItems().add(new SeparatorMenuItem()); + contextMenu.getItems().addAll(chartContextMenu.getItems()); + } + contextMenu.show(AbstractDetailViewNode.this, t.getScreenX(), t.getScreenY()); + t.consume(); } } } - abstract S getNodeForCluser(T cluster); + private class ExpandClusterAction extends Action { + public ExpandClusterAction() { + super("Expand"); + setGraphic(new ImageView(PLUS)); + setEventHandler((ActionEvent t) -> { + final DescriptionLOD next = descLOD.get().next(); + if (next != null) { + loadSubClusters(next); + descLOD.set(next); + } + }); + disabledProperty().bind(descLOD.isEqualTo(DescriptionLOD.FULL)); + } + } + + private class CollapseClusterAction extends Action { + + public CollapseClusterAction() { + super("Collapse"); + + setGraphic(new ImageView(MINUS)); + setEventHandler((ActionEvent t) -> { + final DescriptionLOD previous = descLOD.get().previous(); + if (previous != null) { + loadSubClusters(previous); + descLOD.set(previous); + } + }); + disabledProperty().bind(descLOD.isEqualTo(getEventBundle().getDescriptionLOD())); + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java index 5d9bd03eb4..ae826dd5e5 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java @@ -28,6 +28,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.MissingResourceException; import java.util.Objects; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -106,7 +107,6 @@ public final class EventDetailChart extends XYChart impl */ private final SimpleBooleanProperty bandByType = new SimpleBooleanProperty(false); - // I don't like having these package visible, but it was the easiest way to private ContextMenu chartContextMenu; private TimeLineController controller; @@ -239,36 +239,7 @@ public final class EventDetailChart extends XYChart impl chartContextMenu.hide(); } if (clickEvent.getButton() == MouseButton.SECONDARY && clickEvent.isStillSincePress()) { - - chartContextMenu = ActionUtils.createContextMenu(Arrays.asList(new Action( - NbBundle.getMessage(this.getClass(), "EventDetailChart.chartContextMenu.placeMarker.name")) { - { - setGraphic(new ImageView(new Image("/org/sleuthkit/autopsy/timeline/images/marker.png", 16, 16, true, true, true))); // NON-NLS - setEventHandler((ActionEvent t) -> { - if (guideLine == null) { - guideLine = new GuideLine(0, 0, 0, getHeight(), dateAxis); - guideLine.relocate(clickEvent.getX(), 0); - guideLine.endYProperty().bind(heightProperty().subtract(dateAxis.heightProperty().subtract(dateAxis.tickLengthProperty()))); - - getChartChildren().add(guideLine); - - guideLine.setOnMouseClicked((MouseEvent event) -> { - if (event.getButton() == MouseButton.SECONDARY) { - clearGuideLine(); - event.consume(); - } - }); - } else { - guideLine.relocate(clickEvent.getX(), 0); - } - }); - } - - }, new ActionGroup( - NbBundle.getMessage(this.getClass(), "EventDetailChart.contextMenu.zoomHistory.name"), - new Back(controller), - new Forward(controller)))); - chartContextMenu.setAutoHide(true); + getChartContextMenu(clickEvent); chartContextMenu.show(EventDetailChart.this, clickEvent.getScreenX(), clickEvent.getScreenY()); clickEvent.consume(); } @@ -317,6 +288,42 @@ public final class EventDetailChart extends XYChart impl requestChartLayout(); } + ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException { + if (chartContextMenu != null) { + chartContextMenu.hide(); + } + chartContextMenu = ActionUtils.createContextMenu(Arrays.asList(new Action( + NbBundle.getMessage(this.getClass(), "EventDetailChart.chartContextMenu.placeMarker.name")) { + { + setGraphic(new ImageView(new Image("/org/sleuthkit/autopsy/timeline/images/marker.png", 16, 16, true, true, true))); // NON-NLS + setEventHandler((ActionEvent t) -> { + if (guideLine == null) { + guideLine = new GuideLine(0, 0, 0, getHeight(), getXAxis()); + guideLine.relocate(clickEvent.getX(), 0); + guideLine.endYProperty().bind(heightProperty().subtract(getXAxis().heightProperty().subtract(getXAxis().tickLengthProperty()))); + + getChartChildren().add(guideLine); + + guideLine.setOnMouseClicked((MouseEvent event) -> { + if (event.getButton() == MouseButton.SECONDARY) { + clearGuideLine(); + event.consume(); + } + }); + } else { + guideLine.relocate(clickEvent.getX(), 0); + } + }); + } + + }, new ActionGroup( + NbBundle.getMessage(this.getClass(), "EventDetailChart.contextMenu.zoomHistory.name"), + new Back(controller), + new Forward(controller)))); + chartContextMenu.setAutoHide(true); + return chartContextMenu; + } + @Override public void clearIntervalSelector() { getChartChildren().remove(intervalSelector); @@ -736,13 +743,6 @@ public final class EventDetailChart extends XYChart impl return filteredEvents; } - /** - * @return the chartContextMenu - */ - public ContextMenu getChartContextMenu() { - return chartContextMenu; - } - Property alternateLayoutProperty() { return alternateLayout; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/GuideLine.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/GuideLine.java index ab8b65cd00..3a2dad163b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/GuideLine.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/GuideLine.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.timeline.ui.detailview; import javafx.scene.Cursor; +import javafx.scene.chart.Axis; import javafx.scene.control.Tooltip; import javafx.scene.input.MouseEvent; import javafx.scene.paint.Color; @@ -32,7 +33,7 @@ import org.sleuthkit.autopsy.timeline.TimeLineController; */ class GuideLine extends Line { - private final DateAxis dateAxis; + private final Axis dateAxis; private double startLayoutX; @@ -40,7 +41,7 @@ class GuideLine extends Line { private double dragStartX = 0; - GuideLine(double startX, double startY, double endX, double endY, DateAxis axis) { + GuideLine(double startX, double startY, double endX, double endY, Axis axis) { super(startX, startY, endX, endY); dateAxis = axis; setCursor(Cursor.E_RESIZE); From 7be0b57611466b92502adbed7fab35257a853264 Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 16 Sep 2015 14:30:06 -0400 Subject: [PATCH 08/29] add a description filter and use it rather than the looser text filter to do sub clustering --- .../autopsy/timeline/db/SQLHelper.java | 21 +++++++- .../timeline/filters/DescriptionFilter.java | 53 +++++++++++++++++++ .../autopsy/timeline/filters/Filter.java | 2 + .../ui/detailview/AbstractDetailViewNode.java | 6 +-- 4 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java b/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java index d807bcc741..9b25bd401b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java @@ -30,6 +30,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType; import org.sleuthkit.autopsy.timeline.filters.AbstractFilter; import org.sleuthkit.autopsy.timeline.filters.DataSourceFilter; import org.sleuthkit.autopsy.timeline.filters.DataSourcesFilter; +import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter; import org.sleuthkit.autopsy.timeline.filters.Filter; import org.sleuthkit.autopsy.timeline.filters.HashHitsFilter; import org.sleuthkit.autopsy.timeline.filters.HashSetFilter; @@ -105,10 +106,20 @@ public class SQLHelper { return getSQLWhere((IntersectionFilter) filter); } + /** + * NOTE: I don't like this if-else instance of chain, but I can't decide + * what to do instead -jm + * + * @param filter + * + * @return + */ private static String getSQLWhere(Filter filter) { String result = ""; if (filter == null) { return "1"; + } else if (filter instanceof DescriptionFilter) { + result = getSQLWhere((DescriptionFilter) filter); } else if (filter instanceof TagsFilter) { result = getSQLWhere((TagsFilter) filter); } else if (filter instanceof HashHitsFilter) { @@ -130,7 +141,7 @@ public class SQLHelper { } else if (filter instanceof UnionFilter) { result = getSQLWhere((UnionFilter) filter); } else { - return "1"; + throw new IllegalArgumentException("getSQLWhere not defined for " + filter.getClass().getCanonicalName()); } result = StringUtils.deleteWhitespace(result).equals("(1and1and1)") ? "1" : result; result = StringUtils.deleteWhitespace(result).equals("()") ? "1" : result; @@ -145,6 +156,14 @@ public class SQLHelper { } } + private static String getSQLWhere(DescriptionFilter filter) { + if (filter.isSelected()) { + return "(" + getDescriptionColumn(filter.getDescriptionLoD()) + " LIKE '" + filter.getDescription() + "')"; // NON-NLS + } else { + return "1"; + } + } + private static String getSQLWhere(TagsFilter filter) { if (filter.isSelected() && (false == filter.isDisabled()) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java new file mode 100644 index 0000000000..8ee273f50a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java @@ -0,0 +1,53 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.timeline.filters; + +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; + +public class DescriptionFilter extends AbstractFilter { + + private final DescriptionLOD descriptionLoD; + + private final String description; + + public DescriptionFilter(DescriptionLOD descriptionLoD, String description) { + this.descriptionLoD = descriptionLoD; + this.description = description; + } + + @Override + public DescriptionFilter copyOf() { + DescriptionFilter filterCopy = new DescriptionFilter(getDescriptionLoD(), getDescription()); + filterCopy.setSelected(isSelected()); + filterCopy.setDisabled(isDisabled()); + return filterCopy; + } + + @Override + public String getDisplayName() { + return "description"; + } + + @Override + public String getHTMLReportString() { + return getDescriptionLoD().getDisplayName() + " " + getDisplayName() + " = " + getDescription(); + } + + /** + * @return the descriptionLoD + */ + public DescriptionLOD getDescriptionLoD() { + return descriptionLoD; + } + + /** + * @return the description + */ + public String getDescription() { + return description; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java index 95226e9371..709bf116d4 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java @@ -79,4 +79,6 @@ public interface Filter { SimpleBooleanProperty getDisabledProperty(); boolean isDisabled(); + + } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java index 56a0eca60c..0a720564c9 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java @@ -57,8 +57,8 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; +import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter; import org.sleuthkit.autopsy.timeline.filters.RootFilter; -import org.sleuthkit.autopsy.timeline.filters.TextFilter; import org.sleuthkit.autopsy.timeline.filters.TypeFilter; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; @@ -246,8 +246,8 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A RootFilter getSubClusterFilter() { RootFilter combinedFilter = eventsModel.filterProperty().get().copyOf(); - //make a new filter intersecting the global filter with text(description) and type filters to restrict sub-clusters - combinedFilter.getSubFilters().addAll(new TextFilter(getEventBundle().getDescription()), + //make a new filter intersecting the global filter with description and type filters to restrict sub-clusters + combinedFilter.getSubFilters().addAll(new DescriptionFilter(getEventBundle().getDescriptionLOD(), getDescription()), new TypeFilter(getEventType())); return combinedFilter; } From cc6ba6332de66dfcb182f648e29972067ac4caa4 Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 16 Sep 2015 17:02:09 -0400 Subject: [PATCH 09/29] fix description generation WIP --- .../autopsy/timeline/db/EventsRepository.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java index bae051b66e..1e9571f144 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java @@ -487,26 +487,28 @@ public class EventsRepository { final String uniquePath = f.getUniquePath(); final String parentPath = f.getParentPath(); long datasourceID = f.getDataSource().getId(); - String datasourceName = StringUtils.substringBefore(StringUtils.stripStart(uniquePath, "/"), parentPath); - String rootFolder = StringUtils.substringBetween(parentPath, "/", "/"); - String shortDesc = datasourceName + "/" + StringUtils.defaultIfBlank(rootFolder, ""); - String medD = datasourceName + parentPath; + String datasourceName = StringUtils.substringBeforeLast(uniquePath, parentPath); + + String rootFolder = StringUtils.substringBefore(StringUtils.substringAfter(parentPath, "/"), "/"); + String shortDesc = datasourceName + "/" + StringUtils.defaultString(rootFolder); + String medDesc = datasourceName + parentPath; + final TskData.FileKnown known = f.getKnown(); Set hashSets = f.getHashSetNames(); List tags = tagsManager.getContentTagsByContent(f); //insert it into the db if time is > 0 => time is legitimate (drops logical files) if (f.getAtime() > 0) { - eventDB.insertEvent(f.getAtime(), FileSystemTypes.FILE_ACCESSED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, hashSets, tags, trans); + eventDB.insertEvent(f.getAtime(), FileSystemTypes.FILE_ACCESSED, datasourceID, fID, null, uniquePath, medDesc, shortDesc, known, hashSets, tags, trans); } if (f.getMtime() > 0) { - eventDB.insertEvent(f.getMtime(), FileSystemTypes.FILE_MODIFIED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, hashSets, tags, trans); + eventDB.insertEvent(f.getMtime(), FileSystemTypes.FILE_MODIFIED, datasourceID, fID, null, uniquePath, medDesc, shortDesc, known, hashSets, tags, trans); } if (f.getCtime() > 0) { - eventDB.insertEvent(f.getCtime(), FileSystemTypes.FILE_CHANGED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, hashSets, tags, trans); + eventDB.insertEvent(f.getCtime(), FileSystemTypes.FILE_CHANGED, datasourceID, fID, null, uniquePath, medDesc, shortDesc, known, hashSets, tags, trans); } if (f.getCrtime() > 0) { - eventDB.insertEvent(f.getCrtime(), FileSystemTypes.FILE_CREATED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, hashSets, tags, trans); + eventDB.insertEvent(f.getCrtime(), FileSystemTypes.FILE_CREATED, datasourceID, fID, null, uniquePath, medDesc, shortDesc, known, hashSets, tags, trans); } publish(new ProgressWindow.ProgressUpdate(i, numFiles, From 9c8b89eb26fc04ca144b825201e46d14133200e3 Mon Sep 17 00:00:00 2001 From: jmillman Date: Thu, 17 Sep 2015 14:00:34 -0400 Subject: [PATCH 10/29] make sure short description (root folder) ends with "/" --- Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java index 1e9571f144..6248b99a97 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java @@ -491,6 +491,7 @@ public class EventsRepository { String rootFolder = StringUtils.substringBefore(StringUtils.substringAfter(parentPath, "/"), "/"); String shortDesc = datasourceName + "/" + StringUtils.defaultString(rootFolder); + shortDesc = shortDesc.endsWith("/") ? shortDesc : shortDesc + "/"; String medDesc = datasourceName + parentPath; final TskData.FileKnown known = f.getKnown(); From 03c0e143fcffc0a1ef2d7a4d9c5f82af4053e761 Mon Sep 17 00:00:00 2001 From: jmillman Date: Thu, 17 Sep 2015 14:06:29 -0400 Subject: [PATCH 11/29] fix description generation and clustering - rename getAggregatredEvents to getEventClusters - make more members private - improve logic to get subclusters - introduce notion of RelativeDetail for navigating DescriptionLoDs --- .../datamodel/FilteredEventsModel.java | 6 +- .../autopsy/timeline/db/EventsRepository.java | 12 +- .../ui/detailview/AbstractDetailViewNode.java | 178 ++++++++++++------ .../ui/detailview/EventClusterNode.java | 12 +- .../ui/detailview/EventStripeNode.java | 10 +- .../timeline/zooming/DescriptionLOD.java | 26 ++- 6 files changed, 160 insertions(+), 84 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java index 80aa51d571..664ae74ddd 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java @@ -337,7 +337,7 @@ public final class FilteredEventsModel { zoom = requestedTypeZoom.get(); lod = requestedLOD.get(); } - return repo.getAggregatedEvents(new ZoomParams(range, zoom, filter, lod)); + return repo.getEventClusters(new ZoomParams(range, zoom, filter, lod)); } /** @@ -347,8 +347,8 @@ public final class FilteredEventsModel { * range and pass the requested filter, using the given aggregation * to control the grouping of events */ - public List getAggregatedEvents(ZoomParams params) { - return repo.getAggregatedEvents(params); + public List getEventClusters(ZoomParams params) { + return repo.getEventClusters(params); } synchronized public boolean handleContentTagAdded(ContentTagAddedEvent evt) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java index 6248b99a97..c2a03545a6 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java @@ -98,7 +98,7 @@ public class EventsRepository { private final LoadingCache idToEventCache; private final LoadingCache> eventCountsCache; - private final LoadingCache> aggregateEventsCache; + private final LoadingCache> eventClusterCache; private final ObservableMap datasourcesMap = FXCollections.observableHashMap(); private final ObservableMap hashSetMap = FXCollections.observableHashMap(); @@ -146,7 +146,7 @@ public class EventsRepository { .maximumSize(1000L) .expireAfterAccess(10, TimeUnit.MINUTES) .build(CacheLoader.from(eventDB::countEventsByType)); - aggregateEventsCache = CacheBuilder.newBuilder() + eventClusterCache = CacheBuilder.newBuilder() .maximumSize(1000L) .expireAfterAccess(10, TimeUnit.MINUTES ).build(CacheLoader.from(eventDB::getClusteredEvents)); @@ -206,8 +206,8 @@ public class EventsRepository { } - synchronized public List getAggregatedEvents(ZoomParams params) { - return aggregateEventsCache.getUnchecked(params); + synchronized public List getEventClusters(ZoomParams params) { + return eventClusterCache.getUnchecked(params); } synchronized public Map countEvents(ZoomParams params) { @@ -218,7 +218,7 @@ public class EventsRepository { minCache.invalidateAll(); maxCache.invalidateAll(); eventCountsCache.invalidateAll(); - aggregateEventsCache.invalidateAll(); + eventClusterCache.invalidateAll(); idToEventCache.invalidateAll(); } @@ -292,7 +292,7 @@ public class EventsRepository { synchronized private void invalidateCaches(Set updatedEventIDs) { eventCountsCache.invalidateAll(); - aggregateEventsCache.invalidateAll(); + eventClusterCache.invalidateAll(); idToEventCache.invalidateAll(updatedEventIDs); try { tagNames.setAll(autoCase.getSleuthkitCase().getTagNamesInUse()); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java index 0a720564c9..e52cf31217 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java @@ -1,14 +1,29 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2015 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.detailview; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import static java.util.Objects.nonNull; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.stream.Collectors; @@ -61,6 +76,7 @@ import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter; import org.sleuthkit.autopsy.timeline.filters.RootFilter; import org.sleuthkit.autopsy.timeline.filters.TypeFilter; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; +import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel; import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; import org.sleuthkit.datamodel.SleuthkitCase; @@ -89,11 +105,11 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A b.setVisible(show); b.setManaged(show); } - Map dropShadowMap = new HashMap<>(); + private final Map dropShadowMap = new HashMap<>(); final Color evtColor; private final S parentNode; - DescriptionVisibility descrVis; + private DescriptionVisibility descrVis; /** * Pane that contains AggregateEventNodes of any 'subevents' if they are @@ -102,7 +118,11 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A * //TODO: move more of the control of subnodes/events here and out of * EventDetail Chart */ - final Pane subNodePane = new Pane(); + private final Pane subNodePane = new Pane(); + + Pane getSubNodePane() { + return subNodePane; + } /** * The ImageView used to show the icon for this node's event's type @@ -121,16 +141,28 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A private final T eventBundle; private final EventDetailChart chart; - final SleuthkitCase sleuthkitCase; - final FilteredEventsModel eventsModel; + private final SleuthkitCase sleuthkitCase; - final Button plusButton; - final Button minusButton; + SleuthkitCase getSleuthkitCase() { + return sleuthkitCase; + } - final SimpleObjectProperty descLOD = new SimpleObjectProperty<>(); + FilteredEventsModel getEventsModel() { + return eventsModel; + } + private final FilteredEventsModel eventsModel; + + private final Button plusButton; + private final Button minusButton; + + private final SimpleObjectProperty descLOD = new SimpleObjectProperty<>(); final HBox header; - final Region spacer = new Region(); + Region getSpacer() { + return spacer; + } + + private final Region spacer = new Region(); private final CollapseClusterAction collapseClusterAction; private final ExpandClusterAction expandClusterAction; @@ -160,9 +192,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A configureLODButton(minusButton); HBox.setHgrow(spacer, Priority.ALWAYS); - header = new HBox(getDescrLabel(), getCountLabel(), hashIV, tagIV, /* - * spacer, - */ minusButton, plusButton); + header = new HBox(getDescrLabel(), getCountLabel(), hashIV, tagIV, minusButton, plusButton); header.setMinWidth(USE_PREF_SIZE); header.setPadding(new Insets(2, 5, 2, 5)); @@ -244,12 +274,17 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A show(plusButton, showControls); } + /** + * make a new filter intersecting the global filter with description and + * type filters to restrict sub-clusters + * + */ RootFilter getSubClusterFilter() { - RootFilter combinedFilter = eventsModel.filterProperty().get().copyOf(); - //make a new filter intersecting the global filter with description and type filters to restrict sub-clusters - combinedFilter.getSubFilters().addAll(new DescriptionFilter(getEventBundle().getDescriptionLOD(), getDescription()), + RootFilter subClusterFilter = eventsModel.filterProperty().get().copyOf(); + subClusterFilter.getSubFilters().addAll( + new DescriptionFilter(getEventBundle().getDescriptionLOD(), getDescription()), new TypeFilter(getEventType())); - return combinedFilter; + return subClusterFilter; } abstract Collection makeBundlesFromClusters(List eventClusters); @@ -324,59 +359,84 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A } /** - * loads sub-clusters at the given Description LOD + * loads sub-bundles at the given Description LOD, continues * - * @param newDescriptionLOD + * @param requestedDescrLoD + * @param expand */ - final synchronized void loadSubClusters(DescriptionLOD newDescriptionLOD) { + private synchronized void loadSubBundles(DescriptionLOD.RelativeDetail relativeDetail) { subNodePane.getChildren().clear(); - if (newDescriptionLOD == getEventBundle().getDescriptionLOD()) { + if (descLOD.get().withRelativeDetail(relativeDetail) == getEventBundle().getDescriptionLOD()) { + descLOD.set(getEventBundle().getDescriptionLOD()); showSpans(true); - getChart().setRequiresLayout(true); - getChart().requestChartLayout(); + chart.setRequiresLayout(true); + chart.requestChartLayout(); } else { showSpans(false); - RootFilter combinedFilter = getSubClusterFilter(); - //make a new end inclusive span (to 'filter' with) - final Interval span = new Interval(getEventBundle().getStartMillis(), getEventBundle().getEndMillis() + 1000); + // make new ZoomParams to query with + final RootFilter subClusterFilter = getSubClusterFilter(); + /* + * We need to extend end time because for the query by one second, + * because it is treated as an open interval but we want to include + * events at exactly the time of the last event in this cluster + */ + final Interval subClusterSpan = new Interval(getEventBundle().getStartMillis(), getEventBundle().getEndMillis() + 1000); + final EventTypeZoomLevel eventTypeZoomLevel = eventsModel.eventTypeZoomProperty().get(); + final ZoomParams zoomParams = new ZoomParams(subClusterSpan, eventTypeZoomLevel, subClusterFilter, getDescLOD()); - //make a task to load the subnodes - LoggedTask> loggedTask = new LoggedTask>( + LoggedTask> loggedTask; + loggedTask = new LoggedTask>( NbBundle.getMessage(this.getClass(), "AggregateEventNode.loggedTask.name"), true) { + private Collection bundles; + private volatile DescriptionLOD loadedDescriptionLoD = getDescLOD().withRelativeDetail(relativeDetail); + private DescriptionLOD next = loadedDescriptionLoD; @Override protected List call() throws Exception { - //query for the sub-clusters - List aggregatedEvents = eventsModel.getAggregatedEvents(new ZoomParams(span, - eventsModel.eventTypeZoomProperty().get(), - combinedFilter, - newDescriptionLOD)); + do { + loadedDescriptionLoD = next; + if (loadedDescriptionLoD == getEventBundle().getDescriptionLOD()) { + return Collections.emptyList(); + } + bundles = loadBundles(); + next = loadedDescriptionLoD.withRelativeDetail(relativeDetail); + } while (bundles.size() == 1 && nonNull(next)); - return makeBundlesFromClusters(aggregatedEvents).stream() - .map(aggEvent -> { - return getNodeForCluser(aggEvent); - }).collect(Collectors.toList()); // return list of AggregateEventNodes representing subclusters + // return list of AbstractDetailViewNodes representing sub-bundles + return bundles.stream() + .map(AbstractDetailViewNode.this::getNodeForBundle) + .collect(Collectors.toList()); + } + + private Collection loadBundles() { + return makeBundlesFromClusters(eventsModel.getEventClusters(zoomParams.withDescrLOD(loadedDescriptionLoD))); } @Override protected void succeeded() { + chart.setCursor(Cursor.WAIT); try { - getChart().setCursor(Cursor.WAIT); + List subBundleNodes = get(); + if (subBundleNodes.isEmpty()) { + showSpans(true); + } else { + showSpans(false); + } + descLOD.set(loadedDescriptionLoD); //assign subNodes and request chart layout - subNodePane.getChildren().setAll(get()); - setDescriptionVisibility(descrVis); - getChart().setRequiresLayout(true); - getChart().requestChartLayout(); - getChart().setCursor(null); + subNodePane.getChildren().setAll(subBundleNodes); + chart.setRequiresLayout(true); + chart.requestChartLayout(); } catch (InterruptedException | ExecutionException ex) { LOGGER.log(Level.SEVERE, "Error loading subnodes", ex); } + chart.setCursor(null); } }; //start task - getChart().getController().monitorTask(loggedTask); + chart.getController().monitorTask(loggedTask); } } @@ -390,7 +450,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A this.descrVis = descrVis; final int size = getEventBundle().getEventIDs().size(); - switch (descrVis) { + switch (this.descrVis) { case COUNT_ONLY: descrLabel.setText(""); countLabel.setText(String.valueOf(size)); @@ -411,7 +471,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A } } - abstract S getNodeForCluser(T cluster); + abstract S getNodeForBundle(T bundle); /** * event handler used for mouse events on {@link AggregateEventNode}s @@ -432,10 +492,10 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A } else if (t.isShortcutDown()) { chart.selectedNodes.removeAll(AbstractDetailViewNode.this); } else if (t.getClickCount() > 1) { - final DescriptionLOD next = descLOD.get().next(); + final DescriptionLOD next = descLOD.get().moreDetailed(); if (next != null) { - loadSubClusters(next); - descLOD.set(next); + loadSubBundles(DescriptionLOD.RelativeDetail.MORE); + } } else { chart.selectedNodes.setAll(AbstractDetailViewNode.this); @@ -461,14 +521,15 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A private class ExpandClusterAction extends Action { - public ExpandClusterAction() { + ExpandClusterAction() { super("Expand"); + setGraphic(new ImageView(PLUS)); setEventHandler((ActionEvent t) -> { - final DescriptionLOD next = descLOD.get().next(); + final DescriptionLOD next = descLOD.get().moreDetailed(); if (next != null) { - loadSubClusters(next); - descLOD.set(next); + loadSubBundles(DescriptionLOD.RelativeDetail.MORE); + } }); disabledProperty().bind(descLOD.isEqualTo(DescriptionLOD.FULL)); @@ -477,15 +538,14 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A private class CollapseClusterAction extends Action { - public CollapseClusterAction() { + CollapseClusterAction() { super("Collapse"); setGraphic(new ImageView(MINUS)); setEventHandler((ActionEvent t) -> { - final DescriptionLOD previous = descLOD.get().previous(); + final DescriptionLOD previous = descLOD.get().lessDetailed(); if (previous != null) { - loadSubClusters(previous); - descLOD.set(previous); + loadSubBundles(DescriptionLOD.RelativeDetail.LESS); } }); disabledProperty().bind(descLOD.isEqualTo(getEventBundle().getDescriptionLOD())); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java index b07adac60b..7a3dfc3ac3 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java @@ -66,8 +66,8 @@ public class EventClusterNode extends AbstractDetailViewNode(); try { - for (TimeLineEvent tle : eventsModel.getEventsById(getEventCluster().getEventIDsWithHashHits())) { - Set hashSetNames = sleuthkitCase.getAbstractFileById(tle.getFileID()).getHashSetNames(); + for (TimeLineEvent tle : getEventsModel().getEventsById(getEventCluster().getEventIDsWithHashHits())) { + Set hashSetNames = getSleuthkitCase().getAbstractFileById(tle.getFileID()).getHashSetNames(); for (String hashSetName : hashSetNames) { hashSetCounts.merge(hashSetName, 1L, Long::sum); } @@ -99,7 +99,7 @@ public class EventClusterNode extends AbstractDetailViewNode tagCounts = new HashMap<>(); if (!getEventCluster().getEventIDsWithTags().isEmpty()) { - tagCounts.putAll(eventsModel.getTagCountsByTagName(getEventCluster().getEventIDsWithTags())); + tagCounts.putAll(getEventsModel().getTagCountsByTagName(getEventCluster().getEventIDsWithTags())); } @@ -174,7 +174,7 @@ public class EventClusterNode extends AbstractDetailViewNode { - private static final Logger LOGGER = Logger.getLogger(EventClusterNode.class.getName()); - private final HBox rangesHBox = new HBox(); EventStripeNode(EventStripe eventStripe, EventStripeNode parentNode, EventDetailChart chart) { super(chart, eventStripe, parentNode); minWidthProperty().bind(rangesHBox.widthProperty()); - final VBox internalVBox = new VBox(header, subNodePane); + final VBox internalVBox = new VBox(header, getSubNodePane()); internalVBox.setAlignment(Pos.CENTER_LEFT); for (Range range : eventStripe.getRanges()) { @@ -57,7 +54,7 @@ public class EventStripeNode extends AbstractDetailViewNode Date: Thu, 17 Sep 2015 16:18:31 -0400 Subject: [PATCH 12/29] fix guideLine placement when action is initiated over a DetailEventNode --- .../autopsy/timeline/ui/detailview/EventDetailChart.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java index ae826dd5e5..3d7f7bc017 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java @@ -299,7 +299,8 @@ public final class EventDetailChart extends XYChart impl setEventHandler((ActionEvent t) -> { if (guideLine == null) { guideLine = new GuideLine(0, 0, 0, getHeight(), getXAxis()); - guideLine.relocate(clickEvent.getX(), 0); + + guideLine.relocate(sceneToLocal(clickEvent.getSceneX(), 0).getX(), 0); guideLine.endYProperty().bind(heightProperty().subtract(getXAxis().heightProperty().subtract(getXAxis().tickLengthProperty()))); getChartChildren().add(guideLine); @@ -311,7 +312,7 @@ public final class EventDetailChart extends XYChart impl } }); } else { - guideLine.relocate(clickEvent.getX(), 0); + guideLine.relocate(sceneToLocal(clickEvent.getSceneX(), 0).getX(), 0); } }); } From cb704e8c10f1bf638488b6a5cbb809b5bcfabc5e Mon Sep 17 00:00:00 2001 From: jmillman Date: Tue, 22 Sep 2015 11:41:16 -0400 Subject: [PATCH 13/29] resolve potentially ambiguous column in db ambiguous --- Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java index be9d89d522..6b19122c8d 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java @@ -1068,8 +1068,8 @@ public class EventDB { //compose query string, new-lines only for nicer formatting if printing the entire query String query = "SELECT strftime('" + strfTimeFormat + "',time , 'unixepoch'" + timeZone + ") AS interval," // NON-NLS + "\n group_concat(events.event_id) as event_ids," - + "\n group_concat(CASE WHEN hash_hit = 1 THEN event_id ELSE NULL END) as hash_hits," - + "\n group_concat(CASE WHEN tagged = 1 THEN event_id ELSE NULL END) as taggeds," + + "\n group_concat(CASE WHEN hash_hit = 1 THEN events.event_id ELSE NULL END) as hash_hits," + + "\n group_concat(CASE WHEN tagged = 1 THEN events.event_id ELSE NULL END) as taggeds," + "\n min(time), max(time), " + typeColumn + ", " + descriptionColumn // NON-NLS + "\n FROM events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) // NON-NLS + "\n WHERE time >= " + start + " AND time < " + end + " AND " + SQLHelper.getSQLWhere(filter) // NON-NLS From 448c7ecdb5554c5f44458060d75525871820f5f2 Mon Sep 17 00:00:00 2001 From: jmillman Date: Mon, 21 Sep 2015 17:04:59 -0400 Subject: [PATCH 14/29] quick-hide/filter WIP --- .../autopsy/timeline/db/SQLHelper.java | 2 +- .../timeline/events/FiltersChangedEvent.java | 14 ++ .../timeline/filters/AbstractFilter.java | 6 + .../timeline/filters/DescriptionFilter.java | 29 ++- .../autopsy/timeline/filters/Filter.java | 5 +- .../autopsy/timeline/filters/RootFilter.java | 15 ++ .../ui/detailview/AbstractDetailViewNode.java | 30 ++- .../ui/detailview/EventDetailChart.java | 189 ++++++++++-------- .../timeline/ui/filtering/FilterSetPanel.java | 5 +- .../timeline/ui/filtering/FilterTreeItem.java | 14 +- KeywordSearch/nbproject/suite.properties | 1 - 11 files changed, 209 insertions(+), 101 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/timeline/events/FiltersChangedEvent.java delete mode 100644 KeywordSearch/nbproject/suite.properties diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java b/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java index 9b25bd401b..9282826d66 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java @@ -158,7 +158,7 @@ public class SQLHelper { private static String getSQLWhere(DescriptionFilter filter) { if (filter.isSelected()) { - return "(" + getDescriptionColumn(filter.getDescriptionLoD()) + " LIKE '" + filter.getDescription() + "')"; // NON-NLS + return "(" + getDescriptionColumn(filter.getDescriptionLoD()) + (filter.getFilterMode() == DescriptionFilter.FilterMode.INCLUDE ? "" : "NOT") + "LIKE '" + filter.getDescription() + "' )"; // NON-NLS } else { return "1"; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/events/FiltersChangedEvent.java b/Core/src/org/sleuthkit/autopsy/timeline/events/FiltersChangedEvent.java new file mode 100644 index 0000000000..6e83ca1306 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/events/FiltersChangedEvent.java @@ -0,0 +1,14 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +package org.sleuthkit.autopsy.timeline.events; + +/** + * + */ +public class FiltersChangedEvent { + +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java index 3411ade930..06902075b3 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.timeline.filters; import javafx.beans.property.SimpleBooleanProperty; +import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; /** * Base implementation of a {@link Filter}. Implements active property. @@ -64,4 +65,9 @@ public abstract class AbstractFilter implements Filter { return "[" + (isSelected() ? "x" : " ") + "]"; // NON-NLS } + @Override + public boolean test(EventBundle t) { + return true; + } + } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java index 8ee273f50a..8333275480 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java @@ -5,6 +5,7 @@ */ package org.sleuthkit.autopsy.timeline.filters; +import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; public class DescriptionFilter extends AbstractFilter { @@ -12,15 +13,21 @@ public class DescriptionFilter extends AbstractFilter { private final DescriptionLOD descriptionLoD; private final String description; + private final FilterMode filterMode; - public DescriptionFilter(DescriptionLOD descriptionLoD, String description) { + public FilterMode getFilterMode() { + return filterMode; + } + + public DescriptionFilter(DescriptionLOD descriptionLoD, String description, FilterMode filterMode) { this.descriptionLoD = descriptionLoD; this.description = description; + this.filterMode = filterMode; } @Override public DescriptionFilter copyOf() { - DescriptionFilter filterCopy = new DescriptionFilter(getDescriptionLoD(), getDescription()); + DescriptionFilter filterCopy = new DescriptionFilter(getDescriptionLoD(), getDescription(), getFilterMode()); filterCopy.setSelected(isSelected()); filterCopy.setDisabled(isDisabled()); return filterCopy; @@ -50,4 +57,22 @@ public class DescriptionFilter extends AbstractFilter { return description; } + public enum FilterMode { + + EXCLUDE, + INCLUDE; + } + + @Override + public boolean test(EventBundle t) { + if (filterMode == FilterMode.INCLUDE) { + return getDescription().equals(t.getDescription()) + && getDescriptionLoD() == t.getDescriptionLOD(); + } else { + return (getDescription().equals(t.getDescription()) == false) + || (getDescriptionLoD() != t.getDescriptionLOD() == false); + } + + } + } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java index 709bf116d4..349df6b72e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java @@ -18,14 +18,16 @@ */ package org.sleuthkit.autopsy.timeline.filters; +import java.util.function.Predicate; import javafx.beans.property.SimpleBooleanProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; /** * Interface for Filters */ -public interface Filter { +public interface Filter extends Predicate { /** * @param filters a set of filters to intersect @@ -81,4 +83,5 @@ public interface Filter { boolean isDisabled(); + } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java index bd115f303b..0ca20eb6e5 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.timeline.filters; +import java.util.List; +import java.util.stream.Collectors; import javafx.collections.FXCollections; /** @@ -59,7 +61,20 @@ public class RootFilter extends IntersectionFilter { @Override public RootFilter copyOf() { + + List annonymousSubFilters = getSubFilters().stream() + .filter(subFilter + -> (subFilter.equals(knownFilter)) + && (subFilter.equals(tagsFilter)) + && (subFilter.equals(hashFilter)) + && (subFilter.equals(typeFilter)) + && (subFilter.equals(dataSourcesFilter))) + .map(Filter::copyOf) + .collect(Collectors.toList()); + RootFilter filter = new RootFilter(knownFilter.copyOf(), tagsFilter.copyOf(), hashFilter.copyOf(), textFilter.copyOf(), typeFilter.copyOf(), dataSourcesFilter.copyOf()); + getSubFilters().addAll(annonymousSubFilters); + filter.setSelected(isSelected()); filter.setDisabled(isDisabled()); return filter; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java index e52cf31217..d9b156b5c5 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java @@ -85,6 +85,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A static final Image HASH_PIN = new Image("/org/sleuthkit/autopsy/images/hashset_hits.png"); static final Image PLUS = new Image("/org/sleuthkit/autopsy/timeline/images/plus-button.png"); // NON-NLS static final Image MINUS = new Image("/org/sleuthkit/autopsy/timeline/images/minus-button.png"); // NON-NLS + static final Image HIDE = new Image("/org/sleuthkit/autopsy/timeline/images/funnel.png"); // NON-NLS static final Image TAG = new Image("/org/sleuthkit/autopsy/images/green-tag-icon-16.png"); // NON-NLS static final CornerRadii CORNER_RADII = new CornerRadii(3); /** @@ -152,6 +153,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A } private final FilteredEventsModel eventsModel; + private final Button hideButton; private final Button plusButton; private final Button minusButton; @@ -166,6 +168,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A private final CollapseClusterAction collapseClusterAction; private final ExpandClusterAction expandClusterAction; + private final HideClusterAction hideClusterAction; public AbstractDetailViewNode(EventDetailChart chart, T bundle, S parentEventNode) { this.eventBundle = bundle; @@ -183,6 +186,10 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A show(tagIV, false); } + hideClusterAction = new HideClusterAction(); + hideButton = ActionUtils.createButton(hideClusterAction, ActionUtils.ActionTextBehavior.HIDE); + configureLODButton(hideButton); + expandClusterAction = new ExpandClusterAction(); plusButton = ActionUtils.createButton(expandClusterAction, ActionUtils.ActionTextBehavior.HIDE); configureLODButton(plusButton); @@ -192,7 +199,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A configureLODButton(minusButton); HBox.setHgrow(spacer, Priority.ALWAYS); - header = new HBox(getDescrLabel(), getCountLabel(), hashIV, tagIV, minusButton, plusButton); + header = new HBox(getDescrLabel(), getCountLabel(), hashIV, tagIV, hideButton, minusButton, plusButton); header.setMinWidth(USE_PREF_SIZE); header.setPadding(new Insets(2, 5, 2, 5)); @@ -272,6 +279,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A getSpanFillNode().setEffect(showControls ? dropShadow : null); show(minusButton, showControls); show(plusButton, showControls); + show(hideButton, showControls); } /** @@ -282,7 +290,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A RootFilter getSubClusterFilter() { RootFilter subClusterFilter = eventsModel.filterProperty().get().copyOf(); subClusterFilter.getSubFilters().addAll( - new DescriptionFilter(getEventBundle().getDescriptionLOD(), getDescription()), + new DescriptionFilter(getEventBundle().getDescriptionLOD(), getDescription(), DescriptionFilter.FilterMode.INCLUDE), new TypeFilter(getEventType())); return subClusterFilter; } @@ -501,7 +509,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A chart.selectedNodes.setAll(AbstractDetailViewNode.this); } t.consume(); - } else if (t.getButton() == MouseButton.SECONDARY) { + } else if (t.isPopupTrigger()) { ContextMenu chartContextMenu = chart.getChartContextMenu(t); if (contextMenu == null) { contextMenu = new ContextMenu(); @@ -509,6 +517,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A contextMenu.getItems().add(ActionUtils.createMenuItem(expandClusterAction)); contextMenu.getItems().add(ActionUtils.createMenuItem(collapseClusterAction)); + contextMenu.getItems().add(ActionUtils.createMenuItem(hideClusterAction)); contextMenu.getItems().add(new SeparatorMenuItem()); contextMenu.getItems().addAll(chartContextMenu.getItems()); @@ -551,4 +560,19 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A disabledProperty().bind(descLOD.isEqualTo(getEventBundle().getDescriptionLOD())); } } + + private class HideClusterAction extends Action { + + HideClusterAction() { + super("Hide"); + setGraphic(new ImageView(HIDE)); + setEventHandler((ActionEvent t) -> { + DescriptionFilter descriptionFilter = new DescriptionFilter(getDescLOD(), getDescription(), DescriptionFilter.FilterMode.EXCLUDE); + chart.getFilters().add(descriptionFilter); + eventsModel.getFilter().getSubFilters().add(descriptionFilter); + chart.setRequiresLayout(true); + chart.requestChartLayout(); + }); + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java index 3d7f7bc017..dad03914e2 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java @@ -79,6 +79,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.EventStripe; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; +import org.sleuthkit.autopsy.timeline.filters.Filter; import org.sleuthkit.autopsy.timeline.ui.TimeLineChart; /** @@ -96,9 +97,9 @@ import org.sleuthkit.autopsy.timeline.ui.TimeLineChart; * //TODO: refactor the projected lines to a separate class. -jm */ public final class EventDetailChart extends XYChart implements TimeLineChart { - + private static final int PROJECTED_LINE_Y_OFFSET = 5; - + private static final int PROJECTED_LINE_STROKE_WIDTH = 5; /** @@ -106,11 +107,11 @@ public final class EventDetailChart extends XYChart impl * events together during layout */ private final SimpleBooleanProperty bandByType = new SimpleBooleanProperty(false); - + private ContextMenu chartContextMenu; - + private TimeLineController controller; - + private FilteredEventsModel filteredEvents; /** @@ -164,7 +165,7 @@ public final class EventDetailChart extends XYChart impl * same 'row', creating a denser more compact layout */ private final SimpleBooleanProperty oneEventPerRow = new SimpleBooleanProperty(false); - + private final Map, Line> projectionMap = new HashMap<>(); /** @@ -172,7 +173,7 @@ public final class EventDetailChart extends XYChart impl */ @GuardedBy(value = "this") private boolean requiresLayout = true; - + final ObservableList> selectedNodes; /** @@ -181,7 +182,7 @@ public final class EventDetailChart extends XYChart impl */ private final ObservableList> seriesList = FXCollections.>observableArrayList(); - + private final ObservableList> sortedSeriesList = seriesList .sorted((s1, s2) -> { final List collect = EventType.allTypes.stream().map(EventType::getDisplayName).collect(Collectors.toList()); @@ -202,7 +203,7 @@ public final class EventDetailChart extends XYChart impl */ private final SimpleDoubleProperty truncateWidth = new SimpleDoubleProperty(200.0); private final SimpleBooleanProperty alternateLayout = new SimpleBooleanProperty(true); - + EventDetailChart(DateAxis dateAxis, final Axis verticalAxis, ObservableList> selectedNodes) { super(dateAxis, verticalAxis); dateAxis.setAutoRanging(false); @@ -210,7 +211,7 @@ public final class EventDetailChart extends XYChart impl //verticalAxis.setVisible(false);//TODO: why doesn't this hide the vertical axis, instead we have to turn off all parts individually? -jm verticalAxis.setTickLabelsVisible(false); verticalAxis.setTickMarkVisible(false); - + setLegendVisible(false); setPadding(Insets.EMPTY); setAlternativeColumnFillVisible(true); @@ -221,7 +222,7 @@ public final class EventDetailChart extends XYChart impl //bind listener to events that should trigger layout widthProperty().addListener(layoutInvalidationListener); heightProperty().addListener(layoutInvalidationListener); - + bandByType.addListener(layoutInvalidationListener); oneEventPerRow.addListener(layoutInvalidationListener); truncateAll.addListener(layoutInvalidationListener); @@ -244,7 +245,7 @@ public final class EventDetailChart extends XYChart impl clickEvent.consume(); } }; - + setOnMouseClicked(clickHandler); //use one handler with an if chain because it maintains state @@ -252,7 +253,7 @@ public final class EventDetailChart extends XYChart impl setOnMousePressed(dragHandler); setOnMouseReleased(dragHandler); setOnMouseDragged(dragHandler); - + this.selectedNodes = selectedNodes; this.selectedNodes.addListener(( ListChangeListener.Change> c) -> { @@ -262,12 +263,12 @@ public final class EventDetailChart extends XYChart impl Line removedLine = projectionMap.remove(t1); getChartChildren().removeAll(removedLine); }); - + }); c.getAddedSubList().forEach((DetailViewNode t) -> { - + for (Range range : t.getEventBundle().getRanges()) { - + Line line = new Line(dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(range.lowerEndpoint(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET, dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(range.upperEndpoint(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET ); @@ -279,15 +280,15 @@ public final class EventDetailChart extends XYChart impl } }); } - + this.controller.selectEventIDs(selectedNodes.stream() .flatMap(detailNode -> detailNode.getEventIDs().stream()) .collect(Collectors.toList())); }); - + requestChartLayout(); } - + ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException { if (chartContextMenu != null) { chartContextMenu.hide(); @@ -299,12 +300,12 @@ public final class EventDetailChart extends XYChart impl setEventHandler((ActionEvent t) -> { if (guideLine == null) { guideLine = new GuideLine(0, 0, 0, getHeight(), getXAxis()); - + guideLine.relocate(sceneToLocal(clickEvent.getSceneX(), 0).getX(), 0); guideLine.endYProperty().bind(heightProperty().subtract(getXAxis().heightProperty().subtract(getXAxis().tickLengthProperty()))); - + getChartChildren().add(guideLine); - + guideLine.setOnMouseClicked((MouseEvent event) -> { if (event.getButton() == MouseButton.SECONDARY) { clearGuideLine(); @@ -316,7 +317,7 @@ public final class EventDetailChart extends XYChart impl } }); } - + }, new ActionGroup( NbBundle.getMessage(this.getClass(), "EventDetailChart.contextMenu.zoomHistory.name"), new Back(controller), @@ -324,45 +325,45 @@ public final class EventDetailChart extends XYChart impl chartContextMenu.setAutoHide(true); return chartContextMenu; } - + @Override public void clearIntervalSelector() { getChartChildren().remove(intervalSelector); intervalSelector = null; } - + public synchronized SimpleBooleanProperty bandByTypeProperty() { return bandByType; } - + @Override public synchronized void setController(TimeLineController controller) { this.controller = controller; setModel(this.controller.getEventsModel()); } - + @Override public void setModel(FilteredEventsModel filteredEvents) { - + if (this.filteredEvents != filteredEvents) { filteredEvents.zoomParametersProperty().addListener(o -> { clearGuideLine(); clearIntervalSelector(); - + selectedNodes.clear(); projectionMap.clear(); controller.selectEventIDs(Collections.emptyList()); }); } this.filteredEvents = filteredEvents; - + } - + @Override public IntervalSelector newIntervalSelector(double x, Axis axis) { return new DetailIntervalSelector(x, getHeight() - axis.getHeight() - axis.getTickLength(), axis, controller); } - + synchronized void setBandByType(Boolean t1) { bandByType.set(t1); } @@ -379,35 +380,35 @@ public final class EventDetailChart extends XYChart impl public DateTime getDateTimeForPosition(double x) { return getXAxis().getValueForDisplay(getXAxis().parentToLocal(x, 0).getX()); } - + @Override public IntervalSelector getIntervalSelector() { return intervalSelector; } - + @Override public void setIntervalSelector(IntervalSelector newIntervalSelector) { intervalSelector = newIntervalSelector; getChartChildren().add(getIntervalSelector()); } - + public synchronized SimpleBooleanProperty oneEventPerRowProperty() { return oneEventPerRow; } - + public synchronized SimpleBooleanProperty truncateAllProperty() { return truncateAll; } - + synchronized void setEventOnePerRow(Boolean t1) { oneEventPerRow.set(t1); } - + synchronized void setTruncateAll(Boolean t1) { truncateAll.set(t1); - + } - + @Override protected synchronized void dataItemAdded(Series series, int i, Data data) { final EventCluster aggEvent = data.getYValue(); @@ -435,30 +436,30 @@ public final class EventDetailChart extends XYChart impl }); } } - + @Override protected synchronized void dataItemChanged(Data data) { //TODO: can we use this to help with local detail level adjustment -jm throw new UnsupportedOperationException("Not supported yet."); // NON-NLS //To change body of generated methods, choose Tools | Templates. } - + @Override protected synchronized void dataItemRemoved(Data data, Series series) { EventCluster aggEvent = data.getYValue(); Node removedNode = clusterNodeMap.remove(aggEvent); nodeGroup.getChildren().remove(removedNode); - + EventStripe removedCluster = stripeDescMap.remove(ImmutablePair.of(aggEvent.getEventType(), aggEvent.getDescription())); removedNode = stripeNodeMap.remove(removedCluster); nodeGroup.getChildren().remove(removedNode); - + data.setNode(null); } - + @Override protected void layoutChildren() { super.layoutChildren(); - + } /** @@ -478,24 +479,34 @@ public final class EventDetailChart extends XYChart impl */ @Override protected synchronized void layoutPlotChildren() { - + if (requiresLayout) { setCursor(Cursor.WAIT); double minY = 0; - + maxY.set(0.0); - + if (bandByType.get() == false) { if (alternateLayout.get() == true) { List nodes = new ArrayList<>(stripeNodeMap.values()); + Map> collect = nodes.stream().collect(Collectors.partitioningBy((DetailViewNode t) + -> filters.stream().allMatch(f -> f.test(t.getEventBundle())))); + nodes = collect.get(true); + collect.get(false).forEach((EventStripeNode t) -> { + t.setVisible(false); + t.setManaged(false); + }); nodes.sort(Comparator.comparing(DetailViewNode::getStartMillis)); + layoutNodes(nodes, minY, 0); } else { List nodes = new ArrayList<>(clusterNodeMap.values()); + nodes.removeIf((DetailViewNode t) -> filters.stream() + .allMatch(f -> f.test(t.getEventBundle())) == false); nodes.sort(Comparator.comparing(DetailViewNode::getStartMillis)); layoutNodes(nodes, minY, 0); } - + } else { for (Series s : sortedSeriesList) { if (alternateLayout.get() == true) { @@ -506,6 +517,8 @@ public final class EventDetailChart extends XYChart impl .sorted(Comparator.comparing(EventStripe::getStartMillis)) .map(stripeNodeMap::get) .collect(Collectors.toList()); + nodes.removeIf((DetailViewNode t) -> filters.stream() + .allMatch(f -> f.test(t.getEventBundle())) == false); layoutNodes(nodes, minY, 0); } else { List nodes = s.getData().stream() @@ -514,6 +527,8 @@ public final class EventDetailChart extends XYChart impl .filter(Objects::nonNull) .sorted(Comparator.comparing(EventClusterNode::getStartMillis)) .collect(Collectors.toList()); + nodes.removeIf((DetailViewNode t) -> filters.stream() + .allMatch(f -> f.test(t.getEventBundle())) == false); layoutNodes(nodes, minY, 0); } minY = maxY.get(); @@ -524,7 +539,7 @@ public final class EventDetailChart extends XYChart impl } layoutProjectionMap(); } - + @Override protected synchronized void seriesAdded(Series series, int i) { for (int j = 0; j < series.getData().size(); j++) { @@ -533,7 +548,7 @@ public final class EventDetailChart extends XYChart impl seriesList.add(series); requiresLayout = true; } - + @Override protected synchronized void seriesRemoved(Series series) { for (int j = 0; j < series.getData().size(); j++) { @@ -542,15 +557,15 @@ public final class EventDetailChart extends XYChart impl seriesList.remove(series); requiresLayout = true; } - + synchronized SimpleObjectProperty< DescriptionVisibility> getDescrVisibility() { return descrVisibility; } - + synchronized ReadOnlyDoubleProperty getMaxVScroll() { return maxY.getReadOnlyProperty(); } - + Iterable> getNodes(Predicate> p) { Collection> values = alternateLayout.get() ? stripeNodeMap.values() @@ -561,29 +576,29 @@ public final class EventDetailChart extends XYChart impl .flatMap(EventDetailChart::flatten) .filter(p).collect(Collectors.toList()); } - + public static Stream> flatten(DetailViewNode node) { return Stream.concat( Stream.of(node), node.getSubNodes().stream().flatMap(EventDetailChart::flatten)); } - + Iterable> getAllNodes() { return getNodes(x -> true); } - + synchronized SimpleDoubleProperty getTruncateWidth() { return truncateWidth; } - + synchronized void setVScroll(double d ) { final double h = maxY.get() - (getHeight() * .9); nodeGroup.setTranslateY(-d * h); } - + private void clearGuideLine() { getChartChildren().remove(guideLine); guideLine = null; @@ -608,21 +623,21 @@ public final class EventDetailChart extends XYChart impl //position of start and end according to range of axis double startX = rawDisplayPosition - xOffset; double layoutNodesResultHeight = 0; - + double span = 0; List> subNodes = node.getSubNodes(); if (subNodes.isEmpty() == false) { subNodes.sort(new DetailViewNode.StartTimeComparator()); layoutNodesResultHeight = layoutNodes(subNodes, 0, rawDisplayPosition); } - + if (alternateLayout.get() == false) { double endX = getXAxis().getDisplayPosition(new DateTime(node.getEndMillis())) - xOffset; span = endX - startX; //size timespan border node.setSpanWidths(Arrays.asList(span)); } else { - + EventStripeNode stripeNode = (EventStripeNode) node; List spanWidths = new ArrayList<>(); double x = getXAxis().getDisplayPosition(new DateTime(stripeNode.getStartMillis()));; @@ -647,9 +662,9 @@ public final class EventDetailChart extends XYChart impl spanWidths.add(clusterSpan); } } - + } while (ranges.hasNext()); - + stripeNode.setSpanWidths(spanWidths); } if (truncateAll.get()) { //if truncate option is selected limit width of description label @@ -657,7 +672,7 @@ public final class EventDetailChart extends XYChart impl } else { //else set it unbounded node.setDescriptionWidth(USE_PREF_SIZE);//20 + new Text(tlNode.getDisplayedDescription()).getLayoutBounds().getWidth()); } - + node.autosize(); //compute size of tlNode based on constraints and event data //get position of right edge of node ( influenced by description label) @@ -667,14 +682,14 @@ public final class EventDetailChart extends XYChart impl final double h = layoutNodesResultHeight == 0 ? node.getHeight() : layoutNodesResultHeight + DEFAULT_ROW_HEIGHT; //initial test position double yPos = minY; - + double yPos2 = yPos + h; - + if (oneEventPerRow.get()) { // if onePerRow, just put it at end yPos = (localMax + 2); yPos2 = yPos + h; - + } else {//else boolean overlapping = true; @@ -701,31 +716,31 @@ public final class EventDetailChart extends XYChart impl } } localMax = Math.max(yPos2, localMax); - + Timeline tm = new Timeline(new KeyFrame(Duration.seconds(1.0), new KeyValue(node.layoutXProperty(), startX), new KeyValue(node.layoutYProperty(), yPos))); - + tm.play(); } maxY.set(Math.max(maxY.get(), localMax)); return localMax - minY; } - + private static final int DEFAULT_ROW_HEIGHT = 24; - + private void layoutProjectionMap() { for (final Map.Entry, Line> entry : projectionMap.entrySet()) { final Range eventBundle = entry.getKey(); final Line line = entry.getValue(); - + line.setStartX(getParentXForValue(new DateTime(eventBundle.lowerEndpoint(), TimeLineController.getJodaTimeZone()))); line.setEndX(getParentXForValue(new DateTime(eventBundle.upperEndpoint(), TimeLineController.getJodaTimeZone()))); line.setStartY(getXAxis().getLayoutY() + PROJECTED_LINE_Y_OFFSET); line.setEndY(getXAxis().getLayoutY() + PROJECTED_LINE_Y_OFFSET); } } - + private double getParentXForValue(DateTime dt) { return getXAxis().localToParent(getXAxis().getDisplayPosition(dt), 0).getX(); } @@ -743,34 +758,40 @@ public final class EventDetailChart extends XYChart impl public FilteredEventsModel getFilteredEvents() { return filteredEvents; } - + Property alternateLayoutProperty() { return alternateLayout; } - + + ObservableList filters = FXCollections.observableArrayList(); + + ObservableList getFilters() { + return filters; + } + private class DetailIntervalSelector extends IntervalSelector { - + public DetailIntervalSelector(double x, double height, Axis axis, TimeLineController controller) { super(x, height, axis, controller); } - + @Override protected String formatSpan(DateTime date) { return date.toString(TimeLineController.getZonedFormatter()); } - + @Override protected Interval adjustInterval(Interval i) { return i; } - + @Override protected DateTime parseDateTime(DateTime date) { return date; } - + } - + synchronized void setRequiresLayout(boolean b) { requiresLayout = true; } @@ -782,7 +803,7 @@ public final class EventDetailChart extends XYChart impl protected void requestChartLayout() { super.requestChartLayout(); } - + void applySelectionEffect(DetailViewNode c1, Boolean selected) { c1.applySelectionEffect(selected); } 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 3f9bc03fab..31e8a6e50b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java @@ -54,7 +54,7 @@ import static org.sleuthkit.autopsy.timeline.ui.filtering.Bundle.Timeline_ui_fil * This also implements {@link TimeLineView} since it dynamically updates its * filters based on the contents of a {@link FilteredEventsModel} */ -public class FilterSetPanel extends BorderPane implements TimeLineView { +final public class FilterSetPanel extends BorderPane implements TimeLineView { @FXML private Button applyButton; @@ -173,6 +173,7 @@ public class FilterSetPanel extends BorderPane implements TimeLineView { @Override public void setModel(FilteredEventsModel filteredEvents) { this.filteredEvents = filteredEvents; + filteredEvents.registerForEvents(this); refresh(); this.filteredEvents.filterProperty().addListener((Observable o) -> { refresh(); @@ -181,7 +182,7 @@ public class FilterSetPanel extends BorderPane implements TimeLineView { private void refresh() { Platform.runLater(() -> { - filterTreeTable.setRoot(new FilterTreeItem(filteredEvents.filterProperty().get().copyOf(), expansionMap)); + filterTreeTable.setRoot(new FilterTreeItem(filteredEvents.getFilter().copyOf(), expansionMap)); }); } } 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 d1b71e85e4..2a08f13145 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterTreeItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterTreeItem.java @@ -11,7 +11,7 @@ import org.sleuthkit.autopsy.timeline.filters.Filter; /** * A TreeItem for a filter. */ -public class FilterTreeItem extends TreeItem { +final public class FilterTreeItem extends TreeItem { /** * recursively construct a tree of treeitems to parallel the filter tree of @@ -40,16 +40,16 @@ public class FilterTreeItem extends TreeItem { }); if (f instanceof CompoundFilter) { - CompoundFilter cf = (CompoundFilter) f; + CompoundFilter compoundFilter = (CompoundFilter) f; - for (Filter af : cf.getSubFilters()) { - getChildren().add(new FilterTreeItem(af, expansionMap)); + for (Filter subFilter : compoundFilter.getSubFilters()) { + getChildren().add(new FilterTreeItem(subFilter, expansionMap)); } - cf.getSubFilters().addListener((ListChangeListener.Change c) -> { + compoundFilter.getSubFilters().addListener((ListChangeListener.Change c) -> { while (c.next()) { - for (Filter af : c.getAddedSubList()) { - getChildren().add(new FilterTreeItem(af, expansionMap)); + for (Filter subfFilter : c.getAddedSubList()) { + getChildren().add(new FilterTreeItem(subfFilter, expansionMap)); } } }); diff --git a/KeywordSearch/nbproject/suite.properties b/KeywordSearch/nbproject/suite.properties deleted file mode 100644 index 29d7cc9bd6..0000000000 --- a/KeywordSearch/nbproject/suite.properties +++ /dev/null @@ -1 +0,0 @@ -suite.dir=${basedir}/.. From 56358568f0db25032de20bd259cd5b2b6bf8b5da Mon Sep 17 00:00:00 2001 From: jmillman Date: Tue, 22 Sep 2015 17:18:47 -0400 Subject: [PATCH 15/29] quick-hide/filter WIP 2 --- .../autopsy/timeline/db/SQLHelper.java | 2 +- .../timeline/filters/AbstractFilter.java | 7 - .../timeline/filters/CompoundFilter.java | 2 + .../timeline/filters/DataSourceFilter.java | 2 - .../timeline/filters/DescriptionFilter.java | 73 +++- .../autopsy/timeline/filters/Filter.java | 9 +- .../timeline/filters/IntersectionFilter.java | 2 + .../autopsy/timeline/filters/RootFilter.java | 22 +- .../autopsy/timeline/filters/UnionFilter.java | 2 + .../ui/detailview/AbstractDetailViewNode.java | 6 +- .../ui/detailview/DetailViewPane.java | 17 +- .../ui/detailview/EventDetailChart.java | 333 +++++++++--------- .../timeline/ui/detailview/tree/NavPanel.java | 53 +-- .../autopsy/timeline/zooming/ZoomParams.java | 4 +- 14 files changed, 284 insertions(+), 250 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java b/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java index 9282826d66..a8f9e30cc8 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java @@ -158,7 +158,7 @@ public class SQLHelper { private static String getSQLWhere(DescriptionFilter filter) { if (filter.isSelected()) { - return "(" + getDescriptionColumn(filter.getDescriptionLoD()) + (filter.getFilterMode() == DescriptionFilter.FilterMode.INCLUDE ? "" : "NOT") + "LIKE '" + filter.getDescription() + "' )"; // NON-NLS + return "(" + getDescriptionColumn(filter.getDescriptionLoD()) + (filter.getFilterMode() == DescriptionFilter.FilterMode.INCLUDE ? "" : " NOT") + " LIKE '" + filter.getDescription() + "' )"; // NON-NLS } else { return "1"; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java index 06902075b3..d198f6c5a9 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.timeline.filters; import javafx.beans.property.SimpleBooleanProperty; -import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; /** * Base implementation of a {@link Filter}. Implements active property. @@ -64,10 +63,4 @@ public abstract class AbstractFilter implements Filter { public String getStringCheckBox() { return "[" + (isSelected() ? "x" : " ") + "]"; // NON-NLS } - - @Override - public boolean test(EventBundle t) { - return true; - } - } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java index 297b0a3a1b..175edbf1fd 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java @@ -89,4 +89,6 @@ public abstract class CompoundFilter extends Abstr } return true; } + + } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourceFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourceFilter.java index b75193ddeb..2cd3f93d9e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourceFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/DataSourceFilter.java @@ -84,6 +84,4 @@ public class DataSourceFilter extends AbstractFilter { } return isSelected() == other.isSelected(); } - - } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java index 8333275480..edf7cba479 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java @@ -1,11 +1,24 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2015 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.filters; -import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; +import java.util.Objects; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; public class DescriptionFilter extends AbstractFilter { @@ -35,7 +48,7 @@ public class DescriptionFilter extends AbstractFilter { @Override public String getDisplayName() { - return "description"; + return getFilterMode().getDisplayName() + " Description"; } @Override @@ -59,20 +72,48 @@ public class DescriptionFilter extends AbstractFilter { public enum FilterMode { - EXCLUDE, - INCLUDE; + EXCLUDE("Exclude"), + INCLUDE("Include"); + + private final String displayName; + + private FilterMode(String displayName) { + this.displayName = displayName; + } + + private String getDisplayName() { + return displayName; + } + } + + public boolean test(String t) { + return (filterMode == FilterMode.INCLUDE) == getDescription().equals(t); } @Override - public boolean test(EventBundle t) { - if (filterMode == FilterMode.INCLUDE) { - return getDescription().equals(t.getDescription()) - && getDescriptionLoD() == t.getDescriptionLOD(); - } else { - return (getDescription().equals(t.getDescription()) == false) - || (getDescriptionLoD() != t.getDescriptionLOD() == false); - } - + public int hashCode() { + int hash = 7; + hash = 79 * hash + Objects.hashCode(this.descriptionLoD); + hash = 79 * hash + Objects.hashCode(this.description); + hash = 79 * hash + Objects.hashCode(this.filterMode); + return hash; } + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final DescriptionFilter other = (DescriptionFilter) obj; + if (this.descriptionLoD != other.descriptionLoD) { + return false; + } + if (!Objects.equals(this.description, other.description)) { + return false; + } + return this.filterMode == other.filterMode; + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java index 349df6b72e..d9d5a0bf38 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java @@ -18,16 +18,14 @@ */ package org.sleuthkit.autopsy.timeline.filters; -import java.util.function.Predicate; import javafx.beans.property.SimpleBooleanProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; /** * Interface for Filters */ -public interface Filter extends Predicate { +public interface Filter { /** * @param filters a set of filters to intersect @@ -81,7 +79,6 @@ public interface Filter extends Predicate { SimpleBooleanProperty getDisabledProperty(); boolean isDisabled(); - - - + + } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/IntersectionFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/IntersectionFilter.java index f99249dcf0..f76ebdb9b1 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/IntersectionFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/IntersectionFilter.java @@ -94,4 +94,6 @@ public class IntersectionFilter extends CompoundFilter { int hash = 7; return hash; } + + } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java index 0ca20eb6e5..6401501c77 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.timeline.filters; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import javafx.collections.FXCollections; @@ -61,14 +62,14 @@ public class RootFilter extends IntersectionFilter { @Override public RootFilter copyOf() { - List annonymousSubFilters = getSubFilters().stream() .filter(subFilter - -> (subFilter.equals(knownFilter)) - && (subFilter.equals(tagsFilter)) - && (subFilter.equals(hashFilter)) - && (subFilter.equals(typeFilter)) - && (subFilter.equals(dataSourcesFilter))) + -> !(subFilter.equals(knownFilter) + || subFilter.equals(tagsFilter) + || subFilter.equals(hashFilter) + || subFilter.equals(typeFilter) + || subFilter.equals(textFilter) + || subFilter.equals(dataSourcesFilter))) .map(Filter::copyOf) .collect(Collectors.toList()); @@ -82,7 +83,14 @@ public class RootFilter extends IntersectionFilter { @Override public int hashCode() { - return 3; + int hash = 3; + hash = 29 * hash + Objects.hashCode(this.knownFilter); + hash = 29 * hash + Objects.hashCode(this.tagsFilter); + hash = 29 * hash + Objects.hashCode(this.hashFilter); + hash = 29 * hash + Objects.hashCode(this.textFilter); + hash = 29 * hash + Objects.hashCode(this.typeFilter); + hash = 29 * hash + Objects.hashCode(this.dataSourcesFilter); + return hash; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/UnionFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/UnionFilter.java index e4a287f03d..07ef904e75 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/UnionFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/UnionFilter.java @@ -33,4 +33,6 @@ abstract public class UnionFilter extends Compound public UnionFilter() { super(FXCollections.observableArrayList()); } + + } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java index d9b156b5c5..794e474960 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java @@ -568,8 +568,10 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A setGraphic(new ImageView(HIDE)); setEventHandler((ActionEvent t) -> { DescriptionFilter descriptionFilter = new DescriptionFilter(getDescLOD(), getDescription(), DescriptionFilter.FilterMode.EXCLUDE); - chart.getFilters().add(descriptionFilter); - eventsModel.getFilter().getSubFilters().add(descriptionFilter); + chart.getBundleFilters().add(descriptionFilter); + RootFilter rootFilter = eventsModel.getFilter(); + rootFilter.getSubFilters().add(descriptionFilter); + chart.getController().pushFilters(rootFilter.copyOf()); chart.setRequiresLayout(true); chart.requestChartLayout(); }); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java index f40948c20b..767891d9ef 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2014-15 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,10 +18,8 @@ */ package org.sleuthkit.autopsy.timeline.ui.detailview; -import java.net.URL; import java.util.ArrayList; import java.util.Map; -import java.util.ResourceBundle; import java.util.concurrent.ConcurrentHashMap; import javafx.application.Platform; import javafx.beans.InvalidationListener; @@ -73,6 +71,7 @@ import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; +import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter; import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization; import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane; import org.sleuthkit.autopsy.timeline.ui.detailview.tree.NavTreeNode; @@ -244,6 +243,10 @@ public class DetailViewPane extends AbstractVisualization getBundleFilters() { + return chart.getBundleFilters(); + } + @Override protected Boolean isTickBold(DateTime value) { return false; @@ -378,12 +381,6 @@ public class DetailViewPane extends AbstractVisualization implements TimeLineChart { - + private static final int PROJECTED_LINE_Y_OFFSET = 5; - + private static final int PROJECTED_LINE_STROKE_WIDTH = 5; /** @@ -107,11 +107,11 @@ public final class EventDetailChart extends XYChart impl * events together during layout */ private final SimpleBooleanProperty bandByType = new SimpleBooleanProperty(false); - + private ContextMenu chartContextMenu; - + private TimeLineController controller; - + private FilteredEventsModel filteredEvents; /** @@ -156,16 +156,16 @@ public final class EventDetailChart extends XYChart impl /** * map from event to node */ - private final Map clusterNodeMap = new HashMap<>(); + private final Map> clusterNodeMap = new HashMap<>(); private final Map, EventStripe> stripeDescMap = new HashMap<>(); - private final Map stripeNodeMap = new HashMap<>(); + private final Map> stripeNodeMap = new HashMap<>(); /** * true == enforce that no two events can share the same 'row', leading to * sparser but possibly clearer layout. false == put unrelated events in the * same 'row', creating a denser more compact layout */ private final SimpleBooleanProperty oneEventPerRow = new SimpleBooleanProperty(false); - + private final Map, Line> projectionMap = new HashMap<>(); /** @@ -173,7 +173,7 @@ public final class EventDetailChart extends XYChart impl */ @GuardedBy(value = "this") private boolean requiresLayout = true; - + final ObservableList> selectedNodes; /** @@ -182,7 +182,7 @@ public final class EventDetailChart extends XYChart impl */ private final ObservableList> seriesList = FXCollections.>observableArrayList(); - + private final ObservableList> sortedSeriesList = seriesList .sorted((s1, s2) -> { final List collect = EventType.allTypes.stream().map(EventType::getDisplayName).collect(Collectors.toList()); @@ -203,7 +203,8 @@ public final class EventDetailChart extends XYChart impl */ private final SimpleDoubleProperty truncateWidth = new SimpleDoubleProperty(200.0); private final SimpleBooleanProperty alternateLayout = new SimpleBooleanProperty(true); - + private ObservableList bundleFilters = FXCollections.observableArrayList(); + EventDetailChart(DateAxis dateAxis, final Axis verticalAxis, ObservableList> selectedNodes) { super(dateAxis, verticalAxis); dateAxis.setAutoRanging(false); @@ -211,7 +212,7 @@ public final class EventDetailChart extends XYChart impl //verticalAxis.setVisible(false);//TODO: why doesn't this hide the vertical axis, instead we have to turn off all parts individually? -jm verticalAxis.setTickLabelsVisible(false); verticalAxis.setTickMarkVisible(false); - + setLegendVisible(false); setPadding(Insets.EMPTY); setAlternativeColumnFillVisible(true); @@ -222,7 +223,7 @@ public final class EventDetailChart extends XYChart impl //bind listener to events that should trigger layout widthProperty().addListener(layoutInvalidationListener); heightProperty().addListener(layoutInvalidationListener); - + bandByType.addListener(layoutInvalidationListener); oneEventPerRow.addListener(layoutInvalidationListener); truncateAll.addListener(layoutInvalidationListener); @@ -245,7 +246,7 @@ public final class EventDetailChart extends XYChart impl clickEvent.consume(); } }; - + setOnMouseClicked(clickHandler); //use one handler with an if chain because it maintains state @@ -253,7 +254,7 @@ public final class EventDetailChart extends XYChart impl setOnMousePressed(dragHandler); setOnMouseReleased(dragHandler); setOnMouseDragged(dragHandler); - + this.selectedNodes = selectedNodes; this.selectedNodes.addListener(( ListChangeListener.Change> c) -> { @@ -263,12 +264,12 @@ public final class EventDetailChart extends XYChart impl Line removedLine = projectionMap.remove(t1); getChartChildren().removeAll(removedLine); }); - + }); c.getAddedSubList().forEach((DetailViewNode t) -> { - + for (Range range : t.getEventBundle().getRanges()) { - + Line line = new Line(dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(range.lowerEndpoint(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET, dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(range.upperEndpoint(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET ); @@ -280,90 +281,89 @@ public final class EventDetailChart extends XYChart impl } }); } - + this.controller.selectEventIDs(selectedNodes.stream() .flatMap(detailNode -> detailNode.getEventIDs().stream()) .collect(Collectors.toList())); }); - + requestChartLayout(); } - + ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException { if (chartContextMenu != null) { chartContextMenu.hide(); } - chartContextMenu = ActionUtils.createContextMenu(Arrays.asList(new Action( - NbBundle.getMessage(this.getClass(), "EventDetailChart.chartContextMenu.placeMarker.name")) { - { - setGraphic(new ImageView(new Image("/org/sleuthkit/autopsy/timeline/images/marker.png", 16, 16, true, true, true))); // NON-NLS - setEventHandler((ActionEvent t) -> { - if (guideLine == null) { - guideLine = new GuideLine(0, 0, 0, getHeight(), getXAxis()); - - guideLine.relocate(sceneToLocal(clickEvent.getSceneX(), 0).getX(), 0); - guideLine.endYProperty().bind(heightProperty().subtract(getXAxis().heightProperty().subtract(getXAxis().tickLengthProperty()))); - - getChartChildren().add(guideLine); - - guideLine.setOnMouseClicked((MouseEvent event) -> { - if (event.getButton() == MouseButton.SECONDARY) { - clearGuideLine(); - event.consume(); - } - }); - } else { - guideLine.relocate(sceneToLocal(clickEvent.getSceneX(), 0).getX(), 0); + chartContextMenu = ActionUtils.createContextMenu(Arrays.asList(new Action(NbBundle.getMessage(EventDetailChart.class, "EventDetailChart.chartContextMenu.placeMarker.name")) { + { + setGraphic(new ImageView(new Image("/org/sleuthkit/autopsy/timeline/images/marker.png", 16, 16, true, true, true))); // NON-NLS + setEventHandler((ActionEvent t) -> { + if (guideLine == null) { + guideLine = new GuideLine(0, 0, 0, getHeight(), getXAxis()); + + guideLine.relocate(sceneToLocal(clickEvent.getSceneX(), 0).getX(), 0); + guideLine.endYProperty().bind(heightProperty().subtract(getXAxis().heightProperty().subtract(getXAxis().tickLengthProperty()))); + + getChartChildren().add(guideLine); + + guideLine.setOnMouseClicked((MouseEvent event) -> { + if (event.getButton() == MouseButton.SECONDARY) { + clearGuideLine(); + event.consume(); } }); + } else { + guideLine.relocate(sceneToLocal(clickEvent.getSceneX(), 0).getX(), 0); } - - }, new ActionGroup( - NbBundle.getMessage(this.getClass(), "EventDetailChart.contextMenu.zoomHistory.name"), - new Back(controller), - new Forward(controller)))); + }); + } + + }, new ActionGroup( + NbBundle.getMessage(this.getClass(), "EventDetailChart.contextMenu.zoomHistory.name"), + new Back(controller), + new Forward(controller)))); chartContextMenu.setAutoHide(true); return chartContextMenu; } - + @Override public void clearIntervalSelector() { getChartChildren().remove(intervalSelector); intervalSelector = null; } - + public synchronized SimpleBooleanProperty bandByTypeProperty() { return bandByType; } - + @Override public synchronized void setController(TimeLineController controller) { this.controller = controller; setModel(this.controller.getEventsModel()); } - + @Override public void setModel(FilteredEventsModel filteredEvents) { - + if (this.filteredEvents != filteredEvents) { filteredEvents.zoomParametersProperty().addListener(o -> { clearGuideLine(); clearIntervalSelector(); - + selectedNodes.clear(); projectionMap.clear(); controller.selectEventIDs(Collections.emptyList()); }); } this.filteredEvents = filteredEvents; - + } - + @Override public IntervalSelector newIntervalSelector(double x, Axis axis) { return new DetailIntervalSelector(x, getHeight() - axis.getHeight() - axis.getTickLength(), axis, controller); } - + synchronized void setBandByType(Boolean t1) { bandByType.set(t1); } @@ -380,86 +380,86 @@ public final class EventDetailChart extends XYChart impl public DateTime getDateTimeForPosition(double x) { return getXAxis().getValueForDisplay(getXAxis().parentToLocal(x, 0).getX()); } - + @Override public IntervalSelector getIntervalSelector() { return intervalSelector; } - + @Override public void setIntervalSelector(IntervalSelector newIntervalSelector) { intervalSelector = newIntervalSelector; getChartChildren().add(getIntervalSelector()); } - + public synchronized SimpleBooleanProperty oneEventPerRowProperty() { return oneEventPerRow; } - + public synchronized SimpleBooleanProperty truncateAllProperty() { return truncateAll; } - + synchronized void setEventOnePerRow(Boolean t1) { oneEventPerRow.set(t1); } - + synchronized void setTruncateAll(Boolean t1) { truncateAll.set(t1); - + } - + @Override protected synchronized void dataItemAdded(Series series, int i, Data data) { - final EventCluster aggEvent = data.getYValue(); + final EventCluster eventCluster = data.getYValue(); if (alternateLayout.get()) { - EventStripe eventCluster = stripeDescMap.merge(ImmutablePair.of(aggEvent.getEventType(), aggEvent.getDescription()), - new EventStripe(aggEvent), + EventStripe eventStripe = stripeDescMap.merge(ImmutablePair.of(eventCluster.getEventType(), eventCluster.getDescription()), + new EventStripe(eventCluster), (EventStripe u, EventStripe v) -> { - EventStripeNode remove = stripeNodeMap.remove(u); + AbstractDetailViewNode remove = stripeNodeMap.remove(u); nodeGroup.getChildren().remove(remove); remove = stripeNodeMap.remove(v); nodeGroup.getChildren().remove(remove); return EventStripe.merge(u, v); } ); - EventStripeNode clusterNode = new EventStripeNode(eventCluster, null, EventDetailChart.this); - stripeNodeMap.put(eventCluster, clusterNode); + EventStripeNode clusterNode = new EventStripeNode(eventStripe, null, EventDetailChart.this); + stripeNodeMap.put(eventStripe, clusterNode); nodeGroup.getChildren().add(clusterNode); } else { - clusterNodeMap.computeIfAbsent(aggEvent, (EventCluster t) -> { - EventClusterNode eventNode = new EventClusterNode(aggEvent, null, EventDetailChart.this); - eventNode.setLayoutX(getXAxis().getDisplayPosition(new DateTime(aggEvent.getSpan().getStartMillis()))); - clusterNodeMap.put(aggEvent, eventNode); + clusterNodeMap.computeIfAbsent(eventCluster, (EventCluster t) -> { + EventClusterNode eventNode = new EventClusterNode(eventCluster, null, EventDetailChart.this); + eventNode.setLayoutX(getXAxis().getDisplayPosition(new DateTime(eventCluster.getSpan().getStartMillis()))); + clusterNodeMap.put(eventCluster, eventNode); nodeGroup.getChildren().add(eventNode); return eventNode; }); } } - + @Override protected synchronized void dataItemChanged(Data data) { //TODO: can we use this to help with local detail level adjustment -jm throw new UnsupportedOperationException("Not supported yet."); // NON-NLS //To change body of generated methods, choose Tools | Templates. } - + @Override protected synchronized void dataItemRemoved(Data data, Series series) { EventCluster aggEvent = data.getYValue(); Node removedNode = clusterNodeMap.remove(aggEvent); nodeGroup.getChildren().remove(removedNode); - + EventStripe removedCluster = stripeDescMap.remove(ImmutablePair.of(aggEvent.getEventType(), aggEvent.getDescription())); removedNode = stripeNodeMap.remove(removedCluster); nodeGroup.getChildren().remove(removedNode); - + data.setNode(null); } - + @Override protected void layoutChildren() { super.layoutChildren(); - + } /** @@ -479,67 +479,60 @@ public final class EventDetailChart extends XYChart impl */ @Override protected synchronized void layoutPlotChildren() { - if (requiresLayout) { setCursor(Cursor.WAIT); - double minY = 0; - + maxY.set(0.0); - - if (bandByType.get() == false) { - if (alternateLayout.get() == true) { - List nodes = new ArrayList<>(stripeNodeMap.values()); - Map> collect = nodes.stream().collect(Collectors.partitioningBy((DetailViewNode t) - -> filters.stream().allMatch(f -> f.test(t.getEventBundle())))); - nodes = collect.get(true); - collect.get(false).forEach((EventStripeNode t) -> { - t.setVisible(false); - t.setManaged(false); - }); - nodes.sort(Comparator.comparing(DetailViewNode::getStartMillis)); - - layoutNodes(nodes, minY, 0); - } else { - List nodes = new ArrayList<>(clusterNodeMap.values()); - nodes.removeIf((DetailViewNode t) -> filters.stream() - .allMatch(f -> f.test(t.getEventBundle())) == false); - nodes.sort(Comparator.comparing(DetailViewNode::getStartMillis)); - layoutNodes(nodes, minY, 0); - } - - } else { - for (Series s : sortedSeriesList) { - if (alternateLayout.get() == true) { - List nodes = s.getData().stream() - .map(Data::getYValue) - .map(cluster -> stripeDescMap.get(ImmutablePair.of(cluster.getEventType(), cluster.getDescription()))) - .distinct() - .sorted(Comparator.comparing(EventStripe::getStartMillis)) - .map(stripeNodeMap::get) - .collect(Collectors.toList()); - nodes.removeIf((DetailViewNode t) -> filters.stream() - .allMatch(f -> f.test(t.getEventBundle())) == false); - layoutNodes(nodes, minY, 0); - } else { - List nodes = s.getData().stream() - .map(Data::getYValue) - .map(clusterNodeMap::get) - .filter(Objects::nonNull) - .sorted(Comparator.comparing(EventClusterNode::getStartMillis)) - .collect(Collectors.toList()); - nodes.removeIf((DetailViewNode t) -> filters.stream() - .allMatch(f -> f.test(t.getEventBundle())) == false); - layoutNodes(nodes, minY, 0); - } + Map>> shownPartition; + Map> nodeMap = (alternateLayout.get()) ? stripeNodeMap : clusterNodeMap; + if (bandByType.get()) { + double minY = 0; + for (Series series : sortedSeriesList) { + Stream clusterStream = series.getData().stream().map(Data::getYValue); + Stream bundleStream = (false == alternateLayout.get()) + ? clusterStream + : clusterStream.map(cluster -> ImmutablePair.of( + cluster.getEventType(), cluster.getDescription())) + .map(stripeDescMap::get) + .distinct(); + + shownPartition = bundleStream + .map(nodeMap::get) + .sorted(Comparator.comparing(AbstractDetailViewNode::getStartMillis)) + .collect(Collectors.partitioningBy(node -> getBundleFilters().stream() + .allMatch(filter -> filter.test(node.getDescription())))); + + layoutNodesHelper(shownPartition.get(false), shownPartition.get(true), minY); minY = maxY.get(); } + } else { + shownPartition = nodeMap.values().stream() + .sorted(Comparator.comparing(AbstractDetailViewNode::getStartMillis)) + .collect(Collectors.partitioningBy(node -> getBundleFilters().stream() + .allMatch(filter -> filter.test(node.getDescription())))); + layoutNodesHelper(shownPartition.get(false), shownPartition.get(true), 0); } setCursor(null); requiresLayout = false; } layoutProjectionMap(); } - + + private void layoutNodesHelper(List> hiddenNodes, List> shownNodes, double minY) { + hiddenNodes.forEach((AbstractDetailViewNode t) -> { + nodeGroup.getChildren().remove(t); + }); + + shownNodes.forEach((AbstractDetailViewNode t) -> { + if (false == nodeGroup.getChildren().contains(t)) { + nodeGroup.getChildren().add(t); + } + }); + + shownNodes.sort(Comparator.comparing(DetailViewNode::getStartMillis)); + layoutNodes(shownNodes, minY, 0); + } + @Override protected synchronized void seriesAdded(Series series, int i) { for (int j = 0; j < series.getData().size(); j++) { @@ -548,7 +541,7 @@ public final class EventDetailChart extends XYChart impl seriesList.add(series); requiresLayout = true; } - + @Override protected synchronized void seriesRemoved(Series series) { for (int j = 0; j < series.getData().size(); j++) { @@ -557,15 +550,15 @@ public final class EventDetailChart extends XYChart impl seriesList.remove(series); requiresLayout = true; } - + synchronized SimpleObjectProperty< DescriptionVisibility> getDescrVisibility() { return descrVisibility; } - + synchronized ReadOnlyDoubleProperty getMaxVScroll() { return maxY.getReadOnlyProperty(); } - + Iterable> getNodes(Predicate> p) { Collection> values = alternateLayout.get() ? stripeNodeMap.values() @@ -576,29 +569,29 @@ public final class EventDetailChart extends XYChart impl .flatMap(EventDetailChart::flatten) .filter(p).collect(Collectors.toList()); } - + public static Stream> flatten(DetailViewNode node) { return Stream.concat( Stream.of(node), node.getSubNodes().stream().flatMap(EventDetailChart::flatten)); } - + Iterable> getAllNodes() { return getNodes(x -> true); } - + synchronized SimpleDoubleProperty getTruncateWidth() { return truncateWidth; } - + synchronized void setVScroll(double d ) { final double h = maxY.get() - (getHeight() * .9); nodeGroup.setTranslateY(-d * h); } - + private void clearGuideLine() { getChartChildren().remove(guideLine); guideLine = null; @@ -623,21 +616,21 @@ public final class EventDetailChart extends XYChart impl //position of start and end according to range of axis double startX = rawDisplayPosition - xOffset; double layoutNodesResultHeight = 0; - + double span = 0; List> subNodes = node.getSubNodes(); if (subNodes.isEmpty() == false) { subNodes.sort(new DetailViewNode.StartTimeComparator()); layoutNodesResultHeight = layoutNodes(subNodes, 0, rawDisplayPosition); } - + if (alternateLayout.get() == false) { double endX = getXAxis().getDisplayPosition(new DateTime(node.getEndMillis())) - xOffset; span = endX - startX; //size timespan border node.setSpanWidths(Arrays.asList(span)); } else { - + EventStripeNode stripeNode = (EventStripeNode) node; List spanWidths = new ArrayList<>(); double x = getXAxis().getDisplayPosition(new DateTime(stripeNode.getStartMillis()));; @@ -662,9 +655,9 @@ public final class EventDetailChart extends XYChart impl spanWidths.add(clusterSpan); } } - + } while (ranges.hasNext()); - + stripeNode.setSpanWidths(spanWidths); } if (truncateAll.get()) { //if truncate option is selected limit width of description label @@ -672,7 +665,7 @@ public final class EventDetailChart extends XYChart impl } else { //else set it unbounded node.setDescriptionWidth(USE_PREF_SIZE);//20 + new Text(tlNode.getDisplayedDescription()).getLayoutBounds().getWidth()); } - + node.autosize(); //compute size of tlNode based on constraints and event data //get position of right edge of node ( influenced by description label) @@ -682,14 +675,14 @@ public final class EventDetailChart extends XYChart impl final double h = layoutNodesResultHeight == 0 ? node.getHeight() : layoutNodesResultHeight + DEFAULT_ROW_HEIGHT; //initial test position double yPos = minY; - + double yPos2 = yPos + h; - + if (oneEventPerRow.get()) { // if onePerRow, just put it at end yPos = (localMax + 2); yPos2 = yPos + h; - + } else {//else boolean overlapping = true; @@ -716,31 +709,31 @@ public final class EventDetailChart extends XYChart impl } } localMax = Math.max(yPos2, localMax); - + Timeline tm = new Timeline(new KeyFrame(Duration.seconds(1.0), new KeyValue(node.layoutXProperty(), startX), new KeyValue(node.layoutYProperty(), yPos))); - + tm.play(); } maxY.set(Math.max(maxY.get(), localMax)); return localMax - minY; } - + private static final int DEFAULT_ROW_HEIGHT = 24; - + private void layoutProjectionMap() { for (final Map.Entry, Line> entry : projectionMap.entrySet()) { final Range eventBundle = entry.getKey(); final Line line = entry.getValue(); - + line.setStartX(getParentXForValue(new DateTime(eventBundle.lowerEndpoint(), TimeLineController.getJodaTimeZone()))); line.setEndX(getParentXForValue(new DateTime(eventBundle.upperEndpoint(), TimeLineController.getJodaTimeZone()))); line.setStartY(getXAxis().getLayoutY() + PROJECTED_LINE_Y_OFFSET); line.setEndY(getXAxis().getLayoutY() + PROJECTED_LINE_Y_OFFSET); } } - + private double getParentXForValue(DateTime dt) { return getXAxis().localToParent(getXAxis().getDisplayPosition(dt), 0).getX(); } @@ -758,40 +751,38 @@ public final class EventDetailChart extends XYChart impl public FilteredEventsModel getFilteredEvents() { return filteredEvents; } - + Property alternateLayoutProperty() { return alternateLayout; } - - ObservableList filters = FXCollections.observableArrayList(); - - ObservableList getFilters() { - return filters; + + ObservableList getBundleFilters() { + return bundleFilters; } - + private class DetailIntervalSelector extends IntervalSelector { - - public DetailIntervalSelector(double x, double height, Axis axis, TimeLineController controller) { + + DetailIntervalSelector(double x, double height, Axis axis, TimeLineController controller) { super(x, height, axis, controller); } - + @Override protected String formatSpan(DateTime date) { return date.toString(TimeLineController.getZonedFormatter()); } - + @Override protected Interval adjustInterval(Interval i) { return i; } - + @Override protected DateTime parseDateTime(DateTime date) { return date; } - + } - + synchronized void setRequiresLayout(boolean b) { requiresLayout = true; } @@ -803,7 +794,7 @@ public final class EventDetailChart extends XYChart impl protected void requestChartLayout() { super.requestChartLayout(); } - + void applySelectionEffect(DetailViewNode c1, Boolean selected) { c1.applySelectionEffect(selected); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java index 9971291863..160050bcf0 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2013-15 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,10 +18,8 @@ */ package org.sleuthkit.autopsy.timeline.ui.detailview.tree; -import java.net.URL; import java.util.Arrays; import java.util.Comparator; -import java.util.ResourceBundle; import javafx.application.Platform; import javafx.beans.Observable; import javafx.collections.ObservableList; @@ -36,6 +34,7 @@ import javafx.scene.control.TreeView; import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.timeline.FXMLConstructor; @@ -43,31 +42,18 @@ import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.TimeLineView; import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; +import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter; import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewNode; import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane; -/** - * Display two trees. one shows all folders (groups) and calls out folders with - * images. the user can select folders with images to see them in the main - * GroupListPane The other shows folders with hash set hits. - */ public class NavPanel extends BorderPane implements TimeLineView { private TimeLineController controller; private FilteredEventsModel filteredEvents; - @FXML - private ResourceBundle resources; - - @FXML - private URL location; - private DetailViewPane detailViewPane; - /** - * TreeView for folders with hash hits - */ @FXML private TreeView< NavTreeNode> eventsTree; @@ -102,10 +88,8 @@ public class NavPanel extends BorderPane implements TimeLineView { RootItem root = new RootItem(); final ObservableList aggregatedEvents = detailViewPane.getAggregatedEvents(); - synchronized (aggregatedEvents) { - for (EventCluster agg : aggregatedEvents) { - root.insert(agg); - } + for (EventCluster agg : aggregatedEvents) { + root.insert(agg); } Platform.runLater(() -> { eventsTree.setRoot(root); @@ -144,7 +128,7 @@ public class NavPanel extends BorderPane implements TimeLineView { * A tree cell to display {@link NavTreeNode}s. Shows the description, and * count, as well a a "legend icon" for the event type. */ - private static class EventTreeCell extends TreeCell { + private class EventTreeCell extends TreeCell { @Override protected void updateItem(NavTreeNode item, boolean empty) { @@ -157,14 +141,33 @@ public class NavPanel extends BorderPane implements TimeLineView { rect.setArcHeight(5); rect.setArcWidth(5); rect.setStrokeWidth(2); - rect.setStroke(item.getType().getColor()); - rect.setFill(item.getType().getColor().deriveColor(0, 1, 1, 0.1)); - setGraphic(new StackPane(rect, new ImageView(item.getType().getFXImage()))); + ImageView imageView = new ImageView(item.getType().getFXImage()); + + setGraphic(new StackPane(rect, imageView)); + detailViewPane.getBundleFilters().addListener((Observable observable) -> { + asdasd(item, rect, imageView); + }); + asdasd(item, rect, imageView); + } else { setText(null); setTooltip(null); setGraphic(null); } } + + private void asdasd(NavTreeNode item, Rectangle rect, ImageView imageView) { + if (detailViewPane.getBundleFilters().stream().allMatch((DescriptionFilter t) -> t.test(item.getDescription())) == false) { + setTextFill(Color.gray(0, .6)); + imageView.setOpacity(.6); + rect.setStroke(item.getType().getColor().deriveColor(0, .6, 1, .6)); + rect.setFill(item.getType().getColor().deriveColor(0, .6, .6, 0.1)); + } else { + setTextFill(Color.BLACK); + imageView.setOpacity(1); + rect.setStroke(item.getType().getColor()); + rect.setFill(item.getType().getColor().deriveColor(0, 1, 1, 0.1)); + } + } } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomParams.java b/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomParams.java index 2bb4524e66..9ee3cd8c96 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomParams.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomParams.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.timeline.zooming; import java.util.Objects; import org.joda.time.Interval; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.timeline.filters.Filter; import org.sleuthkit.autopsy.timeline.filters.RootFilter; /** @@ -59,7 +58,6 @@ public class ZoomParams { this.typeZoomLevel = zoomLevel; this.filter = filter; this.descrLOD = descrLOD; - } public ZoomParams withTimeAndType(Interval timeRange, EventTypeZoomLevel zoomLevel) { @@ -82,7 +80,7 @@ public class ZoomParams { return new ZoomParams(timeRange, typeZoomLevel, filter, descrLOD); } - public boolean hasFilter(Filter filterSet) { + public boolean hasFilter(RootFilter filterSet) { return this.filter.equals(filterSet); } From b290534f07829404e72692df15f3fd119273c7f8 Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 23 Sep 2015 16:02:57 -0400 Subject: [PATCH 16/29] quick-hide/filter WIP 3 --- .../sleuthkit/autopsy/coreutils/History.java | 1 - .../autopsy/timeline/db/EventDB.java | 1 + .../timeline/filters/CompoundFilter.java | 2 - .../timeline/filters/DescriptionFilter.java | 6 +-- .../autopsy/timeline/images/eye--minus.png | Bin 0 -> 595 bytes .../autopsy/timeline/images/eye--plus.png | Bin 0 -> 661 bytes .../timeline/ui/AbstractVisualization.java | 2 +- .../ui/detailview/AbstractDetailViewNode.java | 30 +++-------- .../ui/detailview/DetailViewPane.java | 32 ++++++----- .../ui/detailview/EventDetailChart.java | 51 +++++++++++++++--- .../timeline/ui/detailview/tree/NavPanel.java | 18 ++++--- .../timeline/ui/detailview/tree/RootItem.java | 4 +- .../timeline/ui/filtering/FilterSetPanel.java | 5 +- 13 files changed, 88 insertions(+), 64 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/timeline/images/eye--minus.png create mode 100644 Core/src/org/sleuthkit/autopsy/timeline/images/eye--plus.png diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/History.java b/Core/src/org/sleuthkit/autopsy/coreutils/History.java index 50abf7e5cf..b9f3de0073 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/History.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/History.java @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.coreutils; -import java.util.Objects; import javafx.beans.property.Property; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanWrapper; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java index 6b19122c8d..35d8a25c5d 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java @@ -1076,6 +1076,7 @@ public class EventDB { + "\n GROUP BY interval, " + typeColumn + " , " + descriptionColumn // NON-NLS + "\n ORDER BY min(time)"; // NON-NLS + System.out.println(query); // perform query and map results to AggregateEvent objects List events = new ArrayList<>(); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java index 175edbf1fd..297b0a3a1b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java @@ -89,6 +89,4 @@ public abstract class CompoundFilter extends Abstr } return true; } - - } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java index edf7cba479..719e53f61d 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java @@ -48,7 +48,7 @@ public class DescriptionFilter extends AbstractFilter { @Override public String getDisplayName() { - return getFilterMode().getDisplayName() + " Description"; + return getFilterMode().getDisplayName() + " " + getDescription(); } @Override @@ -86,10 +86,6 @@ public class DescriptionFilter extends AbstractFilter { } } - public boolean test(String t) { - return (filterMode == FilterMode.INCLUDE) == getDescription().equals(t); - } - @Override public int hashCode() { int hash = 7; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/images/eye--minus.png b/Core/src/org/sleuthkit/autopsy/timeline/images/eye--minus.png new file mode 100644 index 0000000000000000000000000000000000000000..08b048eae310b10d71082f8edad7b081b79b98af GIT binary patch literal 595 zcmV-Z0<8UsP))U=I<(!Y-aX7<9>mi9o?H_F_AoLP-|H z4jl}~6vtoR_rvL6#}518BhI|{{l53}_jCy%=zqd>^#%y}Wnpw3+ygoY1OKA#C+L70 z_y~rM&N1Lg9xICSRM++GY&N@Tn&!1eqv7SGrfJh?G&(AkNcG_r_+$jk3I$2DBb8maYoJZW_OFc=IbmSqXXFbu&_ zES?BWI}p15NMI`AB+ zCf4iqZnfL(b?z9C$A1Nb!G5`1zNl8K7vb^v{T(4bJ(L`rG;P|h%(7N? zksjJjCUg*CEp!mX6%78ugHYL_SD6<<@ZcXH^f1|F6N?lSu~1OyVK)nzLJP&RAmY-) zDt2N!snUU!#|yklGG1}!`*B)`?XLgeCYLh z8Pu&(UmHGL$gOBJdZ$<{O8I<#$~4WfKp-G=yE+gk5@?KaR716-v9vtTpU19ityZh2 z>pCM8MPU?~%p)dAb4->mGn^G0=Bbu6q_v~Vd@L68;q--mzi+K8l}ce}6INEz*7wB3 zG&CAj80M*#G}vgK44zafm9b8zGv&gdf?zNRic*Hz*>je_<#K|<5&Uhii-DR?q~&ck z+n0L1J}8&VW+W0h?)7>{alN2v?;#Wt!Rz&5cMan4Gmti(!rjfKz2%qb&)Va+_vuu~ zz{DNhXf~VMxm@mhtyXJ`qR6}54xw6oBedIFAd2B1e!qX~UgLrN;)P3|Vx#;GY;a?6 z+hcIJ5imScj1eS^oJPixQ5rNC+%h}K`$6FJL?UtO`RWUG|MGx(*x-*~lRTI=-RpB# z<1>eIukveuH!v1QsMABlGMK(5&TKq;w?6|8RQr)GJc9)=@T%{z^&Jy$_0Iu-gNuNj v1N)Kxnid|R!PM==O+cYaVmRT1UjhsOT9g3B;L;7y00000NkvXXu0mjfDrh(C literal 0 HcmV?d00001 diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualization.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualization.java index 6d6fb8fb2e..993f61501e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualization.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualization.java @@ -304,7 +304,7 @@ public abstract class AbstractVisualization & T //x-positions (pixels) of the current branch and leaf labels double leafLabelX = 0; - if (dateTime.branch.equals("")) { + if (dateTime.branch.isEmpty()) { //if there is only one part to the date (ie only year), just add a label for each tick for (Axis.TickMark t : tickMarks) { assignLeafLabel(new TwoPartDateTime(getTickMarkLabel(t.getValue())).leaf, diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java index 794e474960..b079cb3698 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java @@ -85,7 +85,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A static final Image HASH_PIN = new Image("/org/sleuthkit/autopsy/images/hashset_hits.png"); static final Image PLUS = new Image("/org/sleuthkit/autopsy/timeline/images/plus-button.png"); // NON-NLS static final Image MINUS = new Image("/org/sleuthkit/autopsy/timeline/images/minus-button.png"); // NON-NLS - static final Image HIDE = new Image("/org/sleuthkit/autopsy/timeline/images/funnel.png"); // NON-NLS + static final Image TAG = new Image("/org/sleuthkit/autopsy/images/green-tag-icon-16.png"); // NON-NLS static final CornerRadii CORNER_RADII = new CornerRadii(3); /** @@ -166,9 +166,9 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A private final Region spacer = new Region(); - private final CollapseClusterAction collapseClusterAction; + private final CollapseBundleAction collapseClusterAction; private final ExpandClusterAction expandClusterAction; - private final HideClusterAction hideClusterAction; + private final EventDetailChart.HideBundleAction hideClusterAction; public AbstractDetailViewNode(EventDetailChart chart, T bundle, S parentEventNode) { this.eventBundle = bundle; @@ -186,7 +186,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A show(tagIV, false); } - hideClusterAction = new HideClusterAction(); + hideClusterAction = chart.new HideBundleAction(getEventBundle()); hideButton = ActionUtils.createButton(hideClusterAction, ActionUtils.ActionTextBehavior.HIDE); configureLODButton(hideButton); @@ -194,7 +194,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A plusButton = ActionUtils.createButton(expandClusterAction, ActionUtils.ActionTextBehavior.HIDE); configureLODButton(plusButton); - collapseClusterAction = new CollapseClusterAction(); + collapseClusterAction = new CollapseBundleAction(); minusButton = ActionUtils.createButton(collapseClusterAction, ActionUtils.ActionTextBehavior.HIDE); configureLODButton(minusButton); @@ -545,9 +545,9 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A } } - private class CollapseClusterAction extends Action { + private class CollapseBundleAction extends Action { - CollapseClusterAction() { + CollapseBundleAction() { super("Collapse"); setGraphic(new ImageView(MINUS)); @@ -561,20 +561,4 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A } } - private class HideClusterAction extends Action { - - HideClusterAction() { - super("Hide"); - setGraphic(new ImageView(HIDE)); - setEventHandler((ActionEvent t) -> { - DescriptionFilter descriptionFilter = new DescriptionFilter(getDescLOD(), getDescription(), DescriptionFilter.FilterMode.EXCLUDE); - chart.getBundleFilters().add(descriptionFilter); - RootFilter rootFilter = eventsModel.getFilter(); - rootFilter.getSubFilters().add(descriptionFilter); - chart.getController().pushFilters(rootFilter.copyOf()); - chart.setRequiresLayout(true); - chart.requestChartLayout(); - }); - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java index 767891d9ef..d6f86ac543 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java @@ -68,10 +68,10 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.TimeLineController; +import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; -import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter; import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization; import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane; import org.sleuthkit.autopsy.timeline.ui.detailview.tree.NavTreeNode; @@ -243,8 +243,8 @@ public class DetailViewPane extends AbstractVisualization getBundleFilters() { - return chart.getBundleFilters(); + public ObservableList getQuickHideMasks() { + return chart.getQuickHideMasks(); } @Override @@ -301,11 +301,12 @@ public class DetailViewPane extends AbstractVisualization { - if (isCancelled() == false) { + + if (isCancelled() == false) { + Platform.runLater(() -> { setCursor(Cursor.WAIT); - } - }); + }); + } updateProgress(-1, 1); updateMessage(NbBundle.getMessage(this.getClass(), "DetailViewPane.loggedTask.preparing")); @@ -315,6 +316,7 @@ public class DetailViewPane extends AbstractVisualization { @@ -336,12 +338,11 @@ public class DetailViewPane extends AbstractVisualization xyData = new BarChart.Data<>(new DateTime(e.getSpan().getStartMillis()), e); - - Platform.runLater(() -> { - if (isCancelled() == false) { + if (isCancelled() == false) { + Platform.runLater(() -> { getSeries(e.getEventType()).getData().add(xyData); - } - }); + }); + } } Platform.runLater(() -> { @@ -485,4 +486,11 @@ public class DetailViewPane extends AbstractVisualization implements TimeLineChart { + static final Image HIDE = new Image("/org/sleuthkit/autopsy/timeline/images/eye--minus.png"); // NON-NLS + static final Image SHOW = new Image("/org/sleuthkit/autopsy/timeline/images/eye--plus.png"); // NON-NLS + private static final int PROJECTED_LINE_Y_OFFSET = 5; private static final int PROJECTED_LINE_STROKE_WIDTH = 5; @@ -203,7 +206,7 @@ public final class EventDetailChart extends XYChart impl */ private final SimpleDoubleProperty truncateWidth = new SimpleDoubleProperty(200.0); private final SimpleBooleanProperty alternateLayout = new SimpleBooleanProperty(true); - private ObservableList bundleFilters = FXCollections.observableArrayList(); + private ObservableList quickHideMasks = FXCollections.observableArrayList(); EventDetailChart(DateAxis dateAxis, final Axis verticalAxis, ObservableList> selectedNodes) { super(dateAxis, verticalAxis); @@ -499,8 +502,8 @@ public final class EventDetailChart extends XYChart impl shownPartition = bundleStream .map(nodeMap::get) .sorted(Comparator.comparing(AbstractDetailViewNode::getStartMillis)) - .collect(Collectors.partitioningBy(node -> getBundleFilters().stream() - .allMatch(filter -> filter.test(node.getDescription())))); + .collect(Collectors.partitioningBy(node -> getQuickHideMasks().stream() + .anyMatch(mask -> mask.equals(node.getDescription())))); layoutNodesHelper(shownPartition.get(false), shownPartition.get(true), minY); minY = maxY.get(); @@ -508,9 +511,9 @@ public final class EventDetailChart extends XYChart impl } else { shownPartition = nodeMap.values().stream() .sorted(Comparator.comparing(AbstractDetailViewNode::getStartMillis)) - .collect(Collectors.partitioningBy(node -> getBundleFilters().stream() - .allMatch(filter -> filter.test(node.getDescription())))); - layoutNodesHelper(shownPartition.get(false), shownPartition.get(true), 0); + .collect(Collectors.partitioningBy(node -> getQuickHideMasks().stream() + .anyMatch(mask -> mask.equals(node.getDescription())))); + layoutNodesHelper(shownPartition.get(true), shownPartition.get(false), 0); } setCursor(null); requiresLayout = false; @@ -756,8 +759,8 @@ public final class EventDetailChart extends XYChart impl return alternateLayout; } - ObservableList getBundleFilters() { - return bundleFilters; + ObservableList getQuickHideMasks() { + return quickHideMasks; } private class DetailIntervalSelector extends IntervalSelector { @@ -798,4 +801,36 @@ public final class EventDetailChart extends XYChart impl void applySelectionEffect(DetailViewNode c1, Boolean selected) { c1.applySelectionEffect(selected); } + + public class HideBundleAction extends Action { + + /** + * + * @param description the value of description + */ + public HideBundleAction(final EventBundle bundle) { + super("Hide"); + setGraphic(new ImageView(HIDE)); + setEventHandler((ActionEvent t) -> { + getQuickHideMasks().add(bundle.getDescription()); + filteredEvents.getFilter().getSubFilters().add(new DescriptionFilter(bundle.getDescriptionLOD(), bundle.getDescription(), DescriptionFilter.FilterMode.EXCLUDE)); + setRequiresLayout(true); + requestChartLayout(); + }); + } + } + + public class UnhideBundleAction extends Action { + + public UnhideBundleAction(String description) { + + super("Unhide"); + setGraphic(new ImageView(SHOW)); + setEventHandler((ActionEvent t) -> { + getQuickHideMasks().removeAll(description); + setRequiresLayout(true); + requestChartLayout(); + }); + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java index 160050bcf0..9fdea6af2f 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.timeline.ui.detailview.tree; +import com.google.common.collect.ImmutableList; import java.util.Arrays; import java.util.Comparator; import javafx.application.Platform; @@ -36,13 +37,13 @@ import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; +import org.controlsfx.control.action.ActionUtils; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.TimeLineView; import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; -import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter; import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewNode; import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane; @@ -144,30 +145,35 @@ public class NavPanel extends BorderPane implements TimeLineView { ImageView imageView = new ImageView(item.getType().getFXImage()); setGraphic(new StackPane(rect, imageView)); - detailViewPane.getBundleFilters().addListener((Observable observable) -> { - asdasd(item, rect, imageView); + detailViewPane.getQuickHideMasks().addListener((Observable observable) -> { + configureHiddenState(item, rect, imageView); }); - asdasd(item, rect, imageView); + configureHiddenState(item, rect, imageView); } else { setText(null); setTooltip(null); setGraphic(null); + setContextMenu(null); } + } - private void asdasd(NavTreeNode item, Rectangle rect, ImageView imageView) { - if (detailViewPane.getBundleFilters().stream().allMatch((DescriptionFilter t) -> t.test(item.getDescription())) == false) { + private void configureHiddenState(NavTreeNode item, Rectangle rect, ImageView imageView) { + if (detailViewPane.getQuickHideMasks().stream().anyMatch(mask -> mask.equals(item.getDescription()))) { setTextFill(Color.gray(0, .6)); imageView.setOpacity(.6); rect.setStroke(item.getType().getColor().deriveColor(0, .6, 1, .6)); rect.setFill(item.getType().getColor().deriveColor(0, .6, .6, 0.1)); + setContextMenu(ActionUtils.createContextMenu(ImmutableList.of(detailViewPane.newUnhideBundleAction(item.getDescription())))); } else { setTextFill(Color.BLACK); imageView.setOpacity(1); rect.setStroke(item.getType().getColor()); rect.setFill(item.getType().getColor().deriveColor(0, 1, 1, 0.1)); +// setContextMenu(ActionUtils.createContextMenu(ImmutableList.of(detailViewPane.newHideBundleAction(item.getDescription())))); } } } + } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java index cb15a0b622..818a053823 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java @@ -22,7 +22,6 @@ import java.util.Comparator; import java.util.HashMap; import java.util.Map; import javafx.application.Platform; -import javafx.collections.FXCollections; import javafx.scene.control.TreeItem; import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; @@ -69,8 +68,7 @@ class RootItem extends NavTreeItem { Platform.runLater(() -> { synchronized (getChildren()) { getChildren().add(newTreeItem); - - FXCollections.sort(getChildren(), TreeComparator.Type); + getChildren().sort(TreeComparator.Type); } }); } else { 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 31e8a6e50b..491bc86803 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java @@ -89,7 +89,7 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { assert applyButton != null : "fx:id=\"applyButton\" was not injected: check your FXML file 'FilterSetPanel.fxml'."; // NON-NLS applyButton.setOnAction(e -> { - controller.pushFilters((RootFilter) filterTreeTable.getRoot().getValue().copyOf()); + controller.pushFilters((RootFilter) filterTreeTable.getRoot().getValue()); }); applyButton.setText(Bundle.FilterSetPanel_applyButton_text()); defaultButton.setText(Bundle.FilterSetPanel_defaultButton_text()); @@ -173,7 +173,6 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { @Override public void setModel(FilteredEventsModel filteredEvents) { this.filteredEvents = filteredEvents; - filteredEvents.registerForEvents(this); refresh(); this.filteredEvents.filterProperty().addListener((Observable o) -> { refresh(); @@ -182,7 +181,7 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { private void refresh() { Platform.runLater(() -> { - filterTreeTable.setRoot(new FilterTreeItem(filteredEvents.getFilter().copyOf(), expansionMap)); + filterTreeTable.setRoot(new FilterTreeItem(filteredEvents.getFilter(), expansionMap)); }); } } From e784822d2b2514ebfd8e91e7a35c1f87eb7a1c80 Mon Sep 17 00:00:00 2001 From: jmillman Date: Thu, 24 Sep 2015 13:58:56 -0400 Subject: [PATCH 17/29] quick-hide/filter WIP 4 --- .../sleuthkit/autopsy/coreutils/History.java | 3 +- .../autopsy/timeline/TimeLineController.java | 10 +++- .../timeline/TimeLineTopComponent.java | 6 +- .../datamodel/FilteredEventsModel.java | 3 +- .../autopsy/timeline/db/SQLHelper.java | 2 +- .../timeline/filters/CompoundFilter.java | 8 +++ .../timeline/filters/DescriptionFilter.java | 6 +- .../timeline/filters/IntersectionFilter.java | 8 --- .../autopsy/timeline/filters/RootFilter.java | 23 +++----- .../timeline/ui/VisualizationPanel.java | 6 +- .../ui/detailview/DetailViewPane.java | 6 +- .../ui/detailview/EventDetailChart.java | 23 +++----- .../timeline/ui/detailview/tree/NavPanel.java | 5 +- .../ui/filtering/FilterCheckBoxCell.java | 22 ++++++++ .../timeline/ui/filtering/FilterSetPanel.java | 55 ++++++++++++++++--- .../timeline/zooming/ZoomSettingsPane.java | 2 +- 16 files changed, 121 insertions(+), 67 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/History.java b/Core/src/org/sleuthkit/autopsy/coreutils/History.java index b9f3de0073..409cfe0006 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/History.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/History.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.coreutils; +import java.util.Deque; +import java.util.Objects; import javafx.beans.property.Property; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanWrapper; @@ -142,7 +144,6 @@ public class History { * @throws IllegalArgumentException if newState == null */ synchronized public void advance(T newState) throws IllegalArgumentException { - if (newState != null && Objects.equals(currentState.get(), newState) == false) { if (currentState.get() != null) { historyStack.push(currentState.get()); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index eebf0475bb..1d5ab70ce2 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -76,6 +76,7 @@ import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.db.EventsRepository; +import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter; import org.sleuthkit.autopsy.timeline.filters.RootFilter; import org.sleuthkit.autopsy.timeline.filters.TypeFilter; import org.sleuthkit.autopsy.timeline.utils.IntervalUtils; @@ -136,6 +137,12 @@ public class TimeLineController { private final Case autoCase; + private final ObservableList quickHideMasks = FXCollections.observableArrayList(); + + public ObservableList getQuickHideMasks() { + return quickHideMasks; + } + /** * @return the autopsy Case assigned to the controller */ @@ -173,7 +180,7 @@ public class TimeLineController { @GuardedBy("this") private final ReadOnlyObjectWrapper viewMode = new ReadOnlyObjectWrapper<>(VisualizationMode.COUNTS); - synchronized public ReadOnlyObjectProperty getViewMode() { + synchronized public ReadOnlyObjectProperty viewModeProperty() { return viewMode.getReadOnlyProperty(); } @@ -616,7 +623,6 @@ public class TimeLineController { synchronized private void advance(ZoomParams newState) { historyManager.advance(newState); - } public void selectTimeAndType(Interval interval, EventType type) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java index 33e3f63ac1..f19dc9bf1c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java @@ -169,12 +169,12 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer new Forward(controller).handle(new ActionEvent()); } }); - controller.getViewMode().addListener((Observable observable) -> { - if (controller.getViewMode().get().equals(VisualizationMode.COUNTS)) { + controller.viewModeProperty().addListener((Observable observable) -> { + if (controller.viewModeProperty().get().equals(VisualizationMode.COUNTS)) { tabPane.getSelectionModel().select(filterTab); } }); - eventsTab.disableProperty().bind(controller.getViewMode().isEqualTo(VisualizationMode.COUNTS)); + eventsTab.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS)); visualizationPanel.setController(controller); navPanel.setController(controller); filtersPanel.setController(controller); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java index 664ae74ddd..b857588199 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.timeline.datamodel; import com.google.common.eventbus.EventBus; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -227,7 +228,7 @@ public final class FilteredEventsModel { tagNameFilter.setSelected(Boolean.TRUE); tagsFilter.addSubFilter(tagNameFilter); }); - return new RootFilter(new HideKnownFilter(), tagsFilter, hashHitsFilter, new TextFilter(), new TypeFilter(RootEventType.getInstance()), dataSourcesFilter); + return new RootFilter(new HideKnownFilter(), tagsFilter, hashHitsFilter, new TextFilter(), new TypeFilter(RootEventType.getInstance()), dataSourcesFilter, Collections.emptySet()); } public Interval getBoundingEventsInterval() { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java b/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java index a8f9e30cc8..2317e6344f 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java @@ -157,7 +157,7 @@ public class SQLHelper { } private static String getSQLWhere(DescriptionFilter filter) { - if (filter.isSelected()) { + if (filter.isSelected() && (false == filter.isDisabled())) { return "(" + getDescriptionColumn(filter.getDescriptionLoD()) + (filter.getFilterMode() == DescriptionFilter.FilterMode.INCLUDE ? "" : " NOT") + " LIKE '" + filter.getDescription() + "' )"; // NON-NLS } else { return "1"; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java index 297b0a3a1b..4b5d926727 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java @@ -19,6 +19,7 @@ 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; @@ -89,4 +90,11 @@ public abstract class CompoundFilter extends Abstr } return true; } + + @Override + public int hashCode() { + int hash = 3; + hash = 61 * hash + Objects.hashCode(this.subFilters); + return hash; + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java index 719e53f61d..4f70f4b73a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java @@ -110,6 +110,10 @@ public class DescriptionFilter extends AbstractFilter { if (!Objects.equals(this.description, other.description)) { return false; } - return this.filterMode == other.filterMode; + if (this.filterMode != other.filterMode) { + return false; + } + return isSelected() == other.isSelected() + && isDisabled() == other.isDisabled(); } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/IntersectionFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/IntersectionFilter.java index f76ebdb9b1..7295a066b2 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/IntersectionFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/IntersectionFilter.java @@ -88,12 +88,4 @@ public class IntersectionFilter extends CompoundFilter { } return true; } - - @Override - public int hashCode() { - int hash = 7; - return hash; - } - - } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java index 6401501c77..4c8872d91d 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java @@ -18,8 +18,7 @@ */ package org.sleuthkit.autopsy.timeline.filters; -import java.util.List; -import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; import javafx.collections.FXCollections; @@ -48,8 +47,9 @@ public class RootFilter extends IntersectionFilter { return hashFilter; } - public RootFilter(HideKnownFilter knownFilter, TagsFilter tagsFilter, HashHitsFilter hashFilter, TextFilter textFilter, TypeFilter typeFilter, DataSourcesFilter dataSourceFilter) { + public RootFilter(HideKnownFilter knownFilter, TagsFilter tagsFilter, HashHitsFilter hashFilter, TextFilter textFilter, TypeFilter typeFilter, DataSourcesFilter dataSourceFilter, Set annonymousSubFilters) { super(FXCollections.observableArrayList(knownFilter, tagsFilter, hashFilter, textFilter, dataSourceFilter, typeFilter)); + getSubFilters().addAll(annonymousSubFilters); setSelected(Boolean.TRUE); setDisabled(false); this.knownFilter = knownFilter; @@ -62,7 +62,7 @@ public class RootFilter extends IntersectionFilter { @Override public RootFilter copyOf() { - List annonymousSubFilters = getSubFilters().stream() + Set annonymousSubFilters = getSubFilters().stream() .filter(subFilter -> !(subFilter.equals(knownFilter) || subFilter.equals(tagsFilter) @@ -71,11 +71,9 @@ public class RootFilter extends IntersectionFilter { || subFilter.equals(textFilter) || subFilter.equals(dataSourcesFilter))) .map(Filter::copyOf) - .collect(Collectors.toList()); - - RootFilter filter = new RootFilter(knownFilter.copyOf(), tagsFilter.copyOf(), hashFilter.copyOf(), textFilter.copyOf(), typeFilter.copyOf(), dataSourcesFilter.copyOf()); - getSubFilters().addAll(annonymousSubFilters); + .collect(Collectors.toSet()); + RootFilter filter = new RootFilter(knownFilter.copyOf(), tagsFilter.copyOf(), hashFilter.copyOf(), textFilter.copyOf(), typeFilter.copyOf(), dataSourcesFilter.copyOf(), annonymousSubFilters); filter.setSelected(isSelected()); filter.setDisabled(isDisabled()); return filter; @@ -83,14 +81,7 @@ public class RootFilter extends IntersectionFilter { @Override public int hashCode() { - int hash = 3; - hash = 29 * hash + Objects.hashCode(this.knownFilter); - hash = 29 * hash + Objects.hashCode(this.tagsFilter); - hash = 29 * hash + Objects.hashCode(this.hashFilter); - hash = 29 * hash + Objects.hashCode(this.textFilter); - hash = 29 * hash + Objects.hashCode(this.typeFilter); - hash = 29 * hash + Objects.hashCode(this.dataSourcesFilter); - return hash; + return super.hashCode(); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java index 6d601e06a8..94907ccaa9 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java @@ -315,14 +315,14 @@ public class VisualizationPanel extends BorderPane implements TimeLineView { public synchronized void setController(TimeLineController controller) { this.controller = controller; setModel(controller.getEventsModel()); - setViewMode(controller.getViewMode().get()); + setViewMode(controller.viewModeProperty().get()); controller.getNeedsHistogramRebuild().addListener((ObservableValue observable, Boolean oldValue, Boolean newValue) -> { if (newValue) { refreshHistorgram(); } }); - controller.getViewMode().addListener((ObservableValue ov, VisualizationMode t, VisualizationMode t1) -> { + controller.viewModeProperty().addListener((ObservableValue ov, VisualizationMode t, VisualizationMode t1) -> { setViewMode(t1); }); TimeLineController.getTimeZone().addListener(timeRangeInvalidationListener); @@ -361,7 +361,7 @@ public class VisualizationPanel extends BorderPane implements TimeLineView { } } - private synchronized void setVisualization(final AbstractVisualization newViz) { + private synchronized void setVisualization(final AbstractVisualization newViz) { Platform.runLater(() -> { synchronized (VisualizationPanel.this) { if (visualization != null) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java index d6f86ac543..275f091e6e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java @@ -243,10 +243,6 @@ public class DetailViewPane extends AbstractVisualization getQuickHideMasks() { - return chart.getQuickHideMasks(); - } - @Override protected Boolean isTickBold(DateTime value) { return false; @@ -316,7 +312,7 @@ public class DetailViewPane extends AbstractVisualization { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java index 530481b793..97666e0a02 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java @@ -206,7 +206,6 @@ public final class EventDetailChart extends XYChart impl */ private final SimpleDoubleProperty truncateWidth = new SimpleDoubleProperty(200.0); private final SimpleBooleanProperty alternateLayout = new SimpleBooleanProperty(true); - private ObservableList quickHideMasks = FXCollections.observableArrayList(); EventDetailChart(DateAxis dateAxis, final Axis verticalAxis, ObservableList> selectedNodes) { super(dateAxis, verticalAxis); @@ -502,8 +501,8 @@ public final class EventDetailChart extends XYChart impl shownPartition = bundleStream .map(nodeMap::get) .sorted(Comparator.comparing(AbstractDetailViewNode::getStartMillis)) - .collect(Collectors.partitioningBy(node -> getQuickHideMasks().stream() - .anyMatch(mask -> mask.equals(node.getDescription())))); + .collect(Collectors.partitioningBy(node -> getController().getQuickHideMasks().stream() + .anyMatch(mask -> mask.getDescription().equals(node.getDescription())))); layoutNodesHelper(shownPartition.get(false), shownPartition.get(true), minY); minY = maxY.get(); @@ -511,8 +510,8 @@ public final class EventDetailChart extends XYChart impl } else { shownPartition = nodeMap.values().stream() .sorted(Comparator.comparing(AbstractDetailViewNode::getStartMillis)) - .collect(Collectors.partitioningBy(node -> getQuickHideMasks().stream() - .anyMatch(mask -> mask.equals(node.getDescription())))); + .collect(Collectors.partitioningBy(node -> getController().getQuickHideMasks().stream() + .anyMatch(mask -> mask.getDescription().equals(node.getDescription())))); layoutNodesHelper(shownPartition.get(true), shownPartition.get(false), 0); } setCursor(null); @@ -759,10 +758,6 @@ public final class EventDetailChart extends XYChart impl return alternateLayout; } - ObservableList getQuickHideMasks() { - return quickHideMasks; - } - private class DetailIntervalSelector extends IntervalSelector { DetailIntervalSelector(double x, double height, Axis axis, TimeLineController controller) { @@ -802,7 +797,7 @@ public final class EventDetailChart extends XYChart impl c1.applySelectionEffect(selected); } - public class HideBundleAction extends Action { + class HideBundleAction extends Action { /** * @@ -812,22 +807,22 @@ public final class EventDetailChart extends XYChart impl super("Hide"); setGraphic(new ImageView(HIDE)); setEventHandler((ActionEvent t) -> { - getQuickHideMasks().add(bundle.getDescription()); - filteredEvents.getFilter().getSubFilters().add(new DescriptionFilter(bundle.getDescriptionLOD(), bundle.getDescription(), DescriptionFilter.FilterMode.EXCLUDE)); + DescriptionFilter descriptionFilter = new DescriptionFilter(bundle.getDescriptionLOD(), bundle.getDescription(), DescriptionFilter.FilterMode.EXCLUDE); + getController().getQuickHideMasks().add(descriptionFilter); setRequiresLayout(true); requestChartLayout(); }); } } - public class UnhideBundleAction extends Action { + class UnhideBundleAction extends Action { public UnhideBundleAction(String description) { super("Unhide"); setGraphic(new ImageView(SHOW)); setEventHandler((ActionEvent t) -> { - getQuickHideMasks().removeAll(description); + getController().getQuickHideMasks().removeIf((DescriptionFilter t1) -> t1.getDescription().equals(description)); setRequiresLayout(true); requestChartLayout(); }); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java index 9fdea6af2f..cf9d424ecb 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java @@ -65,7 +65,6 @@ public class NavPanel extends BorderPane implements TimeLineView { private ComboBox>> sortByBox; public NavPanel() { - FXMLConstructor.construct(this, "NavPanel.fxml"); // NON-NLS } @@ -145,7 +144,7 @@ public class NavPanel extends BorderPane implements TimeLineView { ImageView imageView = new ImageView(item.getType().getFXImage()); setGraphic(new StackPane(rect, imageView)); - detailViewPane.getQuickHideMasks().addListener((Observable observable) -> { + controller.getQuickHideMasks().addListener((Observable observable) -> { configureHiddenState(item, rect, imageView); }); configureHiddenState(item, rect, imageView); @@ -160,7 +159,7 @@ public class NavPanel extends BorderPane implements TimeLineView { } private void configureHiddenState(NavTreeNode item, Rectangle rect, ImageView imageView) { - if (detailViewPane.getQuickHideMasks().stream().anyMatch(mask -> mask.equals(item.getDescription()))) { + if (controller.getQuickHideMasks().stream().anyMatch(mask -> mask.getDescription().equals(item.getDescription()))) { setTextFill(Color.gray(0, .6)); imageView.setOpacity(.6); rect.setStroke(item.getType().getColor().deriveColor(0, .6, 1, .6)); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterCheckBoxCell.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterCheckBoxCell.java index d53aadce15..c3a75f0619 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterCheckBoxCell.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterCheckBoxCell.java @@ -22,6 +22,7 @@ import javafx.application.Platform; import javafx.beans.property.SimpleBooleanProperty; import javafx.scene.control.CheckBox; import javafx.scene.control.TreeTableCell; +import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.filters.AbstractFilter; /** @@ -32,6 +33,27 @@ class FilterCheckBoxCell extends TreeTableCell { private final CheckBox checkBox = new CheckBox(); private SimpleBooleanProperty activeProperty; + private final TimeLineController controller; + + FilterCheckBoxCell(TimeLineController controller) { + this.controller = controller; + +// controller.viewModeProperty().addListener((observable, oldVisualizationMode, newVisualizationMode) -> { +// AbstractFilter filter = getItem(); +// switch (newVisualizationMode) { +// case COUNTS: +// if (filter instanceof DescriptionFilter) { +// filter.setDisabled(true); +// } +// break; +// case DETAIL: +// if (filter instanceof DescriptionFilter) { +// filter.setDisabled(false); +// } +// break; +// } +// }); + } @Override protected void updateItem(AbstractFilter item, boolean empty) { 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 491bc86803..d920906c9d 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java @@ -21,7 +21,10 @@ package org.sleuthkit.autopsy.timeline.ui.filtering; import javafx.application.Platform; import javafx.beans.Observable; import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; import javafx.collections.ObservableMap; +import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.ContextMenu; @@ -31,8 +34,11 @@ import javafx.scene.control.TreeItem; import javafx.scene.control.TreeTableColumn; import javafx.scene.control.TreeTableRow; import javafx.scene.control.TreeTableView; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import org.controlsfx.control.action.Action; +import org.controlsfx.control.action.ActionUtils; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.TimeLineController; @@ -41,6 +47,7 @@ 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; @@ -78,7 +85,7 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { private final ObservableMap expansionMap = FXCollections.observableHashMap(); @FXML - @NbBundle.Messages({"FilterSetPanel.applyButton.text=Apply", + @NbBundle.Messages({ "Timeline.ui.filtering.menuItem.all=all", "FilterSetPanel.defaultButton.text=Default", "Timeline.ui.filtering.menuItem.none=none", @@ -88,10 +95,7 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { void initialize() { assert applyButton != null : "fx:id=\"applyButton\" was not injected: check your FXML file 'FilterSetPanel.fxml'."; // NON-NLS - applyButton.setOnAction(e -> { - controller.pushFilters((RootFilter) filterTreeTable.getRoot().getValue()); - }); - applyButton.setText(Bundle.FilterSetPanel_applyButton_text()); + ActionUtils.configureButton(new ApplyFiltersAction(), applyButton); defaultButton.setText(Bundle.FilterSetPanel_defaultButton_text()); //remove column headers via css. @@ -148,7 +152,7 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { //configure tree column to show name of filter and checkbox treeColumn.setCellValueFactory(param -> param.getValue().valueProperty()); - treeColumn.setCellFactory(col -> new FilterCheckBoxCell()); + treeColumn.setCellFactory(col -> new FilterCheckBoxCell(controller)); //configure legend column to show legend (or othe supplamantal ui, eg, text field for text filter) legendColumn.setCellValueFactory(param -> param.getValue().valueProperty()); @@ -168,20 +172,55 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { defaultButton.setOnAction(defaultFiltersAction); defaultButton.disableProperty().bind(defaultFiltersAction.disabledProperty()); this.setModel(timeLineController.getEventsModel()); + controller.getQuickHideMasks().addListener((ListChangeListener.Change c) -> { + while (c.next()) { + ObservableList subFilters = ((RootFilter) filterTreeTable.getRoot().getValue()).getSubFilters(); + for (Filter f : c.getAddedSubList()) { + if (subFilters.contains(f) == false) { + subFilters.addAll(c.getAddedSubList()); + } + } + +// ((RootFilter) filterTreeTable.getRoot().getValue()).getSubFilters().forEach(new Consumer) + } + }); + controller.viewModeProperty().addListener((Observable observable) -> { + applyFilters(); + }); } @Override public void setModel(FilteredEventsModel filteredEvents) { this.filteredEvents = filteredEvents; - refresh(); this.filteredEvents.filterProperty().addListener((Observable o) -> { refresh(); }); + refresh(); + } private void refresh() { Platform.runLater(() -> { - filterTreeTable.setRoot(new FilterTreeItem(filteredEvents.getFilter(), expansionMap)); + filterTreeTable.setRoot(new FilterTreeItem(filteredEvents.getFilter().copyOf(), expansionMap)); }); } + + @NbBundle.Messages({"FilterSetPanel.applyButton.text=Apply"}) + private class ApplyFiltersAction extends Action { + + ApplyFiltersAction() { + super(Bundle.FilterSetPanel_applyButton_text()); + setLongText("(Re)Apply filters"); + setGraphic(new ImageView(TICK)); + setEventHandler((ActionEvent t) -> { + applyFilters(); + }); + } + } + + private void applyFilters() { + controller.pushFilters((RootFilter) filterTreeTable.getRoot().getValue()); + } + + private static final Image TICK = new Image("org/sleuthkit/autopsy/timeline/images/tick.png"); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomSettingsPane.java b/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomSettingsPane.java index 1885c90d21..0e93c5f27d 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomSettingsPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomSettingsPane.java @@ -115,7 +115,7 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView { synchronized public void setController(TimeLineController controller) { this.controller = controller; setModel(controller.getEventsModel()); - descrLODSlider.disableProperty().bind(controller.getViewMode().isEqualTo(VisualizationMode.COUNTS)); + descrLODSlider.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS)); Back back = new Back(controller); backButton.disableProperty().bind(back.disabledProperty()); backButton.setOnAction(back); From dd31799cb8e8b347276efbb38ad4b1efd7fe698b Mon Sep 17 00:00:00 2001 From: millmanorama Date: Thu, 24 Sep 2015 15:55:57 -0400 Subject: [PATCH 18/29] fix path to guava jar --- CoreLibs/nbproject/project.properties | 2 +- CoreLibs/nbproject/project.xml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CoreLibs/nbproject/project.properties b/CoreLibs/nbproject/project.properties index fac6eca1cc..6bb8f91065 100644 --- a/CoreLibs/nbproject/project.properties +++ b/CoreLibs/nbproject/project.properties @@ -20,7 +20,7 @@ file.reference.dom4j-1.6.1.jar=release/modules/ext/dom4j-1.6.1.jar file.reference.geronimo-jms_1.1_spec-1.0.jar=release/modules/ext/geronimo-jms_1.1_spec-1.0.jar file.reference.gson-1.4.jar=release/modules/ext/gson-1.4.jar file.reference.gstreamer-java-1.5.jar=release/modules/ext/gstreamer-java-1.5.jar -file.reference.guava-18.0.jar=C:\\dev\\autopsy\\CoreLibs\\release\\modules\\ext\\guava-18.0.jar +file.reference.guava-18.0.jar=release/modules/ext/guava-18.0.jar file.reference.imageio-bmp-3.1.1.jar=release/modules/ext/imageio-bmp-3.1.1.jar file.reference.imageio-core-3.1.1.jar=release/modules/ext/imageio-core-3.1.1.jar file.reference.imageio-icns-3.1.1.jar=release/modules/ext/imageio-icns-3.1.1.jar diff --git a/CoreLibs/nbproject/project.xml b/CoreLibs/nbproject/project.xml index 09020ac24e..15a2b8da46 100644 --- a/CoreLibs/nbproject/project.xml +++ b/CoreLibs/nbproject/project.xml @@ -714,14 +714,14 @@ ext/imageio-pnm-3.1.1.jar release/modules/ext/imageio-pnm-3.1.1.jar - - ext/guava-18.0.jar - C:\dev\autopsy\CoreLibs\release\modules\ext\guava-18.0.jar - ext/common-lang-3.1.1.jar release/modules/ext/common-lang-3.1.1.jar + + ext/guava-18.0.jar + release/modules/ext/guava-18.0.jar + ext/slf4j-api-1.6.1.jar release/modules/ext/slf4j-api-1.6.1.jar From 11675cd1b7ac940b8abc7fb3c212da6e8621734f Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 25 Sep 2015 10:15:45 -0400 Subject: [PATCH 19/29] quick-hide/filter WIP 5 --- .../org/sleuthkit/autopsy/timeline/db/EventsRepository.java | 5 +++++ Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java | 2 +- .../sleuthkit/autopsy/timeline/filters/AbstractFilter.java | 4 ++++ .../sleuthkit/autopsy/timeline/filters/CompoundFilter.java | 6 +++++- .../autopsy/timeline/filters/DescriptionFilter.java | 3 +-- Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java | 2 +- .../autopsy/timeline/ui/filtering/FilterSetPanel.java | 2 +- 7 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java index c2a03545a6..c2eb36b7f6 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java @@ -622,4 +622,9 @@ public class EventsRepository { } } } + + public boolean areFiltersEquivalent(RootFilter f1, RootFilter f2) { + return SQLHelper.getSQLWhere(f1).equals(SQLHelper.getSQLWhere(f2)); + + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java b/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java index 2317e6344f..4c827c401d 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java @@ -58,7 +58,7 @@ import org.sleuthkit.datamodel.TskData; * Static helper methods for converting between java data model objects and * sqlite queries. */ -public class SQLHelper { +class SQLHelper { static String useHashHitTablesHelper(RootFilter filter) { HashHitsFilter hashHitFilter = filter.getHashHitsFilter(); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java index d198f6c5a9..bc9675cca2 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java @@ -63,4 +63,8 @@ public abstract class AbstractFilter implements Filter { public String getStringCheckBox() { return "[" + (isSelected() ? "x" : " ") + "]"; // NON-NLS } + + final boolean isActive() { + return isSelected() && (isDisabled() == false); + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java index 4b5d926727..c1964b7087 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java @@ -84,7 +84,11 @@ public abstract class CompoundFilter extends Abstr return false; } for (int i = 0; i < oneFilter.getSubFilters().size(); i++) { - if (oneFilter.getSubFilters().get(i).equals(otherFilter.getSubFilters().get(i)) == false) { + final SubFilterType subFilter = oneFilter.getSubFilters().get(i); + final SubFilterType otherSubFilter = otherFilter.getSubFilters().get(i); + if (subFilter.equals(otherSubFilter) == false + || subFilter.isDisabled() != otherSubFilter.isDisabled() + || subFilter.isSelected() != otherSubFilter.isSelected()) { return false; } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java index 4f70f4b73a..bf8f765b6d 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java @@ -113,7 +113,6 @@ public class DescriptionFilter extends AbstractFilter { if (this.filterMode != other.filterMode) { return false; } - return isSelected() == other.isSelected() - && isDisabled() == other.isDisabled(); + return true; } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java index d9d5a0bf38..e4521c5d47 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java @@ -80,5 +80,5 @@ public interface Filter { boolean isDisabled(); - + } 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 d920906c9d..590f6e83f1 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java @@ -177,7 +177,7 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { ObservableList subFilters = ((RootFilter) filterTreeTable.getRoot().getValue()).getSubFilters(); for (Filter f : c.getAddedSubList()) { if (subFilters.contains(f) == false) { - subFilters.addAll(c.getAddedSubList()); + subFilters.addAll(f); } } From 0b38c44840fb599a2981ccf57806a26fb3cb7c99 Mon Sep 17 00:00:00 2001 From: jmillman Date: Fri, 25 Sep 2015 16:39:19 -0400 Subject: [PATCH 20/29] quick-hide/filter WIP 6 filters work, tree / subnodes / hiding sync in progress --- .../datamodel/FilteredEventsModel.java | 8 +- .../timeline/filters/DescriptionFilter.java | 4 +- .../filters/DescriptionsExclusionFilter.java | 78 +++++++++++++++++++ .../autopsy/timeline/filters/RootFilter.java | 36 +++++++-- .../timeline/ui/VisualizationPanel.java | 2 +- .../ui/detailview/AbstractDetailViewNode.java | 22 +++++- .../ui/detailview/DetailViewNode.java | 19 ++--- .../ui/detailview/DetailViewPane.java | 16 ++-- .../ui/detailview/EventDetailChart.java | 71 +++++++++++------ .../tree/EventDescriptionTreeItem.java | 10 ++- .../ui/detailview/tree/EventTypeTreeItem.java | 5 +- .../timeline/ui/detailview/tree/NavPanel.java | 13 ++-- .../ui/detailview/tree/NavTreeItem.java | 3 +- .../timeline/ui/detailview/tree/RootItem.java | 3 +- .../ui/filtering/FilterCheckBoxCell.java | 15 ---- .../timeline/ui/filtering/FilterSetPanel.java | 33 +++++--- .../timeline/ui/filtering/FilterTreeItem.java | 1 + 17 files changed, 240 insertions(+), 99 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionsExclusionFilter.java diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java index b857588199..26cbc1c8a3 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java @@ -47,6 +47,7 @@ import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent; import org.sleuthkit.autopsy.timeline.events.TagsUpdatedEvent; import org.sleuthkit.autopsy.timeline.filters.DataSourceFilter; import org.sleuthkit.autopsy.timeline.filters.DataSourcesFilter; +import org.sleuthkit.autopsy.timeline.filters.DescriptionsExclusionFilter; import org.sleuthkit.autopsy.timeline.filters.Filter; import org.sleuthkit.autopsy.timeline.filters.HashHitsFilter; import org.sleuthkit.autopsy.timeline.filters.HashSetFilter; @@ -144,6 +145,7 @@ public final class FilteredEventsModel { }); requestedFilter.set(getDefaultFilter()); + //TODO: use bindings to keep these in sync? -jm requestedZoomParamters.addListener((Observable observable) -> { final ZoomParams zoomParams = requestedZoomParamters.get(); @@ -154,7 +156,7 @@ public final class FilteredEventsModel { || zoomParams.getTimeRange().equals(requestedTimeRange.get()) == false) { requestedTypeZoom.set(zoomParams.getTypeZoomLevel()); - requestedFilter.set(zoomParams.getFilter().copyOf()); + requestedFilter.set(zoomParams.getFilter()); requestedTimeRange.set(zoomParams.getTimeRange()); requestedLOD.set(zoomParams.getDescriptionLOD()); } @@ -228,7 +230,7 @@ public final class FilteredEventsModel { tagNameFilter.setSelected(Boolean.TRUE); tagsFilter.addSubFilter(tagNameFilter); }); - return new RootFilter(new HideKnownFilter(), tagsFilter, hashHitsFilter, new TextFilter(), new TypeFilter(RootEventType.getInstance()), dataSourcesFilter, Collections.emptySet()); + return new RootFilter(new HideKnownFilter(), tagsFilter, hashHitsFilter, new TextFilter(), new TypeFilter(RootEventType.getInstance()), dataSourcesFilter, new DescriptionsExclusionFilter(), Collections.emptySet()); } public Interval getBoundingEventsInterval() { @@ -327,7 +329,7 @@ public final class FilteredEventsModel { * range and pass the requested filter, using the given aggregation * to control the grouping of events */ - public List getAggregatedEvents() { + public List getEventClusters() { final Interval range; final RootFilter filter; final EventTypeZoomLevel zoom; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java index bf8f765b6d..7f1abd513f 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java @@ -48,12 +48,12 @@ public class DescriptionFilter extends AbstractFilter { @Override public String getDisplayName() { - return getFilterMode().getDisplayName() + " " + getDescription(); + return getDescriptionLoD().getDisplayName() + ": " + getDescription(); } @Override public String getHTMLReportString() { - return getDescriptionLoD().getDisplayName() + " " + getDisplayName() + " = " + getDescription(); + return getDisplayName() + getStringCheckBox(); } /** diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionsExclusionFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionsExclusionFilter.java new file mode 100644 index 0000000000..14e6c24aa4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionsExclusionFilter.java @@ -0,0 +1,78 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.timeline.filters; + +import java.util.Comparator; +import javafx.beans.binding.Bindings; +import org.openide.util.NbBundle; +import static org.sleuthkit.autopsy.timeline.filters.CompoundFilter.areSubFiltersEqual; + +/** + * + */ +public class DescriptionsExclusionFilter extends IntersectionFilter { + + @Override + @NbBundle.Messages("descriptionsExclusionFilter.displayName.text=Exclude Descriptions") + public String getDisplayName() { + return Bundle.descriptionsExclusionFilter_displayName_text(); + } + + public DescriptionsExclusionFilter() { + getDisabledProperty().bind(Bindings.size(getSubFilters()).lessThan(1)); + setSelected(false); + } + + @Override + public DescriptionsExclusionFilter copyOf() { + DescriptionsExclusionFilter filterCopy = new DescriptionsExclusionFilter(); + filterCopy.setSelected(isSelected()); + //add a copy of each subfilter + this.getSubFilters().forEach((DescriptionFilter t) -> { + filterCopy.addSubFilter(t.copyOf()); + }); + return filterCopy; + } + + @Override + public String getHTMLReportString() { + //move this logic into SaveSnapshot + String string = getDisplayName() + getStringCheckBox(); + if (getSubFilters().isEmpty() == false) { + string = string + " : " + super.getHTMLReportString(); + } + return string; + } + + @Override + public int hashCode() { + return 7; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final DescriptionsExclusionFilter other = (DescriptionsExclusionFilter) obj; + + if (isSelected() != other.isSelected()) { + return false; + } + + return areSubFiltersEqual(this, other); + } + + public void addSubFilter(DescriptionFilter hashSetFilter) { + if (getSubFilters().contains(hashSetFilter) == false) { + getSubFilters().add(hashSetFilter); + getSubFilters().sort(Comparator.comparing(DescriptionFilter::getDisplayName)); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java index 4c8872d91d..76006f8c68 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java @@ -34,6 +34,11 @@ public class RootFilter extends IntersectionFilter { private final TextFilter textFilter; private final TypeFilter typeFilter; private final DataSourcesFilter dataSourcesFilter; + private final DescriptionsExclusionFilter descriptionsExclusionFilter; + + public DescriptionsExclusionFilter getDescriptionsExclusionfilter() { + return descriptionsExclusionFilter; + } public DataSourcesFilter getDataSourcesFilter() { return dataSourcesFilter; @@ -47,17 +52,25 @@ public class RootFilter extends IntersectionFilter { return hashFilter; } - public RootFilter(HideKnownFilter knownFilter, TagsFilter tagsFilter, HashHitsFilter hashFilter, TextFilter textFilter, TypeFilter typeFilter, DataSourcesFilter dataSourceFilter, Set annonymousSubFilters) { - super(FXCollections.observableArrayList(knownFilter, tagsFilter, hashFilter, textFilter, dataSourceFilter, typeFilter)); - getSubFilters().addAll(annonymousSubFilters); - setSelected(Boolean.TRUE); - setDisabled(false); + public RootFilter(HideKnownFilter knownFilter, TagsFilter tagsFilter, HashHitsFilter hashFilter, TextFilter textFilter, TypeFilter typeFilter, DataSourcesFilter dataSourceFilter, DescriptionsExclusionFilter descriptionsExclusionFilter, Set annonymousSubFilters) { + super(FXCollections.observableArrayList( + textFilter, + knownFilter, + dataSourceFilter, tagsFilter, + hashFilter, + typeFilter, + descriptionsExclusionFilter + )); this.knownFilter = knownFilter; this.tagsFilter = tagsFilter; this.hashFilter = hashFilter; this.textFilter = textFilter; this.typeFilter = typeFilter; this.dataSourcesFilter = dataSourceFilter; + this.descriptionsExclusionFilter = descriptionsExclusionFilter; + getSubFilters().addAll(annonymousSubFilters); + setSelected(Boolean.TRUE); + setDisabled(false); } @Override @@ -69,11 +82,20 @@ public class RootFilter extends IntersectionFilter { || subFilter.equals(hashFilter) || subFilter.equals(typeFilter) || subFilter.equals(textFilter) - || subFilter.equals(dataSourcesFilter))) + || subFilter.equals(dataSourcesFilter) + || subFilter.equals(descriptionsExclusionFilter))) .map(Filter::copyOf) .collect(Collectors.toSet()); - RootFilter filter = new RootFilter(knownFilter.copyOf(), tagsFilter.copyOf(), hashFilter.copyOf(), textFilter.copyOf(), typeFilter.copyOf(), dataSourcesFilter.copyOf(), annonymousSubFilters); + RootFilter filter = new RootFilter( + knownFilter.copyOf(), + tagsFilter.copyOf(), + hashFilter.copyOf(), + textFilter.copyOf(), + typeFilter.copyOf(), + dataSourcesFilter.copyOf(), + descriptionsExclusionFilter.copyOf(), + annonymousSubFilters); filter.setSelected(isSelected()); filter.setDisabled(isDisabled()); return filter; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java index 94907ccaa9..6aca0b6b6c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java @@ -375,7 +375,7 @@ public class VisualizationPanel extends BorderPane implements TimeLineView { visualization.setController(controller); notificationPane.setContent(visualization); if (visualization instanceof DetailViewPane) { - navPanel.setChart((DetailViewPane) visualization); + navPanel.setDetailViewPane((DetailViewPane) visualization); } visualization.hasEvents.addListener((ObservableValue observable, Boolean oldValue, Boolean newValue) -> { if (newValue == false) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java index b079cb3698..4a5d6f327c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AbstractDetailViewNode.java @@ -29,6 +29,7 @@ import java.util.logging.Level; import java.util.stream.Collectors; import javafx.application.Platform; import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; @@ -80,7 +81,7 @@ import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel; import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; import org.sleuthkit.datamodel.SleuthkitCase; -public abstract class AbstractDetailViewNode< T extends EventBundle, S extends AbstractDetailViewNode> extends StackPane implements DetailViewNode> { +public abstract class AbstractDetailViewNode< T extends EventBundle, S extends AbstractDetailViewNode> extends StackPane implements DetailViewNode { static final Image HASH_PIN = new Image("/org/sleuthkit/autopsy/images/hashset_hits.png"); static final Image PLUS = new Image("/org/sleuthkit/autopsy/timeline/images/plus-button.png"); // NON-NLS @@ -247,12 +248,17 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A @Override @SuppressWarnings("unchecked") - public List getSubNodes() { - return subNodePane.getChildrenUnmodifiable().stream() + public List getSubBundleNodes() { + return subNodePane.getChildren().stream() .map(t -> (S) t) .collect(Collectors.toList()); } + @Override + public ObservableList getSubNodes() { + return subNodePane.getChildren(); + } + /** * apply the 'effect' to visually indicate selection * @@ -373,7 +379,13 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A * @param expand */ private synchronized void loadSubBundles(DescriptionLOD.RelativeDetail relativeDetail) { + List subBundleNodes = getSubBundleNodes(); + chart.getEventBundles().removeIf(bundle -> + subBundleNodes.stream().anyMatch(subNode -> + bundle.equals(subNode.getEventBundle())) + ); subNodePane.getChildren().clear(); + if (descLOD.get().withRelativeDetail(relativeDetail) == getEventBundle().getDescriptionLOD()) { descLOD.set(getEventBundle().getDescriptionLOD()); showSpans(true); @@ -408,6 +420,9 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A return Collections.emptyList(); } bundles = loadBundles(); + Platform.runLater(() -> { + chart.getEventBundles().addAll(bundles); + }); next = loadedDescriptionLoD.withRelativeDetail(relativeDetail); } while (bundles.size() == 1 && nonNull(next)); @@ -434,6 +449,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A descLOD.set(loadedDescriptionLoD); //assign subNodes and request chart layout subNodePane.getChildren().setAll(subBundleNodes); + chart.setRequiresLayout(true); chart.requestChartLayout(); } catch (InterruptedException | ExecutionException ex) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java index 5ae2ab2e0b..ccb178e8c7 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java @@ -8,6 +8,7 @@ package org.sleuthkit.autopsy.timeline.ui.detailview; import java.util.Comparator; import java.util.List; import java.util.Set; +import javafx.scene.Node; import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; @@ -16,15 +17,17 @@ import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; */ public interface DetailViewNode> { - public void setDescriptionVisibility(DescriptionVisibility get); + void setDescriptionVisibility(DescriptionVisibility get); - public List getSubNodes(); + List getSubBundleNodes(); - public void setSpanWidths(List spanWidths); + List getSubNodes(); - public void setDescriptionWidth(double max); + void setSpanWidths(List spanWidths); - public EventBundle getEventBundle(); + void setDescriptionWidth(double max); + + EventBundle getEventBundle(); /** * apply the 'effect' to visually indicate highlighted nodes @@ -33,7 +36,7 @@ public interface DetailViewNode> { */ void applyHighlightEffect(boolean applied); - public void applySelectionEffect(boolean applied); + void applySelectionEffect(boolean applied); default String getDescription() { return getEventBundle().getDescription(); @@ -47,7 +50,7 @@ public interface DetailViewNode> { return getEventBundle().getEventIDs(); } - default public long getStartMillis() { + default long getStartMillis() { return getEventBundle().getStartMillis(); } @@ -55,8 +58,6 @@ public interface DetailViewNode> { return getEventBundle().getEndMillis(); } - - static class StartTimeComparator implements Comparator> { @Override diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java index 275f091e6e..7850c22c65 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.timeline.ui.detailview; import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javafx.application.Platform; @@ -117,12 +118,12 @@ public class DetailViewPane extends AbstractVisualization aggregatedEvents = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); + private final ObservableList eventClusters = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); private final ObservableList> highlightedNodes = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); - public ObservableList getAggregatedEvents() { - return aggregatedEvents; + public ObservableList getEventBundles() { + return chart.getEventBundles(); } public DetailViewPane(Pane partPane, Pane contextPane, Region spacer) { @@ -313,7 +314,6 @@ public class DetailViewPane extends AbstractVisualization { if (isCancelled()) { @@ -325,9 +325,11 @@ public class DetailViewPane extends AbstractVisualization eventClusters = filteredEvents.getEventClusters(); + + final int size = eventClusters.size(); int i = 0; - for (final EventCluster e : aggregatedEvents) { + for (final EventCluster e : eventClusters) { if (isCancelled()) { break; } @@ -346,7 +348,7 @@ public class DetailViewPane extends AbstractVisualization impl * by allowing a single translation of this group. */ private final Group nodeGroup = new Group(); + ObservableList bundles = FXCollections.observableArrayList(); + ObservableList getEventBundles() { + return bundles; + } /** * map from event to node */ @@ -414,6 +418,7 @@ public final class EventDetailChart extends XYChart impl @Override protected synchronized void dataItemAdded(Series series, int i, Data data) { final EventCluster eventCluster = data.getYValue(); + if (alternateLayout.get()) { EventStripe eventStripe = stripeDescMap.merge(ImmutablePair.of(eventCluster.getEventType(), eventCluster.getDescription()), new EventStripe(eventCluster), @@ -425,10 +430,12 @@ public final class EventDetailChart extends XYChart impl return EventStripe.merge(u, v); } ); + bundles.add(eventStripe); EventStripeNode clusterNode = new EventStripeNode(eventStripe, null, EventDetailChart.this); stripeNodeMap.put(eventStripe, clusterNode); nodeGroup.getChildren().add(clusterNode); } else { + bundles.add(eventCluster); clusterNodeMap.computeIfAbsent(eventCluster, (EventCluster t) -> { EventClusterNode eventNode = new EventClusterNode(eventCluster, null, EventDetailChart.this); eventNode.setLayoutX(getXAxis().getDisplayPosition(new DateTime(eventCluster.getSpan().getStartMillis()))); @@ -447,12 +454,14 @@ public final class EventDetailChart extends XYChart impl @Override protected synchronized void dataItemRemoved(Data data, Series series) { - EventCluster aggEvent = data.getYValue(); - Node removedNode = clusterNodeMap.remove(aggEvent); + EventCluster removedCluster = data.getYValue(); + Node removedNode = clusterNodeMap.remove(removedCluster); nodeGroup.getChildren().remove(removedNode); + bundles.remove(removedCluster); - EventStripe removedCluster = stripeDescMap.remove(ImmutablePair.of(aggEvent.getEventType(), aggEvent.getDescription())); - removedNode = stripeNodeMap.remove(removedCluster); + EventStripe removedStripe = stripeDescMap.remove(ImmutablePair.of(removedCluster.getEventType(), removedCluster.getDescription())); + bundles.removeAll(removedStripe); + removedNode = stripeNodeMap.remove(removedStripe); nodeGroup.getChildren().remove(removedNode); data.setNode(null); @@ -485,7 +494,7 @@ public final class EventDetailChart extends XYChart impl setCursor(Cursor.WAIT); maxY.set(0.0); - Map>> shownPartition; + Map>> hiddenPartition; Map> nodeMap = (alternateLayout.get()) ? stripeNodeMap : clusterNodeMap; if (bandByType.get()) { double minY = 0; @@ -498,21 +507,19 @@ public final class EventDetailChart extends XYChart impl .map(stripeDescMap::get) .distinct(); - shownPartition = bundleStream + hiddenPartition = bundleStream .map(nodeMap::get) - .sorted(Comparator.comparing(AbstractDetailViewNode::getStartMillis)) .collect(Collectors.partitioningBy(node -> getController().getQuickHideMasks().stream() .anyMatch(mask -> mask.getDescription().equals(node.getDescription())))); - layoutNodesHelper(shownPartition.get(false), shownPartition.get(true), minY); + layoutNodesHelper(hiddenPartition.get(true), hiddenPartition.get(false), minY, nodeGroup.getChildren(), 0); minY = maxY.get(); } } else { - shownPartition = nodeMap.values().stream() - .sorted(Comparator.comparing(AbstractDetailViewNode::getStartMillis)) + hiddenPartition = nodeMap.values().stream() .collect(Collectors.partitioningBy(node -> getController().getQuickHideMasks().stream() .anyMatch(mask -> mask.getDescription().equals(node.getDescription())))); - layoutNodesHelper(shownPartition.get(true), shownPartition.get(false), 0); + layoutNodesHelper(hiddenPartition.get(true), hiddenPartition.get(false), 0, nodeGroup.getChildren(), 0); } setCursor(null); requiresLayout = false; @@ -520,19 +527,30 @@ public final class EventDetailChart extends XYChart impl layoutProjectionMap(); } - private void layoutNodesHelper(List> hiddenNodes, List> shownNodes, double minY) { + /** + * + * @param hiddenNodes the value of hiddenNodes + * @param shownNodes the value of shownNodes + * @param minY the value of minY + * @param children the value of children + * @param xOffset the value of xOffset + * + * @return the double + */ + private double layoutNodesHelper(List> hiddenNodes, List> shownNodes, double minY, List children, final double xOffset) { + hiddenNodes.forEach((AbstractDetailViewNode t) -> { - nodeGroup.getChildren().remove(t); + children.remove(t); }); shownNodes.forEach((AbstractDetailViewNode t) -> { - if (false == nodeGroup.getChildren().contains(t)) { - nodeGroup.getChildren().add(t); + if (false == children.contains(t)) { + children.add(t); } }); shownNodes.sort(Comparator.comparing(DetailViewNode::getStartMillis)); - layoutNodes(shownNodes, minY, 0); + return layoutNodes(shownNodes, minY, xOffset); } @Override @@ -575,7 +593,7 @@ public final class EventDetailChart extends XYChart impl public static Stream> flatten(DetailViewNode node) { return Stream.concat( Stream.of(node), - node.getSubNodes().stream().flatMap(EventDetailChart::flatten)); + node.getSubBundleNodes().stream().flatMap(EventDetailChart::flatten)); } Iterable> getAllNodes() { @@ -606,7 +624,7 @@ public final class EventDetailChart extends XYChart impl * @param nodes * @param minY */ - private synchronized double layoutNodes(final Collection> nodes, final double minY, final double xOffset) { + private synchronized double layoutNodes(final Collection> nodes, final double minY, final double xOffset) { //hash map from y value to right most occupied x value. This tells you for a given 'row' what is the first avaialable slot Map maxXatY = new HashMap<>(); double localMax = minY; @@ -620,11 +638,18 @@ public final class EventDetailChart extends XYChart impl double layoutNodesResultHeight = 0; double span = 0; - List> subNodes = node.getSubNodes(); - if (subNodes.isEmpty() == false) { - subNodes.sort(new DetailViewNode.StartTimeComparator()); - layoutNodesResultHeight = layoutNodes(subNodes, 0, rawDisplayPosition); - } + @SuppressWarnings("unchecked") + List> subNodes = (List>) node.getSubBundleNodes(); + + Map>> hiddenPartition = subNodes.stream() + .collect(Collectors.partitioningBy(testNode -> getController().getQuickHideMasks().stream() + .anyMatch(mask -> mask.getDescription().equals(testNode.getDescription())))); + + layoutNodesResultHeight = layoutNodesHelper(hiddenPartition.get(true), hiddenPartition.get(false), minY, node.getSubNodes(), rawDisplayPosition); +// if (subNodes.isEmpty() == false) { +// subNodes.sort(new DetailViewNode.StartTimeComparator()); +// layoutNodes(subNodes, 0, rawDisplayPosition); +// } if (alternateLayout.get() == false) { double endX = getXAxis().getDisplayPosition(new DateTime(node.getEndMillis())) - xOffset; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java index 69fdd1fe43..393fd62705 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.timeline.ui.detailview.tree; import java.util.Comparator; import javafx.scene.control.TreeItem; -import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; /** @@ -28,7 +27,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; */ class EventDescriptionTreeItem extends NavTreeItem { - public EventDescriptionTreeItem(EventCluster g) { + EventDescriptionTreeItem(EventBundle g) { setValue(new NavTreeNode(g.getEventType().getBaseType(), g.getDescription(), g.getEventIDs().size())); } @@ -38,7 +37,7 @@ class EventDescriptionTreeItem extends NavTreeItem { } @Override - public void insert(EventCluster g) { + public void insert(EventBundle g) { NavTreeNode value = getValue(); if ((value.getType().getBaseType().equals(g.getEventType().getBaseType()) == false) || ((value.getDescription().equals(g.getDescription()) == false))) { throw new IllegalArgumentException(); @@ -54,6 +53,11 @@ class EventDescriptionTreeItem extends NavTreeItem { @Override public TreeItem findTreeItemForEvent(EventBundle t) { +// if (getValue().getType().getBaseType() == t.getEventType().getBaseType() && t.getDescription().startsWith(getValue().getDescription())) { +// TreeItem treeItem = new TreeItem<>(new NavTreeNode(t.getEventType(), t.getDescription(), t.getEventIDs().size())); +// getChildren().add(treeItem); +// return treeItem; +// } if (getValue().getType().getBaseType() == t.getEventType().getBaseType() && getValue().getDescription().equals(t.getDescription())) { return this; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java index fb2947bc42..8cfe989aee 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java @@ -24,7 +24,6 @@ import java.util.concurrent.ConcurrentHashMap; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.scene.control.TreeItem; -import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; class EventTypeTreeItem extends NavTreeItem { @@ -36,7 +35,7 @@ class EventTypeTreeItem extends NavTreeItem { private final Comparator> comparator = TreeComparator.Description; - EventTypeTreeItem(EventCluster g) { + EventTypeTreeItem(EventBundle g) { setValue(new NavTreeNode(g.getEventType().getBaseType(), g.getEventType().getBaseType().getDisplayName(), 0)); } @@ -53,7 +52,7 @@ class EventTypeTreeItem extends NavTreeItem { * @param tree True if it is part of a tree (versus a list) */ @Override - public void insert(EventCluster g) { + public void insert(EventBundle g) { EventDescriptionTreeItem treeItem = childMap.get(g.getDescription()); if (treeItem == null) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java index cf9d424ecb..1b87b31b8a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java @@ -23,7 +23,6 @@ import java.util.Arrays; import java.util.Comparator; import javafx.application.Platform; import javafx.beans.Observable; -import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; @@ -42,7 +41,7 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.TimeLineView; -import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; +import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewNode; import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane; @@ -68,11 +67,11 @@ public class NavPanel extends BorderPane implements TimeLineView { FXMLConstructor.construct(this, "NavPanel.fxml"); // NON-NLS } - public void setChart(DetailViewPane detailViewPane) { + public void setDetailViewPane(DetailViewPane detailViewPane) { this.detailViewPane = detailViewPane; detailViewPane.setSelectionModel(eventsTree.getSelectionModel()); setRoot(); - detailViewPane.getAggregatedEvents().addListener((Observable observable) -> { + detailViewPane.getEventBundles().addListener((Observable observable) -> { setRoot(); }); detailViewPane.getSelectedNodes().addListener((Observable observable) -> { @@ -86,10 +85,8 @@ public class NavPanel extends BorderPane implements TimeLineView { private void setRoot() { RootItem root = new RootItem(); - final ObservableList aggregatedEvents = detailViewPane.getAggregatedEvents(); - - for (EventCluster agg : aggregatedEvents) { - root.insert(agg); + for (EventBundle bundle : detailViewPane.getEventBundles()) { + root.insert(bundle); } Platform.runLater(() -> { eventsTree.setRoot(root); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java index cfd582433e..2c0be29472 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.timeline.ui.detailview.tree; import java.util.Comparator; import javafx.scene.control.TreeItem; import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; -import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; /** * A node in the nav tree. Manages inserts and resorts. Has parents and @@ -31,7 +30,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; */ abstract class NavTreeItem extends TreeItem { - abstract void insert(EventCluster g); + abstract void insert(EventBundle g); abstract int getCount(); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java index 818a053823..69fe72d83e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java @@ -23,7 +23,6 @@ import java.util.HashMap; import java.util.Map; import javafx.application.Platform; import javafx.scene.control.TreeItem; -import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; @@ -56,7 +55,7 @@ class RootItem extends NavTreeItem { * @param g Group to add */ @Override - public void insert(EventCluster g) { + public void insert(EventBundle g) { EventTypeTreeItem treeItem = childMap.get(g.getEventType().getBaseType()); if (treeItem == null) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterCheckBoxCell.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterCheckBoxCell.java index c3a75f0619..795e4b66b1 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterCheckBoxCell.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterCheckBoxCell.java @@ -38,21 +38,6 @@ class FilterCheckBoxCell extends TreeTableCell { FilterCheckBoxCell(TimeLineController controller) { this.controller = controller; -// controller.viewModeProperty().addListener((observable, oldVisualizationMode, newVisualizationMode) -> { -// AbstractFilter filter = getItem(); -// switch (newVisualizationMode) { -// case COUNTS: -// if (filter instanceof DescriptionFilter) { -// filter.setDisabled(true); -// } -// break; -// case DETAIL: -// if (filter instanceof DescriptionFilter) { -// filter.setDisabled(false); -// } -// break; -// } -// }); } @Override 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 590f6e83f1..b0ac96b323 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java @@ -22,7 +22,6 @@ import javafx.application.Platform; import javafx.beans.Observable; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; -import javafx.collections.ObservableList; import javafx.collections.ObservableMap; import javafx.event.ActionEvent; import javafx.fxml.FXML; @@ -48,6 +47,7 @@ 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.DescriptionsExclusionFilter; import org.sleuthkit.autopsy.timeline.filters.Filter; import org.sleuthkit.autopsy.timeline.filters.RootFilter; import org.sleuthkit.autopsy.timeline.filters.TypeFilter; @@ -162,7 +162,7 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { public FilterSetPanel() { FXMLConstructor.construct(this, "FilterSetPanel.fxml"); // NON-NLS - expansionMap.put(new TypeFilter(RootEventType.getInstance()).getDisplayName(), Boolean.TRUE); + expansionMap.put(new TypeFilter(RootEventType.getInstance()).getDisplayName(), true); } @Override @@ -174,17 +174,16 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { this.setModel(timeLineController.getEventsModel()); controller.getQuickHideMasks().addListener((ListChangeListener.Change c) -> { while (c.next()) { - ObservableList subFilters = ((RootFilter) filterTreeTable.getRoot().getValue()).getSubFilters(); - for (Filter f : c.getAddedSubList()) { - if (subFilters.contains(f) == false) { - subFilters.addAll(f); - } - } + DescriptionsExclusionFilter descriptionExclusionFilter = ((RootFilter) filterTreeTable.getRoot().getValue()).getDescriptionsExclusionfilter(); -// ((RootFilter) filterTreeTable.getRoot().getValue()).getSubFilters().forEach(new Consumer) + for (DescriptionFilter filter : c.getAddedSubList()) { + descriptionExclusionFilter.setSelected(true); + descriptionExclusionFilter.addSubFilter(filter); + } } }); - controller.viewModeProperty().addListener((Observable observable) -> { + + controller.viewModeProperty().addListener(observable -> { applyFilters(); }); } @@ -192,6 +191,15 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { @Override public void setModel(FilteredEventsModel filteredEvents) { this.filteredEvents = filteredEvents; + this.filteredEvents.eventTypeZoomProperty().addListener((Observable observable) -> { + applyFilters(); + }); + this.filteredEvents.descriptionLODProperty().addListener((Observable observable) -> { + applyFilters(); + }); + this.filteredEvents.timeRangeProperty().addListener((Observable observable) -> { + applyFilters(); + }); this.filteredEvents.filterProperty().addListener((Observable o) -> { refresh(); }); @@ -219,7 +227,10 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { } private void applyFilters() { - controller.pushFilters((RootFilter) filterTreeTable.getRoot().getValue()); + Platform.runLater(() -> { + controller.pushFilters((RootFilter) filterTreeTable.getRoot().getValue()); + }); + } private static final Image TICK = new Image("org/sleuthkit/autopsy/timeline/images/tick.png"); 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 2a08f13145..adc57dc646 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterTreeItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterTreeItem.java @@ -49,6 +49,7 @@ final public class FilterTreeItem extends TreeItem { compoundFilter.getSubFilters().addListener((ListChangeListener.Change c) -> { while (c.next()) { for (Filter subfFilter : c.getAddedSubList()) { + setExpanded(true); getChildren().add(new FilterTreeItem(subfFilter, expansionMap)); } } From 19b6a3ee03602bb0c907b7c5f92db67fb72594b2 Mon Sep 17 00:00:00 2001 From: jmillman Date: Fri, 25 Sep 2015 17:32:59 -0400 Subject: [PATCH 21/29] BROKEN! show subnodes in event tree BROKEN! --- .../tree/EventDescriptionTreeItem.java | 40 +++++++++++++++---- .../ui/detailview/tree/EventTypeTreeItem.java | 4 +- .../ui/detailview/tree/NavTreeNode.java | 21 ++++++---- .../timeline/zooming/DescriptionLOD.java | 11 +++++ 4 files changed, 59 insertions(+), 17 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java index 393fd62705..6a3ca264cf 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java @@ -19,16 +19,26 @@ package org.sleuthkit.autopsy.timeline.ui.detailview.tree; import java.util.Comparator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import javafx.scene.control.TreeItem; import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; /** * */ class EventDescriptionTreeItem extends NavTreeItem { + /** + * maps a description to the child item of this item with that description + */ + private final Map childMap = new ConcurrentHashMap<>(); + private final DescriptionLOD descriptionLoD; + EventDescriptionTreeItem(EventBundle g) { - setValue(new NavTreeNode(g.getEventType().getBaseType(), g.getDescription(), g.getEventIDs().size())); + descriptionLoD = g.getDescriptionLOD(); + setValue(new NavTreeNode(g.getEventType().getBaseType(), g.getDescription(), g.getDescriptionLOD(), g.getEventIDs().size())); } @Override @@ -39,11 +49,29 @@ class EventDescriptionTreeItem extends NavTreeItem { @Override public void insert(EventBundle g) { NavTreeNode value = getValue(); - if ((value.getType().getBaseType().equals(g.getEventType().getBaseType()) == false) || ((value.getDescription().equals(g.getDescription()) == false))) { + if (value.getType().getBaseType().equals(g.getEventType().getBaseType()) + && g.getDescription().startsWith(value.getDescription())) { throw new IllegalArgumentException(); } - setValue(new NavTreeNode(value.getType().getBaseType(), value.getDescription(), value.getCount() + g.getEventIDs().size())); + switch (descriptionLoD.getDetailLevelRelativeTo(g.getDescriptionLOD())) { + case LESS: + EventDescriptionTreeItem get = childMap.get(g.getDescription()); + if (get == null) { + EventDescriptionTreeItem eventDescriptionTreeItem = new EventDescriptionTreeItem(g); + childMap.put(g.getDescription(), eventDescriptionTreeItem); + getChildren().add(eventDescriptionTreeItem); + } else { + get.insert(g); + } + break; + case EQUAL: + setValue(new NavTreeNode(value.getType().getBaseType(), value.getDescription(), value.getDescriptionLoD(), value.getCount() + g.getEventIDs().size())); + break; + case MORE: + throw new IllegalArgumentException(); + } + } @Override @@ -53,11 +81,7 @@ class EventDescriptionTreeItem extends NavTreeItem { @Override public TreeItem findTreeItemForEvent(EventBundle t) { -// if (getValue().getType().getBaseType() == t.getEventType().getBaseType() && t.getDescription().startsWith(getValue().getDescription())) { -// TreeItem treeItem = new TreeItem<>(new NavTreeNode(t.getEventType(), t.getDescription(), t.getEventIDs().size())); -// getChildren().add(treeItem); -// return treeItem; -// } + if (getValue().getType().getBaseType() == t.getEventType().getBaseType() && getValue().getDescription().equals(t.getDescription())) { return this; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java index 8cfe989aee..f095d95b78 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java @@ -36,7 +36,7 @@ class EventTypeTreeItem extends NavTreeItem { private final Comparator> comparator = TreeComparator.Description; EventTypeTreeItem(EventBundle g) { - setValue(new NavTreeNode(g.getEventType().getBaseType(), g.getEventType().getBaseType().getDisplayName(), 0)); + setValue(new NavTreeNode(g.getEventType().getBaseType(), g.getEventType().getBaseType().getDisplayName(), g.getDescriptionLOD(), 0)); } @Override @@ -71,7 +71,7 @@ class EventTypeTreeItem extends NavTreeItem { } Platform.runLater(() -> { NavTreeNode value1 = getValue(); - setValue(new NavTreeNode(value1.getType().getBaseType(), value1.getType().getBaseType().getDisplayName(), childMap.values().stream().mapToInt(EventDescriptionTreeItem::getCount).sum())); + setValue(new NavTreeNode(value1.getType().getBaseType(), value1.getType().getBaseType().getDisplayName(),value1.getDescriptionLoD(), childMap.values().stream().mapToInt(EventDescriptionTreeItem::getCount).sum())); }); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeNode.java index 4d193da57b..3b785a9527 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeNode.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.timeline.ui.detailview.tree; import javax.annotation.concurrent.Immutable; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; /** * The data item for the nav tree. Represents a combination of type and @@ -28,12 +29,25 @@ import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; @Immutable public class NavTreeNode { + final private DescriptionLOD descriptionLoD; + final private EventType type; final private String Description; final private int count; + public NavTreeNode(EventType type, String Description, DescriptionLOD descriptionLoD, int count) { + this.type = type; + this.Description = Description; + this.descriptionLoD = descriptionLoD; + this.count = count; + } + + public DescriptionLOD getDescriptionLoD() { + return descriptionLoD; + } + public EventType getType() { return type; } @@ -45,11 +59,4 @@ public class NavTreeNode { public int getCount() { return count; } - - public NavTreeNode(EventType type, String Description, int count) { - this.type = type; - this.Description = Description; - this.count = count; - } - } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/zooming/DescriptionLOD.java b/Core/src/org/sleuthkit/autopsy/timeline/zooming/DescriptionLOD.java index 8990d2ce21..4bb5669f1e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/zooming/DescriptionLOD.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/zooming/DescriptionLOD.java @@ -68,6 +68,17 @@ public enum DescriptionLOD { } } + public RelativeDetail getDetailLevelRelativeTo(DescriptionLOD other) { + int compareTo = this.compareTo(other); + if (compareTo < 0) { + return RelativeDetail.LESS; + } else if (compareTo == 0) { + return RelativeDetail.EQUAL; + } else { + return RelativeDetail.MORE; + } + } + public enum RelativeDetail { EQUAL, From c44febaf93defc0530acb90ea6537a0c05daff70 Mon Sep 17 00:00:00 2001 From: jmillman Date: Mon, 28 Sep 2015 14:59:27 -0400 Subject: [PATCH 22/29] WIP --- .../autopsy/timeline/TimeLineController.java | 1 + .../timeline/datamodel/EventBundle.java | 2 + .../timeline/datamodel/EventCluster.java | 39 +++++++++++++- .../timeline/datamodel/EventStripe.java | 11 ++++ .../filters/DescriptionsExclusionFilter.java | 26 ++++----- .../ui/detailview/AbstractDetailViewNode.java | 34 +++++++----- .../ui/detailview/DetailViewPane.java | 20 +++---- .../ui/detailview/EventClusterNode.java | 3 +- .../ui/detailview/EventDetailChart.java | 53 ++++++++++--------- .../tree/EventDescriptionTreeItem.java | 47 +++++++--------- .../ui/detailview/tree/EventTypeTreeItem.java | 39 +++++--------- .../timeline/ui/detailview/tree/NavPanel.java | 43 ++++++++++----- .../ui/detailview/tree/NavTreeItem.java | 3 +- .../ui/detailview/tree/NavTreeNode.java | 7 ++- .../timeline/ui/detailview/tree/RootItem.java | 36 ++++++++----- 15 files changed, 216 insertions(+), 148 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index 1d5ab70ce2..b175c718df 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -137,6 +137,7 @@ public class TimeLineController { private final Case autoCase; + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private final ObservableList quickHideMasks = FXCollections.observableArrayList(); public ObservableList getQuickHideMasks() { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java index 17d5a01e7b..79e0fac26b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java @@ -6,6 +6,7 @@ package org.sleuthkit.autopsy.timeline.datamodel; import com.google.common.collect.Range; +import java.util.Optional; import java.util.Set; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; @@ -33,4 +34,5 @@ public interface EventBundle { Iterable> getRanges(); + Optional getParentBundle(); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java index 7533c486e1..7f1ed3e813 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java @@ -21,6 +21,8 @@ package org.sleuthkit.autopsy.timeline.datamodel; import com.google.common.collect.Range; import com.google.common.collect.Sets; import java.util.Collections; +import java.util.Objects; +import java.util.Optional; import java.util.Set; import javax.annotation.concurrent.Immutable; import org.joda.time.Interval; @@ -36,6 +38,13 @@ import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; @Immutable public class EventCluster implements EventBundle { + final private EventBundle parent; + + @Override + public Optional getParentBundle() { + return Optional.ofNullable(parent); + } + /** * the smallest time interval containing all the aggregated events */ @@ -73,7 +82,7 @@ public class EventCluster implements EventBundle { */ private final Set hashHits; - public EventCluster(Interval spanningInterval, EventType type, Set eventIDs, Set hashHits, Set tagged, String description, DescriptionLOD lod) { + private EventCluster(Interval spanningInterval, EventType type, Set eventIDs, Set hashHits, Set tagged, String description, DescriptionLOD lod, EventBundle parent) { this.span = spanningInterval; this.type = type; @@ -82,6 +91,11 @@ public class EventCluster implements EventBundle { this.description = description; this.eventIDs = eventIDs; this.lod = lod; + this.parent = parent; + } + + public EventCluster(Interval spanningInterval, EventType type, Set eventIDs, Set hashHits, Set tagged, String description, DescriptionLOD lod) { + this(spanningInterval, type, eventIDs, hashHits, tagged, description, lod, null); } /** @@ -91,30 +105,37 @@ public class EventCluster implements EventBundle { return span; } + @Override public long getStartMillis() { return span.getStartMillis(); } + @Override public long getEndMillis() { return span.getEndMillis(); } + @Override public Set getEventIDs() { return Collections.unmodifiableSet(eventIDs); } + @Override public Set getEventIDsWithHashHits() { return Collections.unmodifiableSet(hashHits); } + @Override public Set getEventIDsWithTags() { return Collections.unmodifiableSet(tagged); } + @Override public String getDescription() { return description; } + @Override public EventType getEventType() { return type; } @@ -162,4 +183,20 @@ public class EventCluster implements EventBundle { return Collections.singletonList(getRange()); } + /** + * return a new EventCluster identical to this one, except with the given + * EventBundle as the parent. + * + * @param parent + * + * @return a new EventCluster identical to this one, except with the given + * EventBundle as the parent. + */ + public EventCluster withParent(EventBundle parent) { + if (Objects.nonNull(this.parent)) { + throw new IllegalStateException("Event Cluster already has a parent!"); + } + + return new EventCluster(span, type, eventIDs, hashHits, tagged, description, lod, parent); + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java index e88e8ff623..4f45a0c46f 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java @@ -13,6 +13,7 @@ import com.google.common.collect.TreeRangeMap; import com.google.common.collect.TreeRangeSet; import java.util.Collections; import java.util.HashSet; +import java.util.Optional; import java.util.Set; import javax.annotation.concurrent.Immutable; import org.python.google.common.base.Objects; @@ -25,6 +26,13 @@ import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; @Immutable public final class EventStripe implements EventBundle { + final private EventBundle parent; + + @Override + public Optional getParentBundle() { + return Optional.ofNullable(parent); + } + private final RangeSet spans = TreeRangeSet.create(); private final RangeMap spanMap = TreeRangeMap.create(); @@ -69,6 +77,7 @@ public final class EventStripe implements EventBundle { eventIDs.addAll(cluster.getEventIDs()); tagged.addAll(cluster.getEventIDsWithTags()); hashHits.addAll(cluster.getEventIDsWithHashHits()); + parent = cluster.getParentBundle().orElse(null); } private EventStripe(EventStripe u, EventStripe v) { @@ -85,6 +94,7 @@ public final class EventStripe implements EventBundle { tagged.addAll(v.getEventIDsWithTags()); hashHits.addAll(u.getEventIDsWithHashHits()); hashHits.addAll(v.getEventIDsWithHashHits()); + parent = u.getParentBundle().orElse(null); } public static EventStripe merge(EventStripe u, EventStripe v) { @@ -93,6 +103,7 @@ public final class EventStripe implements EventBundle { Preconditions.checkArgument(Objects.equal(u.description, v.description)); Preconditions.checkArgument(Objects.equal(u.lod, v.lod)); Preconditions.checkArgument(Objects.equal(u.type, v.type)); + Preconditions.checkArgument(Objects.equal(u.parent, v.parent)); return new EventStripe(u, v); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionsExclusionFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionsExclusionFilter.java index 14e6c24aa4..5206d57ff0 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionsExclusionFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionsExclusionFilter.java @@ -14,18 +14,18 @@ import static org.sleuthkit.autopsy.timeline.filters.CompoundFilter.areSubFilter * */ public class DescriptionsExclusionFilter extends IntersectionFilter { - + @Override @NbBundle.Messages("descriptionsExclusionFilter.displayName.text=Exclude Descriptions") public String getDisplayName() { return Bundle.descriptionsExclusionFilter_displayName_text(); } - + public DescriptionsExclusionFilter() { getDisabledProperty().bind(Bindings.size(getSubFilters()).lessThan(1)); setSelected(false); } - + @Override public DescriptionsExclusionFilter copyOf() { DescriptionsExclusionFilter filterCopy = new DescriptionsExclusionFilter(); @@ -36,7 +36,7 @@ public class DescriptionsExclusionFilter extends IntersectionFilter> loggedTask; - loggedTask = new LoggedTask>( + LoggedTask> loggedTask = new LoggedTask>( NbBundle.getMessage(this.getClass(), "AggregateEventNode.loggedTask.name"), true) { private Collection bundles; private volatile DescriptionLOD loadedDescriptionLoD = getDescLOD().withRelativeDetail(relativeDetail); private DescriptionLOD next = loadedDescriptionLoD; @Override - protected List call() throws Exception { + protected Collection call() throws Exception { do { loadedDescriptionLoD = next; if (loadedDescriptionLoD == getEventBundle().getDescriptionLOD()) { @@ -427,20 +426,26 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A } while (bundles.size() == 1 && nonNull(next)); // return list of AbstractDetailViewNodes representing sub-bundles - return bundles.stream() - .map(AbstractDetailViewNode.this::getNodeForBundle) - .collect(Collectors.toList()); + return bundles; } private Collection loadBundles() { - return makeBundlesFromClusters(eventsModel.getEventClusters(zoomParams.withDescrLOD(loadedDescriptionLoD))); + List eventClusters + = eventsModel.getEventClusters(zoomParams.withDescrLOD(loadedDescriptionLoD)).stream() + .map(cluster -> cluster.withParent(getEventBundle())) + .collect(Collectors.toList()); + + return makeBundlesFromClusters(eventClusters); } @Override protected void succeeded() { chart.setCursor(Cursor.WAIT); try { - List subBundleNodes = get(); + List subBundleNodes = get().stream() + .map(AbstractDetailViewNode.this::getNodeForBundle) + .collect(Collectors.toList()); + if (subBundleNodes.isEmpty()) { showSpans(true); } else { @@ -455,7 +460,9 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A } catch (InterruptedException | ExecutionException ex) { LOGGER.log(Level.SEVERE, "Error loading subnodes", ex); } - chart.setCursor(null); + + chart.setCursor( + null); } }; @@ -487,7 +494,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A case SHOWN: String description = getEventBundle().getDescription(); description = getParentNode() != null - ? " ..." + StringUtils.substringAfter(description, getParentNode().getDescription()) + ? " ... " + StringUtils.substringAfter(description, getParentNode().getDescription()) : description; descrLabel.setText(description); countLabel.setText(((size == 1) ? "" : " (" + size + ")")); // NON-NLS @@ -495,7 +502,8 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A } } - abstract S getNodeForBundle(T bundle); + abstract S + getNodeForBundle(T bundle); /** * event handler used for mouse events on {@link AggregateEventNode}s diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java index 7850c22c65..be04e0c218 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java @@ -62,6 +62,7 @@ import javafx.scene.layout.Pane; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; import javafx.scene.layout.VBox; +import org.controlsfx.control.action.Action; import org.joda.time.DateTime; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.LoggedTask; @@ -77,6 +78,7 @@ import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization; import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane; import org.sleuthkit.autopsy.timeline.ui.detailview.tree.NavTreeNode; import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; /** * FXML Controller class for a {@link EventDetailChart} based implementation of @@ -212,8 +214,8 @@ public class DetailViewPane extends AbstractVisualization { highlightedNodes.clear(); selectedNodes.stream().forEach((tn) -> { - for (DetailViewNode n : chart.getNodes((DetailViewNode t) - -> t.getDescription().equals(tn.getDescription()))) { + for (DetailViewNode n : chart.getNodes((DetailViewNode t) -> + t.getDescription().equals(tn.getDescription()))) { highlightedNodes.add(n); } }); @@ -236,8 +238,8 @@ public class DetailViewPane extends AbstractVisualization { highlightedNodes.clear(); for (TreeItem tn : treeSelectionModel.getSelectedItems()) { - for (DetailViewNode n : chart.getNodes((DetailViewNode t) - -> t.getDescription().equals(tn.getValue().getDescription()))) { + for (DetailViewNode n : chart.getNodes((DetailViewNode t) -> + t.getDescription().equals(tn.getValue().getDescription()))) { highlightedNodes.add(n); } } @@ -348,7 +350,7 @@ public class DetailViewPane extends AbstractVisualization makeBundlesFromClusters(List eventClusters) { + List makeBundlesFromClusters(List eventClusters) { return eventClusters; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java index d870002090..f3836bf2cd 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java @@ -81,6 +81,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter; import org.sleuthkit.autopsy.timeline.ui.TimeLineChart; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; /** * Custom implementation of {@link XYChart} to graph events on a horizontal @@ -512,14 +513,14 @@ public final class EventDetailChart extends XYChart impl .collect(Collectors.partitioningBy(node -> getController().getQuickHideMasks().stream() .anyMatch(mask -> mask.getDescription().equals(node.getDescription())))); - layoutNodesHelper(hiddenPartition.get(true), hiddenPartition.get(false), minY, nodeGroup.getChildren(), 0); + layoutNodesHelper(hiddenPartition.get(true), hiddenPartition.get(false), minY, 0); minY = maxY.get(); } } else { hiddenPartition = nodeMap.values().stream() .collect(Collectors.partitioningBy(node -> getController().getQuickHideMasks().stream() .anyMatch(mask -> mask.getDescription().equals(node.getDescription())))); - layoutNodesHelper(hiddenPartition.get(true), hiddenPartition.get(false), 0, nodeGroup.getChildren(), 0); + layoutNodesHelper(hiddenPartition.get(true), hiddenPartition.get(false), 0, 0); } setCursor(null); requiresLayout = false; @@ -537,16 +538,20 @@ public final class EventDetailChart extends XYChart impl * * @return the double */ - private double layoutNodesHelper(List> hiddenNodes, List> shownNodes, double minY, List children, final double xOffset) { + private double layoutNodesHelper(List> hiddenNodes, List> shownNodes, double minY, final double xOffset) { hiddenNodes.forEach((AbstractDetailViewNode t) -> { - children.remove(t); +// children.remove(t); + t.setVisible(false); + t.setManaged(false); }); shownNodes.forEach((AbstractDetailViewNode t) -> { - if (false == children.contains(t)) { - children.add(t); - } +// if (false == children.contains(t)) { +// children.add(t); +// } + t.setVisible(true); + t.setManaged(true); }); shownNodes.sort(Comparator.comparing(DetailViewNode::getStartMillis)); @@ -640,17 +645,13 @@ public final class EventDetailChart extends XYChart impl double span = 0; @SuppressWarnings("unchecked") List> subNodes = (List>) node.getSubBundleNodes(); + if (subNodes.isEmpty() == false) { + Map>> hiddenPartition = subNodes.stream() + .collect(Collectors.partitioningBy(testNode -> getController().getQuickHideMasks().stream() + .anyMatch(mask -> mask.getDescription().equals(testNode.getDescription())))); - Map>> hiddenPartition = subNodes.stream() - .collect(Collectors.partitioningBy(testNode -> getController().getQuickHideMasks().stream() - .anyMatch(mask -> mask.getDescription().equals(testNode.getDescription())))); - - layoutNodesResultHeight = layoutNodesHelper(hiddenPartition.get(true), hiddenPartition.get(false), minY, node.getSubNodes(), rawDisplayPosition); -// if (subNodes.isEmpty() == false) { -// subNodes.sort(new DetailViewNode.StartTimeComparator()); -// layoutNodes(subNodes, 0, rawDisplayPosition); -// } - + layoutNodesResultHeight = layoutNodesHelper(hiddenPartition.get(true), hiddenPartition.get(false), minY, rawDisplayPosition); + } if (alternateLayout.get() == false) { double endX = getXAxis().getDisplayPosition(new DateTime(node.getEndMillis())) - xOffset; span = endX - startX; @@ -822,32 +823,36 @@ public final class EventDetailChart extends XYChart impl c1.applySelectionEffect(selected); } - class HideBundleAction extends Action { + class HideDescriptionAction extends Action { /** * * @param description the value of description */ - public HideBundleAction(final EventBundle bundle) { + HideDescriptionAction(String description, DescriptionLOD descriptionLoD) { super("Hide"); setGraphic(new ImageView(HIDE)); setEventHandler((ActionEvent t) -> { - DescriptionFilter descriptionFilter = new DescriptionFilter(bundle.getDescriptionLOD(), bundle.getDescription(), DescriptionFilter.FilterMode.EXCLUDE); - getController().getQuickHideMasks().add(descriptionFilter); + getController().getQuickHideMasks().add( + new DescriptionFilter(descriptionLoD, + description, + DescriptionFilter.FilterMode.EXCLUDE)); setRequiresLayout(true); requestChartLayout(); }); } } - class UnhideBundleAction extends Action { + class UnhideDescriptionAction extends Action { - public UnhideBundleAction(String description) { + UnhideDescriptionAction(String description, DescriptionLOD descriptionLoD) { super("Unhide"); setGraphic(new ImageView(SHOW)); setEventHandler((ActionEvent t) -> { - getController().getQuickHideMasks().removeIf((DescriptionFilter t1) -> t1.getDescription().equals(description)); + getController().getQuickHideMasks().removeIf(descriptionFilter -> + descriptionFilter.getDescriptionLoD().equals(descriptionLoD) + && descriptionFilter.getDescription().equals(description)); setRequiresLayout(true); requestChartLayout(); }); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java index 6a3ca264cf..0845d86259 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java @@ -19,11 +19,12 @@ package org.sleuthkit.autopsy.timeline.ui.detailview.tree; import java.util.Comparator; +import java.util.Deque; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import javafx.collections.FXCollections; import javafx.scene.control.TreeItem; import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; -import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; /** * @@ -34,10 +35,14 @@ class EventDescriptionTreeItem extends NavTreeItem { * maps a description to the child item of this item with that description */ private final Map childMap = new ConcurrentHashMap<>(); - private final DescriptionLOD descriptionLoD; + private final EventBundle bundle; + + public EventBundle getEventBundle() { + return bundle; + } EventDescriptionTreeItem(EventBundle g) { - descriptionLoD = g.getDescriptionLOD(); + bundle = g; setValue(new NavTreeNode(g.getEventType().getBaseType(), g.getDescription(), g.getDescriptionLOD(), g.getEventIDs().size())); } @@ -46,37 +51,25 @@ class EventDescriptionTreeItem extends NavTreeItem { return getValue().getCount(); } - @Override - public void insert(EventBundle g) { - NavTreeNode value = getValue(); - if (value.getType().getBaseType().equals(g.getEventType().getBaseType()) - && g.getDescription().startsWith(value.getDescription())) { - throw new IllegalArgumentException(); + public void insert(Deque path) { + EventBundle head = path.removeFirst(); + EventDescriptionTreeItem treeItem = childMap.get(head.getDescription()); + if (treeItem == null) { + treeItem = new EventDescriptionTreeItem(head); + treeItem.setExpanded(true); + childMap.put(head.getDescription(), treeItem); + getChildren().add(treeItem); + FXCollections.sort(getChildren(), TreeComparator.Description); } - switch (descriptionLoD.getDetailLevelRelativeTo(g.getDescriptionLOD())) { - case LESS: - EventDescriptionTreeItem get = childMap.get(g.getDescription()); - if (get == null) { - EventDescriptionTreeItem eventDescriptionTreeItem = new EventDescriptionTreeItem(g); - childMap.put(g.getDescription(), eventDescriptionTreeItem); - getChildren().add(eventDescriptionTreeItem); - } else { - get.insert(g); - } - break; - case EQUAL: - setValue(new NavTreeNode(value.getType().getBaseType(), value.getDescription(), value.getDescriptionLoD(), value.getCount() + g.getEventIDs().size())); - break; - case MORE: - throw new IllegalArgumentException(); + if (path.isEmpty() == false) { + treeItem.insert(path); } - } @Override public void resort(Comparator> comp) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + FXCollections.sort(getChildren(), comp); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java index f095d95b78..51c8b735bc 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java @@ -19,9 +19,9 @@ package org.sleuthkit.autopsy.timeline.ui.detailview.tree; import java.util.Comparator; +import java.util.Deque; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.scene.control.TreeItem; import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; @@ -44,36 +44,21 @@ class EventTypeTreeItem extends NavTreeItem { return getValue().getCount(); } - /** - * Recursive method to add a grouping at a given path. - * - * @param path Full path (or subset not yet added) to add - * @param g Group to add - * @param tree True if it is part of a tree (versus a list) - */ - @Override - public void insert(EventBundle g) { + public void insert(Deque path) { - EventDescriptionTreeItem treeItem = childMap.get(g.getDescription()); + EventBundle head = path.removeFirst(); + EventDescriptionTreeItem treeItem = childMap.get(head.getDescription()); if (treeItem == null) { - final EventDescriptionTreeItem newTreeItem = new EventDescriptionTreeItem(g); - newTreeItem.setExpanded(true); - childMap.put(g.getDescription(), newTreeItem); - - Platform.runLater(() -> { - synchronized (getChildren()) { - getChildren().add(newTreeItem); - FXCollections.sort(getChildren(), comparator); - } - }); - } else { - treeItem.insert(g); + treeItem = new EventDescriptionTreeItem(head); + treeItem.setExpanded(true); + childMap.put(head.getDescription(), treeItem); + getChildren().add(treeItem); + FXCollections.sort(getChildren(), comparator); } - Platform.runLater(() -> { - NavTreeNode value1 = getValue(); - setValue(new NavTreeNode(value1.getType().getBaseType(), value1.getType().getBaseType().getDisplayName(),value1.getDescriptionLoD(), childMap.values().stream().mapToInt(EventDescriptionTreeItem::getCount).sum())); - }); + if (path.isEmpty() == false) { + treeItem.insert(path); + } } @Override diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java index 1b87b31b8a..4b40c3877a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java @@ -21,10 +21,10 @@ package org.sleuthkit.autopsy.timeline.ui.detailview.tree; import com.google.common.collect.ImmutableList; import java.util.Arrays; import java.util.Comparator; -import javafx.application.Platform; import javafx.beans.Observable; import javafx.fxml.FXML; import javafx.scene.control.ComboBox; +import javafx.scene.control.ContextMenu; import javafx.scene.control.Label; import javafx.scene.control.SelectionMode; import javafx.scene.control.Tooltip; @@ -36,8 +36,10 @@ import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; +import org.apache.commons.lang3.StringUtils; import org.controlsfx.control.action.ActionUtils; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.TimeLineView; @@ -83,14 +85,14 @@ public class NavPanel extends BorderPane implements TimeLineView { } + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private void setRoot() { RootItem root = new RootItem(); for (EventBundle bundle : detailViewPane.getEventBundles()) { root.insert(bundle); } - Platform.runLater(() -> { - eventsTree.setRoot(root); - }); + eventsTree.setRoot(root); + } @Override @@ -130,8 +132,17 @@ public class NavPanel extends BorderPane implements TimeLineView { @Override protected void updateItem(NavTreeNode item, boolean empty) { super.updateItem(item, empty); - if (item != null) { - final String text = item.getDescription() + " (" + item.getCount() + ")"; // NON-NLS + if (item == null || empty) { + setText(null); + setTooltip(null); + setGraphic(null); + setContextMenu(null); + } else { + String text = item.getDescription() + " (" + item.getCount() + ")"; // NON-NLS + TreeItem parent = getTreeItem().getParent(); + if (parent != null && parent.getValue() != null && (parent instanceof EventDescriptionTreeItem)) { + text = StringUtils.substringAfter(text, parent.getValue().getDescription()); + } setText(text); setTooltip(new Tooltip(text)); Rectangle rect = new Rectangle(24, 24); @@ -146,28 +157,34 @@ public class NavPanel extends BorderPane implements TimeLineView { }); configureHiddenState(item, rect, imageView); - } else { - setText(null); - setTooltip(null); - setGraphic(null); - setContextMenu(null); } } private void configureHiddenState(NavTreeNode item, Rectangle rect, ImageView imageView) { + TreeItem treeItem = getTreeItem(); + ContextMenu newMenu; if (controller.getQuickHideMasks().stream().anyMatch(mask -> mask.getDescription().equals(item.getDescription()))) { setTextFill(Color.gray(0, .6)); imageView.setOpacity(.6); rect.setStroke(item.getType().getColor().deriveColor(0, .6, 1, .6)); rect.setFill(item.getType().getColor().deriveColor(0, .6, .6, 0.1)); - setContextMenu(ActionUtils.createContextMenu(ImmutableList.of(detailViewPane.newUnhideBundleAction(item.getDescription())))); + if (treeItem != null) { + treeItem.setExpanded(false); + } + newMenu = ActionUtils.createContextMenu(ImmutableList.of(detailViewPane.newUnhideDescriptionAction(item.getDescription(), item.getDescriptionLod()))); } else { setTextFill(Color.BLACK); imageView.setOpacity(1); rect.setStroke(item.getType().getColor()); rect.setFill(item.getType().getColor().deriveColor(0, 1, 1, 0.1)); -// setContextMenu(ActionUtils.createContextMenu(ImmutableList.of(detailViewPane.newHideBundleAction(item.getDescription())))); + newMenu = ActionUtils.createContextMenu(ImmutableList.of(detailViewPane.newHideDescriptionAction(item.getDescription(), item.getDescriptionLod()))); + } + + if (treeItem instanceof EventDescriptionTreeItem) { + setContextMenu(newMenu); + } else { + setContextMenu(null); } } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java index 2c0be29472..bd456cd03a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java @@ -30,8 +30,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; */ abstract class NavTreeItem extends TreeItem { - abstract void insert(EventBundle g); - +// abstract void insert(EventBundle g); abstract int getCount(); abstract void resort(Comparator> comp); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeNode.java index 3b785a9527..54d9397e3b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeNode.java @@ -29,11 +29,10 @@ import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; @Immutable public class NavTreeNode { - final private DescriptionLOD descriptionLoD; - final private EventType type; - final private String Description; + private final DescriptionLOD descriptionLoD; + final private int count; @@ -44,7 +43,7 @@ public class NavTreeNode { this.count = count; } - public DescriptionLOD getDescriptionLoD() { + public DescriptionLOD getDescriptionLod() { return descriptionLoD; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java index 69fe72d83e..c42de09774 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java @@ -18,10 +18,12 @@ */ package org.sleuthkit.autopsy.timeline.ui.detailview.tree; +import java.util.ArrayDeque; import java.util.Comparator; +import java.util.Deque; import java.util.HashMap; import java.util.Map; -import javafx.application.Platform; +import java.util.Optional; import javafx.scene.control.TreeItem; import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; @@ -54,25 +56,31 @@ class RootItem extends NavTreeItem { * * @param g Group to add */ - @Override public void insert(EventBundle g) { EventTypeTreeItem treeItem = childMap.get(g.getEventType().getBaseType()); if (treeItem == null) { - final EventTypeTreeItem newTreeItem = new EventTypeTreeItem(g); - newTreeItem.setExpanded(true); - childMap.put(g.getEventType().getBaseType(), newTreeItem); - newTreeItem.insert(g); + treeItem = new EventTypeTreeItem(g); + treeItem.setExpanded(true); + childMap.put(g.getEventType().getBaseType(), treeItem); - Platform.runLater(() -> { - synchronized (getChildren()) { - getChildren().add(newTreeItem); - getChildren().sort(TreeComparator.Type); - } - }); - } else { - treeItem.insert(g); + getChildren().add(treeItem); + getChildren().sort(TreeComparator.Type); } + treeItem.insert(getTreePath(g)); + } + + static Deque getTreePath(EventBundle g) { + Deque path = new ArrayDeque<>(); + Optional p = Optional.of(g); + + while (p.isPresent()) { + EventBundle parent = p.get(); + path.addFirst(parent); + p = parent.getParentBundle(); + } + + return path; } @Override From 0230b5fff934abb4557d7634316f47aa85f89204 Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 30 Sep 2015 13:32:57 -0400 Subject: [PATCH 23/29] fix merge conflicts that lost hiding controls --- .../ui/detailview/EventStripeNode.java | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java index b7efe34622..09489f1317 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java @@ -145,6 +145,7 @@ final public class EventStripeNode extends StackPane { private final EventStripe eventStripe; private final EventStripeNode parentNode; private final FilteredEventsModel eventsModel; + private final Button hideButton; public EventStripeNode(EventDetailChart chart, EventStripe eventStripe, EventStripeNode parentEventNode) { this.eventStripe = eventStripe; @@ -173,10 +174,16 @@ final public class EventStripeNode extends StackPane { if (eventStripe.getEventIDsWithTags().isEmpty()) { show(tagIV, false); } + + EventDetailChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventStripe.getDescriptionLOD()); + hideButton = ActionUtils.createButton(hideClusterAction, ActionUtils.ActionTextBehavior.HIDE); + configureLoDButton(hideButton); configureLoDButton(plusButton); configureLoDButton(minusButton); //initialize info hbox + infoHBox.getChildren().add(4, hideButton); + infoHBox.setMinWidth(USE_PREF_SIZE); infoHBox.setPadding(new Insets(2, 5, 2, 5)); infoHBox.setAlignment(Pos.CENTER_LEFT); @@ -234,6 +241,7 @@ final public class EventStripeNode extends StackPane { clustersHBox.setEffect(showControls ? dropShadow : null); show(minusButton, showControls); show(plusButton, showControls); + show(hideButton, showControls); } public void setSpanWidths(List spanWidths) { @@ -402,6 +410,10 @@ final public class EventStripeNode extends StackPane { */ @NbBundle.Messages(value = "EventStripeNode.loggedTask.name=Load sub clusters") private synchronized void loadSubBundles(DescriptionLOD.RelativeDetail relativeDetail) { + chart.getEventBundles().removeIf(bundle -> + getSubNodes().stream().anyMatch(subNode -> + bundle.equals(subNode.getEventStripe())) + ); subNodePane.getChildren().clear(); if (descLOD.get().withRelativeDetail(relativeDetail) == eventStripe.getDescriptionLOD()) { descLOD.set(eventStripe.getDescriptionLOD()); @@ -422,7 +434,7 @@ final public class EventStripeNode extends StackPane { final EventTypeZoomLevel eventTypeZoomLevel = eventsModel.eventTypeZoomProperty().get(); final ZoomParams zoomParams = new ZoomParams(subClusterSpan, eventTypeZoomLevel, subClusterFilter, getDescriptionLoD()); - Task> loggedTask = new Task>() { + Task> loggedTask = new Task>() { private volatile DescriptionLOD loadedDescriptionLoD = getDescriptionLoD().withRelativeDetail(relativeDetail); @@ -431,7 +443,7 @@ final public class EventStripeNode extends StackPane { } @Override - protected Set call() throws Exception { + protected Collection call() throws Exception { Collection bundles; DescriptionLOD next = loadedDescriptionLoD; do { @@ -440,6 +452,7 @@ final public class EventStripeNode extends StackPane { return Collections.emptySet(); } bundles = eventsModel.getEventClusters(zoomParams.withDescrLOD(loadedDescriptionLoD)).stream() + .map(cluster -> cluster.withParent(getEventStripe())) .collect(Collectors.toMap( EventCluster::getDescription, //key EventStripe::new, //value @@ -449,24 +462,28 @@ final public class EventStripeNode extends StackPane { } while (bundles.size() == 1 && nonNull(next)); // return list of AbstractEventStripeNodes representing sub-bundles - return bundles.stream() - .map(EventStripeNode.this::getNodeForBundle) - .collect(Collectors.toSet()); + return bundles; + } @Override protected void succeeded() { chart.setCursor(Cursor.WAIT); try { - Set subBundleNodes = get(); - if (subBundleNodes.isEmpty()) { + Collection bundles = get(); + + if (bundles.isEmpty()) { clustersHBox.setVisible(true); } else { clustersHBox.setVisible(false); + chart.getEventBundles().addAll(bundles); + subNodePane.getChildren().setAll(bundles.stream() + .map(EventStripeNode.this::getNodeForBundle) + .collect(Collectors.toSet())); } descLOD.set(loadedDescriptionLoD); //assign subNodes and request chart layout - subNodePane.getChildren().setAll(subBundleNodes); + chart.setRequiresLayout(true); chart.requestChartLayout(); } catch (InterruptedException | ExecutionException ex) { From a2a460665cf9ac6f17b15099fcdc5d3dda3b2ef7 Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 30 Sep 2015 15:20:17 -0400 Subject: [PATCH 24/29] quickhide refactoring --- .../datamodel/FilteredEventsModel.java | 3 +- .../filters/DescriptionsExclusionFilter.java | 80 ------------------- .../autopsy/timeline/filters/RootFilter.java | 19 ++--- .../timeline/ui/filtering/FilterSetPanel.fxml | 28 ++++--- .../timeline/ui/filtering/FilterSetPanel.java | 53 ++++++++---- 5 files changed, 63 insertions(+), 120 deletions(-) delete mode 100644 Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionsExclusionFilter.java diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java index 26cbc1c8a3..654c77a3db 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java @@ -47,7 +47,6 @@ import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent; import org.sleuthkit.autopsy.timeline.events.TagsUpdatedEvent; import org.sleuthkit.autopsy.timeline.filters.DataSourceFilter; import org.sleuthkit.autopsy.timeline.filters.DataSourcesFilter; -import org.sleuthkit.autopsy.timeline.filters.DescriptionsExclusionFilter; import org.sleuthkit.autopsy.timeline.filters.Filter; import org.sleuthkit.autopsy.timeline.filters.HashHitsFilter; import org.sleuthkit.autopsy.timeline.filters.HashSetFilter; @@ -230,7 +229,7 @@ public final class FilteredEventsModel { tagNameFilter.setSelected(Boolean.TRUE); tagsFilter.addSubFilter(tagNameFilter); }); - return new RootFilter(new HideKnownFilter(), tagsFilter, hashHitsFilter, new TextFilter(), new TypeFilter(RootEventType.getInstance()), dataSourcesFilter, new DescriptionsExclusionFilter(), Collections.emptySet()); + return new RootFilter(new HideKnownFilter(), tagsFilter, hashHitsFilter, new TextFilter(), new TypeFilter(RootEventType.getInstance()), dataSourcesFilter, Collections.emptySet()); } public Interval getBoundingEventsInterval() { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionsExclusionFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionsExclusionFilter.java deleted file mode 100644 index 5206d57ff0..0000000000 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionsExclusionFilter.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.sleuthkit.autopsy.timeline.filters; - -import java.util.Comparator; -import javafx.beans.binding.Bindings; -import org.openide.util.NbBundle; -import static org.sleuthkit.autopsy.timeline.filters.CompoundFilter.areSubFiltersEqual; - -/** - * - */ -public class DescriptionsExclusionFilter extends IntersectionFilter { - - @Override - @NbBundle.Messages("descriptionsExclusionFilter.displayName.text=Exclude Descriptions") - public String getDisplayName() { - return Bundle.descriptionsExclusionFilter_displayName_text(); - } - - public DescriptionsExclusionFilter() { - getDisabledProperty().bind(Bindings.size(getSubFilters()).lessThan(1)); - setSelected(false); - } - - @Override - public DescriptionsExclusionFilter copyOf() { - DescriptionsExclusionFilter filterCopy = new DescriptionsExclusionFilter(); - filterCopy.setSelected(isSelected()); - //add a copy of each subfilter - this.getSubFilters().forEach((DescriptionFilter t) -> { - filterCopy.addSubFilter(t.copyOf()); - }); - return filterCopy; - } - - @Override - public String getHTMLReportString() { - //move this logic into SaveSnapshot - String string = getDisplayName() + getStringCheckBox(); - if (getSubFilters().isEmpty() == false) { - string = string + " : " + super.getHTMLReportString(); - } - return string; - } - - @Override - public int hashCode() { - return 7; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final DescriptionsExclusionFilter other = (DescriptionsExclusionFilter) obj; - - if (isSelected() != other.isSelected()) { - return false; - } - - return areSubFiltersEqual(this, other); - } - - public void addSubFilter(DescriptionFilter descriptionFilter) { - if (getSubFilters().contains(descriptionFilter) == false) { - getSubFilters().add(descriptionFilter); - getSubFilters().sort(Comparator.comparing(DescriptionFilter::getDisplayName)); - } else { - getSubFilters().filtered(descriptionFilter::equals).get(0).setSelected(descriptionFilter.isSelected()); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java index 76006f8c68..9034810ed7 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java @@ -34,11 +34,6 @@ public class RootFilter extends IntersectionFilter { private final TextFilter textFilter; private final TypeFilter typeFilter; private final DataSourcesFilter dataSourcesFilter; - private final DescriptionsExclusionFilter descriptionsExclusionFilter; - - public DescriptionsExclusionFilter getDescriptionsExclusionfilter() { - return descriptionsExclusionFilter; - } public DataSourcesFilter getDataSourcesFilter() { return dataSourcesFilter; @@ -52,14 +47,13 @@ public class RootFilter extends IntersectionFilter { return hashFilter; } - public RootFilter(HideKnownFilter knownFilter, TagsFilter tagsFilter, HashHitsFilter hashFilter, TextFilter textFilter, TypeFilter typeFilter, DataSourcesFilter dataSourceFilter, DescriptionsExclusionFilter descriptionsExclusionFilter, Set annonymousSubFilters) { + public RootFilter(HideKnownFilter knownFilter, TagsFilter tagsFilter, HashHitsFilter hashFilter, TextFilter textFilter, TypeFilter typeFilter, DataSourcesFilter dataSourceFilter, Set annonymousSubFilters) { super(FXCollections.observableArrayList( textFilter, knownFilter, dataSourceFilter, tagsFilter, hashFilter, - typeFilter, - descriptionsExclusionFilter + typeFilter )); this.knownFilter = knownFilter; this.tagsFilter = tagsFilter; @@ -67,7 +61,6 @@ public class RootFilter extends IntersectionFilter { this.textFilter = textFilter; this.typeFilter = typeFilter; this.dataSourcesFilter = dataSourceFilter; - this.descriptionsExclusionFilter = descriptionsExclusionFilter; getSubFilters().addAll(annonymousSubFilters); setSelected(Boolean.TRUE); setDisabled(false); @@ -76,14 +69,13 @@ public class RootFilter extends IntersectionFilter { @Override public RootFilter copyOf() { Set annonymousSubFilters = getSubFilters().stream() - .filter(subFilter - -> !(subFilter.equals(knownFilter) + .filter(subFilter -> + !(subFilter.equals(knownFilter) || subFilter.equals(tagsFilter) || subFilter.equals(hashFilter) || subFilter.equals(typeFilter) || subFilter.equals(textFilter) - || subFilter.equals(dataSourcesFilter) - || subFilter.equals(descriptionsExclusionFilter))) + || subFilter.equals(dataSourcesFilter))) .map(Filter::copyOf) .collect(Collectors.toSet()); @@ -94,7 +86,6 @@ public class RootFilter extends IntersectionFilter { textFilter.copyOf(), typeFilter.copyOf(), dataSourcesFilter.copyOf(), - descriptionsExclusionFilter.copyOf(), annonymousSubFilters); filter.setSelected(isSelected()); filter.setDisabled(isDisabled()); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.fxml b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.fxml index 265c986360..4cb165770c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.fxml +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.fxml @@ -26,13 +26,23 @@ -
- - - - - - - - +
+ + + + + + + + + + + + + + + + + +
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 b0ac96b323..ba78fd3bf5 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java @@ -21,12 +21,14 @@ package org.sleuthkit.autopsy.timeline.ui.filtering; import javafx.application.Platform; import javafx.beans.Observable; import javafx.collections.FXCollections; -import javafx.collections.ListChangeListener; import javafx.collections.ObservableMap; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; import javafx.scene.control.ContextMenu; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; import javafx.scene.control.TreeItem; @@ -47,7 +49,6 @@ 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.DescriptionsExclusionFilter; import org.sleuthkit.autopsy.timeline.filters.Filter; import org.sleuthkit.autopsy.timeline.filters.RootFilter; import org.sleuthkit.autopsy.timeline.filters.TypeFilter; @@ -78,6 +79,9 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { @FXML private TreeTableColumn legendColumn; + @FXML + private ListView hiddenDescriptionsListView; + private FilteredEventsModel filteredEvents; private TimeLineController controller; @@ -165,6 +169,22 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { expansionMap.put(new TypeFilter(RootEventType.getInstance()).getDisplayName(), true); } + static class ListCellImpl extends ListCell { + + @Override + protected void updateItem(DescriptionFilter item, boolean empty) { + super.updateItem(item, empty); //To change body of generated methods, choose Tools | Templates. + if (item == null || empty) { + setText(null); + setGraphic(null); + } else { + setGraphic(new CheckBox()); + setText(item.getDisplayName()); + } + } + + } + @Override public void setController(TimeLineController timeLineController) { this.controller = timeLineController; @@ -172,17 +192,20 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { defaultButton.setOnAction(defaultFiltersAction); defaultButton.disableProperty().bind(defaultFiltersAction.disabledProperty()); this.setModel(timeLineController.getEventsModel()); - controller.getQuickHideMasks().addListener((ListChangeListener.Change c) -> { - while (c.next()) { - DescriptionsExclusionFilter descriptionExclusionFilter = ((RootFilter) filterTreeTable.getRoot().getValue()).getDescriptionsExclusionfilter(); - for (DescriptionFilter filter : c.getAddedSubList()) { - descriptionExclusionFilter.setSelected(true); - descriptionExclusionFilter.addSubFilter(filter); - } - } - }); + hiddenDescriptionsListView.setItems(controller.getQuickHideMasks()); + hiddenDescriptionsListView.setCellFactory((ListView param) -> new ListCellImpl()); +// .addListener((ListChangeListener.Change c) -> { +// while (c.next()) { +// DescriptionsExclusionFilter descriptionExclusionFilter = ((RootFilter) filterTreeTable.getRoot().getValue()).getDescriptionsExclusionfilter(); +// +// for (DescriptionFilter filter : c.getAddedSubList()) { +// descriptionExclusionFilter.setSelected(true); +// descriptionExclusionFilter.addSubFilter(filter); +// } +// } +// }); controller.viewModeProperty().addListener(observable -> { applyFilters(); }); @@ -192,13 +215,13 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { public void setModel(FilteredEventsModel filteredEvents) { this.filteredEvents = filteredEvents; this.filteredEvents.eventTypeZoomProperty().addListener((Observable observable) -> { - applyFilters(); + applyFilters(); }); this.filteredEvents.descriptionLODProperty().addListener((Observable observable) -> { - applyFilters(); + applyFilters(); }); this.filteredEvents.timeRangeProperty().addListener((Observable observable) -> { - applyFilters(); + applyFilters(); }); this.filteredEvents.filterProperty().addListener((Observable o) -> { refresh(); @@ -230,7 +253,7 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { Platform.runLater(() -> { controller.pushFilters((RootFilter) filterTreeTable.getRoot().getValue()); }); - + } private static final Image TICK = new Image("org/sleuthkit/autopsy/timeline/images/tick.png"); From db030493854a7567239edfa3bc0a63576c12922d Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 30 Sep 2015 16:43:08 -0400 Subject: [PATCH 25/29] do all quickhiding in memory, refector some filter stuff --- .../coreutils/AbstractFXCellFactory.java | 74 +++++++++++++++++++ .../timeline/filters/AbstractFilter.java | 4 +- .../timeline/filters/CompoundFilter.java | 2 +- .../autopsy/timeline/filters/Filter.java | 2 +- .../timeline/filters/HideKnownFilter.java | 2 +- .../ui/detailview/EventDetailChart.java | 21 +++--- .../ui/filtering/FilterCheckBoxCell.java | 63 ---------------- .../filtering/FilterCheckBoxCellFactory.java | 42 +++++++++++ .../timeline/ui/filtering/FilterSetPanel.java | 24 +++--- 9 files changed, 142 insertions(+), 92 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/coreutils/AbstractFXCellFactory.java delete mode 100644 Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterCheckBoxCell.java create mode 100644 Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterCheckBoxCellFactory.java diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/AbstractFXCellFactory.java b/Core/src/org/sleuthkit/autopsy/coreutils/AbstractFXCellFactory.java new file mode 100644 index 0000000000..169447d183 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/coreutils/AbstractFXCellFactory.java @@ -0,0 +1,74 @@ +/** + * ************************************************************************* + ** This data and information is proprietary to, and a valuable trade secret * + * of, Basis Technology Corp. It is given in confidence by Basis Technology * + * and may only be used as permitted under the license agreement under which * + * it has been distributed, and in no other way. * * Copyright (c) 2014 Basis + * Technology Corp. All rights reserved. * * The technical data and information + * provided herein are provided with * `limited rights', and the computer + * software provided herein is provided * with `restricted rights' as those + * terms are defined in DAR and ASPR * 7-104.9(a). + * ************************************************************************* + */ +package org.sleuthkit.autopsy.coreutils; + +import java.util.function.Supplier; +import javafx.scene.control.IndexedCell; +import javafx.scene.control.ListCell; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TreeTableCell; +import javafx.scene.control.TreeTableColumn; + +/** + * an abstract base class for Cell factories. This class provides the basic + * infrustructure for implementations to be able to create similar cells for + * listview, tableviews or treetableviews via the appropriate method call. + * Implementations need only implement the abstract configureCell method in the + * same spirit as IndexedCell.updateItem + */ +public abstract class AbstractFXCellFactory { + + public TreeTableCell< X, Y> forTreeTable(TreeTableColumn< X, Y> column) { + return new AbstractTreeTableCell(); + } + + public TableCell forTable(TableColumn column) { + return new AbstractTableCell(); + } + + public ListCell< Y> forList() { + return new AbstractListCell(); + } + + protected abstract void configureCell(IndexedCell cell, Y item, boolean empty, Supplier supplier); + + private class AbstractTableCell extends TableCell { + + @Override + @SuppressWarnings({"unchecked"}) //we know it will be X but there is a flaw in getTableRow return type + protected void updateItem(Y item, boolean empty) { + super.updateItem(item, empty); + configureCell(this, item, empty, (() -> (X) this.getTableRow().getItem())); + } + } + + private class AbstractTreeTableCell extends TreeTableCell { + + @Override + protected void updateItem(Y item, boolean empty) { + super.updateItem(item, empty); + configureCell(this, item, empty, (() -> this.getTreeTableRow().getItem())); + } + } + + private class AbstractListCell extends ListCell< Y> { + + @Override + @SuppressWarnings("unchecked") //for a list X should always equal Y + protected void updateItem(Y item, boolean empty) { + super.updateItem(item, empty); + configureCell(this, item, empty, () -> (X) this.getItem()); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java index bc9675cca2..787307916f 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java @@ -30,7 +30,7 @@ public abstract class AbstractFilter implements Filter { private final SimpleBooleanProperty disabled = new SimpleBooleanProperty(false); @Override - public SimpleBooleanProperty getSelectedProperty() { + public SimpleBooleanProperty selectedProperty() { return selected; } @@ -64,7 +64,7 @@ public abstract class AbstractFilter implements Filter { return "[" + (isSelected() ? "x" : " ") + "]"; // NON-NLS } - final boolean isActive() { + public final boolean isActive() { return isSelected() && (isDisabled() == false); } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java index c1964b7087..bf4c6a1b07 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/CompoundFilter.java @@ -72,7 +72,7 @@ public abstract class CompoundFilter extends Abstr private void addSubFilterListeners(List newSubfilters) { for (SubFilterType sf : newSubfilters) { //if a subfilter changes active state - sf.getSelectedProperty().addListener((Observable observable) -> { + sf.selectedProperty().addListener((Observable observable) -> { //set this filter acttive af any of the subfilters are active. setSelected(getSubFilters().parallelStream().anyMatch(Filter::isSelected)); }); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java index 95226e9371..03c1c01efa 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java @@ -66,7 +66,7 @@ public interface Filter { void setSelected(Boolean act); - SimpleBooleanProperty getSelectedProperty(); + SimpleBooleanProperty selectedProperty(); /* * TODO: disabled state only affects the state of the checkboxes in the ui diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/HideKnownFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/HideKnownFilter.java index b47c4d481f..f5e5b30731 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/HideKnownFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/HideKnownFilter.java @@ -33,7 +33,7 @@ public class HideKnownFilter extends AbstractFilter { public HideKnownFilter() { super(); - getSelectedProperty().set(false); + selectedProperty().set(false); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java index 0d0357fc5e..9dde499205 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java @@ -79,6 +79,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.EventStripe; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; +import org.sleuthkit.autopsy.timeline.filters.AbstractFilter; import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter; import org.sleuthkit.autopsy.timeline.ui.TimeLineChart; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; @@ -325,6 +326,7 @@ public final class EventDetailChart extends XYChart impl public synchronized void setController(TimeLineController controller) { this.controller = controller; setModel(this.controller.getEventsModel()); + getController().getQuickHideMasks().addListener(layoutInvalidationListener); } @Override @@ -477,6 +479,7 @@ public final class EventDetailChart extends XYChart impl for (Series series : sortedSeriesList) { hiddenPartition = series.getData().stream().map(Data::getNode).map(EventStripeNode.class::cast) .collect(Collectors.partitioningBy(node -> getController().getQuickHideMasks().stream() + .filter(AbstractFilter::isActive) .anyMatch(mask -> mask.getDescription().equals(node.getDescription())))); layoutNodesHelper(hiddenPartition.get(true), hiddenPartition.get(false), minY, 0); @@ -485,6 +488,7 @@ public final class EventDetailChart extends XYChart impl } else { hiddenPartition = stripeNodeMap.values().stream() .collect(Collectors.partitioningBy(node -> getController().getQuickHideMasks().stream() + .filter(AbstractFilter::isActive) .anyMatch(mask -> mask.getDescription().equals(node.getDescription())))); layoutNodesHelper(hiddenPartition.get(true), hiddenPartition.get(false), 0, 0); } @@ -779,20 +783,15 @@ public final class EventDetailChart extends XYChart impl class HideDescriptionAction extends Action { - /** - * - * @param description the value of description - */ HideDescriptionAction(String description, DescriptionLOD descriptionLoD) { super("Hide"); setGraphic(new ImageView(HIDE)); setEventHandler((ActionEvent t) -> { - getController().getQuickHideMasks().add( - new DescriptionFilter(descriptionLoD, - description, - DescriptionFilter.FilterMode.EXCLUDE)); - setRequiresLayout(true); - requestChartLayout(); + DescriptionFilter descriptionFilter = new DescriptionFilter(descriptionLoD, + description, + DescriptionFilter.FilterMode.EXCLUDE); + descriptionFilter.selectedProperty().addListener(layoutInvalidationListener); + getController().getQuickHideMasks().add(descriptionFilter); }); } } @@ -807,8 +806,6 @@ public final class EventDetailChart extends XYChart impl getController().getQuickHideMasks().removeIf(descriptionFilter -> descriptionFilter.getDescriptionLoD().equals(descriptionLoD) && descriptionFilter.getDescription().equals(description)); - setRequiresLayout(true); - requestChartLayout(); }); } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterCheckBoxCell.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterCheckBoxCell.java deleted file mode 100644 index 795e4b66b1..0000000000 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterCheckBoxCell.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2015 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.application.Platform; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.scene.control.CheckBox; -import javafx.scene.control.TreeTableCell; -import org.sleuthkit.autopsy.timeline.TimeLineController; -import org.sleuthkit.autopsy.timeline.filters.AbstractFilter; - -/** - * A {@link TreeTableCell} that represents the active state of a - * {@link AbstractFilter} as a checkbox - */ -class FilterCheckBoxCell extends TreeTableCell { - - private final CheckBox checkBox = new CheckBox(); - private SimpleBooleanProperty activeProperty; - private final TimeLineController controller; - - FilterCheckBoxCell(TimeLineController controller) { - this.controller = controller; - - } - - @Override - protected void updateItem(AbstractFilter item, boolean empty) { - super.updateItem(item, empty); - Platform.runLater(() -> { - if (activeProperty != null) { - checkBox.selectedProperty().unbindBidirectional(activeProperty); - } - checkBox.disableProperty().unbind(); - if (item == null) { - setText(null); - setGraphic(null); - } else { - setText(item.getDisplayName()); - activeProperty = item.getSelectedProperty(); - checkBox.selectedProperty().bindBidirectional(activeProperty); - checkBox.disableProperty().bind(item.getDisabledProperty()); - setGraphic(checkBox); - } - }); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterCheckBoxCellFactory.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterCheckBoxCellFactory.java new file mode 100644 index 0000000000..54d848b988 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterCheckBoxCellFactory.java @@ -0,0 +1,42 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.timeline.ui.filtering; + +import java.util.function.Supplier; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.scene.control.CheckBox; +import javafx.scene.control.IndexedCell; +import org.sleuthkit.autopsy.coreutils.AbstractFXCellFactory; +import org.sleuthkit.autopsy.timeline.filters.AbstractFilter; + +class FilterCheckBoxCellFactory extends AbstractFXCellFactory { + + private final CheckBox checkBox = new CheckBox(); + private SimpleBooleanProperty selectedProperty; + private SimpleBooleanProperty disabledProperty; + + @Override + protected void configureCell(IndexedCell cell, X item, boolean empty, Supplier supplier) { + if (selectedProperty != null) { + checkBox.selectedProperty().unbindBidirectional(selectedProperty); + } + if (disabledProperty != null) { + checkBox.disableProperty().unbindBidirectional(disabledProperty); + } + if (item == null) { + cell.setText(null); + cell.setGraphic(null); + } else { + cell.setText(item.getDisplayName()); + selectedProperty = item.selectedProperty(); + checkBox.selectedProperty().bindBidirectional(selectedProperty); + disabledProperty = item.getDisabledProperty(); + checkBox.disableProperty().bindBidirectional(disabledProperty); + cell.setGraphic(checkBox); + } + } + +} 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 ba78fd3bf5..cabc79dbc9 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java @@ -156,7 +156,7 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { //configure tree column to show name of filter and checkbox treeColumn.setCellValueFactory(param -> param.getValue().valueProperty()); - treeColumn.setCellFactory(col -> new FilterCheckBoxCell(controller)); + treeColumn.setCellFactory(col -> new FilterCheckBoxCellFactory().forTreeTable(col)); //configure legend column to show legend (or othe supplamantal ui, eg, text field for text filter) legendColumn.setCellValueFactory(param -> param.getValue().valueProperty()); @@ -194,18 +194,18 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { this.setModel(timeLineController.getEventsModel()); hiddenDescriptionsListView.setItems(controller.getQuickHideMasks()); - hiddenDescriptionsListView.setCellFactory((ListView param) -> new ListCellImpl()); + hiddenDescriptionsListView.setCellFactory((ListView param) -> { + final ListCell forList = new FilterCheckBoxCellFactory().forList(); + forList.setContextMenu(new ContextMenu(new MenuItem("unhide and remove from list") { + { + setOnAction((ActionEvent event) -> { + controller.getQuickHideMasks().remove(forList.getItem()); + }); + } + })); + return forList; + }); -// .addListener((ListChangeListener.Change c) -> { -// while (c.next()) { -// DescriptionsExclusionFilter descriptionExclusionFilter = ((RootFilter) filterTreeTable.getRoot().getValue()).getDescriptionsExclusionfilter(); -// -// for (DescriptionFilter filter : c.getAddedSubList()) { -// descriptionExclusionFilter.setSelected(true); -// descriptionExclusionFilter.addSubFilter(filter); -// } -// } -// }); controller.viewModeProperty().addListener(observable -> { applyFilters(); }); From b7cee4bd839980d715befed08e5c79599fde6ddd Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 30 Sep 2015 17:12:59 -0400 Subject: [PATCH 26/29] polish quick hide feature --- .../timeline/datamodel/EventBundle.java | 7 +- .../timeline/datamodel/EventCluster.java | 4 +- .../timeline/datamodel/EventStripe.java | 6 +- .../timeline/filters/AbstractFilter.java | 11 +- .../ui}/AbstractFXCellFactory.java | 2 +- .../ui/detailview/DetailViewPane.java | 8 +- .../ui/detailview/EventDetailChart.java | 26 ++-- .../ui/detailview/EventStripeNode.java | 14 +-- .../tree/EventDescriptionTreeItem.java | 16 ++- .../ui/detailview/tree/EventTypeTreeItem.java | 14 +-- .../timeline/ui/detailview/tree/NavPanel.java | 114 ++++++++++++------ .../ui/detailview/tree/NavTreeItem.java | 10 +- .../ui/detailview/tree/NavTreeNode.java | 61 ---------- .../timeline/ui/detailview/tree/RootItem.java | 12 +- .../ui/detailview/tree/TreeComparator.java | 14 +-- .../filtering/FilterCheckBoxCellFactory.java | 4 +- .../timeline/ui/filtering/FilterSetPanel.fxml | 5 +- .../timeline/ui/filtering/FilterSetPanel.java | 57 ++++++++- 18 files changed, 221 insertions(+), 164 deletions(-) rename Core/src/org/sleuthkit/autopsy/{coreutils => timeline/ui}/AbstractFXCellFactory.java (98%) delete mode 100644 Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeNode.java diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java index 23bcdad928..ddfbcd82e9 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java @@ -31,10 +31,11 @@ public interface EventBundle { String getDescription(); - DescriptionLOD getDescriptionLOD(); + DescriptionLOD getDescriptionLoD(); Set getEventIDs(); + Set getEventIDsWithHashHits(); Set getEventIDsWithTags(); @@ -48,4 +49,8 @@ public interface EventBundle { Iterable> getRanges(); Optional getParentBundle(); + + default long getCount() { + return getEventIDs().size(); + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java index 5df5f2a173..0fc5a7ca42 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java @@ -162,7 +162,7 @@ public class EventCluster implements EventBundle { } @Override - public DescriptionLOD getDescriptionLOD() { + public DescriptionLOD getDescriptionLoD() { return lod; } @@ -194,4 +194,6 @@ public class EventCluster implements EventBundle { } return new EventCluster(span, type, eventIDs, hashHits, tagged, description, lod, parent); } + + } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java index d91b276b57..a3dbb6bc1c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java @@ -78,7 +78,7 @@ public final class EventStripe implements EventBundle { spanMap.put(cluster.getRange(), cluster); type = cluster.getEventType(); description = cluster.getDescription(); - lod = cluster.getDescriptionLOD(); + lod = cluster.getDescriptionLoD(); eventIDs.addAll(cluster.getEventIDs()); tagged.addAll(cluster.getEventIDsWithTags()); hashHits.addAll(cluster.getEventIDsWithHashHits()); @@ -92,7 +92,7 @@ public final class EventStripe implements EventBundle { spanMap.putAll(v.spanMap); type = u.getEventType(); description = u.getDescription(); - lod = u.getDescriptionLOD(); + lod = u.getDescriptionLoD(); eventIDs.addAll(u.getEventIDs()); eventIDs.addAll(v.getEventIDs()); tagged.addAll(u.getEventIDsWithTags()); @@ -118,7 +118,7 @@ public final class EventStripe implements EventBundle { } @Override - public DescriptionLOD getDescriptionLOD() { + public DescriptionLOD getDescriptionLoD() { return lod; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java index 787307916f..c5871454d8 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/AbstractFilter.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.timeline.filters; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; import javafx.beans.property.SimpleBooleanProperty; /** @@ -28,6 +30,7 @@ public abstract class AbstractFilter implements Filter { private final SimpleBooleanProperty selected = new SimpleBooleanProperty(true); private final SimpleBooleanProperty disabled = new SimpleBooleanProperty(false); + private final BooleanBinding activeProperty = Bindings.and(selected, disabled.not()); @Override public SimpleBooleanProperty selectedProperty() { @@ -64,7 +67,11 @@ public abstract class AbstractFilter implements Filter { return "[" + (isSelected() ? "x" : " ") + "]"; // NON-NLS } - public final boolean isActive() { - return isSelected() && (isDisabled() == false); + public final boolean isActive() { + return activeProperty.get(); + } + + public final BooleanBinding activeProperty() { + return activeProperty; } } diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/AbstractFXCellFactory.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractFXCellFactory.java similarity index 98% rename from Core/src/org/sleuthkit/autopsy/coreutils/AbstractFXCellFactory.java rename to Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractFXCellFactory.java index 169447d183..1a049a4e67 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/AbstractFXCellFactory.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractFXCellFactory.java @@ -10,7 +10,7 @@ * terms are defined in DAR and ASPR * 7-104.9(a). * ************************************************************************* */ -package org.sleuthkit.autopsy.coreutils; +package org.sleuthkit.autopsy.timeline.ui; import java.util.function.Supplier; import javafx.scene.control.IndexedCell; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java index e01c830803..744a9a6cb2 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java @@ -74,7 +74,6 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization; -import org.sleuthkit.autopsy.timeline.ui.detailview.tree.NavTreeNode; import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; @@ -94,7 +93,7 @@ public class DetailViewPane extends AbstractVisualization> treeSelectionModel; + private MultipleSelectionModel> treeSelectionModel; //these three could be injected from fxml but it was causing npe's private final DateAxis dateAxis = new DateAxis(); @@ -220,12 +219,12 @@ public class DetailViewPane extends AbstractVisualization> selectionModel) { + public void setSelectionModel(MultipleSelectionModel> selectionModel) { this.treeSelectionModel = selectionModel; treeSelectionModel.getSelectedItems().addListener((Observable observable) -> { highlightedNodes.clear(); - for (TreeItem tn : treeSelectionModel.getSelectedItems()) { + for (TreeItem tn : treeSelectionModel.getSelectedItems()) { for (EventStripeNode n : chart.getNodes((EventStripeNode t) -> t.getDescription().equals(tn.getValue().getDescription()))) { @@ -304,7 +303,6 @@ public class DetailViewPane extends AbstractVisualization { if (isCancelled()) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java index 9dde499205..bae56528d8 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java @@ -787,11 +787,20 @@ public final class EventDetailChart extends XYChart impl super("Hide"); setGraphic(new ImageView(HIDE)); setEventHandler((ActionEvent t) -> { - DescriptionFilter descriptionFilter = new DescriptionFilter(descriptionLoD, + final DescriptionFilter testFilter = new DescriptionFilter( + descriptionLoD, description, DescriptionFilter.FilterMode.EXCLUDE); - descriptionFilter.selectedProperty().addListener(layoutInvalidationListener); - getController().getQuickHideMasks().add(descriptionFilter); + + DescriptionFilter descriptionFilter = getController().getQuickHideMasks().stream() + .filter(testFilter::equals) + .findFirst().orElseGet(() -> { + testFilter.selectedProperty().addListener(layoutInvalidationListener); + getController().getQuickHideMasks().add(testFilter); + return testFilter; + }); + descriptionFilter.setSelected(true); + }); } } @@ -802,11 +811,12 @@ public final class EventDetailChart extends XYChart impl super("Unhide"); setGraphic(new ImageView(SHOW)); - setEventHandler((ActionEvent t) -> { - getController().getQuickHideMasks().removeIf(descriptionFilter -> - descriptionFilter.getDescriptionLoD().equals(descriptionLoD) - && descriptionFilter.getDescription().equals(description)); - }); + setEventHandler((ActionEvent t) -> + getController().getQuickHideMasks().stream() + .filter(descriptionFilter -> descriptionFilter.getDescriptionLoD().equals(descriptionLoD) + && descriptionFilter.getDescription().equals(description)) + .forEach(descriptionfilter -> descriptionfilter.setSelected(false)) + ); } } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java index 09489f1317..7188e1bc53 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java @@ -151,7 +151,7 @@ final public class EventStripeNode extends StackPane { this.eventStripe = eventStripe; this.parentNode = parentEventNode; this.chart = chart; - descLOD.set(eventStripe.getDescriptionLOD()); + descLOD.set(eventStripe.getDescriptionLoD()); sleuthkitCase = chart.getController().getAutopsyCase().getSleuthkitCase(); eventsModel = chart.getController().getEventsModel(); final Color evtColor = getEventType().getColor(); @@ -175,7 +175,7 @@ final public class EventStripeNode extends StackPane { show(tagIV, false); } - EventDetailChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventStripe.getDescriptionLOD()); + EventDetailChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventStripe.getDescriptionLoD()); hideButton = ActionUtils.createButton(hideClusterAction, ActionUtils.ActionTextBehavior.HIDE); configureLoDButton(hideButton); configureLoDButton(plusButton); @@ -362,7 +362,7 @@ final public class EventStripeNode extends StackPane { RootFilter getSubClusterFilter() { RootFilter subClusterFilter = eventsModel.filterProperty().get().copyOf(); subClusterFilter.getSubFilters().addAll( - new DescriptionFilter(eventStripe.getDescriptionLOD(), eventStripe.getDescription(), DescriptionFilter.FilterMode.INCLUDE), + new DescriptionFilter(eventStripe.getDescriptionLoD(), eventStripe.getDescription(), DescriptionFilter.FilterMode.INCLUDE), new TypeFilter(getEventType())); return subClusterFilter; } @@ -415,8 +415,8 @@ final public class EventStripeNode extends StackPane { bundle.equals(subNode.getEventStripe())) ); subNodePane.getChildren().clear(); - if (descLOD.get().withRelativeDetail(relativeDetail) == eventStripe.getDescriptionLOD()) { - descLOD.set(eventStripe.getDescriptionLOD()); + if (descLOD.get().withRelativeDetail(relativeDetail) == eventStripe.getDescriptionLoD()) { + descLOD.set(eventStripe.getDescriptionLoD()); clustersHBox.setVisible(true); chart.setRequiresLayout(true); chart.requestChartLayout(); @@ -448,7 +448,7 @@ final public class EventStripeNode extends StackPane { DescriptionLOD next = loadedDescriptionLoD; do { loadedDescriptionLoD = next; - if (loadedDescriptionLoD == eventStripe.getDescriptionLOD()) { + if (loadedDescriptionLoD == eventStripe.getDescriptionLoD()) { return Collections.emptySet(); } bundles = eventsModel.getEventClusters(zoomParams.withDescrLOD(loadedDescriptionLoD)).stream() @@ -609,7 +609,7 @@ final public class EventStripeNode extends StackPane { loadSubBundles(DescriptionLOD.RelativeDetail.LESS); } }); - disabledProperty().bind(Bindings.createBooleanBinding(() -> nonNull(eventStripe) && descLOD.get() == eventStripe.getDescriptionLOD(), descLOD)); + disabledProperty().bind(Bindings.createBooleanBinding(() -> nonNull(eventStripe) && descLOD.get() == eventStripe.getDescriptionLoD(), descLOD)); } } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java index 2fb4823d78..b3e80eb827 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventDescriptionTreeItem.java @@ -43,11 +43,11 @@ class EventDescriptionTreeItem extends NavTreeItem { EventDescriptionTreeItem(EventBundle g) { bundle = g; - setValue(new NavTreeNode(g.getEventType().getBaseType(), g.getDescription(), g.getDescriptionLOD(), g.getEventIDs().size())); + setValue(g); } @Override - public int getCount() { + public long getCount() { return getValue().getCount(); } @@ -68,15 +68,23 @@ class EventDescriptionTreeItem extends NavTreeItem { } @Override - public void resort(Comparator> comp) { + public void resort(Comparator> comp) { FXCollections.sort(getChildren(), comp); } @Override public NavTreeItem findTreeItemForEvent(EventBundle t) { - if (getValue().getType().getBaseType() == t.getEventType().getBaseType() && getValue().getDescription().equals(t.getDescription())) { + if (getValue().getEventType() == t.getEventType() + && getValue().getDescription().equals(t.getDescription())) { return this; + } else { + for (EventDescriptionTreeItem child : childMap.values()) { + final NavTreeItem findTreeItemForEvent = child.findTreeItemForEvent(t); + if (findTreeItemForEvent != null) { + return findTreeItemForEvent; + } + } } return null; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java index 922359fc2f..f1b91bf202 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java @@ -33,14 +33,14 @@ class EventTypeTreeItem extends NavTreeItem { */ private final Map childMap = new ConcurrentHashMap<>(); - private final Comparator> comparator = TreeComparator.Description; + private final Comparator> comparator = TreeComparator.Description; EventTypeTreeItem(EventBundle g) { - setValue(new NavTreeNode(g.getEventType().getBaseType(), g.getEventType().getBaseType().getDisplayName(), g.getDescriptionLOD(), 0)); + setValue(g); } @Override - public int getCount() { + public long getCount() { return getValue().getCount(); } @@ -62,10 +62,10 @@ class EventTypeTreeItem extends NavTreeItem { @Override public NavTreeItem findTreeItemForEvent(EventBundle t) { - if (t.getEventType().getBaseType() == getValue().getType().getBaseType()) { + if (t.getEventType().getBaseType() == getValue().getEventType().getBaseType()) { - for (TreeItem child : getChildren()) { - final NavTreeItem findTreeItemForEvent = ((NavTreeItem) child).findTreeItemForEvent(t); + for (EventDescriptionTreeItem child : childMap.values()) { + final NavTreeItem findTreeItemForEvent = child.findTreeItemForEvent(t); if (findTreeItemForEvent != null) { return findTreeItemForEvent; } @@ -75,7 +75,7 @@ class EventTypeTreeItem extends NavTreeItem { } @Override - public void resort(Comparator> comp) { + public void resort(Comparator> comp) { FXCollections.sort(getChildren(), comp); } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java index 8dd8a4193a..e16aa157ce 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java @@ -20,8 +20,12 @@ package org.sleuthkit.autopsy.timeline.ui.detailview.tree; import com.google.common.collect.ImmutableList; import java.util.Arrays; +import java.util.Collection; import java.util.Comparator; +import java.util.Objects; +import javafx.beans.InvalidationListener; import javafx.beans.Observable; +import javafx.collections.ListChangeListener; import javafx.fxml.FXML; import javafx.scene.control.ComboBox; import javafx.scene.control.ContextMenu; @@ -45,8 +49,15 @@ import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.TimeLineView; import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; +import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter; import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane; +/** + * Shows all {@link EventBundles} from the assigned {@link DetailViewPane} in a + * tree organized by type and then description. Hidden bundles are shown grayed + * out. Right clicking on a item in the tree shows a context menu to show/hide + * it. + */ public class NavPanel extends BorderPane implements TimeLineView { private TimeLineController controller; @@ -56,34 +67,40 @@ public class NavPanel extends BorderPane implements TimeLineView { private DetailViewPane detailViewPane; @FXML - private TreeView< NavTreeNode> eventsTree; + private TreeView eventsTree; @FXML private Label eventsTreeLabel; @FXML - private ComboBox>> sortByBox; + private ComboBox>> sortByBox; public NavPanel() { - FXMLConstructor.construct(this, "NavPanel.fxml"); // NON-NLS + FXMLConstructor.construct(this, "NavPanel.fxml"); // NON-NLS } public void setDetailViewPane(DetailViewPane detailViewPane) { this.detailViewPane = detailViewPane; detailViewPane.setSelectionModel(eventsTree.getSelectionModel()); - setRoot(); + detailViewPane.getEventBundles().addListener((Observable observable) -> { setRoot(); }); + setRoot(); + detailViewPane.getSelectedNodes().addListener((Observable observable) -> { eventsTree.getSelectionModel().clearSelection(); detailViewPane.getSelectedNodes().forEach(eventStripeNode -> { - eventsTree.getSelectionModel().select(((NavTreeItem) eventsTree.getRoot()).findTreeItemForEvent(eventStripeNode.getEventStripe())); + eventsTree.getSelectionModel().select(getRoot().findTreeItemForEvent(eventStripeNode.getEventStripe())); }); }); } + private NavTreeItem getRoot() { + return (NavTreeItem) eventsTree.getRoot(); + } + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private void setRoot() { RootItem root = new RootItem(); @@ -113,78 +130,105 @@ public class NavPanel extends BorderPane implements TimeLineView { sortByBox.getItems().setAll(Arrays.asList(TreeComparator.Description, TreeComparator.Count)); sortByBox.getSelectionModel().select(TreeComparator.Description); sortByBox.getSelectionModel().selectedItemProperty().addListener((Observable o) -> { - ((NavTreeItem) eventsTree.getRoot()).resort(sortByBox.getSelectionModel().getSelectedItem()); + getRoot().resort(sortByBox.getSelectionModel().getSelectedItem()); }); eventsTree.setShowRoot(false); - eventsTree.setCellFactory((TreeView p) -> new EventTreeCell()); + eventsTree.setCellFactory((TreeView p) -> new EventBundleTreeCell()); eventsTree.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); eventsTreeLabel.setText(NbBundle.getMessage(this.getClass(), "NavPanel.eventsTreeLabel.text")); } /** - * A tree cell to display {@link NavTreeNode}s. Shows the description, and + * A tree cell to display {@link EventBundle}s. Shows the description, and * count, as well a a "legend icon" for the event type. */ - private class EventTreeCell extends TreeCell { + private class EventBundleTreeCell extends TreeCell { + + private static final double HIDDEN_MULTIPLIER = .6; + private final Rectangle rect = new Rectangle(24, 24); + private final ImageView imageView = new ImageView(); + private InvalidationListener filterStateChangeListener; + + EventBundleTreeCell() { + rect.setArcHeight(5); + rect.setArcWidth(5); + rect.setStrokeWidth(2); + } @Override - protected void updateItem(NavTreeNode item, boolean empty) { + protected void updateItem(EventBundle item, boolean empty) { super.updateItem(item, empty); if (item == null || empty) { setText(null); setTooltip(null); setGraphic(null); setContextMenu(null); + deRegisterListeners(controller.getQuickHideMasks()); } else { + filterStateChangeListener = (filterState) -> updateHiddenState(item); + controller.getQuickHideMasks().addListener((ListChangeListener.Change listChange) -> { + while (listChange.next()) { + deRegisterListeners(listChange.getRemoved()); + registerListeners(listChange.getAddedSubList(), item); + } + updateHiddenState(item); + }); + registerListeners(controller.getQuickHideMasks(), item); String text = item.getDescription() + " (" + item.getCount() + ")"; // NON-NLS - TreeItem parent = getTreeItem().getParent(); + TreeItem parent = getTreeItem().getParent(); if (parent != null && parent.getValue() != null && (parent instanceof EventDescriptionTreeItem)) { text = StringUtils.substringAfter(text, parent.getValue().getDescription()); } setText(text); setTooltip(new Tooltip(text)); - Rectangle rect = new Rectangle(24, 24); - rect.setArcHeight(5); - rect.setArcWidth(5); - rect.setStrokeWidth(2); - ImageView imageView = new ImageView(item.getType().getFXImage()); - + imageView.setImage(item.getEventType().getFXImage()); setGraphic(new StackPane(rect, imageView)); - controller.getQuickHideMasks().addListener((Observable observable) -> { - configureHiddenState(item, rect, imageView); - }); - configureHiddenState(item, rect, imageView); - + updateHiddenState(item); } - } - private void configureHiddenState(NavTreeNode item, Rectangle rect, ImageView imageView) { - TreeItem treeItem = getTreeItem(); + private void registerListeners(Collection filters, EventBundle item) { + for (DescriptionFilter filter : filters) { + if (filter.getDescription().equals(item.getDescription())) { + filter.activeProperty().addListener(filterStateChangeListener); + } + } + } + + private void deRegisterListeners(Collection filters) { + if (Objects.nonNull(filterStateChangeListener)) { + for (DescriptionFilter filter : filters) { + filter.activeProperty().removeListener(filterStateChangeListener); + } + } + } + + private void updateHiddenState(EventBundle item) { + TreeItem treeItem = getTreeItem(); ContextMenu newMenu; - if (controller.getQuickHideMasks().stream().anyMatch(mask -> mask.getDescription().equals(item.getDescription()))) { - setTextFill(Color.gray(0, .6)); - imageView.setOpacity(.6); - rect.setStroke(item.getType().getColor().deriveColor(0, .6, 1, .6)); - rect.setFill(item.getType().getColor().deriveColor(0, .6, .6, 0.1)); + if (controller.getQuickHideMasks().stream().anyMatch((mask) -> mask.isActive() && mask.getDescription().equals(item.getDescription()))) { if (treeItem != null) { treeItem.setExpanded(false); } - newMenu = ActionUtils.createContextMenu(ImmutableList.of(detailViewPane.newUnhideDescriptionAction(item.getDescription(), item.getDescriptionLod()))); + setTextFill(Color.gray(0, HIDDEN_MULTIPLIER)); + imageView.setOpacity(HIDDEN_MULTIPLIER); + rect.setStroke(item.getEventType().getColor().deriveColor(0, HIDDEN_MULTIPLIER, 1, HIDDEN_MULTIPLIER)); + rect.setFill(item.getEventType().getColor().deriveColor(0, HIDDEN_MULTIPLIER, HIDDEN_MULTIPLIER, 0.1)); + newMenu = ActionUtils.createContextMenu(ImmutableList.of(detailViewPane.newUnhideDescriptionAction(item.getDescription(), item.getDescriptionLoD()))); } else { setTextFill(Color.BLACK); imageView.setOpacity(1); - rect.setStroke(item.getType().getColor()); - rect.setFill(item.getType().getColor().deriveColor(0, 1, 1, 0.1)); - newMenu = ActionUtils.createContextMenu(ImmutableList.of(detailViewPane.newHideDescriptionAction(item.getDescription(), item.getDescriptionLod()))); + rect.setStroke(item.getEventType().getColor()); + rect.setFill(item.getEventType().getColor().deriveColor(0, 1, 1, 0.1)); + newMenu = ActionUtils.createContextMenu(ImmutableList.of(detailViewPane.newHideDescriptionAction(item.getDescription(), item.getDescriptionLoD()))); } - if (treeItem instanceof EventDescriptionTreeItem) { setContextMenu(newMenu); } else { setContextMenu(null); } } + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java index 4c7ed53d59..d1df7a5b38 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java @@ -25,14 +25,14 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; /** * A node in the nav tree. Manages inserts and resorts. Has parents and * children. Does not have graphical properties these are configured in - * {@link EventTreeCell}. Each GroupTreeItem has a NavTreeNode which has a type, - * description , and count + * {@link EventTreeCell}. Each NavTreeItem has a EventBundle which has a type, + * description , count, etc. */ -abstract class NavTreeItem extends TreeItem { +abstract class NavTreeItem extends TreeItem { - abstract int getCount(); + abstract long getCount(); - abstract void resort(Comparator> comp); + abstract void resort(Comparator> comp); abstract NavTreeItem findTreeItemForEvent(EventBundle t); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeNode.java deleted file mode 100644 index 54d9397e3b..0000000000 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeNode.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2014 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.detailview.tree; - -import javax.annotation.concurrent.Immutable; -import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; -import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; - -/** - * The data item for the nav tree. Represents a combination of type and - * description, as well as the corresponding number of events - */ -@Immutable -public class NavTreeNode { - - final private EventType type; - final private String Description; - private final DescriptionLOD descriptionLoD; - - - final private int count; - - public NavTreeNode(EventType type, String Description, DescriptionLOD descriptionLoD, int count) { - this.type = type; - this.Description = Description; - this.descriptionLoD = descriptionLoD; - this.count = count; - } - - public DescriptionLOD getDescriptionLod() { - return descriptionLoD; - } - - public EventType getType() { - return type; - } - - public String getDescription() { - return Description; - } - - public int getCount() { - return count; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java index d773f35dd7..a96dfaae20 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java @@ -47,7 +47,7 @@ class RootItem extends NavTreeItem { } @Override - public int getCount() { + public long getCount() { return getValue().getCount(); } @@ -83,16 +83,14 @@ class RootItem extends NavTreeItem { } @Override - public void resort(Comparator> comp) { - childMap.values().forEach((ti) -> { - ti.resort(comp); - }); + public void resort(Comparator> comp) { + childMap.values().forEach(ti -> ti.resort(comp)); } @Override public NavTreeItem findTreeItemForEvent(EventBundle t) { - for (TreeItem child : getChildren()) { - final NavTreeItem findTreeItemForEvent = ((NavTreeItem) child).findTreeItemForEvent(t); + for (EventTypeTreeItem child : childMap.values()) { + final NavTreeItem findTreeItemForEvent = child.findTreeItemForEvent(t); if (findTreeItemForEvent != null) { return findTreeItemForEvent; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/TreeComparator.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/TreeComparator.java index 2ec4b0fa1a..aad194f61c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/TreeComparator.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/TreeComparator.java @@ -20,27 +20,27 @@ package org.sleuthkit.autopsy.timeline.ui.detailview.tree; import java.util.Comparator; import javafx.scene.control.TreeItem; +import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; -enum TreeComparator implements Comparator> { +enum TreeComparator implements Comparator> { Description { @Override - public int compare(TreeItem o1, TreeItem o2) { + public int compare(TreeItem o1, TreeItem o2) { return o1.getValue().getDescription().compareTo(o2.getValue().getDescription()); } }, Count { @Override - public int compare(TreeItem o1, TreeItem o2) { - - return -Integer.compare(o1.getValue().getCount(), o2.getValue().getCount()); + public int compare(TreeItem o1, TreeItem o2) { + return Long.compare(o2.getValue().getCount(), o1.getValue().getCount()); } }, Type { @Override - public int compare(TreeItem o1, TreeItem o2) { - return EventType.getComparator().compare(o1.getValue().getType(), o2.getValue().getType()); + public int compare(TreeItem o1, TreeItem o2) { + return EventType.getComparator().compare(o1.getValue().getEventType(), o2.getValue().getEventType()); } }; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterCheckBoxCellFactory.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterCheckBoxCellFactory.java index 54d848b988..b367ade07c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterCheckBoxCellFactory.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterCheckBoxCellFactory.java @@ -9,7 +9,7 @@ import java.util.function.Supplier; import javafx.beans.property.SimpleBooleanProperty; import javafx.scene.control.CheckBox; import javafx.scene.control.IndexedCell; -import org.sleuthkit.autopsy.coreutils.AbstractFXCellFactory; +import org.sleuthkit.autopsy.timeline.ui.AbstractFXCellFactory; import org.sleuthkit.autopsy.timeline.filters.AbstractFilter; class FilterCheckBoxCellFactory extends AbstractFXCellFactory { @@ -26,6 +26,7 @@ class FilterCheckBoxCellFactory extends AbstractFXCell if (disabledProperty != null) { checkBox.disableProperty().unbindBidirectional(disabledProperty); } + if (item == null) { cell.setText(null); cell.setGraphic(null); @@ -38,5 +39,4 @@ class FilterCheckBoxCellFactory extends AbstractFXCell cell.setGraphic(checkBox); } } - } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.fxml b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.fxml index 4cb165770c..6f6d17b81a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.fxml +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.fxml @@ -1,5 +1,6 @@ + @@ -27,7 +28,7 @@
- + @@ -38,7 +39,7 @@ - + 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 cabc79dbc9..6ee97c73a6 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java @@ -31,6 +31,8 @@ import javafx.scene.control.ListCell; import javafx.scene.control.ListView; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; +import javafx.scene.control.SplitPane; +import javafx.scene.control.TitledPane; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeTableColumn; import javafx.scene.control.TreeTableRow; @@ -44,6 +46,7 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.TimeLineView; +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; @@ -81,12 +84,17 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { @FXML private ListView hiddenDescriptionsListView; + @FXML + private TitledPane hiddenDescriptionsPane; + @FXML + private SplitPane splitPane; private FilteredEventsModel filteredEvents; private TimeLineController controller; private final ObservableMap expansionMap = FXCollections.observableHashMap(); + private double position; @FXML @NbBundle.Messages({ @@ -196,22 +204,59 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { hiddenDescriptionsListView.setItems(controller.getQuickHideMasks()); hiddenDescriptionsListView.setCellFactory((ListView param) -> { final ListCell forList = new FilterCheckBoxCellFactory().forList(); - forList.setContextMenu(new ContextMenu(new MenuItem("unhide and remove from list") { - { - setOnAction((ActionEvent event) -> { - controller.getQuickHideMasks().remove(forList.getItem()); - }); + + forList.itemProperty().addListener((Observable observable) -> { + if (forList.getItem() == null) { + forList.setContextMenu(null); + } else { + forList.setContextMenu(new ContextMenu(new MenuItem() { + { + forList.getItem().selectedProperty().addListener((observable, wasSelected, isSelected) -> { + configureText(isSelected); + }); + + configureText(forList.getItem().selectedProperty().get()); + setOnAction((ActionEvent event) -> { + controller.getQuickHideMasks().remove(forList.getItem()); + }); + } + + private void configureText(Boolean newValue) { + if (newValue) { + setText("Unhide and remove from list"); + } else { + setText("Remove from list"); + } + } + })); } - })); + }); + return forList; }); +// hiddenDescriptionsPane.setContent(null); controller.viewModeProperty().addListener(observable -> { applyFilters(); + if (controller.viewModeProperty().get() == VisualizationMode.COUNTS) { + position = splitPane.getDividerPositions()[0]; + splitPane.setDividerPositions(1); + hiddenDescriptionsPane.setExpanded(false); + hiddenDescriptionsPane.setCollapsible(false); + hiddenDescriptionsPane.setDisable(true); + } else { + splitPane.setDividerPositions(position); + hiddenDescriptionsPane.setDisable(false); + hiddenDescriptionsPane.setCollapsible(true); + hiddenDescriptionsPane.setExpanded(true); + hiddenDescriptionsPane.setCollapsible(false); + + } }); } @Override + public void setModel(FilteredEventsModel filteredEvents) { this.filteredEvents = filteredEvents; this.filteredEvents.eventTypeZoomProperty().addListener((Observable observable) -> { From 7890461114dbf772954e0ff0a35f038436e9e85a Mon Sep 17 00:00:00 2001 From: jmillman Date: Thu, 1 Oct 2015 15:32:56 -0400 Subject: [PATCH 27/29] remove obsolete Interface --- .../ui/detailview/DetailViewNode.java | 68 ------------------- .../ui/detailview/EventDetailChart.java | 4 +- 2 files changed, 1 insertion(+), 71 deletions(-) delete mode 100644 Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java deleted file mode 100644 index ccb178e8c7..0000000000 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewNode.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.sleuthkit.autopsy.timeline.ui.detailview; - -import java.util.Comparator; -import java.util.List; -import java.util.Set; -import javafx.scene.Node; -import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; -import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; - -/** - * - */ -public interface DetailViewNode> { - - void setDescriptionVisibility(DescriptionVisibility get); - - List getSubBundleNodes(); - - List getSubNodes(); - - void setSpanWidths(List spanWidths); - - void setDescriptionWidth(double max); - - EventBundle getEventBundle(); - - /** - * apply the 'effect' to visually indicate highlighted nodes - * - * @param applied true to apply the highlight 'effect', false to remove it - */ - void applyHighlightEffect(boolean applied); - - void applySelectionEffect(boolean applied); - - default String getDescription() { - return getEventBundle().getDescription(); - } - - default EventType getEventType() { - return getEventBundle().getEventType(); - } - - default Set getEventIDs() { - return getEventBundle().getEventIDs(); - } - - default long getStartMillis() { - return getEventBundle().getStartMillis(); - } - - default long getEndMillis() { - return getEventBundle().getEndMillis(); - } - - static class StartTimeComparator implements Comparator> { - - @Override - public int compare(DetailViewNode o1, DetailViewNode o2) { - return Long.compare(o1.getStartMillis(), o2.getStartMillis()); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java index bae56528d8..ce3a210758 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java @@ -730,9 +730,7 @@ public final class EventDetailChart extends XYChart impl return alternateLayout; } - void applySelectionEffect(DetailViewNode c1, Boolean selected) { - c1.applySelectionEffect(selected); - } + static private class DetailIntervalSelector extends IntervalSelector { From ddfe4381a338ec41be11279ceac5b96e2d2198cd Mon Sep 17 00:00:00 2001 From: jmillman Date: Thu, 1 Oct 2015 15:51:08 -0400 Subject: [PATCH 28/29] minor cleanup - improve comments - rename DescriptionLOD to DescriptionLoD - use new isActive proprty - rename quick hide mask to quick hide filter - remove un used class FiltersChangedEvent --- .../autopsy/timeline/TimeLineController.java | 14 +- .../timeline/datamodel/EventBundle.java | 4 +- .../timeline/datamodel/EventCluster.java | 17 +- .../timeline/datamodel/EventStripe.java | 6 +- .../datamodel/FilteredEventsModel.java | 16 +- .../timeline/datamodel/TimeLineEvent.java | 19 ++- .../autopsy/timeline/db/EventDB.java | 6 +- .../autopsy/timeline/db/SQLHelper.java | 37 +++-- .../timeline/events/FiltersChangedEvent.java | 14 -- .../timeline/filters/DescriptionFilter.java | 8 +- .../autopsy/timeline/filters/Filter.java | 5 + .../timeline/ui/AbstractFXCellFactory.java | 28 ++-- .../ui/detailview/DetailViewPane.java | 6 +- .../ui/detailview/DetailViewSettingsPane.fxml | 150 +++++++++--------- .../ui/detailview/EventDetailChart.java | 29 ++-- .../ui/detailview/EventStripeNode.java | 26 +-- .../timeline/ui/detailview/tree/NavPanel.java | 11 +- .../timeline/ui/filtering/FilterSetPanel.java | 4 +- ...escriptionLOD.java => DescriptionLoD.java} | 18 +-- .../autopsy/timeline/zooming/ZoomParams.java | 10 +- .../timeline/zooming/ZoomSettingsPane.java | 8 +- CoreLibs/nbproject/project.xml | 5 + 22 files changed, 222 insertions(+), 219 deletions(-) delete mode 100644 Core/src/org/sleuthkit/autopsy/timeline/events/FiltersChangedEvent.java rename Core/src/org/sleuthkit/autopsy/timeline/zooming/{DescriptionLOD.java => DescriptionLoD.java} (80%) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index b175c718df..8367066591 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -80,7 +80,7 @@ import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter; import org.sleuthkit.autopsy.timeline.filters.RootFilter; import org.sleuthkit.autopsy.timeline.filters.TypeFilter; import org.sleuthkit.autopsy.timeline.utils.IntervalUtils; -import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel; import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; import org.sleuthkit.datamodel.SleuthkitCase; @@ -138,10 +138,10 @@ public class TimeLineController { private final Case autoCase; @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - private final ObservableList quickHideMasks = FXCollections.observableArrayList(); + private final ObservableList quickHideMaskFilters = FXCollections.observableArrayList(); - public ObservableList getQuickHideMasks() { - return quickHideMasks; + public ObservableList getQuickHideFilters() { + return quickHideMaskFilters; } /** @@ -264,7 +264,7 @@ public class TimeLineController { InitialZoomState = new ZoomParams(filteredEvents.getSpanningInterval(), EventTypeZoomLevel.BASE_TYPE, filteredEvents.filterProperty().get(), - DescriptionLOD.SHORT); + DescriptionLoD.SHORT); historyManager.advance(InitialZoomState); } @@ -564,12 +564,12 @@ public class TimeLineController { @NbBundle.Messages({"# {0} - the number of events", "Timeline.pushDescrLOD.confdlg.msg=You are about to show details for {0} events." + " This might be very slow or even crash Autopsy.\n\nDo you want to continue?"}) - synchronized public boolean pushDescrLOD(DescriptionLOD newLOD) { + synchronized public boolean pushDescrLOD(DescriptionLoD newLOD) { Map eventCounts = filteredEvents.getEventCounts(filteredEvents.zoomParametersProperty().get().getTimeRange()); final Long count = eventCounts.values().stream().reduce(0l, Long::sum); boolean shouldContinue = true; - if (newLOD == DescriptionLOD.FULL && count > 10_000) { + if (newLOD == DescriptionLoD.FULL && count > 10_000) { String format = NumberFormat.getInstance().format(count); int showConfirmDialog = JOptionPane.showConfirmDialog(mainFrame, diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java index ddfbcd82e9..8714452725 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java @@ -22,7 +22,7 @@ import com.google.common.collect.Range; import java.util.Optional; import java.util.Set; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; -import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; /** * @@ -31,7 +31,7 @@ public interface EventBundle { String getDescription(); - DescriptionLOD getDescriptionLoD(); + DescriptionLoD getDescriptionLoD(); Set getEventIDs(); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java index 0fc5a7ca42..57d5cec0db 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java @@ -28,7 +28,7 @@ import javax.annotation.concurrent.Immutable; import org.joda.time.Interval; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.utils.IntervalUtils; -import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; /** * Represents a set of other (TimeLineEvent) events clustered together. All the @@ -39,13 +39,13 @@ import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; public class EventCluster implements EventBundle { /** - * merge two aggregate events into one new aggregate event. + * merge two event clusters into one new event cluster. * * @param cluster1 * @param cluster2 * - * @return a new aggregate event that is the result of merging the given - * events + * @return a new event cluster that is the result of merging the given + * events clusters */ public static EventCluster merge(EventCluster cluster1, EventCluster cluster2) { if (cluster1.getEventType() != cluster2.getEventType()) { @@ -82,7 +82,7 @@ public class EventCluster implements EventBundle { /** * the description level of detail that the events were clustered at. */ - private final DescriptionLOD lod; + private final DescriptionLoD lod; /** * the set of ids of the clustered events @@ -101,7 +101,7 @@ public class EventCluster implements EventBundle { */ private final Set hashHits; - private EventCluster(Interval spanningInterval, EventType type, Set eventIDs, Set hashHits, Set tagged, String description, DescriptionLOD lod, EventBundle parent) { + private EventCluster(Interval spanningInterval, EventType type, Set eventIDs, Set hashHits, Set tagged, String description, DescriptionLoD lod, EventBundle parent) { this.span = spanningInterval; this.type = type; @@ -113,7 +113,7 @@ public class EventCluster implements EventBundle { this.parent = parent; } - public EventCluster(Interval spanningInterval, EventType type, Set eventIDs, Set hashHits, Set tagged, String description, DescriptionLOD lod) { + public EventCluster(Interval spanningInterval, EventType type, Set eventIDs, Set hashHits, Set tagged, String description, DescriptionLoD lod) { this(spanningInterval, type, eventIDs, hashHits, tagged, description, lod, null); } @@ -162,7 +162,7 @@ public class EventCluster implements EventBundle { } @Override - public DescriptionLOD getDescriptionLoD() { + public DescriptionLoD getDescriptionLoD() { return lod; } @@ -195,5 +195,4 @@ public class EventCluster implements EventBundle { return new EventCluster(span, type, eventIDs, hashHits, tagged, description, lod, parent); } - } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java index a3dbb6bc1c..8a465de1cd 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java @@ -18,7 +18,7 @@ import java.util.Set; import javax.annotation.concurrent.Immutable; import org.python.google.common.base.Objects; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; -import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; /** * A 'collection' of {@link EventCluster}s, all having the same type, @@ -55,7 +55,7 @@ public final class EventStripe implements EventBundle { /** * the description level of detail that the events were clustered at. */ - private final DescriptionLOD lod; + private final DescriptionLoD lod; /** * the set of ids of the events @@ -118,7 +118,7 @@ public final class EventStripe implements EventBundle { } @Override - public DescriptionLOD getDescriptionLoD() { + public DescriptionLoD getDescriptionLoD() { return lod; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java index 654c77a3db..6da5fea635 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java @@ -56,7 +56,7 @@ import org.sleuthkit.autopsy.timeline.filters.TagNameFilter; import org.sleuthkit.autopsy.timeline.filters.TagsFilter; import org.sleuthkit.autopsy.timeline.filters.TextFilter; import org.sleuthkit.autopsy.timeline.filters.TypeFilter; -import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel; import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -105,7 +105,7 @@ public final class FilteredEventsModel { private final ReadOnlyObjectWrapper< EventTypeZoomLevel> requestedTypeZoom = new ReadOnlyObjectWrapper<>(EventTypeZoomLevel.BASE_TYPE); @GuardedBy("this") - private final ReadOnlyObjectWrapper< DescriptionLOD> requestedLOD = new ReadOnlyObjectWrapper<>(DescriptionLOD.SHORT); + private final ReadOnlyObjectWrapper< DescriptionLoD> requestedLOD = new ReadOnlyObjectWrapper<>(DescriptionLoD.SHORT); @GuardedBy("this") private final ReadOnlyObjectWrapper requestedZoomParamters = new ReadOnlyObjectWrapper<>(); @@ -180,7 +180,7 @@ public final class FilteredEventsModel { return requestedTimeRange.getReadOnlyProperty(); } - synchronized public ReadOnlyObjectProperty descriptionLODProperty() { + synchronized public ReadOnlyObjectProperty descriptionLODProperty() { return requestedLOD.getReadOnlyProperty(); } @@ -192,7 +192,7 @@ public final class FilteredEventsModel { return requestedTypeZoom.getReadOnlyProperty(); } - synchronized public DescriptionLOD getDescriptionLOD() { + synchronized public DescriptionLoD getDescriptionLOD() { return requestedLOD.get(); } @@ -322,17 +322,15 @@ public final class FilteredEventsModel { } /** - * @param aggregation * - * @return a list of aggregated events that are within the requested time - * range and pass the requested filter, using the given aggregation - * to control the grouping of events + * @return a list of event clusters at the requested zoom levels that are + * within the requested time range and pass the requested filter */ public List getEventClusters() { final Interval range; final RootFilter filter; final EventTypeZoomLevel zoom; - final DescriptionLOD lod; + final DescriptionLoD lod; synchronized (this) { range = requestedTimeRange.get(); filter = requestedFilter.get(); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/TimeLineEvent.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/TimeLineEvent.java index 744311c321..2296edfbf8 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/TimeLineEvent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/TimeLineEvent.java @@ -22,7 +22,7 @@ import com.google.common.collect.ImmutableMap; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; -import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; import org.sleuthkit.datamodel.TskData; /** @@ -38,7 +38,7 @@ public class TimeLineEvent { private final long time; private final EventType subType; - private final ImmutableMap descriptions; + private final ImmutableMap descriptions; private final TskData.FileKnown known; private final boolean hashHit; @@ -50,10 +50,9 @@ public class TimeLineEvent { this.artifactID = artifactID == 0 ? null : artifactID; this.time = time; this.subType = type; - descriptions = ImmutableMap.of( - DescriptionLOD.FULL, fullDescription, - DescriptionLOD.MEDIUM, medDescription, - DescriptionLOD.SHORT, shortDescription); + descriptions = ImmutableMap.of(DescriptionLoD.FULL, fullDescription, + DescriptionLoD.MEDIUM, medDescription, + DescriptionLoD.SHORT, shortDescription); this.known = known; this.hashHit = hashHit; @@ -94,22 +93,22 @@ public class TimeLineEvent { } public String getFullDescription() { - return getDescription(DescriptionLOD.FULL); + return getDescription(DescriptionLoD.FULL); } public String getMedDescription() { - return getDescription(DescriptionLOD.MEDIUM); + return getDescription(DescriptionLoD.MEDIUM); } public String getShortDescription() { - return getDescription(DescriptionLOD.SHORT); + return getDescription(DescriptionLoD.SHORT); } public TskData.FileKnown getKnown() { return known; } - public String getDescription(DescriptionLOD lod) { + public String getDescription(DescriptionLoD lod) { return descriptions.get(lod); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java index 35d8a25c5d..ce248a2149 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java @@ -64,7 +64,7 @@ import static org.sleuthkit.autopsy.timeline.db.SQLHelper.useTagTablesHelper; import org.sleuthkit.autopsy.timeline.filters.RootFilter; import org.sleuthkit.autopsy.timeline.filters.TagsFilter; import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo; -import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel; import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; import org.sleuthkit.datamodel.SleuthkitCase; @@ -1046,7 +1046,7 @@ public class EventDB { //unpack params Interval timeRange = params.getTimeRange(); RootFilter filter = params.getFilter(); - DescriptionLOD descriptionLOD = params.getDescriptionLOD(); + DescriptionLoD descriptionLOD = params.getDescriptionLOD(); EventTypeZoomLevel typeZoomLevel = params.getTypeZoomLevel(); //ensure length of querried interval is not 0 @@ -1109,7 +1109,7 @@ public class EventDB { * * @throws SQLException */ - private EventCluster eventClusterHelper(ResultSet rs, boolean useSubTypes, DescriptionLOD descriptionLOD, TagsFilter filter) throws SQLException { + private EventCluster eventClusterHelper(ResultSet rs, boolean useSubTypes, DescriptionLoD descriptionLOD, TagsFilter filter) throws SQLException { Interval interval = new Interval(rs.getLong("min(time)") * 1000, rs.getLong("max(time)") * 1000, TimeLineController.getJodaTimeZone());// NON-NLS String eventIDsString = rs.getString("event_ids");// NON-NLS Set eventIDs = SQLHelper.unGroupConcat(eventIDsString, Long::valueOf); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java b/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java index 4c827c401d..65405d7930 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java @@ -43,9 +43,9 @@ import org.sleuthkit.autopsy.timeline.filters.TextFilter; import org.sleuthkit.autopsy.timeline.filters.TypeFilter; import org.sleuthkit.autopsy.timeline.filters.UnionFilter; import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo; -import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; -import static org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD.FULL; -import static org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD.MEDIUM; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; +import static org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD.FULL; +import static org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD.MEDIUM; import static org.sleuthkit.autopsy.timeline.zooming.TimeUnits.DAYS; import static org.sleuthkit.autopsy.timeline.zooming.TimeUnits.HOURS; import static org.sleuthkit.autopsy.timeline.zooming.TimeUnits.MINUTES; @@ -62,12 +62,12 @@ class SQLHelper { static String useHashHitTablesHelper(RootFilter filter) { HashHitsFilter hashHitFilter = filter.getHashHitsFilter(); - return hashHitFilter.isSelected() && false == hashHitFilter.isDisabled() ? " LEFT JOIN hash_set_hits " : " "; + return hashHitFilter.isActive() ? " LEFT JOIN hash_set_hits " : " "; } static String useTagTablesHelper(RootFilter filter) { TagsFilter tagsFilter = filter.getTagsFilter(); - return tagsFilter.isSelected() && false == tagsFilter.isDisabled() ? " LEFT JOIN tags " : " "; + return tagsFilter.isActive() ? " LEFT JOIN tags " : " "; } /** @@ -149,7 +149,7 @@ class SQLHelper { } private static String getSQLWhere(HideKnownFilter filter) { - if (filter.isSelected()) { + if (filter.isActive()) { return "(known_state IS NOT '" + TskData.FileKnown.KNOWN.getFileKnownValue() + "')"; // NON-NLS } else { return "1"; @@ -157,16 +157,16 @@ class SQLHelper { } private static String getSQLWhere(DescriptionFilter filter) { - if (filter.isSelected() && (false == filter.isDisabled())) { - return "(" + getDescriptionColumn(filter.getDescriptionLoD()) + (filter.getFilterMode() == DescriptionFilter.FilterMode.INCLUDE ? "" : " NOT") + " LIKE '" + filter.getDescription() + "' )"; // NON-NLS + if (filter.isActive()) { + String likeOrNotLike = (filter.getFilterMode() == DescriptionFilter.FilterMode.INCLUDE ? "" : " NOT") + " LIKE '"; + return "(" + getDescriptionColumn(filter.getDescriptionLoD()) + likeOrNotLike + filter.getDescription() + "' )"; // NON-NLS } else { return "1"; } } private static String getSQLWhere(TagsFilter filter) { - if (filter.isSelected() - && (false == filter.isDisabled()) + if (filter.isActive() && (filter.getSubFilters().isEmpty() == false)) { String tagNameIDs = filter.getSubFilters().stream() .filter((TagNameFilter t) -> t.isSelected() && !t.isDisabled()) @@ -181,8 +181,7 @@ class SQLHelper { } private static String getSQLWhere(HashHitsFilter filter) { - if (filter.isSelected() - && (false == filter.isDisabled()) + if (filter.isActive() && (filter.getSubFilters().isEmpty() == false)) { String hashSetIDs = filter.getSubFilters().stream() .filter((HashSetFilter t) -> t.isSelected() && !t.isDisabled()) @@ -195,7 +194,7 @@ class SQLHelper { } private static String getSQLWhere(DataSourceFilter filter) { - if (filter.isSelected()) { + if (filter.isActive()) { return "(datasource_id = '" + filter.getDataSourceID() + "')"; } else { return "1"; @@ -203,15 +202,15 @@ class SQLHelper { } private static String getSQLWhere(DataSourcesFilter filter) { - return (filter.isSelected()) ? "(datasource_id in (" + return (filter.isActive()) ? "(datasource_id in (" + filter.getSubFilters().stream() - .filter(AbstractFilter::isSelected) + .filter(AbstractFilter::isActive) .map((dataSourceFilter) -> String.valueOf(dataSourceFilter.getDataSourceID())) .collect(Collectors.joining(", ")) + "))" : "1"; } private static String getSQLWhere(TextFilter filter) { - if (filter.isSelected()) { + if (filter.isActive()) { if (StringUtils.isBlank(filter.getText())) { return "1"; } @@ -237,7 +236,7 @@ class SQLHelper { return "0"; } else if (typeFilter.getEventType() instanceof RootEventType) { if (typeFilter.getSubFilters().stream() - .allMatch(subFilter -> subFilter.isSelected() && subFilter.getSubFilters().stream().allMatch(Filter::isSelected))) { + .allMatch(subFilter -> subFilter.isActive() && subFilter.getSubFilters().stream().allMatch(Filter::isActive))) { return "1"; //then collapse clause to true } } @@ -245,7 +244,7 @@ class SQLHelper { } private static List getActiveSubTypes(TypeFilter filter) { - if (filter.isSelected()) { + if (filter.isActive()) { if (filter.getSubFilters().isEmpty()) { return Collections.singletonList(RootEventType.allTypes.indexOf(filter.getEventType())); } else { @@ -285,7 +284,7 @@ class SQLHelper { } } - static String getDescriptionColumn(DescriptionLOD lod) { + static String getDescriptionColumn(DescriptionLoD lod) { switch (lod) { case FULL: return "full_description"; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/events/FiltersChangedEvent.java b/Core/src/org/sleuthkit/autopsy/timeline/events/FiltersChangedEvent.java deleted file mode 100644 index 6e83ca1306..0000000000 --- a/Core/src/org/sleuthkit/autopsy/timeline/events/FiltersChangedEvent.java +++ /dev/null @@ -1,14 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ - -package org.sleuthkit.autopsy.timeline.events; - -/** - * - */ -public class FiltersChangedEvent { - -} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java index 2bd8d595c2..5ac569c6f9 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/DescriptionFilter.java @@ -19,11 +19,11 @@ package org.sleuthkit.autopsy.timeline.filters; import java.util.Objects; -import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; public class DescriptionFilter extends AbstractFilter { - private final DescriptionLOD descriptionLoD; + private final DescriptionLoD descriptionLoD; private final String description; private final FilterMode filterMode; @@ -31,7 +31,7 @@ public class DescriptionFilter extends AbstractFilter { return filterMode; } - public DescriptionFilter(DescriptionLOD descriptionLoD, String description, FilterMode filterMode) { + public DescriptionFilter(DescriptionLoD descriptionLoD, String description, FilterMode filterMode) { this.descriptionLoD = descriptionLoD; this.description = description; this.filterMode = filterMode; @@ -58,7 +58,7 @@ public class DescriptionFilter extends AbstractFilter { /** * @return the descriptionLoD */ - public DescriptionLOD getDescriptionLoD() { + public DescriptionLoD getDescriptionLoD() { return descriptionLoD; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java index 03c1c01efa..dc44c8912b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/Filter.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.timeline.filters; +import javafx.beans.binding.BooleanBinding; import javafx.beans.property.SimpleBooleanProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -79,4 +80,8 @@ public interface Filter { SimpleBooleanProperty getDisabledProperty(); boolean isDisabled(); + + boolean isActive(); + + BooleanBinding activeProperty(); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractFXCellFactory.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractFXCellFactory.java index 1a049a4e67..dadc14f121 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractFXCellFactory.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractFXCellFactory.java @@ -1,14 +1,20 @@ -/** - * ************************************************************************* - ** This data and information is proprietary to, and a valuable trade secret * - * of, Basis Technology Corp. It is given in confidence by Basis Technology * - * and may only be used as permitted under the license agreement under which * - * it has been distributed, and in no other way. * * Copyright (c) 2014 Basis - * Technology Corp. All rights reserved. * * The technical data and information - * provided herein are provided with * `limited rights', and the computer - * software provided herein is provided * with `restricted rights' as those - * terms are defined in DAR and ASPR * 7-104.9(a). - * ************************************************************************* +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java index 744a9a6cb2..517cddde4e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java @@ -75,7 +75,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization; import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo; -import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; /** * Controller class for a {@link EventDetailChart} based implementation of a @@ -468,11 +468,11 @@ public class DetailViewPane extends AbstractVisualization - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java index ce3a210758..da07263e9f 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventDetailChart.java @@ -82,7 +82,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.filters.AbstractFilter; import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter; import org.sleuthkit.autopsy.timeline.ui.TimeLineChart; -import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; /** * Custom implementation of {@link XYChart} to graph events on a horizontal @@ -326,7 +326,7 @@ public final class EventDetailChart extends XYChart impl public synchronized void setController(TimeLineController controller) { this.controller = controller; setModel(this.controller.getEventsModel()); - getController().getQuickHideMasks().addListener(layoutInvalidationListener); + getController().getQuickHideFilters().addListener(layoutInvalidationListener); } @Override @@ -478,18 +478,18 @@ public final class EventDetailChart extends XYChart impl double minY = 0; for (Series series : sortedSeriesList) { hiddenPartition = series.getData().stream().map(Data::getNode).map(EventStripeNode.class::cast) - .collect(Collectors.partitioningBy(node -> getController().getQuickHideMasks().stream() + .collect(Collectors.partitioningBy(node -> getController().getQuickHideFilters().stream() .filter(AbstractFilter::isActive) - .anyMatch(mask -> mask.getDescription().equals(node.getDescription())))); + .anyMatch(filter -> filter.getDescription().equals(node.getDescription())))); layoutNodesHelper(hiddenPartition.get(true), hiddenPartition.get(false), minY, 0); minY = maxY.get(); } } else { hiddenPartition = stripeNodeMap.values().stream() - .collect(Collectors.partitioningBy(node -> getController().getQuickHideMasks().stream() + .collect(Collectors.partitioningBy(node -> getController().getQuickHideFilters().stream() .filter(AbstractFilter::isActive) - .anyMatch(mask -> mask.getDescription().equals(node.getDescription())))); + .anyMatch(filter -> filter.getDescription().equals(node.getDescription())))); layoutNodesHelper(hiddenPartition.get(true), hiddenPartition.get(false), 0, 0); } setCursor(null); @@ -607,8 +607,9 @@ public final class EventDetailChart extends XYChart impl List subNodes = stripeNode.getSubNodes(); if (subNodes.isEmpty() == false) { Map> hiddenPartition = subNodes.stream() - .collect(Collectors.partitioningBy(testNode -> getController().getQuickHideMasks().stream() - .anyMatch(mask -> mask.getDescription().equals(testNode.getDescription())))); + .collect(Collectors.partitioningBy(testNode -> getController().getQuickHideFilters().stream() + .filter(AbstractFilter::isActive) + .anyMatch(filter -> filter.getDescription().equals(testNode.getDescription())))); layoutNodesResultHeight = layoutNodesHelper(hiddenPartition.get(true), hiddenPartition.get(false), minY, rawDisplayPosition); } @@ -730,8 +731,6 @@ public final class EventDetailChart extends XYChart impl return alternateLayout; } - - static private class DetailIntervalSelector extends IntervalSelector { DetailIntervalSelector(double x, double height, Axis axis, TimeLineController controller) { @@ -781,7 +780,7 @@ public final class EventDetailChart extends XYChart impl class HideDescriptionAction extends Action { - HideDescriptionAction(String description, DescriptionLOD descriptionLoD) { + HideDescriptionAction(String description, DescriptionLoD descriptionLoD) { super("Hide"); setGraphic(new ImageView(HIDE)); setEventHandler((ActionEvent t) -> { @@ -790,11 +789,11 @@ public final class EventDetailChart extends XYChart impl description, DescriptionFilter.FilterMode.EXCLUDE); - DescriptionFilter descriptionFilter = getController().getQuickHideMasks().stream() + DescriptionFilter descriptionFilter = getController().getQuickHideFilters().stream() .filter(testFilter::equals) .findFirst().orElseGet(() -> { testFilter.selectedProperty().addListener(layoutInvalidationListener); - getController().getQuickHideMasks().add(testFilter); + getController().getQuickHideFilters().add(testFilter); return testFilter; }); descriptionFilter.setSelected(true); @@ -805,12 +804,12 @@ public final class EventDetailChart extends XYChart impl class UnhideDescriptionAction extends Action { - UnhideDescriptionAction(String description, DescriptionLOD descriptionLoD) { + UnhideDescriptionAction(String description, DescriptionLoD descriptionLoD) { super("Unhide"); setGraphic(new ImageView(SHOW)); setEventHandler((ActionEvent t) -> - getController().getQuickHideMasks().stream() + getController().getQuickHideFilters().stream() .filter(descriptionFilter -> descriptionFilter.getDescriptionLoD().equals(descriptionLoD) && descriptionFilter.getDescription().equals(description)) .forEach(descriptionfilter -> descriptionfilter.setSelected(false)) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java index 7188e1bc53..666b0e01c8 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java @@ -81,7 +81,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter; import org.sleuthkit.autopsy.timeline.filters.RootFilter; import org.sleuthkit.autopsy.timeline.filters.TypeFilter; -import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel; import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; import org.sleuthkit.datamodel.SleuthkitCase; @@ -116,7 +116,7 @@ final public class EventStripeNode extends StackPane { b.setManaged(show); } - private final SimpleObjectProperty descLOD = new SimpleObjectProperty<>(); + private final SimpleObjectProperty descLOD = new SimpleObjectProperty<>(); private DescriptionVisibility descrVis; private Tooltip tooltip; @@ -398,7 +398,7 @@ final public class EventStripeNode extends StackPane { } } - private DescriptionLOD getDescriptionLoD() { + private DescriptionLoD getDescriptionLoD() { return descLOD.get(); } @@ -409,7 +409,7 @@ final public class EventStripeNode extends StackPane { * @param expand */ @NbBundle.Messages(value = "EventStripeNode.loggedTask.name=Load sub clusters") - private synchronized void loadSubBundles(DescriptionLOD.RelativeDetail relativeDetail) { + private synchronized void loadSubBundles(DescriptionLoD.RelativeDetail relativeDetail) { chart.getEventBundles().removeIf(bundle -> getSubNodes().stream().anyMatch(subNode -> bundle.equals(subNode.getEventStripe())) @@ -436,7 +436,7 @@ final public class EventStripeNode extends StackPane { Task> loggedTask = new Task>() { - private volatile DescriptionLOD loadedDescriptionLoD = getDescriptionLoD().withRelativeDetail(relativeDetail); + private volatile DescriptionLoD loadedDescriptionLoD = getDescriptionLoD().withRelativeDetail(relativeDetail); { updateTitle(Bundle.EventStripeNode_loggedTask_name()); @@ -445,7 +445,7 @@ final public class EventStripeNode extends StackPane { @Override protected Collection call() throws Exception { Collection bundles; - DescriptionLOD next = loadedDescriptionLoD; + DescriptionLoD next = loadedDescriptionLoD; do { loadedDescriptionLoD = next; if (loadedDescriptionLoD == eventStripe.getDescriptionLoD()) { @@ -551,9 +551,9 @@ final public class EventStripeNode extends StackPane { } else if (t.isShortcutDown()) { chart.selectedNodes.removeAll(EventStripeNode.this); } else if (t.getClickCount() > 1) { - final DescriptionLOD next = descLOD.get().moreDetailed(); + final DescriptionLoD next = descLOD.get().moreDetailed(); if (next != null) { - loadSubBundles(DescriptionLOD.RelativeDetail.MORE); + loadSubBundles(DescriptionLoD.RelativeDetail.MORE); } } else { @@ -586,13 +586,13 @@ final public class EventStripeNode extends StackPane { setGraphic(new ImageView(PLUS)); setEventHandler((ActionEvent t) -> { - final DescriptionLOD next = descLOD.get().moreDetailed(); + final DescriptionLoD next = descLOD.get().moreDetailed(); if (next != null) { - loadSubBundles(DescriptionLOD.RelativeDetail.MORE); + loadSubBundles(DescriptionLoD.RelativeDetail.MORE); } }); - disabledProperty().bind(descLOD.isEqualTo(DescriptionLOD.FULL)); + disabledProperty().bind(descLOD.isEqualTo(DescriptionLoD.FULL)); } } @@ -604,9 +604,9 @@ final public class EventStripeNode extends StackPane { setGraphic(new ImageView(MINUS)); setEventHandler((ActionEvent t) -> { - final DescriptionLOD previous = descLOD.get().lessDetailed(); + final DescriptionLoD previous = descLOD.get().lessDetailed(); if (previous != null) { - loadSubBundles(DescriptionLOD.RelativeDetail.LESS); + loadSubBundles(DescriptionLoD.RelativeDetail.LESS); } }); disabledProperty().bind(Bindings.createBooleanBinding(() -> nonNull(eventStripe) && descLOD.get() == eventStripe.getDescriptionLoD(), descLOD)); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java index e16aa157ce..9b8378ed6b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavPanel.java @@ -49,6 +49,7 @@ import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.TimeLineView; import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; +import org.sleuthkit.autopsy.timeline.filters.AbstractFilter; import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter; import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane; @@ -164,17 +165,17 @@ public class NavPanel extends BorderPane implements TimeLineView { setTooltip(null); setGraphic(null); setContextMenu(null); - deRegisterListeners(controller.getQuickHideMasks()); + deRegisterListeners(controller.getQuickHideFilters()); } else { filterStateChangeListener = (filterState) -> updateHiddenState(item); - controller.getQuickHideMasks().addListener((ListChangeListener.Change listChange) -> { + controller.getQuickHideFilters().addListener((ListChangeListener.Change listChange) -> { while (listChange.next()) { deRegisterListeners(listChange.getRemoved()); registerListeners(listChange.getAddedSubList(), item); } updateHiddenState(item); }); - registerListeners(controller.getQuickHideMasks(), item); + registerListeners(controller.getQuickHideFilters(), item); String text = item.getDescription() + " (" + item.getCount() + ")"; // NON-NLS TreeItem parent = getTreeItem().getParent(); if (parent != null && parent.getValue() != null && (parent instanceof EventDescriptionTreeItem)) { @@ -207,7 +208,9 @@ public class NavPanel extends BorderPane implements TimeLineView { private void updateHiddenState(EventBundle item) { TreeItem treeItem = getTreeItem(); ContextMenu newMenu; - if (controller.getQuickHideMasks().stream().anyMatch((mask) -> mask.isActive() && mask.getDescription().equals(item.getDescription()))) { + if (controller.getQuickHideFilters().stream(). + filter(AbstractFilter::isActive) + .anyMatch(filter -> filter.getDescription().equals(item.getDescription()))) { if (treeItem != null) { treeItem.setExpanded(false); } 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 6ee97c73a6..506554fbb7 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java @@ -201,7 +201,7 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { defaultButton.disableProperty().bind(defaultFiltersAction.disabledProperty()); this.setModel(timeLineController.getEventsModel()); - hiddenDescriptionsListView.setItems(controller.getQuickHideMasks()); + hiddenDescriptionsListView.setItems(controller.getQuickHideFilters()); hiddenDescriptionsListView.setCellFactory((ListView param) -> { final ListCell forList = new FilterCheckBoxCellFactory().forList(); @@ -217,7 +217,7 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView { configureText(forList.getItem().selectedProperty().get()); setOnAction((ActionEvent event) -> { - controller.getQuickHideMasks().remove(forList.getItem()); + controller.getQuickHideFilters().remove(forList.getItem()); }); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/zooming/DescriptionLOD.java b/Core/src/org/sleuthkit/autopsy/timeline/zooming/DescriptionLoD.java similarity index 80% rename from Core/src/org/sleuthkit/autopsy/timeline/zooming/DescriptionLOD.java rename to Core/src/org/sleuthkit/autopsy/timeline/zooming/DescriptionLoD.java index 4bb5669f1e..50d612b210 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/zooming/DescriptionLOD.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/zooming/DescriptionLoD.java @@ -23,11 +23,11 @@ import org.openide.util.NbBundle; /** * Enumeration of all description levels of detail. */ -public enum DescriptionLOD { +public enum DescriptionLoD { - SHORT(NbBundle.getMessage(DescriptionLOD.class, "DescriptionLOD.short")), - MEDIUM(NbBundle.getMessage(DescriptionLOD.class, "DescriptionLOD.medium")), - FULL(NbBundle.getMessage(DescriptionLOD.class, "DescriptionLOD.full")); + SHORT(NbBundle.getMessage(DescriptionLoD.class, "DescriptionLOD.short")), + MEDIUM(NbBundle.getMessage(DescriptionLoD.class, "DescriptionLOD.medium")), + FULL(NbBundle.getMessage(DescriptionLoD.class, "DescriptionLOD.full")); private final String displayName; @@ -35,11 +35,11 @@ public enum DescriptionLOD { return displayName; } - private DescriptionLOD(String displayName) { + private DescriptionLoD(String displayName) { this.displayName = displayName; } - public DescriptionLOD moreDetailed() { + public DescriptionLoD moreDetailed() { try { return values()[ordinal() + 1]; } catch (ArrayIndexOutOfBoundsException e) { @@ -47,7 +47,7 @@ public enum DescriptionLOD { } } - public DescriptionLOD lessDetailed() { + public DescriptionLoD lessDetailed() { try { return values()[ordinal() - 1]; } catch (ArrayIndexOutOfBoundsException e) { @@ -55,7 +55,7 @@ public enum DescriptionLOD { } } - public DescriptionLOD withRelativeDetail(RelativeDetail relativeDetail) { + public DescriptionLoD withRelativeDetail(RelativeDetail relativeDetail) { switch (relativeDetail) { case EQUAL: return this; @@ -68,7 +68,7 @@ public enum DescriptionLOD { } } - public RelativeDetail getDetailLevelRelativeTo(DescriptionLOD other) { + public RelativeDetail getDetailLevelRelativeTo(DescriptionLoD other) { int compareTo = this.compareTo(other); if (compareTo < 0) { return RelativeDetail.LESS; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomParams.java b/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomParams.java index 9ee3cd8c96..2bb289f091 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomParams.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomParams.java @@ -35,7 +35,7 @@ public class ZoomParams { private final RootFilter filter; - private final DescriptionLOD descrLOD; + private final DescriptionLoD descrLOD; public Interval getTimeRange() { return timeRange; @@ -49,11 +49,11 @@ public class ZoomParams { return filter; } - public DescriptionLOD getDescriptionLOD() { + public DescriptionLoD getDescriptionLOD() { return descrLOD; } - public ZoomParams(Interval timeRange, EventTypeZoomLevel zoomLevel, RootFilter filter, DescriptionLOD descrLOD) { + public ZoomParams(Interval timeRange, EventTypeZoomLevel zoomLevel, RootFilter filter, DescriptionLoD descrLOD) { this.timeRange = timeRange; this.typeZoomLevel = zoomLevel; this.filter = filter; @@ -72,7 +72,7 @@ public class ZoomParams { return new ZoomParams(timeRange, typeZoomLevel, filter, descrLOD); } - public ZoomParams withDescrLOD(DescriptionLOD descrLOD) { + public ZoomParams withDescrLOD(DescriptionLoD descrLOD) { return new ZoomParams(timeRange, typeZoomLevel, filter, descrLOD); } @@ -92,7 +92,7 @@ public class ZoomParams { return this.timeRange == null ? false : this.timeRange.equals(timeRange); } - public boolean hasDescrLOD(DescriptionLOD newLOD) { + public boolean hasDescrLOD(DescriptionLoD newLOD) { return this.descrLOD.equals(newLOD); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomSettingsPane.java b/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomSettingsPane.java index 0e93c5f27d..b1c5719625 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomSettingsPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomSettingsPane.java @@ -97,7 +97,7 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView { typeZoomSlider.setMin(1); typeZoomSlider.setMax(2); typeZoomSlider.setLabelFormatter(new TypeZoomConverter()); - descrLODSlider.setMax(DescriptionLOD.values().length - 1); + descrLODSlider.setMax(DescriptionLoD.values().length - 1); descrLODSlider.setLabelFormatter(new DescrLODConverter()); descrLODLabel.setText( NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.descrLODLabel.text")); @@ -154,7 +154,7 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView { initializeSlider(descrLODSlider, () -> { - DescriptionLOD newLOD = DescriptionLOD.values()[Math.round(descrLODSlider.valueProperty().floatValue())]; + DescriptionLoD newLOD = DescriptionLoD.values()[Math.round(descrLODSlider.valueProperty().floatValue())]; if (controller.pushDescrLOD(newLOD) == false) { descrLODSlider.setValue(new DescrLODConverter().fromString(filteredEvents.getDescriptionLOD().toString())); } @@ -244,12 +244,12 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView { @Override public String toString(Double object) { - return DescriptionLOD.values()[object.intValue()].getDisplayName(); + return DescriptionLoD.values()[object.intValue()].getDisplayName(); } @Override public Double fromString(String string) { - return new Integer(DescriptionLOD.valueOf(string).ordinal()).doubleValue(); + return new Integer(DescriptionLoD.valueOf(string).ordinal()).doubleValue(); } } } diff --git a/CoreLibs/nbproject/project.xml b/CoreLibs/nbproject/project.xml index dfb0eb4c9d..0f07ff2b83 100644 --- a/CoreLibs/nbproject/project.xml +++ b/CoreLibs/nbproject/project.xml @@ -29,13 +29,18 @@ com.apple.eawt com.apple.eawt.event com.apple.eio + com.google.common.annotations com.google.common.base com.google.common.cache com.google.common.collect com.google.common.escape com.google.common.eventbus + com.google.common.hash com.google.common.html com.google.common.io + com.google.common.math + com.google.common.net + com.google.common.primitives com.google.common.reflect com.google.common.util.concurrent com.google.common.xml From eee0cec72817a66cd29a4ba868066c94a9b9affe Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 7 Oct 2015 10:20:16 -0400 Subject: [PATCH 29/29] restore keywordsearch suite.properties that should never have been removed --- KeywordSearch/nbproject/suite.properties | 1 + 1 file changed, 1 insertion(+) create mode 100644 KeywordSearch/nbproject/suite.properties diff --git a/KeywordSearch/nbproject/suite.properties b/KeywordSearch/nbproject/suite.properties new file mode 100644 index 0000000000..29d7cc9bd6 --- /dev/null +++ b/KeywordSearch/nbproject/suite.properties @@ -0,0 +1 @@ +suite.dir=${basedir}/..