Autopsy  4.19.3
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  */
19 package org.sleuthkit.autopsy.timeline.ui.detailview;
20 
21 import java.util.Arrays;
22 import java.util.MissingResourceException;
23 import java.util.function.Predicate;
24 import javafx.beans.property.SimpleObjectProperty;
25 import javafx.collections.FXCollections;
26 import javafx.collections.ObservableList;
27 import javafx.collections.ObservableSet;
28 import javafx.collections.SetChangeListener;
29 import javafx.geometry.Side;
30 import javafx.scene.chart.Axis;
31 import javafx.scene.control.ContextMenu;
32 import javafx.scene.control.Control;
33 import javafx.scene.control.Skin;
34 import javafx.scene.control.SkinBase;
35 import javafx.scene.image.Image;
36 import javafx.scene.image.ImageView;
37 import javafx.scene.input.MouseEvent;
38 import javafx.scene.layout.Pane;
39 import org.controlsfx.control.MasterDetailPane;
40 import org.controlsfx.control.action.Action;
41 import org.controlsfx.control.action.ActionUtils;
42 import org.joda.time.DateTime;
43 import org.joda.time.Interval;
44 import org.openide.util.NbBundle;
54 
58 final 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();
234  clearIntervalSelector();
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 
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 
423  pinnedLane.setMinHeight(MIN_PINNED_LANE_HEIGHT);
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() {
474  pinnedView.setMinHeight(MIN_PINNED_LANE_HEIGHT);
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) {
509  syncPinnedHeight();
510  //Restore the devider position.
511  masterDetailPane.setDividerPosition(dividerPosition);
512  }
513  }
514  }
515 }
static void configureMouseListeners(final DetailsChartLane<?> chartLane, final TimeLineChart.MouseClickedHandler< DateTime, DetailsChart > mouseClickedHandler, final TimeLineChart.ChartDragHandler< DateTime, DetailsChart > chartDragHandler)
void setIntervalSelector(IntervalSelector<?extends X > newIntervalSelector)

Copyright © 2012-2022 Basis Technology. Generated on: Tue Jun 27 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.