diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/ArtifactEventType.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/ArtifactEventType.java index fa17ab64e6..1c4b7f54dc 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/ArtifactEventType.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/ArtifactEventType.java @@ -87,7 +87,7 @@ public interface ArtifactEventType extends EventType { /** * bundles the per event information derived from a BlackBoard Artifact into - * one object. Primarily used to have a single return value for {@link SubType#buildEventDescription(org.sleuthkit.datamodel.BlackboardArtifact). + * one object. Primarily used to have a single return value for null {@link SubType#buildEventDescription(org.sleuthkit.datamodel.BlackboardArtifact). */ static class AttributeEventDescription { @@ -187,6 +187,8 @@ public interface ArtifactEventType extends EventType { } } + + public static class EmptyExtractor implements BiFunction, String> { @Override diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/WebTypes.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/WebTypes.java index 7de95beaed..1876a35381 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/WebTypes.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/WebTypes.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.timeline.datamodel.eventtype; +import com.google.common.net.InternetDomainName; import java.util.Collections; import java.util.List; import java.util.Map; @@ -38,7 +39,7 @@ public enum WebTypes implements EventType, ArtifactEventType { "downloads.png", // NON-NLS BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED, - new AttributeExtractor(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN), + TopPrivateDomainExtractor.getInstance(), new AttributeExtractor(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH), new AttributeExtractor(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL)) { @@ -67,7 +68,7 @@ public enum WebTypes implements EventType, ArtifactEventType { "cookies.png", // NON-NLS BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, - new AttributeExtractor(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN), + TopPrivateDomainExtractor.getInstance(), new AttributeExtractor(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME), new AttributeExtractor(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_VALUE)), //TODO: review description separators @@ -75,7 +76,7 @@ public enum WebTypes implements EventType, ArtifactEventType { "bookmarks.png", // NON-NLS BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED, - new AttributeExtractor(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN), + TopPrivateDomainExtractor.getInstance(), new AttributeExtractor(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL), new AttributeExtractor(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TITLE)), //TODO: review description separators @@ -83,7 +84,7 @@ public enum WebTypes implements EventType, ArtifactEventType { "history.png", // NON-NLS BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED, - new AttributeExtractor(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN), + TopPrivateDomainExtractor.getInstance(), new AttributeExtractor(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL), new AttributeExtractor(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TITLE)), //TODO: review description separators @@ -92,7 +93,7 @@ public enum WebTypes implements EventType, ArtifactEventType { BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED, new AttributeExtractor(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT), - new AttributeExtractor(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN), + TopPrivateDomainExtractor.getInstance(), new AttributeExtractor(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME)); private final BlackboardAttribute.ATTRIBUTE_TYPE dateTimeAttributeType; @@ -186,4 +187,30 @@ public enum WebTypes implements EventType, ArtifactEventType { return Collections.emptyList(); } + private static class TopPrivateDomainExtractor extends AttributeExtractor { + + final private static TopPrivateDomainExtractor instance = new TopPrivateDomainExtractor(); + + static TopPrivateDomainExtractor getInstance() { + return instance; + } + + @Override + public String apply(BlackboardArtifact artf, Map attrMap) { + String domainString = StringUtils.substringBefore(super.apply(artf, attrMap), "/"); + if (InternetDomainName.isValid(domainString)) { + InternetDomainName domain = InternetDomainName.from(domainString); + return (domain.isUnderPublicSuffix()) + ? domain.topPrivateDomain().toString() + : domain.toString(); + } else { + return domainString; + } + } + + TopPrivateDomainExtractor() { + super(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN); + } + } + } 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 55dda9bf19..a881b4ff5a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java @@ -332,7 +332,7 @@ public class DetailViewPane extends AbstractVisualizationPane { - setCursor(Cursor.NONE); + setCursor(Cursor.DEFAULT); layoutDateLabels(); updateProgress(1, 1); }); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventBundleNodeBase.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventBundleNodeBase.java index d4672e021f..6c028f8dc6 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventBundleNodeBase.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventBundleNodeBase.java @@ -27,11 +27,11 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.stream.Collectors; -import javafx.beans.Observable; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; import javafx.concurrent.Task; import javafx.geometry.Insets; +import javafx.geometry.Orientation; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.Button; @@ -126,7 +126,6 @@ public abstract class EventBundleNodeBase { - chart.layoutPlotChildren(); + heightProperty().addListener(heightProp -> { + chart.requestChartLayout(); }); setMaxHeight(USE_PREF_SIZE); + setMinWidth(USE_PREF_SIZE); setMaxWidth(USE_PREF_SIZE); setLayoutX(chart.getXAxis().getDisplayPosition(new DateTime(eventBundle.getStartMillis())) - getLayoutXCompensation()); //initialize info hbox infoHBox.setMinWidth(USE_PREF_SIZE); infoHBox.setMaxWidth(USE_PREF_SIZE); - infoHBox.setPadding(new Insets(2, 5, 2, 5)); + infoHBox.setPadding(new Insets(2, 3, 2, 3)); infoHBox.setAlignment(Pos.TOP_LEFT); //set up subnode pane sizing contraints subNodePane.setPrefHeight(USE_COMPUTED_SIZE); - subNodePane.setMaxHeight(USE_PREF_SIZE); + subNodePane.setPrefHeight(USE_COMPUTED_SIZE); + subNodePane.setMinHeight(24); subNodePane.setPrefWidth(USE_COMPUTED_SIZE); subNodePane.setMinWidth(USE_PREF_SIZE); subNodePane.setMaxWidth(USE_PREF_SIZE); @@ -177,7 +178,6 @@ public abstract class EventBundleNodeBase { showHoverControls(false); @@ -269,7 +269,7 @@ public abstract class EventBundleNodeBase imp private static final int PROJECTED_LINE_STROKE_WIDTH = 5; private static final int MINIMUM_EVENT_NODE_GAP = 4; - private final TimeLineController controller; private final FilteredEventsModel filteredEvents; private ContextMenu chartContextMenu; - - public ContextMenu getChartContextMenu() { return chartContextMenu; } @@ -204,7 +202,6 @@ public final class EventDetailsChart extends XYChart imp }); Tooltip.install(this, AbstractVisualizationPane.getDragTooltip()); - dateAxis.setAutoRanging(false); verticalAxis.setVisible(false);//TODO: why doesn't this hide the vertical axis, instead we have to turn off all parts individually? -jm @@ -342,7 +339,6 @@ public final class EventDetailsChart extends XYChart imp stripeNodeMap.put(eventStripe, stripeNode); nodeGroup.getChildren().add(stripeNode); data.setNode(stripeNode); - layoutPlotChildren(); } @Override @@ -359,7 +355,6 @@ public final class EventDetailsChart extends XYChart imp EventStripeNode removedNode = stripeNodeMap.remove(removedStripe); nodeGroup.getChildren().remove(removedNode); data.setNode(null); - layoutPlotChildren(); } @Override @@ -463,48 +458,42 @@ public final class EventDetailsChart extends XYChart imp * @param nodes collection of nodes to layout * @param minY the minimum y coordinate to position the nodes at. */ - synchronized double layoutEventBundleNodes(final Collection> nodes, final double minY) { - // map from y value (ranges) to right most occupied x value. + double layoutEventBundleNodes(final Collection> nodes, final double minY) { + TreeRangeMap treeRangeMap = TreeRangeMap.create(); // maximum y values occupied by any of the given nodes, updated as nodes are layed out. double localMax = minY; + Set activeQuickHidefilters = getController().getQuickHideFilters().stream() + .filter(AbstractFilter::isActive) + .map(DescriptionFilter::getDescription) + .collect(Collectors.toSet()); //for each node do a recursive layout to size it and then position it in first available slot - for (final EventBundleNodeBase bundleNode : nodes) { + for (EventBundleNodeBase bundleNode : nodes) { //is the node hiden by a quick hide filter? - boolean quickHide = getController().getQuickHideFilters().stream() - .filter(AbstractFilter::isActive) - .anyMatch(filter -> filter.getDescription().equals(bundleNode.getDescription())); + boolean quickHide = activeQuickHidefilters.contains(bundleNode.getDescription()); if (quickHide) { //hide it and skip layout bundleNode.setVisible(false); bundleNode.setManaged(false); } else { - //make sure it is shown - bundleNode.setVisible(true); - bundleNode.setManaged(true); - //apply advanced layout description visibility options - bundleNode.setDescriptionVisibility(descrVisibility.get()); - bundleNode.setDescriptionWidth(truncateAll.get() ? truncateWidth.get() : USE_PREF_SIZE); - - //do recursive layout - bundleNode.layout(); + bundleLayoutHelper(bundleNode); //get computed height and width double h = bundleNode.getBoundsInLocal().getHeight(); double w = bundleNode.getBoundsInLocal().getWidth(); //get left and right x coords from axis plus computed width double xLeft = getXForEpochMillis(bundleNode.getStartMillis()) - bundleNode.getLayoutXCompensation(); - double xRight = xLeft + w; + double xRight = xLeft + w + MINIMUM_EVENT_NODE_GAP; //initial test position double yTop = minY; - double yBottom = yTop + h; if (oneEventPerRow.get()) { // if onePerRow, just put it at end yTop = (localMax + MINIMUM_EVENT_NODE_GAP); - yBottom = yTop + h; } else { + double yBottom = yTop + h; + //until the node is not overlapping any others try moving it down. boolean overlapping = true; while (overlapping) { @@ -525,21 +514,42 @@ public final class EventDetailsChart extends XYChart imp treeRangeMap.put(Range.closed(yTop, yBottom), xRight); } - localMax = Math.max(yBottom, localMax); + localMax = Math.max(yTop + h, localMax); - //animate node to new position - Timeline timeline = new Timeline(new KeyFrame(Duration.millis(100), - new KeyValue(bundleNode.layoutXProperty(), xLeft), - new KeyValue(bundleNode.layoutYProperty(), yTop))); - timeline.setOnFinished((ActionEvent event) -> { - requestChartLayout(); - }); - timeline.play(); + if ((xLeft != bundleNode.getLayoutX()) || (yTop != bundleNode.getLayoutY())) { + + //animate node to new position + Timeline timeline = new Timeline(new KeyFrame(Duration.millis(100), + new KeyValue(bundleNode.layoutXProperty(), xLeft), + new KeyValue(bundleNode.layoutYProperty(), yTop)) + ); + timeline.setOnFinished((ActionEvent event) -> { + requestChartLayout(); + }); + timeline.play(); + } } } return localMax; //return new max } + private void bundleLayoutHelper(final EventBundleNodeBase bundleNode) { + //make sure it is shown + bundleNode.setVisible(true); + bundleNode.setManaged(true); + //apply advanced layout description visibility options + bundleNode.setDescriptionVisibility(descrVisibility.get()); + bundleNode.setDescriptionWidth(truncateAll.get() ? truncateWidth.get() : USE_PREF_SIZE); + + //do recursive layout + bundleNode.layoutChildren(); + } + + @Override + public void requestChartLayout() { + super.requestChartLayout(); //To change body of generated methods, choose Tools | Templates. + } + private double getXForEpochMillis(Long millis) { DateTime dateTime = new DateTime(millis, TimeLineController.getJodaTimeZone()); return getXAxis().getDisplayPosition(new DateTime(dateTime)); @@ -568,6 +578,7 @@ public final class EventDetailsChart extends XYChart imp */ public FilteredEventsModel getFilteredEvents() { return filteredEvents; + } static private class DetailIntervalSelector extends IntervalSelector { @@ -673,7 +684,7 @@ public final class EventDetailsChart extends XYChart imp .filter(testFilter::equals) .findFirst().orElseGet(() -> { testFilter.selectedProperty().addListener((Observable observable) -> { - layoutPlotChildren(); + requestChartLayout(); }); getController().getQuickHideFilters().add(testFilter); return testFilter; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java index db3a0456f2..2a785bdca0 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java @@ -98,6 +98,8 @@ final public class EventStripeNode extends EventBundleNodeBase