20 package org.sleuthkit.autopsy.timeline.ui.detailview;
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;
30 import java.util.Optional;
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;
95 private static final Image
HASH_HIT =
new Image(
"/org/sleuthkit/autopsy/images/hashset_hits.png");
96 private static final Image
TAG =
new Image(
"/org/sleuthkit/autopsy/images/green-tag-icon-16.png");
97 private static final Image
PIN =
new Image(
"/org/sleuthkit/autopsy/timeline/images/marker--plus.png");
98 private static final Image
UNPIN =
new Image(
"/org/sleuthkit/autopsy/timeline/images/marker--minus.png");
100 private static final Map<EventType, Effect>
dropShadowMap =
new ConcurrentHashMap<>();
102 static void configureActionButton(ButtonBase b) {
103 b.setMinSize(16, 16);
104 b.setMaxSize(16, 16);
105 b.setPrefSize(16, 16);
108 static void show(Node b,
boolean show) {
117 final DetailsChartLane<?> chartLane;
118 final Background highlightedBackground;
119 final Background defaultBackground;
120 final Color evtColor;
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();
128 final Tooltip tooltip =
new Tooltip(Bundle.EventBundleNodeBase_toolTip_loading());
130 final HBox controlsHBox =
new HBox(5);
131 final HBox infoHBox =
new HBox(5, eventTypeImageView, hashIV, tagIV, descrLabel, countLabel, controlsHBox);
132 final SleuthkitCase sleuthkitCase;
139 this.chartLane = chartLane;
141 this.parentNode = parent;
143 sleuthkitCase = chartLane.getController().getAutopsyCase().getSleuthkitCase();
144 eventsModel = chartLane.getController().getEventsModel();
145 eventTypeImageView.setImage(getEventType().getFXImage());
147 if (tlEvent.getEventIDsWithHashHits().isEmpty()) {
151 if (tlEvent.getEventIDsWithTags().isEmpty()) {
156 evtColor = getEventType().
getColor();
160 SELECTION_BORDER =
new Border(
new BorderStroke(evtColor.darker().desaturate(), BorderStrokeStyle.SOLID, CORNER_RADII_3,
new BorderWidths(2)));
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);
166 Tooltip.install(
this, this.tooltip);
169 setOnMouseEntered(mouseEntered -> {
171 showHoverControls(
true);
174 setOnMouseExited(mouseExited -> {
175 showHoverControls(
false);
176 if (parentNode != null) {
177 parentNode.showHoverControls(
true);
182 setOnMouseClicked(
new ClickHandler());
183 show(controlsHBox,
false);
192 return chartLane.getController();
196 return Optional.ofNullable(parentNode);
199 DetailsChartLane<?> getChartLane() {
207 descrLabel.setMaxWidth(w);
210 public abstract List<EventNodeBase<?>>
getSubNodes();
218 setBorder(applied ? SELECTION_BORDER : null);
222 super.layoutChildren();
230 void installActionButtons() {
231 if (pinButton == null) {
232 pinButton =
new Button();
233 controlsHBox.getChildren().add(pinButton);
234 configureActionButton(pinButton);
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);
243 enableTooltip(showControls);
244 installActionButtons();
249 pinButton.setOnAction(actionEvent -> {
250 new UnPinEventAction(controller, tlEvent).handle(actionEvent);
251 showHoverControls(
true);
253 pinButton.setGraphic(
new ImageView(UNPIN));
255 pinButton.setOnAction(actionEvent -> {
256 new PinEventAction(controller, tlEvent).handle(actionEvent);
257 showHoverControls(
true);
259 pinButton.setGraphic(
new ImageView(PIN));
262 show(controlsHBox, showControls);
263 if (parentNode != null) {
264 parentNode.showHoverControls(
false);
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>() {
288 updateTitle(Bundle.EventNodeBase_toolTip_loading2());
292 protected String call() throws Exception {
293 HashMap<String, Long> hashSetCounts =
new HashMap<>();
294 if (tlEvent.getEventIDsWithHashHits().isEmpty() ==
false) {
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);
303 }
catch (TskCoreException ex) {
304 LOGGER.log(Level.SEVERE,
"Error getting hashset hit info for event.", ex);
307 String hashSetCountsString = hashSetCounts.entrySet().stream()
308 .map((Map.Entry<String, Long> t) -> t.getKey() +
" : " + t.getValue())
309 .collect(Collectors.joining(
"\n"));
311 Map<String, Long> tagCounts =
new HashMap<>();
312 if (tlEvent.getEventIDsWithTags().isEmpty() ==
false) {
315 String tagCountsString = tagCounts.entrySet().stream()
316 .map((Map.Entry<String, Long> t) -> t.getKey() +
" : " + t.getValue())
317 .collect(Collectors.joining(
"\n"));
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));
327 protected void succeeded() {
330 tooltip.setText(
get());
331 tooltip.setGraphic(null);
332 }
catch (InterruptedException | ExecutionException ex) {
333 LOGGER.log(Level.SEVERE,
"Tooltip generation failed.", ex);
337 new Thread(tooltTipTask).start();
338 chartLane.getController().monitorTask(tooltTipTask);
342 void enableTooltip(
boolean toolTipEnabled) {
343 if (toolTipEnabled) {
344 Tooltip.install(
this, tooltip);
346 Tooltip.uninstall(
this, tooltip);
350 final EventType getEventType() {
351 return tlEvent.getEventType();
354 long getStartMillis() {
355 return tlEvent.getStartMillis();
358 final long getEndMillis() {
359 return tlEvent.getEndMillis();
362 final double getLayoutXCompensation() {
363 return parentNode != null
364 ? getChartLane().getXAxis().getDisplayPosition(
new DateTime(parentNode.getStartMillis()))
368 abstract String getDescription();
370 void animateTo(
double xLeft,
double yTop) {
371 if (timeline != null) {
373 Platform.runLater(this::requestChartLayout);
375 timeline =
new Timeline(
new KeyFrame(Duration.millis(100),
376 new KeyValue(layoutXProperty(), xLeft),
377 new KeyValue(layoutYProperty(), yTop))
379 timeline.setOnFinished(finished -> Platform.runLater(this::requestChartLayout));
383 void requestChartLayout() {
384 getChartLane().requestChartLayout();
387 @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
388 void setDescriptionVisibility(DescriptionVisibility descrVis) {
389 final int size =
getEvent().getSize();
399 showFullDescription(size);
404 void showCountOnly(
final int size) {
405 descrLabel.setText(
"");
406 countLabel.setText(String.valueOf(size));
409 void hideDescription() {
410 countLabel.setText(
"");
411 descrLabel.setText(
"");
419 synchronized void applyHighlightEffect(
boolean applied) {
421 descrLabel.setStyle(
"-fx-font-weight: bold;");
422 setBackground(highlightedBackground);
424 descrLabel.setStyle(
"-fx-font-weight: normal;");
425 setBackground(defaultBackground);
429 void applyHighlightEffect() {
430 applyHighlightEffect(
true);
433 void clearHighlightEffect() {
434 applyHighlightEffect(
false);
437 abstract Collection<Long> getEventIDs();
439 abstract EventHandler<MouseEvent> getDoubleClickHandler();
441 Iterable<? extends Action> getActions() {
455 ContextMenu chartContextMenu = chartLane.getContextMenu(mouseEvent);
457 ContextMenu contextMenu = ActionUtils.createContextMenu(Lists.newArrayList(getActions()));
458 contextMenu.getItems().add(
new SeparatorMenuItem());
459 contextMenu.getItems().addAll(chartContextMenu.getItems());
460 contextMenu.setAutoHide(
true);
464 void showFullDescription(
final int size) {
465 countLabel.setText((size == 1) ?
"" :
" (" + size +
")");
467 " ..." + StringUtils.substringAfter(
getEvent().getDescription(), parentNode.getDescription()))
468 .orElseGet(
getEvent()::getDescription);
470 descrLabel.setText(description);
476 Platform.runLater(() -> {
489 if (
false == difference.isEmpty()) {
490 Platform.runLater(() -> {
498 @NbBundle.Messages({
"PinEventAction.text=Pin"})
500 super(Bundle.PinEventAction_text());
501 setEventHandler(actionEvent -> controller.
pinEvent(event));
502 setGraphic(
new ImageView(PIN));
508 @NbBundle.Messages({
"UnPinEventAction.text=Unpin"})
510 super(Bundle.UnPinEventAction_text());
511 setEventHandler(actionEvent -> controller.
unPinEvent(event));
512 setGraphic(
new ImageView(UNPIN));
523 if (t.getButton() == MouseButton.PRIMARY) {
524 if (t.getClickCount() > 1) {
525 getDoubleClickHandler().handle(t);
526 }
else if (t.isShiftDown()) {
528 }
else if (t.isShortcutDown()) {
534 }
else if (t.isPopupTrigger() && t.isStillSincePress()) {
Optional< EventNodeBase<?> > getParentNode()
abstract List< EventNodeBase<?> > getSubNodes()
ObservableSet< TimeLineEvent > getPinnedEvents()
final void clearContextMenu()
void handleTimeLineTagEvent(TagsAddedEvent event)
static final Image HASH_HIT
static Tooltip getDefaultTooltip()
static final Logger LOGGER
final EventNodeBase<?> parentNode
void pinEvent(TimeLineEvent event)
void applySelectionEffect(boolean applied)
void handle(MouseEvent t)
final Border SELECTION_BORDER
void handleTimeLineTagEvent(TagsDeletedEvent event)
Set< SingleEvent > getEventsById(Collection< Long > eventIDs)
static final Map< EventType, Effect > dropShadowMap
Map< String, Long > getTagCountsByTagName(Set< Long > eventIDsWithTags)
default BaseTypes getBaseType()
synchronized static Logger getLogger(String name)
void setMaxDescriptionWidth(double w)
ContextMenu getContextMenu(MouseEvent mouseEvent)
void unPinEvent(TimeLineEvent event)
TimeLineController getController()