Autopsy  4.1
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 2011-2016 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 com.google.common.collect.ImmutableList;
22 import java.util.List;
23 import java.util.Objects;
24 import java.util.function.Function;
25 import java.util.function.Predicate;
26 import java.util.stream.Collectors;
27 import javafx.application.Platform;
28 import javafx.beans.InvalidationListener;
29 import javafx.beans.Observable;
30 import javafx.collections.ObservableList;
31 import javafx.concurrent.Task;
32 import javafx.fxml.FXML;
33 import javafx.scene.Node;
34 import javafx.scene.chart.Axis;
35 import javafx.scene.control.Alert;
36 import javafx.scene.control.ButtonBar;
37 import javafx.scene.control.ButtonType;
38 import javafx.scene.control.CheckBox;
39 import javafx.scene.control.Label;
40 import javafx.scene.control.MenuButton;
41 import javafx.scene.control.RadioButton;
42 import javafx.scene.control.Slider;
43 import javafx.scene.control.ToggleButton;
44 import javafx.scene.control.ToggleGroup;
45 import javafx.scene.layout.HBox;
46 import javafx.stage.Modality;
47 import org.apache.commons.lang3.StringUtils;
48 import org.controlsfx.control.action.Action;
49 import org.joda.time.DateTime;
50 import org.joda.time.Interval;
51 import org.openide.util.NbBundle;
64 
79 public class DetailViewPane extends AbstractTimelineChart<DateTime, EventStripe, EventNodeBase<?>, DetailsChart> {
80 
81  private final static Logger LOGGER = Logger.getLogger(DetailViewPane.class.getName());
82 
83  private final DateAxis detailsChartDateAxis = new DateAxis();
84  private final DateAxis pinnedDateAxis = new DateAxis();
85 
86  @NbBundle.Messages("DetailViewPane.primaryLaneLabel.text=All Events (Filtered)")
87  private final Axis<EventStripe> verticalAxis = new EventAxis<>(Bundle.DetailViewPane_primaryLaneLabel_text());
88 
94 
100 
107  super(controller);
108  this.selectedEvents = new MappedList<>(getSelectedNodes(), EventNodeBase<?>::getEvent);
109 
110  //initialize chart;
111  setChart(new DetailsChart(controller, detailsChartDateAxis, pinnedDateAxis, verticalAxis, getSelectedNodes()));
112 
113  //bind layout fo axes and spacers
114  detailsChartDateAxis.getTickMarks().addListener((Observable observable) -> layoutDateLabels());
115  detailsChartDateAxis.getTickSpacing().addListener(observable -> layoutDateLabels());
116  verticalAxis.setAutoRanging(false); //prevent XYChart.updateAxisRange() from accessing dataSeries on JFX thread causing ConcurrentModificationException
117 
118  getSelectedNodes().addListener((Observable observable) -> {
119  //update selected nodes highlight
120  getChart().setHighlightPredicate(getSelectedNodes()::contains);
121 
122  //update controllers list of selected event ids when view's selection changes.
124  .flatMap(detailNode -> detailNode.getEventIDs().stream())
125  .collect(Collectors.toList()));
126  });
127  }
128 
129  /*
130  * Get all the trees of events flattened into a single list, but only
131  * including EventStripes and any leaf SingleEvents, since, EventClusters
132  * contain no interesting non-time related information.
133  */
134  public ObservableList<TimeLineEvent> getAllNestedEvents() {
135  return getChart().getAllNestedEvents();
136  }
137 
138  /*
139  * Get a list of the events that are selected in thes view.
140  */
141  public ObservableList<TimeLineEvent> getSelectedEvents() {
142  return selectedEvents;
143  }
144 
152  public void setHighLightedEvents(ObservableList<TimeLineEvent> highlightedEvents) {
153  highlightedEvents.addListener((Observable observable) -> {
154  /*
155  * build a predicate that matches events with the same description
156  * as any of the events in highlightedEvents or which are selected
157  */
158  Predicate<EventNodeBase<?>> highlightPredicate =
159  highlightedEvents.stream() // => events
160  .map(TimeLineEvent::getDescription)// => event descriptions
161  .map(new Function<String, Predicate<EventNodeBase<?>>>() {
162  @Override
163  public Predicate<EventNodeBase<?>> apply(String description) {
164  return eventNode -> StringUtils.equalsIgnoreCase(eventNode.getDescription(), description);
165  }
166  })// => predicates that match strings agains the descriptions of the events in highlightedEvents
167  .reduce(getSelectedNodes()::contains, Predicate::or); // => predicate that matches an of the descriptions or selected nodes
168  getChart().setHighlightPredicate(highlightPredicate); //use this predicate to highlight nodes
169  });
170  }
171 
172  @Override
173  final protected DateAxis getXAxis() {
174  return detailsChartDateAxis;
175  }
176 
185  public Action newUnhideDescriptionAction(String description, DescriptionLoD descriptionLoD) {
186  return new UnhideDescriptionAction(description, descriptionLoD, getChart());
187  }
188 
197  public Action newHideDescriptionAction(String description, DescriptionLoD descriptionLoD) {
198  return new HideDescriptionAction(description, descriptionLoD, getChart());
199  }
200 
202  @Override
203  protected void clearData() {
204  getChart().reset();
205  }
206 
207  @Override
208  protected Boolean isTickBold(DateTime value) {
209  return false;
210  }
211 
212  @Override
213  final protected Axis<EventStripe> getYAxis() {
214  return verticalAxis;
215  }
216 
217  @Override
218  protected double getTickSpacing() {
219  return detailsChartDateAxis.getTickSpacing().get();
220  }
221 
222  @Override
223  protected String getTickMarkLabel(DateTime value) {
224  return detailsChartDateAxis.getTickMarkLabel(value);
225  }
226 
227  @Override
228  protected Task<Boolean> getNewUpdateTask() {
229  return new DetailsUpdateTask();
230  }
231 
232  @Override
233  protected void applySelectionEffect(EventNodeBase<?> c1, Boolean selected) {
234  c1.applySelectionEffect(selected);
235  }
236 
237  @Override
238  protected double getAxisMargin() {
239  return 0;
240  }
241 
242  @Override
243  final protected ViewMode getViewMode() {
244  return ViewMode.DETAIL;
245  }
246 
247  @Override
248  protected ImmutableList<Node> getSettingsControls() {
249  return ImmutableList.copyOf(new DetailViewSettingsPane(getChart().getLayoutSettings()).getChildrenUnmodifiable());
250  }
251 
252  @Override
253  protected boolean hasCustomTimeNavigationControls() {
254  return false;
255  }
256 
257  @Override
258  protected ImmutableList<Node> getTimeNavigationControls() {
259  return ImmutableList.of();
260  }
261 
266  static private class DetailViewSettingsPane extends HBox {
267 
268  @FXML
269  private RadioButton hiddenRadio;
270 
271  @FXML
272  private RadioButton showRadio;
273 
274  @FXML
275  private ToggleGroup descrVisibility;
276 
277  @FXML
278  private RadioButton countsRadio;
279 
280  @FXML
281  private CheckBox bandByTypeBox;
282 
283  @FXML
284  private CheckBox oneEventPerRowBox;
285 
286  @FXML
287  private CheckBox truncateAllBox;
288 
289  @FXML
290  private Slider truncateWidthSlider;
291 
292  @FXML
293  private Label truncateSliderLabel;
294 
295  @FXML
297 
298  @FXML
299  private ToggleButton pinnedEventsToggle;
300 
301  private final DetailsChartLayoutSettings layoutSettings;
302 
303  DetailViewSettingsPane(DetailsChartLayoutSettings layoutSettings) {
304  this.layoutSettings = layoutSettings;
305  FXMLConstructor.construct(DetailViewSettingsPane.this, "DetailViewSettingsPane.fxml"); //NON-NLS
306  }
307 
308  @NbBundle.Messages({
309  "DetailViewPane.truncateSliderLabel.text=max description width (px):",
310  "DetailViewPane.advancedLayoutOptionsButtonLabel.text=Advanced Layout Options",
311  "DetailViewPane.bandByTypeBox.text=Band by Type",
312  "DetailViewPane.oneEventPerRowBox.text=One Per Row",
313  "DetailViewPane.truncateAllBox.text=Truncate Descriptions",
314  "DetailViewPane.showRadio.text=Show Full Description",
315  "DetailViewPane.countsRadio.text=Show Counts Only",
316  "DetailViewPane.hiddenRadio.text=Hide Description"})
317  @FXML
318  void initialize() {
319  assert bandByTypeBox != null : "fx:id=\"bandByTypeBox\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; //NON-NLS
320  assert oneEventPerRowBox != null : "fx:id=\"oneEventPerRowBox\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; //NON-NLS
321  assert truncateAllBox != null : "fx:id=\"truncateAllBox\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; //NON-NLS
322  assert truncateWidthSlider != null : "fx:id=\"truncateAllSlider\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; //NON-NLS
323  assert pinnedEventsToggle != null : "fx:id=\"pinnedEventsToggle\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; //NON-NLS
324 
325  //bind widgets to settings object properties
326  bandByTypeBox.selectedProperty().bindBidirectional(layoutSettings.bandByTypeProperty());
327 
328  oneEventPerRowBox.selectedProperty().bindBidirectional(layoutSettings.oneEventPerRowProperty());
329  truncateAllBox.selectedProperty().bindBidirectional(layoutSettings.truncateAllProperty());
330  truncateSliderLabel.disableProperty().bind(truncateAllBox.selectedProperty().not());
331  pinnedEventsToggle.selectedProperty().bindBidirectional(layoutSettings.pinnedLaneShowing());
332 
333  final InvalidationListener sliderListener = observable -> {
334  if (truncateWidthSlider.isValueChanging() == false) {
335  layoutSettings.truncateWidthProperty().set(truncateWidthSlider.getValue());
336  }
337  };
338  truncateWidthSlider.valueProperty().addListener(sliderListener);
339  truncateWidthSlider.valueChangingProperty().addListener(sliderListener);
340 
341  descrVisibility.selectedToggleProperty().addListener((observable, oldToggle, newToggle) -> {
342  if (newToggle == countsRadio) {
343  layoutSettings.descrVisibilityProperty().set(DescriptionVisibility.COUNT_ONLY);
344  } else if (newToggle == showRadio) {
345  layoutSettings.descrVisibilityProperty().set(DescriptionVisibility.SHOWN);
346  } else if (newToggle == hiddenRadio) {
347  layoutSettings.descrVisibilityProperty().set(DescriptionVisibility.HIDDEN);
348  }
349  });
350 
351  //Assign localized labels
352  truncateSliderLabel.setText(Bundle.DetailViewPane_truncateSliderLabel_text());
353  advancedLayoutOptionsButtonLabel.setText(Bundle.DetailViewPane_advancedLayoutOptionsButtonLabel_text());
354  bandByTypeBox.setText(Bundle.DetailViewPane_bandByTypeBox_text());
355  oneEventPerRowBox.setText(Bundle.DetailViewPane_oneEventPerRowBox_text());
356  truncateAllBox.setText(Bundle.DetailViewPane_truncateAllBox_text());
357  showRadio.setText(Bundle.DetailViewPane_showRadio_text());
358  countsRadio.setText(Bundle.DetailViewPane_countsRadio_text());
359  hiddenRadio.setText(Bundle.DetailViewPane_hiddenRadio_text());
360  }
361  }
362 
363  @NbBundle.Messages({
364  "DetailViewPane.loggedTask.queryDb=Retrieving event data",
365  "DetailViewPane.loggedTask.name=Updating Details View",
366  "DetailViewPane.loggedTask.updateUI=Populating view",
367  "DetailViewPane.loggedTask.continueButton=Continue",
368  "DetailViewPane.loggedTask.backButton=Back (Cancel)",
369  "# {0} - number of events",
370  "DetailViewPane.loggedTask.prompt=You are about to show details for {0} events. This might be very slow and could exhaust available memory.\n\nDo you want to continue?"})
371  private class DetailsUpdateTask extends ViewRefreshTask<Interval> {
372 
374  super(Bundle.DetailViewPane_loggedTask_name(), true);
375  }
376 
377  @Override
378  protected Boolean call() throws Exception {
379  super.call();
380 
381  if (isCancelled()) {
382  return null;
383  }
384  FilteredEventsModel eventsModel = getEventsModel();
385  ZoomParams newZoomParams = eventsModel.getZoomParamaters();
386 
387  //if the zoomParams haven't actually changed, just bail
388  if (Objects.equals(currentZoomParams, newZoomParams)) {
389  return true;
390  }
391 
392  updateMessage(Bundle.DetailViewPane_loggedTask_queryDb());
393 
394  //get the event stripes to be displayed
395  List<EventStripe> eventStripes = eventsModel.getEventStripes();
396  final int size = eventStripes.size();
397  //if there are too many stipes show a confirmation dialog
398  if (size > 2000) {
399  Task<ButtonType> task = new Task<ButtonType>() {
400  @Override
401  protected ButtonType call() throws Exception {
402  ButtonType ContinueButtonType = new ButtonType(Bundle.DetailViewPane_loggedTask_continueButton(), ButtonBar.ButtonData.OK_DONE);
403  ButtonType back = new ButtonType(Bundle.DetailViewPane_loggedTask_backButton(), ButtonBar.ButtonData.CANCEL_CLOSE);
404 
405  Alert alert = new Alert(Alert.AlertType.WARNING, Bundle.DetailViewPane_loggedTask_prompt(size), ContinueButtonType, back);
406  alert.setHeaderText("");
407  alert.initModality(Modality.APPLICATION_MODAL);
408  alert.initOwner(getScene().getWindow());
409  ButtonType userResponse = alert.showAndWait().orElse(back);
410  if (userResponse == back) {
411  DetailsUpdateTask.this.cancel();
412  }
413  return userResponse;
414  }
415  };
416  //show dialog on JFX thread and block this thread until the dialog is dismissed.
417  Platform.runLater(task);
418  task.get();
419  }
420  if (isCancelled()) {
421  return null;
422  }
423  //we are going to accept the new zoomParams
424  currentZoomParams = newZoomParams;
425 
426  //clear the chart and set the horixontal axis
427  resetView(eventsModel.getTimeRange());
428 
429  updateMessage(Bundle.DetailViewPane_loggedTask_updateUI());
430 
431  //add all the stripes
432  for (int i = 0; i < size; i++) {
433  if (isCancelled()) {
434  return null;
435  }
436  updateProgress(i, size);
437  final EventStripe stripe = eventStripes.get(i);
438  Platform.runLater(() -> getChart().addStripe(stripe));
439  }
440 
441  return eventStripes.isEmpty() == false;
442  }
443 
444  @Override
445  protected void cancelled() {
446  super.cancelled();
448  }
449 
450  @Override
451  protected void setDateValues(Interval timeRange) {
452  detailsChartDateAxis.setRange(timeRange, true);
453  pinnedDateAxis.setRange(timeRange, true);
454  }
455 
456  @Override
457  protected void succeeded() {
458  super.succeeded();
460  }
461  }
462 }
Action newHideDescriptionAction(String description, DescriptionLoD descriptionLoD)
synchronized void selectEventIDs(Collection< Long > eventIDs)
void setHighLightedEvents(ObservableList< TimeLineEvent > highlightedEvents)
void applySelectionEffect(EventNodeBase<?> c1, Boolean selected)
synchronized static Logger getLogger(String name)
Definition: Logger.java:161
static void construct(Node node, String fxmlFileName)
final MappedList< TimeLineEvent, EventNodeBase<?> > selectedEvents
Action newUnhideDescriptionAction(String description, DescriptionLoD descriptionLoD)

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.