19 package org.sleuthkit.autopsy.timeline;
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;
88 @TopComponent.Description(
89 preferredID =
"TimeLineTopComponent",
91 persistenceType = TopComponent.PERSISTENCE_NEVER)
92 @TopComponent.Registration(mode =
"timeline", openAtStartup =
false)
93 @RetainLocation(
"timeline")
94 @SuppressWarnings(
"PMD.SingularField")
97 private static final long serialVersionUID = 1L;
107 private final ExplorerManager explorerManager = new ExplorerManager();
114 private final ModifiableProxyLookup proxyLookup = new ModifiableProxyLookup();
116 private final PropertyChangeListener focusPropertyListener = new PropertyChangeListener() {
141 public void propertyChange(
final PropertyChangeEvent focusEvent) {
142 if (focusEvent.getPropertyName().equalsIgnoreCase(
"focusOwner")) {
143 final Component newFocusOwner = (Component) focusEvent.getNewValue();
145 if (newFocusOwner == null) {
148 if (isDescendingFrom(newFocusOwner, contentViewerPanel)) {
150 proxyLookup.setNewLookups(createLookup(contentViewerPanel.getExplorerManager(), getActionMap()));
153 proxyLookup.setNewLookups(createLookup(explorerManager, getActionMap()));
160 @NbBundle.Messages({
"TimelineTopComponent.selectedEventListener.errorMsg=There was a problem getting the content for the selected event."})
161 private final InvalidationListener selectedEventsListener =
new InvalidationListener() {
170 public void invalidated(Observable observable) {
173 List<Long> selectedEventIDs = ImmutableList.copyOf(controller.getSelectedEventIDs());
176 switch (controller.getViewMode()) {
181 for (
int i = 0; i < selectedEventIDs.size(); i++) {
184 Children children =
new Children.Array();
185 children.add(childArray);
187 SwingUtilities.invokeLater(() -> {
189 explorerManager.setRootContext(
new AbstractNode(children));
192 explorerManager.setSelectedNodes(childArray);
193 }
catch (PropertyVetoException ex) {
195 logger.log(Level.SEVERE,
"Selecting the event node was vetoed.", ex);
198 if (childArray.length == 1) {
199 contentViewerPanel.setNode(childArray[0]);
201 contentViewerPanel.setNode(null);
204 }
catch (TskCoreException ex) {
205 logger.log(Level.SEVERE,
"Failed to lookup Sleuthkit object backing a SingleEvent.", ex);
206 Platform.runLater(() -> {
207 Notifications.create()
208 .owner(jFXViewPanel.getScene().getWindow())
209 .text(Bundle.TimelineTopComponent_selectedEventListener_errorMsg())
219 SwingUtilities.invokeLater(() -> {
220 dataResultPanel.setPath(getResultViewerSummaryString());
221 dataResultPanel.setNode(tableFilterNode);
225 throw new UnsupportedOperationException(
"Unknown view mode: " + controller.getViewMode());
231 switch (controller.getViewMode()) {
238 SwingUtilities.invokeLater(() -> {
239 splitYPane.remove(contentViewerPanel);
240 if (horizontalSplitPane.getParent() != splitYPane) {
241 splitYPane.setBottomComponent(horizontalSplitPane);
242 horizontalSplitPane.setRightComponent(contentViewerPanel);
251 SwingUtilities.invokeLater(() -> splitYPane.setBottomComponent(contentViewerPanel));
254 throw new UnsupportedOperationException(
"Unknown ViewMode: " + controller.getViewMode());
267 associateLookup(proxyLookup);
280 horizontalSplitPane.setLeftComponent(dataResultPanel);
281 horizontalSplitPane.setRightComponent(contentViewerPanel);
283 dataResultPanel.
open();
284 contentViewerPanel.initialize();
295 this.controller = controller;
297 Platform.runLater(this::initFXComponents);
308 KeyboardFocusManager.getCurrentKeyboardFocusManager()
309 .addPropertyChangeListener(
"focusOwner", focusPropertyListener);
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);
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"));
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"));
336 eventsTreeTab.disableProperty().bind(controller.viewModeProperty().isNotEqualTo(ViewMode.DETAIL));
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) {
343 leftTabPane.getSelectionModel().select(filterTab);
348 final VBox leftVBox =
new VBox(5, timeZonePanel, historyToolBar, zoomSettingsPane, leftTabPane);
349 SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE);
351 final ViewFrame viewFrame =
new ViewFrame(controller, eventsTree);
352 final SplitPane mainSplitPane =
new SplitPane(leftVBox, viewFrame);
353 mainSplitPane.setDividerPositions(0);
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);
365 jFXViewPanel.setScene(scene);
366 jFXstatusPanel.setScene(
new Scene(
new StatusBar(controller)));
376 return modes.stream().filter(mode -> mode.getName().equals(
"timeline") || mode.getName().equals(
"ImageGallery"))
377 .collect(Collectors.toList());
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();
395 jFXstatusPanel.setPreferredSize(
new java.awt.Dimension(100, 16));
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);
403 horizontalSplitPane.setDividerLocation(600);
404 horizontalSplitPane.setResizeWeight(0.5);
405 horizontalSplitPane.setPreferredSize(
new java.awt.Dimension(1200, 300));
406 horizontalSplitPane.setRequestFocusEnabled(
false);
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)
414 leftFillerPanelLayout.setVerticalGroup(
415 leftFillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
416 .addGap(0, 54, Short.MAX_VALUE)
419 horizontalSplitPane.setLeftComponent(leftFillerPanel);
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)
427 rightfillerPanelLayout.setVerticalGroup(
428 rightfillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
429 .addGap(0, 54, Short.MAX_VALUE)
432 horizontalSplitPane.setRightComponent(rightfillerPanel);
434 splitYPane.setRightComponent(horizontalSplitPane);
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)
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)
448 .addComponent(jFXstatusPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 29, javax.swing.GroupLayout.PREFERRED_SIZE))
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"
466 super.componentOpened();
467 WindowManager.getDefault().setTopComponentFloating(
this,
true);
470 KeyboardFocusManager.getCurrentKeyboardFocusManager()
471 .addPropertyChangeListener(
"focusOwner", focusPropertyListener);
474 int major = version.getMajor();
475 int minor = version.getMinor();
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();
488 super.componentClosed();
489 KeyboardFocusManager.getCurrentKeyboardFocusManager()
490 .removePropertyChangeListener(
"focusOwner", focusPropertyListener);
495 return explorerManager;
505 "# {0} - start of date range",
506 "# {1} - end of date range",
507 "TimeLineResultView.startDateToEndDate.text={0} to {1}"})
509 Interval selectedTimeRange = controller.getSelectedTimeRange();
510 if (selectedTimeRange == null) {
514 String start = selectedTimeRange.getStart()
516 .toString(zonedFormatter);
517 String end = selectedTimeRange.getEnd()
519 .toString(zonedFormatter);
520 return Bundle.TimeLineResultView_startDateToEndDate_text(start, end);
535 private final ExplorerManager explorerManager =
new ExplorerManager();
539 super(
new BorderLayout());
545 return explorerManager;
566 add(wrapped, BorderLayout.CENTER);
TimeLineTopComponent(TimeLineController controller)
static final KeyStroke BOOKMARK_SHORTCUT
static EventNode createEventNode(final Long eventID, FilteredEventsModel eventsModel)
void setNode(Node selectedNode)
String getResultViewerSummaryString()
DataContentExplorerPanel()
javafx.embed.swing.JFXPanel jFXstatusPanel
ExplorerManager getExplorerManager()
javax.swing.JSplitPane splitYPane
static DataResultPanel createInstanceUninitialized(String title, String description, Node currentRootNode, int childNodeCount, DataContent customContentView)
static final KeyStroke EXTERNAL_VIEWER_SHORTCUT
static ReadOnlyObjectProperty< TimeZone > timeZoneProperty()
void setNode(Node selectedNode)
List< Mode > availableModes(List< Mode > modes)
static DataContentPanel createInstance()
javafx.embed.swing.JFXPanel jFXViewPanel
SleuthkitCase getSleuthkitCase()
synchronized ObservableList< Long > getSelectedEventIDs()
static DateTimeZone getJodaTimeZone()
javax.swing.JSplitPane horizontalSplitPane
static synchronized ExternalViewerShortcutAction getInstance()
synchronized ReadOnlyObjectProperty< ViewMode > viewModeProperty()
static Case getCurrentCase()
void propertyChange(PropertyChangeEvent evt)
ExplorerManager getExplorerManager()
synchronized static Logger getLogger(String name)
final DataContentPanel wrapped
void propertyChange(PropertyChangeEvent evt)
static DateTimeFormatter getZonedFormatter()
javax.swing.JPanel rightfillerPanel
javax.swing.JPanel leftFillerPanel