split AbstractVisualizationPane into AbstractTimeLineView and AbstractTimelineChart

CountsViewPane and DetailViewPane extend AbstractTimelineChart, ListViewPane extends AbstractTimeLineView
This commit is contained in:
jmillman 2016-05-17 12:15:41 -04:00
parent b3c6f0a40c
commit 4d14c32a94
13 changed files with 452 additions and 473 deletions

View File

@ -0,0 +1,360 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.sleuthkit.autopsy.timeline.ui;
import com.google.common.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.concurrent.Task;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import org.controlsfx.control.MaskerPane;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
public abstract class AbstractTimeLineView extends BorderPane {
private static final Logger LOGGER = Logger.getLogger(AbstractTimeLineView.class.getName());
/**
* Boolean property that holds true if the visualization does not show any
* events with the current zoom and filter settings.
*/
private final ReadOnlyBooleanWrapper hasVisibleEvents = new ReadOnlyBooleanWrapper(true);
/**
* Boolean property that holds true if the visualization may not represent
* the current state of the DB, because, for example, tags have been updated
* but the vis. was not refreshed.
*/
private final ReadOnlyBooleanWrapper outOfDate = new ReadOnlyBooleanWrapper(false);
/**
* List of Nodes to insert into the toolbar. This should be set in an
* implementations constructor.
*/
private List<Node> settingsNodes;
/**
* Listener that is attached to various properties that should trigger a vis
* update when they change.
*/
private InvalidationListener updateListener = (Observable any) -> refresh();
/**
* task used to reload the content of this visualization
*/
private Task<Boolean> updateTask;
private final TimeLineController controller;
private final FilteredEventsModel filteredEvents;
/**
* Constructor
*
* @param controller
*/
public AbstractTimeLineView(TimeLineController controller) {
this.controller = controller;
this.filteredEvents = controller.getEventsModel();
this.filteredEvents.registerForEvents(this);
this.filteredEvents.zoomParametersProperty().addListener(updateListener);
TimeLineController.getTimeZone().addListener(updateListener);
}
/**
* Handle a RefreshRequestedEvent from the events model by updating the
* visualization.
*
* @param event The RefreshRequestedEvent to handle.
*/
@Subscribe
public void handleRefreshRequested(RefreshRequestedEvent event) {
refresh();
}
/**
* Does the visualization represent an out-of-date state of the DB. It might
* if, for example, tags have been updated but the vis. was not refreshed.
*
* @return True if the visualization does not represent the curent state of
* the DB.
*/
public boolean isOutOfDate() {
return outOfDate.get();
}
/**
* Get a ReadOnlyBooleanProperty that holds true if this visualization does
* not represent the current state of the DB>
*
* @return A ReadOnlyBooleanProperty that holds the out-of-date state for
* this visualization.
*/
public ReadOnlyBooleanProperty outOfDateProperty() {
return outOfDate.getReadOnlyProperty();
}
/**
* Get the TimelineController for this visualization.
*
* @return The TimelineController for this visualization.
*/
protected TimeLineController getController() {
return controller;
}
/**
* Refresh this visualization based on current state of zoom / filters.
* Primarily this invokes the background VisualizationUpdateTask returned by
* getUpdateTask(), which derived classes must implement.
*
* TODO: replace this logic with a javafx Service ? -jm
*/
protected final synchronized void refresh() {
if (updateTask != null) {
updateTask.cancel(true);
updateTask = null;
}
updateTask = getNewUpdateTask();
updateTask.stateProperty().addListener((Observable observable) -> {
switch (updateTask.getState()) {
case CANCELLED:
case FAILED:
case READY:
case RUNNING:
case SCHEDULED:
break;
case SUCCEEDED:
try {
this.hasVisibleEvents.set(updateTask.get());
} catch (InterruptedException | ExecutionException ex) {
LOGGER.log(Level.SEVERE, "Unexpected exception updating visualization", ex); //NON-NLS
}
break;
}
});
getController().monitorTask(updateTask);
}
/**
* Get the FilteredEventsModel for this visualization.
*
* @return The FilteredEventsModel for this visualization.
*/
protected FilteredEventsModel getEventsModel() {
return filteredEvents;
}
/**
* Get a new background Task that fetches the appropriate data and loads it
* into this visualization.
*
* @return A new task to execute on a background thread to reload this
* visualization with different data.
*/
protected abstract Task<Boolean> getNewUpdateTask();
/**
* Get a List of nodes containing settings widgets to insert into this
* visualization's header.
*
* @return The List of settings Nodes.
*/
protected List<Node> getSettingsNodes() {
return Collections.unmodifiableList(settingsNodes);
}
/**
* Set the List of nodes containing settings widgets to insert into this
* visualization's header.
*
*
* @param settingsNodes The List of nodes containing settings widgets to
* insert into this visualization's header.
*/
final protected void setSettingsNodes(List<Node> settingsNodes) {
this.settingsNodes = new ArrayList<>(settingsNodes);
}
/**
* Dispose of this visualization and any resources it holds onto.
*/
final synchronized void dispose() {
//cancel and gc updateTask
if (updateTask != null) {
updateTask.cancel(true);
updateTask = null;
}
//remvoe and gc updateListener
this.filteredEvents.zoomParametersProperty().removeListener(updateListener);
TimeLineController.getTimeZone().removeListener(updateListener);
updateListener = null;
filteredEvents.unRegisterForEvents(this);
}
/**
* Are there are any events visible in this visualization with the current
* view parameters?
*
* @return True if there are events visible in this visualization with the
* current view parameters.
*/
boolean hasVisibleEvents() {
return hasVisibleEventsProperty().get();
}
/**
* A property that indicates whether there are any events visible in this
* visualization with the current view parameters.
*
* @return A property that indicates whether there are any events visible in
* this visualization with the current view parameters.
*/
ReadOnlyBooleanProperty hasVisibleEventsProperty() {
return hasVisibleEvents.getReadOnlyProperty();
}
/**
* Set this visualization out of date because, for example, tags have been
* updated but the vis. was not refreshed.
*/
void setOutOfDate() {
outOfDate.set(true);
}
/**
* Clear all data items from this chart.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
abstract protected void clearData();
/**
* Base class for Tasks that refreshes a view when the view settings change.
*
* @param <AxisValuesType> The type of a single object that can represent
* the range of data displayed along the X-Axis.
*/
protected abstract class ViewRefreshTask<AxisValuesType> extends LoggedTask<Boolean> {
private final Node center;
/**
* Constructor
*
* @param taskName The name of this task.
* @param logStateChanges Whether or not task state changes should be
* logged.
*/
protected ViewRefreshTask(String taskName, boolean logStateChanges) {
super(taskName, logStateChanges);
this.center = getCenter();
}
/**
* Sets initial progress value and message and shows blocking progress
* indicator over the visualization. Derived Tasks should be sure to
* call this as part of their call() implementation.
*
* @return True
*
* @throws Exception If there is an unhandled exception during the
* background operation
*/
@NbBundle.Messages(value = {"VisualizationUpdateTask.preparing=Analyzing zoom and filter settings"})
@Override
protected Boolean call() throws Exception {
updateProgress(-1, 1);
updateMessage(Bundle.VisualizationUpdateTask_preparing());
Platform.runLater(() -> {
MaskerPane maskerPane = new MaskerPane();
maskerPane.textProperty().bind(messageProperty());
maskerPane.progressProperty().bind(progressProperty());
setCenter(new StackPane(center, maskerPane));
setCursor(Cursor.WAIT);
});
return true;
}
/**
* Updates the horizontal axis and removes the blocking progress
* indicator. Derived Tasks should be sure to call this as part of their
* succeeded() implementation.
*/
@Override
protected void succeeded() {
super.succeeded();
outOfDate.set(false);
cleanup();
}
/**
* Removes the blocking progress indicator. Derived Tasks should be sure
* to call this as part of their cancelled() implementation.
*/
@Override
protected void cancelled() {
super.cancelled();
cleanup();
}
/**
* Removes the blocking progress indicator. Derived Tasks should be sure
* to call this as part of their failed() implementation.
*/
@Override
protected void failed() {
super.failed();
cleanup();
}
/**
* Removes the blocking progress indicator and reset the cursor to the
* default.
*/
private void cleanup() {
setCenter(center); //clear masker pane installed in call()
setCursor(Cursor.DEFAULT);
}
/**
* Set the horizontal range that this chart will show.
*
* @param values A single object representing the range that this chart
* will show.
*/
protected abstract void setDateValues(AxisValuesType values);
/**
* Clears the chart data and sets the horizontal axis range. For use
* within the derived implementation of the call() method.
*
* @param axisValues
*/
@ThreadConfined(type = ThreadConfined.ThreadType.NOT_UI)
protected void resetView(AxisValuesType axisValues) {
Platform.runLater(() -> {
clearData();
setDateValues(axisValues);
});
}
}
}

