Autopsy  4.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-16 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;
52 
56 final class DetailsChart extends Control implements TimeLineChart<DateTime> {
57 
59  private final DateAxis detailsChartDateAxis;
60  private final DateAxis pinnedDateAxis;
61  private final Axis<EventStripe> verticalAxis;
62 
66  private final SimpleObjectProperty<IntervalSelector<? extends DateTime>> intervalSelectorProp = new SimpleObjectProperty<>();
67 
71  private final ObservableSet<GuideLine> guideLines = FXCollections.observableSet();
72 
79  private final SimpleObjectProperty<Predicate<EventNodeBase<?>>> highlightPredicate = new SimpleObjectProperty<>((x) -> false);
80 
84  private final ObservableList<EventNodeBase<?>> selectedNodes;
85 
91  private final ObservableList<TimeLineEvent> nestedEvents = FXCollections.observableArrayList();
92 
97  private final DetailsChartLayoutSettings layoutSettings;
98 
102  private final TimeLineController controller;
103 
107  @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
108  private final ObservableList<EventStripe> rootEventStripes = FXCollections.observableArrayList();
109 
124  DetailsChart(TimeLineController controller, DateAxis detailsChartDateAxis, DateAxis pinnedDateAxis, Axis<EventStripe> verticalAxis, ObservableList<EventNodeBase<?>> selectedNodes) {
125  this.controller = controller;
126  this.layoutSettings = new DetailsChartLayoutSettings(controller);
127  this.detailsChartDateAxis = detailsChartDateAxis;
128  this.verticalAxis = verticalAxis;
129  this.pinnedDateAxis = pinnedDateAxis;
130  this.selectedNodes = selectedNodes;
131 
132  FilteredEventsModel eventsModel = getController().getEventsModel();
133 
134  /*
135  * If the time range is changed, clear the guide line and the interval
136  * selector, since they may not be in view any more.
137  */
138  eventsModel.timeRangeProperty().addListener(o -> clearTimeBasedUIElements());
139 
140  //if the view paramaters change, clear the selection
141  eventsModel.zoomParametersProperty().addListener(o -> getSelectedNodes().clear());
142  }
143 
152  DateTime getDateTimeForPosition(double xPos) {
153  return getXAxis().getValueForDisplay(getXAxis().parentToLocal(xPos, 0).getX());
154  }
155 
161  @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
162  void addStripe(EventStripe stripe) {
163  rootEventStripes.add(stripe);
164  nestedEvents.add(stripe);
165  }
166 
172  void clearGuideLine(GuideLine guideLine) {
173  guideLines.remove(guideLine);
174  }
175 
176  @Override
177  public ObservableList<EventNodeBase<?>> getSelectedNodes() {
178  return selectedNodes;
179  }
180 
186  DetailsChartLayoutSettings getLayoutSettings() {
187  return layoutSettings;
188  }
189 
199  void setHighlightPredicate(Predicate<EventNodeBase<?>> highlightPredicate) {
200  this.highlightPredicate.set(highlightPredicate);
201  }
202 
206  @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
207  void reset() {
208  rootEventStripes.clear();
209  nestedEvents.clear();
210  }
211 
215  public ObservableList<TimeLineEvent> getAllNestedEvents() {
216  return nestedEvents;
217  }
218 
223  private void clearTimeBasedUIElements() {
224  guideLines.clear();
225  clearIntervalSelector();
226  }
227 
228  @Override
229  public void clearIntervalSelector() {
230  intervalSelectorProp.set(null);
231  }
232 
233  @Override
234  public IntervalSelector<DateTime> newIntervalSelector() {
235  return new DetailIntervalSelector(this);
236  }
237 
238  @Override
239  public IntervalSelector<? extends DateTime> getIntervalSelector() {
240  return intervalSelectorProp.get();
241  }
242 
243  @Override
244  public void setIntervalSelector(IntervalSelector<? extends DateTime> newIntervalSelector) {
245  intervalSelectorProp.set(newIntervalSelector);
246  }
247 
248  @Override
249  public Axis<DateTime> getXAxis() {
250  return detailsChartDateAxis;
251  }
252 
253  @Override
254  public TimeLineController getController() {
255  return controller;
256  }
257 
258  @Override
259  public void clearContextMenu() {
260  setContextMenu(null);
261  }
262 
263  @Override
264  public ContextMenu getContextMenu(MouseEvent mouseEvent) throws MissingResourceException {
265  //get the current context menu and hide it if it is not null
266  ContextMenu contextMenu = getContextMenu();
267  if (contextMenu != null) {
268  contextMenu.hide();
269  }
270 
271  //make and assign a new context menu based on the given mouseEvent
272  setContextMenu(ActionUtils.createContextMenu(Arrays.asList(
273  new PlaceMarkerAction(this, mouseEvent),
274  ActionUtils.ACTION_SEPARATOR,
275  TimeLineChart.newZoomHistoyActionGroup(getController())
276  )));
277  return getContextMenu();
278  }
279 
280  @Override
281  protected Skin<?> createDefaultSkin() {
282  return new DetailsChartSkin(this);
283  }
284 
290  ObservableList<EventStripe> getRootEventStripes() {
291  return rootEventStripes;
292  }
293 
297  private static class DetailIntervalSelector extends IntervalSelector<DateTime> {
298 
305  super(chart);
306  }
307 
308  @Override
309  protected String formatSpan(DateTime date) {
310  return date.toString(TimeLineController.getZonedFormatter());
311  }
312 
313  @Override
314  protected Interval adjustInterval(Interval i) {
315  return i;
316  }
317 
318  @Override
319  protected DateTime parseDateTime(DateTime date) {
320  return date;
321  }
322  }
323 
327  static private class PlaceMarkerAction extends Action {
328 
329  private static final Image MARKER = new Image("/org/sleuthkit/autopsy/timeline/images/marker.png", 16, 16, true, true, true); //NON-NLS
330  private GuideLine guideLine;
331 
332  @NbBundle.Messages({"PlaceMArkerAction.name=Place Marker"})
333  PlaceMarkerAction(DetailsChart chart, MouseEvent clickEvent) {
334  super(Bundle.PlaceMArkerAction_name());
335 
336  setGraphic(new ImageView(MARKER)); // NON-NLS
337  setEventHandler(actionEvent -> {
338  if (guideLine == null) {
339  guideLine = new GuideLine(chart);
340  guideLine.relocate(chart.sceneToLocal(clickEvent.getSceneX(), 0).getX(), 0);
341  chart.guideLines.add(guideLine);
342 
343  } else {
344  guideLine.relocate(chart.sceneToLocal(clickEvent.getSceneX(), 0).getX(), 0);
345  }
346  });
347  }
348  }
349 
354  static private class DetailsChartSkin extends SkinBase<DetailsChart> {
355 
359  private static final int MIN_PINNED_LANE_HEIGHT = 50;
360 
361  /*
362  * The ChartLane for the main area of this chart. It is affected by all
363  * the view settings.
364  */
366 
370  private final ScrollingLaneWrapper primaryView;
371 
372  /*
373  * The ChartLane for the area of this chart that shows pinned eventsd.
374  * It is not affected any filters.
375  */
377 
381  private final ScrollingLaneWrapper pinnedView;
382 
388  private final MasterDetailPane masterDetailPane;
389 
393  private final Pane rootPane;
394 
400  private double dividerPosition = .1;
401 
402  @NbBundle.Messages("DetailViewPane.pinnedLaneLabel.text=Pinned Events")
403  DetailsChartSkin(DetailsChart chart) {
404  super(chart);
405  //initialize chart lanes;
406  primaryLane = new PrimaryDetailsChartLane(chart, getSkinnable().detailsChartDateAxis, getSkinnable().verticalAxis);
407  primaryView = new ScrollingLaneWrapper(primaryLane);
408  pinnedLane = new PinnedEventsChartLane(chart, getSkinnable().pinnedDateAxis, new EventAxis<>(Bundle.DetailViewPane_pinnedLaneLabel_text()));
409  pinnedView = new ScrollingLaneWrapper(pinnedLane);
410 
411  pinnedLane.setMinHeight(MIN_PINNED_LANE_HEIGHT);
412  pinnedLane.maxVScrollProperty().addListener(maxVScroll -> syncPinnedHeight());
413 
414  //assemble scene graph
415  masterDetailPane = new MasterDetailPane(Side.TOP, primaryView, pinnedView, false);
416  masterDetailPane.setDividerPosition(dividerPosition);
417  masterDetailPane.prefHeightProperty().bind(getSkinnable().heightProperty());
418  masterDetailPane.prefWidthProperty().bind(getSkinnable().widthProperty());
419  rootPane = new Pane(masterDetailPane);
420  getChildren().add(rootPane);
421 
422  //maintain highlighted effect on correct nodes
423  getSkinnable().highlightPredicate.addListener((observable, oldPredicate, newPredicate) -> {
424  primaryLane.getAllNodes().forEach(primaryNode -> primaryNode.applyHighlightEffect(newPredicate.test(primaryNode)));
425  pinnedLane.getAllNodes().forEach(pinnedNode -> pinnedNode.applyHighlightEffect(newPredicate.test(pinnedNode)));
426  });
427 
428  //configure mouse listeners
429  TimeLineChart.MouseClickedHandler<DateTime, DetailsChart> mouseClickedHandler = new TimeLineChart.MouseClickedHandler<>(getSkinnable());
430  TimeLineChart.ChartDragHandler<DateTime, DetailsChart> chartDragHandler = new TimeLineChart.ChartDragHandler<>(getSkinnable());
431  configureMouseListeners(primaryLane, mouseClickedHandler, chartDragHandler);
432  configureMouseListeners(pinnedLane, mouseClickedHandler, chartDragHandler);
433 
434  //show and hide pinned lane in response to settings property change
435  getSkinnable().getLayoutSettings().pinnedLaneShowing().addListener(observable -> syncPinnedLaneShowing());
437 
438  //show and remove interval selector in sync with control state change
439  getSkinnable().intervalSelectorProp.addListener((observable, oldIntervalSelector, newIntervalSelector) -> {
440  rootPane.getChildren().remove(oldIntervalSelector);
441  if (null != newIntervalSelector) {
442  rootPane.getChildren().add(newIntervalSelector);
443  }
444  });
445 
446  //show and remove guidelines in sync with control state change
447  getSkinnable().guideLines.addListener((SetChangeListener.Change<? extends GuideLine> change) -> {
448  if (change.wasRemoved()) {
449  rootPane.getChildren().remove(change.getElementRemoved());
450  }
451  if (change.wasAdded()) {
452  rootPane.getChildren().add(change.getElementAdded());
453  }
454  });
455  }
456 
461  private void syncPinnedHeight() {
462  pinnedView.setMinHeight(MIN_PINNED_LANE_HEIGHT);
463  pinnedView.setMaxHeight(pinnedLane.maxVScrollProperty().get() + 30);
464  }
465 
475  static private void configureMouseListeners(final DetailsChartLane<?> chartLane, final TimeLineChart.MouseClickedHandler<DateTime, DetailsChart> mouseClickedHandler, final TimeLineChart.ChartDragHandler<DateTime, DetailsChart> chartDragHandler) {
476  chartLane.setOnMousePressed(chartDragHandler);
477  chartLane.setOnMouseReleased(chartDragHandler);
478  chartLane.setOnMouseDragged(chartDragHandler);
479  chartLane.setOnMouseClicked(chartDragHandler);
480  chartLane.addEventHandler(MouseEvent.MOUSE_CLICKED, mouseClickedHandler);
481  }
482 
487  private void syncPinnedLaneShowing() {
488  boolean pinnedLaneShowing = getSkinnable().getLayoutSettings().isPinnedLaneShowing();
489  if (pinnedLaneShowing == false) {
490  //Save the divider position for later.
491  dividerPosition = masterDetailPane.getDividerPosition();
492  }
493 
494  masterDetailPane.setShowDetailNode(pinnedLaneShowing);
495 
496  if (pinnedLaneShowing) {
497  syncPinnedHeight();
498  //Restore the devider position.
499  masterDetailPane.setDividerPosition(dividerPosition);
500  }
501  }
502  }
503 }
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-2016 Basis Technology. Generated on: Mon Apr 24 2017
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.