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;
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private final ObservableList<DescriptionFilter> quickHideMasks = FXCollections.observableArrayList();
public ObservableList<DescriptionFilter> getQuickHideMasks() {

View File

@ -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<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.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<EventBundle> 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<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.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<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;
}
@Override
public long getStartMillis() {
return span.getStartMillis();
}
@Override
public long getEndMillis() {
return span.getEndMillis();
}
@Override
public Set<Long> getEventIDs() {
return Collections.unmodifiableSet(eventIDs);
}
@Override
public Set<Long> getEventIDsWithHashHits() {
return Collections.unmodifiableSet(hashHits);
}
@Override
public Set<Long> 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);
}
}

View File

@ -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<EventBundle> getParentBundle() {
return Optional.ofNullable(parent);
}
private final RangeSet<Long> spans = TreeRangeSet.create();
private final RangeMap<Long, EventCluster> 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);
}

View File

@ -14,18 +14,18 @@ import static org.sleuthkit.autopsy.timeline.filters.CompoundFilter.areSubFilter
*
*/
public class DescriptionsExclusionFilter extends IntersectionFilter<DescriptionFilter> {
@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<DescriptionF
});
return filterCopy;
}
@Override
public String getHTMLReportString() {
//move this logic into SaveSnapshot
@ -46,12 +46,12 @@ public class DescriptionsExclusionFilter extends IntersectionFilter<DescriptionF
}
return string;
}
@Override
public int hashCode() {
return 7;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
@ -61,18 +61,20 @@ public class DescriptionsExclusionFilter extends IntersectionFilter<DescriptionF
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);
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());
}
}
}

View File

@ -169,7 +169,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
private final CollapseBundleAction collapseClusterAction;
private final ExpandClusterAction expandClusterAction;
private final EventDetailChart.HideBundleAction hideClusterAction;
private final EventDetailChart.HideDescriptionAction hideClusterAction;
public AbstractDetailViewNode(EventDetailChart chart, T bundle, S parentEventNode) {
this.eventBundle = bundle;
@ -187,7 +187,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
show(tagIV, false);
}
hideClusterAction = chart.new HideBundleAction(getEventBundle());
hideClusterAction = chart.new HideDescriptionAction(getDescription(), bundle.getDescriptionLOD());
hideButton = ActionUtils.createButton(hideClusterAction, ActionUtils.ActionTextBehavior.HIDE);
configureLODButton(hideButton);
@ -405,15 +405,14 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
final EventTypeZoomLevel eventTypeZoomLevel = eventsModel.eventTypeZoomProperty().get();
final ZoomParams zoomParams = new ZoomParams(subClusterSpan, eventTypeZoomLevel, subClusterFilter, getDescLOD());
LoggedTask<List<S>> loggedTask;
loggedTask = new LoggedTask<List<S>>(
LoggedTask<Collection<T>> loggedTask = new LoggedTask<Collection<T>>(
NbBundle.getMessage(this.getClass(), "AggregateEventNode.loggedTask.name"), true) {
private Collection<T> bundles;
private volatile DescriptionLOD loadedDescriptionLoD = getDescLOD().withRelativeDetail(relativeDetail);
private DescriptionLOD next = loadedDescriptionLoD;
@Override
protected List<S> call() throws Exception {
protected Collection<T> 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<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
protected void succeeded() {
chart.setCursor(Cursor.WAIT);
try {
List<S> subBundleNodes = get();
List<S> 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

View File

@ -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<DateTime, EventCluster
selectedNodes.addListener((Observable observable) -> {
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<DateTime, EventCluster
treeSelectionModel.getSelectedItems().addListener((Observable observable) -> {
highlightedNodes.clear();
for (TreeItem<NavTreeNode> 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<DateTime, EventCluster
layoutDateLabels();
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) {
return chart.new UnhideBundleAction(description);
public Action newUnhideDescriptionAction(String description, DescriptionLOD descriptionLoD) {
return chart.new UnhideDescriptionAction(description, descriptionLoD);
}
public EventDetailChart.HideBundleAction newHideBundleAction(EventBundle bundle) {
return chart.new HideBundleAction(bundle);
public Action newHideDescriptionAction(String description, DescriptionLOD descriptionLoD) {
return chart.new HideDescriptionAction(description, descriptionLoD);
}
}

View File

@ -18,7 +18,6 @@
*/
package org.sleuthkit.autopsy.timeline.ui.detailview;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -169,7 +168,7 @@ public class EventClusterNode extends AbstractDetailViewNode<EventCluster, Event
}
@Override
Collection<EventCluster> makeBundlesFromClusters(List<EventCluster> eventClusters) {
List<EventCluster> makeBundlesFromClusters(List<EventCluster> 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.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<DateTime, EventCluster> 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<DateTime, EventCluster> impl
*
* @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) -> {
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<DateTime, EventCluster> impl
double span = 0;
@SuppressWarnings("unchecked")
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()
.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<DateTime, EventCluster> 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();
});

View File

@ -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<String, EventDescriptionTreeItem> 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<EventBundle> 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<TreeItem<NavTreeNode>> comp) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
FXCollections.sort(getChildren(), comp);
}
@Override

View File

@ -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<EventBundle> 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

View File

@ -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<NavTreeNode> 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<NavTreeNode> 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);
}
}
}

View File

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

View File

@ -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;
}

View File

@ -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<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