Autopsy 4.22.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 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 */
19package org.sleuthkit.autopsy.timeline;
20
21import com.google.common.collect.ImmutableList;
22import java.awt.BorderLayout;
23import java.awt.Component;
24import java.awt.KeyboardFocusManager;
25import java.beans.PropertyChangeEvent;
26import java.beans.PropertyChangeListener;
27import java.beans.PropertyVetoException;
28import java.util.List;
29import java.util.logging.Level;
30import java.util.stream.Collectors;
31import javafx.application.Platform;
32import javafx.beans.InvalidationListener;
33import javafx.beans.Observable;
34import javafx.scene.Scene;
35import javafx.scene.control.SplitPane;
36import javafx.scene.control.Tab;
37import javafx.scene.control.TabPane;
38import javafx.scene.image.ImageView;
39import javafx.scene.input.KeyCode;
40import javafx.scene.input.KeyCodeCombination;
41import javafx.scene.input.KeyEvent;
42import javafx.scene.layout.Priority;
43import javafx.scene.layout.VBox;
44import javax.swing.JComponent;
45import javax.swing.JPanel;
46import javax.swing.SwingUtilities;
47import static javax.swing.SwingUtilities.isDescendingFrom;
48import org.controlsfx.control.Notifications;
49import org.joda.time.Interval;
50import org.joda.time.format.DateTimeFormatter;
51import org.openide.explorer.ExplorerManager;
52import static org.openide.explorer.ExplorerUtils.createLookup;
53import org.openide.nodes.AbstractNode;
54import org.openide.nodes.Children;
55import org.openide.nodes.Node;
56import org.openide.util.NbBundle;
57import org.openide.windows.Mode;
58import org.openide.windows.RetainLocation;
59import org.openide.windows.TopComponent;
60import org.openide.windows.WindowManager;
61import org.sleuthkit.autopsy.actions.AddBookmarkTagAction;
62import org.sleuthkit.autopsy.casemodule.Case;
63import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
64import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
65import org.sleuthkit.autopsy.corecomponents.DataContentPanel;
66import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
67import org.sleuthkit.autopsy.corecomponents.TableFilterNode;
68import org.sleuthkit.autopsy.coreutils.Logger;
69import org.sleuthkit.autopsy.coreutils.ThreadConfined;
70import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction;
71import org.sleuthkit.autopsy.timeline.actions.Back;
72import org.sleuthkit.autopsy.timeline.actions.Forward;
73import org.sleuthkit.autopsy.timeline.explorernodes.EventNode;
74import org.sleuthkit.autopsy.timeline.explorernodes.EventRootNode;
75import org.sleuthkit.autopsy.timeline.ui.HistoryToolBar;
76import org.sleuthkit.autopsy.timeline.ui.StatusBar;
77import org.sleuthkit.autopsy.timeline.ui.TimeZonePanel;
78import org.sleuthkit.autopsy.timeline.ui.ViewFrame;
79import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
80import org.sleuthkit.autopsy.timeline.ui.filtering.FilterSetPanel;
81import org.sleuthkit.autopsy.timeline.zooming.ZoomSettingsPane;
82import org.sleuthkit.datamodel.TskCoreException;
83import 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
95public 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
102
103 @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
105
106 @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
107 private final ExplorerManager explorerManager = new ExplorerManager();
108
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(() -> {
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(() -> {
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
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
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
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}"})
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();
539
541 super(new BorderLayout());
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 DataResultPanel createInstanceUninitialized(String title, String description, Node currentRootNode, int childNodeCount, DataContent customContentView)
synchronized static Logger getLogger(String name)
Definition Logger.java:124
static ReadOnlyObjectProperty< TimeZone > timeZoneProperty()
static EventNode createEventNode(final Long eventID, EventsModel eventsModel)

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.