From c44febaf93defc0530acb90ea6537a0c05daff70 Mon Sep 17 00:00:00 2001 From: jmillman Date: Mon, 28 Sep 2015 14:59:27 -0400 Subject: [PATCH] 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