various cleenup and introduction of eventbus to try to get view in timeline selection working consistently

This commit is contained in:
jmillman 2016-06-10 16:45:07 -04:00
parent 3a3af6647e
commit e6984d3961
12 changed files with 282 additions and 200 deletions

View File

@ -26,7 +26,6 @@ import java.time.temporal.ChronoUnit;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
@ -54,6 +53,7 @@ import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent; import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; 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.autopsy.timeline.utils.IntervalUtils;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact; 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 * choose a specific event and a time range around it to show in the Timeline
* List View. * List View.
*/ */
final class ShowInTimelineDialog extends Dialog<ShowInTimelineDialog.EvenstInInterval> { final class ShowInTimelineDialog extends Dialog<ViewInTimelineRequestedEvent> {
private static final Logger LOGGER = Logger.getLogger(ShowInTimelineDialog.class.getName()); private static final Logger LOGGER = Logger.getLogger(ShowInTimelineDialog.class.getName());
@ -218,10 +218,10 @@ final class ShowInTimelineDialog extends Dialog<ShowInTimelineDialog.EvenstInInt
* *
* @return The EventInTimeRange that is the "result" of this dialof. * @return The EventInTimeRange that is the "result" of this dialof.
*/ */
private EvenstInInterval makeEventInTimeRange(SingleEvent selectedEvent) { private ViewInTimelineRequestedEvent makeEventInTimeRange(SingleEvent selectedEvent) {
Duration selectedDuration = Duration.of(amountSpinner.getValue(), unitComboBox.getSelectionModel().getSelectedItem()); Duration selectedDuration = Duration.of(amountSpinner.getValue(), unitComboBox.getSelectionModel().getSelectedItem());
Interval range = IntervalUtils.getIntervalAround(Instant.ofEpochMilli(selectedEvent.getStartMillis()), selectedDuration); Interval range = IntervalUtils.getIntervalAround(Instant.ofEpochMilli(selectedEvent.getStartMillis()), selectedDuration);
return new EvenstInInterval(Collections.singleton(selectedEvent.getEventID()), range); return new ViewInTimelineRequestedEvent(Collections.singleton(selectedEvent.getEventID()), range);
} }
/** /**
@ -281,43 +281,4 @@ final class ShowInTimelineDialog extends Dialog<ShowInTimelineDialog.EvenstInInt
} }
} }
} }
/**
* Encapsulates the result of the ShowIntimelineDialog: a Set of event IDs
* and an Interval.
*/
static final class EvenstInInterval {
private final Set<Long> eventIDs;
private final Interval range;
/**
* Constructor
*
* @param eventIDs The event IDs to include.
* @param range The Interval to show.
*/
EvenstInInterval(Set<Long> eventIDs, Interval range) {
this.eventIDs = eventIDs;
this.range = range;
}
/**
* Get the event IDs.
*
* @return The event IDs
*/
public Set<Long> getEventIDs() {
return eventIDs;
}
/**
* Get the Interval.
*
* @return The Interval.
*/
public Interval getInterval() {
return range;
}
}
} }

View File

