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

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