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,50 +426,49 @@ 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>() {
//this will be on JFX thread @Override
switch (newSate) { public void accept(Worker.State newSate) {
case SUCCEEDED: //this will be on JFX thread
/* switch (newSate) {
* Record if ingest was running the last time the db was case SUCCEEDED:
* rebuilt, and hence it might stale. /*
*/ * Record if ingest was running the last time the db was
try { * rebuilt, and hence it might stale.
perCaseTimelineProperties.setIngestRunning(ingestRunning); */
} catch (IOException ex) { try {
MessageNotifyUtil.Notify.error(Bundle.Timeline_dialogs_title(), perCaseTimelineProperties.setIngestRunning(ingestRunning);
ingestRunning ? Bundle.TimeLineController_setIngestRunning_errMsgRunning() } catch (IOException ex) {
: Bundle.TimeLinecontroller_setIngestRunning_errMsgNotRunning()); MessageNotifyUtil.Notify.error(Bundle.Timeline_dialogs_title(),
LOGGER.log(Level.SEVERE, "Error marking the ingest state while the timeline db was populated.", ex); //NON-NLS ingestRunning ? Bundle.TimeLineController_setIngestRunning_errMsgRunning()
} : Bundle.TimeLinecontroller_setIngestRunning_errMsgNotRunning());
if (markDBNotStale) { LOGGER.log(Level.SEVERE, "Error marking the ingest state while the timeline db was populated.", ex); //NON-NLS
setEventsDBStale(false); }
filteredEvents.postDBUpdated(); 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) 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;
} case FAILED:
break; case CANCELLED:
setEventsDBStale(true);
case FAILED: break;
case CANCELLED: }
setEventsDBStale(true);
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(); synchronized (filteredEvents) {
} else { pushTimeRange(requestEvent.getInterval());
synchronized (filteredEvents) { selectEventIDs(requestEvent.getEventIDs());
if (interval != null) { setViewMode(ViewMode.LIST);
pushTimeRange(interval); eventbus.post(requestEvent);
}
if (eventIDs != null) {
setViewMode(ViewMode.LIST);
selectEventIDs(eventIDs);
}
}
} }
} }
@ -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;
/** /**
@ -100,6 +100,9 @@ public abstract class AbstractTimeLineView extends BorderPane {
public void handleRefreshRequested(RefreshRequestedEvent event) { public void handleRefreshRequested(RefreshRequestedEvent event) {
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
@ -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,55 +638,54 @@ 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(); }
}
//Set a new AbstractTimeLineView as the one hosted by this ViewFrame. //Set a new AbstractTimeLineView as the one hosted by this ViewFrame.
switch (newViewMode) { switch (newViewMode) {
case LIST: case LIST:
hostedView = new ListViewPane(controller); hostedView = new ListViewPane(controller);
//TODO: should remove listeners from events tree //TODO: should remove listeners from events tree
break; break;
case COUNTS: case COUNTS:
hostedView = new CountsViewPane(controller); hostedView = new CountsViewPane(controller);
//TODO: should remove listeners from events tree //TODO: should remove listeners from events tree
break; break;
case DETAIL: case DETAIL:
DetailViewPane detailViewPane = new DetailViewPane(controller); DetailViewPane detailViewPane = new DetailViewPane(controller);
//link events tree to detailview instance. //link events tree to detailview instance.
detailViewPane.setHighLightedEvents(eventsTree.getSelectedEvents()); detailViewPane.setHighLightedEvents(eventsTree.getSelectedEvents());
eventsTree.setDetailViewPane(detailViewPane); eventsTree.setDetailViewPane(detailViewPane);
hostedView = detailViewPane; hostedView = detailViewPane;
break; break;
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
//configure settings and time navigation nodes //configure settings and time navigation nodes
setViewSettingsControls(hostedView.getSettingsControls()); setViewSettingsControls(hostedView.getSettingsControls());
setTimeNavigationControls(hostedView.hasCustomTimeNavigationControls() setTimeNavigationControls(hostedView.hasCustomTimeNavigationControls()
? hostedView.getTimeNavigationControls() ? hostedView.getTimeNavigationControls()
: defaultTimeNavigationNodes); : defaultTimeNavigationNodes);
//do further setup of new view. //do further setup of new view.
ActionUtils.configureButton(new Refresh(), refreshButton);//configure new refresh action for new view ActionUtils.configureButton(new Refresh(), refreshButton);//configure new refresh action for new view
hostedView.refresh(); hostedView.refresh();
notificationPane.setContent(hostedView); notificationPane.setContent(hostedView);
//listen to has events property and show "dialog" if it is false. //listen to has events property and show "dialog" if it is false.
hostedView.hasVisibleEventsProperty().addListener(hasEvents -> { hostedView.hasVisibleEventsProperty().addListener(hasEvents -> {
notificationPane.setContent(hostedView.hasVisibleEvents() notificationPane.setContent(hostedView.hasVisibleEvents()
? hostedView ? hostedView
: new StackPane(hostedView, : new StackPane(hostedView,
NO_EVENTS_BACKGROUND, NO_EVENTS_BACKGROUND,
new NoEventsDialog(() -> notificationPane.setContent(hostedView)) new NoEventsDialog(() -> notificationPane.setContent(hostedView))
) )
); );
});
}); });
} }

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());
Integer[] indices = selectedIndices.toArray(new Integer[selectedIndices.size()]); if (selectedIndices.size() > 0) {
if (indices.length >= 1) { Integer[] indices = selectedIndices.toArray(new Integer[selectedIndices.size()]);
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,13 +31,14 @@ 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;
/** /**
* An AbstractTimeLineView that uses a TableView to display events. * An AbstractTimeLineView that uses a TableView to display events.
*/ */
public class ListViewPane extends AbstractTimeLineView { public class ListViewPane extends AbstractTimeLineView {
private final ListTimeline listTimeline; private final ListTimeline listTimeline;
/** /**
@ -51,42 +53,46 @@ public class ListViewPane extends AbstractTimeLineView {
//initialize chart; //initialize chart;
setCenter(listTimeline); setCenter(listTimeline);
} }
@Override @Override
protected Task<Boolean> getNewUpdateTask() { protected Task<Boolean> getNewUpdateTask() {
return new ListUpdateTask(); return new ListUpdateTask();
} }
@Override @Override
protected void clearData() { protected void clearData() {
listTimeline.clear(); listTimeline.clear();
} }
@Override @Override
final protected ViewMode getViewMode() { final protected ViewMode getViewMode() {
return ViewMode.LIST; return ViewMode.LIST;
} }
@Override @Override
protected ImmutableList<Node> getSettingsControls() { protected ImmutableList<Node> getSettingsControls() {
return ImmutableList.of(); return ImmutableList.of();
} }
@Override @Override
protected ImmutableList<Node> getTimeNavigationControls() { protected ImmutableList<Node> getTimeNavigationControls() {
return ImmutableList.copyOf(listTimeline.getNavControls()); return ImmutableList.copyOf(listTimeline.getNavControls());
} }
@Override @Override
protected boolean hasCustomTimeNavigationControls() { protected boolean hasCustomTimeNavigationControls() {
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({
"ListViewPane.loggedTask.queryDb=Retreiving event data", "ListViewPane.loggedTask.queryDb=Retreiving event data",
"ListViewPane.loggedTask.name=Updating List View", "ListViewPane.loggedTask.name=Updating List View",
@ -94,14 +100,14 @@ public class ListViewPane extends AbstractTimeLineView {
ListUpdateTask() { ListUpdateTask() {
super(Bundle.ListViewPane_loggedTask_name(), true); super(Bundle.ListViewPane_loggedTask_name(), true);
} }
@Override @Override
protected Boolean call() throws Exception { protected Boolean call() throws Exception {
super.call(); super.call();
if (isCancelled()) { if (isCancelled()) {
return null; return null;
} }
FilteredEventsModel eventsModel = getEventsModel(); FilteredEventsModel eventsModel = getEventsModel();
//grab the currently selected event //grab the currently selected event
@ -113,7 +119,7 @@ public class ListViewPane extends AbstractTimeLineView {
//get the combined events to be displayed //get the combined events to be displayed
updateMessage(Bundle.ListViewPane_loggedTask_queryDb()); updateMessage(Bundle.ListViewPane_loggedTask_queryDb());
List<CombinedEvent> combinedEvents = eventsModel.getCombinedEvents(); List<CombinedEvent> combinedEvents = eventsModel.getCombinedEvents();
updateMessage(Bundle.ListViewPane_loggedTask_updateUI()); updateMessage(Bundle.ListViewPane_loggedTask_updateUI());
Platform.runLater(() -> { Platform.runLater(() -> {
//put the combined events into the table. //put the combined events into the table.
@ -121,17 +127,16 @@ public class ListViewPane extends AbstractTimeLineView {
//restore the selected events //restore the selected events
listTimeline.selectEvents(selectedEventIDs); listTimeline.selectEvents(selectedEventIDs);
}); });
return combinedEvents.isEmpty() == false; return combinedEvents.isEmpty() == false;
} }
@Override @Override
protected void cancelled() { protected void cancelled() {
super.cancelled(); super.cancelled();
getController().retreat(); getController().retreat();
} }
@Override @Override
protected void setDateValues(Interval timeRange) { protected void setDateValues(Interval timeRange) {
} }