cleanup, comments

This commit is contained in:
jmillman 2015-12-01 16:07:58 -05:00
parent 67dbaa4cdc
commit 9cd5800ce7
5 changed files with 159 additions and 104 deletions

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2014 Basis Technology Corp. * Copyright 2014-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -33,6 +33,7 @@ import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.concurrent.Service;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Cursor; import javafx.scene.Cursor;
@ -75,7 +76,7 @@ import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
* @param <Y> the type of data plotted along the y axis * @param <Y> the type of data plotted along the y axis
* @param <NodeType> the type of nodes used to represent data items * @param <NodeType> the type of nodes used to represent data items
* @param <ChartType> the type of the {@link XYChart<X,Y>} this class uses to * @param <ChartType> the type of the {@link XYChart<X,Y>} this class uses to
* plot the * data. * plot the data.
* *
* TODO: this is becoming (too?) closely tied to the notion that their is a * TODO: this is becoming (too?) closely tied to the notion that their is a
* {@link XYChart} doing the rendering. Is this a good idea? -jm TODO: pull up * {@link XYChart} doing the rendering. Is this a good idea? -jm TODO: pull up
@ -97,16 +98,13 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
* access to chart data via series * access to chart data via series
*/ */
protected final ObservableList<XYChart.Series<X, Y>> dataSeries = FXCollections.<XYChart.Series<X, Y>>observableArrayList(); protected final ObservableList<XYChart.Series<X, Y>> dataSeries = FXCollections.<XYChart.Series<X, Y>>observableArrayList();
protected final Map<EventType, XYChart.Series<X, Y>> eventTypeToSeriesMap = new HashMap<>();
abstract protected void resetData();
protected ChartType chart; protected ChartType chart;
//// replacement axis label componenets //// replacement axis label componenets
private final Pane leafPane; // container for the leaf lables in the declutterd axis private final Pane leafPane; // container for the leaf lables in the declutterd axis
private final Pane branchPane;// container for the branch lables in the declutterd axis private final Pane branchPane;// container for the branch lables in the declutterd axis
protected final Region spacer; protected final Region spacer;
/** /**
@ -148,7 +146,7 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
* @return true if the tick label for the given value should be bold ( has * @return true if the tick label for the given value should be bold ( has
* relevant data), false* otherwise * relevant data), false* otherwise
*/ */
protected abstract Boolean isTickBold(X value); abstract protected Boolean isTickBold(X value);
/** /**
* apply this visualization's 'selection effect' to the given node * apply this visualization's 'selection effect' to the given node
@ -157,27 +155,27 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
* @param applied true if the effect should be applied, false if the effect * @param applied true if the effect should be applied, false if the effect
* should * should
*/ */
protected abstract void applySelectionEffect(NodeType node, Boolean applied); abstract protected void applySelectionEffect(NodeType node, Boolean applied);
/** /**
* @return a task to execute on a background thread to reload this * @return a task to execute on a background thread to reload this
* visualization with different data. * visualization with different data.
*/ */
protected abstract Task<Boolean> getUpdateTask(); abstract protected Task<Boolean> getUpdateTask();
/** /**
* @return return the {@link Effect} applied to 'selected nodes' in this * @return return the {@link Effect} applied to 'selected nodes' in this
* visualization, or null if selection is visualized via another * visualization, or null if selection is visualized via another
* mechanism * mechanism
*/ */
protected abstract Effect getSelectionEffect(); abstract protected Effect getSelectionEffect();
/** /**
* @param tickValue * @param tickValue
* *
* @return a String to use for a tick mark label given a tick value * @return a String to use for a tick mark label given a tick value
*/ */
protected abstract String getTickMarkLabel(X tickValue); abstract protected String getTickMarkLabel(X tickValue);
/** /**
* the spacing (in pixels) between tick marks of the horizontal axis. This * the spacing (in pixels) between tick marks of the horizontal axis. This
@ -185,22 +183,27 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
* *
* @return the spacing in pixels between tick marks of the horizontal axis * @return the spacing in pixels between tick marks of the horizontal axis
*/ */
protected abstract double getTickSpacing(); abstract protected double getTickSpacing();
/** /**
* @return the horizontal axis used by this Visualization's chart * @return the horizontal axis used by this Visualization's chart
*/ */
protected abstract Axis<X> getXAxis(); abstract protected Axis<X> getXAxis();
/** /**
* @return the vertical axis used by this Visualization's chart * @return the vertical axis used by this Visualization's chart
*/ */
protected abstract Axis<Y> getYAxis(); abstract protected Axis<Y> getYAxis();
abstract protected void resetData();
/** /**
* update this visualization based on current state of zoom / filters. * update this visualization based on current state of zoom / filters.
* Primarily this invokes the background {@link Task} returned by * Primarily this invokes the background {@link VisualizationUpdateTask}
* {@link #getUpdateTask()} which derived classes must implement. * returned by {@link #getUpdateTask()}, which derived classes must
* implement.
*
* TODO: replace this logic with a {@link Service} ? -jm
*/ */
final synchronized public void update() { final synchronized public void update() {
if (updateTask != null) { if (updateTask != null) {
@ -236,8 +239,10 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
invalidationListener = null; invalidationListener = null;
} }
/**
* make a series for each event type in a consistent order
*/
protected final void createSeries() { protected final void createSeries() {
//make all series to ensure they get created in consistent order
for (EventType eventType : EventType.allTypes) { for (EventType eventType : EventType.allTypes) {
XYChart.Series<X, Y> series = new XYChart.Series<>(); XYChart.Series<X, Y> series = new XYChart.Series<>();
series.setName(eventType.getDisplayName()); series.setName(eventType.getDisplayName());
@ -246,9 +251,19 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
} }
} }
/**
*
* @param et the EventType to get the series for
*
* @return a Series object to contain all the events with the given
* EventType
*/
protected final XYChart.Series<X, Y> getSeries(final EventType et) {
return eventTypeToSeriesMap.get(et);
}
protected AbstractVisualizationPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) { protected AbstractVisualizationPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) {
this.controller = controller; this.controller = controller;
this.filteredEvents = controller.getEventsModel(); this.filteredEvents = controller.getEventsModel();
this.filteredEvents.registerForEvents(this); this.filteredEvents.registerForEvents(this);
this.filteredEvents.zoomParametersProperty().addListener(invalidationListener); this.filteredEvents.zoomParametersProperty().addListener(invalidationListener);
@ -268,18 +283,10 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
TimeLineController.getTimeZone().addListener(invalidationListener); TimeLineController.getTimeZone().addListener(invalidationListener);
//show tooltip text in status bar //show tooltip text in status bar
hoverProperty().addListener((observable, oldActivated, newActivated) -> { hoverProperty().addListener(observable -> controller.setStatus(isHover() ? DEFAULT_TOOLTIP.getText() : ""));
if (newActivated) {
controller.setStatus(DEFAULT_TOOLTIP.getText());
} else {
controller.setStatus("");
}
});
} }
protected final Map<EventType, XYChart.Series<X, Y>> eventTypeToSeriesMap = new HashMap<>();
@Subscribe @Subscribe
public void handleRefreshRequested(RefreshRequestedEvent event) { public void handleRefreshRequested(RefreshRequestedEvent event) {
update(); update();
@ -477,17 +484,8 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
} }
} }
/** protected Interval getTimeRange() {
* NOTE: Because this method modifies data directly used by the chart, this return filteredEvents.timeRangeProperty().get();
* method should only be called from JavaFX thread!
*
* @param et the EventType to get the series for
*
* @return a Series object to contain all the events with the given
* EventType
*/
protected final XYChart.Series<X, Y> getSeries(final EventType et) {
return eventTypeToSeriesMap.get(et);
} }
abstract protected class VisualizationUpdateTask<AxisValuesType> extends LoggedTask<Boolean> { abstract protected class VisualizationUpdateTask<AxisValuesType> extends LoggedTask<Boolean> {
@ -496,30 +494,36 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
super(taskName, logStateChanges); super(taskName, logStateChanges);
} }
protected void installMaskerPane() { /**
MaskerPane maskerPane = new MaskerPane(); * Sets initial progress value and message and shows blocking progress
maskerPane.textProperty().bind(messageProperty()); * indicator over the visualization. Derived Tasks should be sure to
maskerPane.progressProperty().bind(progressProperty()); * call this as part of their call() implementation.
setCenter(new StackPane(chart, maskerPane)); *
} * @return true
*
protected Interval getTimeRange() { * @throws Exception
return filteredEvents.timeRangeProperty().get(); */
}
@NbBundle.Messages({"VisualizationUpdateTask.preparing=Analyzing zoom and filter settings"}) @NbBundle.Messages({"VisualizationUpdateTask.preparing=Analyzing zoom and filter settings"})
@Override @Override
protected Boolean call() throws Exception { protected Boolean call() throws Exception {
updateProgress(-1, 1); updateProgress(-1, 1);
updateMessage(Bundle.VisualizationUpdateTask_preparing()); updateMessage(Bundle.VisualizationUpdateTask_preparing());
Platform.runLater(() -> { Platform.runLater(() -> {
installMaskerPane(); MaskerPane maskerPane = new MaskerPane();
maskerPane.textProperty().bind(messageProperty());
maskerPane.progressProperty().bind(progressProperty());
setCenter(new StackPane(chart, maskerPane));
setCursor(Cursor.WAIT); setCursor(Cursor.WAIT);
}); });
return true; return true;
} }
/**
* updates the horisontal axis and removes the blocking progress
* indicator. Derived Tasks should be sure to call this as part of their
* succeeded() implementation.
*/
@Override @Override
protected void succeeded() { protected void succeeded() {
super.succeeded(); super.succeeded();
@ -532,7 +536,8 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
} }
/** /**
* for use within the derived impementation of {@link #call() } * Clears the chart data and sets the horisontal axis range. For use
* within the derived impementation of {@link #call() }
* *
* @param axisValues * @param axisValues
*/ */

