Autopsy 4.22.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-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 */
20package org.sleuthkit.autopsy.timeline.ui.detailview;
21
22import com.google.common.collect.Lists;
23import com.google.common.collect.Sets;
24import com.google.common.eventbus.Subscribe;
25import java.util.Arrays;
26import java.util.Collection;
27import java.util.List;
28import java.util.Map;
29import java.util.Optional;
30import java.util.concurrent.ConcurrentHashMap;
31import java.util.concurrent.ExecutionException;
32import java.util.logging.Level;
33import javafx.animation.KeyFrame;
34import javafx.animation.KeyValue;
35import javafx.animation.Timeline;
36import javafx.application.Platform;
37import javafx.concurrent.Task;
38import javafx.event.EventHandler;
39import javafx.geometry.Insets;
40import javafx.scene.Node;
41import javafx.scene.control.Button;
42import javafx.scene.control.ButtonBase;
43import javafx.scene.control.ContextMenu;
44import javafx.scene.control.Label;
45import javafx.scene.control.SeparatorMenuItem;
46import javafx.scene.control.Tooltip;
47import javafx.scene.effect.DropShadow;
48import javafx.scene.effect.Effect;
49import javafx.scene.image.Image;
50import javafx.scene.image.ImageView;
51import javafx.scene.input.MouseButton;
52import javafx.scene.input.MouseEvent;
53import javafx.scene.layout.Background;
54import javafx.scene.layout.BackgroundFill;
55import javafx.scene.layout.Border;
56import javafx.scene.layout.BorderStroke;
57import javafx.scene.layout.BorderStrokeStyle;
58import javafx.scene.layout.BorderWidths;
59import javafx.scene.layout.HBox;
60import javafx.scene.layout.StackPane;
61import javafx.scene.paint.Color;
62import javafx.util.Duration;
63import org.apache.commons.lang3.StringUtils;
64import org.controlsfx.control.action.Action;
65import org.controlsfx.control.action.ActionUtils;
66import org.joda.time.DateTime;
67import org.openide.util.NbBundle;
68import org.sleuthkit.autopsy.coreutils.Logger;
69import org.sleuthkit.autopsy.coreutils.ThreadConfined;
70import org.sleuthkit.autopsy.timeline.EventsModel;
71import org.sleuthkit.autopsy.timeline.TimeLineController;
72import org.sleuthkit.autopsy.timeline.events.TagsAddedEvent;
73import org.sleuthkit.autopsy.timeline.events.TagsDeletedEvent;
74import org.sleuthkit.autopsy.timeline.ui.AbstractTimelineChart;
75import org.sleuthkit.autopsy.timeline.ui.ContextMenuProvider;
76import static org.sleuthkit.autopsy.timeline.ui.EventTypeUtils.getColor;
77import static org.sleuthkit.autopsy.timeline.ui.EventTypeUtils.getImagePath;
78import static org.sleuthkit.autopsy.timeline.ui.detailview.EventNodeBase.show;
79import static org.sleuthkit.autopsy.timeline.ui.detailview.MultiEventNodeBase.CORNER_RADII_3;
80import org.sleuthkit.autopsy.timeline.ui.detailview.datamodel.DetailViewEvent;
81import org.sleuthkit.datamodel.SleuthkitCase;
82import org.sleuthkit.datamodel.TimelineEventType;
83
87public 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"})
469 PinEventAction(TimeLineController controller, DetailViewEvent event) {
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"})
479 UnPinEventAction(TimeLineController controller, DetailViewEvent event) {
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}
synchronized static Logger getLogger(String name)
Definition Logger.java:124
abstract List< EventNodeBase<?> > getSubNodes()
static final Map< TimelineEventType, Effect > dropShadowMap

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.