19 package org.sleuthkit.autopsy.timeline.ui.detailview;
21 import java.util.Collection;
22 import java.util.HashMap;
23 import java.util.List;
26 import java.util.concurrent.ConcurrentHashMap;
27 import java.util.concurrent.ExecutionException;
28 import java.util.logging.Level;
29 import java.util.stream.Collectors;
30 import javafx.animation.KeyFrame;
31 import javafx.animation.KeyValue;
32 import javafx.animation.Timeline;
33 import javafx.application.Platform;
34 import javafx.beans.binding.Bindings;
35 import javafx.beans.property.SimpleObjectProperty;
36 import javafx.collections.FXCollections;
37 import javafx.collections.ObservableList;
38 import javafx.concurrent.Task;
39 import javafx.event.EventHandler;
40 import javafx.geometry.Insets;
41 import javafx.geometry.Orientation;
42 import javafx.geometry.Pos;
43 import javafx.scene.Node;
44 import javafx.scene.control.Button;
45 import javafx.scene.control.ContextMenu;
46 import javafx.scene.control.Label;
47 import javafx.scene.control.SeparatorMenuItem;
48 import javafx.scene.control.Tooltip;
49 import javafx.scene.effect.DropShadow;
50 import javafx.scene.effect.Effect;
51 import javafx.scene.image.Image;
52 import javafx.scene.image.ImageView;
53 import javafx.scene.input.MouseButton;
54 import javafx.scene.input.MouseEvent;
55 import javafx.scene.layout.Background;
56 import javafx.scene.layout.BackgroundFill;
57 import javafx.scene.layout.Border;
58 import javafx.scene.layout.BorderStroke;
59 import javafx.scene.layout.BorderStrokeStyle;
60 import javafx.scene.layout.BorderWidths;
61 import javafx.scene.layout.CornerRadii;
62 import javafx.scene.layout.HBox;
63 import javafx.scene.layout.Pane;
64 import javafx.scene.layout.StackPane;
65 import javafx.scene.paint.Color;
66 import javafx.util.Duration;
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;
87 @NbBundle.Messages({
"EventBundleNodeBase.toolTip.loading=loading..."})
91 private static final Image HASH_PIN =
new Image(
"/org/sleuthkit/autopsy/images/hashset_hits.png");
92 private static final Image TAG =
new Image(
"/org/sleuthkit/autopsy/images/green-tag-icon-16.png");
94 static final CornerRadii CORNER_RADII_3 =
new CornerRadii(3);
95 static final CornerRadii CORNER_RADII_1 =
new CornerRadii(1);
98 private static final Map<EventType, Effect> dropShadowMap =
new ConcurrentHashMap<>();
100 static void configureLoDButton(Button b) {
101 b.setMinSize(16, 16);
102 b.setMaxSize(16, 16);
103 b.setPrefSize(16, 16);
107 static void show(Node b,
boolean show) {
113 final SimpleObjectProperty<DescriptionLoD> descLOD =
new SimpleObjectProperty<>();
114 final SimpleObjectProperty<DescriptionVisibility> descVisibility =
new SimpleObjectProperty<>();
119 final SleuthkitCase sleuthkitCase;
122 final Background highlightedBackground;
123 final Background defaultBackground;
124 final Color evtColor;
126 final ObservableList<ParentNodeType> subNodes = FXCollections.observableArrayList();
127 final Pane subNodePane =
new Pane();
128 final Label descrLabel =
new Label();
129 final Label countLabel =
new Label();
131 final ImageView hashIV =
new ImageView(HASH_PIN);
132 final ImageView tagIV =
new ImageView(TAG);
133 final HBox infoHBox =
new HBox(5, descrLabel, countLabel, hashIV, tagIV);
135 private final Tooltip tooltip =
new Tooltip(Bundle.EventBundleNodeBase_toolTip_loading());
139 this.eventBundle = eventBundle;
140 this.parentNode = parentNode;
142 this.descLOD.set(eventBundle.getDescriptionLoD());
145 evtColor = getEventType().getColor();
146 defaultBackground =
new Background(
new BackgroundFill(evtColor.deriveColor(0, 1, 1, .1), CORNER_RADII_3, Insets.EMPTY));
147 highlightedBackground =
new Background(
new BackgroundFill(evtColor.deriveColor(0, 1.1, 1.1, .3), CORNER_RADII_3, Insets.EMPTY));
148 SELECTION_BORDER =
new Border(
new BorderStroke(evtColor.darker().desaturate(), BorderStrokeStyle.SOLID, CORNER_RADII_3,
new BorderWidths(2)));
149 if (eventBundle.getEventIDsWithHashHits().isEmpty()) {
152 if (eventBundle.getEventIDsWithTags().isEmpty()) {
156 setBackground(defaultBackground);
157 setAlignment(Pos.TOP_LEFT);
158 setMaxWidth(USE_PREF_SIZE);
159 infoHBox.setMaxWidth(USE_PREF_SIZE);
160 subNodePane.setPrefWidth(USE_COMPUTED_SIZE);
161 subNodePane.setMinWidth(USE_PREF_SIZE);
162 subNodePane.setMaxWidth(USE_PREF_SIZE);
168 Platform.runLater(() ->
169 setLayoutX(chart.
getXAxis().getDisplayPosition(
new DateTime(eventBundle.getStartMillis())) - getLayoutXCompensation())
173 infoHBox.setPadding(
new Insets(2, 3, 2, 3));
174 infoHBox.setAlignment(Pos.TOP_LEFT);
176 Tooltip.install(
this, this.tooltip);
179 setOnMouseEntered((MouseEvent e) -> {
182 showHoverControls(
true);
185 setOnMouseExited((MouseEvent event) -> {
186 showHoverControls(
false);
187 if (parentNode != null) {
188 parentNode.showHoverControls(
true);
193 setOnMouseClicked(
new ClickHandler());
194 descVisibility.addListener(observable -> setDescriptionVisibiltiyImpl(descVisibility.get()));
197 Bindings.bindContent(subNodePane.getChildren(), subNodes);
201 return descLOD.get();
208 final double getLayoutXCompensation() {
209 return parentNode != null
210 ? chart.
getXAxis().getDisplayPosition(
new DateTime(parentNode.getStartMillis()))
219 abstract void installActionButtons();
225 @NbBundle.Messages({
"# {0} - counts",
226 "# {1} - event type",
227 "# {2} - description",
228 "# {3} - start date/time",
229 "# {4} - end date/time",
230 "EventBundleNodeBase.tooltip.text={0} {1} events\n{2}\nbetween\t{3}\nand \t{4}",
231 "EventBundleNodeBase.toolTip.loading2=loading tooltip",
232 "# {0} - hash set count string",
233 "EventBundleNodeBase.toolTip.hashSetHits=\n\nHash Set Hits\n{0}",
234 "# {0} - tag count string",
235 "EventBundleNodeBase.toolTip.tags=\n\nTags\n{0}"})
238 if (tooltip.getText().equalsIgnoreCase(Bundle.EventBundleNodeBase_toolTip_loading())) {
239 final Task<String> tooltTipTask =
new Task<String>() {
241 updateTitle(Bundle.EventBundleNodeBase_toolTip_loading2());
245 protected String call()
throws Exception {
246 HashMap<String, Long> hashSetCounts =
new HashMap<>();
247 if (eventBundle.getEventIDsWithHashHits().isEmpty() ==
false) {
251 Set<String> hashSetNames = sleuthkitCase.getAbstractFileById(tle.getFileID()).getHashSetNames();
252 for (String hashSetName : hashSetNames) {
253 hashSetCounts.merge(hashSetName, 1L, Long::sum);
256 }
catch (TskCoreException ex) {
257 LOGGER.log(Level.SEVERE,
"Error getting hashset hit info for event.", ex);
260 String hashSetCountsString = hashSetCounts.entrySet().stream()
261 .map((Map.Entry<String, Long> t) -> t.getKey() +
" : " + t.getValue())
262 .collect(Collectors.joining(
"\n"));
264 Map<String, Long> tagCounts =
new HashMap<>();
265 if (eventBundle.getEventIDsWithTags().isEmpty() ==
false) {
268 String tagCountsString = tagCounts.entrySet().stream()
269 .map((Map.Entry<String, Long> t) -> t.getKey() +
" : " + t.getValue())
270 .collect(Collectors.joining(
"\n"));
272 return Bundle.EventBundleNodeBase_tooltip_text(getEventIDs().size(), getEventType(), getDescription(),
275 + (hashSetCountsString.isEmpty() ?
"" : Bundle.EventBundleNodeBase_toolTip_hashSetHits(hashSetCountsString))
276 + (tagCountsString.isEmpty() ?
"" : Bundle.EventBundleNodeBase_toolTip_tags(tagCountsString));
280 protected void succeeded() {
283 tooltip.setText(
get());
284 tooltip.setGraphic(null);
285 }
catch (InterruptedException | ExecutionException ex) {
286 LOGGER.log(Level.SEVERE,
"Tooltip generation failed.", ex);
290 new Thread(tooltTipTask).start();
301 setBorder(applied ? SELECTION_BORDER : null);
309 abstract void applyHighlightEffect(
boolean applied);
311 @SuppressWarnings(
"unchecked")
312 public List<ParentNodeType> getSubNodes() {
318 void showHoverControls(
final boolean showControls) {
319 Effect dropShadow = dropShadowMap.computeIfAbsent(getEventType(),
320 eventType ->
new DropShadow(-10, eventType.getColor()));
321 setEffect(showControls ? dropShadow : null);
323 enableTooltip(showControls);
324 if (parentNode != null) {
325 parentNode.showHoverControls(
false);
330 return getEventBundle().getEventType();
333 final String getDescription() {
334 return getEventBundle().getDescription();
337 final long getStartMillis() {
338 return getEventBundle().getStartMillis();
341 final long getEndMillis() {
342 return getEventBundle().getEndMillis();
345 final Set<Long> getEventIDs() {
346 return getEventBundle().getEventIDs();
351 return Orientation.HORIZONTAL;
356 chart.layoutEventBundleNodes(subNodes, 0);
357 super.layoutChildren();
360 abstract ParentNodeType createChildNode(ParentType rawChild);
365 abstract void setMaxDescriptionWidth(
double w);
368 descVisibility.set(
get);
371 void enableTooltip(
boolean toolTipEnabled) {
372 if (toolTipEnabled) {
373 Tooltip.install(
this, tooltip);
375 Tooltip.uninstall(
this, tooltip);
379 void animateTo(
double xLeft,
double yTop) {
380 if (timeline != null) {
382 Platform.runLater(chart::requestChartLayout);
384 timeline =
new Timeline(
new KeyFrame(Duration.millis(100),
385 new KeyValue(layoutXProperty(), xLeft),
386 new KeyValue(layoutYProperty(), yTop))
388 timeline.setOnFinished(finished -> Platform.runLater(chart::requestChartLayout));
392 abstract EventHandler<MouseEvent> getDoubleClickHandler();
394 abstract Collection<? extends Action> getActions();
405 if (t.getButton() == MouseButton.PRIMARY) {
406 if (t.getClickCount() > 1) {
407 getDoubleClickHandler().handle(t);
408 }
else if (t.isShiftDown()) {
410 }
else if (t.isShortcutDown()) {
416 }
else if (t.getButton() == MouseButton.SECONDARY) {
418 if (contextMenu == null) {
419 contextMenu =
new ContextMenu();
420 contextMenu.setAutoHide(
true);
422 contextMenu.getItems().addAll(ActionUtils.createContextMenu(getActions()).getItems());
424 contextMenu.getItems().add(
new SeparatorMenuItem());
425 contextMenu.getItems().addAll(chartContextMenu.getItems());
void handle(MouseEvent t)
final ParentNodeType parentNode
TimeLineController getController()
final BundleType getEventBundle()
FilteredEventsModel getEventsModel()
Orientation getContentBias()
void applySelectionEffect(boolean applied)
static Tooltip getDefaultTooltip()
void requestChartLayout()
final EventDetailsChart chart
final Border SELECTION_BORDER
synchronized void monitorTask(final Task<?> task)
EventBundleNodeBase(EventDetailsChart chart, BundleType eventBundle, ParentNodeType parentNode)
SleuthkitCase getSleuthkitCase()
final BundleType eventBundle
Map< String, Long > getTagCountsByTagName(Set< Long > eventIDsWithTags)
synchronized static Logger getLogger(String name)
static DateTimeFormatter getZonedFormatter()
ContextMenu getChartContextMenu()
Set< TimeLineEvent > getEventsById(Collection< Long > eventIDs)