Autopsy  4.19.3
Graphical digital forensics platform for The Sleuth Kit and other tools.
TimeLineTopComponent.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2014-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;
20 
21 import com.google.common.collect.ImmutableList;
22 import java.awt.BorderLayout;
23 import java.awt.Component;
24 import java.awt.KeyboardFocusManager;
25 import java.beans.PropertyChangeEvent;
26 import java.beans.PropertyChangeListener;
27 import java.beans.PropertyVetoException;
28 import java.util.List;
29 import java.util.logging.Level;
30 import java.util.stream.Collectors;
31 import javafx.application.Platform;
32 import javafx.beans.InvalidationListener;
33 import javafx.beans.Observable;
34 import javafx.scene.Scene;
35 import javafx.scene.control.SplitPane;
36 import javafx.scene.control.Tab;
37 import javafx.scene.control.TabPane;
38 import javafx.scene.image.ImageView;
39 import javafx.scene.input.KeyCode;
40 import javafx.scene.input.KeyCodeCombination;
41 import javafx.scene.input.KeyEvent;
42 import javafx.scene.layout.Priority;
43 import javafx.scene.layout.VBox;
44 import javax.swing.JComponent;
45 import javax.swing.JPanel;
46 import javax.swing.SwingUtilities;
47 import static javax.swing.SwingUtilities.isDescendingFrom;
48 import org.controlsfx.control.Notifications;
49 import org.joda.time.Interval;
50 import org.joda.time.format.DateTimeFormatter;
51 import org.openide.explorer.ExplorerManager;
52 import static org.openide.explorer.ExplorerUtils.createLookup;
53 import org.openide.nodes.AbstractNode;
54 import org.openide.nodes.Children;
55 import org.openide.nodes.Node;
56 import org.openide.util.NbBundle;
57 import org.openide.windows.Mode;
58 import org.openide.windows.RetainLocation;
59 import org.openide.windows.TopComponent;
60 import org.openide.windows.WindowManager;
82 import org.sleuthkit.datamodel.TskCoreException;
83 import org.sleuthkit.datamodel.VersionNumber;
84 
88 @TopComponent.Description(
89  preferredID = "TimeLineTopComponent",
90  //iconBase="SET/PATH/TO/ICON/HERE", //use this to put icon in window title area,
91  persistenceType = TopComponent.PERSISTENCE_NEVER)
92 @TopComponent.Registration(mode = "timeline", openAtStartup = false)
93 @RetainLocation("timeline")
94 @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
95 public final class TimeLineTopComponent extends TopComponent implements ExplorerManager.Provider {
96 
97  private static final long serialVersionUID = 1L;
98  private static final Logger logger = Logger.getLogger(TimeLineTopComponent.class.getName());
99 
101  private final DataContentExplorerPanel contentViewerPanel;
102 
103  @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
104  private final DataResultPanel dataResultPanel;
105 
106  @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
107  private final ExplorerManager explorerManager = new ExplorerManager();
108 
109  private TimeLineController controller;
110 
114  private final ModifiableProxyLookup proxyLookup = new ModifiableProxyLookup();
115 
116  private final PropertyChangeListener focusPropertyListener = new PropertyChangeListener() {
140  @Override
141  public void propertyChange(final PropertyChangeEvent focusEvent) {
142  if (focusEvent.getPropertyName().equalsIgnoreCase("focusOwner")) {
143  final Component newFocusOwner = (Component) focusEvent.getNewValue();
144 
145  if (newFocusOwner == null) {
146  return;
147  }
148  if (isDescendingFrom(newFocusOwner, contentViewerPanel)) {
149  //if the focus owner is within the MessageContentViewer (the attachments table)
150  proxyLookup.setNewLookups(createLookup(contentViewerPanel.getExplorerManager(), getActionMap()));
151  } else if (isDescendingFrom(newFocusOwner, TimeLineTopComponent.this)) {
152  //... or if it is within the Results table.
153  proxyLookup.setNewLookups(createLookup(explorerManager, getActionMap()));
154 
155  }
156  }
157  }
158  };
159 
160  @NbBundle.Messages({"TimelineTopComponent.selectedEventListener.errorMsg=There was a problem getting the content for the selected event."})
161  private final InvalidationListener selectedEventsListener = new InvalidationListener() {
169  @Override
170  public void invalidated(Observable observable) {
171  // make a copy because this list gets updated as the user navigates around
172  // and causes concurrent access exceptions
173  List<Long> selectedEventIDs = ImmutableList.copyOf(controller.getSelectedEventIDs());
174 
175  //depending on the active view mode, we either update the dataResultPanel, or update the contentViewerPanel directly.
176  switch (controller.getViewMode()) {
177  case LIST:
178  //make an array of EventNodes for the selected events
179  EventNode[] childArray = new EventNode[selectedEventIDs.size()];
180  try {
181  for (int i = 0; i < selectedEventIDs.size(); i++) {
182  childArray[i] = EventNode.createEventNode(selectedEventIDs.get(i), controller.getEventsModel());
183  }
184  Children children = new Children.Array();
185  children.add(childArray);
186 
187  SwingUtilities.invokeLater(() -> {
188  //set generic container node as root context
189  explorerManager.setRootContext(new AbstractNode(children));
190  try {
191  //set selected nodes for actions
192  explorerManager.setSelectedNodes(childArray);
193  } catch (PropertyVetoException ex) {
194  //I don't know why this would ever happen.
195  logger.log(Level.SEVERE, "Selecting the event node was vetoed.", ex); // NON-NLS
196  }
197  //if there is only one event selected push it into content viewer.
198  if (childArray.length == 1) {
199  contentViewerPanel.setNode(childArray[0]);
200  } else {
201  contentViewerPanel.setNode(null);
202  }
203  });
204  } catch (TskCoreException ex) {
205  logger.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); // NON-NLS
206  Platform.runLater(() -> {
207  Notifications.create()
208  .owner(jFXViewPanel.getScene().getWindow())
209  .text(Bundle.TimelineTopComponent_selectedEventListener_errorMsg())
210  .showError();
211  });
212  }
213  break;
214  case COUNTS:
215  case DETAIL:
216  //make a root node with nodes for the selected events as children and push it to the result viewer.
217  EventRootNode rootNode = new EventRootNode(selectedEventIDs, controller.getEventsModel());
218  TableFilterNode tableFilterNode = new TableFilterNode(rootNode, true, "Event");
219  SwingUtilities.invokeLater(() -> {
220  dataResultPanel.setPath(getResultViewerSummaryString());
221  dataResultPanel.setNode(tableFilterNode);
222  });
223  break;
224  default:
225  throw new UnsupportedOperationException("Unknown view mode: " + controller.getViewMode());
226  }
227  }
228  };
229 
230  private void syncViewMode() {
231  switch (controller.getViewMode()) {
232  case COUNTS:
233  case DETAIL:
234  /*
235  * For counts and details mode, restore the result table at the
236  * bottom left.
237  */
238  SwingUtilities.invokeLater(() -> {
239  splitYPane.remove(contentViewerPanel);
240  if (horizontalSplitPane.getParent() != splitYPane) {
241  splitYPane.setBottomComponent(horizontalSplitPane);
242  horizontalSplitPane.setRightComponent(contentViewerPanel);
243  }
244  });
245  break;
246  case LIST:
247  /*
248  * For list mode, remove the result table, and let the content
249  * viewer expand across the bottom.
250  */
251  SwingUtilities.invokeLater(() -> splitYPane.setBottomComponent(contentViewerPanel));
252  break;
253  default:
254  throw new UnsupportedOperationException("Unknown ViewMode: " + controller.getViewMode());
255  }
256  }
257 
266  initComponents();
267  associateLookup(proxyLookup);
268  setName(NbBundle.getMessage(TimeLineTopComponent.class, "CTL_TimeLineTopComponent"));
269 
270  getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(AddBookmarkTagAction.BOOKMARK_SHORTCUT, "addBookmarkTag"); //NON-NLS
271  getActionMap().put("addBookmarkTag", new AddBookmarkTagAction()); //NON-NLS
272  getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(ExternalViewerShortcutAction.EXTERNAL_VIEWER_SHORTCUT, "useExternalViewer"); //NON-NLS
273  getActionMap().put("useExternalViewer", ExternalViewerShortcutAction.getInstance()); //NON-NLS
274 
275  //create linked result and content views
276  contentViewerPanel = new DataContentExplorerPanel();
277  dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, contentViewerPanel);
278 
279  //add them to bottom splitpane
280  horizontalSplitPane.setLeftComponent(dataResultPanel);
281  horizontalSplitPane.setRightComponent(contentViewerPanel);
282 
283  dataResultPanel.open(); //get the explorermanager
284  contentViewerPanel.initialize();
285  }
286 
293  this();
294 
295  this.controller = controller;
296 
297  Platform.runLater(this::initFXComponents);
298 
299  //set up listeners
300  TimeLineController.timeZoneProperty().addListener(timeZone -> dataResultPanel.setPath(getResultViewerSummaryString()));
301  controller.getSelectedEventIDs().addListener(selectedEventsListener);
302 
303  //Listen to ViewMode and adjust GUI componenets as needed.
304  controller.viewModeProperty().addListener(viewMode -> syncViewMode());
305  syncViewMode();
306 
307  //add listener that maintains correct selection in the Global Actions Context
308  KeyboardFocusManager.getCurrentKeyboardFocusManager()
309  .addPropertyChangeListener("focusOwner", focusPropertyListener);
310  }
311 
315  @NbBundle.Messages({
316  "TimeLineTopComponent.eventsTab.name=Events",
317  "TimeLineTopComponent.filterTab.name=Filters"})
318  @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
319  void initFXComponents() {
321  final TimeZonePanel timeZonePanel = new TimeZonePanel();
322  VBox.setVgrow(timeZonePanel, Priority.SOMETIMES);
323  HistoryToolBar historyToolBar = new HistoryToolBar(controller);
324  final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane(controller);
325 
326  //set up filter tab
327  final Tab filterTab = new Tab(Bundle.TimeLineTopComponent_filterTab_name(), new FilterSetPanel(controller));
328  filterTab.setClosable(false);
329  filterTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/funnel.png")); // NON-NLS
330 
331  //set up events tab
332  final EventsTree eventsTree = new EventsTree(controller);
333  final Tab eventsTreeTab = new Tab(Bundle.TimeLineTopComponent_eventsTab_name(), eventsTree);
334  eventsTreeTab.setClosable(false);
335  eventsTreeTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/timeline_marker.png")); // NON-NLS
336  eventsTreeTab.disableProperty().bind(controller.viewModeProperty().isNotEqualTo(ViewMode.DETAIL));
337 
338  // There is a bug in the sort of the EventsTree, it doesn't seem to
339  //sort anything. For now removing from tabpane until fixed
340  final TabPane leftTabPane = new TabPane(filterTab);
341  VBox.setVgrow(leftTabPane, Priority.ALWAYS);
342  controller.viewModeProperty().addListener(viewMode -> {
343  if (controller.getViewMode() != ViewMode.DETAIL) {
344  //if view mode is not details, switch back to the filter tab
345  leftTabPane.getSelectionModel().select(filterTab);
346  }
347  });
348 
349  //assemble left column
350  final VBox leftVBox = new VBox(5, timeZonePanel, historyToolBar, zoomSettingsPane, leftTabPane);
351  SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE);
352 
353  final ViewFrame viewFrame = new ViewFrame(controller, eventsTree);
354  final SplitPane mainSplitPane = new SplitPane(leftVBox, viewFrame);
355  mainSplitPane.setDividerPositions(0);
356 
357  final Scene scene = new Scene(mainSplitPane);
358  scene.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
359  if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(keyEvent)) {
360  new Back(controller).handle(null);
361  } else if (new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN).match(keyEvent)) {
362  new Forward(controller).handle(null);
363  }
364  });
365 
366  //add ui componenets to JFXPanels
367  jFXViewPanel.setScene(scene);
368  jFXstatusPanel.setScene(new Scene(new StatusBar(controller)));
369  }
370 
371  @Override
372  public List<Mode> availableModes(List<Mode> modes) {
373  /*
374  * This looks like the right thing to do, but online discussions seems
375  * to indicate this method is effectively deprecated. A break point
376  * placed here was never hit.
377  */
378  return modes.stream().filter(mode -> mode.getName().equals("timeline") || mode.getName().equals("ImageGallery"))
379  .collect(Collectors.toList());
380  }
381 
387  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
388  private void initComponents() {
389 
390  jFXstatusPanel = new javafx.embed.swing.JFXPanel();
391  splitYPane = new javax.swing.JSplitPane();
392  jFXViewPanel = new javafx.embed.swing.JFXPanel();
393  horizontalSplitPane = new javax.swing.JSplitPane();
394  leftFillerPanel = new javax.swing.JPanel();
395  rightfillerPanel = new javax.swing.JPanel();
396 
397  jFXstatusPanel.setPreferredSize(new java.awt.Dimension(100, 16));
398 
399  splitYPane.setDividerLocation(420);
400  splitYPane.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
401  splitYPane.setResizeWeight(0.9);
402  splitYPane.setPreferredSize(new java.awt.Dimension(1024, 400));
403  splitYPane.setLeftComponent(jFXViewPanel);
404 
405  horizontalSplitPane.setDividerLocation(600);
406  horizontalSplitPane.setResizeWeight(0.5);
407  horizontalSplitPane.setPreferredSize(new java.awt.Dimension(1200, 300));
408  horizontalSplitPane.setRequestFocusEnabled(false);
409 
410  javax.swing.GroupLayout leftFillerPanelLayout = new javax.swing.GroupLayout(leftFillerPanel);
411  leftFillerPanel.setLayout(leftFillerPanelLayout);
412  leftFillerPanelLayout.setHorizontalGroup(
413  leftFillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
414  .addGap(0, 599, Short.MAX_VALUE)
415  );
416  leftFillerPanelLayout.setVerticalGroup(
417  leftFillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
418  .addGap(0, 54, Short.MAX_VALUE)
419  );
420 
421  horizontalSplitPane.setLeftComponent(leftFillerPanel);
422 
423  javax.swing.GroupLayout rightfillerPanelLayout = new javax.swing.GroupLayout(rightfillerPanel);
424  rightfillerPanel.setLayout(rightfillerPanelLayout);
425  rightfillerPanelLayout.setHorizontalGroup(
426  rightfillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
427  .addGap(0, 364, Short.MAX_VALUE)
428  );
429  rightfillerPanelLayout.setVerticalGroup(
430  rightfillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
431  .addGap(0, 54, Short.MAX_VALUE)
432  );
433 
434  horizontalSplitPane.setRightComponent(rightfillerPanel);
435 
436  splitYPane.setRightComponent(horizontalSplitPane);
437 
438  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
439  this.setLayout(layout);
440  layout.setHorizontalGroup(
441  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
442  .addComponent(splitYPane, javax.swing.GroupLayout.DEFAULT_SIZE, 972, Short.MAX_VALUE)
443  .addComponent(jFXstatusPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
444  );
445  layout.setVerticalGroup(
446  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
447  .addGroup(layout.createSequentialGroup()
448  .addComponent(splitYPane, javax.swing.GroupLayout.DEFAULT_SIZE, 482, Short.MAX_VALUE)
449  .addGap(0, 0, 0)
450  .addComponent(jFXstatusPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 29, javax.swing.GroupLayout.PREFERRED_SIZE))
451  );
452  }// </editor-fold>//GEN-END:initComponents
453 
454  // Variables declaration - do not modify//GEN-BEGIN:variables
455  private javax.swing.JSplitPane horizontalSplitPane;
456  private javafx.embed.swing.JFXPanel jFXViewPanel;
457  private javafx.embed.swing.JFXPanel jFXstatusPanel;
458  private javax.swing.JPanel leftFillerPanel;
459  private javax.swing.JPanel rightfillerPanel;
460  private javax.swing.JSplitPane splitYPane;
461  // End of variables declaration//GEN-END:variables
462 
463  @NbBundle.Messages ({
464  "Timeline.old.version= This Case was created with an older version of Autopsy.\nThe Timeline with not show events from data sources added with the older version of Autopsy"
465  })
466  @Override
467  public void componentOpened() {
468  super.componentOpened();
469  WindowManager.getDefault().setTopComponentFloating(this, true);
470 
471  //add listener that maintains correct selection in the Global Actions Context
472  KeyboardFocusManager.getCurrentKeyboardFocusManager()
473  .addPropertyChangeListener("focusOwner", focusPropertyListener);
474 
475  VersionNumber version = Case.getCurrentCase().getSleuthkitCase().getDBSchemaCreationVersion();
476  int major = version.getMajor();
477  int minor = version.getMinor();
478 
479  if(major < 8 || (major == 8 && minor <= 2)) {
480  Platform.runLater(() -> {
481  Notifications.create()
482  .owner(jFXViewPanel.getScene().getWindow())
483  .text(Bundle.Timeline_old_version()).showInformation();
484  });
485  }
486  }
487 
488  @Override
489  protected void componentClosed() {
490  super.componentClosed();
491  KeyboardFocusManager.getCurrentKeyboardFocusManager()
492  .removePropertyChangeListener("focusOwner", focusPropertyListener);
493  }
494 
495  @Override
496  public ExplorerManager getExplorerManager() {
497  return explorerManager;
498  }
499 
506  @NbBundle.Messages({
507  "# {0} - start of date range",
508  "# {1} - end of date range",
509  "TimeLineResultView.startDateToEndDate.text={0} to {1}"})
510  private String getResultViewerSummaryString() {
511  Interval selectedTimeRange = controller.getSelectedTimeRange();
512  if (selectedTimeRange == null) {
513  return "";
514  } else {
515  final DateTimeFormatter zonedFormatter = TimeLineController.getZonedFormatter();
516  String start = selectedTimeRange.getStart()
518  .toString(zonedFormatter);
519  String end = selectedTimeRange.getEnd()
521  .toString(zonedFormatter);
522  return Bundle.TimeLineResultView_startDateToEndDate_text(start, end);
523 
524  }
525  }
526 
535  final private static class DataContentExplorerPanel extends JPanel implements ExplorerManager.Provider, DataContent {
536 
537  private final ExplorerManager explorerManager = new ExplorerManager();
538  private final DataContentPanel wrapped;
539 
541  super(new BorderLayout());
542  wrapped = DataContentPanel.createInstance();
543  }
544 
545  @Override
546  public ExplorerManager getExplorerManager() {
547  return explorerManager;
548  }
549 
550  @Override
551  public void setNode(Node selectedNode) {
552  wrapped.setNode(selectedNode);
553  }
554 
555  @Override
556  public void propertyChange(PropertyChangeEvent evt) {
557  wrapped.propertyChange(evt);
558  }
559 
567  private void initialize() {
568  add(wrapped, BorderLayout.CENTER);
569  }
570  }
571 }
static EventNode createEventNode(final Long eventID, EventsModel eventsModel)
Definition: EventNode.java:264
static DataResultPanel createInstanceUninitialized(String title, String description, Node currentRootNode, int childNodeCount, DataContent customContentView)
static ReadOnlyObjectProperty< TimeZone > timeZoneProperty()
synchronized ObservableList< Long > getSelectedEventIDs()
synchronized ReadOnlyObjectProperty< ViewMode > viewModeProperty()
synchronized static Logger getLogger(String name)
Definition: Logger.java:124

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