Autopsy  3.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
VisualizationPanel.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2013 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;
20 
21 import java.net.URL;
22 import java.time.Instant;
23 import java.time.LocalDateTime;
24 import java.time.ZonedDateTime;
25 import java.util.ArrayList;
26 import java.util.ResourceBundle;
27 import javafx.application.Platform;
28 import javafx.beans.InvalidationListener;
29 import javafx.beans.Observable;
30 import javafx.beans.value.ChangeListener;
31 import javafx.beans.value.ObservableValue;
32 import javafx.event.ActionEvent;
33 import javafx.fxml.FXML;
34 import javafx.geometry.Insets;
35 import javafx.geometry.Rectangle2D;
36 import javafx.scene.SnapshotParameters;
37 import javafx.scene.control.*;
38 import javafx.scene.effect.Lighting;
39 import javafx.scene.image.WritableImage;
40 import javafx.scene.input.MouseEvent;
41 import javafx.scene.layout.Background;
42 import javafx.scene.layout.BackgroundFill;
43 import javafx.scene.layout.BorderPane;
44 import javafx.scene.layout.CornerRadii;
45 import javafx.scene.layout.HBox;
46 import javafx.scene.layout.Pane;
47 import javafx.scene.layout.Priority;
48 import javafx.scene.layout.Region;
49 import static javafx.scene.layout.Region.USE_PREF_SIZE;
50 import javafx.scene.layout.StackPane;
51 import javafx.scene.paint.Color;
52 import javax.annotation.concurrent.GuardedBy;
53 import jfxtras.scene.control.LocalDateTimeTextField;
54 import org.controlsfx.control.RangeSlider;
55 import org.controlsfx.control.action.Action;
56 import org.joda.time.DateTime;
57 import org.joda.time.DateTimeZone;
58 import org.joda.time.Interval;
59 import org.openide.util.NbBundle;
74 
82 public class VisualizationPanel extends BorderPane implements TimeLineView {
83 
84  @GuardedBy("this")
85  private LoggedTask<Void> histogramTask;
86 
87  private static final Logger LOGGER = Logger.getLogger(VisualizationPanel.class.getName());
88 
89  private final NavPanel navPanel;
90 
91  private AbstractVisualization<?, ?, ?, ?> visualization;
92 
93  @FXML // ResourceBundle that was given to the FXMLLoader
94  private ResourceBundle resources;
95 
96  @FXML // URL location of the FXML file that was given to the FXMLLoader
97  private URL location;
98 
100  @FXML // fx:id="histogramBox"
101  protected HBox histogramBox; // Value injected by FXMLLoader
102 
103  @FXML // fx:id="rangeHistogramStack"
104  protected StackPane rangeHistogramStack; // Value injected by FXMLLoader
105 
106  private final RangeSlider rangeSlider = new RangeSlider(0, 1.0, .25, .75);
107 
109  @FXML
110  protected MenuButton zoomMenuButton;
111 
112  @FXML
113  private Separator rightSeperator;
114 
115  @FXML
116  private Separator leftSeperator;
117 
118  @FXML
119  protected Button zoomOutButton;
120 
121  @FXML
122  protected Button zoomInButton;
123 
124  @FXML
125  protected LocalDateTimeTextField startPicker;
126 
127  @FXML
128  protected LocalDateTimeTextField endPicker;
129 
131  @FXML
132  protected Pane partPane;
133 
134  @FXML
135  protected Pane contextPane;
136 
137  @FXML
138  protected Region spacer;
139 
141  @FXML
142  private ToolBar toolBar;
143 
144  @FXML
145  private ToggleButton countsToggle;
146 
147  @FXML
148  private ToggleButton detailsToggle;
149 
150  @FXML
151  private Button snapShotButton;
152 
153  private double preDragPos;
154 
156 
158 
159  private final ChangeListener<Object> rangeSliderListener
160  = (observable1, oldValue, newValue) -> {
161  if (rangeSlider.isHighValueChanging() == false && rangeSlider.isLowValueChanging() == false) {
162  Long minTime = filteredEvents.getMinTime() * 1000;
163  controller.pushTimeRange(new Interval(
164  new Double(rangeSlider.getLowValue() + minTime).longValue(),
165  new Double(rangeSlider.getHighValue() + minTime).longValue(),
166  DateTimeZone.UTC));
167  }
168  };
169 
170  private final InvalidationListener endListener = (Observable observable) -> {
171  if (endPicker.getLocalDateTime() != null) {
173  ZonedDateTime.of(endPicker.getLocalDateTime(), TimeLineController.getTimeZoneID()).toInstant().toEpochMilli()));
174  }
175  };
176 
177  private final InvalidationListener startListener = (Observable observable) -> {
178  if (startPicker.getLocalDateTime() != null) {
180  ZonedDateTime.of(startPicker.getLocalDateTime(), TimeLineController.getTimeZoneID()).toInstant().toEpochMilli()));
181  }
182  };
183 
184  static private final Background background = new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY));
185 
186  static private final Lighting lighting = new Lighting();
187 
189  this.navPanel = navPanel;
190  FXMLConstructor.construct(this, "VisualizationPanel.fxml"); // NON-NLS
191  }
192 
193  @FXML // This method is called by the FXMLLoader when initialization is complete
194  protected void initialize() {
195  assert endPicker != null : "fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
196  assert histogramBox != null : "fx:id=\"histogramBox\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
197  assert startPicker != null : "fx:id=\"startPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
198  assert rangeHistogramStack != null : "fx:id=\"rangeHistogramStack\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
199  assert countsToggle != null : "fx:id=\"countsToggle\" was not injected: check your FXML file 'VisToggle.fxml'."; // NON-NLS
200  assert detailsToggle != null : "fx:id=\"eventsToggle\" was not injected: check your FXML file 'VisToggle.fxml'."; // NON-NLS
201 
202  HBox.setHgrow(leftSeperator, Priority.ALWAYS);
203  HBox.setHgrow(rightSeperator, Priority.ALWAYS);
204  ChangeListener<Toggle> toggleListener = (ObservableValue<? extends Toggle> observable,
205  Toggle oldValue,
206  Toggle newValue) -> {
207  if (newValue == null) {
208  countsToggle.getToggleGroup().selectToggle(oldValue != null ? oldValue : countsToggle);
209  } else if (newValue == countsToggle && oldValue != null) {
211  } else if (newValue == detailsToggle && oldValue != null) {
213  }
214  };
215 
216  if (countsToggle.getToggleGroup() != null) {
217  countsToggle.getToggleGroup().selectedToggleProperty().addListener(toggleListener);
218  } else {
219  countsToggle.toggleGroupProperty().addListener((Observable observable) -> {
220  countsToggle.getToggleGroup().selectedToggleProperty().addListener(toggleListener);
221  });
222  }
223  countsToggle.setText(NbBundle.getMessage(this.getClass(), "VisualizationPanel.countsToggle.text"));
224  detailsToggle.setText(NbBundle.getMessage(this.getClass(), "VisualizationPanel.detailsToggle.text"));
225 
226  //setup rangeslider
227  rangeSlider.setOpacity(.7);
228  rangeSlider.setMin(0);
229 
230 // /** this is still needed to not get swamped by low/high value changes.
231 // * https://bitbucket.org/controlsfx/controlsfx/issue/241/rangeslider-high-low-properties
232 // * TODO: committ an appropriate version of this fix to the ControlsFX
233 // * repo on bitbucket, remove this after next release -jm */
234 // Skin<?> skin = rangeSlider.getSkin();
235 // if (skin != null) {
236 // attachDragListener((RangeSliderSkin) skin);
237 // } else {
238 // rangeSlider.skinProperty().addListener((Observable observable) -> {
239 // RangeSliderSkin skin1 = (RangeSliderSkin) rangeSlider.getSkin();
240 // attachDragListener(skin1);
241 // });
242 // }
243 
244  rangeSlider.setBlockIncrement(1);
245 
246  rangeHistogramStack.getChildren().add(rangeSlider);
247 
248  /* this padding attempts to compensates for the fact that the
249  * rangeslider track doesn't extend to edge of node,and so the
250  * histrogram doesn't quite line up with the rangeslider */
251  histogramBox.setStyle(" -fx-padding: 0,0.5em,0,.5em; "); // NON-NLS
252 
253  zoomMenuButton.getItems().clear();
254  for (ZoomRanges b : ZoomRanges.values()) {
255 
256  MenuItem menuItem = new MenuItem(b.getDisplayName());
257  menuItem.setOnAction((event) -> {
258  if (b != ZoomRanges.ALL) {
259  controller.pushPeriod(b.getPeriod());
260  } else {
261  controller.showFullRange();
262  }
263  });
264  zoomMenuButton.getItems().add(menuItem);
265  }
266  zoomMenuButton.setText(NbBundle.getMessage(this.getClass(), "VisualizationPanel.zoomMenuButton.text"));
267 
268  zoomOutButton.setOnAction(e -> {
270  });
271  zoomInButton.setOnAction(e -> {
273  });
274 
275  snapShotButton.setOnAction((ActionEvent event) -> {
276  //take snapshot
277  final SnapshotParameters snapshotParameters = new SnapshotParameters();
278  snapshotParameters.setViewport(new Rectangle2D(visualization.getBoundsInParent().getMinX(), visualization.getBoundsInParent().getMinY(),
279  visualization.getBoundsInParent().getWidth(),
280  contextPane.getLayoutBounds().getHeight() + visualization.getLayoutBounds().getHeight() + partPane.getLayoutBounds().getHeight()
281  ));
282  WritableImage snapshot = this.snapshot(snapshotParameters, null);
283  //pass snapshot to save action
284  new SaveSnapshot(controller, snapshot).handle(event);
285  });
286 
287  snapShotButton.setText(NbBundle.getMessage(this.getClass(), "VisualizationPanel.snapShotButton.text"));
288  }
289 
290 // /**
291 // * TODO: committed an appropriate version of this fix to the ControlsFX repo
292 // * on bitbucket, remove this after next release -jm
293 // *
294 // * @param skin
295 // */
296 // private void attachDragListener(RangeSliderSkin skin) {
297 // if (skin != null) {
298 // for (Node n : skin.getChildren()) {
299 // if (n.getStyleClass().contains("track")) {
300 // n.setOpacity(.3);
301 // }
302 // if (n.getStyleClass().contains("range-bar")) {
303 // StackPane rangeBar = (StackPane) n;
304 // rangeBar.setOnMousePressed((MouseEvent e) -> {
305 // rangeBar.requestFocus();
306 // preDragPos = e.getX();
307 // });
308 //
309 // //don't mark as not changing until mouse is released
310 // rangeBar.setOnMouseReleased((MouseEvent event) -> {
311 // rangeSlider.setLowValueChanging(false);
312 // rangeSlider.setHighValueChanging(false);
313 // });
314 // rangeBar.setOnMouseDragged((MouseEvent event) -> {
315 // final double min = rangeSlider.getMin();
316 // final double max = rangeSlider.getMax();
317 //
318 // ///!!! compensate for range and width so that rangebar actualy stays with the slider
319 // double delta = (event.getX() - preDragPos) * (max - min) / rangeSlider.
320 // getWidth();
321 // ////////////////////////////////////////////////////
322 //
323 // final double lowValue = rangeSlider.getLowValue();
324 // final double newLowValue = Math.min(Math.max(min, lowValue + delta),
325 // max);
326 // final double highValue = rangeSlider.getHighValue();
327 // final double newHighValue = Math.min(Math.max(min, highValue + delta),
328 // max);
329 //
330 // if (newLowValue <= min || newHighValue >= max) {
331 // return;
332 // }
333 //
334 // rangeSlider.setLowValueChanging(true);
335 // rangeSlider.setHighValueChanging(true);
336 // rangeSlider.setLowValue(newLowValue);
337 // rangeSlider.setHighValue(newHighValue);
338 // });
339 // }
340 // }
341 // }
342 // }
343 
344  @Override
345  public synchronized void setController(TimeLineController controller) {
346  this.controller = controller;
347  setModel(controller.getEventsModel());
348 
349  setViewMode(controller.getViewMode().get());
350 
351  controller.getNeedsHistogramRebuild().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
352  if (newValue) {
354  }
355  });
356 
357  controller.getViewMode().addListener((ObservableValue<? extends VisualizationMode> ov, VisualizationMode t, VisualizationMode t1) -> {
358  setViewMode(t1);
359  });
361  }
362 
363  private void setViewMode(VisualizationMode visualizationMode) {
364  switch (visualizationMode) {
365  case COUNTS:
366  setVisualization(new CountsViewPane(partPane, contextPane, spacer));
367  countsToggle.setSelected(true);
368  break;
369  case DETAIL:
370  setVisualization(new DetailViewPane(partPane, contextPane, spacer));
371  detailsToggle.setSelected(true);
372  break;
373  }
374  }
375 
376  synchronized void setVisualization(final AbstractVisualization<?, ?, ?, ?> newViz) {
377  Platform.runLater(() -> {
378  synchronized (VisualizationPanel.this) {
379  if (visualization != null) {
380  toolBar.getItems().removeAll(visualization.getSettingsNodes());
382  }
383 
384  visualization = newViz;
385  toolBar.getItems().addAll(newViz.getSettingsNodes());
386 
388  setCenter(visualization);
389  if (visualization instanceof DetailViewPane) {
390  navPanel.setChart((DetailViewPane) visualization);
391  }
392  visualization.hasEvents.addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
393  if (newValue == false) {
394 
395  setCenter(new StackPane(visualization, new Region() {
396  {
397  setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)));
398  setOpacity(.3);
399  }
400  }, new NoEventsDialog(() -> {
401  setCenter(visualization);
402  })));
403  } else {
404  setCenter(visualization);
405  }
406  });
407  }
408  });
409  }
410 
411  synchronized private void refreshHistorgram() {
412 
413  if (histogramTask != null) {
414  histogramTask.cancel(true);
415  }
416 
418  NbBundle.getMessage(this.getClass(), "VisualizationPanel.histogramTask.title"), true) {
419 
420  @Override
421  protected Void call() throws Exception {
422 
423  updateMessage(NbBundle.getMessage(this.getClass(), "VisualizationPanel.histogramTask.preparing"));
424 
425  long max = 0;
427  final long lowerBound = rangeInfo.getLowerBound();
428  final long upperBound = rangeInfo.getUpperBound();
429  Interval timeRange = new Interval(new DateTime(lowerBound, TimeLineController.getJodaTimeZone()), new DateTime(upperBound, TimeLineController.getJodaTimeZone()));
430 
431  //extend range to block bounderies (ie day, month, year)
432  int p = 0; // progress counter
433 
434  //clear old data, and reset ranges and series
435  Platform.runLater(() -> {
436  updateMessage(NbBundle.getMessage(this.getClass(), "VisualizationPanel.histogramTask.resetUI"));
437 
438  });
439 
440  ArrayList<Long> bins = new ArrayList<>();
441 
442  DateTime start = timeRange.getStart();
443  while (timeRange.contains(start)) {
444  if (isCancelled()) {
445  return null;
446  }
447  DateTime end = start.plus(rangeInfo.getPeriodSize().getPeriod());
448  final Interval interval = new Interval(start, end);
449  //increment for next iteration
450 
451  start = end;
452 
453  updateMessage(NbBundle.getMessage(this.getClass(), "VisualizationPanel.histogramTask.queryDb"));
454  //query for current range
455  long count = filteredEvents.getEventCounts(interval).values().stream().mapToLong(Long::valueOf).sum();
456  bins.add(count);
457 
458  max = Math.max(count, max);
459 
460  final double fMax = Math.log(max);
461  final ArrayList<Long> fbins = new ArrayList<>(bins);
462  Platform.runLater(() -> {
463  updateMessage(NbBundle.getMessage(this.getClass(), "VisualizationPanel.histogramTask.updateUI2"));
464 
465  histogramBox.getChildren().clear();
466 
467  for (Long bin : fbins) {
468  if (isCancelled()) {
469  break;
470  }
471  Region bar = new Region();
472  //scale them to fit in histogram height
473  bar.prefHeightProperty().bind(histogramBox.heightProperty().multiply(Math.log(bin)).divide(fMax));
474  bar.setMaxHeight(USE_PREF_SIZE);
475  bar.setMinHeight(USE_PREF_SIZE);
476  bar.setBackground(background);
477  bar.setOnMouseEntered((MouseEvent event) -> {
478  Tooltip.install(bar, new Tooltip(bin.toString()));
479  });
480  bar.setEffect(lighting);
481  //they each get equal width to fill the histogram horizontally
482  HBox.setHgrow(bar, Priority.ALWAYS);
483  histogramBox.getChildren().add(bar);
484  }
485  });
486  }
487  return null;
488  }
489 
490  };
491  new Thread(histogramTask).start();
493  }
494 
495  @Override
497  this.filteredEvents = filteredEvents;
498 
499  refreshTimeUI(filteredEvents.timeRange().get());
500  this.filteredEvents.timeRange().addListener((Observable observable) -> {
501  refreshTimeUI(filteredEvents.timeRange().get());
502  });
503  TimeLineController.getTimeZone().addListener((Observable observable) -> {
504  refreshTimeUI(filteredEvents.timeRange().get());
505  });
506  }
507 
508  private void refreshTimeUI(Interval interval) {
510 
511  final Long minTime = rangeDivisionInfo.getLowerBound();
512  final long maxTime = rangeDivisionInfo.getUpperBound();
513 
514  if (minTime > 0 && maxTime > minTime) {
515 
516  Platform.runLater(() -> {
517  startPicker.localDateTimeProperty().removeListener(startListener);
518  endPicker.localDateTimeProperty().removeListener(endListener);
519  rangeSlider.highValueChangingProperty().removeListener(rangeSliderListener);
520  rangeSlider.lowValueChangingProperty().removeListener(rangeSliderListener);
521 
522  rangeSlider.setMax((Long) (maxTime - minTime));
523  rangeSlider.setHighValue(interval.getEndMillis() - minTime);
524  rangeSlider.setLowValue(interval.getStartMillis() - minTime);
525  endPicker.setLocalDateTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(interval.getEndMillis()), TimeLineController.getTimeZoneID()));
526  startPicker.setLocalDateTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(interval.getStartMillis()), TimeLineController.getTimeZoneID()));
527 
528  rangeSlider.highValueChangingProperty().addListener(rangeSliderListener);
529  rangeSlider.lowValueChangingProperty().addListener(rangeSliderListener);
530  startPicker.localDateTimeProperty().addListener(startListener);
531  endPicker.localDateTimeProperty().addListener(endListener);
532  });
533  }
534  }
535 
536  private class NoEventsDialog extends TitledPane {
537 
538  private final Runnable closeCallback;
539 
540  @FXML // ResourceBundle that was given to the FXMLLoader
541  private ResourceBundle resources;
542 
543  @FXML // URL location of the FXML file that was given to the FXMLLoader
544  private URL location;
545 
546  @FXML
547  private Button resetFiltersButton;
548 
549  @FXML
550  private Button dismissButton;
551 
552  @FXML
553  private Button zoomButton;
554 
555  @FXML
556  private Label visualizationModeLabel;
557 
558  @FXML
559  private Label noEventsDialogLabel;
560 
561  @FXML
562  private Label startLabel;
563 
564  @FXML
565  private Label endLabel;
566 
567  public NoEventsDialog(Runnable closeCallback) {
568  this.closeCallback = closeCallback;
569  FXMLConstructor.construct(this, "NoEventsDialog.fxml"); // NON-NLS
570 
571  }
572 
573  @FXML
574  void initialize() {
575  assert resetFiltersButton != null : "fx:id=\"resetFiltersButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
576  assert dismissButton != null : "fx:id=\"dismissButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
577  assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
578 
579  visualizationModeLabel.setText(
580  NbBundle.getMessage(this.getClass(), "VisualizationPanel.visualizationModeLabel.text"));
581  noEventsDialogLabel.setText(
582  NbBundle.getMessage(this.getClass(), "VisualizationPanel.noEventsDialogLabel.text"));
583  zoomButton.setText(NbBundle.getMessage(this.getClass(), "VisualizationPanel.zoomButton.text"));
584  startLabel.setText(NbBundle.getMessage(this.getClass(), "VisualizationPanel.startLabel.text"));
585  endLabel.setText(NbBundle.getMessage(this.getClass(), "VisualizationPanel.endLabel.text"));
586 
587  Action zoomOutAction = new ZoomOut(controller);
588  zoomButton.setOnAction(zoomOutAction);
589  zoomButton.disableProperty().bind(zoomOutAction.disabledProperty());
590 
591  dismissButton.setOnAction(e -> {
592  closeCallback.run();
593  });
594  Action defaultFiltersAction = new DefaultFilters(controller);
595  resetFiltersButton.setOnAction(defaultFiltersAction);
596  resetFiltersButton.disableProperty().bind(defaultFiltersAction.disabledProperty());
597  resetFiltersButton.setText(
598  NbBundle.getMessage(this.getClass(), "VisualizationPanel.resetFiltersButton.text"));
599  }
600  }
601 }
void setModel(FilteredEventsModel filteredEvents)
static ReadOnlyObjectProperty< TimeZone > getTimeZone()
synchronized void setController(TimeLineController controller)
synchronized void setController(TimeLineController controller)
static void construct(Node n, String fxmlFileName)
static RangeDivisionInfo getRangeDivisionInfo(Interval timeRange)
synchronized ReadOnlyObjectProperty< Interval > timeRange()
synchronized void monitorTask(final Task<?> task)
Map< EventType, Long > getEventCounts(Interval timeRange)
synchronized ReadOnlyObjectProperty< VisualizationMode > getViewMode()
void setChart(DetailViewPane detailViewPane)
Definition: NavPanel.java:79
void setViewMode(VisualizationMode visualizationMode)
synchronized void pushTimeRange(Interval timeRange)
synchronized void setViewMode(VisualizationMode visualizationMode)

Copyright © 2012-2015 Basis Technology. Generated on: Mon Oct 19 2015
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.