From 2858421d46a35031158b7de7b60b56f05d314db2 Mon Sep 17 00:00:00 2001 From: jmillman Date: Fri, 13 May 2016 16:57:33 -0400 Subject: [PATCH] flesh out ListViewPane, keep events sorted by date --- .../datamodel/FilteredEventsModel.java | 4 +- .../autopsy/timeline/db/EventDB.java | 10 +- .../autopsy/timeline/db/EventsRepository.java | 2 +- .../timeline/ui/VisualizationPanel.java | 37 ++-- .../timeline/ui/listvew/ListChart.java | 163 +++++++++++++++--- .../timeline/ui/listvew/ListViewPane.java | 4 +- 6 files changed, 163 insertions(+), 57 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java index 8917c0cb84..8a744a5e20 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java @@ -279,7 +279,7 @@ public final class FilteredEventsModel { return repo.getTagCountsByTagName(eventIDsWithTags); } - public Set getEventIDs(Interval timeRange, Filter filter) { + public List getEventIDs(Interval timeRange, Filter filter) { final Interval overlap; final RootFilter intersect; synchronized (this) { @@ -290,7 +290,7 @@ public final class FilteredEventsModel { return repo.getEventIDs(overlap, intersect); } - public Set getEventIDs() { + public List getEventIDs() { return getEventIDs(requestedTimeRange.get(), requestedFilter.get()); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java index 996c35ef3b..2374e7a032 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java @@ -343,18 +343,19 @@ public class EventDB { return result; } - Set getEventIDs(Interval timeRange, RootFilter filter) { + List getEventIDs(Interval timeRange, RootFilter filter) { return getEventIDs(timeRange.getStartMillis() / 1000, timeRange.getEndMillis() / 1000, filter); } - Set getEventIDs(Long startTime, Long endTime, RootFilter filter) { + List getEventIDs(Long startTime, Long endTime, RootFilter filter) { if (Objects.equals(startTime, endTime)) { endTime++; } - Set resultIDs = new HashSet<>(); + ArrayList resultIDs = new ArrayList<>(); DBLock.lock(); - final String query = "SELECT events.event_id AS event_id FROM events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) + " WHERE time >= " + startTime + " AND time <" + endTime + " AND " + SQLHelper.getSQLWhere(filter); // NON-NLS + final String query = "SELECT events.event_id AS event_id FROM events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) + + " WHERE time >= " + startTime + " AND time <" + endTime + " AND " + SQLHelper.getSQLWhere(filter) + " ORDER BY time ASC"; // NON-NLS try (Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery(query)) { while (rs.next()) { @@ -1168,7 +1169,6 @@ public class EventDB { return useSubTypes ? "sub_type" : "base_type"; //NON-NLS } - private PreparedStatement prepareStatement(String queryString) throws SQLException { PreparedStatement prepareStatement = con.prepareStatement(queryString); preparedStatements.add(prepareStatement); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java index a93bf9dc18..9686abb142 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java @@ -214,7 +214,7 @@ public class EventsRepository { idToEventCache.invalidateAll(); } - public Set getEventIDs(Interval timeRange, RootFilter filter) { + public List getEventIDs(Interval timeRange, RootFilter filter) { return eventDB.getEventIDs(timeRange, filter); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java index 7cb033f9a8..f31b5e4f0d 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java @@ -29,15 +29,12 @@ import java.util.function.Supplier; import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.Observable; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; import javafx.geometry.Insets; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.MenuButton; import javafx.scene.control.TitledPane; -import javafx.scene.control.Toggle; import javafx.scene.control.ToggleButton; import javafx.scene.control.ToolBar; import javafx.scene.control.Tooltip; @@ -60,8 +57,10 @@ import javax.annotation.Nonnull; import javax.annotation.concurrent.GuardedBy; import jfxtras.scene.control.LocalDateTimePicker; import jfxtras.scene.control.LocalDateTimeTextField; +import jfxtras.scene.control.ToggleGroupValue; import org.controlsfx.control.NotificationPane; import org.controlsfx.control.RangeSlider; +import org.controlsfx.control.SegmentedButton; import org.controlsfx.control.action.Action; import org.controlsfx.control.action.ActionUtils; import org.joda.time.DateTime; @@ -162,6 +161,8 @@ final public class VisualizationPanel extends BorderPane { @FXML private Label visualizationModeLabel; @FXML + private SegmentedButton modeSegButton; + @FXML private ToggleButton countsToggle; @FXML private ToggleButton detailsToggle; @@ -261,6 +262,7 @@ final public class VisualizationPanel extends BorderPane { "VisualizationPanel.endLabel.text=End:", "VisualizationPanel.countsToggle.text=Counts", "VisualizationPanel.detailsToggle.text=Details", + "VisualizationPanel.listToggle.text=List", "VisualizationPanel.zoomMenuButton.text=Zoom in/out to", "VisualizationPanel.tagsAddedOrDeleted=Tags have been created and/or deleted. The visualization may not be up to date." }) @@ -280,25 +282,18 @@ final public class VisualizationPanel extends BorderPane { visualizationModeLabel.setText(Bundle.VisualizationPanel_visualizationModeLabel_text()); countsToggle.setText(Bundle.VisualizationPanel_countsToggle_text()); detailsToggle.setText(Bundle.VisualizationPanel_detailsToggle_text()); - ChangeListener toggleListener = (ObservableValue observable, Toggle oldValue, Toggle newValue) -> { - if (newValue == null) { - countsToggle.getToggleGroup().selectToggle(oldValue != null ? oldValue : countsToggle); - } else if (newValue == countsToggle && oldValue != null) { - controller.setVisualizationMode(VisualizationMode.COUNTS); - } else if (newValue == detailsToggle && oldValue != null) { - controller.setVisualizationMode(VisualizationMode.DETAIL); - } else if (newValue == listToggle && oldValue != null) { - controller.setVisualizationMode(VisualizationMode.LIST); - } - }; + listToggle.setText(Bundle.VisualizationPanel_listToggle_text()); - if (countsToggle.getToggleGroup() != null) { - countsToggle.getToggleGroup().selectedToggleProperty().addListener(toggleListener); - } else { - countsToggle.toggleGroupProperty().addListener((Observable toggleGroup) -> { - countsToggle.getToggleGroup().selectedToggleProperty().addListener(toggleListener); - }); - } + ToggleGroupValue visModeToggleGroup = new ToggleGroupValue<>(); + visModeToggleGroup.add(listToggle, VisualizationMode.LIST); + visModeToggleGroup.add(detailsToggle, VisualizationMode.DETAIL); + visModeToggleGroup.add(countsToggle, VisualizationMode.COUNTS); + + modeSegButton.setToggleGroup(visModeToggleGroup); + + visModeToggleGroup.valueProperty().addListener((observable, oldVisMode, newValue) -> { + controller.setVisualizationMode(newValue != null ? newValue : (oldVisMode != null ? oldVisMode : VisualizationMode.COUNTS)); + }); controller.visualizationModeProperty().addListener(visualizationMode -> syncVisualizationMode()); syncVisualizationMode(); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListChart.java index b01e9a605a..242c8f6578 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListChart.java @@ -7,6 +7,7 @@ package org.sleuthkit.autopsy.timeline.ui.listvew; import java.util.Arrays; import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.Node; @@ -14,50 +15,66 @@ import javafx.scene.chart.Axis; import javafx.scene.control.ContextMenu; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; +import javafx.scene.control.TableRow; import javafx.scene.control.TableView; -import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.MouseEvent; +import javafx.util.Callback; import org.sleuthkit.autopsy.timeline.TimeLineController; -import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; +import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent; import org.sleuthkit.autopsy.timeline.ui.IntervalSelector; import org.sleuthkit.autopsy.timeline.ui.TimeLineChart; -import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; /** * */ class ListChart extends TableView implements TimeLineChart { + Callback, ObservableValue> cellValueFactory = param -> new SimpleObjectProperty<>(param.getValue()); + private final TimeLineController controller; - private final TableColumn idColumn = new TableColumn<>(); - private final TableColumn millisColumn = new TableColumn<>(); - private final TableColumn iconColumn = new TableColumn<>(); - private final TableColumn descriptionColumn = new TableColumn<>(); - private final TableColumn baseTypeColumn = new TableColumn<>(); - private final TableColumn subTypeColumn = new TableColumn<>(); - private final TableColumn knownColumn = new TableColumn<>(); + private final TableColumn idColumn = new TableColumn<>("Event ID"); + private final TableColumn millisColumn = new TableColumn<>("Date/Time"); + private final TableColumn iconColumn = new TableColumn<>("Icon"); + private final TableColumn descriptionColumn = new TableColumn<>("Description"); + private final TableColumn baseTypeColumn = new TableColumn<>("Base Type"); + private final TableColumn subTypeColumn = new TableColumn<>("Sub Type"); + private final TableColumn knownColumn = new TableColumn<>("Known"); ListChart(TimeLineController controller) { this.controller = controller; - getColumns().addAll(Arrays.asList(idColumn, iconColumn, millisColumn)); + setColumnResizePolicy(CONSTRAINED_RESIZE_POLICY); + getColumns().addAll(Arrays.asList(idColumn, millisColumn, iconColumn, descriptionColumn, baseTypeColumn, subTypeColumn, knownColumn)); - idColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue())); + setRowFactory(tableView -> new EventRow()); - millisColumn.setCellValueFactory(param -> { - return new SimpleObjectProperty<>(controller.getEventsModel().getEventById(param.getValue()).getStartMillis()); - }); + idColumn.setCellValueFactory(cellValueFactory); + idColumn.setSortable(false); + + millisColumn.setCellValueFactory(cellValueFactory); millisColumn.setCellFactory(col -> new EpochMillisCell()); - iconColumn.setCellValueFactory(param -> { - return new SimpleObjectProperty<>(controller.getEventsModel().getEventById(param.getValue()).getEventType().getFXImage()); - }); + millisColumn.setSortable(false); + + iconColumn.setCellValueFactory(cellValueFactory); iconColumn.setCellFactory(col -> new ImageCell()); + iconColumn.setSortable(false); - millisColumn.setSortType(TableColumn.SortType.DESCENDING); - millisColumn.setSortable(true); - millisColumn.setComparator(Long::compare); - getSortOrder().setAll(Arrays.asList(millisColumn)); + descriptionColumn.setCellValueFactory(cellValueFactory); + descriptionColumn.setCellFactory(col -> new DescriptionCell()); + descriptionColumn.setSortable(false); + baseTypeColumn.setCellValueFactory(cellValueFactory); + baseTypeColumn.setCellFactory(col -> new BaseTypeCell()); + baseTypeColumn.setSortable(false); + + subTypeColumn.setCellValueFactory(cellValueFactory); + subTypeColumn.setCellFactory(col -> new EventTypeCell()); + subTypeColumn.setSortable(false); + + knownColumn.setCellValueFactory(cellValueFactory); + knownColumn.setCellFactory(col -> new KnownCell()); + knownColumn.setSortable(false); } @Override @@ -105,15 +122,86 @@ class ListChart extends TableView implements TimeLineChart { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } - private static class ImageCell extends TableCell { + private static class ImageCell extends TableCell { @Override - protected void updateItem(Image item, boolean empty) { + protected void updateItem(Long item, boolean empty) { super.updateItem(item, empty); if (empty || item == null) { setGraphic(null); } else { - setGraphic(new ImageView(item)); + EventRow tableRow = (EventRow) getTableRow(); + if (tableRow != null) { + setGraphic(new ImageView(tableRow.getEvent().getEventType().getFXImage())); + } + } + } + } + + private static class DescriptionCell extends TableCell { + + @Override + protected void updateItem(Long item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setText(""); + } else { + EventRow tableRow = (EventRow) getTableRow(); + if (tableRow != null) { + setText(tableRow.getEvent().getDescription(DescriptionLoD.FULL)); + } + } + } + } + + private static class BaseTypeCell extends TableCell { + + @Override + protected void updateItem(Long item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setText(""); + } else { + EventRow tableRow = (EventRow) getTableRow(); + if (tableRow != null) { + setText(tableRow.getEvent().getEventType().getBaseType().getDisplayName()); + } + } + } + } + + private static class EventTypeCell extends TableCell { + + @Override + protected void updateItem(Long item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setText(""); + } else { + EventRow tableRow = (EventRow) getTableRow(); + if (tableRow != null) { + setText(tableRow.getEvent().getEventType().getDisplayName()); + } + } + } + } + + private static class KnownCell extends TableCell { + + @Override + protected void updateItem(Long item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setText(""); + } else { + EventRow tableRow = (EventRow) getTableRow(); + if (tableRow != null) { + setText(tableRow.getEvent().getKnown().getName()); + } } } } @@ -127,9 +215,32 @@ class ListChart extends TableView implements TimeLineChart { if (empty || item == null) { setText(""); } else { - setText(TimeLineController.getZonedFormatter().print(item)); + + EventRow tableRow = (EventRow) getTableRow(); + if (tableRow != null) { + setText(TimeLineController.getZonedFormatter().print(tableRow.getEvent().getStartMillis())); + } } } } + private class EventRow extends TableRow { + + private SingleEvent event; + + SingleEvent getEvent() { + return event; + } + + @Override + protected void updateItem(Long item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + event = null; + } else { + event = controller.getEventsModel().getEventById(item); + } + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java index 68b08506df..1bc0c5b088 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java @@ -5,7 +5,7 @@ */ package org.sleuthkit.autopsy.timeline.ui.listvew; -import java.util.Set; +import java.util.List; import javafx.concurrent.Task; import javafx.scene.Node; import javafx.scene.Parent; @@ -116,7 +116,7 @@ public class ListViewPane extends AbstractVisualizationPane eventIDs = eventsModel.getEventIDs(); + List eventIDs = eventsModel.getEventIDs(); getChart().getItems().setAll(eventIDs); // updateMessage(Bundle.DetailViewPane_loggedTask_updateUI());