mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-19 11:07:43 +00:00
Merge pull request #2158 from millmanorama/TL-list-view-maintain-selection
TL maintain selection in list view when contents change
This commit is contained in:
commit
4bdec3f2c8
@ -23,9 +23,10 @@ import java.util.Arrays;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Bindings;
|
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.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
@ -57,7 +58,7 @@ import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
|||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The inner component that makes up the Lsit view. Manages the table.
|
* The inner component that makes up the List view. Manages the TableView.
|
||||||
*/
|
*/
|
||||||
class ListTimeline extends BorderPane {
|
class ListTimeline extends BorderPane {
|
||||||
|
|
||||||
@ -100,6 +101,9 @@ class ListTimeline extends BorderPane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
|
@NbBundle.Messages({
|
||||||
|
"# {0} - the number of events",
|
||||||
|
"ListTimeline.evetnCountLabel.text={0} events"})
|
||||||
void initialize() {
|
void initialize() {
|
||||||
assert eventCountLabel != null : "fx:id=\"eventCountLabel\" was not injected: check your FXML file 'ListViewPane.fxml'.";
|
assert eventCountLabel != null : "fx:id=\"eventCountLabel\" was not injected: check your FXML file 'ListViewPane.fxml'.";
|
||||||
assert table != null : "fx:id=\"table\" was not injected: check your FXML file 'ListViewPane.fxml'.";
|
assert table != null : "fx:id=\"table\" was not injected: check your FXML file 'ListViewPane.fxml'.";
|
||||||
@ -114,31 +118,44 @@ class ListTimeline extends BorderPane {
|
|||||||
//override default row with one that provides context menu.S
|
//override default row with one that provides context menu.S
|
||||||
table.setRowFactory(tableView -> new EventRow());
|
table.setRowFactory(tableView -> new EventRow());
|
||||||
|
|
||||||
//remove idColumn (can be used for debugging).
|
//remove idColumn (can be restored for debugging).
|
||||||
table.getColumns().remove(idColumn);
|
table.getColumns().remove(idColumn);
|
||||||
|
|
||||||
// set up cell and cell-value factories for columns
|
///// set up cell and cell-value factories for columns
|
||||||
//
|
|
||||||
millisColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
millisColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
||||||
millisColumn.setCellFactory(col -> new EpochMillisCell());
|
millisColumn.setCellFactory(col -> new TextEventTableCell(singleEvent ->
|
||||||
|
TimeLineController.getZonedFormatter().print(singleEvent.getStartMillis())));
|
||||||
|
|
||||||
iconColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
iconColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
||||||
iconColumn.setCellFactory(col -> new ImageCell());
|
iconColumn.setCellFactory(col -> new ImageCell());
|
||||||
|
|
||||||
descriptionColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
descriptionColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
||||||
descriptionColumn.setCellFactory(col -> new DescriptionCell());
|
descriptionColumn.setCellFactory(col -> new TextEventTableCell(singleEvent ->
|
||||||
|
singleEvent.getDescription(DescriptionLoD.FULL)));
|
||||||
|
|
||||||
baseTypeColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
baseTypeColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
||||||
baseTypeColumn.setCellFactory(col -> new BaseTypeCell());
|
baseTypeColumn.setCellFactory(col -> new TextEventTableCell(singleEvent ->
|
||||||
|
singleEvent.getEventType().getBaseType().getDisplayName()));
|
||||||
|
|
||||||
subTypeColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
subTypeColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
||||||
subTypeColumn.setCellFactory(col -> new EventTypeCell());
|
subTypeColumn.setCellFactory(col -> new TextEventTableCell(singleEvent ->
|
||||||
|
singleEvent.getEventType().getDisplayName()));
|
||||||
|
|
||||||
knownColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
knownColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
||||||
knownColumn.setCellFactory(col -> new KnownCell());
|
knownColumn.setCellFactory(col -> new TextEventTableCell(singleEvent ->
|
||||||
|
singleEvent.getKnown().getName()));
|
||||||
|
|
||||||
//bind event count lable no number of items in table
|
//bind event count label to number of items in the table
|
||||||
eventCountLabel.textProperty().bind(Bindings.size(table.getItems()).asString().concat(" events"));
|
eventCountLabel.textProperty().bind(new StringBinding() {
|
||||||
|
{
|
||||||
|
bind(table.getItems());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String computeValue() {
|
||||||
|
return Bundle.ListTimeline_evetnCountLabel_text(table.getItems().size());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -149,6 +166,15 @@ class ListTimeline extends BorderPane {
|
|||||||
table.getItems().clear();
|
table.getItems().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the selected event ID.
|
||||||
|
*
|
||||||
|
* @return The selected event ID.
|
||||||
|
*/
|
||||||
|
Long getSelectedEventID() {
|
||||||
|
return table.getSelectionModel().getSelectedItem();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the Collection of events (by ID) to show in the table.
|
* Set the Collection of events (by ID) to show in the table.
|
||||||
*
|
*
|
||||||
@ -169,6 +195,18 @@ class ListTimeline extends BorderPane {
|
|||||||
return table.getSelectionModel().getSelectedItems();
|
return table.getSelectionModel().getSelectedItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the ID of the event that is selected.
|
||||||
|
*
|
||||||
|
* @param selectedEventID The ID of the event that should be selected.
|
||||||
|
*/
|
||||||
|
void selectEventID(Long selectedEventID) {
|
||||||
|
//restore selection.
|
||||||
|
table.scrollTo(selectedEventID);
|
||||||
|
table.getSelectionModel().select(selectedEventID);
|
||||||
|
table.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TableCell to show the icon for the type of an event.
|
* TableCell to show the icon for the type of an event.
|
||||||
*/
|
*/
|
||||||
@ -187,86 +225,23 @@ class ListTimeline extends BorderPane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TableCell to show the full description for an event.
|
* TableCell to show text derived from a SingleEvent by the given Funtion.
|
||||||
*/
|
*/
|
||||||
private class DescriptionCell extends EventTableCell {
|
private class TextEventTableCell extends EventTableCell {
|
||||||
|
|
||||||
@Override
|
private final Function<SingleEvent, String> textSupplier;
|
||||||
protected void updateItem(Long item, boolean empty) {
|
|
||||||
super.updateItem(item, empty);
|
|
||||||
|
|
||||||
if (empty || item == null) {
|
TextEventTableCell(Function<SingleEvent, String> textSupplier) {
|
||||||
setText("");
|
this.textSupplier = textSupplier;
|
||||||
} else {
|
|
||||||
setText(getEvent().getDescription(DescriptionLoD.FULL));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TableCell to show the base type of an event.
|
|
||||||
*/
|
|
||||||
private class BaseTypeCell extends EventTableCell {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void updateItem(Long item, boolean empty) {
|
protected void updateItem(Long item, boolean empty) {
|
||||||
super.updateItem(item, empty);
|
super.updateItem(item, empty);
|
||||||
|
|
||||||
if (empty || item == null) {
|
if (empty || item == null) {
|
||||||
setText("");
|
setText(null);
|
||||||
} else {
|
} else {
|
||||||
setText(getEvent().getEventType().getBaseType().getDisplayName());
|
setText(textSupplier.apply(getEvent()));
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TableCell to show the sub type of an event.
|
|
||||||
*/
|
|
||||||
private class EventTypeCell extends EventTableCell {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void updateItem(Long item, boolean empty) {
|
|
||||||
super.updateItem(item, empty);
|
|
||||||
|
|
||||||
if (empty || item == null) {
|
|
||||||
setText("");
|
|
||||||
} else {
|
|
||||||
setText(getEvent().getEventType().getDisplayName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TableCell to show the known state of the file backing an event.
|
|
||||||
*/
|
|
||||||
private class KnownCell extends EventTableCell {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void updateItem(Long item, boolean empty) {
|
|
||||||
super.updateItem(item, empty);
|
|
||||||
|
|
||||||
if (empty || item == null) {
|
|
||||||
setText("");
|
|
||||||
} else {
|
|
||||||
setText(getEvent().getKnown().getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TableCell to show the (start) time of an event.
|
|
||||||
*/
|
|
||||||
private class EpochMillisCell extends EventTableCell {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void updateItem(Long item, boolean empty) {
|
|
||||||
super.updateItem(item, empty);
|
|
||||||
|
|
||||||
if (empty || item == null) {
|
|
||||||
setText("");
|
|
||||||
} else {
|
|
||||||
setText(TimeLineController.getZonedFormatter().print(getEvent().getStartMillis()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,23 +29,11 @@ import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
|||||||
import org.sleuthkit.autopsy.timeline.ui.AbstractTimeLineView;
|
import org.sleuthkit.autopsy.timeline.ui.AbstractTimeLineView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param <X> The type of data plotted along the x axis
|
* An AbstractTimeLineView that uses a TableView to represent the events.
|
||||||
* @param <Y> The type of data plotted along the y axis
|
|
||||||
* @param <NodeType> The type of nodes used to represent data items
|
|
||||||
* @param <ChartType> The type of the TimeLineChart<X> this class uses to plot
|
|
||||||
* the data. Must extend Region.
|
|
||||||
*
|
|
||||||
* TODO: this is becoming (too?) closely tied to the notion that there is a
|
|
||||||
* XYChart doing the rendering. Is this a good idea? -jm
|
|
||||||
*
|
|
||||||
* TODO: pull up common history context menu items out of derived classes? -jm
|
|
||||||
*
|
|
||||||
* public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node,
|
|
||||||
* ChartType extends Region & TimeLineChart<X>> extends BorderPane {
|
|
||||||
*/
|
*/
|
||||||
public class ListViewPane extends AbstractTimeLineView {
|
public class ListViewPane extends AbstractTimeLineView {
|
||||||
|
|
||||||
private final ListTimeline listChart;
|
private final ListTimeline listTimeline;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
@ -54,15 +42,15 @@ public class ListViewPane extends AbstractTimeLineView {
|
|||||||
*/
|
*/
|
||||||
public ListViewPane(TimeLineController controller) {
|
public ListViewPane(TimeLineController controller) {
|
||||||
super(controller);
|
super(controller);
|
||||||
listChart = new ListTimeline(controller);
|
listTimeline = new ListTimeline(controller);
|
||||||
|
|
||||||
//initialize chart;
|
//initialize chart;
|
||||||
setCenter(listChart);
|
setCenter(listTimeline);
|
||||||
setSettingsNodes(new ListViewPane.ListViewSettingsPane().getChildrenUnmodifiable());
|
setSettingsNodes(new ListViewPane.ListViewSettingsPane().getChildrenUnmodifiable());
|
||||||
|
|
||||||
//keep controller's list of selected event IDs in sync with this list's
|
//keep controller's list of selected event IDs in sync with this list's
|
||||||
listChart.getSelectedEventIDs().addListener((Observable selectedIDs) -> {
|
listTimeline.getSelectedEventIDs().addListener((Observable selectedIDs) -> {
|
||||||
controller.selectEventIDs(listChart.getSelectedEventIDs());
|
controller.selectEventIDs(listTimeline.getSelectedEventIDs());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,13 +61,10 @@ public class ListViewPane extends AbstractTimeLineView {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void clearData() {
|
protected void clearData() {
|
||||||
listChart.clear();
|
listTimeline.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ListViewSettingsPane extends Parent {
|
private static class ListViewSettingsPane extends Parent {
|
||||||
|
|
||||||
ListViewSettingsPane() {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ListUpdateTask extends ViewRefreshTask<Interval> {
|
private class ListUpdateTask extends ViewRefreshTask<Interval> {
|
||||||
@ -90,21 +75,30 @@ public class ListViewPane extends AbstractTimeLineView {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Boolean call() throws Exception {
|
protected Boolean call() throws Exception {
|
||||||
super.call(); //To change body of generated methods, choose Tools | Templates.
|
super.call();
|
||||||
if (isCancelled()) {
|
if (isCancelled()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
FilteredEventsModel eventsModel = getEventsModel();
|
FilteredEventsModel eventsModel = getEventsModel();
|
||||||
|
|
||||||
//clear the chart and set the horixontal axis
|
//grab the currently selected event
|
||||||
|
Long selectedEventID = listTimeline.getSelectedEventID();
|
||||||
|
|
||||||
|
//clear the chart and set the time range.
|
||||||
resetView(eventsModel.getTimeRange());
|
resetView(eventsModel.getTimeRange());
|
||||||
|
|
||||||
updateMessage("Querying db for events");
|
updateMessage("Querying DB for events");
|
||||||
//get the event stripes to be displayed
|
//get the IDs of th events to be displayed
|
||||||
List<Long> eventIDs = eventsModel.getEventIDs();
|
List<Long> eventIDs = eventsModel.getEventIDs();
|
||||||
Platform.runLater(() -> listChart.setEventIDs(eventIDs));
|
updateMessage("Updating UI");
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
//put the event IDs into the table.
|
||||||
|
listTimeline.setEventIDs(eventIDs);
|
||||||
|
//restore the selected event
|
||||||
|
listTimeline.selectEventID(selectedEventID);
|
||||||
|
});
|
||||||
|
|
||||||
updateMessage("updating ui");
|
|
||||||
return eventIDs.isEmpty() == false;
|
return eventIDs.isEmpty() == false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,6 +111,5 @@ public class ListViewPane extends AbstractTimeLineView {
|
|||||||
@Override
|
@Override
|
||||||
protected void setDateValues(Interval timeRange) {
|
protected void setDateValues(Interval timeRange) {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user