mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-17 10:17:41 +00:00
various cleenup and introduction of eventbus to try to get view in timeline selection working consistently
This commit is contained in:
parent
3a3af6647e
commit
e6984d3961
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,7 +426,9 @@ public class TimeLineController {
|
||||
|
||||
//get a task that rebuilds the repo with the bellow state listener attached
|
||||
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
|
||||
switch (newSate) {
|
||||
case SUCCEEDED:
|
||||
@ -444,31 +448,28 @@ public class TimeLineController {
|
||||
setEventsDBStale(false);
|
||||
filteredEvents.postDBUpdated();
|
||||
}
|
||||
|
||||
if (file == null && artifact == null) {
|
||||
SwingUtilities.invokeLater(this::showWindow);
|
||||
SwingUtilities.invokeLater(TimeLineController.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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
@ -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 {
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
private void showEvents(ViewInTimelineRequestedEvent requestEvent) {
|
||||
synchronized (filteredEvents) {
|
||||
if (interval != null) {
|
||||
pushTimeRange(interval);
|
||||
}
|
||||
if (eventIDs != null) {
|
||||
pushTimeRange(requestEvent.getInterval());
|
||||
selectEventIDs(requestEvent.getEventIDs());
|
||||
setViewMode(ViewMode.LIST);
|
||||
selectEventIDs(eventIDs);
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
/**
|
||||
@ -101,6 +101,9 @@ public abstract class AbstractTimeLineView extends BorderPane {
|
||||
refresh();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@ -227,6 +230,7 @@ public abstract class AbstractTimeLineView extends BorderPane {
|
||||
TimeLineController.getTimeZone().removeListener(updateListener);
|
||||
updateListener = null;
|
||||
filteredEvents.unRegisterForEvents(this);
|
||||
controller.unRegisterForEvents(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -638,7 +638,6 @@ final public class ViewFrame extends BorderPane {
|
||||
private void syncViewMode() {
|
||||
ViewMode newViewMode = controller.getViewMode();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
//clear out old view.
|
||||
if (hostedView != null) {
|
||||
hostedView.dispose();
|
||||
@ -664,6 +663,7 @@ final public class ViewFrame extends BorderPane {
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown ViewMode: " + newViewMode.toString());//NON-NLS
|
||||
}
|
||||
controller.registerForEvents(hostedView);
|
||||
|
||||
viewModeToggleGroup.setValue(newViewMode); //this selects the right toggle automatically
|
||||
|
||||
@ -687,7 +687,6 @@ final public class ViewFrame extends BorderPane {
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
||||
if (selectedIndices.size() > 0) {
|
||||
Integer[] indices = selectedIndices.toArray(new Integer[selectedIndices.size()]);
|
||||
if (indices.length >= 1) {
|
||||
table.getSelectionModel().selectIndices(indices[0], ArrayUtils.toPrimitive(indices));
|
||||
table.requestFocus();
|
||||
}
|
||||
|
@ -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,6 +31,7 @@ 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;
|
||||
|
||||
/**
|
||||
@ -52,7 +54,6 @@ public class ListViewPane extends AbstractTimeLineView {
|
||||
//initialize chart;
|
||||
setCenter(listTimeline);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -85,6 +86,11 @@ public class ListViewPane extends AbstractTimeLineView {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void handleViewInTimelineRequested(ViewInTimelineRequestedEvent event) {
|
||||
listTimeline.selectEvents(event.getEventIDs());
|
||||
}
|
||||
|
||||
private class ListUpdateTask extends ViewRefreshTask<Interval> {
|
||||
|
||||
@NbBundle.Messages({
|
||||
@ -123,7 +129,6 @@ public class ListViewPane extends AbstractTimeLineView {
|
||||
});
|
||||
|
||||
return combinedEvents.isEmpty() == false;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Loading…
x
Reference in New Issue
Block a user