@ -18,6 +18,7 @@
*/ */
package org.sleuthkit.autopsy.timeline; package org.sleuthkit.autopsy.timeline;
import com.google.common.eventbus.EventBus;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.io.IOException; import java.io.IOException;
@ -27,7 +28,6 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; 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.TimeLineEvent;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.db.EventsRepository; 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.DescriptionFilter;
import org.sleuthkit.autopsy.timeline.filters.RootFilter; import org.sleuthkit.autopsy.timeline.filters.RootFilter;
import org.sleuthkit.autopsy.timeline.filters.TypeFilter; import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
@ -145,6 +146,7 @@ public class TimeLineController {
private final ReadOnlyStringWrapper taskTitle = new ReadOnlyStringWrapper(); private final ReadOnlyStringWrapper taskTitle = new ReadOnlyStringWrapper();
private final ReadOnlyStringWrapper statusMessage = 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 * 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) //selected events (ie shown in the result viewer)
@GuardedBy("this") @GuardedBy("this")
private final ObservableList<Long> selectedEventIDs = FXCollections.<Long>synchronizedObservableList(FXCollections.<Long>observableArrayList()); private final ObservableList<Long> selectedEventIDs = FXCollections.<Long>observableArrayList();
@GuardedBy("this") @GuardedBy("this")
private final ReadOnlyObjectWrapper<Interval> selectedTimeRange = new ReadOnlyObjectWrapper<>(); private final ReadOnlyObjectWrapper<Interval> selectedTimeRange = new ReadOnlyObjectWrapper<>();
@ -424,7 +426,9 @@ public class TimeLineController {
//get a task that rebuilds the repo with the bellow state listener attached //get a task that rebuilds the repo with the bellow state listener attached
final CancellationProgressTask<?> rebuildRepositoryTask; final CancellationProgressTask<?> rebuildRepositoryTask;
rebuildRepositoryTask = repoBuilder.apply(newSate -> { rebuildRepositoryTask = repoBuilder.apply(new Consumer<Worker.State>() {
@Override
public void accept(Worker.State newSate) {
//this will be on JFX thread //this will be on JFX thread
switch (newSate) { switch (newSate) {
case SUCCEEDED: case SUCCEEDED:
@ -444,31 +448,28 @@ public class TimeLineController {
setEventsDBStale(false); setEventsDBStale(false);
filteredEvents.postDBUpdated(); filteredEvents.postDBUpdated();
} }
if (file == null && artifact == null) { if (file == null && artifact == null) {
SwingUtilities.invokeLater(this::showWindow); SwingUtilities.invokeLater(TimeLineController.this::showWindow);
TimeLineController.this.showFullRange(); TimeLineController.this.showFullRange();
} else { } else {
Platform.runLater(() -> {
ShowInTimelineDialog d = (file == null) ShowInTimelineDialog d = (file == null)
? new ShowInTimelineDialog(TimeLineController.this, artifact) ? new ShowInTimelineDialog(TimeLineController.this, artifact)
: new ShowInTimelineDialog(TimeLineController.this, file); : new ShowInTimelineDialog(TimeLineController.this, file);
Optional<ShowInTimelineDialog.EvenstInInterval> result = d.showAndWait(); Optional<ViewInTimelineRequestedEvent> result = d.showAndWait();
result.ifPresent(eventInTimeRange -> { result.ifPresent(viewInTimelineRequestedEvent -> {
SwingUtilities.invokeLater(TimeLineController.this::showWindow); SwingUtilities.invokeLater(TimeLineController.this::showWindow);
showEvents(eventInTimeRange.getEventIDs(), eventInTimeRange.getInterval()); showEvents(viewInTimelineRequestedEvent);
}); });
});
} }
break; break;
case FAILED: case FAILED:
case CANCELLED: case CANCELLED:
setEventsDBStale(true); setEventsDBStale(true);
break; break;
} }
}
}); });
/* /*
@ -483,6 +484,7 @@ public class TimeLineController {
* done. * done.
*/ */
@ThreadConfined(type = ThreadConfined.ThreadType.JFX) @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
public void rebuildRepo(AbstractFile file, BlackboardArtifact artifact) { public void rebuildRepo(AbstractFile file, BlackboardArtifact artifact) {
rebuildRepoHelper(eventsRepository::rebuildRepository, true, file, artifact); rebuildRepoHelper(eventsRepository::rebuildRepository, true, file, artifact);
} }
@ -505,19 +507,13 @@ public class TimeLineController {
} }
} }
public void showEvents(Set<Long> eventIDs, Interval interval) { @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
if (eventIDs == null && interval == null) { private void showEvents(ViewInTimelineRequestedEvent requestEvent) {
showFullRange();
} else {
synchronized (filteredEvents) { synchronized (filteredEvents) {
if (interval != null) { pushTimeRange(requestEvent.getInterval());
pushTimeRange(interval); selectEventIDs(requestEvent.getEventIDs());
}
if (eventIDs != null) {
setViewMode(ViewMode.LIST); setViewMode(ViewMode.LIST);
selectEventIDs(eventIDs); eventbus.post(requestEvent);
}
}
} }
} }
@ -662,11 +658,6 @@ public class TimeLineController {
pushTimeRange(new Interval(start, end)); pushTimeRange(new Interval(start, end));
} }
public void selectEventIDs(Collection<Long> events) {
selectedTimeRange.set(filteredEvents.getSpanningInterval(events));
selectedEventIDs.setAll(events);
}
/** /**
* Show the timeline TimeLineTopComponent. This method will construct a new * Show the timeline TimeLineTopComponent. This method will construct a new
* instance of TimeLineTopComponent if necessary. * instance of TimeLineTopComponent if necessary.
@ -770,6 +761,17 @@ public class TimeLineController {
historyManager.advance(newState); 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<Long> eventIDs) {
selectedTimeRange.set(filteredEvents.getSpanningInterval(eventIDs));
selectedEventIDs.setAll(eventIDs);
}
public void selectTimeAndType(Interval interval, EventType type) { public void selectTimeAndType(Interval interval, EventType type) {
final Interval timeRange = filteredEvents.getSpanningInterval().overlap(interval); 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) { static synchronized public void setTimeZone(TimeZone timeZone) {
TimeLineController.timeZone.set(timeZone); TimeLineController.timeZone.set(timeZone);
} }
/** /**

View File

@ -114,7 +114,7 @@ public final class FilteredEventsModel {
@GuardedBy("this") @GuardedBy("this")
private final ReadOnlyObjectWrapper<ZoomParams> requestedZoomParamters = new ReadOnlyObjectWrapper<>(); private final ReadOnlyObjectWrapper<ZoomParams> 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 * The underlying repo for events. Atomic access to repo is synchronized

View File

@ -36,7 +36,6 @@ import org.sleuthkit.datamodel.TskCoreException;
public interface ArtifactEventType extends EventType { public interface ArtifactEventType extends EventType {
public static final Logger LOGGER = Logger.getLogger(ArtifactEventType.class.getName()); public static final Logger LOGGER = Logger.getLogger(ArtifactEventType.class.getName());
static final EmptyExtractor EMPTY_EXTRACTOR = new EmptyExtractor();
public static Set<ArtifactEventType> getAllArtifactEventTypes() { public static Set<ArtifactEventType> getAllArtifactEventTypes() {
return allTypes.stream() return allTypes.stream()

View File

@ -669,37 +669,26 @@ public class EventDB {
} }
} }
List<Long> getDerivedEventIDs(Set<Long> fileIDs, Set<Long> artifactIDS) { /**
DBLock.lock(); * Get a List of event IDs for the events that are derived from the given
String query = "SELECT Group_Concat(event_id) FROM events" * artifact.
+ " WHERE ( file_id IN (" + StringUtils.join(fileIDs, ", ") + ") AND artifact_id IS NULL)" *
+ " OR artifact_id IN (" + StringUtils.join(artifactIDS, ", ") + ")"; * @param artifact The BlackboardArtifact to get derived event IDs for.
*
try (Statement stmt = con.createStatement(); * @return A List of event IDs for the events that are derived from the
ResultSet rs = stmt.executeQuery(query);) { // NON-NLS * given artifact.
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;
}
List<Long> getEventIDsForArtifact(BlackboardArtifact artifact) { List<Long> getEventIDsForArtifact(BlackboardArtifact artifact) {
DBLock.lock(); 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<Long> results = new ArrayList<>(); ArrayList<Long> results = new ArrayList<>();
try (Statement stmt = con.createStatement(); try (Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(query);) { // NON-NLS ResultSet rs = stmt.executeQuery(query);) {
while (rs.next()) { while (rs.next()) {
results.add(rs.getLong("event_id")); results.add(rs.getLong("event_id"));
} }
} catch (SQLException ex) { } catch (SQLException ex) {
LOGGER.log(Level.SEVERE, "Error executing getEventIDsForArtifact query.", ex); // NON-NLS LOGGER.log(Level.SEVERE, "Error executing getEventIDsForArtifact query.", ex); // NON-NLS
} finally { } finally {
@ -708,19 +697,33 @@ public class EventDB {
return results; 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<Long> getEventIDsForFile(AbstractFile file, boolean includeDerivedArtifacts) { List<Long> getEventIDsForFile(AbstractFile file, boolean includeDerivedArtifacts) {
DBLock.lock(); DBLock.lock();
String query = "SELECT event_id FROM events WHERE file_id == " + file.getId() String query = "SELECT event_id FROM events WHERE file_id == " + file.getId()
+ (includeDerivedArtifacts ? "" : " AND artifact_id IS NULL"); + (includeDerivedArtifacts ? "" : " AND artifact_id IS NULL");
ArrayList<Long> results = new ArrayList<>(); ArrayList<Long> results = new ArrayList<>();
try (Statement stmt = con.createStatement(); try (Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(query);) { // NON-NLS ResultSet rs = stmt.executeQuery(query);) {
while (rs.next()) { while (rs.next()) {
results.add(rs.getLong("event_id")); results.add(rs.getLong("event_id"));
} }
} catch (SQLException ex) { } catch (SQLException ex) {
LOGGER.log(Level.SEVERE, "Error executing getEventIDsForFile query.", ex); // NON-NLS LOGGER.log(Level.SEVERE, "Error executing getEventIDsForFile query.", ex); // NON-NLS
} finally { } finally {

View File

@ -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<Long> getEventIDsForFile(AbstractFile file, boolean includedDerivedArtifacts) { public List<Long> getEventIDsForFile(AbstractFile file, boolean includedDerivedArtifacts) {
return eventDB.getEventIDsForFile(file, 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<Long> getEventIDsForArtifact(BlackboardArtifact artifact) { public List<Long> getEventIDsForArtifact(BlackboardArtifact artifact) {
return eventDB.getEventIDsForArtifact(artifact); return eventDB.getEventIDsForArtifact(artifact);
} }

View File

@ -0,0 +1,61 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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<Long> eventIDs;
private final Interval range;
/**
* Constructor
*
* @param eventIDs The event IDs to include.
* @param range The Interval to show.
*/
public ViewInTimelineRequestedEvent(Set<Long> eventIDs, Interval range) {
this.eventIDs = eventIDs;
this.range = range;
}
/**
* Get the event IDs.
*
* @return The event IDs
*/
public Set<Long> getEventIDs() {
return eventIDs;
}
/**
* Get the Interval.
*
* @return The Interval.
*/
public Interval getInterval() {
return range;
}
}

