diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java b/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java index 7cbd797fc0..bec6b62569 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java @@ -26,7 +26,6 @@ import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; import javafx.beans.property.SimpleObjectProperty; @@ -54,6 +53,7 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; +import org.sleuthkit.autopsy.timeline.events.ViewInTimelineRequestedEvent; import org.sleuthkit.autopsy.timeline.utils.IntervalUtils; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -63,7 +63,7 @@ import org.sleuthkit.datamodel.BlackboardArtifact; * choose a specific event and a time range around it to show in the Timeline * List View. */ -final class ShowInTimelineDialog extends Dialog { +final class ShowInTimelineDialog extends Dialog { private static final Logger LOGGER = Logger.getLogger(ShowInTimelineDialog.class.getName()); @@ -218,10 +218,10 @@ final class ShowInTimelineDialog extends Dialog eventIDs; - private final Interval range; - - /** - * Constructor - * - * @param eventIDs The event IDs to include. - * @param range The Interval to show. - */ - EvenstInInterval(Set eventIDs, Interval range) { - this.eventIDs = eventIDs; - this.range = range; - } - - /** - * Get the event IDs. - * - * @return The event IDs - */ - public Set getEventIDs() { - return eventIDs; - } - - /** - * Get the Interval. - * - * @return The Interval. - */ - public Interval getInterval() { - return range; - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index 82d3e23d20..1e9e7c0507 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.timeline; +import com.google.common.eventbus.EventBus; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.IOException; @@ -27,7 +28,6 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.TimeZone; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -83,6 +83,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.db.EventsRepository; +import org.sleuthkit.autopsy.timeline.events.ViewInTimelineRequestedEvent; import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter; import org.sleuthkit.autopsy.timeline.filters.RootFilter; import org.sleuthkit.autopsy.timeline.filters.TypeFilter; @@ -145,6 +146,7 @@ public class TimeLineController { private final ReadOnlyStringWrapper taskTitle = new ReadOnlyStringWrapper(); private final ReadOnlyStringWrapper statusMessage = new ReadOnlyStringWrapper(); + private EventBus eventbus = new EventBus("TimeLineController_EventBus"); /** * Status is a string that will be displayed in the status bar as a kind of @@ -222,7 +224,7 @@ public class TimeLineController { //selected events (ie shown in the result viewer) @GuardedBy("this") - private final ObservableList selectedEventIDs = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); + private final ObservableList selectedEventIDs = FXCollections.observableArrayList(); @GuardedBy("this") private final ReadOnlyObjectWrapper selectedTimeRange = new ReadOnlyObjectWrapper<>(); @@ -424,50 +426,49 @@ public class TimeLineController { //get a task that rebuilds the repo with the bellow state listener attached final CancellationProgressTask rebuildRepositoryTask; - rebuildRepositoryTask = repoBuilder.apply(newSate -> { - //this will be on JFX thread - switch (newSate) { - case SUCCEEDED: - /* - * Record if ingest was running the last time the db was - * rebuilt, and hence it might stale. - */ - try { - perCaseTimelineProperties.setIngestRunning(ingestRunning); - } catch (IOException ex) { - MessageNotifyUtil.Notify.error(Bundle.Timeline_dialogs_title(), - ingestRunning ? Bundle.TimeLineController_setIngestRunning_errMsgRunning() - : Bundle.TimeLinecontroller_setIngestRunning_errMsgNotRunning()); - LOGGER.log(Level.SEVERE, "Error marking the ingest state while the timeline db was populated.", ex); //NON-NLS - } - if (markDBNotStale) { - setEventsDBStale(false); - filteredEvents.postDBUpdated(); - } + rebuildRepositoryTask = repoBuilder.apply(new Consumer() { + @Override + public void accept(Worker.State newSate) { + //this will be on JFX thread + switch (newSate) { + case SUCCEEDED: + /* + * Record if ingest was running the last time the db was + * rebuilt, and hence it might stale. + */ + try { + perCaseTimelineProperties.setIngestRunning(ingestRunning); + } catch (IOException ex) { + MessageNotifyUtil.Notify.error(Bundle.Timeline_dialogs_title(), + ingestRunning ? Bundle.TimeLineController_setIngestRunning_errMsgRunning() + : Bundle.TimeLinecontroller_setIngestRunning_errMsgNotRunning()); + LOGGER.log(Level.SEVERE, "Error marking the ingest state while the timeline db was populated.", ex); //NON-NLS + } + if (markDBNotStale) { + setEventsDBStale(false); + filteredEvents.postDBUpdated(); + } + if (file == null && artifact == null) { + SwingUtilities.invokeLater(TimeLineController.this::showWindow); + TimeLineController.this.showFullRange(); + } else { - if (file == null && artifact == null) { - SwingUtilities.invokeLater(this::showWindow); - TimeLineController.this.showFullRange(); - } else { - Platform.runLater(() -> { ShowInTimelineDialog d = (file == null) ? new ShowInTimelineDialog(TimeLineController.this, artifact) : new ShowInTimelineDialog(TimeLineController.this, file); - Optional result = d.showAndWait(); - result.ifPresent(eventInTimeRange -> { + Optional result = d.showAndWait(); + result.ifPresent(viewInTimelineRequestedEvent -> { SwingUtilities.invokeLater(TimeLineController.this::showWindow); - showEvents(eventInTimeRange.getEventIDs(), eventInTimeRange.getInterval()); + showEvents(viewInTimelineRequestedEvent); }); - }); - - } - break; - - case FAILED: - case CANCELLED: - setEventsDBStale(true); - break; + } + break; + case FAILED: + case CANCELLED: + setEventsDBStale(true); + break; + } } }); @@ -483,6 +484,7 @@ public class TimeLineController { * done. */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + public void rebuildRepo(AbstractFile file, BlackboardArtifact artifact) { rebuildRepoHelper(eventsRepository::rebuildRepository, true, file, artifact); } @@ -505,19 +507,13 @@ public class TimeLineController { } } - public void showEvents(Set eventIDs, Interval interval) { - if (eventIDs == null && interval == null) { - showFullRange(); - } else { - synchronized (filteredEvents) { - if (interval != null) { - pushTimeRange(interval); - } - if (eventIDs != null) { - setViewMode(ViewMode.LIST); - selectEventIDs(eventIDs); - } - } + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + private void showEvents(ViewInTimelineRequestedEvent requestEvent) { + synchronized (filteredEvents) { + pushTimeRange(requestEvent.getInterval()); + selectEventIDs(requestEvent.getEventIDs()); + setViewMode(ViewMode.LIST); + eventbus.post(requestEvent); } } @@ -662,11 +658,6 @@ public class TimeLineController { pushTimeRange(new Interval(start, end)); } - public void selectEventIDs(Collection events) { - selectedTimeRange.set(filteredEvents.getSpanningInterval(events)); - selectedEventIDs.setAll(events); - } - /** * Show the timeline TimeLineTopComponent. This method will construct a new * instance of TimeLineTopComponent if necessary. @@ -770,6 +761,17 @@ public class TimeLineController { historyManager.advance(newState); } + /** + * Select the given event IDs and set their spanning interval as the + * selected time range. + * + * @param eventIDs The eventIDs to select + */ + synchronized public void selectEventIDs(Collection eventIDs) { + selectedTimeRange.set(filteredEvents.getSpanningInterval(eventIDs)); + selectedEventIDs.setAll(eventIDs); + } + public void selectTimeAndType(Interval interval, EventType type) { final Interval timeRange = filteredEvents.getSpanningInterval().overlap(interval); @@ -855,8 +857,28 @@ public class TimeLineController { } } + /** + * Register the given object to receive events. + * + * @param o The object to register. Must implement public methods annotated + * with Subscribe. + */ + synchronized public void registerForEvents(Object o) { + eventbus.register(o); + } + + /** + * Un-register the given object, so it no longer receives events. + * + * @param o The object to un-register. + */ + synchronized public void unRegisterForEvents(Object o) { + eventbus.unregister(0); + } + static synchronized public void setTimeZone(TimeZone timeZone) { TimeLineController.timeZone.set(timeZone); + } /** diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java index 11c9ab9b68..5691c3211a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java @@ -114,7 +114,7 @@ public final class FilteredEventsModel { @GuardedBy("this") private final ReadOnlyObjectWrapper requestedZoomParamters = new ReadOnlyObjectWrapper<>(); - private final EventBus eventbus = new EventBus("Event_Repository_EventBus"); //NON-NLS + private final EventBus eventbus = new EventBus("FilteredEventsModel_EventBus"); //NON-NLS /** * The underlying repo for events. Atomic access to repo is synchronized diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/ArtifactEventType.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/ArtifactEventType.java index 9602183efa..4812fe0b8b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/ArtifactEventType.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/ArtifactEventType.java @@ -36,7 +36,6 @@ import org.sleuthkit.datamodel.TskCoreException; public interface ArtifactEventType extends EventType { public static final Logger LOGGER = Logger.getLogger(ArtifactEventType.class.getName()); - static final EmptyExtractor EMPTY_EXTRACTOR = new EmptyExtractor(); public static Set getAllArtifactEventTypes() { return allTypes.stream() diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java index 7882e266c6..00502b191e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java @@ -669,37 +669,26 @@ public class EventDB { } } - List getDerivedEventIDs(Set fileIDs, Set artifactIDS) { - DBLock.lock(); - String query = "SELECT Group_Concat(event_id) FROM events" - + " WHERE ( file_id IN (" + StringUtils.join(fileIDs, ", ") + ") AND artifact_id IS NULL)" - + " OR artifact_id IN (" + StringUtils.join(artifactIDS, ", ") + ")"; - - try (Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery(query);) { // NON-NLS - while (rs.next()) { - return SQLHelper.unGroupConcat(rs.getString("Group_Concat(event_id)"), Long::valueOf); - } - } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "Error executing get spanning interval query.", ex); // NON-NLS - } finally { - DBLock.unlock(); - } - return null; - } - + /** + * Get a List of event IDs for the events that are derived from the given + * artifact. + * + * @param artifact The BlackboardArtifact to get derived event IDs for. + * + * @return A List of event IDs for the events that are derived from the + * given artifact. + */ List getEventIDsForArtifact(BlackboardArtifact artifact) { DBLock.lock(); - String query = "SELECT event_id FROM events WHERE artifact_id == " + artifact.getArtifactID() ; + + String query = "SELECT event_id FROM events WHERE artifact_id == " + artifact.getArtifactID(); ArrayList results = new ArrayList<>(); - try (Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery(query);) { // NON-NLS + ResultSet rs = stmt.executeQuery(query);) { while (rs.next()) { results.add(rs.getLong("event_id")); } - } catch (SQLException ex) { LOGGER.log(Level.SEVERE, "Error executing getEventIDsForArtifact query.", ex); // NON-NLS } finally { @@ -708,19 +697,33 @@ public class EventDB { return results; } + /** + * Get a List of event IDs for the events that are derived from the given + * file. + * + * @param file The AbstractFile to get derived event IDs + * for. + * @param includeDerivedArtifacts If true, also get event IDs for events + * derived from artifacts derived form this + * file. If false, only gets events derived + * directly from this file (file system + * timestamps). + * + * @return A List of event IDs for the events that are derived from the + * given file. + */ List getEventIDsForFile(AbstractFile file, boolean includeDerivedArtifacts) { DBLock.lock(); + String query = "SELECT event_id FROM events WHERE file_id == " + file.getId() + (includeDerivedArtifacts ? "" : " AND artifact_id IS NULL"); ArrayList results = new ArrayList<>(); - try (Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery(query);) { // NON-NLS + ResultSet rs = stmt.executeQuery(query);) { while (rs.next()) { results.add(rs.getLong("event_id")); } - } catch (SQLException ex) { LOGGER.log(Level.SEVERE, "Error executing getEventIDsForFile query.", ex); // NON-NLS } finally { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java index a3af11eb2a..fa5137a564 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java @@ -208,11 +208,35 @@ public class EventsRepository { } + /** + * Get a List of event IDs for the events that are derived from the given + * file. + * + * @param file The AbstractFile to get derived event IDs + * for. + * @param includeDerivedArtifacts If true, also get event IDs for events + * derived from artifacts derived form this + * file. If false, only gets events derived + * directly from this file (file system + * timestamps). + * + * @return A List of event IDs for the events that are derived from the + * given file. + */ public List getEventIDsForFile(AbstractFile file, boolean includedDerivedArtifacts) { return eventDB.getEventIDsForFile(file, includedDerivedArtifacts); } + /** + * Get a List of event IDs for the events that are derived from the given + * artifact. + * + * @param artifact The BlackboardArtifact to get derived event IDs for. + * + * @return A List of event IDs for the events that are derived from the + * given artifact. + */ public List getEventIDsForArtifact(BlackboardArtifact artifact) { return eventDB.getEventIDsForArtifact(artifact); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/events/ViewInTimelineRequestedEvent.java b/Core/src/org/sleuthkit/autopsy/timeline/events/ViewInTimelineRequestedEvent.java new file mode 100644 index 0000000000..9c321a7f95 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/events/ViewInTimelineRequestedEvent.java @@ -0,0 +1,61 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.timeline.events; + +import java.util.Set; +import org.joda.time.Interval; + +/** + * Encapsulates the result of the ShowIntimelineDialog: a Set of event IDs and + * an Interval. + */ +public final class ViewInTimelineRequestedEvent { + + private final Set eventIDs; + private final Interval range; + + /** + * Constructor + * + * @param eventIDs The event IDs to include. + * @param range The Interval to show. + */ + public ViewInTimelineRequestedEvent(Set eventIDs, Interval range) { + this.eventIDs = eventIDs; + this.range = range; + } + + /** + * Get the event IDs. + * + * @return The event IDs + */ + public Set getEventIDs() { + return eventIDs; + } + + /** + * Get the Interval. + * + * @return The Interval. + */ + public Interval getInterval() { + return range; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractTimeLineView.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractTimeLineView.java index 6f4920f2bc..40a6ae07e0 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractTimeLineView.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractTimeLineView.java @@ -74,7 +74,7 @@ public abstract class AbstractTimeLineView extends BorderPane { */ private Task updateTask; - public final TimeLineController controller; + private final TimeLineController controller; private final FilteredEventsModel filteredEvents; /** @@ -100,6 +100,9 @@ public abstract class AbstractTimeLineView extends BorderPane { public void handleRefreshRequested(RefreshRequestedEvent event) { refresh(); } + + + /** * Does the view represent an out-of-date state of the DB. It might if, for @@ -227,6 +230,7 @@ public abstract class AbstractTimeLineView extends BorderPane { TimeLineController.getTimeZone().removeListener(updateListener); updateListener = null; filteredEvents.unRegisterForEvents(this); + controller.unRegisterForEvents(this); } /** diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.java index 4b4fe6093e..46df5150e9 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.java @@ -638,55 +638,54 @@ final public class ViewFrame extends BorderPane { private void syncViewMode() { ViewMode newViewMode = controller.getViewMode(); - Platform.runLater(() -> { - //clear out old view. - if (hostedView != null) { - hostedView.dispose(); - } + //clear out old view. + if (hostedView != null) { + hostedView.dispose(); + } - //Set a new AbstractTimeLineView as the one hosted by this ViewFrame. - switch (newViewMode) { - case LIST: - hostedView = new ListViewPane(controller); - //TODO: should remove listeners from events tree - break; - case COUNTS: - hostedView = new CountsViewPane(controller); - //TODO: should remove listeners from events tree - break; - case DETAIL: - DetailViewPane detailViewPane = new DetailViewPane(controller); - //link events tree to detailview instance. - detailViewPane.setHighLightedEvents(eventsTree.getSelectedEvents()); - eventsTree.setDetailViewPane(detailViewPane); - hostedView = detailViewPane; - break; - default: - throw new IllegalArgumentException("Unknown ViewMode: " + newViewMode.toString());//NON-NLS - } + //Set a new AbstractTimeLineView as the one hosted by this ViewFrame. + switch (newViewMode) { + case LIST: + hostedView = new ListViewPane(controller); + //TODO: should remove listeners from events tree + break; + case COUNTS: + hostedView = new CountsViewPane(controller); + //TODO: should remove listeners from events tree + break; + case DETAIL: + DetailViewPane detailViewPane = new DetailViewPane(controller); + //link events tree to detailview instance. + detailViewPane.setHighLightedEvents(eventsTree.getSelectedEvents()); + eventsTree.setDetailViewPane(detailViewPane); + hostedView = detailViewPane; + break; + default: + throw new IllegalArgumentException("Unknown ViewMode: " + newViewMode.toString());//NON-NLS + } + controller.registerForEvents(hostedView); - viewModeToggleGroup.setValue(newViewMode); //this selects the right toggle automatically + viewModeToggleGroup.setValue(newViewMode); //this selects the right toggle automatically - //configure settings and time navigation nodes - setViewSettingsControls(hostedView.getSettingsControls()); - setTimeNavigationControls(hostedView.hasCustomTimeNavigationControls() - ? hostedView.getTimeNavigationControls() - : defaultTimeNavigationNodes); + //configure settings and time navigation nodes + setViewSettingsControls(hostedView.getSettingsControls()); + setTimeNavigationControls(hostedView.hasCustomTimeNavigationControls() + ? hostedView.getTimeNavigationControls() + : defaultTimeNavigationNodes); - //do further setup of new view. - ActionUtils.configureButton(new Refresh(), refreshButton);//configure new refresh action for new view - hostedView.refresh(); - notificationPane.setContent(hostedView); - //listen to has events property and show "dialog" if it is false. - hostedView.hasVisibleEventsProperty().addListener(hasEvents -> { - notificationPane.setContent(hostedView.hasVisibleEvents() - ? hostedView - : new StackPane(hostedView, - NO_EVENTS_BACKGROUND, - new NoEventsDialog(() -> notificationPane.setContent(hostedView)) - ) - ); - }); + //do further setup of new view. + ActionUtils.configureButton(new Refresh(), refreshButton);//configure new refresh action for new view + hostedView.refresh(); + notificationPane.setContent(hostedView); + //listen to has events property and show "dialog" if it is false. + hostedView.hasVisibleEventsProperty().addListener(hasEvents -> { + notificationPane.setContent(hostedView.hasVisibleEvents() + ? hostedView + : new StackPane(hostedView, + NO_EVENTS_BACKGROUND, + new NoEventsDialog(() -> notificationPane.setContent(hostedView)) + ) + ); }); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/ZoomRanges.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/ZoomRanges.java index 83c337197f..f71ba00983 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/ZoomRanges.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/ZoomRanges.java @@ -30,7 +30,7 @@ public enum ZoomRanges { THREE_YEARS(NbBundle.getMessage(ZoomRanges.class, "Timeline.ui.ZoomRanges.threeyears.text"), Years.THREE), FIVE_YEARS(NbBundle.getMessage(ZoomRanges.class, "Timeline.ui.ZoomRanges.fiveyears.text"), Years.years(5)), TEN_YEARS(NbBundle.getMessage(ZoomRanges.class, "Timeline.ui.ZoomRanges.tenyears.text"), Years.years(10)), - ALL(NbBundle.getMessage(ZoomRanges.class, "Timeline.ui.ZoomRanges.all.text"), Years.years(1000000)); + ALL(NbBundle.getMessage(ZoomRanges.class, "Timeline.ui.ZoomRanges.all.text"), Years.years(1_000_000)); private ZoomRanges(String displayName, ReadablePeriod period) { this.displayName = displayName; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java index 0c5351c684..2007519707 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java @@ -40,12 +40,12 @@ import java.util.function.Function; import java.util.logging.Level; import java.util.stream.Collectors; import javafx.application.Platform; -import javafx.beans.Observable; import javafx.beans.binding.Bindings; import javafx.beans.binding.IntegerBinding; import javafx.beans.binding.StringBinding; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; +import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.geometry.Pos; @@ -242,17 +242,20 @@ class ListTimeline extends BorderPane { } }); - table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); - //keep controller's list of selected event IDs in sync with this list's - table.getSelectionModel().getSelectedItems().addListener((Observable change) -> { - //keep the selectedEventsIDs in sync with the table's selection model, via getRepresentitiveEventID(). + /* + * push list view selection to controller, mapping from CombinedEvent to + * eventID via getRepresentitiveEventID(). + */ + table.getSelectionModel().getSelectedItems().addListener((ListChangeListener.Change c) -> { controller.selectEventIDs(table.getSelectionModel().getSelectedItems().stream() .filter(Objects::nonNull) .map(CombinedEvent::getRepresentativeEventID) .collect(Collectors.toSet())); }); + table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); selectEvents(controller.getSelectedEventIDs()); + } /** @@ -290,6 +293,7 @@ class ListTimeline extends BorderPane { */ void selectEvents(Collection selectedEventIDs) { table.getSelectionModel().clearSelection(); + if (selectedEventIDs.isEmpty() == false) { List selectedCombinedEvents = table.getItems().stream() .filter(combinedEvent -> combinedEvent.getEventIDs().stream().anyMatch(selectedEventIDs::contains)) @@ -305,8 +309,8 @@ class ListTimeline extends BorderPane { .filter(index -> index >= 0) .collect(Collectors.toSet()); - Integer[] indices = selectedIndices.toArray(new Integer[selectedIndices.size()]); - if (indices.length >= 1) { + if (selectedIndices.size() > 0) { + Integer[] indices = selectedIndices.toArray(new Integer[selectedIndices.size()]); table.getSelectionModel().selectIndices(indices[0], ArrayUtils.toPrimitive(indices)); table.requestFocus(); } 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 882fe00770..c1bdf60473 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.timeline.ui.listvew; import com.google.common.collect.ImmutableList; +import com.google.common.eventbus.Subscribe; import java.util.List; import javafx.application.Platform; import javafx.collections.ObservableList; @@ -30,13 +31,14 @@ import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.ViewMode; import org.sleuthkit.autopsy.timeline.datamodel.CombinedEvent; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; +import org.sleuthkit.autopsy.timeline.events.ViewInTimelineRequestedEvent; import org.sleuthkit.autopsy.timeline.ui.AbstractTimeLineView; /** * An AbstractTimeLineView that uses a TableView to display events. */ public class ListViewPane extends AbstractTimeLineView { - + private final ListTimeline listTimeline; /** @@ -51,42 +53,46 @@ public class ListViewPane extends AbstractTimeLineView { //initialize chart; setCenter(listTimeline); - - + } - + @Override protected Task getNewUpdateTask() { return new ListUpdateTask(); } - + @Override protected void clearData() { listTimeline.clear(); } - + @Override final protected ViewMode getViewMode() { return ViewMode.LIST; } - + @Override protected ImmutableList getSettingsControls() { return ImmutableList.of(); } - + @Override protected ImmutableList getTimeNavigationControls() { return ImmutableList.copyOf(listTimeline.getNavControls()); } - + @Override protected boolean hasCustomTimeNavigationControls() { return true; } - + + @Subscribe + public void handleViewInTimelineRequested(ViewInTimelineRequestedEvent event) { + listTimeline.selectEvents(event.getEventIDs()); + } + private class ListUpdateTask extends ViewRefreshTask { - + @NbBundle.Messages({ "ListViewPane.loggedTask.queryDb=Retreiving event data", "ListViewPane.loggedTask.name=Updating List View", @@ -94,14 +100,14 @@ public class ListViewPane extends AbstractTimeLineView { ListUpdateTask() { super(Bundle.ListViewPane_loggedTask_name(), true); } - + @Override protected Boolean call() throws Exception { super.call(); if (isCancelled()) { return null; } - + FilteredEventsModel eventsModel = getEventsModel(); //grab the currently selected event @@ -113,7 +119,7 @@ public class ListViewPane extends AbstractTimeLineView { //get the combined events to be displayed updateMessage(Bundle.ListViewPane_loggedTask_queryDb()); List combinedEvents = eventsModel.getCombinedEvents(); - + updateMessage(Bundle.ListViewPane_loggedTask_updateUI()); Platform.runLater(() -> { //put the combined events into the table. @@ -121,17 +127,16 @@ public class ListViewPane extends AbstractTimeLineView { //restore the selected events listTimeline.selectEvents(selectedEventIDs); }); - + return combinedEvents.isEmpty() == false; - } - + @Override protected void cancelled() { super.cancelled(); getController().retreat(); } - + @Override protected void setDateValues(Interval timeRange) { }