Autopsy  4.12.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
EventNodeBase.java
Go to the documentation of this file.
1 
2 /*
3  * Autopsy Forensic Browser
4  *
5  * Copyright 2016-19 Basis Technology Corp.
6  * Contact: carrier <at> sleuthkit <dot> org
7  *
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */
20 package org.sleuthkit.autopsy.timeline.ui.detailview;
21 
22 import com.google.common.collect.Lists;
23 import com.google.common.collect.Sets;
24 import com.google.common.eventbus.Subscribe;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Optional;
31 import java.util.Set;
32 import java.util.concurrent.ConcurrentHashMap;
33 import java.util.concurrent.ExecutionException;
34 import java.util.logging.Level;
35 import java.util.stream.Collectors;
36 import javafx.animation.KeyFrame;
37 import javafx.animation.KeyValue;
38 import javafx.animation.Timeline;
39 import javafx.application.Platform;
40 import javafx.concurrent.Task;
41 import javafx.event.EventHandler;
42 import javafx.geometry.Insets;
43 import javafx.scene.Node;
44 import javafx.scene.control.Button;
45 import javafx.scene.control.ButtonBase;
46 import javafx.scene.control.ContextMenu;
47 import javafx.scene.control.Label;
48 import javafx.scene.control.SeparatorMenuItem;
49 import javafx.scene.control.Tooltip;
50 import javafx.scene.effect.DropShadow;
51 import javafx.scene.effect.Effect;
52 import javafx.scene.image.Image;
53 import javafx.scene.image.ImageView;
54 import javafx.scene.input.MouseButton;
55 import javafx.scene.input.MouseEvent;
56 import javafx.scene.layout.Background;
57 import javafx.scene.layout.BackgroundFill;
58 import javafx.scene.layout.Border;
59 import javafx.scene.layout.BorderStroke;
60 import javafx.scene.layout.BorderStrokeStyle;
61 import javafx.scene.layout.BorderWidths;
62 import javafx.scene.layout.HBox;
63 import javafx.scene.layout.StackPane;
64 import javafx.scene.paint.Color;
65 import javafx.util.Duration;
66 import org.apache.commons.lang3.StringUtils;
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;
82 import static org.sleuthkit.autopsy.timeline.ui.detailview.MultiEventNodeBase.CORNER_RADII_3;
84 import org.sleuthkit.datamodel.SleuthkitCase;
85 import org.sleuthkit.datamodel.TskCoreException;
86 import org.sleuthkit.datamodel.TimelineEventType;
87 import org.sleuthkit.datamodel.TimelineEvent;
88 
92 public abstract class EventNodeBase<Type extends DetailViewEvent> extends StackPane implements ContextMenuProvider {
93 
94  private static final Logger LOGGER = Logger.getLogger(EventNodeBase.class.getName());
95 
96  private static final Image HASH_HIT = new Image("/org/sleuthkit/autopsy/images/hashset_hits.png"); //NOI18N NON-NLS
97  private static final Image TAG = new Image("/org/sleuthkit/autopsy/images/green-tag-icon-16.png"); // NON-NLS //NOI18N
98  private static final Image PIN = new Image("/org/sleuthkit/autopsy/timeline/images/marker--plus.png"); // NON-NLS //NOI18N
99  private static final Image UNPIN = new Image("/org/sleuthkit/autopsy/timeline/images/marker--minus.png"); // NON-NLS //NOI18N
100 
101  private static final Map<TimelineEventType, Effect> dropShadowMap = new ConcurrentHashMap<>();
102 
103  static void configureActionButton(ButtonBase b) {
104  b.setMinSize(16, 16);
105  b.setMaxSize(16, 16);
106  b.setPrefSize(16, 16);
107  }
108 
109  static void show(Node b, boolean show) {
110  b.setVisible(show);
111  b.setManaged(show);
112  }
113 
114  private final Type tlEvent;
115 
117 
118  final DetailsChartLane<?> chartLane;
119  final Background highlightedBackground;
120  final Background defaultBackground;
121  final Color evtColor;
122 
123  final Label countLabel = new Label();
124  final Label descrLabel = new Label();
125  final ImageView hashIV = new ImageView(HASH_HIT);
126  final ImageView tagIV = new ImageView(TAG);
127  final ImageView eventTypeImageView = new ImageView();
128 
129  final Tooltip tooltip = new Tooltip(Bundle.EventBundleNodeBase_toolTip_loading());
130 
131  final HBox controlsHBox = new HBox(5);
132  final HBox infoHBox = new HBox(5, eventTypeImageView, hashIV, tagIV, descrLabel, countLabel, controlsHBox);
133  final SleuthkitCase sleuthkitCase;
134  final FilteredEventsModel eventsModel;
135  private Timeline timeline;
136  private Button pinButton;
137  private final Border SELECTION_BORDER;
138 
139  EventNodeBase(Type tlEvent, EventNodeBase<?> parent, DetailsChartLane<?> chartLane) {
140  this.chartLane = chartLane;
141  this.tlEvent = tlEvent;
142  this.parentNode = parent;
143 
144  sleuthkitCase = chartLane.getController().getAutopsyCase().getSleuthkitCase();
145  eventsModel = chartLane.getController().getEventsModel();
146  eventTypeImageView.setImage(new Image(getImagePath(getEventType())));
147 
148  if (tlEvent.getEventIDsWithHashHits().isEmpty()) {
149  show(hashIV, false);
150  }
151 
152  if (tlEvent.getEventIDsWithTags().isEmpty()) {
153  show(tagIV, false);
154  }
155 
156  if (chartLane.getController().getEventsModel().getEventTypeZoom() == TimelineEventType.TypeLevel.SUB_TYPE) {
157  evtColor = getColor(getEventType());
158  } else {
159  evtColor = getColor(getEventType().getBaseType());
160  }
161  SELECTION_BORDER = new Border(new BorderStroke(evtColor.darker().desaturate(), BorderStrokeStyle.SOLID, CORNER_RADII_3, new BorderWidths(2)));
162 
163  defaultBackground = new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .1), CORNER_RADII_3, Insets.EMPTY));
164  highlightedBackground = new Background(new BackgroundFill(evtColor.deriveColor(0, 1.1, 1.1, .3), CORNER_RADII_3, Insets.EMPTY));
165  setBackground(defaultBackground);
166 
167  Tooltip.install(this, this.tooltip);
168 
169  //set up mouse hover effect and tooltip
170  setOnMouseEntered(mouseEntered -> {
171  Tooltip.uninstall(chartLane, AbstractTimelineChart.getDefaultTooltip());
172  showHoverControls(true);
173  toFront();
174  });
175 
176  setOnMouseExited(mouseExited -> {
177  showHoverControls(false);
178  if (parentNode != null) {
179  parentNode.showHoverControls(true);
180  } else {
181  Tooltip.install(chartLane, AbstractTimelineChart.getDefaultTooltip());
182  }
183  });
184  setOnMouseClicked(new ClickHandler());
185  show(controlsHBox, false);
186  }
187 
188  public Type getEvent() {
189  return tlEvent;
190  }
191 
192  @Override
194  return chartLane.getController();
195  }
196 
197  public Optional<EventNodeBase<?>> getParentNode() {
198  return Optional.ofNullable(parentNode);
199  }
200 
201  DetailsChartLane<?> getChartLane() {
202  return chartLane;
203  }
204 
208  public void setMaxDescriptionWidth(double w) {
209  descrLabel.setMaxWidth(w);
210  }
211 
212  public abstract List<EventNodeBase<?>> getSubNodes();
213 
219  public void applySelectionEffect(boolean applied) {
220  setBorder(applied ? SELECTION_BORDER : null);
221  }
222 
223  @Override
224  protected void layoutChildren() {
225  super.layoutChildren();
226  }
227 
233  void installActionButtons() {
234  if (pinButton == null) {
235  pinButton = new Button();
236  controlsHBox.getChildren().add(pinButton);
237  configureActionButton(pinButton);
238  }
239  }
240 
241  final void showHoverControls(final boolean showControls) {
242  Effect dropShadow = dropShadowMap.computeIfAbsent(getEventType(),
243  eventType -> new DropShadow(-10, getColor(eventType)));
244  setEffect(showControls ? dropShadow : null);
245  installTooltip();
246  enableTooltip(showControls);
247  installActionButtons();
248 
249  TimeLineController controller = getChartLane().getController();
250 
251  if (controller.getPinnedEvents().contains(tlEvent)) {
252  pinButton.setOnAction(actionEvent -> {
253  new UnPinEventAction(controller, tlEvent).handle(actionEvent);
254  showHoverControls(true);
255  });
256  pinButton.setGraphic(new ImageView(UNPIN));
257  } else {
258  pinButton.setOnAction(actionEvent -> {
259  new PinEventAction(controller, tlEvent).handle(actionEvent);
260  showHoverControls(true);
261  });
262  pinButton.setGraphic(new ImageView(PIN));
263  }
264 
265  show(controlsHBox, showControls);
266  if (parentNode != null) {
267  parentNode.showHoverControls(false);
268  }
269  }
270 
275  @NbBundle.Messages({"# {0} - counts",
276  "# {1} - event type",
277  "# {2} - description",
278  "# {3} - start date/time",
279  "# {4} - end date/time",
280  "EventNodeBase.tooltip.text={0} {1} events\n{2}\nbetween\t{3}\nand \t{4}",
281  "EventNodeBase.toolTip.loading2=loading tooltip",
282  "# {0} - hash set count string",
283  "EventNodeBase.toolTip.hashSetHits=\n\nHash Set Hits\n{0}",
284  "# {0} - tag count string",
285  "EventNodeBase.toolTip.tags=\n\nTags\n{0}"})
286  @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
287  void installTooltip() {
288  if (tooltip.getText().equalsIgnoreCase(Bundle.EventBundleNodeBase_toolTip_loading())) {
289  final Task<String> tooltTipTask = new Task<String>() {
290  {
291  updateTitle(Bundle.EventNodeBase_toolTip_loading2());
292  }
293 
294  @Override
295  protected String call() throws Exception {
296  HashMap<String, Long> hashSetCounts = new HashMap<>();
297  if (tlEvent.getEventIDsWithHashHits().isEmpty() == false) {
298  try {
299  //TODO:push this to DB
300  for (TimelineEvent tle : eventsModel.getEventsById(tlEvent.getEventIDsWithHashHits())) {
301  Set<String> hashSetNames = sleuthkitCase.getContentById(tle.getFileObjID()).getHashSetNames();
302  for (String hashSetName : hashSetNames) {
303  hashSetCounts.merge(hashSetName, 1L, Long::sum);
304  }
305  }
306  } catch (TskCoreException ex) {
307  LOGGER.log(Level.SEVERE, "Error getting hashset hit info for event.", ex); //NON-NLS
308  }
309  }
310  String hashSetCountsString = hashSetCounts.entrySet().stream()
311  .map((Map.Entry<String, Long> t) -> t.getKey() + " : " + t.getValue())
312  .collect(Collectors.joining("\n"));
313 
314  Map<String, Long> tagCounts = new HashMap<>();
315  if (tlEvent.getEventIDsWithTags().isEmpty() == false) {
316  tagCounts.putAll(eventsModel.getTagCountsByTagName(tlEvent.getEventIDsWithTags()));
317  }
318  String tagCountsString = tagCounts.entrySet().stream()
319  .map((Map.Entry<String, Long> t) -> t.getKey() + " : " + t.getValue())
320  .collect(Collectors.joining("\n"));
321 
322  return Bundle.EventNodeBase_tooltip_text(getEventIDs().size(), getEventType(), getDescription(),
323  TimeLineController.getZonedFormatter().print(getStartMillis()),
324  TimeLineController.getZonedFormatter().print(getEndMillis() + 1000))
325  + (hashSetCountsString.isEmpty() ? "" : Bundle.EventNodeBase_toolTip_hashSetHits(hashSetCountsString))
326  + (tagCountsString.isEmpty() ? "" : Bundle.EventNodeBase_toolTip_tags(tagCountsString));
327  }
328 
329  @Override
330  protected void done() {
331  super.succeeded();
332  try {
333  tooltip.setText(get());
334  tooltip.setGraphic(null);
335  } catch (InterruptedException | ExecutionException ex) {
336  LOGGER.log(Level.SEVERE, "Tooltip generation failed.", ex); //NON-NLS
337  }
338  }
339  };
340  new Thread(tooltTipTask).start();
341  chartLane.getController().monitorTask(tooltTipTask);
342  }
343  }
344 
345  void enableTooltip(boolean toolTipEnabled) {
346  if (toolTipEnabled) {
347  Tooltip.install(this, tooltip);
348  } else {
349  Tooltip.uninstall(this, tooltip);
350  }
351  }
352 
353  final TimelineEventType getEventType() {
354  return tlEvent.getEventType();
355  }
356 
357  long getStartMillis() {
358  return tlEvent.getStartMillis();
359  }
360 
361  final long getEndMillis() {
362  return tlEvent.getEndMillis();
363  }
364 
365  final double getLayoutXCompensation() {
366  return parentNode != null
367  ? getChartLane().getXAxis().getDisplayPosition(new DateTime(parentNode.getStartMillis()))
368  : 0;
369  }
370 
371  abstract String getDescription();
372 
373  void animateTo(double xLeft, double yTop) {
374  if (timeline != null) {
375  timeline.stop();
376  Platform.runLater(this::requestChartLayout);
377  }
378  timeline = new Timeline(new KeyFrame(Duration.millis(100),
379  new KeyValue(layoutXProperty(), xLeft),
380  new KeyValue(layoutYProperty(), yTop))
381  );
382  timeline.setOnFinished(finished -> Platform.runLater(this::requestChartLayout));
383  timeline.play();
384  }
385 
386  void requestChartLayout() {
387  getChartLane().requestChartLayout();
388  }
389 
390  @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
391  void setDescriptionVisibility(DescriptionVisibility descrVis) {
392  final int size = getEvent().getSize();
393  switch (descrVis) {
394  case HIDDEN:
395  hideDescription();
396  break;
397  case COUNT_ONLY:
398  showCountOnly(size);
399  break;
400  case SHOWN:
401  default:
402  showFullDescription(size);
403  break;
404  }
405  }
406 
407  void showCountOnly(final int size) {
408  descrLabel.setText("");
409  countLabel.setText(String.valueOf(size));
410  }
411 
412  void hideDescription() {
413  countLabel.setText("");
414  descrLabel.setText("");
415  }
416 
422  synchronized void applyHighlightEffect(boolean applied) {
423  if (applied) {
424  descrLabel.setStyle("-fx-font-weight: bold;"); // NON-NLS
425  setBackground(highlightedBackground);
426  } else {
427  descrLabel.setStyle("-fx-font-weight: normal;"); // NON-NLS
428  setBackground(defaultBackground);
429  }
430  }
431 
432  void applyHighlightEffect() {
433  applyHighlightEffect(true);
434  }
435 
436  void clearHighlightEffect() {
437  applyHighlightEffect(false);
438  }
439 
440  abstract Collection<Long> getEventIDs();
441 
442  abstract EventHandler<MouseEvent> getDoubleClickHandler();
443 
444  Iterable<? extends Action> getActions() {
445  if (getController().getPinnedEvents().contains(getEvent())) {
446  return Arrays.asList(new UnPinEventAction(getController(), getEvent()));
447  } else {
448  return Arrays.asList(new PinEventAction(getController(), getEvent()));
449  }
450  }
451 
452  @Deprecated
453  @Override
454  final public void clearContextMenu() {
455  }
456 
457  public ContextMenu getContextMenu(MouseEvent mouseEvent) {
458  ContextMenu chartContextMenu = chartLane.getContextMenu(mouseEvent);
459 
460  ContextMenu contextMenu = ActionUtils.createContextMenu(Lists.newArrayList(getActions()));
461  contextMenu.getItems().add(new SeparatorMenuItem());
462  contextMenu.getItems().addAll(chartContextMenu.getItems());
463  contextMenu.setAutoHide(true);
464  return contextMenu;
465  }
466 
467  void showFullDescription(final int size) {
468  countLabel.setText((size == 1) ? "" : " (" + size + ")"); // NON-NLS
469  String description = getParentNode().map(pNode
470  -> " ..." + StringUtils.substringAfter(getEvent().getDescription(), parentNode.getDescription()))
471  .orElseGet(getEvent()::getDescription);
472 
473  descrLabel.setText(description);
474  }
475 
476  @Subscribe
478  if (false == Sets.intersection(getEvent().getEventIDs(), event.getUpdatedEventIDs()).isEmpty()) {
479  Platform.runLater(() -> {
480  show(tagIV, true);
481  });
482  }
483  }
484 
488  @Subscribe
490  Sets.SetView<Long> difference = Sets.difference(getEvent().getEventIDs(), event.getUpdatedEventIDs());
491 
492  if (false == difference.isEmpty()) {
493  Platform.runLater(() -> {
494  show(tagIV, true);
495  });
496  }
497  }
498 
499  private static class PinEventAction extends Action {
500 
501  @NbBundle.Messages({"PinEventAction.text=Pin"})
503  super(Bundle.PinEventAction_text());
504  setEventHandler(actionEvent -> controller.pinEvent(event));
505  setGraphic(new ImageView(PIN));
506  }
507  }
508 
509  private static class UnPinEventAction extends Action {
510 
511  @NbBundle.Messages({"UnPinEventAction.text=Unpin"})
513  super(Bundle.UnPinEventAction_text());
514  setEventHandler(actionEvent -> controller.unPinEvent(event));
515  setGraphic(new ImageView(UNPIN));
516  }
517  }
518 
522  private class ClickHandler implements EventHandler<MouseEvent> {
523 
524  @Override
525  public void handle(MouseEvent t) {
526  if (t.getButton() == MouseButton.PRIMARY) {
527  if (t.getClickCount() > 1) {
528  getDoubleClickHandler().handle(t);
529  } else if (t.isShiftDown()) {
530  chartLane.getSelectedNodes().add(EventNodeBase.this);
531  } else if (t.isShortcutDown()) {
532  chartLane.getSelectedNodes().removeAll(EventNodeBase.this);
533  } else {
534  chartLane.getSelectedNodes().setAll(EventNodeBase.this);
535  }
536  t.consume();
537  } else if (t.isPopupTrigger() && t.isStillSincePress()) {
538  getContextMenu(t).show(EventNodeBase.this, t.getScreenX(), t.getScreenY());
539  t.consume();
540  }
541  }
542  }
543 }
Map< String, Long > getTagCountsByTagName(Set< Long > eventIDsWithTags)
Set< TimelineEvent > getEventsById(Collection< Long > eventIDs)
abstract List< EventNodeBase<?> > getSubNodes()
static final Map< TimelineEventType, Effect > dropShadowMap
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static String getImagePath(TimelineEventType type)
ObservableSet< DetailViewEvent > getPinnedEvents()
static Color getColor(TimelineEventType type)

Copyright © 2012-2018 Basis Technology. Generated on: Wed Sep 18 2019
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.