diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java index aca7219998..8470175a0c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-15 Basis Technology Corp. + * Copyright 2013-16 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,10 +18,13 @@ */ package org.sleuthkit.autopsy.timeline; +import com.google.common.collect.Iterables; import java.awt.BorderLayout; import java.util.Collections; import java.util.List; import javafx.application.Platform; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; import javafx.embed.swing.JFXPanel; import javafx.scene.Scene; import javafx.scene.control.SplitPane; @@ -46,6 +49,7 @@ import org.sleuthkit.autopsy.corecomponents.DataResultPanel; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.timeline.actions.Back; import org.sleuthkit.autopsy.timeline.actions.Forward; +import org.sleuthkit.autopsy.timeline.explorernodes.EventNode; import org.sleuthkit.autopsy.timeline.ui.HistoryToolBar; import org.sleuthkit.autopsy.timeline.ui.StatusBar; import org.sleuthkit.autopsy.timeline.ui.TimeLineResultView; @@ -56,7 +60,7 @@ import org.sleuthkit.autopsy.timeline.ui.filtering.FilterSetPanel; import org.sleuthkit.autopsy.timeline.zooming.ZoomSettingsPane; /** - * TopComponent for the timeline feature. + * TopComponent for the Timeline feature. */ @TopComponent.Description( preferredID = "TimeLineTopComponent", @@ -75,6 +79,21 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer private final TimeLineController controller; + /** + * Listener that drives the ContentViewer when in List ViewMode. + */ + private final InvalidationListener selectionListener = new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + if (controller.getSelectedEventIDs().size() == 1) { + EventNode eventNode = EventNode.createEventNode(Iterables.getOnlyElement(controller.getSelectedEventIDs()), controller.getEventsModel()); + SwingUtilities.invokeLater(() -> dataContentPanel.setNode(eventNode)); + } else { + SwingUtilities.invokeLater(() -> dataContentPanel.setNode(null)); + } + } + }; + public TimeLineTopComponent(TimeLineController controller) { initComponents(); this.controller = controller; @@ -108,6 +127,7 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer lowerSplitXPane.add(contentViewerContainerPanel); } }); + controller.getSelectedEventIDs().removeListener(selectionListener); break; case LIST: /* @@ -119,6 +139,7 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer splitYPane.add(contentViewerContainerPanel); dataResultPanel.setNode(null); }); + controller.getSelectedEventIDs().addListener(selectionListener); break; default: throw new UnsupportedOperationException("Unknown ViewMode: " + controller.getViewMode()); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java index abb7245eda..9ea0b37e39 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2014-16 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,21 +31,25 @@ import org.openide.nodes.Children; import org.openide.nodes.PropertySupport; import org.openide.nodes.Sheet; import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.DataModelActionsFactory; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.autopsy.timeline.TimeLineController; +import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; /** - * * Explorer Node for {@link SingleEvent}s. + * * Explorer Node for SingleEvents. */ -class EventNode extends DisplayableItemNode { +public class EventNode extends DisplayableItemNode { private static final Logger LOGGER = Logger.getLogger(EventNode.class.getName()); @@ -155,10 +159,48 @@ class EventNode extends DisplayableItemNode { } @Override - public void setValue(String t) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + final public void setValue(String t) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { String oldValue = getValue(); value = t; firePropertyChange("time", oldValue, t); // NON-NLS } } + + /** + * Factory method to create an EventNode form the event ID and the events + * model. + * + * @param eventID The ID of the event this node is for. + * @param eventsModel The model that provides access to the events DB. + * + * @return An EventNode with the file (and artifact) backing this event in + * its lookup. + */ + public static EventNode createEventNode(final Long eventID, FilteredEventsModel eventsModel) { + /* + * Look up the event by id and creata an EventNode with the appropriate + * data in the lookup. + */ + final SingleEvent eventById = eventsModel.getEventById(eventID); + try { + SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase(); + AbstractFile file = sleuthkitCase.getAbstractFileById(eventById.getFileID()); + if (file != null) { + if (eventById.getArtifactID().isPresent()) { + BlackboardArtifact blackboardArtifact = sleuthkitCase.getBlackboardArtifact(eventById.getArtifactID().get()); + return new EventNode(eventById, file, blackboardArtifact); + } else { + return new EventNode(eventById, file); + } + } else { + //This should never happen in normal operations + LOGGER.log(Level.WARNING, "Failed to lookup sleuthkit object backing TimeLineEvent."); // NON-NLS + return null; + } + } catch (IllegalStateException | TskCoreException ex) { + //if some how the case was closed or ther is another unspecified exception, just bail out with a warning. + LOGGER.log(Level.WARNING, "Failed to lookup sleuthkit object backing TimeLineEvent.", ex); // NON-NLS + return null; + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java index 6fd3693514..7438a0f802 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java @@ -20,23 +20,16 @@ package org.sleuthkit.autopsy.timeline.explorernodes; import java.util.Collection; import java.util.List; -import java.util.logging.Level; import org.openide.nodes.AbstractNode; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; -import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; /** * Root Explorer node to represent events. @@ -132,31 +125,7 @@ public class EventRootNode extends DisplayableItemNode { */ return new TooManyNode(eventIDs.size()); } else { - /* - * look up the event by id and creata an EventNode with the - * appropriate data in the lookup. - */ - final SingleEvent eventById = filteredEvents.getEventById(eventID); - try { - SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase(); - AbstractFile file = sleuthkitCase.getAbstractFileById(eventById.getFileID()); - if (file != null) { - if (eventById.getArtifactID().isPresent()) { - BlackboardArtifact blackboardArtifact = sleuthkitCase.getBlackboardArtifact(eventById.getArtifactID().get()); - return new EventNode(eventById, file, blackboardArtifact); - } else { - return new EventNode(eventById, file); - } - } else { - //This should never happen in normal operations - LOGGER.log(Level.WARNING, "Failed to lookup sleuthkit object backing TimeLineEvent."); // NON-NLS - return null; - } - } catch (IllegalStateException | TskCoreException ex) { - //if some how the case was closed or ther is another unspecified exception, just bail out with a warning. - LOGGER.log(Level.WARNING, "Failed to lookup sleuthkit object backing TimeLineEvent.", ex); // NON-NLS - return null; - } + return EventNode.createEventNode(eventID, filteredEvents); } } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineResultView.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineResultView.java index f61e2b5b28..9fb308229f 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineResultView.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineResultView.java @@ -62,13 +62,9 @@ public class TimeLineResultView { dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, dataContent); //set up listeners on relevant properties - TimeLineController.getTimeZone().addListener((Observable observable) -> { - dataResultPanel.setPath(getSummaryString()); - }); + TimeLineController.getTimeZone().addListener(timeZone -> dataResultPanel.setPath(getSummaryString())); - controller.getSelectedEventIDs().addListener((Observable o) -> { - refresh(); - }); + controller.getSelectedEventIDs().addListener((Observable selectedIDs) -> refresh()); refresh(); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListChart.java index 6f01fbbec0..e9b5f12d1a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListChart.java @@ -22,6 +22,7 @@ import java.util.Collection; import javafx.beans.binding.Bindings; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; +import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.control.TableCell; @@ -128,6 +129,15 @@ class ListChart extends BorderPane { table.getItems().setAll(eventIDs); } + /** + * Get the List of IDs of events that are selected in this list. + * + * @return The List of IDs of events that are selected in this list. + */ + ObservableList getSelectedEventIDs() { + return table.getSelectionModel().getSelectedItems(); + } + private class ImageCell extends EventTableCell { @Override diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java index 6f3fdec60f..574de0fcfe 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.timeline.ui.listvew; import java.util.List; import javafx.application.Platform; +import javafx.beans.Observable; import javafx.concurrent.Task; import javafx.scene.Parent; import org.joda.time.Interval; @@ -58,6 +59,11 @@ public class ListViewPane extends AbstractTimeLineView { //initialize chart; setCenter(listChart); setSettingsNodes(new ListViewPane.ListViewSettingsPane().getChildrenUnmodifiable()); + + //keep controller's list of selected event IDs in sync with this list's + listChart.getSelectedEventIDs().addListener((Observable selectedIDs) -> { + controller.selectEventIDs(listChart.getSelectedEventIDs()); + }); } @Override