This commit is contained in:
jmillman 2015-09-28 14:59:27 -04:00
parent 19b6a3ee03
commit c44febaf93
15 changed files with 216 additions and 148 deletions

View File

@ -137,6 +137,7 @@ public class TimeLineController {
private final Case autoCase; private final Case autoCase;
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private final ObservableList<DescriptionFilter> quickHideMasks = FXCollections.observableArrayList(); private final ObservableList<DescriptionFilter> quickHideMasks = FXCollections.observableArrayList();
public ObservableList<DescriptionFilter> getQuickHideMasks() { public ObservableList<DescriptionFilter> getQuickHideMasks() {

View File

@ -6,6 +6,7 @@
package org.sleuthkit.autopsy.timeline.datamodel; package org.sleuthkit.autopsy.timeline.datamodel;
import com.google.common.collect.Range; import com.google.common.collect.Range;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
@ -33,4 +34,5 @@ public interface EventBundle {
Iterable<Range<Long>> getRanges(); Iterable<Range<Long>> getRanges();
Optional<EventBundle> getParentBundle();
} }

View File

@ -21,6 +21,8 @@ package org.sleuthkit.autopsy.timeline.datamodel;
import com.google.common.collect.Range; import com.google.common.collect.Range;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import java.util.Collections; import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import org.joda.time.Interval; import org.joda.time.Interval;
@ -36,6 +38,13 @@ import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
@Immutable @Immutable
public class EventCluster implements EventBundle { public class EventCluster implements EventBundle {
final private EventBundle parent;
@Override
public Optional<EventBundle> getParentBundle() {
return Optional.ofNullable(parent);
}
/** /**
* the smallest time interval containing all the aggregated events * the smallest time interval containing all the aggregated events
*/ */
@ -73,7 +82,7 @@ public class EventCluster implements EventBundle {
*/ */
private final Set<Long> hashHits; private final Set<Long> hashHits;
public EventCluster(Interval spanningInterval, EventType type, Set<Long> eventIDs, Set<Long> hashHits, Set<Long> tagged, String description, DescriptionLOD lod) { private EventCluster(Interval spanningInterval, EventType type, Set<Long> eventIDs, Set<Long> hashHits, Set<Long> tagged, String description, DescriptionLOD lod, EventBundle parent) {
this.span = spanningInterval; this.span = spanningInterval;
this.type = type; this.type = type;
@ -82,6 +91,11 @@ public class EventCluster implements EventBundle {
this.description = description; this.description = description;
this.eventIDs = eventIDs; this.eventIDs = eventIDs;
this.lod = lod; this.lod = lod;
this.parent = parent;
}
public EventCluster(Interval spanningInterval, EventType type, Set<Long> eventIDs, Set<Long> hashHits, Set<Long> 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; return span;
} }
@Override
public long getStartMillis() { public long getStartMillis() {
return span.getStartMillis(); return span.getStartMillis();
} }
@Override
public long getEndMillis() { public long getEndMillis() {
return span.getEndMillis(); return span.getEndMillis();
} }
@Override
public Set<Long> getEventIDs() { public Set<Long> getEventIDs() {
return Collections.unmodifiableSet(eventIDs); return Collections.unmodifiableSet(eventIDs);
} }
@Override
public Set<Long> getEventIDsWithHashHits() { public Set<Long> getEventIDsWithHashHits() {
return Collections.unmodifiableSet(hashHits); return Collections.unmodifiableSet(hashHits);
} }
@Override
public Set<Long> getEventIDsWithTags() { public Set<Long> getEventIDsWithTags() {
return Collections.unmodifiableSet(tagged); return Collections.unmodifiableSet(tagged);
} }
@Override
public String getDescription() { public String getDescription() {
return description; return description;
} }
@Override
public EventType getEventType() { public EventType getEventType() {
return type; return type;
} }
@ -162,4 +183,20 @@ public class EventCluster implements EventBundle {
return Collections.singletonList(getRange()); 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);
}
} }

View File

@ -13,6 +13,7 @@ import com.google.common.collect.TreeRangeMap;
import com.google.common.collect.TreeRangeSet; import com.google.common.collect.TreeRangeSet;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import org.python.google.common.base.Objects; import org.python.google.common.base.Objects;
@ -25,6 +26,13 @@ import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
@Immutable @Immutable
public final class EventStripe implements EventBundle { public final class EventStripe implements EventBundle {
final private EventBundle parent;
@Override
public Optional<EventBundle> getParentBundle() {
return Optional.ofNullable(parent);
}
private final RangeSet<Long> spans = TreeRangeSet.create(); private final RangeSet<Long> spans = TreeRangeSet.create();
private final RangeMap<Long, EventCluster> spanMap = TreeRangeMap.create(); private final RangeMap<Long, EventCluster> spanMap = TreeRangeMap.create();
@ -69,6 +77,7 @@ public final class EventStripe implements EventBundle {
eventIDs.addAll(cluster.getEventIDs()); eventIDs.addAll(cluster.getEventIDs());
tagged.addAll(cluster.getEventIDsWithTags()); tagged.addAll(cluster.getEventIDsWithTags());
hashHits.addAll(cluster.getEventIDsWithHashHits()); hashHits.addAll(cluster.getEventIDsWithHashHits());
parent = cluster.getParentBundle().orElse(null);
} }
private EventStripe(EventStripe u, EventStripe v) { private EventStripe(EventStripe u, EventStripe v) {
@ -85,6 +94,7 @@ public final class EventStripe implements EventBundle {
tagged.addAll(v.getEventIDsWithTags()); tagged.addAll(v.getEventIDsWithTags());
hashHits.addAll(u.getEventIDsWithHashHits()); hashHits.addAll(u.getEventIDsWithHashHits());
hashHits.addAll(v.getEventIDsWithHashHits()); hashHits.addAll(v.getEventIDsWithHashHits());
parent = u.getParentBundle().orElse(null);
} }
public static EventStripe merge(EventStripe u, EventStripe v) { 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.description, v.description));
Preconditions.checkArgument(Objects.equal(u.lod, v.lod)); Preconditions.checkArgument(Objects.equal(u.lod, v.lod));
Preconditions.checkArgument(Objects.equal(u.type, v.type)); Preconditions.checkArgument(Objects.equal(u.type, v.type));
Preconditions.checkArgument(Objects.equal(u.parent, v.parent));
return new EventStripe(u, v); return new EventStripe(u, v);
} }

View File

@ -14,18 +14,18 @@ import static org.sleuthkit.autopsy.timeline.filters.CompoundFilter.areSubFilter
* *
*/ */
public class DescriptionsExclusionFilter extends IntersectionFilter<DescriptionFilter> { public class DescriptionsExclusionFilter extends IntersectionFilter<DescriptionFilter> {
@Override @Override
@NbBundle.Messages("descriptionsExclusionFilter.displayName.text=Exclude Descriptions") @NbBundle.Messages("descriptionsExclusionFilter.displayName.text=Exclude Descriptions")
public String getDisplayName() { public String getDisplayName() {
return Bundle.descriptionsExclusionFilter_displayName_text(); return Bundle.descriptionsExclusionFilter_displayName_text();
} }
public DescriptionsExclusionFilter() { public DescriptionsExclusionFilter() {
getDisabledProperty().bind(Bindings.size(getSubFilters()).lessThan(1)); getDisabledProperty().bind(Bindings.size(getSubFilters()).lessThan(1));
setSelected(false); setSelected(false);
} }
@Override @Override
public DescriptionsExclusionFilter copyOf() { public DescriptionsExclusionFilter copyOf() {
DescriptionsExclusionFilter filterCopy = new DescriptionsExclusionFilter(); DescriptionsExclusionFilter filterCopy = new DescriptionsExclusionFilter();
@ -36,7 +36,7 @@ public class DescriptionsExclusionFilter extends IntersectionFilter<DescriptionF
}); });
return filterCopy; return filterCopy;
} }
@Override @Override
public String getHTMLReportString() { public String getHTMLReportString() {
//move this logic into SaveSnapshot //move this logic into SaveSnapshot
@ -46,12 +46,12 @@ public class DescriptionsExclusionFilter extends IntersectionFilter<DescriptionF
} }
return string; return string;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return 7; return 7;
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj == null) { if (obj == null) {
@ -61,18 +61,20 @@ public class DescriptionsExclusionFilter extends IntersectionFilter<DescriptionF
return false; return false;
} }
final DescriptionsExclusionFilter other = (DescriptionsExclusionFilter) obj; final DescriptionsExclusionFilter other = (DescriptionsExclusionFilter) obj;
if (isSelected() != other.isSelected()) { if (isSelected() != other.isSelected()) {
return false; return false;
} }
return areSubFiltersEqual(this, other); return areSubFiltersEqual(this, other);
} }
public void addSubFilter(DescriptionFilter hashSetFilter) { public void addSubFilter(DescriptionFilter descriptionFilter) {
if (getSubFilters().contains(hashSetFilter) == false) { if (getSubFilters().contains(descriptionFilter) == false) {
getSubFilters().add(hashSetFilter); getSubFilters().add(descriptionFilter);
getSubFilters().sort(Comparator.comparing(DescriptionFilter::getDisplayName)); getSubFilters().sort(Comparator.comparing(DescriptionFilter::getDisplayName));
} else {
getSubFilters().filtered(descriptionFilter::equals).get(0).setSelected(descriptionFilter.isSelected());
} }
} }
} }

