19 package org.sleuthkit.autopsy.timeline.ui;
21 import com.google.common.eventbus.Subscribe;
22 import java.util.Collections;
23 import java.util.Comparator;
24 import java.util.HashMap;
25 import java.util.List;
27 import java.util.concurrent.ExecutionException;
28 import java.util.logging.Level;
29 import javafx.application.Platform;
30 import javafx.beans.InvalidationListener;
31 import javafx.beans.Observable;
32 import javafx.beans.property.SimpleBooleanProperty;
33 import javafx.collections.FXCollections;
34 import javafx.collections.ListChangeListener;
35 import javafx.collections.ObservableList;
36 import javafx.concurrent.Service;
37 import javafx.concurrent.Task;
38 import javafx.geometry.Pos;
39 import javafx.scene.Cursor;
40 import javafx.scene.Node;
41 import javafx.scene.chart.Axis;
42 import javafx.scene.chart.Chart;
43 import javafx.scene.chart.XYChart;
44 import javafx.scene.control.Label;
45 import javafx.scene.control.OverrunStyle;
46 import javafx.scene.control.Tooltip;
47 import javafx.scene.effect.Effect;
48 import javafx.scene.input.MouseButton;
49 import javafx.scene.input.MouseEvent;
50 import javafx.scene.layout.BorderPane;
51 import javafx.scene.layout.Pane;
52 import javafx.scene.layout.Region;
53 import javafx.scene.layout.StackPane;
54 import javafx.scene.text.Font;
55 import javafx.scene.text.FontWeight;
56 import javafx.scene.text.Text;
57 import javafx.scene.text.TextAlignment;
58 import javax.annotation.concurrent.Immutable;
59 import org.apache.commons.lang3.StringUtils;
60 import org.controlsfx.control.MaskerPane;
61 import org.joda.time.Interval;
62 import org.openide.util.NbBundle;
87 @NbBundle.Messages(
"AbstractVisualization.Default_Tooltip.text=Drag the mouse to select a time interval to zoom into.\nRight-click for more actions.")
88 private static final Tooltip
DEFAULT_TOOLTIP =
new Tooltip(Bundle.AbstractVisualization_Default_Tooltip_text());
95 protected final SimpleBooleanProperty
hasEvents =
new SimpleBooleanProperty(
true);
100 protected final ObservableList<XYChart.Series<X, Y>>
dataSeries = FXCollections.<XYChart.Series<X, Y>>observableArrayList();
119 final protected ObservableList<NodeType>
selectedNodes = FXCollections.observableArrayList();
140 return Collections.unmodifiableList(settingsNodes);
149 abstract protected Boolean
isTickBold(X value);
191 abstract protected Axis<X>
getXAxis();
196 abstract protected Axis<Y>
getYAxis();
208 final synchronized public void update() {
209 if (updateTask != null) {
210 updateTask.cancel(
true);
214 updateTask.stateProperty().addListener((Observable observable) -> {
215 switch (updateTask.getState()) {
224 this.hasEvents.set(updateTask.get());
225 }
catch (InterruptedException | ExecutionException ex) {
226 LOGGER.log(Level.SEVERE,
"Unexpected exception updating visualization", ex);
235 if (updateTask != null) {
236 updateTask.cancel(
true);
239 invalidationListener = null;
246 for (EventType eventType : EventType.allTypes) {
247 XYChart.Series<X, Y> series =
new XYChart.Series<>();
249 eventTypeToSeriesMap.put(eventType, series);
250 dataSeries.add(series);
261 protected final XYChart.Series<X, Y>
getSeries(
final EventType et) {
262 return eventTypeToSeriesMap.get(et);
270 this.leafPane = partPane;
271 this.branchPane = contextPane;
276 selectedNodes.addListener((ListChangeListener.Change<? extends NodeType> c) -> {
278 c.getRemoved().forEach(n -> applySelectionEffect(n, false));
279 c.getAddedSubList().forEach(n -> applySelectionEffect(n, true));
286 hoverProperty().addListener(observable -> controller.
setStatus(isHover() ? DEFAULT_TOOLTIP.getText() :
""));
312 public synchronized
void layoutDateLabels() {
315 branchPane.getChildren().clear();
316 leafPane.getChildren().clear();
319 ObservableList<Axis.TickMark<X>> tickMarks = FXCollections.observableArrayList(getXAxis().getTickMarks());
320 tickMarks.sort(Comparator.comparing(Axis.TickMark::getPosition));
322 if (tickMarks.isEmpty() ==
false) {
324 double spacing = getTickSpacing();
327 TwoPartDateTime dateTime =
new TwoPartDateTime(getTickMarkLabel(tickMarks.get(0).getValue()));
328 String lastSeenBranchLabel = dateTime.branch;
332 double leafLabelX = 0;
334 if (dateTime.branch.isEmpty()) {
336 for (Axis.TickMark<X> t : tickMarks) {
337 assignLeafLabel(
new TwoPartDateTime(getTickMarkLabel(t.getValue())).leaf,
340 isTickBold(t.getValue())
343 leafLabelX += spacing;
348 double branchLabelX = 0;
349 double branchLabelWidth = 0;
351 for (Axis.TickMark<X> t : tickMarks) {
354 dateTime =
new TwoPartDateTime(getTickMarkLabel(t.getValue()));
357 if (lastSeenBranchLabel.equals(dateTime.branch)) {
359 branchLabelWidth += spacing;
361 assignBranchLabel(lastSeenBranchLabel, branchLabelWidth, branchLabelX);
363 lastSeenBranchLabel = dateTime.branch;
364 branchLabelX += branchLabelWidth;
365 branchLabelWidth = spacing;
368 assignLeafLabel(dateTime.leaf, spacing, leafLabelX, isTickBold(t.getValue()));
371 leafLabelX += spacing;
374 assignBranchLabel(lastSeenBranchLabel, branchLabelWidth, branchLabelX);
378 requestParentLayout();
382 chart.addEventHandler(MouseEvent.MOUSE_CLICKED, (MouseEvent event) -> {
383 if (event.getButton() == MouseButton.PRIMARY && event.isStillSincePress()) {
384 selectedNodes.clear();
398 private synchronized void assignLeafLabel(String labelText,
double labelWidth,
double labelX,
boolean bold) {
400 Text label =
new Text(
" " + labelText +
" ");
401 label.setTextAlignment(TextAlignment.CENTER);
402 label.setFont(Font.font(null, bold ? FontWeight.BOLD : FontWeight.NORMAL, 10));
404 label.relocate(labelX + labelWidth / 2 - label.getBoundsInLocal().getWidth() / 2, 0);
407 if (leafPane.getChildren().isEmpty()) {
409 leafPane.getChildren().add(label);
412 final Text lastLabel = (Text) leafPane.getChildren().get(leafPane.getChildren().size() - 1);
414 if (!lastLabel.getBoundsInParent().intersects(label.getBoundsInParent())) {
415 leafPane.getChildren().add(label);
428 private synchronized void assignBranchLabel(String labelText,
double labelWidth,
double labelX) {
430 Label label =
new Label(labelText);
431 label.setAlignment(Pos.CENTER);
432 label.setTextAlignment(TextAlignment.CENTER);
433 label.setFont(Font.font(10));
436 label.setTextOverrun(OverrunStyle.LEADING_ELLIPSIS);
438 label.setMinWidth(labelWidth);
439 label.setPrefWidth(labelWidth);
440 label.setMaxWidth(labelWidth);
441 label.relocate(labelX, 0);
444 label.setStyle(
"-fx-border-width: 0 0 0 0 ; -fx-border-color:black;");
446 label.setStyle(
"-fx-border-width: 0 0 0 1; -fx-border-color:black;");
449 branchPane.getChildren().add(label);
476 int splitIndex = StringUtils.lastIndexOfAny(dateString,
" ",
"-",
":");
477 if (splitIndex < 0) {
481 leaf = StringUtils.substring(dateString, splitIndex + 1);
482 branch = StringUtils.substring(dateString, 0, splitIndex);
494 super(taskName, logStateChanges);
506 @NbBundle.Messages({
"VisualizationUpdateTask.preparing=Analyzing zoom and filter settings"})
508 protected Boolean
call()
throws Exception {
509 updateProgress(-1, 1);
510 updateMessage(Bundle.VisualizationUpdateTask_preparing());
511 Platform.runLater(() -> {
512 MaskerPane maskerPane =
new MaskerPane();
513 maskerPane.textProperty().bind(messageProperty());
514 maskerPane.progressProperty().bind(progressProperty());
515 setCenter(
new StackPane(chart, maskerPane));
516 setCursor(Cursor.WAIT);
532 Platform.runLater(() -> {
534 setCursor(Cursor.DEFAULT);
545 protected
void resetChart(AxisValuesType axisValues) {
547 Platform.runLater(() -> {
548 setDateAxisValues(axisValues);
553 abstract protected void setDateAxisValues(AxisValuesType values);
Task< Boolean > updateTask
synchronized void registerForEvents(Object o)
InvalidationListener invalidationListener
final ObservableList< NodeType > selectedNodes
FilteredEventsModel getEventsModel()
void setChartClickHandler()
final void createSeries()
static final Tooltip DEFAULT_TOOLTIP
AbstractVisualizationPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer)
static ReadOnlyObjectProperty< TimeZone > getTimeZone()
List< Node > getSettingsNodes()
static final Logger LOGGER
final ObservableList< XYChart.Series< X, Y > > dataSeries
void handleRefreshRequested(RefreshRequestedEvent event)
static Tooltip getDefaultTooltip()
final Map< EventType, XYChart.Series< X, Y > > eventTypeToSeriesMap
VisualizationUpdateTask(String taskName, boolean logStateChanges)
final synchronized void dispose()
abstract double getTickSpacing()
synchronized ReadOnlyObjectProperty< ZoomParams > zoomParametersProperty()
List< Node > settingsNodes
abstract void resetData()
abstract Axis< X > getXAxis()
synchronized void assignLeafLabel(String labelText, double labelWidth, double labelX, boolean bold)
synchronized void assignBranchLabel(String labelText, double labelWidth, double labelX)
ObservableList< NodeType > getSelectedNodes()
abstract Axis< Y > getYAxis()
final TimeLineController controller
abstract Boolean isTickBold(X value)
void setStatus(String string)
synchronized void monitorTask(final Task<?> task)
final FilteredEventsModel filteredEvents
final XYChart.Series< X, Y > getSeries(final EventType et)
synchronized static Logger getLogger(String name)
abstract Task< Boolean > getUpdateTask()
final SimpleBooleanProperty hasEvents
abstract Effect getSelectionEffect()
abstract void applySelectionEffect(NodeType node, Boolean applied)
synchronized ReadOnlyObjectProperty< Interval > timeRangeProperty()
abstract String getTickMarkLabel(X tickValue)
final synchronized void update()