View File

@ -18,27 +18,14 @@
*/
package org.sleuthkit.autopsy.timeline.ui;
import com.google.common.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.concurrent.Task;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.chart.Axis;
import javafx.scene.chart.XYChart;
@ -46,14 +33,12 @@ import javafx.scene.control.Label;
import javafx.scene.control.OverrunStyle;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
@ -61,15 +46,11 @@ import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javax.annotation.concurrent.Immutable;
import org.apache.commons.lang3.StringUtils;
import org.controlsfx.control.MaskerPane;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
/**
* Abstract base class for TimeLineChart based visualizations.
@ -85,9 +66,9 @@ import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
*
* 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 implements TimeLineView {
public abstract class AbstractTimelineChart<X, Y, NodeType extends Node, ChartType extends Region & TimeLineChart<X>> extends AbstractTimeLineView {
private static final Logger LOGGER = Logger.getLogger(AbstractVisualizationPane.class.getName());
private static final Logger LOGGER = Logger.getLogger(AbstractTimelineChart.class.getName());
@NbBundle.Messages("AbstractVisualization.Default_Tooltip.text=Drag the mouse to select a time interval to zoom into.\nRight-click for more actions.")
private static final Tooltip DEFAULT_TOOLTIP = new Tooltip(Bundle.AbstractVisualization_Default_Tooltip_text());
@ -99,22 +80,19 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
*
* @return The default Tooltip.
*/
public static Tooltip getDefaultTooltip() {
static public Tooltip getDefaultTooltip() {
return DEFAULT_TOOLTIP;
}
/**
* Boolean property that holds true if the visualization may not represent
* the current state of the DB, because, for example, tags have been updated
* but the vis. was not refreshed.
* The visualization nodes that are selected.
*
* @return An ObservableList<NodeType> of the nodes that are selected in
* this visualization.
*/
private final ReadOnlyBooleanWrapper outOfDate = new ReadOnlyBooleanWrapper(false);
/**
* Boolean property that holds true if the visualization does not show any
* events with the current zoom and filter settings.
*/
private final ReadOnlyBooleanWrapper hasVisibleEvents = new ReadOnlyBooleanWrapper(true);
protected ObservableList<NodeType> getSelectedNodes() {
return selectedNodes;
}
/**
* Access to chart data via series
@ -127,54 +105,11 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
//// replacement axis label componenets
private final Pane specificLabelPane = new Pane(); // container for the specfic labels in the decluttered axis
private final Pane contextLabelPane = new Pane();// container for the contextual labels in the decluttered axis
// container for the contextual labels in the decluttered axis
private final Region spacer = new Region();
/**
* task used to reload the content of this visualization
*/
private Task<Boolean> updateTask;
final private TimeLineController controller;
final private FilteredEventsModel filteredEvents;
final private ObservableList<NodeType> selectedNodes = FXCollections.observableArrayList();
/**
* Listener that is attached to various properties that should trigger a vis
* update when they change.
*/
private InvalidationListener updateListener = any -> refresh();
/**
* Does the visualization represent an out-of-date state of the DB. It might
* if, for example, tags have been updated but the vis. was not refreshed.
*
* @return True if the visualization does not represent the curent state of
* the DB.
*/
public boolean isOutOfDate() {
return outOfDate.get();
}
/**
* Set this visualization out of date because, for example, tags have been
* updated but the vis. was not refreshed.
*/
void setOutOfDate() {
outOfDate.set(true);
}
/**
* Get a ReadOnlyBooleanProperty that holds true if this visualization does
* not represent the current state of the DB>
*
* @return A ReadOnlyBooleanProperty that holds the out-of-date state for
* this visualization.
*/
public ReadOnlyBooleanProperty outOfDateProperty() {
return outOfDate.getReadOnlyProperty();
}
public Pane getSpecificLabelPane() {
return specificLabelPane;
}
@ -187,53 +122,6 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
return spacer;
}
/**
* The visualization nodes that are selected.
*
* @return An ObservableList<NodeType> of the nodes that are selected in
* this visualization.
*/
protected ObservableList<NodeType> getSelectedNodes() {
return selectedNodes;
}
/**
* List of Nodes to insert into the toolbar. This should be set in an
* implementations constructor.
*/
private List<Node> settingsNodes;
/**
* Get a List of nodes containing settings widgets to insert into this
* visualization's header.
*
* @return The List of settings Nodes.
*/
protected List<Node> getSettingsNodes() {
return Collections.unmodifiableList(settingsNodes);
}
/**
* Set the List of nodes containing settings widgets to insert into this
* visualization's header.
*
*
* @param settingsNodes The List of nodes containing settings widgets to
* insert into this visualization's header.
*/
protected void setSettingsNodes(List<Node> settingsNodes) {
this.settingsNodes = new ArrayList<>(settingsNodes);
}
/**
* Get the TimelineController for this visualization.
*
* @return The TimelineController for this visualization.
*/
protected TimeLineController getController() {
return controller;
}
/**
* Get the CharType that implements this visualization.
*
@ -243,15 +131,6 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
return chart;
}
/**
* Get the FilteredEventsModel for this visualization.
*
* @return The FilteredEventsModel for this visualization.
*/
protected FilteredEventsModel getEventsModel() {
return filteredEvents;
}
/**
* Set the ChartType that implements this visualization.
*
@ -263,28 +142,6 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
setCenter(chart);
}
/**
* A property that indicates whether there are any events visible in this
* visualization with the current view parameters.
*
* @return A property that indicates whether there are any events visible in
* this visualization with the current view parameters.
*/
ReadOnlyBooleanProperty hasVisibleEventsProperty() {
return hasVisibleEvents.getReadOnlyProperty();
}
/**
* Are there are any events visible in this visualization with the current
* view parameters?
*
* @return True if there are events visible in this visualization with the
* current view parameters.
*/
boolean hasVisibleEvents() {
return hasVisibleEventsProperty().get();
}
/**
* Apply this visualization's 'selection effect' to the given node.
*
@ -324,15 +181,6 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
*/
abstract protected void applySelectionEffect(NodeType node, Boolean applied);
/**
* Get a new background Task that fetches the appropriate data and loads it
* into this visualization.
*
* @return A new task to execute on a background thread to reload this
* visualization with different data.
*/
abstract protected Task<Boolean> getNewUpdateTask();
/**
* Get the label that should be used for a tick mark at the given value.
*
@ -373,74 +221,6 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
*/
abstract protected double getAxisMargin();
/**
* Clear all data items from this chart.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
abstract protected void clearChartData();
/**
* Refresh this visualization based on current state of zoom / filters.
* Primarily this invokes the background VisualizationUpdateTask returned by
* getUpdateTask(), which derived classes must implement.
*
* TODO: replace this logic with a javafx Service ? -jm
*/
protected final synchronized void refresh() {
if (updateTask != null) {
updateTask.cancel(true);
updateTask = null;
}
updateTask = getNewUpdateTask();
updateTask.stateProperty().addListener((Observable observable) -> {
switch (updateTask.getState()) {
case CANCELLED:
case FAILED:
case READY:
case RUNNING:
case SCHEDULED:
break;
case SUCCEEDED:
try {
this.hasVisibleEvents.set(updateTask.get());
} catch (InterruptedException | ExecutionException ex) {
LOGGER.log(Level.SEVERE, "Unexpected exception updating visualization", ex); //NON-NLS
}
break;
}
});
controller.monitorTask(updateTask);
}
/**
* Handle a RefreshRequestedEvent from the events model by updating the
* visualization.
*
* @param event The RefreshRequestedEvent to handle.
*/
@Subscribe
public void handleRefreshRequested(RefreshRequestedEvent event) {
refresh();
}
/**
* Dispose of this visualization and any resources it holds onto.
*/
final synchronized void dispose() {
//cancel and gc updateTask
if (updateTask != null) {
updateTask.cancel(true);
updateTask = null;
}
//remvoe and gc updateListener
this.filteredEvents.zoomParametersProperty().removeListener(updateListener);
TimeLineController.getTimeZone().removeListener(updateListener);
updateListener = null;
filteredEvents.unRegisterForEvents(this);
}
/**
* Make a series for each event type in a consistent order.
*/
@ -470,23 +250,8 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
*
* @param controller The TimelineController for this visualization.
*/
protected AbstractVisualizationPane(TimeLineController controller) {
this.controller = controller;
this.filteredEvents = controller.getEventsModel();
this.filteredEvents.registerForEvents(this);
this.filteredEvents.zoomParametersProperty().addListener(updateListener);
// Platform.runLater(() -> {
// VBox vBox = new VBox(specificLabelPane, contextLabelPane);
// vBox.setFillWidth(false);
// HBox hBox = new HBox(spacer, vBox);
// hBox.setFillHeight(false);
// setBottom(hBox);
// DoubleBinding spacerSize = getYAxis().widthProperty().add(getYAxis().tickLengthProperty()).add(getAxisMargin());
// spacer.minWidthProperty().bind(spacerSize);
// spacer.prefWidthProperty().bind(spacerSize);
// spacer.maxWidthProperty().bind(spacerSize);
// });
protected AbstractTimelineChart(TimeLineController controller) {
super(controller);
createSeries();
selectedNodes.addListener((ListChangeListener.Change<? extends NodeType> change) -> {
@ -496,10 +261,8 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
}
});
TimeLineController.getTimeZone().addListener(updateListener);
//show tooltip text in status bar
hoverProperty().addListener(hoverProp -> controller.setStatusMessage(isHover() ? DEFAULT_TOOLTIP.getText() : ""));
hoverProperty().addListener(hoverProp -> controller.setStatusMessage(isHover() ? getDefaultTooltip().getText() : ""));
}
@ -690,116 +453,4 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
}
}
/**
* Base class for Tasks that refresh a visualization when the view settings
* change.
*
* @param <AxisValuesType> The type of a single object that can represent
* the range of data displayed along the X-Axis.
*/
abstract protected class VisualizationRefreshTask<AxisValuesType> extends LoggedTask<Boolean> {
private final Node center;
/**
* Constructor
*
* @param taskName The name of this task.
* @param logStateChanges Whether or not task state changes should be
* logged.
*/
protected VisualizationRefreshTask(String taskName, boolean logStateChanges) {
super(taskName, logStateChanges);
this.center = getCenter();
}
/**
* Sets initial progress value and message and shows blocking progress
* indicator over the visualization. Derived Tasks should be sure to
* call this as part of their call() implementation.
*
* @return True
*
* @throws Exception If there is an unhandled exception during the
* background operation
*/
@NbBundle.Messages({"VisualizationUpdateTask.preparing=Analyzing zoom and filter settings"})
@Override
protected Boolean call() throws Exception {
updateProgress(-1, 1);
updateMessage(Bundle.VisualizationUpdateTask_preparing());
Platform.runLater(() -> {
MaskerPane maskerPane = new MaskerPane();
maskerPane.textProperty().bind(messageProperty());
maskerPane.progressProperty().bind(progressProperty());
setCenter(new StackPane(center, maskerPane));
setCursor(Cursor.WAIT);
});
return true;
}
/**
* Updates the horizontal axis and removes the blocking progress
* indicator. Derived Tasks should be sure to call this as part of their
* succeeded() implementation.
*/
@Override
protected void succeeded() {
super.succeeded();
outOfDate.set(false);
cleanup();
}
/**
* Removes the blocking progress indicator. Derived Tasks should be sure
* to call this as part of their cancelled() implementation.
*/
@Override
protected void cancelled() {
super.cancelled();
cleanup();
}
/**
* Removes the blocking progress indicator. Derived Tasks should be sure
* to call this as part of their failed() implementation.
*/
@Override
protected void failed() {
super.failed();
cleanup();
}
/**
* Removes the blocking progress indicator and reset the cursor to the
* default.
*/
private void cleanup() {
setCenter(center); //clear masker pane installed in call()
setCursor(Cursor.DEFAULT);
}
/**
* Clears the chart data and sets the horizontal axis range. For use
* within the derived implementation of the call() method.
*
* @param axisValues
*/
@ThreadConfined(type = ThreadConfined.ThreadType.NOT_UI)
protected void resetChart(AxisValuesType axisValues) {
Platform.runLater(() -> {
clearChartData();
setDateAxisValues(axisValues);
});
}
/**
* Set the horizontal range that this chart will show.
*
* @param values A single object representing the range that this chart
* will show.
*/
abstract protected void setDateAxisValues(AxisValuesType values);
}
}

