Autopsy  4.20.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.List;
28 import java.util.Map;
29 import java.util.Optional;
30 import java.util.concurrent.ConcurrentHashMap;
31 import java.util.concurrent.ExecutionException;
32 import java.util.logging.Level;
33 import javafx.animation.KeyFrame;
34 import javafx.animation.KeyValue;
35 import javafx.animation.Timeline;
36 import javafx.application.Platform;
37 import javafx.concurrent.Task;
38 import javafx.event.EventHandler;
39 import javafx.geometry.Insets;
40 import javafx.scene.Node;
41 import javafx.scene.control.Button;
42 import javafx.scene.control.ButtonBase;
43 import javafx.scene.control.ContextMenu;
44 import javafx.scene.control.Label;
45 import javafx.scene.control.SeparatorMenuItem;
46 import javafx.scene.control.Tooltip;
47 import javafx.scene.effect.DropShadow;
48 import javafx.scene.effect.Effect;
49 import javafx.scene.image.Image;
50 import javafx.scene.image.ImageView;
51 import javafx.scene.input.MouseButton;
52 import javafx.scene.input.MouseEvent;
53 import javafx.scene.layout.Background;
54 import javafx.scene.layout.BackgroundFill;
55 import javafx.scene.layout.Border;
56 import javafx.scene.layout.BorderStroke;
57 import javafx.scene.layout.BorderStrokeStyle;
58 import javafx.scene.layout.BorderWidths;
59 import javafx.scene.layout.HBox;
60 import javafx.scene.layout.StackPane;
61 import javafx.scene.paint.Color;
62 import javafx.util.Duration;
63 import org.apache.commons.lang3.StringUtils;
64 import org.controlsfx.control.action.Action;
65 import org.controlsfx.control.action.ActionUtils;
66 import org.joda.time.DateTime;
67 import org.openide.util.NbBundle;
79 import static org.sleuthkit.autopsy.timeline.ui.detailview.MultiEventNodeBase.CORNER_RADII_3;
81 import org.sleuthkit.datamodel.SleuthkitCase;
82 import org.sleuthkit.datamodel.TimelineEventType;
83 
87 public abstract class EventNodeBase<Type extends DetailViewEvent> extends StackPane implements ContextMenuProvider {
88 
89  private static final Logger LOGGER = Logger.getLogger(EventNodeBase.class.getName());
90 
91  private static final Image HASH_HIT = 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  private static final Image PIN = new Image("/org/sleuthkit/autopsy/timeline/images/marker--plus.png"); // NON-NLS //NOI18N
94  private static final Image UNPIN = new Image("/org/sleuthkit/autopsy/timeline/images/marker--minus.png"); // NON-NLS //NOI18N
95 
96  private static final Map<TimelineEventType, Effect> dropShadowMap = new ConcurrentHashMap<>();
97 
98  static void configureActionButton(ButtonBase b) {
99  b.setMinSize(16, 16);
100  b.setMaxSize(16, 16);
101  b.setPrefSize(16, 16);
102  }
103 
104  static void show(Node b, boolean show) {
105  b.setVisible(show);
106  b.setManaged(show);
107  }
108 
109  private final Type tlEvent;
110 
112 
113  final DetailsChartLane<?> chartLane;
114  final Background highlightedBackground;
115  final Background defaultBackground;
116  final Color evtColor;
117 
118  final Label countLabel = new Label();
119  final Label descrLabel = new Label();
120  final ImageView hashIV = new ImageView(HASH_HIT);
121  final ImageView tagIV = new ImageView(TAG);
122  final ImageView eventTypeImageView = new ImageView();
123 
124  final Tooltip tooltip = new Tooltip(Bundle.EventBundleNodeBase_toolTip_loading());
125 
126  final HBox controlsHBox = new HBox(5);
127  final HBox infoHBox = new HBox(5, eventTypeImageView, hashIV, tagIV, descrLabel, countLabel, controlsHBox);
128  final SleuthkitCase sleuthkitCase;
129  final EventsModel eventsModel;
130  private Timeline timeline;
131  private Button pinButton;
132  private final Border SELECTION_BORDER;
133 
134  EventNodeBase(Type tlEvent, EventNodeBase<?> parent, DetailsChartLane<?> chartLane) {
135  this.chartLane = chartLane;
136  this.tlEvent = tlEvent;
137  this.parentNode = parent;
138 
139  sleuthkitCase = chartLane.getController().getAutopsyCase().getSleuthkitCase();
140  eventsModel = chartLane.getController().getEventsModel();
141  eventTypeImageView.setImage(new Image(getImagePath(getEventType())));
142 
143  if (tlEvent.getEventIDsWithHashHits().isEmpty()) {
144  show(hashIV, false);
145  }
146 
147  if (tlEvent.getEventIDsWithTags().isEmpty()) {
148  show(tagIV, false);
149  }
150 
151  if (chartLane.getController().getEventsModel().getEventTypeZoom() == TimelineEventType.HierarchyLevel.CATEGORY) {
152  evtColor = getColor(getEventType());
153  } else {
154  evtColor = getColor(getEventType().getCategory());
155  }
156  SELECTION_BORDER = new Border(new BorderStroke(evtColor.darker().desaturate(), BorderStrokeStyle.SOLID, CORNER_RADII_3, new BorderWidths(2)));
157 
158  defaultBackground = new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .1), CORNER_RADII_3, Insets.EMPTY));
159  highlightedBackground = new Background(new BackgroundFill(evtColor.deriveColor(0, 1.1, 1.1, .3), CORNER_RADII_3, Insets.EMPTY));
160  setBackground(defaultBackground);
161 
162  Tooltip.install(this, this.tooltip);
163 
164  //set up mouse hover effect and tooltip
165  setOnMouseEntered(mouseEntered -> {
166  Tooltip.uninstall(chartLane, AbstractTimelineChart.getDefaultTooltip());
167  showHoverControls(true);
168  toFront();
169  });
170 
171  setOnMouseExited(mouseExited -> {
172  showHoverControls(false);
173  if (parentNode != null) {
174  parentNode.showHoverControls(true);
175  } else {
176  Tooltip.install(chartLane, AbstractTimelineChart.getDefaultTooltip());
177  }
178  });
179  setOnMouseClicked(new ClickHandler());
180  show(controlsHBox, false);
181  }
182 
183  public Type getEvent() {
184  return tlEvent;
185  }
186 
187  @Override
189  return chartLane.getController();
190  }
191 
192  public Optional<EventNodeBase<?>> getParentNode() {
193  return Optional.ofNullable(parentNode);
194  }
195 
196  DetailsChartLane<?> getChartLane() {
197  return chartLane;
198  }
199 
203  public void setMaxDescriptionWidth(double w) {
204  descrLabel.setMaxWidth(w);
205  }
206 
207  public abstract List<EventNodeBase<?>> getSubNodes();
208 
214  public void applySelectionEffect(boolean applied) {
215  setBorder(applied ? SELECTION_BORDER : null);
216  }
217 
218  @Override
219  protected void layoutChildren() {
220  super.layoutChildren();
221  }
222 
228  void installActionButtons() {
229  if (pinButton == null) {
230  pinButton = new Button();
231  controlsHBox.getChildren().add(pinButton);
232  configureActionButton(pinButton);
233  }
234  }
235 
236  final void showHoverControls(final boolean showControls) {
237  Effect dropShadow = dropShadowMap.computeIfAbsent(getEventType(),
238  eventType -> new DropShadow(-10, getColor(eventType)));
239  setEffect(showControls ? dropShadow : null);
240  installTooltip();
241  enableTooltip(showControls);
242  installActionButtons();
243 
244  TimeLineController controller = getChartLane().getController();
245 
246  if (controller.getPinnedEvents().contains(tlEvent)) {
247  pinButton.setOnAction(actionEvent -> {
248  new UnPinEventAction(controller, tlEvent).handle(actionEvent);
249  showHoverControls(true);
250  });
251  pinButton.setGraphic(new ImageView(UNPIN));
252  } else {
253  pinButton.setOnAction(actionEvent -> {
254  new PinEventAction(controller, tlEvent).handle(actionEvent);
255  showHoverControls(true);
256  });
257  pinButton.setGraphic(new ImageView(PIN));
258  }
259 
260  show(controlsHBox, showControls);
261  if (parentNode != null) {
262  parentNode.showHoverControls(false);
263  }
264  }
265 
270  @NbBundle.Messages({"# {0} - counts",
271  "# {1} - event type",
272  "# {2} - description",
273  "# {3} - start date/time",
274  "# {4} - end date/time",
275  "EventNodeBase.tooltip.text={0} {1} events\n{2}\nbetween\t{3}\nand \t{4}",
276  "EventNodeBase.toolTip.loading2=loading tooltip",
277  "# {0} - hash set count string",
278  "EventNodeBase.toolTip.hashSetHits=\n\nHash Set Hits\n{0}",
279  "# {0} - tag count string",
280  "EventNodeBase.toolTip.tags=\n\nTags\n{0}"})
281  @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
282  void installTooltip() {
283  if (tooltip.getText().equalsIgnoreCase(Bundle.EventBundleNodeBase_toolTip_loading())) {
284  final Task<String> tooltTipTask = new Task<String>() {
285  {
286  updateTitle(Bundle.EventNodeBase_toolTip_loading2());
287  }
288 
289  @Override
290  protected String call() throws Exception {
291  return Bundle.EventNodeBase_tooltip_text(getEventIDs().size(), getEventType(), getDescription(),
292  TimeLineController.getZonedFormatter().print(getStartMillis()),
293  TimeLineController.getZonedFormatter().print(getEndMillis() + 1000));
294  }
295 
296  @Override
297  protected void done() {
298  super.succeeded();
299  try {
300  tooltip.setText(get());
301  tooltip.setGraphic(null);
302  } catch (InterruptedException | ExecutionException ex) {
303  LOGGER.log(Level.SEVERE, "Tooltip generation failed.", ex); //NON-NLS
304  }
305  }
306  };
307  new Thread(tooltTipTask).start();
308  chartLane.getController().monitorTask(tooltTipTask);
309  }
310  }
311 
312  void enableTooltip(boolean toolTipEnabled) {
313  if (toolTipEnabled) {
314  Tooltip.install(this, tooltip);
315  } else {
316  Tooltip.uninstall(this, tooltip);
317  }
318  }
319 
320  final TimelineEventType getEventType() {
321  return tlEvent.getEventType();
322  }
323 
324  long getStartMillis() {
325  return tlEvent.getStartMillis();
326  }
327 
328  final long getEndMillis() {
329  return tlEvent.getEndMillis();
330  }
331 
332  final double getLayoutXCompensation() {
333  return parentNode != null
334  ? getChartLane().getXAxis().getDisplayPosition(new DateTime(parentNode.getStartMillis()))
335  : 0;
336  }
337 
338  abstract String getDescription();
339 
340  void animateTo(double xLeft, double yTop) {
341  if (timeline != null) {
342  timeline.stop();
343  Platform.runLater(this::requestChartLayout);
344  }
345  timeline = new Timeline(new KeyFrame(Duration.millis(100),
346  new KeyValue(layoutXProperty(), xLeft),
347  new KeyValue(layoutYProperty(), yTop))
348  );
349  timeline.setOnFinished(finished -> Platform.runLater(this::requestChartLayout));
350  timeline.play();
351  }
352 
353  void requestChartLayout() {
354  getChartLane().requestChartLayout();
355  }
356 
357  @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
358  void setDescriptionVisibility(DescriptionVisibility descrVis) {
359  final int size = getEvent().getSize();
360  switch (descrVis) {
361  case HIDDEN:
362  hideDescription();
363  break;
364  case COUNT_ONLY:
365  showCountOnly(size);
366  break;
367  case SHOWN:
368  default:
369  showFullDescription(size);
370  break;
371  }
372  }
373 
374  void showCountOnly(final int size) {
375  descrLabel.setText("");
376  countLabel.setText(String.valueOf(size));
377  }
378 
379  void hideDescription() {
380  countLabel.setText("");
381  descrLabel.setText("");
382  }
383 
389  synchronized void applyHighlightEffect(boolean applied) {
390  if (applied) {
391  descrLabel.setStyle("-fx-font-weight: bold;"); // NON-NLS
392  setBackground(highlightedBackground);
393  } else {
394  descrLabel.setStyle("-fx-font-weight: normal;"); // NON-NLS
395  setBackground(defaultBackground);
396  }
397  }
398 
399  void applyHighlightEffect() {
400  applyHighlightEffect(true);
401  }
402 
403  void clearHighlightEffect() {
404  applyHighlightEffect(false);
405  }
406 
407  abstract Collection<Long> getEventIDs();
408 
409  abstract EventHandler<MouseEvent> getDoubleClickHandler();
410 
411  Iterable<? extends Action> getActions() {
412  if (getController().getPinnedEvents().contains(getEvent())) {
413  return Arrays.asList(new UnPinEventAction(getController(), getEvent()));
414  } else {
415  return Arrays.asList(new PinEventAction(getController(), getEvent()));
416  }
417  }
418 
419  @Deprecated
420  @Override
421  final public void clearContextMenu() {
422  }
423 
424  public ContextMenu getContextMenu(MouseEvent mouseEvent) {
425  ContextMenu chartContextMenu = chartLane.getContextMenu(mouseEvent);
426 
427  ContextMenu contextMenu = ActionUtils.createContextMenu(Lists.newArrayList(getActions()));
428  contextMenu.getItems().add(new SeparatorMenuItem());
429  contextMenu.getItems().addAll(chartContextMenu.getItems());
430  contextMenu.setAutoHide(true);
431  return contextMenu;
432  }
433 
434  void showFullDescription(final int size) {
435  countLabel.setText((size == 1) ? "" : " (" + size + ")"); // NON-NLS
436  String description = getParentNode().map(pNode
437  -> " ..." + StringUtils.substringAfter(getEvent().getDescription(), parentNode.getDescription()))
438  .orElseGet(getEvent()::getDescription);
439 
440  descrLabel.setText(description);
441  }
442 
443  @Subscribe
445  if (false == Sets.intersection(getEvent().getEventIDs(), event.getUpdatedEventIDs()).isEmpty()) {
446  Platform.runLater(() -> {
447  show(tagIV, true);
448  });
449  }
450  }
451 
455  @Subscribe
457  Sets.SetView<Long> difference = Sets.difference(getEvent().getEventIDs(), event.getUpdatedEventIDs());
458 
459  if (false == difference.isEmpty()) {
460  Platform.runLater(() -> {
461  show(tagIV, true);
462  });
463  }
464  }
465 
466  private static class PinEventAction extends Action {
467 
468  @NbBundle.Messages({"PinEventAction.text=Pin"})
470  super(Bundle.PinEventAction_text());
471  setEventHandler(actionEvent -> controller.pinEvent(event));
472  setGraphic(new ImageView(PIN));
473  }
474  }
475 
476  private static class UnPinEventAction extends Action {
477 
478  @NbBundle.Messages({"UnPinEventAction.text=Unpin"})
480  super(Bundle.UnPinEventAction_text());
481  setEventHandler(actionEvent -> controller.unPinEvent(event));
482  setGraphic(new ImageView(UNPIN));
483  }
484  }
485 
489  private class ClickHandler implements EventHandler<MouseEvent> {
490 
491  @Override
492  public void handle(MouseEvent t) {
493  if (t.getButton() == MouseButton.PRIMARY) {
494  if (t.getClickCount() > 1) {
495  getDoubleClickHandler().handle(t);
496  } else if (t.isShiftDown()) {
497  chartLane.getSelectedNodes().add(EventNodeBase.this);
498  } else if (t.isShortcutDown()) {
499  chartLane.getSelectedNodes().removeAll(EventNodeBase.this);
500  } else {
501  chartLane.getSelectedNodes().setAll(EventNodeBase.this);
502  }
503  t.consume();
504  } else if (t.isPopupTrigger() && t.isStillSincePress()) {
505  getContextMenu(t).show(EventNodeBase.this, t.getScreenX(), t.getScreenY());
506  t.consume();
507  }
508  }
509  }
510 }
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-2022 Basis Technology. Generated on: Tue Aug 1 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.