View File

@ -252,20 +252,30 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
chart.setRangeInfo(rangeInfo); //do we need this. It seems like a hack. chart.setRangeInfo(rangeInfo); //do we need this. It seems like a hack.
List<Interval> intervals = rangeInfo.getIntervals(); List<Interval> intervals = rangeInfo.getIntervals();
//clear old data, and reset ranges and series
List<String> categories = Lists.transform(intervals, rangeInfo::formatForTick); List<String> categories = Lists.transform(intervals, rangeInfo::formatForTick);
//clear old data, and reset ranges and series
resetChart(categories); resetChart(categories);
updateMessage(Bundle.CountsViewPane_loggedTask_updatingCounts()); updateMessage(Bundle.CountsViewPane_loggedTask_updatingCounts());
int chartMax = 0; int chartMax = 0;
int numIntervals = intervals.size(); int numIntervals = intervals.size();
/*
* for each interval query database for event counts and add to
* chart.
*
* Doing this in chunks might seem inefficient but it lets us reuse
* more cached results as the user navigates to overlapping viewws
*
* //TODO: implement similar chunked caching in DetailsView -jm
*/
for (int i = 0; i < numIntervals; i++) { for (int i = 0; i < numIntervals; i++) {
if (isCancelled()) { if (isCancelled()) {
return null; return null;
} }
updateProgress(i, numIntervals); updateProgress(i, numIntervals);
final Interval interval = intervals.get(i); final Interval interval = intervals.get(i);
int maxPerInterval = 0; //used in total max tracking int maxPerInterval = 0;
//query for current interval //query for current interval
Map<EventType, Long> eventCounts = filteredEvents.getEventCounts(interval); Map<EventType, Long> eventCounts = filteredEvents.getEventCounts(interval);
@ -290,6 +300,7 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
} }
chartMax = Math.max(chartMax, maxPerInterval); chartMax = Math.max(chartMax, maxPerInterval);
} }
//adjust vertical axis according to scale type and max counts
double countAxisUpperbound = 1 + chartMax * 1.2; double countAxisUpperbound = 1 + chartMax * 1.2;
double tickUnit = ScaleType.LINEAR.equals(scale.get()) double tickUnit = ScaleType.LINEAR.equals(scale.get())
? Math.pow(10, Math.max(0, Math.floor(Math.log10(chartMax)) - 1)) ? Math.pow(10, Math.max(0, Math.floor(Math.log10(chartMax)) - 1))
@ -298,7 +309,7 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
countAxis.setTickUnit(tickUnit); countAxis.setTickUnit(tickUnit);
countAxis.setUpperBound(countAxisUpperbound); countAxis.setUpperBound(countAxisUpperbound);
}); });
return chartMax > 0; return chartMax > 0; // are there events
} }
@Override @Override

