Autopsy  4.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
DetailViewPane.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2014-15 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.ArrayList;
22 import java.util.List;
23 import javafx.application.Platform;
24 import javafx.beans.InvalidationListener;
25 import javafx.beans.Observable;
26 import javafx.collections.FXCollections;
27 import javafx.collections.ListChangeListener;
28 import javafx.collections.ObservableList;
29 import javafx.concurrent.Task;
30 import javafx.fxml.FXML;
31 import javafx.geometry.Orientation;
32 import javafx.scene.chart.Axis;
33 import javafx.scene.chart.XYChart;
34 import javafx.scene.control.Alert;
35 import javafx.scene.control.ButtonBar;
36 import javafx.scene.control.ButtonType;
37 import javafx.scene.control.CheckBox;
38 import javafx.scene.control.CustomMenuItem;
39 import javafx.scene.control.Label;
40 import javafx.scene.control.MenuButton;
41 import javafx.scene.control.MultipleSelectionModel;
42 import javafx.scene.control.RadioButton;
43 import javafx.scene.control.ScrollBar;
44 import javafx.scene.control.SeparatorMenuItem;
45 import javafx.scene.control.Slider;
46 import javafx.scene.control.ToggleGroup;
47 import javafx.scene.control.TreeItem;
48 import javafx.scene.effect.Effect;
49 import static javafx.scene.input.KeyCode.DOWN;
50 import static javafx.scene.input.KeyCode.KP_DOWN;
51 import static javafx.scene.input.KeyCode.KP_UP;
52 import static javafx.scene.input.KeyCode.PAGE_DOWN;
53 import static javafx.scene.input.KeyCode.PAGE_UP;
54 import static javafx.scene.input.KeyCode.UP;
55 import javafx.scene.input.KeyEvent;
56 import javafx.scene.layout.HBox;
57 import javafx.scene.layout.Pane;
58 import javafx.scene.layout.Priority;
59 import javafx.scene.layout.Region;
60 import javafx.scene.layout.VBox;
61 import javafx.stage.Modality;
62 import org.controlsfx.control.action.Action;
63 import org.joda.time.DateTime;
64 import org.joda.time.Interval;
65 import org.openide.util.NbBundle;
74 
87 public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStripe, EventBundleNodeBase<?, ?, ?>, EventDetailsChart> {
88 
89  private final static Logger LOGGER = Logger.getLogger(DetailViewPane.class.getName());
90 
91  private static final double LINE_SCROLL_PERCENTAGE = .10;
92  private static final double PAGE_SCROLL_PERCENTAGE = .70;
93 
94  private final DateAxis dateAxis = new DateAxis();
95  private final Axis<EventStripe> verticalAxis = new EventAxis();
96  private final ScrollBar vertScrollBar = new ScrollBar();
97  private final Region scrollBarSpacer = new Region();
98 
99  private MultipleSelectionModel<TreeItem<EventBundle<?>>> treeSelectionModel;
100  private final ObservableList<EventBundleNodeBase<?, ?, ?>> highlightedNodes = FXCollections.synchronizedObservableList(FXCollections.observableArrayList());
101 
102  public ObservableList<EventStripe> getEventStripes() {
103  return chart.getEventStripes();
104  }
105 
106  @Override
107  protected void resetData() {
108  for (XYChart.Series<DateTime, EventStripe> s : dataSeries) {
109  s.getData().forEach(chart::removeDataItem);
110  s.getData().clear();
111  }
112  Platform.runLater(() -> {
113  vertScrollBar.setValue(0);
114  });
115 
116  }
117 
118  public DetailViewPane(TimeLineController controller, Pane partPane, Pane contextPane, Region bottomLeftSpacer) {
119  super(controller, partPane, contextPane, bottomLeftSpacer);
120  //initialize chart;
121  chart = new EventDetailsChart(controller, dateAxis, verticalAxis, selectedNodes);
122  setChartClickHandler(); //can we push this into chart
123  chart.setData(dataSeries);
124  setCenter(chart);
125 
126  settingsNodes = new ArrayList<>(new DetailViewSettingsPane().getChildrenUnmodifiable());
127  //bind layout fo axes and spacers
128  dateAxis.setTickLabelGap(0);
129  dateAxis.setAutoRanging(false);
130  dateAxis.setTickLabelsVisible(false);
131  dateAxis.getTickMarks().addListener((Observable observable) -> layoutDateLabels());
132  dateAxis.getTickSpacing().addListener(observable -> layoutDateLabels());
133 
134  verticalAxis.setAutoRanging(false); //prevent XYChart.updateAxisRange() from accessing dataSeries on JFX thread causing ConcurrentModificationException
135  bottomLeftSpacer.minWidthProperty().bind(verticalAxis.widthProperty().add(verticalAxis.tickLengthProperty()));
136  bottomLeftSpacer.prefWidthProperty().bind(verticalAxis.widthProperty().add(verticalAxis.tickLengthProperty()));
137  bottomLeftSpacer.maxWidthProperty().bind(verticalAxis.widthProperty().add(verticalAxis.tickLengthProperty()));
138 
139  scrollBarSpacer.minHeightProperty().bind(dateAxis.heightProperty());
140 
141  //configure scrollbar
142  vertScrollBar.setOrientation(Orientation.VERTICAL);
143  vertScrollBar.maxProperty().bind(chart.maxVScrollProperty().subtract(chart.heightProperty()));
144  vertScrollBar.visibleAmountProperty().bind(chart.heightProperty());
145  vertScrollBar.visibleProperty().bind(vertScrollBar.visibleAmountProperty().greaterThanOrEqualTo(0));
146  VBox.setVgrow(vertScrollBar, Priority.ALWAYS);
147  setRight(new VBox(vertScrollBar, scrollBarSpacer));
148 
149  //interpret scroll events to the scrollBar
150  this.setOnScroll(scrollEvent ->
151  vertScrollBar.valueProperty().set(clampScroll(vertScrollBar.getValue() - scrollEvent.getDeltaY())));
152 
153  //request focus for keyboard scrolling
154  setOnMouseClicked(mouseEvent -> requestFocus());
155 
156  //interpret scroll related keys to scrollBar
157  this.setOnKeyPressed((KeyEvent t) -> {
158  switch (t.getCode()) {
159  case PAGE_UP:
160  incrementScrollValue(-PAGE_SCROLL_PERCENTAGE);
161  t.consume();
162  break;
163  case PAGE_DOWN:
164  incrementScrollValue(PAGE_SCROLL_PERCENTAGE);
165  t.consume();
166  break;
167  case KP_UP:
168  case UP:
169  incrementScrollValue(-LINE_SCROLL_PERCENTAGE);
170  t.consume();
171  break;
172  case KP_DOWN:
173  case DOWN:
174  incrementScrollValue(LINE_SCROLL_PERCENTAGE);
175  t.consume();
176  break;
177  }
178  });
179 
180  //scrollbar value change handler. This forwards changes in scroll bar to chart
181  this.vertScrollBar.valueProperty().addListener(observable -> chart.setVScroll(vertScrollBar.getValue()));
182 
183  //maintain highlighted effect on correct nodes
184  highlightedNodes.addListener((ListChangeListener.Change<? extends EventBundleNodeBase<?, ?, ?>> change) -> {
185  while (change.next()) {
186  change.getAddedSubList().forEach(node -> {
187  node.applyHighlightEffect(true);
188  });
189  change.getRemoved().forEach(node -> {
190  node.applyHighlightEffect(false);
191  });
192  }
193  });
194 
195  selectedNodes.addListener((Observable observable) -> {
196  highlightedNodes.clear();
197  selectedNodes.stream().forEach((tn) -> {
199  t.getDescription().equals(tn.getDescription()))) {
200  highlightedNodes.add(n);
201  }
202  });
203  });
204  }
205 
206  private void incrementScrollValue(double factor) {
207  vertScrollBar.valueProperty().set(clampScroll(vertScrollBar.getValue() + factor * chart.getHeight()));
208  }
209 
210  private Double clampScroll(Double value) {
211  return Math.max(0, Math.min(vertScrollBar.getMax() + 50, value));
212  }
213 
214  public void setSelectionModel(MultipleSelectionModel<TreeItem<EventBundle<?>>> selectionModel) {
215  this.treeSelectionModel = selectionModel;
216 
217  treeSelectionModel.getSelectedItems().addListener((Observable observable) -> {
218  highlightedNodes.clear();
219  for (TreeItem<EventBundle<?>> tn : treeSelectionModel.getSelectedItems()) {
220 
221  for (EventBundleNodeBase<?, ?, ?> n : chart.getNodes((EventBundleNodeBase<?, ?, ?> t) ->
222  t.getDescription().equals(tn.getValue().getDescription()))) {
223  highlightedNodes.add(n);
224  }
225  }
226  });
227  }
228 
229  @Override
230  protected Boolean isTickBold(DateTime value) {
231  return false;
232  }
233 
234  @Override
235  protected Axis<EventStripe> getYAxis() {
236  return verticalAxis;
237  }
238 
239  @Override
240  protected Axis<DateTime> getXAxis() {
241  return dateAxis;
242  }
243 
244  @Override
245  protected double getTickSpacing() {
246  return dateAxis.getTickSpacing().get();
247  }
248 
249  @Override
250  protected String getTickMarkLabel(DateTime value) {
251  return dateAxis.getTickMarkLabel(value);
252  }
253 
254  @Override
255  protected Task<Boolean> getUpdateTask() {
256  return new DetailsUpdateTask();
257  }
258 
259  @Override
260  protected Effect getSelectionEffect() {
261  return null;
262  }
263 
264  @Override
265  protected void applySelectionEffect(EventBundleNodeBase<?, ?, ?> c1, Boolean selected) {
266  c1.applySelectionEffect(selected);
267  }
268 
269  private class DetailViewSettingsPane extends HBox {
270 
271  @FXML
272  private RadioButton hiddenRadio;
273 
274  @FXML
275  private RadioButton showRadio;
276 
277  @FXML
278  private ToggleGroup descrVisibility;
279 
280  @FXML
281  private RadioButton countsRadio;
282 
283  @FXML
284  private CheckBox bandByTypeBox;
285 
286  @FXML
287  private CheckBox oneEventPerRowBox;
288 
289  @FXML
290  private CheckBox truncateAllBox;
291 
292  @FXML
293  private Slider truncateWidthSlider;
294 
295  @FXML
296  private Label truncateSliderLabel;
297 
298  @FXML
300 
301  @FXML
302  private CustomMenuItem bandByTypeBoxMenuItem;
303 
304  @FXML
305  private CustomMenuItem oneEventPerRowBoxMenuItem;
306 
307  @FXML
308  private CustomMenuItem truncateAllBoxMenuItem;
309 
310  @FXML
311  private CustomMenuItem truncateSliderLabelMenuItem;
312 
313  @FXML
314  private CustomMenuItem showRadioMenuItem;
315 
316  @FXML
317  private CustomMenuItem countsRadioMenuItem;
318 
319  @FXML
320  private CustomMenuItem hiddenRadioMenuItem;
321 
322  @FXML
323  private SeparatorMenuItem descVisibilitySeparatorMenuItem;
324 
326  FXMLConstructor.construct(DetailViewSettingsPane.this, "DetailViewSettingsPane.fxml"); // NON-NLS
327  }
328 
329  @FXML
330  void initialize() {
331  assert bandByTypeBox != null : "fx:id=\"bandByTypeBox\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; // NON-NLS
332  assert oneEventPerRowBox != null : "fx:id=\"oneEventPerRowBox\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; // NON-NLS
333  assert truncateAllBox != null : "fx:id=\"truncateAllBox\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; // NON-NLS
334  assert truncateWidthSlider != null : "fx:id=\"truncateAllSlider\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; // NON-NLS
335  bandByTypeBox.selectedProperty().bindBidirectional(chart.bandByTypeProperty());
336  truncateAllBox.selectedProperty().bindBidirectional(chart.truncateAllProperty());
337  oneEventPerRowBox.selectedProperty().bindBidirectional(chart.oneEventPerRowProperty());
338  truncateSliderLabel.disableProperty().bind(truncateAllBox.selectedProperty().not());
339  truncateSliderLabel.setText(NbBundle.getMessage(DetailViewPane.class, "DetailViewPane.truncateSliderLabel.text"));
340  final InvalidationListener sliderListener = o -> {
341  if (truncateWidthSlider.isValueChanging() == false) {
342  chart.getTruncateWidth().set(truncateWidthSlider.getValue());
343  }
344  };
345  truncateWidthSlider.valueProperty().addListener(sliderListener);
346  truncateWidthSlider.valueChangingProperty().addListener(sliderListener);
347 
348  descrVisibility.selectedToggleProperty().addListener((observable, oldToggle, newToggle) -> {
349  if (newToggle == countsRadio) {
350  chart.descrVisibilityProperty().set(DescriptionVisibility.COUNT_ONLY);
351  } else if (newToggle == showRadio) {
352  chart.descrVisibilityProperty().set(DescriptionVisibility.SHOWN);
353  } else if (newToggle == hiddenRadio) {
354  chart.descrVisibilityProperty().set(DescriptionVisibility.HIDDEN);
355  }
356  });
357 
358  advancedLayoutOptionsButtonLabel.setText(
359  NbBundle.getMessage(DetailViewPane.class, "DetailViewPane.advancedLayoutOptionsButtonLabel.text"));
360  bandByTypeBox.setText(NbBundle.getMessage(DetailViewPane.class, "DetailViewPane.bandByTypeBox.text"));
361  bandByTypeBoxMenuItem.setText(
362  NbBundle.getMessage(DetailViewPane.class, "DetailViewPane.bandByTypeBoxMenuItem.text"));
363  oneEventPerRowBox.setText(NbBundle.getMessage(DetailViewPane.class, "DetailViewPane.oneEventPerRowBox.text"));
364  oneEventPerRowBoxMenuItem.setText(
365  NbBundle.getMessage(DetailViewPane.class, "DetailViewPane.oneEventPerRowBoxMenuItem.text"));
366  truncateAllBox.setText(NbBundle.getMessage(DetailViewPane.class, "DetailViewPan.truncateAllBox.text"));
367  truncateAllBoxMenuItem.setText(
368  NbBundle.getMessage(DetailViewPane.class, "DetailViewPan.truncateAllBoxMenuItem.text"));
369  truncateSliderLabelMenuItem.setText(
370  NbBundle.getMessage(DetailViewPane.class, "DetailViewPane.truncateSlideLabelMenuItem.text"));
371  descVisibilitySeparatorMenuItem.setText(
372  NbBundle.getMessage(DetailViewPane.class, "DetailViewPane.descVisSeparatorMenuItem.text"));
373  showRadioMenuItem.setText(NbBundle.getMessage(DetailViewPane.class, "DetailViewPane.showRadioMenuItem.text"));
374  showRadio.setText(NbBundle.getMessage(DetailViewPane.class, "DetailViewPane.showRadio.text"));
375  countsRadioMenuItem.setText(NbBundle.getMessage(DetailViewPane.class, "DetailViewPane.countsRadioMenuItem.text"));
376  countsRadio.setText(NbBundle.getMessage(DetailViewPane.class, "DetailViewPane.countsRadio.text"));
377  hiddenRadioMenuItem.setText(NbBundle.getMessage(DetailViewPane.class, "DetailViewPane.hiddenRadioMenuItem.text"));
378  hiddenRadio.setText(NbBundle.getMessage(DetailViewPane.class, "DetailViewPane.hiddenRadio.text"));
379  }
380  }
381 
382  public Action newUnhideDescriptionAction(String description, DescriptionLoD descriptionLoD) {
383  return chart.new UnhideDescriptionAction(description, descriptionLoD);
384  }
385 
386  public Action newHideDescriptionAction(String description, DescriptionLoD descriptionLoD) {
387  return chart.new HideDescriptionAction(description, descriptionLoD);
388  }
389 
390  @NbBundle.Messages({
391  "DetailViewPane.loggedTask.queryDb=Retreiving event data",
392  "DetailViewPane.loggedTask.name=Updating Details View",
393  "DetailViewPane.loggedTask.updateUI=Populating visualization",
394  "DetailViewPane.loggedTask.continueButton=Continue",
395  "DetailViewPane.loggedTask.backButton=Back (Cancel)",
396  "# {0} - number of events",
397  "DetailViewPane.loggedTask.prompt=You are about to show details for {0} events. This might be very slow or even crash Autopsy.\n\nDo you want to continue?"})
398  private class DetailsUpdateTask extends VisualizationUpdateTask<Interval> {
399 
401  super(Bundle.DetailViewPane_loggedTask_name(), true);
402  }
403 
404  @Override
405  protected Boolean call() throws Exception {
406  super.call();
407  if (isCancelled()) {
408  return null;
409  }
410 
411  resetChart(getTimeRange());
412 
413  updateMessage(Bundle.DetailViewPane_loggedTask_queryDb());
414  List<EventStripe> eventStripes = filteredEvents.getEventStripes();
415  if (eventStripes.size() > 2000) {
416  Task<ButtonType> task = new Task<ButtonType>() {
417 
418  @Override
419  protected ButtonType call() throws Exception {
420  ButtonType ContinueButtonType = new ButtonType(Bundle.DetailViewPane_loggedTask_continueButton(), ButtonBar.ButtonData.OK_DONE);
421  ButtonType back = new ButtonType(Bundle.DetailViewPane_loggedTask_backButton(), ButtonBar.ButtonData.CANCEL_CLOSE);
422 
423  Alert alert = new Alert(Alert.AlertType.WARNING, Bundle.DetailViewPane_loggedTask_prompt(eventStripes.size()), ContinueButtonType, back);
424  alert.setHeaderText("");
425  alert.initModality(Modality.APPLICATION_MODAL);
426  alert.initOwner(getScene().getWindow());
427  ButtonType orElse = alert.showAndWait().orElse(back);
428  if (orElse == back) {
429  DetailsUpdateTask.this.cancel();
430  }
431  return orElse;
432  }
433  };
434  Platform.runLater(task);
435  ButtonType get = task.get();
436  }
437  updateMessage(Bundle.DetailViewPane_loggedTask_updateUI());
438  final int size = eventStripes.size();
439  for (int i = 0; i < size; i++) {
440  if (isCancelled()) {
441  return null;
442  }
443  updateProgress(i, size);
444  final EventStripe cluster = eventStripes.get(i);
445  final XYChart.Data<DateTime, EventStripe> dataItem = new XYChart.Data<>(new DateTime(cluster.getStartMillis()), cluster);
446  getSeries(cluster.getEventType()).getData().add(dataItem);
447  chart.addDataItem(dataItem);
448  }
449 
450  return eventStripes.isEmpty() == false;
451  }
452 
453  @Override
454  protected void cancelled() {
455  super.cancelled();
456  controller.retreat();
457  }
458 
459  @Override
460  protected void setDateAxisValues(Interval timeRange) {
461  dateAxis.setRange(timeRange, true);
462  }
463  }
464 }
Action newHideDescriptionAction(String description, DescriptionLoD descriptionLoD)
final ObservableList< EventBundleNodeBase<?,?,?> > highlightedNodes
DetailViewPane(TimeLineController controller, Pane partPane, Pane contextPane, Region bottomLeftSpacer)
void applySelectionEffect(EventBundleNodeBase<?,?,?> c1, Boolean selected)
MultipleSelectionModel< TreeItem< EventBundle<?> > > treeSelectionModel
synchronized static Logger getLogger(String name)
Definition: Logger.java:166
static void construct(Node node, String fxmlFileName)
void setSelectionModel(MultipleSelectionModel< TreeItem< EventBundle<?>>> selectionModel)
Action newUnhideDescriptionAction(String description, DescriptionLoD descriptionLoD)

Copyright © 2012-2015 Basis Technology. Generated on: Wed Apr 6 2016
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.