19package org.sleuthkit.autopsy.timeline.ui.countsview;
21import java.util.Arrays;
22import java.util.Optional;
23import java.util.logging.Level;
24import javafx.collections.ObservableList;
25import javafx.event.EventHandler;
26import javafx.scene.Cursor;
27import javafx.scene.Node;
28import javafx.scene.chart.CategoryAxis;
29import javafx.scene.chart.NumberAxis;
30import javafx.scene.chart.StackedBarChart;
31import javafx.scene.chart.XYChart;
32import javafx.scene.control.Alert;
33import javafx.scene.control.ButtonType;
34import javafx.scene.control.ContextMenu;
35import javafx.scene.control.SeparatorMenuItem;
36import javafx.scene.control.Tooltip;
37import javafx.scene.effect.DropShadow;
38import javafx.scene.effect.Effect;
39import javafx.scene.effect.Lighting;
40import javafx.scene.image.ImageView;
41import javafx.scene.input.MouseButton;
42import javafx.scene.input.MouseEvent;
43import javafx.util.StringConverter;
44import org.controlsfx.control.Notifications;
45import org.controlsfx.control.action.Action;
46import org.controlsfx.control.action.ActionUtils;
47import org.joda.time.DateTime;
48import org.joda.time.Interval;
49import org.joda.time.Seconds;
50import org.openide.util.NbBundle;
51import org.sleuthkit.autopsy.coreutils.ColorUtilities;
52import org.sleuthkit.autopsy.coreutils.Logger;
53import org.sleuthkit.autopsy.timeline.EventsModel;
54import org.sleuthkit.autopsy.timeline.PromptDialogManager;
55import org.sleuthkit.autopsy.timeline.TimeLineController;
56import org.sleuthkit.autopsy.timeline.ViewMode;
57import static org.sleuthkit.autopsy.timeline.ui.EventTypeUtils.getColor;
58import static org.sleuthkit.autopsy.timeline.ui.EventTypeUtils.getImagePath;
59import org.sleuthkit.autopsy.timeline.ui.IntervalSelector;
60import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
61import org.sleuthkit.autopsy.timeline.utils.RangeDivision;
62import org.sleuthkit.datamodel.TskCoreException;
63import org.sleuthkit.datamodel.TimelineEventType;
69final class EventCountsChart
extends StackedBarChart<String, Number> implements
TimeLineChart<String> {
71 private static final Logger logger = Logger.getLogger(EventCountsChart.class.getName());
72 private static final Effect SELECTED_NODE_EFFECT =
new Lighting();
73 private ContextMenu chartContextMenu;
75 private final TimeLineController controller;
76 private final EventsModel filteredEvents;
78 private IntervalSelector<? extends String> intervalSelector;
80 final ObservableList<Node> selectedNodes;
87 private RangeDivision rangeInfo;
89 EventCountsChart(TimeLineController controller, CategoryAxis dateAxis, NumberAxis countAxis, ObservableList<Node> selectedNodes) {
90 super(dateAxis, countAxis);
91 this.controller = controller;
92 this.filteredEvents = controller.getEventsModel();
95 dateAxis.setAnimated(
true);
96 dateAxis.setLabel(
null);
97 dateAxis.setTickLabelsVisible(
false);
98 dateAxis.setTickLabelGap(0);
100 countAxis.setAutoRanging(
false);
101 countAxis.setLowerBound(0);
102 countAxis.setAnimated(
true);
103 countAxis.setMinorTickCount(0);
106 setAlternativeRowFillVisible(
true);
108 setLegendVisible(
false);
113 setOnMousePressed(chartDragHandler);
114 setOnMouseReleased(chartDragHandler);
115 setOnMouseDragged(chartDragHandler);
119 this.selectedNodes = selectedNodes;
128 chartContextMenu =
null;
132 public ContextMenu getContextMenu(MouseEvent clickEvent) {
133 if (chartContextMenu !=
null) {
134 chartContextMenu.hide();
137 chartContextMenu = ActionUtils.createContextMenu(
138 Arrays.asList(TimeLineChart.newZoomHistoyActionGroup(controller)));
139 chartContextMenu.setAutoHide(
true);
140 return chartContextMenu;
150 getChartChildren().remove(intervalSelector);
151 intervalSelector =
null;
156 return intervalSelector;
160 public void setIntervalSelector(IntervalSelector<? extends String> newIntervalSelector) {
161 intervalSelector = newIntervalSelector;
163 intervalSelector.prefHeightProperty().addListener(observable -> newIntervalSelector.autosize());
174 return selectedNodes;
177 void setRangeInfo(RangeDivision rangeInfo) {
178 this.rangeInfo = rangeInfo;
181 Effect getSelectionEffect() {
182 return SELECTED_NODE_EFFECT;
195 "# {1} - event type displayname",
196 "# {2} - start date time",
197 "# {3} - end date time",
198 "CountsViewPane.tooltip.text={0} {1} events\nbetween {2}\nand {3}"})
200 protected void dataItemAdded(Series<String, Number> series,
int itemIndex, Data<String, Number> item) {
201 ExtraData extraValue = (ExtraData) item.getExtraValue();
202 TimelineEventType eventType = extraValue.getEventType();
203 Interval interval = extraValue.getInterval();
204 long count = extraValue.getRawCount();
206 item.nodeProperty().addListener(observable -> {
207 final Node node = item.getNode();
209 node.setStyle(
"-fx-border-width: 2; "
210 +
" -fx-border-color: " + ColorUtilities.getRGBCode(getColor(eventType.getParent())) +
"; "
211 +
" -fx-bar-fill: " + ColorUtilities.getRGBCode(getColor(eventType)));
212 node.setCursor(Cursor.HAND);
214 final Tooltip tooltip =
new Tooltip(Bundle.CountsViewPane_tooltip_text(
215 count, eventType.getDisplayName(),
217 interval.getEnd().toString(rangeInfo.getTickFormatter())));
218 tooltip.setGraphic(
new ImageView(getImagePath(eventType)));
219 Tooltip.install(node, tooltip);
221 node.setOnMouseEntered(mouseEntered -> node.setEffect(
new DropShadow(10, getColor(eventType))));
222 node.setOnMouseExited(mouseExited -> node.setEffect(selectedNodes.contains(node) ? SELECTED_NODE_EFFECT :
null));
226 super.dataItemAdded(series, itemIndex, item);
237 return n.intValue() == n.doubleValue()
238 ? Integer.toString(n.intValue()) :
"";
244 return Double.valueOf(
string).intValue();
256 CountsIntervalSelector(EventCountsChart
chart) {
258 this.countsChart =
chart;
275 return new Interval(lowerDate, upperDate.plus(
countsChart.rangeInfo.getPeriodSize().toUnitPeriod()));
280 return date ==
null ?
new DateTime(
countsChart.rangeInfo.getLowerBound()) :
countsChart.rangeInfo.getTickFormatter().parseDateTime(date);
293 private class BarClickHandler
implements EventHandler<MouseEvent> {
299 private final TimelineEventType
type;
305 BarClickHandler(XYChart.Data<String, Number> data) {
306 EventCountsChart.ExtraData extraData = (EventCountsChart.ExtraData) data.getExtraValue();
307 this.interval = extraData.getInterval();
308 this.type = extraData.getEventType();
309 this.node = data.getNode();
310 this.startDateString = data.getXValue();
313 @NbBundle.Messages({
"Timeline.ui.countsview.menuItem.selectTimeRange=Select Time Range",
314 "SelectIntervalAction.errorMessage=Error selecting interval."})
315 class SelectIntervalAction extends Action {
317 SelectIntervalAction() {
318 super(Bundle.Timeline_ui_countsview_menuItem_selectTimeRange());
319 setEventHandler(action -> {
321 controller.selectTimeAndType(
interval, TimelineEventType.ROOT_EVENT_TYPE);
323 }
catch (TskCoreException ex) {
324 Notifications.create().owner(getScene().getWindow())
325 .text(Bundle.SelectIntervalAction_errorMessage())
327 logger.log(Level.SEVERE,
"Error selecting interval.", ex);
329 selectedNodes.clear();
330 for (XYChart.Series<String, Number> s : getData()) {
331 s.getData().forEach((XYChart.Data<String, Number> d) -> {
332 if (startDateString.contains(d.getXValue())) {
333 selectedNodes.add(d.getNode());
341 @NbBundle.Messages({
"Timeline.ui.countsview.menuItem.selectEventType=Select Event Type",
342 "SelectTypeAction.errorMessage=Error selecting type."})
343 class SelectTypeAction extends Action {
346 super(Bundle.Timeline_ui_countsview_menuItem_selectEventType());
347 setEventHandler(action -> {
349 controller.selectTimeAndType(filteredEvents.getSpanningInterval(), type);
351 }
catch (TskCoreException ex) {
352 Notifications.create().owner(getScene().getWindow())
353 .text(Bundle.SelectTypeAction_errorMessage())
355 logger.log(Level.SEVERE,
"Error selecting type.", ex);
357 selectedNodes.clear();
358 getData().stream().filter(series -> series.getName().equals(type.getDisplayName()))
360 .ifPresent(series -> series.getData().forEach(data -> selectedNodes.add(data.getNode())));
365 @NbBundle.Messages({
"Timeline.ui.countsview.menuItem.selectTimeandType=Select Time and Type",
366 "SelectIntervalAndTypeAction.errorMessage=Error selecting interval and type."})
367 class SelectIntervalAndTypeAction extends Action {
369 SelectIntervalAndTypeAction() {
370 super(Bundle.Timeline_ui_countsview_menuItem_selectTimeandType());
371 setEventHandler(action -> {
373 controller.selectTimeAndType(interval, type);
375 }
catch (TskCoreException ex) {
376 Notifications.create().owner(getScene().getWindow())
377 .text(Bundle.SelectIntervalAndTypeAction_errorMessage())
379 logger.log(Level.SEVERE,
"Error selecting interval and type.", ex);
381 selectedNodes.setAll(node);
386 @NbBundle.Messages({
"Timeline.ui.countsview.menuItem.zoomIntoTimeRange=Zoom into Time Range",
387 "ZoomToIntervalAction.errorMessage=Error zooming to interval."})
388 class ZoomToIntervalAction extends Action {
390 ZoomToIntervalAction() {
391 super(Bundle.Timeline_ui_countsview_menuItem_zoomIntoTimeRange());
392 setEventHandler(action -> {
394 if (interval.toDuration().isShorterThan(Seconds.ONE.toStandardDuration()) ==
false) {
395 controller.pushTimeRange(interval);
397 }
catch (TskCoreException ex) {
398 Notifications.create().owner(getScene().getWindow())
399 .text(Bundle.ZoomToIntervalAction_errorMessage())
401 logger.log(Level.SEVERE,
"Error zooming to interval.", ex);
409 "CountsViewPane.detailSwitchMessage=There is no temporal resolution smaller than Seconds.\nWould you like to switch to the Details view instead?",
410 "CountsViewPane.detailSwitchTitle=\"Switch to Details View?",
411 "BarClickHandler.selectTimeAndType.errorMessage=Error selecting time and type.",
412 "BarClickHandler_zoomIn_errorMessage=Error zooming in."})
415 if (e.getClickCount() == 1) {
416 if (e.getButton().equals(MouseButton.PRIMARY)) {
419 }
catch (TskCoreException ex) {
420 Notifications.create().owner(getScene().getWindow())
421 .text(Bundle.BarClickHandler_selectTimeAndType_errorMessage())
423 logger.log(Level.SEVERE,
"Error selecting time and type.", ex);
425 selectedNodes.setAll(
node);
426 }
else if (e.getButton().equals(MouseButton.SECONDARY)) {
427 getContextMenu(e).hide();
433 ActionUtils.createMenuItem(
new SelectIntervalAction()),
434 ActionUtils.createMenuItem(
new SelectTypeAction()),
435 ActionUtils.createMenuItem(
new SelectIntervalAndTypeAction()),
436 new SeparatorMenuItem(),
437 ActionUtils.createMenuItem(
new ZoomToIntervalAction()));
445 }
else if (e.getClickCount() >= 2) {
446 if (
interval.toDuration().isLongerThan(Seconds.ONE.toStandardDuration())) {
449 }
catch (TskCoreException ex) {
450 Notifications.create().owner(getScene().getWindow())
451 .text(Bundle.BarClickHandler_zoomIn_errorMessage())
453 logger.log(Level.SEVERE,
"Error zooming in.", ex);
456 Alert alert =
new Alert(Alert.AlertType.CONFIRMATION, Bundle.CountsViewPane_detailSwitchMessage(), ButtonType.YES, ButtonType.NO);
457 alert.setTitle(Bundle.CountsViewPane_detailSwitchTitle());
460 alert.showAndWait().ifPresent(response -> {
461 if (response == ButtonType.YES) {
474 static class ExtraData {
476 private final Interval interval;
477 private final TimelineEventType eventType;
478 private final long rawCount;
480 ExtraData(Interval interval, TimelineEventType eventType,
long rawCount) {
481 this.interval = interval;
482 this.eventType = eventType;
483 this.rawCount = rawCount;
486 public long getRawCount() {
490 public Interval getInterval() {
494 public TimelineEventType getEventType() {
final ReadOnlyObjectWrapper< Interval > timeRangeProperty
static void setDialogIcons(Dialog<?> dialog)
static DateTimeZone getJodaTimeZone()
EventsModel getEventsModel()
IntervalSelector(IntervalSelectorProvider< X > chart)
final IntervalSelectorProvider< X > chart
final String startDateString
void handle(final MouseEvent e)
ContextMenu barContextMenu
final TimelineEventType type
Interval adjustInterval(Interval i)
DateTime parseDateTime(String date)
String formatSpan(String date)
final EventCountsChart countsChart
Number fromString(String string)
String toString(Number n)
static RangeDivision getRangeDivision(Interval timeRange, DateTimeZone timeZone)
TimeLineController getController()
ObservableList<? extends Node > getSelectedNodes()
void clearIntervalSelector()
IntervalSelector< X > newIntervalSelector()
IntervalSelector<? extends X > getIntervalSelector()