View File

@ -74,7 +74,7 @@ public abstract class AbstractTimeLineView extends BorderPane {
*/ */
private Task<Boolean> updateTask; private Task<Boolean> updateTask;
public final TimeLineController controller; private final TimeLineController controller;
private final FilteredEventsModel filteredEvents; private final FilteredEventsModel filteredEvents;
/** /**
@ -101,6 +101,9 @@ public abstract class AbstractTimeLineView extends BorderPane {
refresh(); refresh();
} }
/** /**
* Does the view represent an out-of-date state of the DB. It might if, for * Does the view represent an out-of-date state of the DB. It might if, for
* example, tags have been updated but the view was not refreshed. * example, tags have been updated but the view was not refreshed.
@ -227,6 +230,7 @@ public abstract class AbstractTimeLineView extends BorderPane {
TimeLineController.getTimeZone().removeListener(updateListener); TimeLineController.getTimeZone().removeListener(updateListener);
updateListener = null; updateListener = null;
filteredEvents.unRegisterForEvents(this); filteredEvents.unRegisterForEvents(this);
controller.unRegisterForEvents(this);
} }
/** /**

View File

@ -638,7 +638,6 @@ final public class ViewFrame extends BorderPane {
private void syncViewMode() { private void syncViewMode() {
ViewMode newViewMode = controller.getViewMode(); ViewMode newViewMode = controller.getViewMode();
Platform.runLater(() -> {
//clear out old view. //clear out old view.
if (hostedView != null) { if (hostedView != null) {
hostedView.dispose(); hostedView.dispose();
@ -664,6 +663,7 @@ final public class ViewFrame extends BorderPane {
default: default:
throw new IllegalArgumentException("Unknown ViewMode: " + newViewMode.toString());//NON-NLS 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
@ -687,7 +687,6 @@ final public class ViewFrame extends BorderPane {
) )
); );
}); });
});
} }
/** /**

View File

@ -30,7 +30,7 @@ public enum ZoomRanges {
THREE_YEARS(NbBundle.getMessage(ZoomRanges.class, "Timeline.ui.ZoomRanges.threeyears.text"), Years.THREE), 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)), 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)), 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) { private ZoomRanges(String displayName, ReadablePeriod period) {
this.displayName = displayName; this.displayName = displayName;

View File

@ -40,12 +40,12 @@ import java.util.function.Function;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.binding.IntegerBinding; import javafx.beans.binding.IntegerBinding;
import javafx.beans.binding.StringBinding; import javafx.beans.binding.StringBinding;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.Pos; 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 * push list view selection to controller, mapping from CombinedEvent to
table.getSelectionModel().getSelectedItems().addListener((Observable change) -> { * eventID via getRepresentitiveEventID().
//keep the selectedEventsIDs in sync with the table's selection model, via getRepresentitiveEventID(). */
table.getSelectionModel().getSelectedItems().addListener((ListChangeListener.Change<? extends CombinedEvent> c) -> {
controller.selectEventIDs(table.getSelectionModel().getSelectedItems().stream() controller.selectEventIDs(table.getSelectionModel().getSelectedItems().stream()
.filter(Objects::nonNull) .filter(Objects::nonNull)
.map(CombinedEvent::getRepresentativeEventID) .map(CombinedEvent::getRepresentativeEventID)
.collect(Collectors.toSet())); .collect(Collectors.toSet()));
}); });
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
selectEvents(controller.getSelectedEventIDs()); selectEvents(controller.getSelectedEventIDs());
} }
/** /**
@ -290,6 +293,7 @@ class ListTimeline extends BorderPane {
*/ */
void selectEvents(Collection<Long> selectedEventIDs) { void selectEvents(Collection<Long> selectedEventIDs) {
table.getSelectionModel().clearSelection(); table.getSelectionModel().clearSelection();
if (selectedEventIDs.isEmpty() == false) { if (selectedEventIDs.isEmpty() == false) {
List<CombinedEvent> selectedCombinedEvents = table.getItems().stream() List<CombinedEvent> selectedCombinedEvents = table.getItems().stream()
.filter(combinedEvent -> combinedEvent.getEventIDs().stream().anyMatch(selectedEventIDs::contains)) .filter(combinedEvent -> combinedEvent.getEventIDs().stream().anyMatch(selectedEventIDs::contains))
@ -305,8 +309,8 @@ class ListTimeline extends BorderPane {
.filter(index -> index >= 0) .filter(index -> index >= 0)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
if (selectedIndices.size() > 0) {
Integer[] indices = selectedIndices.toArray(new Integer[selectedIndices.size()]); Integer[] indices = selectedIndices.toArray(new Integer[selectedIndices.size()]);
if (indices.length >= 1) {
table.getSelectionModel().selectIndices(indices[0], ArrayUtils.toPrimitive(indices)); table.getSelectionModel().selectIndices(indices[0], ArrayUtils.toPrimitive(indices));
table.requestFocus(); table.requestFocus();
} }

View File

@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.timeline.ui.listvew; package org.sleuthkit.autopsy.timeline.ui.listvew;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.eventbus.Subscribe;
import java.util.List; import java.util.List;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
@ -30,6 +31,7 @@ import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.ViewMode; import org.sleuthkit.autopsy.timeline.ViewMode;
import org.sleuthkit.autopsy.timeline.datamodel.CombinedEvent; import org.sleuthkit.autopsy.timeline.datamodel.CombinedEvent;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.events.ViewInTimelineRequestedEvent;
import org.sleuthkit.autopsy.timeline.ui.AbstractTimeLineView; import org.sleuthkit.autopsy.timeline.ui.AbstractTimeLineView;
/** /**
@ -52,7 +54,6 @@ public class ListViewPane extends AbstractTimeLineView {
//initialize chart; //initialize chart;
setCenter(listTimeline); setCenter(listTimeline);
} }
@Override @Override
@ -85,6 +86,11 @@ public class ListViewPane extends AbstractTimeLineView {
return true; return true;
} }
@Subscribe
public void handleViewInTimelineRequested(ViewInTimelineRequestedEvent event) {
listTimeline.selectEvents(event.getEventIDs());
}
private class ListUpdateTask extends ViewRefreshTask<Interval> { private class ListUpdateTask extends ViewRefreshTask<Interval> {
@NbBundle.Messages({ @NbBundle.Messages({
@ -123,7 +129,6 @@ public class ListViewPane extends AbstractTimeLineView {
}); });
return combinedEvents.isEmpty() == false; return combinedEvents.isEmpty() == false;
} }
@Override @Override