Autopsy  4.9.1
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 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;
83 import static org.sleuthkit.autopsy.timeline.ui.detailview.MultiEventNodeBase.CORNER_RADII_3;
85 import org.sleuthkit.datamodel.SleuthkitCase;
86 import org.sleuthkit.datamodel.TskCoreException;
87 
91 public abstract class EventNodeBase<Type extends TimeLineEvent> extends StackPane implements ContextMenuProvider {
92 
93  private static final Logger LOGGER = Logger.getLogger(EventNodeBase.class.getName());
94 
95  private static final Image HASH_HIT = new Image("/org/sleuthkit/autopsy/images/hashset_hits.png"); //NOI18N NON-NLS
96  private static final Image TAG = new Image("/org/sleuthkit/autopsy/images/green-tag-icon-16.png"); // NON-NLS //NOI18N
97  private static final Image PIN = new Image("/org/sleuthkit/autopsy/timeline/images/marker--plus.png"); // NON-NLS //NOI18N
98  private static final Image UNPIN = new Image("/org/sleuthkit/autopsy/timeline/images/marker--minus.png"); // NON-NLS //NOI18N
99 
100  private static final Map<EventType, Effect> dropShadowMap = new ConcurrentHashMap<>();
101 
102  static void configureActionButton(ButtonBase b) {
103  b.setMinSize(16, 16);
104  b.setMaxSize(16, 16);
105  b.setPrefSize(16, 16);
106  }
107 
108  static void show(Node b, boolean show) {
109  b.setVisible(show);
110  b.setManaged(show);
111  }
112 
113  private final Type tlEvent;
114 
116 
117  final DetailsChartLane<?> chartLane;
118  final Background highlightedBackground;
119  final Background defaultBackground;
120  final Color evtColor;
121 
122  final Label countLabel = new Label();
123  final Label descrLabel = new Label();
124  final ImageView hashIV = new ImageView(HASH_HIT);
125  final ImageView tagIV = new ImageView(TAG);
126  final ImageView eventTypeImageView = new ImageView();
127 
128  final Tooltip tooltip = new Tooltip(Bundle.EventBundleNodeBase_toolTip_loading());
129 
130  final HBox controlsHBox = new HBox(5);
131  final HBox infoHBox = new HBox(5, eventTypeImageView, hashIV, tagIV, descrLabel, countLabel, controlsHBox);
132  final SleuthkitCase sleuthkitCase;
133  final FilteredEventsModel eventsModel;
134  private Timeline timeline;
135  private Button pinButton;
136  private final Border SELECTION_BORDER;
137 
138  EventNodeBase(Type tlEvent, EventNodeBase<?> parent, DetailsChartLane<?> chartLane) {
139  this.chartLane = chartLane;
140  this.tlEvent = tlEvent;
141  this.parentNode = parent;
142 
143  sleuthkitCase = chartLane.getController().getAutopsyCase().getSleuthkitCase();
144  eventsModel = chartLane.getController().getEventsModel();
145  eventTypeImageView.setImage(getEventType().getFXImage());
146 
147  if (tlEvent.getEventIDsWithHashHits().isEmpty()) {
148  show(hashIV, false);
149  }
150 
151  if (tlEvent.getEventIDsWithTags().isEmpty()) {
152  show(tagIV, false);
153  }
154 
155  if (chartLane.getController().getEventsModel().getEventTypeZoom() == EventTypeZoomLevel.SUB_TYPE) {
156  evtColor = getEventType().getColor();
157  } else {
158  evtColor = getEventType().getBaseType().getColor();
159  }
160  SELECTION_BORDER = new Border(new BorderStroke(evtColor.darker().desaturate(), BorderStrokeStyle.SOLID, CORNER_RADII_3, new BorderWidths(2)));
161 
162  defaultBackground = new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .1), CORNER_RADII_3, Insets.EMPTY));
163  highlightedBackground = new Background(new BackgroundFill(evtColor.deriveColor(0, 1.1, 1.1, .3), CORNER_RADII_3, Insets.EMPTY));
164  setBackground(defaultBackground);
165 
166  Tooltip.install(this, this.tooltip);
167 
168  //set up mouse hover effect and tooltip
169  setOnMouseEntered(mouseEntered -> {
170  Tooltip.uninstall(chartLane, AbstractTimelineChart.getDefaultTooltip());
171  showHoverControls(true);
172  toFront();
173  });
174  setOnMouseExited(mouseExited -> {
175  showHoverControls(false);
176  if (parentNode != null) {
177  parentNode.showHoverControls(true);
178  } else {
179  Tooltip.install(chartLane, AbstractTimelineChart.getDefaultTooltip());
180  }
181  });
182  setOnMouseClicked(new ClickHandler());
183  show(controlsHBox, false);
184  }
185 
186  public Type getEvent() {
187  return tlEvent;
188  }
189 
190  @Override
192  return chartLane.getController();
193  }
194 
195  public Optional<EventNodeBase<?>> getParentNode() {
196  return Optional.ofNullable(parentNode);
197  }
198 
199  DetailsChartLane<?> getChartLane() {
200  return chartLane;
201  }
202 
206  public void setMaxDescriptionWidth(double w) {
207  descrLabel.setMaxWidth(w);
208  }
209 
210  public abstract List<EventNodeBase<?>> getSubNodes();
211 
217  public void applySelectionEffect(boolean applied) {
218  setBorder(applied ? SELECTION_BORDER : null);
219  }
220 
221  protected void layoutChildren() {
222  super.layoutChildren();
223  }
224 
230  void installActionButtons() {
231  if (pinButton == null) {
232  pinButton = new Button();
233  controlsHBox.getChildren().add(pinButton);
234  configureActionButton(pinButton);
235  }
236  }
237 
238  final void showHoverControls(final boolean showControls) {
239  Effect dropShadow = dropShadowMap.computeIfAbsent(getEventType(),
240  eventType -> new DropShadow(-10, eventType.getColor()));
241  setEffect(showControls ? dropShadow : null);
242  installTooltip();
243  enableTooltip(showControls);
244  installActionButtons();
245 
246  TimeLineController controller = getChartLane().getController();
247 
248  if (controller.getPinnedEvents().contains(tlEvent)) {
249  pinButton.setOnAction(actionEvent -> {
250  new UnPinEventAction(controller, tlEvent).handle(actionEvent);
251  showHoverControls(true);
252  });
253  pinButton.setGraphic(new ImageView(UNPIN));
254  } else {
255  pinButton.setOnAction(actionEvent -> {
256  new PinEventAction(controller, tlEvent).handle(actionEvent);
257  showHoverControls(true);
258  });
259  pinButton.setGraphic(new ImageView(PIN));
260  }
261 
262  show(controlsHBox, showControls);
263  if (parentNode != null) {
264  parentNode.showHoverControls(false);
265  }
266  }
267 
272  @NbBundle.Messages({"# {0} - counts",
273  "# {1} - event type",
274  "# {2} - description",
275  "# {3} - start date/time",
276  "# {4} - end date/time",
277  "EventNodeBase.tooltip.text={0} {1} events\n{2}\nbetween\t{3}\nand \t{4}",
278  "EventNodeBase.toolTip.loading2=loading tooltip",
279  "# {0} - hash set count string",
280  "EventNodeBase.toolTip.hashSetHits=\n\nHash Set Hits\n{0}",
281  "# {0} - tag count string",
282  "EventNodeBase.toolTip.tags=\n\nTags\n{0}"})
283  @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
284  void installTooltip() {
285  if (tooltip.getText().equalsIgnoreCase(Bundle.EventBundleNodeBase_toolTip_loading())) {
286  final Task<String> tooltTipTask = new Task<String>() {
287  {
288  updateTitle(Bundle.EventNodeBase_toolTip_loading2());
289  }
290 
291  @Override
292  protected String call() throws Exception {
293  HashMap<String, Long> hashSetCounts = new HashMap<>();
294  if (tlEvent.getEventIDsWithHashHits().isEmpty() == false) {
295  try {
296  //TODO:push this to DB
297  for (SingleEvent tle : eventsModel.getEventsById(tlEvent.getEventIDsWithHashHits())) {
298  Set<String> hashSetNames = sleuthkitCase.getAbstractFileById(tle.getFileID()).getHashSetNames();
299  for (String hashSetName : hashSetNames) {
300  hashSetCounts.merge(hashSetName, 1L, Long::sum);
301  }
302  }
303  } catch (TskCoreException ex) {
304  LOGGER.log(Level.SEVERE, "Error getting hashset hit info for event.", ex); //NON-NLS
305  }
306  }
307  String hashSetCountsString = hashSetCounts.entrySet().stream()
308  .map((Map.Entry<String, Long> t) -> t.getKey() + " : " + t.getValue())
309  .collect(Collectors.joining("\n"));
310 
311  Map<String, Long> tagCounts = new HashMap<>();
312  if (tlEvent.getEventIDsWithTags().isEmpty() == false) {
313  tagCounts.putAll(eventsModel.getTagCountsByTagName(tlEvent.getEventIDsWithTags()));
314  }
315  String tagCountsString = tagCounts.entrySet().stream()
316  .map((Map.Entry<String, Long> t) -> t.getKey() + " : " + t.getValue())
317  .collect(Collectors.joining("\n"));
318 
319  return Bundle.EventNodeBase_tooltip_text(getEventIDs().size(), getEventType(), getDescription(),
320  TimeLineController.getZonedFormatter().print(getStartMillis()),
321  TimeLineController.getZonedFormatter().print(getEndMillis() + 1000))
322  + (hashSetCountsString.isEmpty() ? "" : Bundle.EventNodeBase_toolTip_hashSetHits(hashSetCountsString))
323  + (tagCountsString.isEmpty() ? "" : Bundle.EventNodeBase_toolTip_tags(tagCountsString));
324  }
325 
326  @Override
327  protected void succeeded() {
328  super.succeeded();
329  try {
330  tooltip.setText(get());
331  tooltip.setGraphic(null);
332  } catch (InterruptedException | ExecutionException ex) {
333  LOGGER.log(Level.SEVERE, "Tooltip generation failed.", ex); //NON-NLS
334  }
335  }
336  };
337  new Thread(tooltTipTask).start();
338  chartLane.getController().monitorTask(tooltTipTask);
339  }
340  }
341 
342  void enableTooltip(boolean toolTipEnabled) {
343  if (toolTipEnabled) {
344  Tooltip.install(this, tooltip);
345  } else {
346  Tooltip.uninstall(this, tooltip);
347  }
348  }
349 
350  final EventType getEventType() {
351  return tlEvent.getEventType();
352  }
353 
354  long getStartMillis() {
355  return tlEvent.getStartMillis();
356  }
357 
358  final long getEndMillis() {
359  return tlEvent.getEndMillis();
360  }
361 
362  final double getLayoutXCompensation() {
363  return parentNode != null
364  ? getChartLane().getXAxis().getDisplayPosition(new DateTime(parentNode.getStartMillis()))
365  : 0;
366  }
367 
368  abstract String getDescription();
369 
370  void animateTo(double xLeft, double yTop) {
371  if (timeline != null) {
372  timeline.stop();
373  Platform.runLater(this::requestChartLayout);
374  }
375  timeline = new Timeline(new KeyFrame(Duration.millis(100),
376  new KeyValue(layoutXProperty(), xLeft),
377  new KeyValue(layoutYProperty(), yTop))
378  );
379  timeline.setOnFinished(finished -> Platform.runLater(this::requestChartLayout));
380  timeline.play();
381  }
382 
383  void requestChartLayout() {
384  getChartLane().requestChartLayout();
385  }
386 
387  @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
388  void setDescriptionVisibility(DescriptionVisibility descrVis) {
389  final int size = getEvent().getSize();
390  switch (descrVis) {
391  case HIDDEN:
392  hideDescription();
393  break;
394  case COUNT_ONLY:
395  showCountOnly(size);
396  break;
397  case SHOWN:
398  default:
399  showFullDescription(size);
400  break;
401  }
402  }
403 
404  void showCountOnly(final int size) {
405  descrLabel.setText("");
406  countLabel.setText(String.valueOf(size));
407  }
408 
409  void hideDescription() {
410  countLabel.setText("");
411  descrLabel.setText("");
412  }
413 
419  synchronized void applyHighlightEffect(boolean applied) {
420  if (applied) {
421  descrLabel.setStyle("-fx-font-weight: bold;"); // NON-NLS
422  setBackground(highlightedBackground);
423  } else {
424  descrLabel.setStyle("-fx-font-weight: normal;"); // NON-NLS
425  setBackground(defaultBackground);
426  }
427  }
428 
429  void applyHighlightEffect() {
430  applyHighlightEffect(true);
431  }
432 
433  void clearHighlightEffect() {
434  applyHighlightEffect(false);
435  }
436 
437  abstract Collection<Long> getEventIDs();
438 
439  abstract EventHandler<MouseEvent> getDoubleClickHandler();
440 
441  Iterable<? extends Action> getActions() {
442  if (getController().getPinnedEvents().contains(getEvent())) {
443  return Arrays.asList(new UnPinEventAction(getController(), getEvent()));
444  } else {
445  return Arrays.asList(new PinEventAction(getController(), getEvent()));
446  }
447  }
448 
449  @Deprecated
450  @Override
451  final public void clearContextMenu() {
452  }
453 
454  public ContextMenu getContextMenu(MouseEvent mouseEvent) {
455  ContextMenu chartContextMenu = chartLane.getContextMenu(mouseEvent);
456 
457  ContextMenu contextMenu = ActionUtils.createContextMenu(Lists.newArrayList(getActions()));
458  contextMenu.getItems().add(new SeparatorMenuItem());
459  contextMenu.getItems().addAll(chartContextMenu.getItems());
460  contextMenu.setAutoHide(true);
461  return contextMenu;
462  }
463 
464  void showFullDescription(final int size) {
465  countLabel.setText((size == 1) ? "" : " (" + size + ")"); // NON-NLS
466  String description = getParentNode().map(pNode ->
467  " ..." + StringUtils.substringAfter(getEvent().getDescription(), parentNode.getDescription()))
468  .orElseGet(getEvent()::getDescription);
469 
470  descrLabel.setText(description);
471  }
472 
473  @Subscribe
475  if (false == Sets.intersection(getEvent().getEventIDs(), event.getUpdatedEventIDs()).isEmpty()) {
476  Platform.runLater(() -> {
477  show(tagIV, true);
478  });
479  }
480  }
481 
485  @Subscribe
487  Sets.SetView<Long> difference = Sets.difference(getEvent().getEventIDs(), event.getUpdatedEventIDs());
488 
489  if (false == difference.isEmpty()) {
490  Platform.runLater(() -> {
491  show(tagIV, true);
492  });
493  }
494  }
495 
496  private static class PinEventAction extends Action {
497 
498  @NbBundle.Messages({"PinEventAction.text=Pin"})
499  PinEventAction(TimeLineController controller, TimeLineEvent event) {
500  super(Bundle.PinEventAction_text());
501  setEventHandler(actionEvent -> controller.pinEvent(event));
502  setGraphic(new ImageView(PIN));
503  }
504  }
505 
506  private static class UnPinEventAction extends Action {
507 
508  @NbBundle.Messages({"UnPinEventAction.text=Unpin"})
510  super(Bundle.UnPinEventAction_text());
511  setEventHandler(actionEvent -> controller.unPinEvent(event));
512  setGraphic(new ImageView(UNPIN));
513  }
514  }
515 
519  private class ClickHandler implements EventHandler<MouseEvent> {
520 
521  @Override
522  public void handle(MouseEvent t) {
523  if (t.getButton() == MouseButton.PRIMARY) {
524  if (t.getClickCount() > 1) {
525  getDoubleClickHandler().handle(t);
526  } else if (t.isShiftDown()) {
527  chartLane.getSelectedNodes().add(EventNodeBase.this);
528  } else if (t.isShortcutDown()) {
529  chartLane.getSelectedNodes().removeAll(EventNodeBase.this);
530  } else {
531  chartLane.getSelectedNodes().setAll(EventNodeBase.this);
532  }
533  t.consume();
534  } else if (t.isPopupTrigger() && t.isStillSincePress()) {
535  getContextMenu(t).show(EventNodeBase.this, t.getScreenX(), t.getScreenY());
536  t.consume();
537  }
538  }
539  }
540 }
abstract List< EventNodeBase<?> > getSubNodes()
Set< SingleEvent > getEventsById(Collection< Long > eventIDs)
static final Map< EventType, Effect > dropShadowMap
Map< String, Long > getTagCountsByTagName(Set< Long > eventIDsWithTags)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124

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