Autopsy 4.22.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
DetailsChart.java
Go to the documentation of this file.
1/*
2 * Autopsy Forensic Browser
3 *
4 * Copyright 2014-19 Basis Technology Corp.
5 * Contact: carrier <at> sleuthkit <dot> org
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19package org.sleuthkit.autopsy.timeline.ui.detailview;
20
21import java.util.Arrays;
22import java.util.MissingResourceException;
23import java.util.function.Predicate;
24import javafx.beans.property.SimpleObjectProperty;
25import javafx.collections.FXCollections;
26import javafx.collections.ObservableList;
27import javafx.collections.ObservableSet;
28import javafx.collections.SetChangeListener;
29import javafx.geometry.Side;
30import javafx.scene.chart.Axis;
31import javafx.scene.control.ContextMenu;
32import javafx.scene.control.Control;
33import javafx.scene.control.Skin;
34import javafx.scene.control.SkinBase;
35import javafx.scene.image.Image;
36import javafx.scene.image.ImageView;
37import javafx.scene.input.MouseEvent;
38import javafx.scene.layout.Pane;
39import org.controlsfx.control.MasterDetailPane;
40import org.controlsfx.control.action.Action;
41import org.controlsfx.control.action.ActionUtils;
42import org.joda.time.DateTime;
43import org.joda.time.Interval;
44import org.openide.util.NbBundle;
45import org.sleuthkit.autopsy.coreutils.ThreadConfined;
46import org.sleuthkit.autopsy.timeline.EventsModel;
47import org.sleuthkit.autopsy.timeline.TimeLineController;
48import org.sleuthkit.autopsy.timeline.actions.AddManualEvent;
49import org.sleuthkit.autopsy.timeline.ui.IntervalSelector;
50import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
51import org.sleuthkit.autopsy.timeline.ui.detailview.datamodel.DetailViewEvent;
52import org.sleuthkit.autopsy.timeline.ui.detailview.datamodel.DetailsViewModel;
53import org.sleuthkit.autopsy.timeline.ui.detailview.datamodel.EventStripe;
54
58final class DetailsChart extends Control implements TimeLineChart<DateTime> {
59
61 private final DateAxis detailsChartDateAxis;
62 private final DateAxis pinnedDateAxis;
63 private final Axis<EventStripe> verticalAxis;
64
68 private final SimpleObjectProperty<IntervalSelector<? extends DateTime>> intervalSelectorProp = new SimpleObjectProperty<>();
69
73 private final ObservableSet<GuideLine> guideLines = FXCollections.observableSet();
74
81 private final SimpleObjectProperty<Predicate<EventNodeBase<?>>> highlightPredicate = new SimpleObjectProperty<>(dummy -> false);
82
86 private final ObservableList<EventNodeBase<?>> selectedNodes;
87
93 private final ObservableList<DetailViewEvent> nestedEvents = FXCollections.observableArrayList();
94
99 private final DetailsChartLayoutSettings layoutSettings;
100 private final DetailsViewModel detailsViewModel;
101
102 DetailsViewModel getDetailsViewModel() {
103 return detailsViewModel;
104 }
105
109 private final TimeLineController controller;
110
114 @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
115 private final ObservableList<EventStripe> rootEventStripes = FXCollections.observableArrayList();
116
132 DetailsChart(DetailsViewModel detailsViewModel, TimeLineController controller, DateAxis detailsChartDateAxis, DateAxis pinnedDateAxis, Axis<EventStripe> verticalAxis, ObservableList<EventNodeBase<?>> selectedNodes) {
133 this.detailsViewModel = detailsViewModel;
134 this.controller = controller;
135 this.layoutSettings = new DetailsChartLayoutSettings(controller);
136 this.detailsChartDateAxis = detailsChartDateAxis;
137 this.verticalAxis = verticalAxis;
138 this.pinnedDateAxis = pinnedDateAxis;
139 this.selectedNodes = selectedNodes;
140
141 EventsModel eventsModel = getController().getEventsModel();
142
143 /*
144 * If the time range is changed, clear the guide line and the interval
145 * selector, since they may not be in view any more.
146 */
147 eventsModel.timeRangeProperty().addListener(observable -> clearTimeBasedUIElements());
148
149 //if the view paramaters change, clear the selection
150 eventsModel.modelParamsProperty().addListener(observable -> getSelectedNodes().clear());
151 }
152
161 DateTime getDateTimeForPosition(double xPos) {
162 return getXAxis().getValueForDisplay(getXAxis().parentToLocal(xPos, 0).getX());
163 }
164
170 @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
171 void addStripe(EventStripe stripe) {
172 rootEventStripes.add(stripe);
173 nestedEvents.add(stripe);
174 }
175
181 void clearGuideLine(GuideLine guideLine) {
182 guideLines.remove(guideLine);
183 }
184
185 @Override
186 public ObservableList<EventNodeBase<?>> getSelectedNodes() {
187 return selectedNodes;
188 }
189
195 DetailsChartLayoutSettings getLayoutSettings() {
196 return layoutSettings;
197 }
198
208 void setHighlightPredicate(Predicate<EventNodeBase<?>> highlightPredicate) {
209 this.highlightPredicate.set(highlightPredicate);
210 }
211
215 @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
216 void reset() {
217 rootEventStripes.clear();
218 nestedEvents.clear();
219 }
220
224 public ObservableList<DetailViewEvent> getAllNestedEvents() {
225 return nestedEvents;
226 }
227
232 private void clearTimeBasedUIElements() {
233 guideLines.clear();
235 }
236
237 @Override
238 public void clearIntervalSelector() {
239 intervalSelectorProp.set(null);
240 }
241
242 @Override
243 public IntervalSelector<DateTime> newIntervalSelector() {
244 return new DetailIntervalSelector(this);
245 }
246
247 @Override
248 public IntervalSelector<? extends DateTime> getIntervalSelector() {
249 return intervalSelectorProp.get();
250 }
251
252 @Override
253 public void setIntervalSelector(IntervalSelector<? extends DateTime> newIntervalSelector) {
254 intervalSelectorProp.set(newIntervalSelector);
255 }
256
257 @Override
258 public Axis<DateTime> getXAxis() {
259 return detailsChartDateAxis;
260 }
261
262 @Override
263 public TimeLineController getController() {
264 return controller;
265 }
266
267 @Override
268 public void clearContextMenu() {
269 setContextMenu(null);
270 }
271
272 @Override
273 public ContextMenu getContextMenu(MouseEvent mouseEvent) throws MissingResourceException {
274 //get the current context menu and hide it if it is not null
275 ContextMenu contextMenu = getContextMenu();
276 if (contextMenu != null) {
277 contextMenu.hide();
278 }
279
280 long selectedTimeMillis = getXAxis().getValueForDisplay(getXAxis().parentToLocal(mouseEvent.getX(), 0).getX()).getMillis();
281
282 //make and assign a new context menu based on the given mouseEvent
283 setContextMenu(ActionUtils.createContextMenu(Arrays.asList(
284 new PlaceMarkerAction(this, mouseEvent),
285 new AddManualEvent(controller, selectedTimeMillis),
286 ActionUtils.ACTION_SEPARATOR,
287 TimeLineChart.newZoomHistoyActionGroup(getController())
288 )));
289 return getContextMenu();
290 }
291
292 @Override
293 protected Skin<?> createDefaultSkin() {
294 return new DetailsChartSkin(this);
295 }
296
302 ObservableList<EventStripe> getRootEventStripes() {
303 return rootEventStripes;
304 }
305
309 private static class DetailIntervalSelector extends IntervalSelector<DateTime> {
310
316 DetailIntervalSelector(IntervalSelector.IntervalSelectorProvider<DateTime> chart) {
317 super(chart);
318 }
319
320 @Override
321 protected String formatSpan(DateTime date) {
322 return date.toString(TimeLineController.getZonedFormatter());
323 }
324
325 @Override
326 protected Interval adjustInterval(Interval interval) {
327 return interval;
328 }
329
330 @Override
331 protected DateTime parseDateTime(DateTime date) {
332 return date;
333 }
334 }
335
339 static private class PlaceMarkerAction extends Action {
340
341 private static final Image MARKER = new Image("/org/sleuthkit/autopsy/timeline/images/marker.png", 16, 16, true, true, true); //NON-NLS
342 private GuideLine guideLine;
343
344 @NbBundle.Messages({"PlaceMArkerAction.name=Place Marker"})
345 PlaceMarkerAction(DetailsChart chart, MouseEvent clickEvent) {
346 super(Bundle.PlaceMArkerAction_name());
347
348 setGraphic(new ImageView(MARKER)); // NON-NLS
349 setEventHandler(actionEvent -> {
350 if (guideLine == null) {
351 guideLine = new GuideLine(chart);
352 guideLine.relocate(chart.sceneToLocal(clickEvent.getSceneX(), 0).getX(), 0);
353 chart.guideLines.add(guideLine);
354
355 } else {
356 guideLine.relocate(chart.sceneToLocal(clickEvent.getSceneX(), 0).getX(), 0);
357 }
358 });
359 }
360 }
361
366 static private class DetailsChartSkin extends SkinBase<DetailsChart> {
367
371 private static final int MIN_PINNED_LANE_HEIGHT = 50;
372
373 /*
374 * The ChartLane for the main area of this chart. It is affected by all
375 * the view settings.
376 */
378
382 private final ScrollingLaneWrapper primaryView;
383
384 /*
385 * The ChartLane for the area of this chart that shows pinned eventsd.
386 * It is not affected any filters.
387 */
389
393 private final ScrollingLaneWrapper pinnedView;
394
400 private final MasterDetailPane masterDetailPane;
401
405 private final Pane rootPane;
406
412 private double dividerPosition = .1;
413
414 @NbBundle.Messages("DetailViewPane.pinnedLaneLabel.text=Pinned Events")
415 DetailsChartSkin(DetailsChart chart) {
416 super(chart);
417 //initialize chart lanes;
418 primaryLane = new PrimaryDetailsChartLane(chart, getSkinnable().detailsChartDateAxis, getSkinnable().verticalAxis);
419 primaryView = new ScrollingLaneWrapper(primaryLane);
420 pinnedLane = new PinnedEventsChartLane(chart, getSkinnable().pinnedDateAxis, new EventAxis<>(Bundle.DetailViewPane_pinnedLaneLabel_text()));
421 pinnedView = new ScrollingLaneWrapper(pinnedLane);
422
424 pinnedLane.maxVScrollProperty().addListener(maxVScroll -> syncPinnedHeight());
425
426 //assemble scene graph
427 masterDetailPane = new MasterDetailPane(Side.TOP, primaryView, pinnedView, false);
428 masterDetailPane.setDividerPosition(dividerPosition);
429 masterDetailPane.prefHeightProperty().bind(getSkinnable().heightProperty());
430 masterDetailPane.prefWidthProperty().bind(getSkinnable().widthProperty());
431 rootPane = new Pane(masterDetailPane);
432 getChildren().add(rootPane);
433
434 //maintain highlighted effect on correct nodes
435 getSkinnable().highlightPredicate.addListener((observable, oldPredicate, newPredicate) -> {
436 primaryLane.getAllNodes().forEach(primaryNode -> primaryNode.applyHighlightEffect(newPredicate.test(primaryNode)));
437 pinnedLane.getAllNodes().forEach(pinnedNode -> pinnedNode.applyHighlightEffect(newPredicate.test(pinnedNode)));
438 });
439
440 //configure mouse listeners
441 TimeLineChart.MouseClickedHandler<DateTime, DetailsChart> mouseClickedHandler = new TimeLineChart.MouseClickedHandler<>(getSkinnable());
442 TimeLineChart.ChartDragHandler<DateTime, DetailsChart> chartDragHandler = new TimeLineChart.ChartDragHandler<>(getSkinnable());
443 configureMouseListeners(primaryLane, mouseClickedHandler, chartDragHandler);
444 configureMouseListeners(pinnedLane, mouseClickedHandler, chartDragHandler);
445
446 //show and hide pinned lane in response to settings property change
447 getSkinnable().getLayoutSettings().pinnedLaneShowing().addListener(observable -> syncPinnedLaneShowing());
449
450 //show and remove interval selector in sync with control state change
451 getSkinnable().intervalSelectorProp.addListener((observable, oldIntervalSelector, newIntervalSelector) -> {
452 rootPane.getChildren().remove(oldIntervalSelector);
453 if (null != newIntervalSelector) {
454 rootPane.getChildren().add(newIntervalSelector);
455 }
456 });
457
458 //show and remove guidelines in sync with control state change
459 getSkinnable().guideLines.addListener((SetChangeListener.Change<? extends GuideLine> change) -> {
460 if (change.wasRemoved()) {
461 rootPane.getChildren().remove(change.getElementRemoved());
462 }
463 if (change.wasAdded()) {
464 rootPane.getChildren().add(change.getElementAdded());
465 }
466 });
467 }
468
473 private void syncPinnedHeight() {
475 pinnedView.setMaxHeight(pinnedLane.maxVScrollProperty().get() + 30);
476 }
477
487 static private void configureMouseListeners(final DetailsChartLane<?> chartLane, final TimeLineChart.MouseClickedHandler<DateTime, DetailsChart> mouseClickedHandler, final TimeLineChart.ChartDragHandler<DateTime, DetailsChart> chartDragHandler) {
488 chartLane.setOnMousePressed(chartDragHandler);
489 chartLane.setOnMouseReleased(chartDragHandler);
490 chartLane.setOnMouseDragged(chartDragHandler);
491 chartLane.setOnMouseClicked(chartDragHandler);
492 chartLane.addEventHandler(MouseEvent.MOUSE_CLICKED, mouseClickedHandler);
493 }
494
499 private void syncPinnedLaneShowing() {
500 boolean pinnedLaneShowing = getSkinnable().getLayoutSettings().isPinnedLaneShowing();
501 if (pinnedLaneShowing == false) {
502 //Save the divider position for later.
503 dividerPosition = masterDetailPane.getDividerPosition();
504 }
505
506 masterDetailPane.setShowDetailNode(pinnedLaneShowing);
507
508 if (pinnedLaneShowing) {
510 //Restore the devider position.
511 masterDetailPane.setDividerPosition(dividerPosition);
512 }
513 }
514 }
515}
final ReadOnlyObjectWrapper< Interval > timeRangeProperty
IntervalSelector(IntervalSelectorProvider< X > chart)
static void configureMouseListeners(final DetailsChartLane<?> chartLane, final TimeLineChart.MouseClickedHandler< DateTime, DetailsChart > mouseClickedHandler, final TimeLineChart.ChartDragHandler< DateTime, DetailsChart > chartDragHandler)
ObservableList<? extends Node > getSelectedNodes()
IntervalSelector<? extends X > getIntervalSelector()

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.