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