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.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<ShowInTimelineDialog.EvenstInInterval> {
final class ShowInTimelineDialog extends Dialog<ViewInTimelineRequestedEvent> {
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.
*/
private EvenstInInterval makeEventInTimeRange(SingleEvent selectedEvent) {
private ViewInTimelineRequestedEvent makeEventInTimeRange(SingleEvent selectedEvent) {
Duration selectedDuration = Duration.of(amountSpinner.getValue(), unitComboBox.getSelectionModel().getSelectedItem());
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;
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<Long> selectedEventIDs = FXCollections.<Long>synchronizedObservableList(FXCollections.<Long>observableArrayList());
private final ObservableList<Long> selectedEventIDs = FXCollections.<Long>observableArrayList();
@GuardedBy("this")
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
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<Worker.State>() {
@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<ShowInTimelineDialog.EvenstInInterval> result = d.showAndWait();
result.ifPresent(eventInTimeRange -> {
Optional<ViewInTimelineRequestedEvent> 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<Long> 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<Long> 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<Long> 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);
}
/**

View File

@ -114,7 +114,7 @@ public final class FilteredEventsModel {
@GuardedBy("this")
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

View File

@ -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<ArtifactEventType> getAllArtifactEventTypes() {
return allTypes.stream()

View File

@ -669,37 +669,26 @@ public class EventDB {
}
}
List<Long> getDerivedEventIDs(Set<Long> fileIDs, Set<Long> 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<Long> 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<Long> 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<Long> 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<Long> 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 {

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) {
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) {
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;
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);
}
/**

View File

@ -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))
)
);
});
}

View File

@ -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;

View File

@ -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<? extends CombinedEvent> 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<Long> selectedEventIDs) {
table.getSelectionModel().clearSelection();
if (selectedEventIDs.isEmpty() == false) {
List<CombinedEvent> 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();
}

View File

@ -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<Boolean> getNewUpdateTask() {
return new ListUpdateTask();
}
@Override
protected void clearData() {
listTimeline.clear();
}
@Override
final protected ViewMode getViewMode() {
return ViewMode.LIST;
}
@Override
protected ImmutableList<Node> getSettingsControls() {
return ImmutableList.of();
}
@Override
protected ImmutableList<Node> 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<Interval> {
@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<CombinedEvent> 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) {
}