View File

@ -169,7 +169,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
private final CollapseBundleAction collapseClusterAction; private final CollapseBundleAction collapseClusterAction;
private final ExpandClusterAction expandClusterAction; private final ExpandClusterAction expandClusterAction;
private final EventDetailChart.HideBundleAction hideClusterAction; private final EventDetailChart.HideDescriptionAction hideClusterAction;
public AbstractDetailViewNode(EventDetailChart chart, T bundle, S parentEventNode) { public AbstractDetailViewNode(EventDetailChart chart, T bundle, S parentEventNode) {
this.eventBundle = bundle; this.eventBundle = bundle;
@ -187,7 +187,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
show(tagIV, false); show(tagIV, false);
} }
hideClusterAction = chart.new HideBundleAction(getEventBundle()); hideClusterAction = chart.new HideDescriptionAction(getDescription(), bundle.getDescriptionLOD());
hideButton = ActionUtils.createButton(hideClusterAction, ActionUtils.ActionTextBehavior.HIDE); hideButton = ActionUtils.createButton(hideClusterAction, ActionUtils.ActionTextBehavior.HIDE);
configureLODButton(hideButton); configureLODButton(hideButton);
@ -405,15 +405,14 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
final EventTypeZoomLevel eventTypeZoomLevel = eventsModel.eventTypeZoomProperty().get(); final EventTypeZoomLevel eventTypeZoomLevel = eventsModel.eventTypeZoomProperty().get();
final ZoomParams zoomParams = new ZoomParams(subClusterSpan, eventTypeZoomLevel, subClusterFilter, getDescLOD()); final ZoomParams zoomParams = new ZoomParams(subClusterSpan, eventTypeZoomLevel, subClusterFilter, getDescLOD());
LoggedTask<List<S>> loggedTask; LoggedTask<Collection<T>> loggedTask = new LoggedTask<Collection<T>>(
loggedTask = new LoggedTask<List<S>>(
NbBundle.getMessage(this.getClass(), "AggregateEventNode.loggedTask.name"), true) { NbBundle.getMessage(this.getClass(), "AggregateEventNode.loggedTask.name"), true) {
private Collection<T> bundles; private Collection<T> bundles;
private volatile DescriptionLOD loadedDescriptionLoD = getDescLOD().withRelativeDetail(relativeDetail); private volatile DescriptionLOD loadedDescriptionLoD = getDescLOD().withRelativeDetail(relativeDetail);
private DescriptionLOD next = loadedDescriptionLoD; private DescriptionLOD next = loadedDescriptionLoD;
@Override @Override
protected List<S> call() throws Exception { protected Collection<T> call() throws Exception {
do { do {
loadedDescriptionLoD = next; loadedDescriptionLoD = next;
if (loadedDescriptionLoD == getEventBundle().getDescriptionLOD()) { if (loadedDescriptionLoD == getEventBundle().getDescriptionLOD()) {
@ -427,20 +426,26 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
} while (bundles.size() == 1 && nonNull(next)); } while (bundles.size() == 1 && nonNull(next));
// return list of AbstractDetailViewNodes representing sub-bundles // return list of AbstractDetailViewNodes representing sub-bundles
return bundles.stream() return bundles;
.map(AbstractDetailViewNode.this::getNodeForBundle)
.collect(Collectors.toList());
} }
private Collection<T> loadBundles() { private Collection<T> loadBundles() {
return makeBundlesFromClusters(eventsModel.getEventClusters(zoomParams.withDescrLOD(loadedDescriptionLoD))); List<EventCluster> eventClusters
= eventsModel.getEventClusters(zoomParams.withDescrLOD(loadedDescriptionLoD)).stream()
.map(cluster -> cluster.withParent(getEventBundle()))
.collect(Collectors.toList());
return makeBundlesFromClusters(eventClusters);
} }
@Override @Override
protected void succeeded() { protected void succeeded() {
chart.setCursor(Cursor.WAIT); chart.setCursor(Cursor.WAIT);
try { try {
List<S> subBundleNodes = get(); List<S> subBundleNodes = get().stream()
.map(AbstractDetailViewNode.this::getNodeForBundle)
.collect(Collectors.toList());
if (subBundleNodes.isEmpty()) { if (subBundleNodes.isEmpty()) {
showSpans(true); showSpans(true);
} else { } else {
@ -455,7 +460,9 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
} catch (InterruptedException | ExecutionException ex) { } catch (InterruptedException | ExecutionException ex) {
LOGGER.log(Level.SEVERE, "Error loading subnodes", 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: case SHOWN:
String description = getEventBundle().getDescription(); String description = getEventBundle().getDescription();
description = getParentNode() != null description = getParentNode() != null
? " ..." + StringUtils.substringAfter(description, getParentNode().getDescription()) ? " ... " + StringUtils.substringAfter(description, getParentNode().getDescription())
: description; : description;
descrLabel.setText(description); descrLabel.setText(description);
countLabel.setText(((size == 1) ? "" : " (" + size + ")")); // NON-NLS 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 * event handler used for mouse events on {@link AggregateEventNode}s

View File

@ -62,6 +62,7 @@ import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority; import javafx.scene.layout.Priority;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import org.controlsfx.control.action.Action;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.LoggedTask; 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.countsview.CountsViewPane;
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.NavTreeNode; import org.sleuthkit.autopsy.timeline.ui.detailview.tree.NavTreeNode;
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo; import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
/** /**
* FXML Controller class for a {@link EventDetailChart} based implementation of * FXML Controller class for a {@link EventDetailChart} based implementation of
@ -212,8 +214,8 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
selectedNodes.addListener((Observable observable) -> { selectedNodes.addListener((Observable observable) -> {
highlightedNodes.clear(); highlightedNodes.clear();
selectedNodes.stream().forEach((tn) -> { selectedNodes.stream().forEach((tn) -> {
for (DetailViewNode<?> n : chart.getNodes((DetailViewNode<?> t) for (DetailViewNode<?> n : chart.getNodes((DetailViewNode<?> t) ->
-> t.getDescription().equals(tn.getDescription()))) { t.getDescription().equals(tn.getDescription()))) {
highlightedNodes.add(n); highlightedNodes.add(n);
} }
}); });
@ -236,8 +238,8 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
treeSelectionModel.getSelectedItems().addListener((Observable observable) -> { treeSelectionModel.getSelectedItems().addListener((Observable observable) -> {
highlightedNodes.clear(); highlightedNodes.clear();
for (TreeItem<NavTreeNode> tn : treeSelectionModel.getSelectedItems()) { for (TreeItem<NavTreeNode> tn : treeSelectionModel.getSelectedItems()) {
for (DetailViewNode<?> n : chart.getNodes((DetailViewNode<?> t) for (DetailViewNode<?> n : chart.getNodes((DetailViewNode<?> t) ->
-> t.getDescription().equals(tn.getValue().getDescription()))) { t.getDescription().equals(tn.getValue().getDescription()))) {
highlightedNodes.add(n); highlightedNodes.add(n);
} }
} }
@ -348,7 +350,7 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
layoutDateLabels(); layoutDateLabels();
updateProgress(1, 1); updateProgress(1, 1);
}); });
return chart.getEventBundles().isEmpty() == false; return eventClusters.isEmpty() == false;
} }
}; };
} }
@ -484,11 +486,11 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
} }
public EventDetailChart.UnhideBundleAction newUnhideBundleAction(String description) { public Action newUnhideDescriptionAction(String description, DescriptionLOD descriptionLoD) {
return chart.new UnhideBundleAction(description); return chart.new UnhideDescriptionAction(description, descriptionLoD);
} }
public EventDetailChart.HideBundleAction newHideBundleAction(EventBundle bundle) { public Action newHideDescriptionAction(String description, DescriptionLOD descriptionLoD) {
return chart.new HideBundleAction(bundle); return chart.new HideDescriptionAction(description, descriptionLoD);
} }
} }

View File

@ -18,7 +18,6 @@
*/ */
package org.sleuthkit.autopsy.timeline.ui.detailview; package org.sleuthkit.autopsy.timeline.ui.detailview;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -169,7 +168,7 @@ public class EventClusterNode extends AbstractDetailViewNode<EventCluster, Event
} }
@Override @Override
Collection<EventCluster> makeBundlesFromClusters(List<EventCluster> eventClusters) { List<EventCluster> makeBundlesFromClusters(List<EventCluster> eventClusters) {
return eventClusters; return eventClusters;
} }

View File

@ -81,6 +81,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter; import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart; 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 * Custom implementation of {@link XYChart} to graph events on a horizontal
@ -512,14 +513,14 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
.collect(Collectors.partitioningBy(node -> getController().getQuickHideMasks().stream() .collect(Collectors.partitioningBy(node -> getController().getQuickHideMasks().stream()
.anyMatch(mask -> mask.getDescription().equals(node.getDescription())))); .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(); minY = maxY.get();
} }
} else { } else {
hiddenPartition = nodeMap.values().stream() hiddenPartition = nodeMap.values().stream()
.collect(Collectors.partitioningBy(node -> getController().getQuickHideMasks().stream() .collect(Collectors.partitioningBy(node -> getController().getQuickHideMasks().stream()
.anyMatch(mask -> mask.getDescription().equals(node.getDescription())))); .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); setCursor(null);
requiresLayout = false; requiresLayout = false;
@ -537,16 +538,20 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
* *
* @return the double * @return the double
*/ */
private double layoutNodesHelper(List<AbstractDetailViewNode<?, ?>> hiddenNodes, List<AbstractDetailViewNode<?, ?>> shownNodes, double minY, List<Node> children, final double xOffset) { private double layoutNodesHelper(List<AbstractDetailViewNode<?, ?>> hiddenNodes, List<AbstractDetailViewNode<?, ?>> shownNodes, double minY, final double xOffset) {
hiddenNodes.forEach((AbstractDetailViewNode<?, ?> t) -> { hiddenNodes.forEach((AbstractDetailViewNode<?, ?> t) -> {
children.remove(t); // children.remove(t);
t.setVisible(false);
t.setManaged(false);
}); });
shownNodes.forEach((AbstractDetailViewNode<?, ?> t) -> { shownNodes.forEach((AbstractDetailViewNode<?, ?> t) -> {
if (false == children.contains(t)) { // if (false == children.contains(t)) {
children.add(t); // children.add(t);
} // }
t.setVisible(true);
t.setManaged(true);
}); });
shownNodes.sort(Comparator.comparing(DetailViewNode<?>::getStartMillis)); shownNodes.sort(Comparator.comparing(DetailViewNode<?>::getStartMillis));
@ -640,17 +645,13 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
double span = 0; double span = 0;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<AbstractDetailViewNode<?, ?>> subNodes = (List<AbstractDetailViewNode<?, ?>>) node.getSubBundleNodes(); List<AbstractDetailViewNode<?, ?>> subNodes = (List<AbstractDetailViewNode<?, ?>>) node.getSubBundleNodes();
if (subNodes.isEmpty() == false) {
Map<Boolean, List<AbstractDetailViewNode<?, ?>>> hiddenPartition = subNodes.stream()
.collect(Collectors.partitioningBy(testNode -> getController().getQuickHideMasks().stream()
.anyMatch(mask -> mask.getDescription().equals(testNode.getDescription()))));
Map<Boolean, List<AbstractDetailViewNode<?, ?>>> hiddenPartition = subNodes.stream() layoutNodesResultHeight = layoutNodesHelper(hiddenPartition.get(true), hiddenPartition.get(false), minY, rawDisplayPosition);
.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) { if (alternateLayout.get() == false) {
double endX = getXAxis().getDisplayPosition(new DateTime(node.getEndMillis())) - xOffset; double endX = getXAxis().getDisplayPosition(new DateTime(node.getEndMillis())) - xOffset;
span = endX - startX; span = endX - startX;
@ -822,32 +823,36 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
c1.applySelectionEffect(selected); c1.applySelectionEffect(selected);
} }
class HideBundleAction extends Action { class HideDescriptionAction extends Action {
/** /**
* *
* @param description the value of description * @param description the value of description
*/ */
public HideBundleAction(final EventBundle bundle) { HideDescriptionAction(String description, DescriptionLOD descriptionLoD) {
super("Hide"); super("Hide");
setGraphic(new ImageView(HIDE)); setGraphic(new ImageView(HIDE));
setEventHandler((ActionEvent t) -> { setEventHandler((ActionEvent t) -> {
DescriptionFilter descriptionFilter = new DescriptionFilter(bundle.getDescriptionLOD(), bundle.getDescription(), DescriptionFilter.FilterMode.EXCLUDE); getController().getQuickHideMasks().add(
getController().getQuickHideMasks().add(descriptionFilter); new DescriptionFilter(descriptionLoD,
description,
DescriptionFilter.FilterMode.EXCLUDE));
setRequiresLayout(true); setRequiresLayout(true);
requestChartLayout(); requestChartLayout();
}); });
} }
} }
class UnhideBundleAction extends Action { class UnhideDescriptionAction extends Action {
public UnhideBundleAction(String description) { UnhideDescriptionAction(String description, DescriptionLOD descriptionLoD) {
super("Unhide"); super("Unhide");
setGraphic(new ImageView(SHOW)); setGraphic(new ImageView(SHOW));
setEventHandler((ActionEvent t) -> { 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); setRequiresLayout(true);
requestChartLayout(); requestChartLayout();
}); });

View File

@ -19,11 +19,12 @@
package org.sleuthkit.autopsy.timeline.ui.detailview.tree; package org.sleuthkit.autopsy.timeline.ui.detailview.tree;
import java.util.Comparator; import java.util.Comparator;
import java.util.Deque;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import javafx.collections.FXCollections;
import javafx.scene.control.TreeItem; import javafx.scene.control.TreeItem;
import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; 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 * maps a description to the child item of this item with that description
*/ */
private final Map<String, EventDescriptionTreeItem> childMap = new ConcurrentHashMap<>(); private final Map<String, EventDescriptionTreeItem> childMap = new ConcurrentHashMap<>();
private final DescriptionLOD descriptionLoD; private final EventBundle bundle;
public EventBundle getEventBundle() {
return bundle;
}
EventDescriptionTreeItem(EventBundle g) { EventDescriptionTreeItem(EventBundle g) {
descriptionLoD = g.getDescriptionLOD(); bundle = g;
setValue(new NavTreeNode(g.getEventType().getBaseType(), g.getDescription(), g.getDescriptionLOD(), g.getEventIDs().size())); setValue(new NavTreeNode(g.getEventType().getBaseType(), g.getDescription(), g.getDescriptionLOD(), g.getEventIDs().size()));
} }
@ -46,37 +51,25 @@ class EventDescriptionTreeItem extends NavTreeItem {
return getValue().getCount(); return getValue().getCount();
} }
@Override public void insert(Deque<EventBundle> path) {
public void insert(EventBundle g) { EventBundle head = path.removeFirst();
NavTreeNode value = getValue(); EventDescriptionTreeItem treeItem = childMap.get(head.getDescription());
if (value.getType().getBaseType().equals(g.getEventType().getBaseType()) if (treeItem == null) {
&& g.getDescription().startsWith(value.getDescription())) { treeItem = new EventDescriptionTreeItem(head);
throw new IllegalArgumentException(); treeItem.setExpanded(true);
childMap.put(head.getDescription(), treeItem);
getChildren().add(treeItem);
FXCollections.sort(getChildren(), TreeComparator.Description);
} }
switch (descriptionLoD.getDetailLevelRelativeTo(g.getDescriptionLOD())) { if (path.isEmpty() == false) {
case LESS: treeItem.insert(path);
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 @Override
public void resort(Comparator<TreeItem<NavTreeNode>> comp) { public void resort(Comparator<TreeItem<NavTreeNode>> comp) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. FXCollections.sort(getChildren(), comp);
} }
@Override @Override

View File

@ -19,9 +19,9 @@
package org.sleuthkit.autopsy.timeline.ui.detailview.tree; package org.sleuthkit.autopsy.timeline.ui.detailview.tree;
import java.util.Comparator; import java.util.Comparator;
import java.util.Deque;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import javafx.application.Platform;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.scene.control.TreeItem; import javafx.scene.control.TreeItem;
import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
@ -44,36 +44,21 @@ class EventTypeTreeItem extends NavTreeItem {
return getValue().getCount(); return getValue().getCount();
} }
/** public void insert(Deque<EventBundle> path) {
* 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) {
EventDescriptionTreeItem treeItem = childMap.get(g.getDescription()); EventBundle head = path.removeFirst();
EventDescriptionTreeItem treeItem = childMap.get(head.getDescription());
if (treeItem == null) { if (treeItem == null) {
final EventDescriptionTreeItem newTreeItem = new EventDescriptionTreeItem(g); treeItem = new EventDescriptionTreeItem(head);
newTreeItem.setExpanded(true); treeItem.setExpanded(true);
childMap.put(g.getDescription(), newTreeItem); childMap.put(head.getDescription(), treeItem);
getChildren().add(treeItem);
Platform.runLater(() -> { FXCollections.sort(getChildren(), comparator);
synchronized (getChildren()) {
getChildren().add(newTreeItem);
FXCollections.sort(getChildren(), comparator);
}
});
} else {
treeItem.insert(g);
} }
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 @Override

View File

@ -21,10 +21,10 @@ package org.sleuthkit.autopsy.timeline.ui.detailview.tree;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import javafx.application.Platform;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.ComboBox; import javafx.scene.control.ComboBox;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.SelectionMode; import javafx.scene.control.SelectionMode;
import javafx.scene.control.Tooltip; import javafx.scene.control.Tooltip;
@ -36,8 +36,10 @@ import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle; import javafx.scene.shape.Rectangle;
import org.apache.commons.lang3.StringUtils;
import org.controlsfx.control.action.ActionUtils; import org.controlsfx.control.action.ActionUtils;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineView; 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() { private void setRoot() {
RootItem root = new RootItem(); RootItem root = new RootItem();
for (EventBundle bundle : detailViewPane.getEventBundles()) { for (EventBundle bundle : detailViewPane.getEventBundles()) {
root.insert(bundle); root.insert(bundle);
} }
Platform.runLater(() -> { eventsTree.setRoot(root);
eventsTree.setRoot(root);
});
} }
@Override @Override
@ -130,8 +132,17 @@ public class NavPanel extends BorderPane implements TimeLineView {
@Override @Override
protected void updateItem(NavTreeNode item, boolean empty) { protected void updateItem(NavTreeNode item, boolean empty) {
super.updateItem(item, empty); super.updateItem(item, empty);
if (item != null) { if (item == null || empty) {
final String text = item.getDescription() + " (" + item.getCount() + ")"; // NON-NLS setText(null);
setTooltip(null);
setGraphic(null);
setContextMenu(null);
} else {
String text = item.getDescription() + " (" + item.getCount() + ")"; // NON-NLS
TreeItem<NavTreeNode> parent = getTreeItem().getParent();
if (parent != null && parent.getValue() != null && (parent instanceof EventDescriptionTreeItem)) {
text = StringUtils.substringAfter(text, parent.getValue().getDescription());
}
setText(text); setText(text);
setTooltip(new Tooltip(text)); setTooltip(new Tooltip(text));
Rectangle rect = new Rectangle(24, 24); Rectangle rect = new Rectangle(24, 24);
@ -146,28 +157,34 @@ public class NavPanel extends BorderPane implements TimeLineView {
}); });
configureHiddenState(item, rect, imageView); configureHiddenState(item, rect, imageView);
} else {
setText(null);
setTooltip(null);
setGraphic(null);
setContextMenu(null);
} }
} }
private void configureHiddenState(NavTreeNode item, Rectangle rect, ImageView imageView) { private void configureHiddenState(NavTreeNode item, Rectangle rect, ImageView imageView) {
TreeItem<NavTreeNode> treeItem = getTreeItem();
ContextMenu newMenu;
if (controller.getQuickHideMasks().stream().anyMatch(mask -> mask.getDescription().equals(item.getDescription()))) { if (controller.getQuickHideMasks().stream().anyMatch(mask -> mask.getDescription().equals(item.getDescription()))) {
setTextFill(Color.gray(0, .6)); setTextFill(Color.gray(0, .6));
imageView.setOpacity(.6); imageView.setOpacity(.6);
rect.setStroke(item.getType().getColor().deriveColor(0, .6, 1, .6)); rect.setStroke(item.getType().getColor().deriveColor(0, .6, 1, .6));
rect.setFill(item.getType().getColor().deriveColor(0, .6, .6, 0.1)); 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 { } else {
setTextFill(Color.BLACK); setTextFill(Color.BLACK);
imageView.setOpacity(1); imageView.setOpacity(1);
rect.setStroke(item.getType().getColor()); rect.setStroke(item.getType().getColor());
rect.setFill(item.getType().getColor().deriveColor(0, 1, 1, 0.1)); 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);
} }
} }
} }

View File

@ -30,8 +30,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
*/ */
abstract class NavTreeItem extends TreeItem<NavTreeNode> { abstract class NavTreeItem extends TreeItem<NavTreeNode> {
abstract void insert(EventBundle g); // abstract void insert(EventBundle g);
abstract int getCount(); abstract int getCount();
abstract void resort(Comparator<TreeItem<NavTreeNode>> comp); abstract void resort(Comparator<TreeItem<NavTreeNode>> comp);

View File

@ -29,11 +29,10 @@ import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
@Immutable @Immutable
public class NavTreeNode { public class NavTreeNode {
final private DescriptionLOD descriptionLoD;
final private EventType type; final private EventType type;
final private String Description; final private String Description;
private final DescriptionLOD descriptionLoD;
final private int count; final private int count;
@ -44,7 +43,7 @@ public class NavTreeNode {
this.count = count; this.count = count;
} }
public DescriptionLOD getDescriptionLoD() { public DescriptionLOD getDescriptionLod() {
return descriptionLoD; return descriptionLoD;
} }

View File

@ -18,10 +18,12 @@
*/ */
package org.sleuthkit.autopsy.timeline.ui.detailview.tree; package org.sleuthkit.autopsy.timeline.ui.detailview.tree;
import java.util.ArrayDeque;
import java.util.Comparator; import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javafx.application.Platform; import java.util.Optional;
import javafx.scene.control.TreeItem; import javafx.scene.control.TreeItem;
import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
@ -54,25 +56,31 @@ class RootItem extends NavTreeItem {
* *
* @param g Group to add * @param g Group to add
*/ */
@Override
public void insert(EventBundle g) { public void insert(EventBundle g) {
EventTypeTreeItem treeItem = childMap.get(g.getEventType().getBaseType()); EventTypeTreeItem treeItem = childMap.get(g.getEventType().getBaseType());
if (treeItem == null) { if (treeItem == null) {
final EventTypeTreeItem newTreeItem = new EventTypeTreeItem(g); treeItem = new EventTypeTreeItem(g);
newTreeItem.setExpanded(true); treeItem.setExpanded(true);
childMap.put(g.getEventType().getBaseType(), newTreeItem); childMap.put(g.getEventType().getBaseType(), treeItem);
newTreeItem.insert(g);
Platform.runLater(() -> { getChildren().add(treeItem);
synchronized (getChildren()) { getChildren().sort(TreeComparator.Type);
getChildren().add(newTreeItem);
getChildren().sort(TreeComparator.Type);
}
});
} else {
treeItem.insert(g);
} }
treeItem.insert(getTreePath(g));
}
static Deque<EventBundle> getTreePath(EventBundle g) {
Deque<EventBundle> path = new ArrayDeque<>();
Optional<EventBundle> p = Optional.of(g);
while (p.isPresent()) {
EventBundle parent = p.get();
path.addFirst(parent);
p = parent.getParentBundle();
}
return path;
} }
@Override @Override