cleanup in VisualizationPanel

This commit is contained in:
jmillman 2016-05-06 13:23:06 -04:00
parent 060c912336
commit 9c0d549c51

View File

@ -70,6 +70,7 @@ import org.joda.time.Interval;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.LoggedTask; import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.VisualizationMode; import org.sleuthkit.autopsy.timeline.VisualizationMode;
@ -88,10 +89,10 @@ import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo; import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
/** /**
* A container for an {@link AbstractVisualizationPane}, has a toolbar on top to * A container for an AbstractVisualizationPane, has a Toolbar on top to hold
* hold settings widgets supplied by contained {@link AbstAbstractVisualization} * settings widgets supplied by contained AbstractVisualizationPane and, the
* and, the histogram / time selection on bottom. Also supplies containers for * histogram / time selection on bottom. Also supplies containers for
* replacement axis to contained {@link AbstractAbstractVisualization} * replacement axis to contained AbstractVisualizationPane
* *
* TODO: refactor common code out of histogram and CountsView? -jm * TODO: refactor common code out of histogram and CountsView? -jm
*/ */
@ -101,22 +102,24 @@ final public class VisualizationPanel extends BorderPane {
private static final Image INFORMATION = new Image("org/sleuthkit/autopsy/timeline/images/information.png", 16, 16, true, true); // NON-NLS private static final Image INFORMATION = new Image("org/sleuthkit/autopsy/timeline/images/information.png", 16, 16, true, true); // NON-NLS
private static final Image REFRESH = new Image("org/sleuthkit/autopsy/timeline/images/arrow-circle-double-135.png"); // NON-NLS private static final Image REFRESH = new Image("org/sleuthkit/autopsy/timeline/images/arrow-circle-double-135.png"); // NON-NLS
private static final Background background = new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)); private static final Background GRAY_BACKGROUND = new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY));
@GuardedBy("this") @GuardedBy("this")
private LoggedTask<Void> histogramTask; private LoggedTask<Void> histogramTask;
private final EventsTree eventsTree; private final EventsTree eventsTree;
private AbstractVisualizationPane<?, ?, ?, ?> visualization; private AbstractVisualizationPane<?, ?, ?, ?> visualization;
//// range slider and histogram componenets
/** /*
* hbox that contains the histogram bars. //TODO: abstract this into a * HBox that contains the histogram bars.
* seperate class, and/or use a real bar chart? *
* //TODO: Abstract this into a seperate class, and/or use a real bar
* chart? -jm
*/ */
@FXML @FXML
private HBox histogramBox; private HBox histogramBox;
/** /*
* stack pane that superimposes rangeslider over histogram * Stack pane that superimposes rangeslider over histogram
*/ */
@FXML @FXML
private StackPane rangeHistogramStack; private StackPane rangeHistogramStack;
@ -126,7 +129,6 @@ final public class VisualizationPanel extends BorderPane {
//// time range selection components //// time range selection components
@FXML @FXML
private MenuButton zoomMenuButton; private MenuButton zoomMenuButton;
@FXML @FXML
private Button zoomOutButton; private Button zoomOutButton;
@FXML @FXML
@ -144,6 +146,8 @@ final public class VisualizationPanel extends BorderPane {
@FXML @FXML
private ToolBar toolBar; private ToolBar toolBar;
@FXML @FXML
private Label visualizationModeLabel;
@FXML
private ToggleButton countsToggle; private ToggleButton countsToggle;
@FXML @FXML
private ToggleButton detailsToggle; private ToggleButton detailsToggle;
@ -151,20 +155,25 @@ final public class VisualizationPanel extends BorderPane {
private Button snapShotButton; private Button snapShotButton;
@FXML @FXML
private Button refreshButton; private Button refreshButton;
@FXML
private Label visualizationModeLabel;
/** /*
* wraps contained visualization so that we can show notifications over it. * Wraps contained visualization so that we can show notifications over it.
*/ */
private final NotificationPane notificationPane = new NotificationPane(); private final NotificationPane notificationPane = new NotificationPane();
/*
* Boolean property that holds true if the visualziation 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 needsRefresh = new ReadOnlyBooleanWrapper(false); private final ReadOnlyBooleanWrapper needsRefresh = new ReadOnlyBooleanWrapper(false);
private final TimeLineController controller; private final TimeLineController controller;
private final FilteredEventsModel filteredEvents; private final FilteredEventsModel filteredEvents;
/** /**
* listen to change in range slider selected time and push to controller. * Listen to changes in the range slider selection and forward to the
* waits until the user releases thumb to send controller. * controller. Waits until the user releases thumb to send to controller.
*/ */
private final InvalidationListener rangeSliderListener = new InvalidationListener() { private final InvalidationListener rangeSliderListener = new InvalidationListener() {
@Override @Override
@ -197,20 +206,21 @@ final public class VisualizationPanel extends BorderPane {
private final InvalidationListener startListener = new PickerListener(() -> startPicker, Interval::withStartMillis); private final InvalidationListener startListener = new PickerListener(() -> startPicker, Interval::withStartMillis);
/** /**
* convert the given LocalDateTime to epoch millis USING THE CURERNT * Convert the given LocalDateTime to epoch millis USING THE CURRENT
* TIMEZONE FROM TIMELINECONTROLLER * TIMEZONE FROM THE TIMELINECONTROLLER
* *
* @param localDateTime * @param localDateTime The LocalDateTime to convert to millis since the
* Unix epoch.
* *
* @return the given localdatetime as epoch millis * @return the given LocalDateTime as epoch millis
*/ */
private static long localDateTimeToEpochMilli(LocalDateTime localDateTime) { private static long localDateTimeToEpochMilli(LocalDateTime localDateTime) {
return localDateTime.atZone(TimeLineController.getTimeZoneID()).toInstant().toEpochMilli(); return localDateTime.atZone(TimeLineController.getTimeZoneID()).toInstant().toEpochMilli();
} }
/** /**
* Convert the given epoch millis to a LocalDateTime USING THE CURERNT * Convert the given "millis from the Unix Epoch" to a LocalDateTime USING
* TIMEZONE FROM TIMELINECONTROLLER * THE CURRENT TIMEZONE FROM THE TIMELINECONTROLLER
* *
* @param millis The milliseconds to convert. * @param millis The milliseconds to convert.
* *
@ -220,6 +230,12 @@ final public class VisualizationPanel extends BorderPane {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), TimeLineController.getTimeZoneID()); return LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), TimeLineController.getTimeZoneID());
} }
/**
* Constructor
*
* @param controller The TimeLineController for this VisualizationPanel
* @param eventsTree The EventsTree this VisualizationPanel hosts.
*/
public VisualizationPanel(@Nonnull TimeLineController controller, @Nonnull EventsTree eventsTree) { public VisualizationPanel(@Nonnull TimeLineController controller, @Nonnull EventsTree eventsTree) {
this.controller = controller; this.controller = controller;
this.filteredEvents = controller.getEventsModel(); this.filteredEvents = controller.getEventsModel();
@ -227,14 +243,15 @@ final public class VisualizationPanel extends BorderPane {
FXMLConstructor.construct(this, "VisualizationPanel.fxml"); // NON-NLS FXMLConstructor.construct(this, "VisualizationPanel.fxml"); // NON-NLS
} }
@FXML // This method is called by the FXMLLoader when initialization is complete @FXML
@NbBundle.Messages({ @NbBundle.Messages({
"VisualizationPanel.visualizationModeLabel.text=Visualization Mode:", "VisualizationPanel.visualizationModeLabel.text=Visualization Mode:",
"VisualizationPanel.startLabel.text=Start:", "VisualizationPanel.startLabel.text=Start:",
"VisualizationPanel.endLabel.text=End:", "VisualizationPanel.endLabel.text=End:",
"VisualizationPanel.countsToggle.text=Counts", "VisualizationPanel.countsToggle.text=Counts",
"VisualizationPanel.detailsToggle.text=Details", "VisualizationPanel.detailsToggle.text=Details",
"VisualizationPanel.zoomMenuButton.text=Zoom in/out to"}) "VisualizationPanel.zoomMenuButton.text=Zoom in/out to",
"VisualizationPanel.tagsAddedOrDeleted=Tags have been created and/or deleted. The visualization may not be up to date."})
void initialize() { void initialize() {
assert endPicker != null : "fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS assert endPicker != null : "fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
assert histogramBox != null : "fx:id=\"histogramBox\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS assert histogramBox != null : "fx:id=\"histogramBox\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
@ -245,7 +262,15 @@ final public class VisualizationPanel extends BorderPane {
//configure notification pane //configure notification pane
notificationPane.getStyleClass().add(NotificationPane.STYLE_CLASS_DARK); notificationPane.getStyleClass().add(NotificationPane.STYLE_CLASS_DARK);
notificationPane.getActions().setAll(new Refresh());
setCenter(notificationPane); setCenter(notificationPane);
needsRefresh.addListener(observable -> {
if (needsRefresh.get()) {
notificationPane.show(Bundle.VisualizationPanel_tagsAddedOrDeleted(), new ImageView(INFORMATION));
} else {
notificationPane.hide();
}
});
//configure visualization mode toggle //configure visualization mode toggle
visualizationModeLabel.setText(Bundle.VisualizationPanel_visualizationModeLabel_text()); visualizationModeLabel.setText(Bundle.VisualizationPanel_visualizationModeLabel_text());
@ -354,13 +379,22 @@ final public class VisualizationPanel extends BorderPane {
} }
/**
* Set the given AbstractVisualizationPane as the one hosted by this
* VisualizationPanel.
*
* @param newViz The AbstractVisualizationPane to host.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.NOT_UI)
private synchronized void setVisualization(final AbstractVisualizationPane<?, ?, ?, ?> newViz) { private synchronized void setVisualization(final AbstractVisualizationPane<?, ?, ?, ?> newViz) {
Platform.runLater(() -> { Platform.runLater(() -> {
//clear out old vis.
if (visualization != null) { if (visualization != null) {
toolBar.getItems().removeAll(visualization.getSettingsNodes()); toolBar.getItems().removeAll(visualization.getSettingsNodes());
visualization.dispose(); visualization.dispose();
} }
//setup new vis.
visualization = newViz; visualization = newViz;
visualization.update(); visualization.update();
toolBar.getItems().addAll(newViz.getSettingsNodes()); toolBar.getItems().addAll(newViz.getSettingsNodes());
@ -374,7 +408,6 @@ final public class VisualizationPanel extends BorderPane {
} }
visualization.hasEvents.addListener((observable, oldValue, newValue) -> { visualization.hasEvents.addListener((observable, oldValue, newValue) -> {
if (newValue == false) { if (newValue == false) {
notificationPane.setContent( notificationPane.setContent(
new StackPane(visualization, new StackPane(visualization,
new Region() { new Region() {
@ -388,36 +421,55 @@ final public class VisualizationPanel extends BorderPane {
notificationPane.setContent(visualization); notificationPane.setContent(visualization);
} }
}); });
}); });
setNeedsRefresh(false); setNeedsRefresh(false);
} }
/**
* Handle TagsUpdatedEvents.
*
* Mark that the visualization needs to be refreshed.
*
* NOTE: This VisualizationPanel must be registered with the
* filteredEventsModel's EventBus in order for this handler to be invoked.
*
* @param event The TagsUpdatedEvent to handle.
*/
@Subscribe @Subscribe
public void handleTimeLineTagEvent(TagsUpdatedEvent event) { public void handleTimeLineTagEvent(TagsUpdatedEvent event) {
setNeedsRefresh(true); setNeedsRefresh(true);
} }
/**
* Handle RefreshRequestedEvent.
*
* Mark that the visualization has been refreshed.
*
* NOTE: This VisualizationPanel must be registered with the
* filteredEventsModel's EventBus in order for this handler to be invoked.
*
* @param event The TagsUpdatedEvent to handle.
*/
@Subscribe @Subscribe
public void handleRefreshRequestedEvent(RefreshRequestedEvent event) { public void handleRefreshRequestedEvent(RefreshRequestedEvent event) {
setNeedsRefresh(false); setNeedsRefresh(false);
} }
@NbBundle.Messages("VisualizationPanel.tagsAddedOrDeleted=Tags have been created and/or deleted. The visualization may not be up to date.") /**
* Set if the visualziation may not represent the current state of the DB,
* because, for example, tags have been updated.
*
* @param needsRefresh True if the visualization may not represent the
* current state of the DB.
*/
private void setNeedsRefresh(Boolean needsRefresh) { private void setNeedsRefresh(Boolean needsRefresh) {
Platform.runLater(() -> { Platform.runLater(() -> VisualizationPanel.this.needsRefresh.set(needsRefresh));
VisualizationPanel.this.needsRefresh.set(needsRefresh);
if (needsRefresh) {
notificationPane.getActions().setAll(new Refresh());
notificationPane.show(Bundle.VisualizationPanel_tagsAddedOrDeleted(), new ImageView(INFORMATION));
} else {
notificationPane.hide();
}
});
} }
/**
* Refresh the Histogram to represent the current state of the DB.
*/
synchronized private void refreshHistorgram() { synchronized private void refreshHistorgram() {
if (histogramTask != null) { if (histogramTask != null) {
histogramTask.cancel(true); histogramTask.cancel(true);
} }
@ -482,7 +534,7 @@ final public class VisualizationPanel extends BorderPane {
bar.prefHeightProperty().bind(histogramBox.heightProperty().multiply(Math.log(bin)).divide(fMax)); bar.prefHeightProperty().bind(histogramBox.heightProperty().multiply(Math.log(bin)).divide(fMax));
bar.setMaxHeight(USE_PREF_SIZE); bar.setMaxHeight(USE_PREF_SIZE);
bar.setMinHeight(USE_PREF_SIZE); bar.setMinHeight(USE_PREF_SIZE);
bar.setBackground(background); bar.setBackground(GRAY_BACKGROUND);
bar.setOnMouseEntered((MouseEvent event) -> { bar.setOnMouseEntered((MouseEvent event) -> {
Tooltip.install(bar, new Tooltip(bin.toString())); Tooltip.install(bar, new Tooltip(bin.toString()));
}); });
@ -664,12 +716,14 @@ final public class VisualizationPanel extends BorderPane {
} }
} }
/**
* Action that refreshes the Visualization.
*/
private class Refresh extends Action { private class Refresh extends Action {
@NbBundle.Messages({ @NbBundle.Messages({
"VisualizationPanel.refresh.text=Refresh", "VisualizationPanel.refresh.text=Refresh",
"VisualizationPanel.refresh.longText=Refresh the visualization to include information that is in the database but not visualized, such as newly updated tags."}) "VisualizationPanel.refresh.longText=Refresh the visualization to include information that is in the database but not visualized, such as newly updated tags."})
Refresh() { Refresh() {
super(Bundle.VisualizationPanel_refresh_text()); super(Bundle.VisualizationPanel_refresh_text());
setLongText(Bundle.VisualizationPanel_refresh_longText()); setLongText(Bundle.VisualizationPanel_refresh_longText());