19 package org.sleuthkit.autopsy.timeline.ui;
 
   21 import com.google.common.collect.ImmutableList;
 
   22 import com.google.common.eventbus.Subscribe;
 
   23 import java.time.Instant;
 
   24 import java.time.LocalDate;
 
   25 import java.time.LocalDateTime;
 
   26 import java.time.ZoneOffset;
 
   27 import java.util.ArrayList;
 
   28 import java.util.List;
 
   29 import java.util.function.BiFunction;
 
   30 import java.util.function.Supplier;
 
   31 import java.util.logging.Level;
 
   32 import javafx.application.Platform;
 
   33 import javafx.beans.InvalidationListener;
 
   34 import javafx.beans.Observable;
 
   35 import javafx.collections.FXCollections;
 
   36 import javafx.collections.ObservableList;
 
   37 import javafx.fxml.FXML;
 
   38 import javafx.geometry.Insets;
 
   39 import javafx.scene.Node;
 
   40 import javafx.scene.control.Button;
 
   41 import javafx.scene.control.Label;
 
   42 import javafx.scene.control.MenuButton;
 
   43 import javafx.scene.control.TitledPane;
 
   44 import javafx.scene.control.ToggleButton;
 
   45 import javafx.scene.control.ToolBar;
 
   46 import javafx.scene.control.Tooltip;
 
   47 import javafx.scene.effect.Lighting;
 
   48 import javafx.scene.image.Image;
 
   49 import javafx.scene.image.ImageView;
 
   50 import javafx.scene.input.MouseEvent;
 
   51 import javafx.scene.layout.Background;
 
   52 import javafx.scene.layout.BackgroundFill;
 
   53 import javafx.scene.layout.BorderPane;
 
   54 import javafx.scene.layout.CornerRadii;
 
   55 import javafx.scene.layout.HBox;
 
   56 import javafx.scene.layout.Priority;
 
   57 import javafx.scene.layout.Region;
 
   58 import static javafx.scene.layout.Region.USE_PREF_SIZE;
 
   59 import javafx.scene.layout.StackPane;
 
   60 import javafx.scene.paint.Color;
 
   61 import javafx.util.Callback;
 
   62 import javax.annotation.Nonnull;
 
   63 import javax.annotation.concurrent.GuardedBy;
 
   64 import jfxtras.scene.control.LocalDateTimePicker;
 
   65 import jfxtras.scene.control.LocalDateTimeTextField;
 
   66 import jfxtras.scene.control.ToggleGroupValue;
 
   67 import org.controlsfx.control.NotificationPane;
 
   68 import org.controlsfx.control.Notifications;
 
   69 import org.controlsfx.control.RangeSlider;
 
   70 import org.controlsfx.control.SegmentedButton;
 
   71 import org.controlsfx.control.action.Action;
 
   72 import org.controlsfx.control.action.ActionUtils;
 
   73 import org.joda.time.DateTime;
 
   74 import org.joda.time.Interval;
 
   75 import org.openide.util.NbBundle;
 
  111     private static final Image 
WARNING = 
new Image(
"org/sleuthkit/autopsy/timeline/images/warning_triangle.png", 16, 16, 
true, 
true); 
 
  112     private static final Image 
REFRESH = 
new Image(
"org/sleuthkit/autopsy/timeline/images/arrow-circle-double-135.png"); 
 
  113     private static final Background 
GRAY_BACKGROUND = 
new Background(
new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY));
 
  122             setBackground(GRAY_BACKGROUND);
 
  159     private final RangeSlider 
rangeSlider = new RangeSlider(0, 1.0, .25, .75);
 
  224     private final ObservableList<Node> 