View File

@ -173,6 +173,48 @@ final class EventCountsChart extends StackedBarChart<String, Number> implements
return SELECTED_NODE_EFFECT; return SELECTED_NODE_EFFECT;
} }
/**
* Add the bar click handler,tooltip, border styling and hover effect to the
* node generated by StackedBarChart.
*
* @param series
* @param itemIndex
* @param item
*/
@NbBundle.Messages({
"# {0} - count",
"# {1} - event type displayname",
"# {2} - start date time",
"# {3} - end date time",
"CountsViewPane.tooltip.text={0} {1} events\nbetween {2}\nand {3}"})
@Override
protected void dataItemAdded(Series<String, Number> series, int itemIndex, Data<String, Number> item) {
ExtraData extraValue = (ExtraData) item.getExtraValue();
EventType eventType = extraValue.getEventType();
Interval interval = extraValue.getInterval();
long count = extraValue.getRawCount();
item.nodeProperty().addListener((Observable o) -> {
final Node node = item.getNode();
if (node != null) {
node.setStyle("-fx-border-width: 2; -fx-border-color: " + ColorUtilities.getRGBCode(eventType.getSuperType().getColor()) + "; -fx-bar-fill: " + ColorUtilities.getRGBCode(eventType.getColor())); // NON-NLS
node.setCursor(Cursor.HAND);
final Tooltip tooltip = new Tooltip(Bundle.CountsViewPane_tooltip_text(
count, eventType.getDisplayName(),
item.getXValue(),
interval.getEnd().toString(rangeInfo.getTickFormatter())));
tooltip.setGraphic(new ImageView(eventType.getFXImage()));
Tooltip.install(node, tooltip);
node.setOnMouseEntered((mouseEntered) -> node.setEffect(new DropShadow(10, eventType.getColor())));
node.setOnMouseExited((MouseEvent mouseExited) -> node.setEffect(selectedNodes.contains(node) ? SELECTED_NODE_EFFECT : null));
node.setOnMouseClicked(new BarClickHandler(item));
}
});
super.dataItemAdded(series, itemIndex, item); //To change body of generated methods, choose Tools | Templates.
}
/** /**
* StringConvereter used to 'format' vertical axis labels * StringConvereter used to 'format' vertical axis labels
*/ */
@ -228,40 +270,6 @@ final class EventCountsChart extends StackedBarChart<String, Number> implements
} }
} }
@NbBundle.Messages({
"# {0} - count",
"# {1} - event type displayname",
"# {2} - start date time",
"# {3} - end date time",
"CountsViewPane.tooltip.text={0} {1} events\nbetween {2}\nand {3}"})
@Override
protected void dataItemAdded(Series<String, Number> series, int itemIndex, Data<String, Number> item) {
ExtraData extraValue = (ExtraData) item.getExtraValue();
EventType eventType = extraValue.getEventType();
Interval interval = extraValue.getInterval();
long count = extraValue.getRawCount();
item.nodeProperty().addListener((Observable o) -> {
final Node node = item.getNode();
if (node != null) {
node.setStyle("-fx-border-width: 2; -fx-border-color: " + ColorUtilities.getRGBCode(eventType.getSuperType().getColor()) + "; -fx-bar-fill: " + ColorUtilities.getRGBCode(eventType.getColor())); // NON-NLS
node.setCursor(Cursor.HAND);
final Tooltip tooltip = new Tooltip(Bundle.CountsViewPane_tooltip_text(
count, eventType.getDisplayName(),
item.getXValue(),
interval.getEnd().toString(rangeInfo.getTickFormatter())));
tooltip.setGraphic(new ImageView(eventType.getFXImage()));
Tooltip.install(node, tooltip);
node.setOnMouseEntered((mouseEntered) -> node.setEffect(new DropShadow(10, eventType.getColor())));
node.setOnMouseExited((MouseEvent mouseExited) -> node.setEffect(selectedNodes.contains(node) ? SELECTED_NODE_EFFECT : null));
node.setOnMouseClicked(new BarClickHandler(item));
}
});
super.dataItemAdded(series, itemIndex, item); //To change body of generated methods, choose Tools | Templates.
}
/** /**
* EventHandler for click events on nodes representing a bar(segment) in the * EventHandler for click events on nodes representing a bar(segment) in the
* stacked bar chart. * stacked bar chart.
@ -415,6 +423,10 @@ final class EventCountsChart extends StackedBarChart<String, Number> implements
} }
} }
/**
* Encapsulate extra data stuffed into each {@link Data} item to give click
* handler and tooltip access to more info.
*/
static class ExtraData { static class ExtraData {
private final Interval interval; private final Interval interval;

View File

@ -110,7 +110,9 @@ public final class EventDetailsChart extends XYChart<DateTime, EventStripe> impl
private ContextMenu chartContextMenu; private ContextMenu chartContextMenu;
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)//at start of layout pass
private Set<String> activeQuickHidefilters; private Set<String> activeQuickHidefilters;
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)//at start of layout pass
private double descriptionWidth; private double descriptionWidth;
@Override @Override
@ -420,12 +422,15 @@ public final class EventDetailsChart extends XYChart<DateTime, EventStripe> impl
protected void layoutPlotChildren() { protected void layoutPlotChildren() {
setCursor(Cursor.WAIT); setCursor(Cursor.WAIT);
maxY.set(0); maxY.set(0);
//These don't change during a layout pass and are expensive to compute per node. So we do it once at the start
activeQuickHidefilters = getController().getQuickHideFilters().stream() activeQuickHidefilters = getController().getQuickHideFilters().stream()
.filter(AbstractFilter::isActive) .filter(AbstractFilter::isActive)
.map(DescriptionFilter::getDescription) .map(DescriptionFilter::getDescription)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
descriptionWidth = getDescriptionWidth(); //This dosn't change during a layout pass and is expensive to compute per node. So we do it once at the start
descriptionWidth = truncateAll.get() ? truncateWidth.get() : USE_PREF_SIZE;
if (bandByType.get()) { if (bandByType.get()) {
sortedStripeNodes.stream() sortedStripeNodes.stream()
@ -438,10 +443,6 @@ public final class EventDetailsChart extends XYChart<DateTime, EventStripe> impl
setCursor(null); setCursor(null);
} }
private double getDescriptionWidth() {
return truncateAll.get() ? truncateWidth.get() : USE_PREF_SIZE;
}
ReadOnlyDoubleProperty maxVScrollProperty() { ReadOnlyDoubleProperty maxVScrollProperty() {
return maxY.getReadOnlyProperty(); return maxY.getReadOnlyProperty();
} }
@ -531,8 +532,8 @@ public final class EventDetailsChart extends XYChart<DateTime, EventStripe> impl
//initial test position //initial test position
double yTop = (oneEventPerRow.get()) double yTop = (oneEventPerRow.get())
? (localMax + MINIMUM_EVENT_NODE_GAP) ? (localMax + MINIMUM_EVENT_NODE_GAP)// if onePerRow, just put it at end
: computeYTop(minY, h, maxXatY, xLeft, xRight); // if onePerRow, just put it at end : computeYTop(minY, h, maxXatY, xLeft, xRight);
localMax = Math.max(yTop + h, localMax); localMax = Math.max(yTop + h, localMax);
@ -545,6 +546,23 @@ public final class EventDetailsChart extends XYChart<DateTime, EventStripe> impl
return localMax; //return new max return localMax; //return new max
} }
/**
* Given information about the current layout pass so far and about a
* particular node, compute the y position of that node.
*
*
* @param yMin the smallest (towards the top of the screen) y position to
* consider
* @param h the height of the node we are trying to position
* @param maxXatY a map from y ranges to the max x within that range. NOTE:
* This map will be updated to include the node in question.
* @param xLeft the left x-cord of the node to position
* @param xRight the left x-cord of the node to position
*
* @return the y position for the node in question.
*
*
*/
private double computeYTop(double yMin, double h, TreeRangeMap<Double, Double> maxXatY, double xLeft, double xRight) { private double computeYTop(double yMin, double h, TreeRangeMap<Double, Double> maxXatY, double xLeft, double xRight) {
double yTop = yMin; double yTop = yMin;
double yBottom = yTop + h; double yBottom = yTop + h;
@ -589,10 +607,10 @@ public final class EventDetailsChart extends XYChart<DateTime, EventStripe> impl
} }
/** /**
* expose as public * expose as protected
*/ */
@Override @Override
public void requestChartLayout() { protected void requestChartLayout() {
super.requestChartLayout(); super.requestChartLayout();
} }

View File

@ -1,7 +1,20 @@
/* /*
* To change this license header, choose License Headers in Project Properties. * Autopsy Forensic Browser
* To change this template file, choose Tools | Templates *
* and open the template in the editor. * Copyright 2015 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ */
package org.sleuthkit.autopsy.timeline.ui.detailview; package org.sleuthkit.autopsy.timeline.ui.detailview;
@ -14,10 +27,6 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
* More specifically it takes an EventStripeNode and produces a stream of * More specifically it takes an EventStripeNode and produces a stream of
* EventStripes containing the stripes for the given node and all child * EventStripes containing the stripes for the given node and all child
* eventStripes, ignoring intervening EventCluster nodes. * eventStripes, ignoring intervening EventCluster nodes.
*
* @see
* #loadSubBundles(org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD.RelativeDetail)
* for usage
*/ */
class StripeFlattener implements Function<EventStripeNode, Stream<EventStripe>> { class StripeFlattener implements Function<EventStripeNode, Stream<EventStripe>> {