Autopsy  4.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
EventBundleNodeBase.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2015 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.Collection;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26 import java.util.concurrent.ConcurrentHashMap;
27 import java.util.concurrent.ExecutionException;
28 import java.util.logging.Level;
29 import java.util.stream.Collectors;
30 import javafx.animation.KeyFrame;
31 import javafx.animation.KeyValue;
32 import javafx.animation.Timeline;
33 import javafx.application.Platform;
34 import javafx.beans.binding.Bindings;
35 import javafx.beans.property.SimpleObjectProperty;
36 import javafx.collections.FXCollections;
37 import javafx.collections.ObservableList;
38 import javafx.concurrent.Task;
39 import javafx.event.EventHandler;
40 import javafx.geometry.Insets;
41 import javafx.geometry.Orientation;
42 import javafx.geometry.Pos;
43 import javafx.scene.Node;
44 import javafx.scene.control.Button;
45 import javafx.scene.control.ContextMenu;
46 import javafx.scene.control.Label;
47 import javafx.scene.control.SeparatorMenuItem;
48 import javafx.scene.control.Tooltip;
49 import javafx.scene.effect.DropShadow;
50 import javafx.scene.effect.Effect;
51 import javafx.scene.image.Image;
52 import javafx.scene.image.ImageView;
53 import javafx.scene.input.MouseButton;
54 import javafx.scene.input.MouseEvent;
55 import javafx.scene.layout.Background;
56 import javafx.scene.layout.BackgroundFill;
57 import javafx.scene.layout.Border;
58 import javafx.scene.layout.BorderStroke;
59 import javafx.scene.layout.BorderStrokeStyle;
60 import javafx.scene.layout.BorderWidths;
61 import javafx.scene.layout.CornerRadii;
62 import javafx.scene.layout.HBox;
63 import javafx.scene.layout.Pane;
64 import javafx.scene.layout.StackPane;
65 import javafx.scene.paint.Color;
66 import javafx.util.Duration;
67 import org.controlsfx.control.action.Action;
68 import org.controlsfx.control.action.ActionUtils;
69 import org.joda.time.DateTime;
70 import org.openide.util.NbBundle;
81 import org.sleuthkit.datamodel.SleuthkitCase;
82 import org.sleuthkit.datamodel.TskCoreException;
83 
87 @NbBundle.Messages({"EventBundleNodeBase.toolTip.loading=loading..."})
88 public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentType>, ParentType extends EventBundle<BundleType>, ParentNodeType extends EventBundleNodeBase<ParentType, BundleType, ?>> extends StackPane {
89 
90  private static final Logger LOGGER = Logger.getLogger(EventBundleNodeBase.class.getName());
91  private static final Image HASH_PIN = new Image("/org/sleuthkit/autopsy/images/hashset_hits.png"); //NOI18N NON-NLS
92  private static final Image TAG = new Image("/org/sleuthkit/autopsy/images/green-tag-icon-16.png"); // NON-NLS //NOI18N
93 
94  static final CornerRadii CORNER_RADII_3 = new CornerRadii(3);
95  static final CornerRadii CORNER_RADII_1 = new CornerRadii(1);
96 
97  private final Border SELECTION_BORDER;
98  private static final Map<EventType, Effect> dropShadowMap = new ConcurrentHashMap<>();
99 
100  static void configureLoDButton(Button b) {
101  b.setMinSize(16, 16);
102  b.setMaxSize(16, 16);
103  b.setPrefSize(16, 16);
104  show(b, false);
105  }
106 
107  static void show(Node b, boolean show) {
108  b.setVisible(show);
109  b.setManaged(show);
110  }
111 
112  protected final EventDetailsChart chart;
113  final SimpleObjectProperty<DescriptionLoD> descLOD = new SimpleObjectProperty<>();
114  final SimpleObjectProperty<DescriptionVisibility> descVisibility = new SimpleObjectProperty<>();
115  protected final BundleType eventBundle;
116 
117  protected final ParentNodeType parentNode;
118 
119  final SleuthkitCase sleuthkitCase;
120  final FilteredEventsModel eventsModel;
121 
122  final Background highlightedBackground;
123  final Background defaultBackground;
124  final Color evtColor;
125 
126  final ObservableList<ParentNodeType> subNodes = FXCollections.observableArrayList();
127  final Pane subNodePane = new Pane();
128  final Label descrLabel = new Label();
129  final Label countLabel = new Label();
130 
131  final ImageView hashIV = new ImageView(HASH_PIN);
132  final ImageView tagIV = new ImageView(TAG);
133  final HBox infoHBox = new HBox(5, descrLabel, countLabel, hashIV, tagIV);
134 
135  private final Tooltip tooltip = new Tooltip(Bundle.EventBundleNodeBase_toolTip_loading());
136  private Timeline timeline;
137 
138  public EventBundleNodeBase(EventDetailsChart chart, BundleType eventBundle, ParentNodeType parentNode) {
139  this.eventBundle = eventBundle;
140  this.parentNode = parentNode;
141  this.chart = chart;
142  this.descLOD.set(eventBundle.getDescriptionLoD());
143  sleuthkitCase = chart.getController().getAutopsyCase().getSleuthkitCase();
144  eventsModel = chart.getController().getEventsModel();
145  evtColor = getEventType().getColor();
146  defaultBackground = new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .1), CORNER_RADII_3, Insets.EMPTY));
147  highlightedBackground = new Background(new BackgroundFill(evtColor.deriveColor(0, 1.1, 1.1, .3), CORNER_RADII_3, Insets.EMPTY));
148  SELECTION_BORDER = new Border(new BorderStroke(evtColor.darker().desaturate(), BorderStrokeStyle.SOLID, CORNER_RADII_3, new BorderWidths(2)));
149  if (eventBundle.getEventIDsWithHashHits().isEmpty()) {
150  show(hashIV, false);
151  }
152  if (eventBundle.getEventIDsWithTags().isEmpty()) {
153  show(tagIV, false);
154  }
155 
156  setBackground(defaultBackground);
157  setAlignment(Pos.TOP_LEFT);
158  setMaxWidth(USE_PREF_SIZE);
159  infoHBox.setMaxWidth(USE_PREF_SIZE);
160  subNodePane.setPrefWidth(USE_COMPUTED_SIZE);
161  subNodePane.setMinWidth(USE_PREF_SIZE);
162  subNodePane.setMaxWidth(USE_PREF_SIZE);
163  /*
164  * This triggers the layout when a mousover causes the action buttons to
165  * interesect with another node, forcing it down.
166  */
167  heightProperty().addListener(heightProp -> chart.requestChartLayout());
168  Platform.runLater(() ->
169  setLayoutX(chart.getXAxis().getDisplayPosition(new DateTime(eventBundle.getStartMillis())) - getLayoutXCompensation())
170  );
171 
172  //initialize info hbox
173  infoHBox.setPadding(new Insets(2, 3, 2, 3));
174  infoHBox.setAlignment(Pos.TOP_LEFT);
175 
176  Tooltip.install(this, this.tooltip);
177 
178  //set up mouse hover effect and tooltip
179  setOnMouseEntered((MouseEvent e) -> {
180 
181  Tooltip.uninstall(chart, AbstractVisualizationPane.getDefaultTooltip());
182  showHoverControls(true);
183  toFront();
184  });
185  setOnMouseExited((MouseEvent event) -> {
186  showHoverControls(false);
187  if (parentNode != null) {
188  parentNode.showHoverControls(true);
189  } else {
190  Tooltip.install(chart, AbstractVisualizationPane.getDefaultTooltip());
191  }
192  });
193  setOnMouseClicked(new ClickHandler());
194  descVisibility.addListener(observable -> setDescriptionVisibiltiyImpl(descVisibility.get()));
195  descVisibility.set(DescriptionVisibility.SHOWN); //trigger listener for initial value
196 
197  Bindings.bindContent(subNodePane.getChildren(), subNodes);
198  }
199 
200  final DescriptionLoD getDescriptionLoD() {
201  return descLOD.get();
202  }
203 
204  public final BundleType getEventBundle() {
205  return eventBundle;
206  }
207 
208  final double getLayoutXCompensation() {
209  return parentNode != null
210  ? chart.getXAxis().getDisplayPosition(new DateTime(parentNode.getStartMillis()))
211  : 0;
212  }
213 
219  abstract void installActionButtons();
220 
225  @NbBundle.Messages({"# {0} - counts",
226  "# {1} - event type",
227  "# {2} - description",
228  "# {3} - start date/time",
229  "# {4} - end date/time",
230  "EventBundleNodeBase.tooltip.text={0} {1} events\n{2}\nbetween\t{3}\nand \t{4}",
231  "EventBundleNodeBase.toolTip.loading2=loading tooltip",
232  "# {0} - hash set count string",
233  "EventBundleNodeBase.toolTip.hashSetHits=\n\nHash Set Hits\n{0}",
234  "# {0} - tag count string",
235  "EventBundleNodeBase.toolTip.tags=\n\nTags\n{0}"})
237  private void installTooltip() {
238  if (tooltip.getText().equalsIgnoreCase(Bundle.EventBundleNodeBase_toolTip_loading())) {
239  final Task<String> tooltTipTask = new Task<String>() {
240  {
241  updateTitle(Bundle.EventBundleNodeBase_toolTip_loading2());
242  }
243 
244  @Override
245  protected String call() throws Exception {
246  HashMap<String, Long> hashSetCounts = new HashMap<>();
247  if (eventBundle.getEventIDsWithHashHits().isEmpty() == false) {
248  try {
249  //TODO:push this to DB
250  for (TimeLineEvent tle : eventsModel.getEventsById(eventBundle.getEventIDsWithHashHits())) {
251  Set<String> hashSetNames = sleuthkitCase.getAbstractFileById(tle.getFileID()).getHashSetNames();
252  for (String hashSetName : hashSetNames) {
253  hashSetCounts.merge(hashSetName, 1L, Long::sum);
254  }
255  }
256  } catch (TskCoreException ex) {
257  LOGGER.log(Level.SEVERE, "Error getting hashset hit info for event.", ex); //NON-NLS
258  }
259  }
260  String hashSetCountsString = hashSetCounts.entrySet().stream()
261  .map((Map.Entry<String, Long> t) -> t.getKey() + " : " + t.getValue())
262  .collect(Collectors.joining("\n"));
263 
264  Map<String, Long> tagCounts = new HashMap<>();
265  if (eventBundle.getEventIDsWithTags().isEmpty() == false) {
266  tagCounts.putAll(eventsModel.getTagCountsByTagName(eventBundle.getEventIDsWithTags()));
267  }
268  String tagCountsString = tagCounts.entrySet().stream()
269  .map((Map.Entry<String, Long> t) -> t.getKey() + " : " + t.getValue())
270  .collect(Collectors.joining("\n"));
271 
272  return Bundle.EventBundleNodeBase_tooltip_text(getEventIDs().size(), getEventType(), getDescription(),
273  TimeLineController.getZonedFormatter().print(getStartMillis()),
274  TimeLineController.getZonedFormatter().print(getEndMillis() + 1000))
275  + (hashSetCountsString.isEmpty() ? "" : Bundle.EventBundleNodeBase_toolTip_hashSetHits(hashSetCountsString))
276  + (tagCountsString.isEmpty() ? "" : Bundle.EventBundleNodeBase_toolTip_tags(tagCountsString));
277  }
278 
279  @Override
280  protected void succeeded() {
281  super.succeeded();
282  try {
283  tooltip.setText(get());
284  tooltip.setGraphic(null);
285  } catch (InterruptedException | ExecutionException ex) {
286  LOGGER.log(Level.SEVERE, "Tooltip generation failed.", ex); //NON-NLS
287  }
288  }
289  };
290  new Thread(tooltTipTask).start();
291  chart.getController().monitorTask(tooltTipTask);
292  }
293  }
294 
300  public void applySelectionEffect(boolean applied) {
301  setBorder(applied ? SELECTION_BORDER : null);
302  }
303 
309  abstract void applyHighlightEffect(boolean applied);
310 
311  @SuppressWarnings("unchecked")
312  public List<ParentNodeType> getSubNodes() {
313  return subNodes;
314  }
315 
316  abstract void setDescriptionVisibiltiyImpl(DescriptionVisibility get);
317 
318  void showHoverControls(final boolean showControls) {
319  Effect dropShadow = dropShadowMap.computeIfAbsent(getEventType(),
320  eventType -> new DropShadow(-10, eventType.getColor()));
321  setEffect(showControls ? dropShadow : null);
322  installTooltip();
323  enableTooltip(showControls);
324  if (parentNode != null) {
325  parentNode.showHoverControls(false);
326  }
327  }
328 
329  final EventType getEventType() {
330  return getEventBundle().getEventType();
331  }
332 
333  final String getDescription() {
334  return getEventBundle().getDescription();
335  }
336 
337  final long getStartMillis() {
338  return getEventBundle().getStartMillis();
339  }
340 
341  final long getEndMillis() {
342  return getEventBundle().getEndMillis();
343  }
344 
345  final Set<Long> getEventIDs() {
346  return getEventBundle().getEventIDs();
347  }
348 
349  @Override
350  public Orientation getContentBias() {
351  return Orientation.HORIZONTAL;
352  }
353 
354  @Override
355  protected void layoutChildren() {
356  chart.layoutEventBundleNodes(subNodes, 0);
357  super.layoutChildren();
358  }
359 
360  abstract ParentNodeType createChildNode(ParentType rawChild);
361 
365  abstract void setMaxDescriptionWidth(double w);
366 
367  void setDescriptionVisibility(DescriptionVisibility get) {
368  descVisibility.set(get);
369  }
370 
371  void enableTooltip(boolean toolTipEnabled) {
372  if (toolTipEnabled) {
373  Tooltip.install(this, tooltip);
374  } else {
375  Tooltip.uninstall(this, tooltip);
376  }
377  }
378 
379  void animateTo(double xLeft, double yTop) {
380  if (timeline != null) {
381  timeline.stop();
382  Platform.runLater(chart::requestChartLayout);
383  }
384  timeline = new Timeline(new KeyFrame(Duration.millis(100),
385  new KeyValue(layoutXProperty(), xLeft),
386  new KeyValue(layoutYProperty(), yTop))
387  );
388  timeline.setOnFinished(finished -> Platform.runLater(chart::requestChartLayout));
389  timeline.play();
390  }
391 
392  abstract EventHandler<MouseEvent> getDoubleClickHandler();
393 
394  abstract Collection<? extends Action> getActions();
395 
399  private class ClickHandler implements EventHandler<MouseEvent> {
400 
401  private ContextMenu contextMenu;
402 
403  @Override
404  public void handle(MouseEvent t) {
405  if (t.getButton() == MouseButton.PRIMARY) {
406  if (t.getClickCount() > 1) {
407  getDoubleClickHandler().handle(t);
408  } else if (t.isShiftDown()) {
409  chart.selectedNodes.add(EventBundleNodeBase.this);
410  } else if (t.isShortcutDown()) {
411  chart.selectedNodes.removeAll(EventBundleNodeBase.this);
412  } else {
413  chart.selectedNodes.setAll(EventBundleNodeBase.this);
414  }
415  t.consume();
416  } else if (t.getButton() == MouseButton.SECONDARY) {
417  ContextMenu chartContextMenu = chart.getChartContextMenu(t);
418  if (contextMenu == null) {
419  contextMenu = new ContextMenu();
420  contextMenu.setAutoHide(true);
421 
422  contextMenu.getItems().addAll(ActionUtils.createContextMenu(getActions()).getItems());
423 
424  contextMenu.getItems().add(new SeparatorMenuItem());
425  contextMenu.getItems().addAll(chartContextMenu.getItems());
426  }
427  contextMenu.show(EventBundleNodeBase.this, t.getScreenX(), t.getScreenY());
428  t.consume();
429  }
430  }
431  }
432 
433 }
synchronized void monitorTask(final Task<?> task)
EventBundleNodeBase(EventDetailsChart chart, BundleType eventBundle, ParentNodeType parentNode)
Map< String, Long > getTagCountsByTagName(Set< Long > eventIDsWithTags)
synchronized static Logger getLogger(String name)
Definition: Logger.java:166
Set< TimeLineEvent > getEventsById(Collection< Long > eventIDs)

Copyright © 2012-2015 Basis Technology. Generated on: Wed Apr 6 2016
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.