19 package org.sleuthkit.autopsy.timeline.ui.detailview;
21 import com.google.common.collect.Range;
22 import com.google.common.collect.TreeRangeMap;
23 import java.util.Arrays;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.Comparator;
28 import java.util.MissingResourceException;
30 import java.util.concurrent.ConcurrentHashMap;
31 import java.util.function.Function;
32 import java.util.function.Predicate;
33 import java.util.stream.Collectors;
34 import java.util.stream.Stream;
35 import javafx.application.Platform;
36 import javafx.beans.InvalidationListener;
37 import javafx.beans.Observable;
38 import javafx.beans.property.ReadOnlyDoubleProperty;
39 import javafx.beans.property.ReadOnlyDoubleWrapper;
40 import javafx.beans.property.SimpleBooleanProperty;
41 import javafx.beans.property.SimpleDoubleProperty;
42 import javafx.beans.property.SimpleObjectProperty;
43 import javafx.collections.FXCollections;
44 import javafx.collections.ListChangeListener;
45 import javafx.collections.ObservableList;
46 import javafx.event.ActionEvent;
47 import javafx.geometry.Insets;
48 import javafx.scene.Cursor;
49 import javafx.scene.Group;
50 import javafx.scene.Scene;
51 import javafx.scene.chart.Axis;
52 import javafx.scene.chart.NumberAxis;
53 import javafx.scene.chart.XYChart;
54 import javafx.scene.control.ContextMenu;
55 import javafx.scene.control.Tooltip;
56 import javafx.scene.image.Image;
57 import javafx.scene.image.ImageView;
58 import javafx.scene.input.MouseEvent;
59 import javafx.scene.shape.Line;
60 import javafx.scene.shape.StrokeLineCap;
61 import org.controlsfx.control.action.Action;
62 import org.controlsfx.control.action.ActionUtils;
63 import org.joda.time.DateTime;
64 import org.joda.time.Interval;
65 import org.openide.util.NbBundle;
99 private static final String
styleSheet = GuideLine.class.getResource(
"EventsDetailsChart.css").toExternalForm();
100 private static final Image
HIDE =
new Image(
"/org/sleuthkit/autopsy/timeline/images/eye--minus.png");
101 private static final Image
SHOW =
new Image(
"/org/sleuthkit/autopsy/timeline/images/eye--plus.png");
102 private static final Image
MARKER =
new Image(
"/org/sleuthkit/autopsy/timeline/images/marker.png", 16, 16,
true,
true,
true);
146 private final ReadOnlyDoubleWrapper
maxY =
new ReadOnlyDoubleWrapper(0.0);
148 final ObservableList<EventBundleNodeBase<?, ?, ?>> selectedNodes;
165 private final SimpleBooleanProperty
bandByType = new SimpleBooleanProperty(false);
171 private final SimpleBooleanProperty
oneEventPerRow = new SimpleBooleanProperty(false);
185 final SimpleBooleanProperty truncateAll = new SimpleBooleanProperty(false);
191 final SimpleDoubleProperty truncateWidth = new SimpleDoubleProperty(200.0);
194 super(dateAxis, verticalAxis);
197 this.filteredEvents = this.controller.getEventsModel();
199 sceneProperty().addListener(observable -> {
200 Scene scene = getScene();
201 if (scene != null && scene.getStylesheets().contains(styleSheet) ==
false) {
202 scene.getStylesheets().add(styleSheet);
209 selectedNodes.clear();
211 controller.selectEventIDs(Collections.emptyList());
216 dateAxis.setAutoRanging(
false);
217 verticalAxis.setVisible(
false);
218 verticalAxis.setTickLabelsVisible(
false);
219 verticalAxis.setTickMarkVisible(
false);
220 setLegendVisible(
false);
221 setPadding(Insets.EMPTY);
222 setAlternativeColumnFillVisible(
true);
225 getPlotChildren().add(nodeGroup);
237 boundsInLocalProperty().addListener((Observable observable) -> {
238 setPrefHeight(boundsInLocalProperty().
get().getHeight());
242 setOnMousePressed(chartDragHandler);
243 setOnMouseReleased(chartDragHandler);
244 setOnMouseDragged(chartDragHandler);
248 this.selectedNodes = selectedNodes;
252 ObservableList<EventStripe> getEventStripes() {
263 if (chartContextMenu != null) {
264 chartContextMenu.hide();
267 chartContextMenu = ActionUtils.createContextMenu(Arrays.asList(
new PlaceMarkerAction(clickEvent),
269 chartContextMenu.setAutoHide(
true);
288 synchronized void setBandByType(Boolean t1) {
302 return getXAxis().getValueForDisplay(
getXAxis().parentToLocal(x, 0).getX());
316 SimpleBooleanProperty oneEventPerRowProperty() {
320 SimpleDoubleProperty getTruncateWidth() {
321 return truncateWidth;
324 SimpleBooleanProperty truncateAllProperty() {
328 SimpleObjectProperty< DescriptionVisibility> descrVisibilityProperty() {
339 protected void seriesAdded(Series<DateTime, EventStripe> series,
int i) {
361 protected void dataItemAdded(Series<DateTime, EventStripe> series,
int itemIndex, Data<DateTime, EventStripe> item) {
372 protected void dataItemRemoved(Data<DateTime, EventStripe> item, Series<DateTime, EventStripe> series) {
391 void addDataItem(Data<DateTime, EventStripe> data) {
396 Platform.runLater(() -> {
399 nodeGroup.getChildren().add(stripeNode);
400 data.setNode(stripeNode);
411 void removeDataItem(Data<DateTime, EventStripe> data) {
412 Platform.runLater(() -> {
413 EventStripeNode removedNode = (EventStripeNode) data.getNode();
414 eventStripes.removeAll(
new StripeFlattener().apply(removedNode).collect(Collectors.toList()));
416 nodeGroup.getChildren().removeAll(removedNode);
423 setCursor(Cursor.WAIT);
430 .collect(Collectors.toSet());
437 .collect(Collectors.groupingBy(EventStripeNode::getEventType)).values()
438 .forEach(inputNodes -> maxY.set(layoutEventBundleNodes(inputNodes, maxY.get())));
440 maxY.set(layoutEventBundleNodes(
sortedStripeNodes.sorted(Comparator.comparing(EventStripeNode::getStartMillis)), 0));
446 ReadOnlyDoubleProperty maxVScrollProperty() {
447 return maxY.getReadOnlyProperty();
453 synchronized Iterable<EventBundleNodeBase<?, ?, ?>> getNodes(Predicate<EventBundleNodeBase<?, ?, ?>> p) {
455 Function<EventBundleNodeBase<?, ?, ?>, Stream<EventBundleNodeBase<?, ?, ?>>> stripeFlattener =
456 new Function<EventBundleNodeBase<?, ?, ?>, Stream<EventBundleNodeBase<?, ?, ?>>>() {
458 public Stream<EventBundleNodeBase<?, ?, ?>> apply(EventBundleNodeBase<?, ?, ?> node) {
459 return Stream.concat(
461 node.getSubNodes().stream().flatMap(this::apply));
466 .flatMap(stripeFlattener)
467 .filter(p).collect(Collectors.toList());
470 synchronized void setVScroll(
double vScrollValue) {
471 nodeGroup.setTranslateY(-vScrollValue);
474 void clearGuideLine() {
509 double layoutEventBundleNodes(
final Collection<? extends EventBundleNodeBase<?, ?, ?>> nodes,
final double minY) {
511 TreeRangeMap<Double, Double> maxXatY = TreeRangeMap.create();
514 double localMax = minY;
517 for (EventBundleNodeBase<?, ?, ?> bundleNode : nodes) {
522 bundleNode.setVisible(
false);
523 bundleNode.setManaged(
false);
527 double h = bundleNode.getBoundsInLocal().getHeight();
528 double w = bundleNode.getBoundsInLocal().getWidth();
530 double xLeft =
getXForEpochMillis(bundleNode.getStartMillis()) - bundleNode.getLayoutXCompensation();
535 ? (localMax + MINIMUM_EVENT_NODE_GAP)
538 localMax = Math.max(yTop + h, localMax);
540 if ((xLeft != bundleNode.getLayoutX()) || (yTop != bundleNode.getLayoutY())) {
542 bundleNode.animateTo(xLeft, yTop);
566 private double computeYTop(
double yMin,
double h, TreeRangeMap<Double, Double> maxXatY,
double xLeft,
double xRight) {
568 double yBottom = yTop + h;
570 boolean overlapping =
true;
571 while (overlapping) {
575 final Double maxX = maxXatY.get(y);
576 if (maxX != null && maxX >= xLeft - MINIMUM_EVENT_NODE_GAP) {
586 maxXatY.put(Range.closed(yTop, yBottom), xRight);
599 bundleNode.setVisible(
true);
600 bundleNode.setManaged(
true);
614 super.requestChartLayout();
618 DateTime dateTime =
new DateTime(millis);
619 return getXAxis().getDisplayPosition(dateTime);
629 final Line line = entry.getValue();
634 line.setStartY(
getXAxis().getLayoutY() + PROJECTED_LINE_Y_OFFSET);
635 line.setEndY(
getXAxis().getLayoutY() + PROJECTED_LINE_Y_OFFSET);
671 @NbBundle.Messages({
"EventDetailChart.chartContextMenu.placeMarker.name=Place Marker"})
673 super(Bundle.EventDetailChart_chartContextMenu_placeMarker_name());
675 setGraphic(
new ImageView(MARKER));
676 setEventHandler(actionEvent -> {
679 guideLine.relocate(sceneToLocal(clickEvent.getSceneX(), 0).getX(), 0);
683 guideLine.relocate(sceneToLocal(clickEvent.getSceneX(), 0).getX(), 0);
699 while (change.next()) {
703 getChartChildren().removeAll(removedLine);
714 line.setStroke(addedNode.getEventType().getColor().deriveColor(0, 1, 1, .5));
715 line.setStrokeWidth(PROJECTED_LINE_STROKE_WIDTH);
716 line.setStrokeLineCap(StrokeLineCap.ROUND);
718 getChartChildren().add(line);
723 .flatMap(detailNode -> detailNode.getEventIDs().stream())
724 .collect(Collectors.toList()));
728 @NbBundle.Messages({
"HideDescriptionAction.displayName=Hide",
729 "HideDescriptionAction.displayMsg=Hide this group from the details view."})
730 class HideDescriptionAction extends Action {
732 HideDescriptionAction(String description,
DescriptionLoD descriptionLoD) {
733 super(Bundle.HideDescriptionAction_displayName());
734 setLongText(Bundle.HideDescriptionAction_displayMsg());
735 setGraphic(
new ImageView(HIDE));
736 setEventHandler((ActionEvent t) -> {
743 .filter(testFilter::equals)
744 .findFirst().orElseGet(() -> {
754 @NbBundle.Messages({
"UnhideDescriptionAction.displayName=Unhide"})
755 class UnhideDescriptionAction extends Action {
757 UnhideDescriptionAction(String description, DescriptionLoD descriptionLoD) {
758 super(Bundle.UnhideDescriptionAction_displayName());
759 setGraphic(
new ImageView(SHOW));
760 setEventHandler((ActionEvent t) ->
762 .filter(descriptionFilter -> descriptionFilter.
getDescriptionLoD().equals(descriptionLoD)
764 .forEach(descriptionfilter -> descriptionfilter.setSelected(
false))
IntervalSelector< DateTime > newIntervalSelector()
void dataItemChanged(Data< DateTime, EventStripe > item)
final ObservableList< EventStripeNode > stripeNodes
void layoutPlotChildren()
static final int PROJECTED_LINE_STROKE_WIDTH
TimeLineController getController()
final BundleType getEventBundle()
static final int MINIMUM_EVENT_NODE_GAP
IntervalSelector<?extends DateTime > intervalSelector
FilteredEventsModel getFilteredEvents()
final ObservableList< EventStripe > eventStripes
void dataItemRemoved(Data< DateTime, EventStripe > item, Series< DateTime, EventStripe > series)
DescriptionLoD getDescriptionLoD()
static Tooltip getDefaultTooltip()
final InvalidationListener layoutInvalidationListener
DateTime getDateTimeForPosition(double x)
IntervalSelector<?extends DateTime > getIntervalSelector()
void selectEventIDs(Collection< Long > events)
synchronized ReadOnlyObjectProperty< ZoomParams > zoomParametersProperty()
final TimeLineChart< X > chart
void setIntervalSelector(IntervalSelector<?extends DateTime > newIntervalSelector)
void requestChartLayout()
final Map< EventCluster, Line > projectionMap
final TimeLineController controller
String formatSpan(DateTime date)
void dataItemAdded(Series< DateTime, EventStripe > series, int itemIndex, Data< DateTime, EventStripe > item)
static ActionGroup newZoomHistoyActionGroup(TimeLineController controller)
double computeYTop(double yMin, double h, TreeRangeMap< Double, Double > maxXatY, double xLeft, double xRight)
SimpleBooleanProperty selectedProperty()
final SimpleBooleanProperty oneEventPerRow
void seriesRemoved(Series< DateTime, EventStripe > series)
final SimpleBooleanProperty bandByType
synchronized SimpleBooleanProperty bandByTypeProperty()
final FilteredEventsModel filteredEvents
void seriesAdded(Series< DateTime, EventStripe > series, int i)
final ReadOnlyDoubleWrapper maxY
double getXForEpochMillis(Long millis)
ContextMenu getChartContextMenu(MouseEvent clickEvent)
Set< String > activeQuickHidefilters
void setSelected(Boolean act)
final SimpleObjectProperty< DescriptionVisibility > descrVisibility
static final int MINIMUM_ROW_HEIGHT
void layoutProjectionMap()
void layoutBundleHelper(final EventBundleNodeBase<?,?,?> bundleNode)
static final String styleSheet
static DateTimeZone getJodaTimeZone()
static final Image MARKER
ContextMenu chartContextMenu
void onChanged(ListChangeListener.Change<?extends EventBundleNodeBase<?,?,?>> change)
void clearIntervalSelector()
final ObservableList< EventStripeNode > sortedStripeNodes
static final int PROJECTED_LINE_Y_OFFSET
DateTime parseDateTime(DateTime date)
static DateTimeFormatter getZonedFormatter()
ContextMenu getChartContextMenu()
final Axis< DateTime > dateAxis
ObservableList< DescriptionFilter > getQuickHideFilters()
Interval adjustInterval(Interval i)
double getParentXForEpochMillis(Long epochMillis)