19 package org.sleuthkit.autopsy.timeline.ui;
21 import com.google.common.eventbus.Subscribe;
22 import java.time.Instant;
23 import java.time.LocalDate;
24 import java.time.LocalDateTime;
25 import java.time.ZoneOffset;
26 import java.util.ArrayList;
27 import java.util.function.BiFunction;
28 import java.util.function.Supplier;
29 import javafx.application.Platform;
30 import javafx.beans.InvalidationListener;
31 import javafx.beans.Observable;
32 import javafx.beans.value.ChangeListener;
33 import javafx.beans.value.ObservableValue;
34 import javafx.event.ActionEvent;
35 import javafx.fxml.FXML;
36 import javafx.geometry.Insets;
37 import javafx.scene.control.Button;
38 import javafx.scene.control.Label;
39 import javafx.scene.control.MenuButton;
40 import javafx.scene.control.TitledPane;
41 import javafx.scene.control.Toggle;
42 import javafx.scene.control.ToggleButton;
43 import javafx.scene.control.ToolBar;
44 import javafx.scene.control.Tooltip;
45 import javafx.scene.effect.Lighting;
46 import javafx.scene.image.Image;
47 import javafx.scene.image.ImageView;
48 import javafx.scene.input.MouseEvent;
49 import javafx.scene.layout.Background;
50 import javafx.scene.layout.BackgroundFill;
51 import javafx.scene.layout.BorderPane;
52 import javafx.scene.layout.CornerRadii;
53 import javafx.scene.layout.HBox;
54 import javafx.scene.layout.Pane;
55 import javafx.scene.layout.Priority;
56 import javafx.scene.layout.Region;
57 import static javafx.scene.layout.Region.USE_PREF_SIZE;
58 import javafx.scene.layout.StackPane;
59 import javafx.scene.paint.Color;
60 import javafx.util.Callback;
61 import javax.annotation.Nonnull;
62 import javax.annotation.concurrent.GuardedBy;
63 import jfxtras.scene.control.LocalDateTimePicker;
64 import jfxtras.scene.control.LocalDateTimeTextField;
65 import org.controlsfx.control.NotificationPane;
66 import org.controlsfx.control.RangeSlider;
67 import org.controlsfx.control.action.Action;
68 import org.controlsfx.control.action.ActionUtils;
69 import org.joda.time.DateTime;
70 import org.joda.time.Interval;
71 import org.openide.util.NbBundle;
103 private static final Image
INFORMATION =
new Image(
"org/sleuthkit/autopsy/timeline/images/information.png", 16, 16,
true,
true);
104 private static final Image
REFRESH =
new Image(
"org/sleuthkit/autopsy/timeline/images/arrow-circle-double-135.png");
105 private static final Background
background =
new Background(
new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY));
125 private final RangeSlider
rangeSlider = new RangeSlider(0, 1.0, .25, .75);
178 public void invalidated(Observable observable) {
182 if (
false == controller.pushTimeRange(
new Interval(
184 (
long) (
rangeSlider.getHighValue() + minTime + 1000)))) {
238 @NbBundle.Messages({
"VisualizationPanel.refresh=refresh",
239 "VisualizationPanel.visualizationModeLabel.text=Visualization Mode:",
240 "VisualizationPanel.startLabel.text=Start:",
241 "VisualizationPanel.endLabel.text=End:",
242 "VisualizationPanel.countsToggle.text=Counts",
243 "VisualizationPanel.detailsToggle.text=Details",
244 "VisualizationPanel.zoomMenuButton.text=Zoom in/out to"})
246 assert
endPicker != null :
"fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'.";
247 assert
histogramBox != null :
"fx:id=\"histogramBox\" was not injected: check your FXML file 'ViewWrapper.fxml'.";
248 assert
startPicker != null :
"fx:id=\"startPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'.";
249 assert
rangeHistogramStack != null :
"fx:id=\"rangeHistogramStack\" was not injected: check your FXML file 'ViewWrapper.fxml'.";
250 assert
countsToggle != null :
"fx:id=\"countsToggle\" was not injected: check your FXML file 'VisToggle.fxml'.";
251 assert
detailsToggle != null :
"fx:id=\"eventsToggle\" was not injected: check your FXML file 'VisToggle.fxml'.";
255 notificationPane.getActions().setAll(
new Action(Bundle.VisualizationPanel_refresh()) {
257 setGraphic(
new ImageView(REFRESH));
258 setEventHandler((ActionEvent t) -> {
268 countsToggle.setText(Bundle.VisualizationPanel_countsToggle_text());
269 detailsToggle.setText(Bundle.VisualizationPanel_detailsToggle_text());
270 ChangeListener<Toggle> toggleListener = (ObservableValue<? extends Toggle> observable, Toggle oldValue, Toggle newValue) -> {
271 if (newValue == null) {
273 }
else if (newValue ==
countsToggle && oldValue != null) {
281 countsToggle.getToggleGroup().selectedToggleProperty().addListener(toggleListener);
283 countsToggle.toggleGroupProperty().addListener((Observable observable) -> {
284 countsToggle.getToggleGroup().selectedToggleProperty().addListener(toggleListener);
294 startLabel.setText(Bundle.VisualizationPanel_startLabel_text());
295 endLabel.setText(Bundle.VisualizationPanel_endLabel_text());
299 startPicker.setParseErrorCallback(throwable -> null);
300 endPicker.setParseErrorCallback(throwable -> null);
303 LocalDateDisabler localDateDisabler =
new LocalDateDisabler();
304 startPicker.setLocalDateTimeRangeCallback(localDateDisabler);
305 endPicker.setLocalDateTimeRangeCallback(localDateDisabler);
322 histogramBox.setStyle(
" -fx-padding: 0,0.5em,0,.5em; ");
326 for (ZoomRanges zoomRange : ZoomRanges.values()) {
328 new Action(zoomRange.getDisplayName(),
event -> {
329 if (zoomRange != ZoomRanges.ALL) {
336 zoomMenuButton.setText(Bundle.VisualizationPanel_zoomMenuButton_text());
344 TimeLineController.getTimeZone().addListener(timeZoneProp ->
refreshTimeUI());
360 switch (visualizationMode) {
373 Platform.runLater(() -> {
389 if (newValue ==
false) {
395 setBackground(
new Background(
new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)));
409 @NbBundle.Messages(
"VisualizationPanel.tagsAddedOrDeleted=Tags have been created and/or deleted. The visualization may not be up to date.")
412 if (tagsFilter.isSelected() && tagsFilter.isDisabled() ==
false) {
413 Platform.runLater(() -> {
414 notificationPane.show(Bundle.VisualizationPanel_tagsAddedOrDeleted(),
new ImageView(INFORMATION));
426 NbBundle.getMessage(
VisualizationPanel.class,
"VisualizationPanel.histogramTask.title"),
true) {
427 private final Lighting lighting =
new Lighting();
430 protected Void call()
throws Exception {
432 updateMessage(NbBundle.getMessage(
VisualizationPanel.class,
"VisualizationPanel.histogramTask.preparing"));
444 Platform.runLater(() -> {
445 updateMessage(NbBundle.getMessage(
VisualizationPanel.class,
"VisualizationPanel.histogramTask.resetUI"));
449 ArrayList<Long> bins =
new ArrayList<>();
451 DateTime start = timeRange.getStart();
452 while (timeRange.contains(start)) {
457 final Interval interval =
new Interval(start, end);
462 updateMessage(NbBundle.getMessage(
VisualizationPanel.class,
"VisualizationPanel.histogramTask.queryDb"));
467 max = Math.max(count, max);
469 final double fMax = Math.log(max);
470 final ArrayList<Long> fbins =
new ArrayList<>(bins);
471 Platform.runLater(() -> {
472 updateMessage(NbBundle.getMessage(
VisualizationPanel.class,
"VisualizationPanel.histogramTask.updateUI2"));
476 for (Long bin : fbins) {
480 Region bar =
new Region();
482 bar.prefHeightProperty().bind(
histogramBox.heightProperty().multiply(Math.log(bin)).divide(fMax));
483 bar.setMaxHeight(USE_PREF_SIZE);
484 bar.setMinHeight(USE_PREF_SIZE);
485 bar.setBackground(background);
486 bar.setOnMouseEntered((MouseEvent event) -> {
487 Tooltip.install(bar,
new Tooltip(bin.toString()));
489 bar.setEffect(lighting);
491 HBox.setHgrow(bar, Priority.ALWAYS);
515 long startMillis = interval.getStartMillis();
516 long endMillis = interval.getEndMillis();
518 if (minTime > 0 && maxTime > minTime) {
520 Platform.runLater(() -> {
521 startPicker.localDateTimeProperty().removeListener(startListener);
522 endPicker.localDateTimeProperty().removeListener(endListener);
535 startPicker.localDateTimeProperty().addListener(startListener);
536 endPicker.localDateTimeProperty().addListener(endListener);
541 @NbBundle.Messages(
"NoEventsDialog.titledPane.text=No Visible Events")
566 assert resetFiltersButton != null :
"fx:id=\"resetFiltersButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'.";
567 assert dismissButton != null :
"fx:id=\"dismissButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'.";
568 assert zoomButton != null :
"fx:id=\"zoomButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'.";
570 titledPane.setText(Bundle.NoEventsDialog_titledPane_text());
571 noEventsDialogLabel.setText(NbBundle.getMessage(
NoEventsDialog.class,
"VisualizationPanel.noEventsDialogLabel.text"));
573 dismissButton.setOnAction(actionEvent -> closeCallback.run());
590 PickerListener(Supplier<LocalDateTimeTextField> pickerSupplier, BiFunction<Interval, Long, Interval> intervalMapper) {
597 LocalDateTime pickerTime = pickerSupplier.get().getLocalDateTime();
598 if (pickerTime != null) {
608 private class LocalDateDisabler implements Callback<LocalDateTimePicker.LocalDateTimeRange, Void> {
611 public Void
call(LocalDateTimePicker.LocalDateTimeRange viewedRange) {
613 endPicker.disabledLocalDateTimes().clear();
617 long spanStartMillis = spanningInterval.getStartMillis();
618 long spaneEndMillis = spanningInterval.getEndMillis();
620 LocalDate rangeStartLocalDate = viewedRange.getStartLocalDateTime().toLocalDate();
621 LocalDate rangeEndLocalDate = viewedRange.getEndLocalDateTime().toLocalDate().plusDays(1);
623 for (LocalDate dt = rangeStartLocalDate;
false == dt.isAfter(rangeEndLocalDate); dt = dt.plusDays(1)) {
624 long startOfDay = dt.atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli();
625 long endOfDay = dt.plusDays(1).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli();
627 if (endOfDay < spanStartMillis || startOfDay > spaneEndMillis) {
628 startPicker.disabledLocalDateTimes().add(dt.atStartOfDay());
629 endPicker.disabledLocalDateTimes().add(dt.atStartOfDay());
646 private final LocalDateTimeTextField
picker;
653 public Boolean
call(LocalDateTime param) {
658 if (picker.isPickerShowing() ==
false) {
660 picker.setDisplayedLocalDateTime(picker.getLocalDateTime());
final InvalidationListener endListener
Boolean call(LocalDateTime param)
synchronized void registerForEvents(Object o)
final TimeLineController controller
ToggleButton detailsToggle
LocalDateTimeTextField endPicker
LocalDateTimeTextField startPicker
Button resetFiltersButton
static final Image REFRESH
synchronized ReadOnlyObjectProperty< VisualizationMode > viewModeProperty()
void invalidated(Observable observable)
ReadOnlyBooleanProperty eventsDBStaleProperty()
final InvalidationListener zoomListener
LoggedTask< Void > histogramTask
List< Node > getSettingsNodes()
synchronized void setVisualization(final AbstractVisualizationPane<?,?,?,?> newViz)
Label visualizationModeLabel
final BiFunction< Interval, Long, Interval > intervalMapper
final FilteredEventsModel filteredEvents
static final Background background
final synchronized void dispose()
Map< EventType, Long > getEventCounts(Interval timeRange)
synchronized ReadOnlyObjectProperty< ZoomParams > zoomParametersProperty()
Interval getSpanningInterval()
static long localDateTimeToEpochMilli(LocalDateTime localDateTime)
static final Logger LOGGER
Void call(LocalDateTimePicker.LocalDateTimeRange viewedRange)
synchronized boolean pushTimeRange(Interval timeRange)
synchronized RootFilter getFilter()
static RangeDivisionInfo getRangeDivisionInfo(Interval timeRange)
void handleTimeLineTagEvent(TagsUpdatedEvent event)
final NotificationPane notificationPane
static ZoneId getTimeZoneID()
AbstractVisualizationPane<?,?,?,?> visualization
VisualizationPanel(@Nonnull TimeLineController controller,@Nonnull EventsTree eventsTree)
final InvalidationListener startListener
synchronized void monitorTask(final Task<?> task)
synchronized void pushPeriod(ReadablePeriod period)
synchronized void refreshHistorgram()
ToggleButton countsToggle
final LocalDateTimeTextField picker
Label noEventsDialogLabel
static DateTimeZone getJodaTimeZone()
final InvalidationListener rangeSliderListener
final Runnable closeCallback
final RangeSlider rangeSlider
StackPane rangeHistogramStack
final EventsTree eventsTree
TagsFilter getTagsFilter()
NoEventsDialog(Runnable closeCallback)
synchronized static Logger getLogger(String name)
void setDetailViewPane(DetailViewPane detailViewPane)
final SimpleBooleanProperty hasEvents
boolean isEventsDBStale()
static LocalDateTime epochMillisToLocalDateTime(long millis)
TimeUnits getPeriodSize()
void setViewMode(VisualizationMode visualizationMode)
static void construct(Node node, String fxmlFileName)
synchronized ReadOnlyObjectProperty< Interval > timeRangeProperty()
final Supplier< LocalDateTimeTextField > pickerSupplier
final synchronized void update()
synchronized void setViewMode(VisualizationMode visualizationMode)
MenuButton zoomMenuButton
void refreshTimeUI(Interval interval)
static final Image INFORMATION