Autopsy  4.12.0
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  final TabPane leftTabPane = new TabPane(filterTab, eventsTreeTab);
339  VBox.setVgrow(leftTabPane, Priority.ALWAYS);
340  controller.viewModeProperty().addListener(viewMode -> {
341  if (controller.getViewMode() != ViewMode.DETAIL) {
342  //if view mode is not details, switch back to the filter tab
343  leftTabPane.getSelectionModel().select(filterTab);
344  }
345  });
346 
347  //assemble left column
348  final VBox leftVBox = new VBox(5, timeZonePanel, historyToolBar, zoomSettingsPane, leftTabPane);
349  SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE);
350 
351  final ViewFrame viewFrame = new ViewFrame(controller, eventsTree);
352  final SplitPane mainSplitPane = new SplitPane(leftVBox, viewFrame);
353  mainSplitPane.setDividerPositions(0);
354 
355  final Scene scene = new Scene(mainSplitPane);
356  scene.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
357  if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(keyEvent)) {
358  new Back(controller).handle(null);
359  } else if (new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN).match(keyEvent)) {
360  new Forward(controller).handle(null);
361  }
362  });
363 
364  //add ui componenets to JFXPanels
365  jFXViewPanel.setScene(scene);
366  jFXstatusPanel.setScene(new Scene(new StatusBar(controller)));
367  }
368 
369  @Override
370  public List<Mode> availableModes(List<Mode> modes) {
371  /*
372  * This looks like the right thing to do, but online discussions seems
373  * to indicate this method is effectively deprecated. A break point
374  * placed here was never hit.
375  */
376  return modes.stream().filter(mode -> mode.getName().equals("timeline") || mode.getName().equals("ImageGallery"))
377  .collect(Collectors.toList());
378  }
379 
385  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
386  private void initComponents() {
387 
388  jFXstatusPanel = new javafx.embed.swing.JFXPanel();
389  splitYPane = new javax.swing.JSplitPane();
390  jFXViewPanel = new javafx.embed.swing.JFXPanel();
391  horizontalSplitPane = new javax.swing.JSplitPane();
392  leftFillerPanel = new javax.swing.JPanel();
393  rightfillerPanel = new javax.swing.JPanel();
394 
395  jFXstatusPanel.setPreferredSize(new java.awt.Dimension(100, 16));
396 
397  splitYPane.setDividerLocation(420);
398  splitYPane.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
399  splitYPane.setResizeWeight(0.9);
400  splitYPane.setPreferredSize(new java.awt.Dimension(1024, 400));
401  splitYPane.setLeftComponent(jFXViewPanel);
402 
403  horizontalSplitPane.setDividerLocation(600);
404  horizontalSplitPane.setResizeWeight(0.5);
405  horizontalSplitPane.setPreferredSize(new java.awt.Dimension(1200, 300));
406  horizontalSplitPane.setRequestFocusEnabled(false);
407 
408  javax.swing.GroupLayout leftFillerPanelLayout = new javax.swing.GroupLayout(leftFillerPanel);
409  leftFillerPanel.setLayout(leftFillerPanelLayout);
410  leftFillerPanelLayout.setHorizontalGroup(
411  leftFillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
412  .addGap(0, 599, Short.MAX_VALUE)
413  );
414  leftFillerPanelLayout.setVerticalGroup(
415  leftFillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
416  .addGap(0, 54, Short.MAX_VALUE)
417  );
418 
419  horizontalSplitPane.setLeftComponent(leftFillerPanel);
420 
421  javax.swing.GroupLayout rightfillerPanelLayout = new javax.swing.GroupLayout(rightfillerPanel);
422  rightfillerPanel.setLayout(rightfillerPanelLayout);
423  rightfillerPanelLayout.setHorizontalGroup(
424  rightfillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
425  .addGap(0, 364, Short.MAX_VALUE)
426  );
427  rightfillerPanelLayout.setVerticalGroup(
428  rightfillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
429  .addGap(0, 54, Short.MAX_VALUE)
430  );
431 
432  horizontalSplitPane.setRightComponent(rightfillerPanel);
433 
434  splitYPane.setRightComponent(horizontalSplitPane);
435 
436  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
437  this.setLayout(layout);
438  layout.setHorizontalGroup(
439  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
440  .addComponent(splitYPane, javax.swing.GroupLayout.DEFAULT_SIZE, 972, Short.MAX_VALUE)
441  .addComponent(jFXstatusPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
442  );
443  layout.setVerticalGroup(
444  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
445  .addGroup(layout.createSequentialGroup()
446  .addComponent(splitYPane, javax.swing.GroupLayout.DEFAULT_SIZE, 482, Short.MAX_VALUE)
447  .addGap(0, 0, 0)
448  .addComponent(jFXstatusPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 29, javax.swing.GroupLayout.PREFERRED_SIZE))
449  );
450  }// </editor-fold>//GEN-END:initComponents
451 
452  // Variables declaration - do not modify//GEN-BEGIN:variables
453  private javax.swing.JSplitPane horizontalSplitPane;
454  private javafx.embed.swing.JFXPanel jFXViewPanel;
455  private javafx.embed.swing.JFXPanel jFXstatusPanel;
456  private javax.swing.JPanel leftFillerPanel;
457  private javax.swing.JPanel rightfillerPanel;
458  private javax.swing.JSplitPane splitYPane;
459  // End of variables declaration//GEN-END:variables
460 
461  @NbBundle.Messages ({
462  "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"
463  })
464  @Override
465  public void componentOpened() {
466  super.componentOpened();
467  WindowManager.getDefault().setTopComponentFloating(this, true);
468 
469  //add listener that maintains correct selection in the Global Actions Context
470  KeyboardFocusManager.getCurrentKeyboardFocusManager()
471  .addPropertyChangeListener("focusOwner", focusPropertyListener);
472 
473  VersionNumber version = Case.getCurrentCase().getSleuthkitCase().getDBSchemaCreationVersion();
474  int major = version.getMajor();
475  int minor = version.getMinor();
476 
477  if(major < 8 || (major == 8 && minor <= 2)) {
478  Platform.runLater(() -> {
479  Notifications.create()
480  .owner(jFXViewPanel.getScene().getWindow())
481  .text(Bundle.Timeline_old_version()).showInformation();
482  });
483  }
484  }
485 
486  @Override
487  protected void componentClosed() {
488  super.componentClosed();
489  KeyboardFocusManager.getCurrentKeyboardFocusManager()
490  .removePropertyChangeListener("focusOwner", focusPropertyListener);
491  }
492 
493  @Override
494  public ExplorerManager getExplorerManager() {
495  return explorerManager;
496  }
497 
504  @NbBundle.Messages({
505  "# {0} - start of date range",
506  "# {1} - end of date range",
507  "TimeLineResultView.startDateToEndDate.text={0} to {1}"})
508  private String getResultViewerSummaryString() {
509  Interval selectedTimeRange = controller.getSelectedTimeRange();
510  if (selectedTimeRange == null) {
511  return "";
512  } else {
513  final DateTimeFormatter zonedFormatter = TimeLineController.getZonedFormatter();
514  String start = selectedTimeRange.getStart()
516  .toString(zonedFormatter);
517  String end = selectedTimeRange.getEnd()
519  .toString(zonedFormatter);
520  return Bundle.TimeLineResultView_startDateToEndDate_text(start, end);
521 
522  }
523  }
524 
533  final private static class DataContentExplorerPanel extends JPanel implements ExplorerManager.Provider, DataContent {
534 
535  private final ExplorerManager explorerManager = new ExplorerManager();
536  private final DataContentPanel wrapped;
537 
539  super(new BorderLayout());
540  wrapped = DataContentPanel.createInstance();
541  }
542 
543  @Override
544  public ExplorerManager getExplorerManager() {
545  return explorerManager;
546  }
547 
548  @Override
549  public void setNode(Node selectedNode) {
550  wrapped.setNode(selectedNode);
551  }
552 
553  @Override
554  public void propertyChange(PropertyChangeEvent evt) {
555  wrapped.propertyChange(evt);
556  }
557 
565  private void initialize() {
566  add(wrapped, BorderLayout.CENTER);
567  }
568  }
569 }
static EventNode createEventNode(final Long eventID, FilteredEventsModel eventsModel)
Definition: EventNode.java:243
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-2018 Basis Technology. Generated on: Wed Sep 18 2019
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.