Autopsy  3.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
AbstractVisualization.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2014 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.timeline.ui;
20 
21 import java.util.Collections;
22 import java.util.List;
23 import java.util.concurrent.ExecutionException;
24 import javafx.beans.InvalidationListener;
25 import javafx.beans.Observable;
26 import javafx.beans.property.ReadOnlyListProperty;
27 import javafx.beans.property.ReadOnlyListWrapper;
28 import javafx.beans.property.SimpleBooleanProperty;
29 import javafx.collections.FXCollections;
30 import javafx.collections.ListChangeListener;
31 import javafx.collections.ObservableList;
32 import javafx.concurrent.Task;
33 import javafx.geometry.Pos;
34 import javafx.scene.Node;
35 import javafx.scene.chart.Axis;
36 import javafx.scene.chart.BarChart;
37 import javafx.scene.chart.Chart;
38 import javafx.scene.chart.XYChart;
39 import javafx.scene.control.Label;
40 import javafx.scene.control.OverrunStyle;
41 import javafx.scene.effect.Effect;
42 import javafx.scene.input.MouseButton;
43 import javafx.scene.input.MouseEvent;
44 import javafx.scene.layout.BorderPane;
45 import javafx.scene.layout.Pane;
46 import javafx.scene.layout.Region;
47 import javafx.scene.text.Font;
48 import javafx.scene.text.FontWeight;
49 import javafx.scene.text.Text;
50 import javafx.scene.text.TextAlignment;
51 import javax.annotation.concurrent.Immutable;
52 import org.apache.commons.lang3.StringUtils;
53 import org.openide.util.Exceptions;
57 
71 public abstract class AbstractVisualization<X, Y, N extends Node, C extends XYChart<X, Y> & TimeLineChart<X>> extends BorderPane implements TimeLineView {
72 
73  protected final SimpleBooleanProperty hasEvents = new SimpleBooleanProperty(true);
74 
75  protected final ObservableList<BarChart.Series<X, Y>> dataSets = FXCollections.<BarChart.Series<X, Y>>observableArrayList();
76 
77  protected C chart;
78 
80  private final Pane leafPane; // container for the leaf lables in the declutterd axis
81 
82  private final Pane branchPane;// container for the branch lables in the declutterd axis
83 
84  protected final Region spacer;
85 
87  private Task<Boolean> updateTask;
88 
90 
92 
93  protected ReadOnlyListWrapper<N> selectedNodes = new ReadOnlyListWrapper<>(FXCollections.observableArrayList());
94 
95  public ReadOnlyListProperty<N> getSelectedNodes() {
96  return selectedNodes.getReadOnlyProperty();
97  }
98 
101  protected List<Node> settingsNodes;
102 
105  protected List<Node> getSettingsNodes() {
106  return Collections.unmodifiableList(settingsNodes);
107  }
108 
114  protected abstract Boolean isTickBold(X value);
115 
122  protected abstract void applySelectionEffect(N node, Boolean applied);
123 
126  protected abstract Task<Boolean> getUpdateTask();
127 
131  protected abstract Effect getSelectionEffect();
132 
137  protected abstract String getTickMarkLabel(X tickValue);
138 
144  protected abstract double getTickSpacing();
145 
147  protected abstract Axis<X> getXAxis();
148 
150  protected abstract Axis<Y> getYAxis();
151 
155  synchronized public void update() {
156  if (updateTask != null) {
157  updateTask.cancel(true);
158  updateTask = null;
159  }
160  updateTask = getUpdateTask();
161  updateTask.stateProperty().addListener((Observable observable) -> {
162  switch (updateTask.getState()) {
163  case CANCELLED:
164  case FAILED:
165  case READY:
166  case RUNNING:
167  case SCHEDULED:
168  break;
169  case SUCCEEDED:
170  try {
171  this.hasEvents.set(updateTask.get());
172  } catch (InterruptedException | ExecutionException ex) {
173  Exceptions.printStackTrace(ex);
174  }
175  break;
176  }
177  });
178  controller.monitorTask(updateTask);
179  }
180 
181  synchronized public void dispose() {
182  if (updateTask != null) {
183  updateTask.cancel(true);
184  }
185  this.filteredEvents.getRequestedZoomParamters().removeListener(invalidationListener);
186  invalidationListener = null;
187  }
188 
189  protected AbstractVisualization(Pane partPane, Pane contextPane, Region spacer) {
190  this.leafPane = partPane;
191  this.branchPane = contextPane;
192  this.spacer = spacer;
193  selectedNodes.addListener((ListChangeListener.Change<? extends N> c) -> {
194  while (c.next()) {
195  c.getRemoved().forEach((N n) -> {
196  applySelectionEffect(n, false);
197  });
198 
199  c.getAddedSubList().forEach((N c1) -> {
200  applySelectionEffect(c1, true);
201  });
202  }
203  });
204  }
205 
206  @Override
207  synchronized public void setController(TimeLineController controller) {
208  this.controller = controller;
209  chart.setController(controller);
210 
211  setModel(controller.getEventsModel());
212  TimeLineController.getTimeZone().addListener((Observable observable) -> {
213  update();
214  });
215  }
216 
217  @Override
218  synchronized public void setModel(FilteredEventsModel filteredEvents) {
219  this.filteredEvents = filteredEvents;
220 
221  this.filteredEvents.getRequestedZoomParamters().addListener(invalidationListener);
222  update();
223  }
224 
225  protected InvalidationListener invalidationListener = (Observable observable) -> {
226  update();
227  };
228 
247  public synchronized void layoutDateLabels() {
248 
249  //clear old labels
250  branchPane.getChildren().clear();
251  leafPane.getChildren().clear();
252  //since the tickmarks aren't necessarily in value/position order,
253  //make a clone of the list sorted by position along axis
254  ObservableList<Axis.TickMark<X>> tickMarks = FXCollections.observableArrayList(getXAxis().getTickMarks());
255  tickMarks.sort((Axis.TickMark<X> t, Axis.TickMark<X> t1) -> Double.compare(t.getPosition(), t1.getPosition()));
256 
257  if (tickMarks.isEmpty() == false) {
258  //get the spacing between ticks in the underlying axis
259  double spacing = getTickSpacing();
260 
261  //initialize values from first tick
262  TwoPartDateTime dateTime = new TwoPartDateTime(getTickMarkLabel(tickMarks.get(0).getValue()));
263  String lastSeenBranchLabel = dateTime.branch;
264  //cumulative width of the current branch label
265 
266  //x-positions (pixels) of the current branch and leaf labels
267  double leafLabelX = 0;
268 
269  if (dateTime.branch.equals("")) {
270  //if there is only one part to the date (ie only year), just add a label for each tick
271  for (Axis.TickMark<X> t : tickMarks) {
272  assignLeafLabel(new TwoPartDateTime(getTickMarkLabel(t.getValue())).leaf,
273  spacing,
274  leafLabelX,
275  isTickBold(t.getValue())
276  );
277 
278  leafLabelX += spacing; //increment x
279  }
280  } else {
281  //there are two parts so ...
282  //initialize additional state
283  double branchLabelX = 0;
284  double branchLabelWidth = 0;
285 
286  for (Axis.TickMark<X> t : tickMarks) { //for each tick
287 
288  //split the label into a TwoPartDateTime
289  dateTime = new TwoPartDateTime(getTickMarkLabel(t.getValue()));
290 
291  //if we are still on the same branch
292  if (lastSeenBranchLabel.equals(dateTime.branch)) {
293  //increment branch width
294  branchLabelWidth += spacing;
295  } else {// we are on to a new branch, so ...
296  assignBranchLabel(lastSeenBranchLabel, branchLabelWidth, branchLabelX);
297  //and then update label, x-pos, and width
298  lastSeenBranchLabel = dateTime.branch;
299  branchLabelX += branchLabelWidth;
300  branchLabelWidth = spacing;
301  }
302  //add the label for the leaf (highest frequency part)
303  assignLeafLabel(dateTime.leaf, spacing, leafLabelX, isTickBold(t.getValue()));
304 
305  //increment leaf position
306  leafLabelX += spacing;
307  }
308  //we have reached end so add branch label for current branch
309  assignBranchLabel(lastSeenBranchLabel, branchLabelWidth, branchLabelX);
310  }
311  }
312  //request layout since we have modified scene graph structure
313  requestParentLayout();
314  }
315 
316  protected void setChartClickHandler() {
317  chart.addEventHandler(MouseEvent.MOUSE_CLICKED, (MouseEvent event) -> {
318  if (event.getButton() == MouseButton.PRIMARY && event.isStillSincePress()) {
319  selectedNodes.clear();
320  }
321  });
322  }
323 
332  private synchronized void assignLeafLabel(String labelText, double labelWidth, double labelX, boolean bold) {
333 
334  Text label = new Text(" " + labelText + " ");
335  label.setTextAlignment(TextAlignment.CENTER);
336  label.setFont(Font.font(null, bold ? FontWeight.BOLD : FontWeight.NORMAL, 10));
337  //position label accounting for width
338  label.relocate(labelX + labelWidth / 2 - label.getBoundsInLocal().getWidth() / 2, 0);
339  label.autosize();
340 
341  if (leafPane.getChildren().isEmpty()) {
342  //just add first label
343  leafPane.getChildren().add(label);
344  } else {
345  //otherwise don't actually add the label if it would intersect with previous label
346  final Text lastLabel = (Text) leafPane.getChildren().get(leafPane.getChildren().size() - 1);
347 
348  if (!lastLabel.getBoundsInParent().intersects(label.getBoundsInParent())) {
349  leafPane.getChildren().add(label);
350  }
351  }
352  }
353 
361  private synchronized void assignBranchLabel(String labelText, double labelWidth, double labelX) {
362 
363  Label label = new Label(labelText);
364  label.setAlignment(Pos.CENTER);
365  label.setTextAlignment(TextAlignment.CENTER);
366  label.setFont(Font.font(10));
367  //use a leading ellipse since that is the lowest frequency part,
368  //and can be infered more easily from other surrounding labels
369  label.setTextOverrun(OverrunStyle.LEADING_ELLIPSIS);
370  //force size
371  label.setMinWidth(labelWidth);
372  label.setPrefWidth(labelWidth);
373  label.setMaxWidth(labelWidth);
374  label.relocate(labelX, 0);
375 
376  if (labelX == 0) { // first label has no border
377  label.setStyle("-fx-border-width: 0 0 0 0 ; -fx-border-color:black;"); // NON-NLS
378  } else { // subsequent labels have border on left to create dividers
379  label.setStyle("-fx-border-width: 0 0 0 1; -fx-border-color:black;"); // NON-NLS
380  }
381 
382  branchPane.getChildren().add(label);
383  }
384 
392  @Immutable
393  private static final class TwoPartDateTime {
394 
396  private final String branch;
397 
399  private final String leaf;
400 
401  TwoPartDateTime(String dateString) {
402  //find index of separator to spit on
403  int splitIndex = StringUtils.lastIndexOfAny(dateString, " ", "-", ":");
404  if (splitIndex < 0) { // there is only one part
405  leaf = dateString;
406  branch = "";
407  } else { //split at index
408  leaf = StringUtils.substring(dateString, splitIndex + 1);
409  branch = StringUtils.substring(dateString, 0, splitIndex);
410  }
411  }
412  }
413 }
abstract void applySelectionEffect(N node, Boolean applied)
static ReadOnlyObjectProperty< TimeZone > getTimeZone()
synchronized void setModel(FilteredEventsModel filteredEvents)
synchronized void setController(TimeLineController controller)
synchronized void assignLeafLabel(String labelText, double labelWidth, double labelX, boolean bold)
final ObservableList< BarChart.Series< X, Y > > dataSets
AbstractVisualization(Pane partPane, Pane contextPane, Region spacer)
synchronized void monitorTask(final Task<?> task)
synchronized void assignBranchLabel(String labelText, double labelWidth, double labelX)
synchronized ReadOnlyObjectProperty< ZoomParams > getRequestedZoomParamters()

Copyright © 2012-2015 Basis Technology. Generated on: Mon Oct 19 2015
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.