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:
Richard Cordovano 2016-05-25 20:02:31 -04:00
commit 4bdec3f2c8
2 changed files with 79 additions and 111 deletions

View File

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

View File

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