settingsNodes = FXCollections.observableArrayList();
 
  246         "ViewFrame.rangeSliderListener.errorMessage=Error responding to range slider."})
 
  249         public void invalidated(Observable observable) {
 
  256                             (
long) (
rangeSlider.getHighValue() + minTime + 1000)))) {
 
  260                     Notifications.create().owner(getScene().getWindow())
 
  261                             .text(Bundle.ViewFrame_rangeSliderListener_errorMessage())
 
  263                     logger.log(Level.SEVERE, 
"Error responding to range slider.", ex);
 
  325         "ViewFrame.viewModeLabel.text=View Mode:",
 
  326         "ViewFrame.startLabel.text=Start:",
 
  327         "ViewFrame.endLabel.text=End:",
 
  328         "ViewFrame.countsToggle.text=Counts",
 
  329         "ViewFrame.detailsToggle.text=Details",
 
  330         "ViewFrame.listToggle.text=List",
 
  331         "ViewFrame.zoomMenuButton.text=Zoom in/out to",
 
  332         "ViewFrame.zoomMenuButton.errorMessage=Error pushing time range.",
 
  333         "ViewFrame.tagsAddedOrDeleted=Tags have been created and/or deleted.  The view may not be up to date." 
  336         assert 
endPicker != null : 
"fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; 
 
  337         assert 
histogramBox != null : 
"fx:id=\"histogramBox\" was not injected: check your FXML file 'ViewWrapper.fxml'."; 
 
  338         assert 
startPicker != null : 
"fx:id=\"startPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; 
 
  339         assert 
rangeHistogramStack != null : 
"fx:id=\"rangeHistogramStack\" was not injected: check your FXML file 'ViewWrapper.fxml'."; 
 
  340         assert 
countsToggle != null : 
"fx:id=\"countsToggle\" was not injected: check your FXML file 'VisToggle.fxml'."; 
 
  341         assert 
detailsToggle != null : 
"fx:id=\"eventsToggle\" was not injected: check your FXML file 'VisToggle.fxml'."; 
 
  353         viewModeLabel.setText(Bundle.ViewFrame_viewModeLabel_text());
 
  354         countsToggle.setText(Bundle.ViewFrame_countsToggle_text());
 
  355         detailsToggle.setText(Bundle.ViewFrame_detailsToggle_text());
 
  356         listToggle.setText(Bundle.ViewFrame_listToggle_text());
 
  373         startLabel.setText(Bundle.ViewFrame_startLabel_text());
 
  374         endLabel.setText(Bundle.ViewFrame_endLabel_text());
 
  378         startPicker.setParseErrorCallback(throwable -> null);
 
  379         endPicker.setParseErrorCallback(throwable -> null);
 
  382         LocalDateDisabler localDateDisabler = 
new LocalDateDisabler();
 
  383         startPicker.setLocalDateTimeRangeCallback(localDateDisabler);
 
  384         endPicker.setLocalDateTimeRangeCallback(localDateDisabler);
 
  401         histogramBox.setStyle(
"   -fx-padding: 0,0.5em,0,.5em; "); 
 
  405         for (ZoomRanges zoomRange : ZoomRanges.values()) {
 
  407                     new Action(zoomRange.getDisplayName(), actionEvent -> {
 
  410                         } 
catch (TskCoreException ex) {
 
  411                             Notifications.create().owner(getScene().getWindow())
 
  412                                     .text(Bundle.ViewFrame_zoomMenuButton_errorMessage())
 
  414                             logger.log(Level.SEVERE, 
"Error pushing a time range.", ex);
 
  427         TimeLineController.timeZoneProperty().addListener(timeZoneProp -> 
refreshTimeUI());
 
  445         Platform.runLater(() -> {
 
  462         Platform.runLater(() -> {
 
  476         "ViewFrame.notification.cacheInvalidated=The event data has been updated, the visualization may be out of date."})
 
  478         Platform.runLater(() -> {
 
  489     @NbBundle.Messages({
"ViewFrame.histogramTask.title=Rebuilding Histogram",
 
  490         "ViewFrame.histogramTask.preparing=Preparing",
 
  491         "ViewFrame.histogramTask.resetUI=Resetting UI",
 
  492         "ViewFrame.histogramTask.queryDb=Querying FB",
 
  493         "ViewFrame.histogramTask.updateUI2=Updating UI"})
 
  500             private final Lighting lighting = 
new Lighting();
 
  503             protected Void call() 
throws Exception {
 
  505                 updateMessage(Bundle.ViewFrame_histogramTask_preparing());
 
  517                 Platform.runLater(() -> {
 
  518                     updateMessage(Bundle.ViewFrame_histogramTask_resetUI());
 
  522                 ArrayList<Long> bins = 
new ArrayList<>();
 
  524                 DateTime start = timeRange.getStart();
 
  525                 while (timeRange.contains(start)) {
 
  530                     final Interval interval = 
new Interval(start, end);
 
  535                     updateMessage(Bundle.ViewFrame_histogramTask_queryDb());
 
  540                     max = Math.max(count, max);
 
  542                     final double fMax = Math.log(max);
 
  543                     final ArrayList<Long> fbins = 
new ArrayList<>(bins);
 
  544                     Platform.runLater(() -> {
 
  545                         updateMessage(Bundle.ViewFrame_histogramTask_updateUI2());
 
  549                         for (Long bin : fbins) {
 
  553                             Region bar = 
new Region();
 
  555                             bar.prefHeightProperty().bind(
histogramBox.heightProperty().multiply(Math.log(bin)).divide(fMax));
 
  556                             bar.setMaxHeight(USE_PREF_SIZE);
 
  557                             bar.setMinHeight(USE_PREF_SIZE);
 
  558                             bar.setBackground(GRAY_BACKGROUND);
 
  559                             bar.setOnMouseEntered((MouseEvent event) -> {
 
  560                                 Tooltip.install(bar, 
new Tooltip(bin.toString()));
 
  562                             bar.setEffect(lighting);
 
  564                             HBox.setHgrow(bar, Priority.ALWAYS);
 
  581         "ViewFrame.refreshTimeUI.errorMessage=Error gettig the spanning interval."})
 
  591             if ( maxTime > minTime) {
 
  592                 Platform.runLater(() -> {
 
  593                     startPicker.localDateTimeProperty().removeListener(startListener);
 
  594                     endPicker.localDateTimeProperty().removeListener(endListener);
 
  607                     startPicker.localDateTimeProperty().addListener(startListener);
 
  608                     endPicker.localDateTimeProperty().addListener(endListener);
 
  612             Notifications.create().owner(getScene().getWindow())
 
  613                     .text(Bundle.ViewFrame_refreshTimeUI_errorMessage())
 
  615             logger.log(Level.SEVERE, 
"Error gettig the spanning interval.", ex);
 
  633         switch (newViewMode) {
 
  650                 throw new IllegalArgumentException(
"Unknown ViewMode: " + newViewMode.toString());
 
  670         hostedView.hasVisibleEventsProperty().addListener(hasEvents -> {
 
  674                             NO_EVENTS_BACKGROUND,
 
  703         this.timeNavigationNodes.setAll(timeNavigationNodes);
 
  704         timeRangeToolBar.getItems().addAll(TIME_TOOLBAR_INSERTION_INDEX, timeNavigationNodes);
 
  708     @NbBundle.Messages(
"NoEventsDialog.titledPane.text=No Visible Events")
 
  732         @NbBundle.Messages(
"ViewFrame.noEventsDialogLabel.text=There are no events visible with the current zoom / filter settings.")
 
  734             assert resetFiltersButton != null : 
"fx:id=\"resetFiltersButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; 
 
  735             assert dismissButton != null : 
"fx:id=\"dismissButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; 
 
  736             assert zoomButton != null : 
"fx:id=\"zoomButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; 
 
  738             titledPane.setText(Bundle.NoEventsDialog_titledPane_text());
 
  739             noEventsDialogLabel.setText(Bundle.ViewFrame_noEventsDialogLabel_text());
 
  741             dismissButton.setOnAction(actionEvent -> closeCallback.run());
 
  758         PickerListener(Supplier<LocalDateTimeTextField> pickerSupplier, BiFunction<Interval, Long, Interval> intervalMapper) {
 
  763         @NbBundle.Messages({
"ViewFrame.pickerListener.errorMessage=Error responding to date/time picker change."})
 
  766             LocalDateTime pickerTime = pickerSupplier.get().getLocalDateTime();
 
  767             if (pickerTime != null) {
 
  771                     Notifications.create().owner(getScene().getWindow())
 
  772                             .text(Bundle.ViewFrame_pickerListener_errorMessage())
 
  774                     logger.log(Level.WARNING, 
"Error responding to date/time picker change.", ex); 
 
  775                 } 
catch (IllegalArgumentException ex ) {
 
  776                     logger.log(Level.INFO, 
"Timeline: User supplied invalid time range."); 
 
  779                 Platform.runLater(
ViewFrame.this::refreshTimeUI);
 
  787     private class LocalDateDisabler implements Callback<LocalDateTimePicker.LocalDateTimeRange, Void> {
 
  790             "ViewFrame.localDateDisabler.errorMessage=Error getting spanning interval."})
 
  792         public Void 
call(LocalDateTimePicker.LocalDateTimeRange viewedRange) {
 
  795             endPicker.disabledLocalDateTimes().clear();
 
  799                 long spanStartMillis = spanningInterval.getStartMillis();
 
  800                 long spaneEndMillis = spanningInterval.getEndMillis();
 
  802                 LocalDate rangeStartLocalDate = viewedRange.getStartLocalDateTime().toLocalDate();
 
  803                 LocalDate rangeEndLocalDate = viewedRange.getEndLocalDateTime().toLocalDate().plusDays(1);
 
  805                 for (LocalDate dt = rangeStartLocalDate; 
false == dt.isAfter(rangeEndLocalDate); dt = dt.plusDays(1)) {
 
  806                     long startOfDay = dt.atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli();
 
  807                     long endOfDay = dt.plusDays(1).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli();
 
  809                     if (endOfDay < spanStartMillis || startOfDay > spaneEndMillis) {
 
  810                         startPicker.disabledLocalDateTimes().add(dt.atStartOfDay());
 
  811                         endPicker.disabledLocalDateTimes().add(dt.atStartOfDay());
 
  816                 Notifications.create().owner(getScene().getWindow())
 
  817                         .text(Bundle.ViewFrame_localDateDisabler_errorMessage())
 
  819                 logger.log(Level.SEVERE, 
"Error getting spanning interval.", ex);
 
  835         private final LocalDateTimeTextField 
picker;
 
  842             "ViewFrame.dateTimeValidator.errorMessage=Error getting spanning interval."})
 
  844         public Boolean 
call(LocalDateTime param) {
 
  850                     if (picker.isPickerShowing() == 
false) {
 
  852                         picker.setDisplayedLocalDateTime(picker.getLocalDateTime());
 
  857                 Notifications.create().owner(getScene().getWindow())
 
  858                         .text(Bundle.ViewFrame_dateTimeValidator_errorMessage())
 
  860                 logger.log(Level.SEVERE, 
"Error getting spanning interval.", ex);
 
  872             "ViewFrame.refresh.text=Refresh View",
 
  873             "ViewFrame.refresh.longText=Refresh the view to include information that is in the DB but not displayed, such as newly updated tags."})
 
  875             super(Bundle.ViewFrame_refresh_text());
 
  876             setLongText(Bundle.ViewFrame_refresh_longText());
 
  877             setGraphic(
new ImageView(REFRESH));
 
static final Image WARNING
final EventsTree eventsTree
final Runnable closeCallback
Void call(LocalDateTimePicker.LocalDateTimeRange viewedRange)
void setHighLightedEvents(ObservableList< DetailViewEvent > highlightedEvents)
ReadOnlyBooleanProperty needsRefreshProperty()
abstract boolean hasCustomTimeNavigationControls()
final InvalidationListener zoomListener
void setViewSettingsControls(List< Node > newSettingsNodes)
synchronized ViewMode getViewMode()
void handleCacheInvalidated(EventsModel.CacheInvalidatedEvent event)
final NotificationPane notificationPane
abstract ImmutableList< Node > getTimeNavigationControls()
synchronized void refreshHistorgram()
static final Logger logger
final ObservableList< Node > settingsNodes
synchronized void setViewMode(ViewMode viewMode)
final synchronized void refresh()
ToggleButton countsToggle
Boolean call(LocalDateTime param)
LoggedTask< Void > histogramTask
final ObservableList< Node > timeNavigationNodes
abstract ImmutableList< Node > getSettingsControls()
final ReadOnlyObjectWrapper< Interval > timeRangeProperty
void handleTimeLineTagUpdate(TagsUpdatedEvent event)
synchronized boolean pushTimeRange(Interval timeRange)
void setTimeNavigationControls(List< Node > timeNavigationNodes)
final ReadOnlyBooleanWrapper hasVisibleEvents
final RangeSlider rangeSlider
Interval getSpanningInterval(DateTimeZone timeZone)
final TimeLineController controller
final InvalidationListener endListener
static final Background GRAY_BACKGROUND
static final Region NO_EVENTS_BACKGROUND
final InvalidationListener rangeSliderListener
static ZoneId getTimeZoneID()
synchronized void registerForEvents(Object listener)
void invalidated(Observable observable)
final ReadOnlyObjectWrapper< EventsModelParams > modelParamsProperty
static final int SETTINGS_TOOLBAR_INSERTION_INDEX
synchronized void monitorTask(final Task<?> task)
synchronized void pushPeriod(ReadablePeriod period)
StackPane rangeHistogramStack
ObservableList< DetailViewEvent > getSelectedEvents()
SleuthkitCase getSleuthkitCase()
final InvalidationListener startListener
ViewFrame(@Nonnull TimeLineController controller,@Nonnull EventsTree eventsTree)
static LocalDateTime epochMillisToLocalDateTime(long millis)
NoEventsDialog(Runnable closeCallback)
Label noEventsDialogLabel
void handleRefreshRequested(RefreshRequestedEvent event)
ToggleGroupValue< ViewMode > viewModeToggleGroup
final BiFunction< Interval, Long, Interval > intervalMapper
static DateTimeZone getJodaTimeZone()
LocalDateTimeTextField startPicker
static final int TIME_TOOLBAR_INSERTION_INDEX
synchronized void registerForEvents(Object subscriber)
AbstractTimeLineView hostedView
Button resetFiltersButton
TimeUnits getPeriodSize()
synchronized ReadOnlyObjectProperty< ViewMode > viewModeProperty()
synchronized static Logger getLogger(String name)
void setDetailViewPane(DetailViewPane detailViewPane)
void postRefreshRequest()
static final Image REFRESH
LocalDateTimeTextField endPicker
final Supplier< LocalDateTimeTextField > pickerSupplier
static void construct(Node node, String fxmlFileName)
ToggleButton detailsToggle
ImmutableList< Node > defaultTimeNavigationNodes
final ReadOnlyBooleanWrapper needsRefresh
synchronized Interval getTimeRange()
static RangeDivision getRangeDivision(Interval timeRange, DateTimeZone timeZone)
SegmentedButton modeSegButton
MenuButton zoomMenuButton
Map< TimelineEventType, Long > getEventCounts(Interval timeRange)
final EventsModel filteredEvents
final LocalDateTimeTextField picker
void registerForEvents(Object listener)
static long localDateTimeToEpochMilli(LocalDateTime localDateTime)