Autopsy  3.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
AggregateEventNode.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2013 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.detailview;
20 
21 import java.util.List;
22 import java.util.concurrent.ExecutionException;
23 import java.util.stream.Collectors;
24 import javafx.application.Platform;
25 import javafx.beans.property.SimpleObjectProperty;
26 import javafx.event.EventHandler;
27 import javafx.geometry.Insets;
28 import javafx.geometry.Pos;
29 import javafx.scene.Cursor;
30 import javafx.scene.control.Button;
31 import javafx.scene.control.ContextMenu;
32 import javafx.scene.control.Label;
33 import javafx.scene.control.OverrunStyle;
34 import javafx.scene.control.Tooltip;
35 import javafx.scene.effect.DropShadow;
36 import javafx.scene.image.Image;
37 import javafx.scene.image.ImageView;
38 import javafx.scene.input.MouseButton;
39 import javafx.scene.input.MouseEvent;
40 import javafx.scene.layout.Background;
41 import javafx.scene.layout.BackgroundFill;
42 import javafx.scene.layout.Border;
43 import javafx.scene.layout.BorderPane;
44 import javafx.scene.layout.BorderStroke;
45 import javafx.scene.layout.BorderStrokeStyle;
46 import javafx.scene.layout.BorderWidths;
47 import javafx.scene.layout.CornerRadii;
48 import javafx.scene.layout.HBox;
49 import javafx.scene.layout.Pane;
50 import javafx.scene.layout.Priority;
51 import javafx.scene.layout.Region;
52 import javafx.scene.layout.StackPane;
53 import javafx.scene.paint.Color;
54 import org.apache.commons.lang3.StringUtils;
55 import org.joda.time.DateTime;
56 import org.joda.time.Interval;
57 import org.openide.util.Exceptions;
58 import org.openide.util.NbBundle;
68 
70 public class AggregateEventNode extends StackPane {
71 
72  private final static Image PLUS = new Image("/org/sleuthkit/autopsy/timeline/images/plus-button.png"); // NON-NLS
73  private final static Image MINUS = new Image("/org/sleuthkit/autopsy/timeline/images/minus-button.png"); // NON-NLS
74 
75  private static final CornerRadii CORNER_RADII = new CornerRadii(3);
76 
78  private static final Border selectionBorder = new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CORNER_RADII, new BorderWidths(2)));
79 
81  private final AggregateEvent event;
82 
84 
86  private final Region spanRegion = new Region();
87 
89  private final Label descrLabel = new Label();
90 
92  private final Label countLabel = new Label();
93 
95  private final ImageView eventTypeImageView = new ImageView();
96 
102  private final Pane subNodePane = new Pane();
103 
109  private final SimpleObjectProperty<ContextMenu> contextMenu = new SimpleObjectProperty<>();
110 
113  private Background spanFill;
114 
115  private final Button plusButton = new Button(null, new ImageView(PLUS)) {
116  {
117  setMinSize(16, 16);
118  setMaxSize(16, 16);
119  setPrefSize(16, 16);
120  }
121  };
122  private final Button minusButton = new Button(null, new ImageView(MINUS)) {
123  {
124  setMinSize(16, 16);
125  setMaxSize(16, 16);
126  setPrefSize(16, 16);
127  }
128  };
129  private final EventDetailChart chart;
130 
131  private SimpleObjectProperty<DescriptionLOD> descLOD = new SimpleObjectProperty<>();
132  private DescriptionVisibility descrVis;
133 
134  public AggregateEventNode(final AggregateEvent event, AggregateEventNode parentEventNode, EventDetailChart chart) {
135  this.event = event;
136  descLOD.set(event.getLOD());
137  this.parentEventNode = parentEventNode;
138  this.chart = chart;
139  final Region region = new Region();
140  HBox.setHgrow(region, Priority.ALWAYS);
141  final HBox hBox = new HBox(descrLabel, countLabel, region, minusButton, plusButton);
142  hBox.setPrefWidth(USE_COMPUTED_SIZE);
143  hBox.setMinWidth(USE_PREF_SIZE);
144  hBox.setPadding(new Insets(2, 5, 2, 5));
145  hBox.setAlignment(Pos.CENTER_LEFT);
146 
147  minusButton.setVisible(false);
148  plusButton.setVisible(false);
149  minusButton.setManaged(false);
150  plusButton.setManaged(false);
151  final BorderPane borderPane = new BorderPane(subNodePane, hBox, null, null, null);
152  BorderPane.setAlignment(subNodePane, Pos.TOP_LEFT);
153  borderPane.setPrefWidth(USE_COMPUTED_SIZE);
154 
155  getChildren().addAll(spanRegion, borderPane);
156 
157  setAlignment(Pos.TOP_LEFT);
158  setMinHeight(24);
159  minWidthProperty().bind(spanRegion.widthProperty());
160  setPrefHeight(USE_COMPUTED_SIZE);
161  setMaxHeight(USE_PREF_SIZE);
162 
163  //set up subnode pane sizing contraints
164  subNodePane.setPrefHeight(USE_COMPUTED_SIZE);
165  subNodePane.setMinHeight(USE_PREF_SIZE);
166  subNodePane.setMinWidth(USE_PREF_SIZE);
167  subNodePane.setMaxHeight(USE_PREF_SIZE);
168  subNodePane.setMaxWidth(USE_PREF_SIZE);
169  subNodePane.setPickOnBounds(false);
170 
171  //setup description label
172  eventTypeImageView.setImage(event.getType().getFXImage());
173  descrLabel.setGraphic(eventTypeImageView);
174  descrLabel.setPrefWidth(USE_COMPUTED_SIZE);
175  descrLabel.setTextOverrun(OverrunStyle.CENTER_ELLIPSIS);
176 
177  descrLabel.setMouseTransparent(true);
178  setDescriptionVisibility(chart.getDescrVisibility().get());
179 
180  //setup backgrounds
181  final Color evtColor = event.getType().getColor();
182  spanFill = new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY));
183  setBackground(new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY)));
184  setCursor(Cursor.HAND);
185  spanRegion.setStyle("-fx-border-width:2 0 2 2; -fx-border-radius: 2; -fx-border-color: " + ColorUtilities.getRGBCode(evtColor) + ";"); // NON-NLS
186  spanRegion.setBackground(spanFill);
187 
188  //set up mouse hover effect and tooltip
189  setOnMouseEntered((MouseEvent e) -> {
190  //defer tooltip creation till needed, this had a surprisingly large impact on speed of loading the chart
191  installTooltip();
192  spanRegion.setEffect(new DropShadow(10, evtColor));
193  minusButton.setVisible(true);
194  plusButton.setVisible(true);
195  minusButton.setManaged(true);
196  plusButton.setManaged(true);
197  toFront();
198 
199  });
200 
201  setOnMouseExited((MouseEvent e) -> {
202  spanRegion.setEffect(null);
203  minusButton.setVisible(false);
204  plusButton.setVisible(false);
205  minusButton.setManaged(false);
206  plusButton.setManaged(false);
207 
208  });
209 
210  setOnMouseClicked(new EventMouseHandler());
211 
212  plusButton.disableProperty().bind(descLOD.isEqualTo(DescriptionLOD.FULL));
213  minusButton.disableProperty().bind(descLOD.isEqualTo(event.getLOD()));
214 
215  plusButton.setOnMouseClicked(e -> {
216  final DescriptionLOD next = descLOD.get().next();
217  if (next != null) {
218  loadSubClusters(next);
219  descLOD.set(next);
220  }
221  });
222  minusButton.setOnMouseClicked(e -> {
223  final DescriptionLOD previous = descLOD.get().previous();
224  if (previous != null) {
225  loadSubClusters(previous);
226  descLOD.set(previous);
227  }
228  });
229  }
230 
231  private void installTooltip() {
232  Tooltip.install(AggregateEventNode.this, new Tooltip(
233  NbBundle.getMessage(this.getClass(), "AggregateEventNode.installTooltip.text",
235  getEvent().getSpan().getStart().toString(TimeLineController.getZonedFormatter()),
236  getEvent().getSpan().getEnd().toString(TimeLineController.getZonedFormatter()))));
237  }
238 
239  public Pane getSubNodePane() {
240  return subNodePane;
241  }
242 
244  return event;
245  }
246 
253  public void setSpanWidth(double w) {
254  spanRegion.setPrefWidth(w);
255  spanRegion.setMaxWidth(w);
256  spanRegion.setMinWidth(Math.max(2, w));
257  }
258 
263  public void setDescriptionWidth(double w) {
264  descrLabel.setMaxWidth(w);
265  }
266 
268  final void setDescriptionVisibility(DescriptionVisibility descrVis) {
269  this.descrVis = descrVis;
270  final int size = event.getEventIDs().size();
271 
272  switch (descrVis) {
273 
274  case COUNT_ONLY:
275  descrLabel.setText("");
276  countLabel.setText(String.valueOf(size));
277  break;
278  case HIDDEN:
279  countLabel.setText("");
280  descrLabel.setText("");
281  break;
282  default:
283  case SHOWN:
284  String description = event.getDescription();
285  description = parentEventNode != null
286  ? " ..." + StringUtils.substringAfter(description, parentEventNode.getEvent().getDescription())
287  : description;
288  descrLabel.setText(description);
289  countLabel.setText(((size == 1) ? "" : " (" + size + ")")); // NON-NLS
290  break;
291  }
292  }
293 
298  void applySelectionEffect(final boolean applied) {
299  Platform.runLater(() -> {
300  if (applied) {
301  setBorder(selectionBorder);
302  } else {
303  setBorder(null);
304  }
305  });
306  }
307 
312  void applyHighlightEffect(boolean applied) {
313 
314  if (applied) {
315  descrLabel.setStyle("-fx-font-weight: bold;"); // NON-NLS
316  spanFill = new Background(new BackgroundFill(getEvent().getType().getColor().deriveColor(0, 1, 1, .3), CORNER_RADII, Insets.EMPTY));
317  spanRegion.setBackground(spanFill);
318  setBackground(new Background(new BackgroundFill(getEvent().getType().getColor().deriveColor(0, 1, 1, .2), CORNER_RADII, Insets.EMPTY)));
319  } else {
320  descrLabel.setStyle("-fx-font-weight: normal;"); // NON-NLS
321  spanFill = new Background(new BackgroundFill(getEvent().getType().getColor().deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY));
322  spanRegion.setBackground(spanFill);
323  setBackground(new Background(new BackgroundFill(getEvent().getType().getColor().deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY)));
324  }
325  }
326 
327  String getDisplayedDescription() {
328  return descrLabel.getText();
329  }
330 
331  double getLayoutXCompensation() {
332  return (parentEventNode != null ? parentEventNode.getLayoutXCompensation() : 0)
333  + getBoundsInParent().getMinX();
334  }
335 
339  public ContextMenu getContextMenu() {
340  return contextMenu.get();
341  }
342 
346  public void setContextMenu(ContextMenu contextMenu) {
347  this.contextMenu.set(contextMenu);
348  }
349 
355  private void loadSubClusters(DescriptionLOD newLOD) {
356  getSubNodePane().getChildren().clear();
357  if (newLOD == event.getLOD()) {
358  getSubNodePane().getChildren().clear();
359  chart.setRequiresLayout(true);
360  chart.requestChartLayout();
361  } else {
362  //make a new filter intersecting the global filter with text(description) and type filters to restrict sub-clusters
363  final Filter combinedFilter = Filter.intersect(new Filter[]{new TextFilter(event.getDescription()),
364  new TypeFilter(event.getType()),
365  chart.getFilteredEvents().filter().get()});
366 
367  //make a new end inclusive span (to 'filter' with)
368  final Interval span = event.getSpan().withEndMillis(event.getSpan().getEndMillis() + 1000);
369 
370  //make a task to load the subnodes
372  NbBundle.getMessage(this.getClass(), "AggregateEventNode.loggedTask.name"), true) {
373 
374  @Override
375  protected List<AggregateEventNode> call() throws Exception {
376  //query for the sub-clusters
377  List<AggregateEvent> aggregatedEvents = chart.getFilteredEvents().getAggregatedEvents(new ZoomParams(span,
378  chart.getFilteredEvents().eventTypeZoom().get(),
379  combinedFilter,
380  newLOD));
381  //for each sub cluster make an AggregateEventNode to visually represent it, and set x-position
382  return aggregatedEvents.stream().map((AggregateEvent t) -> {
383  AggregateEventNode subNode = new AggregateEventNode(t, AggregateEventNode.this, chart);
384  subNode.setLayoutX(chart.getXAxis().getDisplayPosition(new DateTime(t.getSpan().getStartMillis())) - getLayoutXCompensation());
385  return subNode;
386  }).collect(Collectors.toList()); // return list of AggregateEventNodes representing subclusters
387  }
388 
389  @Override
390  protected void succeeded() {
391  try {
392  chart.setCursor(Cursor.WAIT);
393  //assign subNodes and request chart layout
394  getSubNodePane().getChildren().setAll(get());
395  setDescriptionVisibility(descrVis);
396  chart.setRequiresLayout(true);
397  chart.requestChartLayout();
398  chart.setCursor(null);
399  } catch (InterruptedException | ExecutionException ex) {
400  Exceptions.printStackTrace(ex);
401  }
402  }
403  };
404 
405  //start task
406  chart.getController().monitorTask(loggedTask);
407  }
408  }
409 
411  private class EventMouseHandler implements EventHandler<MouseEvent> {
412 
413  @Override
414  public void handle(MouseEvent t) {
415  if (t.getButton() == MouseButton.PRIMARY) {
416  t.consume();
417  if (t.isShiftDown()) {
418  if (chart.selectedNodes.contains(AggregateEventNode.this) == false) {
419  chart.selectedNodes.add(AggregateEventNode.this);
420  }
421  } else if (t.isShortcutDown()) {
422  chart.selectedNodes.removeAll(AggregateEventNode.this);
423  } else if (t.getClickCount() > 1) {
424  final DescriptionLOD next = descLOD.get().next();
425  if (next != null) {
426  loadSubClusters(next);
427  descLOD.set(next);
428  }
429  } else {
430  chart.selectedNodes.setAll(AggregateEventNode.this);
431  }
432  }
433  }
434  }
435 }
static IntersectionFilter intersect(ObservableList< Filter > filters)
Definition: Filter.java:38
synchronized ReadOnlyObjectProperty< Filter > filter()
synchronized void monitorTask(final Task<?> task)
AggregateEventNode(final AggregateEvent event, AggregateEventNode parentEventNode, EventDetailChart chart)
synchronized ReadOnlyObjectProperty< EventTypeZoomLevel > eventTypeZoom()

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.