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