View File

@ -1,14 +0,0 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.sleuthkit.autopsy.timeline.ui;
/**
*
* @author jmillman
*/
public interface TimeLineView {
}

View File

@ -121,7 +121,7 @@ final public class VisualizationPanel extends BorderPane {
private LoggedTask<Void> histogramTask;
private final EventsTree eventsTree;
private AbstractVisualizationPane<?, ?, ?, ?> visualization;
private AbstractTimeLineView visualization;
/*
* HBox that contains the histogram bars.
@ -580,7 +580,7 @@ final public class VisualizationPanel extends BorderPane {
* AbstractVislualization for one of the correct type.
*/
private void syncVisualizationMode() {
AbstractVisualizationPane<?, ?, ?, ?> vizPane;
AbstractTimeLineView vizPane;
VisualizationMode visMode = controller.visualizationModeProperty().get();
//make new visualization.

View File

@ -60,7 +60,7 @@ import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
import org.sleuthkit.autopsy.timeline.ui.AbstractTimelineChart;
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
/**
@ -79,7 +79,7 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
* Platform.runLater(java.lang.Runnable). The FilteredEventsModel should
* encapsulate all need synchronization internally.
*/
public class CountsViewPane extends AbstractVisualizationPane<String, Number, Node, EventCountsChart> {
public class CountsViewPane extends AbstractTimelineChart<String, Number, Node, EventCountsChart> {
private static final Logger LOGGER = Logger.getLogger(CountsViewPane.class.getName());
@ -170,7 +170,7 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
@Override
protected void clearChartData() {
protected void clearData() {
for (XYChart.Series<String, Number> series : dataSeries) {
series.getData().clear();
}
@ -349,7 +349,7 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
@NbBundle.Messages({
"CountsViewPane.loggedTask.name=Updating Counts View",
"CountsViewPane.loggedTask.updatingCounts=Populating visualization"})
private class CountsUpdateTask extends VisualizationRefreshTask<List<String>> {
private class CountsUpdateTask extends ViewRefreshTask<List<String>> {
CountsUpdateTask() {
super(Bundle.CountsViewPane_loggedTask_name(), true);
@ -374,7 +374,7 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
List<Interval> intervals = rangeInfo.getIntervals();
//clear old data, and reset ranges and series
resetChart(Lists.transform(intervals, rangeInfo::formatForTick));
resetView(Lists.transform(intervals, rangeInfo::formatForTick));
updateMessage(Bundle.CountsViewPane_loggedTask_updatingCounts());
int chartMax = 0;
@ -432,7 +432,7 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
}
@Override
protected void setDateAxisValues(List<String> categories) {
protected void setDateValues(List<String> categories) {
dateAxis.getCategories().setAll(categories);
}
}

View File

@ -56,7 +56,7 @@ import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
import org.sleuthkit.autopsy.timeline.ui.AbstractTimelineChart;
import org.sleuthkit.autopsy.timeline.utils.MappedList;
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
@ -75,7 +75,7 @@ import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
* grouped EventStripes, etc, etc. The leaves of the trees are EventClusters or
* SingleEvents.
*/
public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStripe, EventNodeBase<?>, DetailsChart> {
public class DetailViewPane extends AbstractTimelineChart<DateTime, EventStripe, EventNodeBase<?>, DetailsChart> {
private final static Logger LOGGER = Logger.getLogger(DetailViewPane.class.getName());
@ -218,7 +218,7 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStr
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
@Override
protected void clearChartData() {
protected void clearData() {
getChart().reset();
}
@ -365,9 +365,8 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStr
"DetailViewPane.loggedTask.continueButton=Continue",
"DetailViewPane.loggedTask.backButton=Back (Cancel)",
"# {0} - number of events",
"DetailViewPane.loggedTask.prompt=You are about to show details for {0} events. This might be very slow and could exhaust available memory.\n\nDo you want to continue?"})
private class DetailsUpdateTask extends VisualizationRefreshTask<Interval> {
private class DetailsUpdateTask extends ViewRefreshTask<Interval> {
DetailsUpdateTask() {
super(Bundle.DetailViewPane_loggedTask_name(), true);
@ -423,7 +422,7 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStr
currentZoomParams = newZoomParams;
//clear the chart and set the horixontal axis
resetChart(eventsModel.getTimeRange());
resetView(eventsModel.getTimeRange());
updateMessage(Bundle.DetailViewPane_loggedTask_updateUI());
@ -447,7 +446,7 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStr
}
@Override
protected void setDateAxisValues(Interval timeRange) {
protected void setDateValues(Interval timeRange) {
detailsChartDateAxis.setRange(timeRange, true);
pinnedDateAxis.setRange(timeRange, true);
}

View File

@ -32,6 +32,7 @@ import javafx.scene.control.ContextMenu;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.scene.control.SkinBase;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
@ -216,6 +217,10 @@ final class DetailsChart extends Control implements TimeLineChart<DateTime> {
return nestedEvents;
}
Tooltip getDefaultTooltip() {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
/**
* Clear any time based UI elements (GuideLines, IntervalSelector,...) from
* this chart.

View File

@ -58,7 +58,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
import org.sleuthkit.autopsy.timeline.ui.AbstractTimelineChart;
import org.sleuthkit.autopsy.timeline.ui.ContextMenuProvider;
/**
@ -178,7 +178,7 @@ abstract class DetailsChartLane<Y extends TimeLineEvent> extends XYChart<DateTim
//add a dummy series or the chart is never rendered
setData(FXCollections.observableList(Arrays.asList(new Series<DateTime, Y>())));
Tooltip.install(this, AbstractVisualizationPane.getDefaultTooltip());
Tooltip.install(this, AbstractTimelineChart.getDefaultTooltip());
dateAxis.setAutoRanging(false);
setLegendVisible(false);

View File

@ -77,7 +77,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.events.TagsAddedEvent;
import org.sleuthkit.autopsy.timeline.events.TagsDeletedEvent;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
import org.sleuthkit.autopsy.timeline.ui.AbstractTimelineChart;
import org.sleuthkit.autopsy.timeline.ui.ContextMenuProvider;
import static org.sleuthkit.autopsy.timeline.ui.detailview.EventNodeBase.show;
import static org.sleuthkit.autopsy.timeline.ui.detailview.MultiEventNodeBase.CORNER_RADII_3;
@ -167,7 +167,7 @@ public abstract class EventNodeBase<Type extends TimeLineEvent> extends StackPan
//set up mouse hover effect and tooltip
setOnMouseEntered(mouseEntered -> {
Tooltip.uninstall(chartLane, AbstractVisualizationPane.getDefaultTooltip());
Tooltip.uninstall(chartLane, AbstractTimelineChart.getDefaultTooltip());
showHoverControls(true);
toFront();
});
@ -176,7 +176,7 @@ public abstract class EventNodeBase<Type extends TimeLineEvent> extends StackPan
if (parentNode != null) {
parentNode.showHoverControls(true);
} else {
Tooltip.install(chartLane, AbstractVisualizationPane.getDefaultTooltip());
Tooltip.install(chartLane, AbstractTimelineChart.getDefaultTooltip());
}
});
setOnMouseClicked(new ClickHandler());

View File

@ -25,7 +25,6 @@ import javafx.scene.shape.Line;
import org.joda.time.DateTime;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
/**
* Subclass of {@link Line} with appropriate behavior (mouse listeners) to act
@ -35,7 +34,7 @@ import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
"GuideLine.tooltip.text={0}\nRight-click to remove.\nDrag to reposition."})
class GuideLine extends Line {
private static final Tooltip CHART_DEFAULT_TOOLTIP = AbstractVisualizationPane.getDefaultTooltip();
private final Tooltip CHART_DEFAULT_TOOLTIP ;
private final Tooltip tooltip = new Tooltip();
private final DetailsChart chart;
@ -49,6 +48,7 @@ class GuideLine extends Line {
*/
GuideLine(DetailsChart chart) {
super(0, 0, 0, 0);
CHART_DEFAULT_TOOLTIP = chart.getDefaultTooltip();
this.chart = chart;
Axis<DateTime> xAxis = chart.getXAxis();
endYProperty().bind(chart.heightProperty().subtract(xAxis.heightProperty().subtract(xAxis.tickLengthProperty())));

View File

@ -5,7 +5,7 @@
*/
package org.sleuthkit.autopsy.timeline.ui.listvew;
import java.util.List;
import java.util.Collection;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
@ -43,16 +43,30 @@ class ListChart extends BorderPane implements TimeLineChart<Long> {
@FXML
private TableView<Long> table;
Callback<TableColumn.CellDataFeatures<Long, Long>, ObservableValue<Long>> cellValueFactory = param -> new SimpleObjectProperty<>(param.getValue());
private static final Callback<TableColumn.CellDataFeatures<Long, Long>, ObservableValue<Long>> CELL_VALUE_FACTORY = param -> new SimpleObjectProperty<>(param.getValue());
private final TimeLineController controller;
private final TableColumn<Long, Long> idColumn = new TableColumn<>("Event ID");
private final TableColumn<Long, Long> millisColumn = new TableColumn<>("Date/Time");
private final TableColumn<Long, Long> iconColumn = new TableColumn<>("Icon");
private final TableColumn<Long, Long> descriptionColumn = new TableColumn<>("Description");
private final TableColumn<Long, Long> baseTypeColumn = new TableColumn<>("Base Type");
private final TableColumn<Long, Long> subTypeColumn = new TableColumn<>("Sub Type");
private final TableColumn<Long, Long> knownColumn = new TableColumn<>("Known");
@FXML
private TableColumn<Long, Long> idColumn;
@FXML
private TableColumn<Long, Long> millisColumn;
@FXML
private TableColumn<Long, Long> iconColumn;
@FXML
private TableColumn<Long, Long> descriptionColumn;
@FXML
private TableColumn<Long, Long> baseTypeColumn;
@FXML
private TableColumn<Long, Long> subTypeColumn;
@FXML
private TableColumn<Long, Long> knownColumn;
ListChart(TimeLineController controller) {
this.controller = controller;
@ -71,26 +85,27 @@ class ListChart extends BorderPane implements TimeLineChart<Long> {
assert subTypeColumn != null : "fx:id=\"subTypeColumn\" was not injected: check your FXML file 'ListViewPane.fxml'.";
assert knownColumn != null : "fx:id=\"knownColumn\" was not injected: check your FXML file 'ListViewPane.fxml'.";
// setRowFactory(tableView -> new EventRow());
idColumn.setCellValueFactory(cellValueFactory);
table.setRowFactory(tableView -> new EventRow());
idColumn.setCellValueFactory(CELL_VALUE_FACTORY);
millisColumn.setCellValueFactory(cellValueFactory);
millisColumn.setCellValueFactory(CELL_VALUE_FACTORY);
millisColumn.setCellFactory(col -> new EpochMillisCell());
iconColumn.setCellValueFactory(cellValueFactory);
iconColumn.setCellValueFactory(CELL_VALUE_FACTORY);
iconColumn.setCellFactory(col -> new ImageCell());
descriptionColumn.setCellValueFactory(cellValueFactory);
descriptionColumn.setCellValueFactory(CELL_VALUE_FACTORY);
descriptionColumn.setCellFactory(col -> new DescriptionCell());
baseTypeColumn.setCellValueFactory(cellValueFactory);
baseTypeColumn.setCellValueFactory(CELL_VALUE_FACTORY);
baseTypeColumn.setCellFactory(col -> new BaseTypeCell());
subTypeColumn.setCellValueFactory(cellValueFactory);
subTypeColumn.setCellValueFactory(CELL_VALUE_FACTORY);
subTypeColumn.setCellFactory(col -> new EventTypeCell());
knownColumn.setCellValueFactory(cellValueFactory);
knownColumn.setCellValueFactory(CELL_VALUE_FACTORY);
knownColumn.setCellFactory(col -> new KnownCell());
eventCountLabel.textProperty().bind(Bindings.size(table.getItems()).asString().concat(" events"));
}
@ -146,7 +161,7 @@ class ListChart extends BorderPane implements TimeLineChart<Long> {
}
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
void setEventIDs(List<Long> eventIDs) {
void setEventIDs(Collection<Long> eventIDs) {
table.getItems().setAll(eventIDs);
}

View File

@ -8,7 +8,7 @@
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<fx:root maxHeight="-Infinity" minHeight="-Infinity" minWidth="-Infinity" type="BorderPane" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<fx:root type="BorderPane" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<top>
<HBox alignment="CENTER" BorderPane.alignment="CENTER">
<children>
@ -23,13 +23,13 @@
<center>
<TableView fx:id="table" tableMenuButtonVisible="true" BorderPane.alignment="CENTER">
<columns>
<TableColumn fx:id="idColumn" editable="false" maxWidth="200.0" prefWidth="75.0" sortable="false" text="ID" />
<TableColumn fx:id="millisColumn" editable="false" maxWidth="300.0" minWidth="150.0" prefWidth="300.0" resizable="false" sortable="false" text="Date/Time" />
<TableColumn fx:id="millisColumn" editable="false" maxWidth="200.0" minWidth="150.0" prefWidth="150.0" resizable="false" sortable="false" text="Date/Time" />
<TableColumn fx:id="iconColumn" editable="false" maxWidth="36.0" minWidth="36.0" prefWidth="36.0" resizable="false" sortable="false" text="Icon" />
<TableColumn fx:id="descriptionColumn" editable="false" maxWidth="1000.0" minWidth="100.0" prefWidth="200.0" sortable="false" text="Description" />
<TableColumn fx:id="descriptionColumn" editable="false" maxWidth="1000.0" minWidth="100.0" prefWidth="300.0" sortable="false" text="Description" />
<TableColumn fx:id="baseTypeColumn" editable="false" maxWidth="75.0" minWidth="75.0" prefWidth="75.0" sortable="false" text="BaseType" />
<TableColumn fx:id="subTypeColumn" editable="false" maxWidth="75.0" minWidth="75.0" prefWidth="75.0" sortable="false" text="SubType" />
<TableColumn fx:id="knownColumn" editable="false" maxWidth="100.0" minWidth="75.0" prefWidth="75.0" sortable="false" text="Known" />
<TableColumn fx:id="subTypeColumn" editable="false" maxWidth="100.0" minWidth="100.0" prefWidth="100.0" sortable="false" text="SubType" />
<TableColumn fx:id="knownColumn" editable="false" maxWidth="75.0" minWidth="75.0" prefWidth="75.0" sortable="false" text="Known" />
<TableColumn fx:id="idColumn" editable="false" maxWidth="150.0" minWidth="50.0" prefWidth="75.0" sortable="false" text="ID" />
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />

View File

@ -8,15 +8,11 @@ package org.sleuthkit.autopsy.timeline.ui.listvew;
import java.util.List;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.chart.Axis;
import org.joda.time.Interval;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
import org.sleuthkit.autopsy.timeline.ui.TimeLineView;
import org.sleuthkit.autopsy.timeline.ui.AbstractTimeLineView;
/**
* @param <X> The type of data plotted along the x axis
@ -33,7 +29,9 @@ import org.sleuthkit.autopsy.timeline.ui.TimeLineView;
* public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node,
* ChartType extends Region & TimeLineChart<X>> extends BorderPane {
*/
public class ListViewPane extends AbstractVisualizationPane<Long, SingleEvent, Node, ListChart> implements TimeLineView {
public class ListViewPane extends AbstractTimeLineView {
private final ListChart listChart;
/**
* Constructor
@ -42,55 +40,21 @@ public class ListViewPane extends AbstractVisualizationPane<Long, SingleEvent, N
*/
public ListViewPane(TimeLineController controller) {
super(controller);
listChart = new ListChart(controller);
//initialize chart;
setChart(new ListChart(controller));
setCenter(listChart);
setSettingsNodes(new ListViewPane.ListViewSettingsPane().getChildrenUnmodifiable());
}
@Override
protected Boolean isTickBold(Long value) {
return true;
}
@Override
protected void applySelectionEffect(Node node, Boolean applied) {
// throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
protected Task<Boolean> getNewUpdateTask() {
return new ListUpdateTask();
}
@Override
protected String getTickMarkLabel(Long tickValue) {
return "";
}
@Override
protected double getTickSpacing() {
return 0;
}
@Override
protected Axis<Long> getXAxis() {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
protected Axis<SingleEvent> getYAxis() {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
protected double getAxisMargin() {
return 0;
}
@Override
protected void clearChartData() {
getChart().clear();
protected void clearData() {
listChart.clear();
}
private static class ListViewSettingsPane extends Parent {
@ -99,7 +63,7 @@ public class ListViewPane extends AbstractVisualizationPane<Long, SingleEvent, N
}
}
private class ListUpdateTask extends VisualizationRefreshTask<Interval> {
private class ListUpdateTask extends ViewRefreshTask<Interval> {
ListUpdateTask() {
super("List update task", true);
@ -114,12 +78,12 @@ public class ListViewPane extends AbstractVisualizationPane<Long, SingleEvent, N
FilteredEventsModel eventsModel = getEventsModel();
//clear the chart and set the horixontal axis
resetChart(eventsModel.getTimeRange());
resetView(eventsModel.getTimeRange());
updateMessage("Querying db for events");
//get the event stripes to be displayed
List<Long> eventIDs = eventsModel.getEventIDs();
Platform.runLater(() -> getChart().setEventIDs(eventIDs));
Platform.runLater(() -> listChart.setEventIDs(eventIDs));
updateMessage("updating ui");
return eventIDs.isEmpty() == false;
@ -132,9 +96,8 @@ public class ListViewPane extends AbstractVisualizationPane<Long, SingleEvent, N
}
@Override
protected void setDateAxisValues(Interval timeRange) {
// detailsChartDateAxis.setRange(timeRange, true);
// pinnedDateAxis.setRange(timeRange, true);
}
protected void setDateValues(Interval timeRange) {
}
}
}