Autopsy  4.19.3
Graphical digital forensics platform for The Sleuth Kit and other tools.
ViewFrame.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-19 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.timeline.ui;
20 
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;
97 import org.sleuthkit.datamodel.TskCoreException;
98 
107 final public class ViewFrame extends BorderPane {
108 
109  private static final Logger logger = Logger.getLogger(ViewFrame.class.getName());
110 
111  private static final Image WARNING = new Image("org/sleuthkit/autopsy/timeline/images/warning_triangle.png", 16, 16, true, true); //NON-NLS
112  private static final Image REFRESH = new Image("org/sleuthkit/autopsy/timeline/images/arrow-circle-double-135.png"); //NON-NLS
113  private static final Background GRAY_BACKGROUND = new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY));
114 
120  private final static Region NO_EVENTS_BACKGROUND = new Region() {
121  {
122  setBackground(GRAY_BACKGROUND);
123  setOpacity(.3);
124  }
125  };
126 
131  private static final int SETTINGS_TOOLBAR_INSERTION_INDEX = 2;
132 
137  private static final int TIME_TOOLBAR_INSERTION_INDEX = 2;
138 
139  @GuardedBy("this")
140  private LoggedTask<Void> histogramTask;
141 
142  private final EventsTree eventsTree;
144 
145  /*
146  * HBox that contains the histogram bars.
147  *
148  * //TODO: Abstract this into a seperate class, and/or use a real bar
149  * chart? -jm
150  */
151  @FXML
152  private HBox histogramBox;
153  /*
154  * Stack pane that superimposes rangeslider over histogram
155  */
156  @FXML
157  private StackPane rangeHistogramStack;
158 
159  private final RangeSlider rangeSlider = new RangeSlider(0, 1.0, .25, .75);
160 
164  @FXML
165  private ToolBar timeRangeToolBar;
166 
171  @FXML
172  private HBox zoomInOutHBox;
173 
175  @FXML
176  private MenuButton zoomMenuButton;
177  @FXML
178  private Button zoomOutButton;
179  @FXML
180  private Button zoomInButton;
181  @FXML
182  private LocalDateTimeTextField startPicker;
183  @FXML
184  private LocalDateTimeTextField endPicker;
185  @FXML
186  private Label startLabel;
187  @FXML
188  private Label endLabel;
189 
191  @FXML
192  private ToolBar toolBar;
193 
194  private ToggleGroupValue<ViewMode> viewModeToggleGroup;
195  @FXML
196  private Label viewModeLabel;
197  @FXML
198  private SegmentedButton modeSegButton;
199  @FXML
200  private ToggleButton countsToggle;
201  @FXML
202  private ToggleButton detailsToggle;
203  @FXML
204  private ToggleButton listToggle;
205 
206  @FXML
207  private Button addEventButton;
208  @FXML
209  private Button snapShotButton;
210  @FXML
211  private Button refreshButton;
212 
213  /*
214  * Default zoom in/out buttons provided by the ViewFrame, some views replace
215  * these with other nodes (eg, list view)
216  */
217  @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
218  private ImmutableList<Node> defaultTimeNavigationNodes;
219 
220  /*
221  * The settings nodes for the current view.
222  */
223  @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
224  private final ObservableList<Node> settingsNodes = FXCollections.observableArrayList();
225 
226  /*
227  * The time nagivation nodes for the current view.
228  */
229  @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
230  private final ObservableList<Node> timeNavigationNodes = FXCollections.observableArrayList();
231 
236  private final NotificationPane notificationPane = new NotificationPane();
237 
240 
245  @NbBundle.Messages({
246  "ViewFrame.rangeSliderListener.errorMessage=Error responding to range slider."})
247  private final InvalidationListener rangeSliderListener = new InvalidationListener() {
248  @Override
249  public void invalidated(Observable observable) {
250  if (rangeSlider.isHighValueChanging() == false
251  && rangeSlider.isLowValueChanging() == false) {
252  try {
254  if (false == controller.pushTimeRange(new Interval(
255  (long) (rangeSlider.getLowValue() + minTime),
256  (long) (rangeSlider.getHighValue() + minTime + 1000)))) {
257  refreshTimeUI();
258  }
259  } catch (TskCoreException ex) {
260  Notifications.create().owner(getScene().getWindow())
261  .text(Bundle.ViewFrame_rangeSliderListener_errorMessage())
262  .showError();
263  logger.log(Level.SEVERE, "Error responding to range slider.", ex);
264  }
265  }
266  }
267  };
268 
272  private final InvalidationListener zoomListener = any -> handleRefreshRequested(null);
273 
277  private final InvalidationListener endListener = new PickerListener(() -> endPicker, Interval::withEndMillis);
278 
282  private final InvalidationListener startListener = new PickerListener(() -> startPicker, Interval::withStartMillis);
283 
293  private static long localDateTimeToEpochMilli(LocalDateTime localDateTime) {
294  return localDateTime.atZone(TimeLineController.getTimeZoneID()).toInstant().toEpochMilli();
295  }
296 
305  private static LocalDateTime epochMillisToLocalDateTime(long millis) {
306  return LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), TimeLineController.getTimeZoneID());
307  }
308 
316  this.controller = controller;
317  this.filteredEvents = controller.getEventsModel();
318  this.eventsTree = eventsTree;
319  FXMLConstructor.construct(this, "ViewFrame.fxml"); //NON-NLS
320 
321  }
322 
323  @FXML
324  @NbBundle.Messages({
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."
334  })
335  void initialize() {
336  assert endPicker != null : "fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; //NON-NLS
337  assert histogramBox != null : "fx:id=\"histogramBox\" was not injected: check your FXML file 'ViewWrapper.fxml'."; //NON-NLS
338  assert startPicker != null : "fx:id=\"startPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; //NON-NLS
339  assert rangeHistogramStack != null : "fx:id=\"rangeHistogramStack\" was not injected: check your FXML file 'ViewWrapper.fxml'."; //NON-NLS
340  assert countsToggle != null : "fx:id=\"countsToggle\" was not injected: check your FXML file 'VisToggle.fxml'."; //NON-NLS
341  assert detailsToggle != null : "fx:id=\"eventsToggle\" was not injected: check your FXML file 'VisToggle.fxml'."; //NON-NLS
342 
345 
346  //configure notification pane
347  notificationPane.getStyleClass().add(NotificationPane.STYLE_CLASS_DARK);
348 
349  notificationPane.setGraphic(new ImageView(WARNING));
350  setCenter(notificationPane);
351 
352  //configure view mode toggle
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());
357  viewModeToggleGroup = new ToggleGroupValue<>();
361  modeSegButton.setToggleGroup(viewModeToggleGroup);
362  viewModeToggleGroup.valueProperty().addListener((observable, oldViewMode, newViewVode)
363  -> controller.setViewMode(newViewVode != null ? newViewVode : (oldViewMode != null ? oldViewMode : ViewMode.COUNTS))
364  );
365 
366  controller.viewModeProperty().addListener(viewMode -> syncViewMode());
367  syncViewMode();
368 
369  ActionUtils.configureButton(new AddManualEvent(controller), addEventButton);
370  ActionUtils.configureButton(new SaveSnapshotAsReport(controller, notificationPane::getContent), snapShotButton);
371 
373  startLabel.setText(Bundle.ViewFrame_startLabel_text());
374  endLabel.setText(Bundle.ViewFrame_endLabel_text());
375 
376  //suppress stacktraces on malformed input
377  //TODO: should we do anything else? show a warning?
378  startPicker.setParseErrorCallback(throwable -> null);
379  endPicker.setParseErrorCallback(throwable -> null);
380 
381  //disable dates outside scope of case
382  LocalDateDisabler localDateDisabler = new LocalDateDisabler();
383  startPicker.setLocalDateTimeRangeCallback(localDateDisabler);
384  endPicker.setLocalDateTimeRangeCallback(localDateDisabler);
385 
386  //prevent selection of (date/)times outside the scope of this case
387  startPicker.setValueValidationCallback(new LocalDateTimeValidator(startPicker));
388  endPicker.setValueValidationCallback(new LocalDateTimeValidator(endPicker));
389 
390  //setup rangeslider
391  rangeSlider.setOpacity(.7);
392  rangeSlider.setMin(0);
393  rangeSlider.setBlockIncrement(1);
394  rangeHistogramStack.getChildren().add(rangeSlider);
395 
396  /*
397  * This padding attempts to compensates for the fact that the
398  * rangeslider track doesn't extend to edge of node,and so the
399  * histrogram doesn't quite line up with the rangeslider
400  */
401  histogramBox.setStyle(" -fx-padding: 0,0.5em,0,.5em; "); //NON-NLS
402 
403  //configure zoom buttons
404  zoomMenuButton.getItems().clear();
405  for (ZoomRanges zoomRange : ZoomRanges.values()) {
406  zoomMenuButton.getItems().add(ActionUtils.createMenuItem(
407  new Action(zoomRange.getDisplayName(), actionEvent -> {
408  try {
409  controller.pushPeriod(zoomRange.getPeriod());
410  } catch (TskCoreException ex) {
411  Notifications.create().owner(getScene().getWindow())
412  .text(Bundle.ViewFrame_zoomMenuButton_errorMessage())
413  .showError();
414  logger.log(Level.SEVERE, "Error pushing a time range.", ex);
415  }
416  })
417  ));
418  }
419  zoomMenuButton.setText(Bundle.ViewFrame_zoomMenuButton_text());
420  ActionUtils.configureButton(new ZoomOut(controller), zoomOutButton);
421  ActionUtils.configureButton(new ZoomIn(controller), zoomInButton);
422 
423  //register for EventBus events (tags)
425 
426  //listen for changes in the time range / zoom params
427  TimeLineController.timeZoneProperty().addListener(timeZoneProp -> refreshTimeUI());
428  filteredEvents.timeRangeProperty().addListener(timeRangeProp -> refreshTimeUI());
429  filteredEvents.modelParamsProperty().addListener(zoomListener);
430  refreshTimeUI(); //populate the view
431 
433  }
434 
443  @Subscribe
445  Platform.runLater(() -> {
446  hostedView.setNeedsRefresh();
447  notificationPane.show(Bundle.ViewFrame_tagsAddedOrDeleted());
448  });
449  }
450 
460  @Subscribe
462  Platform.runLater(() -> {
463  notificationPane.hide();
465  });
466  }
467 
474  @Subscribe
475  @NbBundle.Messages({
476  "ViewFrame.notification.cacheInvalidated=The event data has been updated, the visualization may be out of date."})
478  Platform.runLater(() -> {
479  if (hostedView.needsRefresh() == false) {
480  hostedView.setNeedsRefresh();
481  notificationPane.show(Bundle.ViewFrame_notification_cacheInvalidated());
482  }
483  });
484  }
485 
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"})
494  synchronized private void refreshHistorgram() {
495  if (histogramTask != null) {
496  histogramTask.cancel(true);
497  }
498 
499  histogramTask = new LoggedTask<Void>(Bundle.ViewFrame_histogramTask_title(), true) {
500  private final Lighting lighting = new Lighting();
501 
502  @Override
503  protected Void call() throws Exception {
504 
505  updateMessage(Bundle.ViewFrame_histogramTask_preparing());
506 
507  long max = 0;
509  final long lowerBound = rangeInfo.getLowerBound();
510  final long upperBound = rangeInfo.getUpperBound();
511  Interval timeRange = new Interval(new DateTime(lowerBound, TimeLineController.getJodaTimeZone()), new DateTime(upperBound, TimeLineController.getJodaTimeZone()));
512 
513  //extend range to block bounderies (ie day, month, year)
514  int p = 0; // progress counter
515 
516  //clear old data, and reset ranges and series
517  Platform.runLater(() -> {
518  updateMessage(Bundle.ViewFrame_histogramTask_resetUI());
519 
520  });
521 
522  ArrayList<Long> bins = new ArrayList<>();
523 
524  DateTime start = timeRange.getStart();
525  while (timeRange.contains(start)) {
526  if (isCancelled()) {
527  return null;
528  }
529  DateTime end = start.plus(rangeInfo.getPeriodSize().toUnitPeriod());
530  final Interval interval = new Interval(start, end);
531  //increment for next iteration
532 
533  start = end;
534 
535  updateMessage(Bundle.ViewFrame_histogramTask_queryDb());
536  //query for current range
537  long count = filteredEvents.getEventCounts(interval).values().stream().mapToLong(Long::valueOf).sum();
538  bins.add(count);
539 
540  max = Math.max(count, max);
541 
542  final double fMax = Math.log(max);
543  final ArrayList<Long> fbins = new ArrayList<>(bins);
544  Platform.runLater(() -> {
545  updateMessage(Bundle.ViewFrame_histogramTask_updateUI2());
546 
547  histogramBox.getChildren().clear();
548 
549  for (Long bin : fbins) {
550  if (isCancelled()) {
551  break;
552  }
553  Region bar = new Region();
554  //scale them to fit in histogram height
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()));
561  });
562  bar.setEffect(lighting);
563  //they each get equal width to fill the histogram horizontally
564  HBox.setHgrow(bar, Priority.ALWAYS);
565  histogramBox.getChildren().add(bar);
566  }
567  });
568  }
569  return null;
570  }
571 
572  };
573  new Thread(histogramTask).start();
575  }
576 
580  @NbBundle.Messages({
581  "ViewFrame.refreshTimeUI.errorMessage=Error gettig the spanning interval."})
582  private void refreshTimeUI() {
583  try {
585  final long minTime = rangeDivisionInfo.getLowerBound();
586  final long maxTime = rangeDivisionInfo.getUpperBound();
587 
588  long startMillis = filteredEvents.getTimeRange().getStartMillis();
589  long endMillis = filteredEvents.getTimeRange().getEndMillis();
590 
591  if ( maxTime > minTime) {
592  Platform.runLater(() -> {
593  startPicker.localDateTimeProperty().removeListener(startListener);
594  endPicker.localDateTimeProperty().removeListener(endListener);
595  rangeSlider.highValueChangingProperty().removeListener(rangeSliderListener);
596  rangeSlider.lowValueChangingProperty().removeListener(rangeSliderListener);
597 
598  rangeSlider.setMax((maxTime - minTime));
599 
600  rangeSlider.setLowValue(startMillis - minTime);
601  rangeSlider.setHighValue(endMillis - minTime);
602  startPicker.setLocalDateTime(epochMillisToLocalDateTime(startMillis));
603  endPicker.setLocalDateTime(epochMillisToLocalDateTime(endMillis));
604 
605  rangeSlider.highValueChangingProperty().addListener(rangeSliderListener);
606  rangeSlider.lowValueChangingProperty().addListener(rangeSliderListener);
607  startPicker.localDateTimeProperty().addListener(startListener);
608  endPicker.localDateTimeProperty().addListener(endListener);
609  });
610  }
611  } catch (TskCoreException ex) {
612  Notifications.create().owner(getScene().getWindow())
613  .text(Bundle.ViewFrame_refreshTimeUI_errorMessage())
614  .showError();
615  logger.log(Level.SEVERE, "Error gettig the spanning interval.", ex);
616  }
617  }
618 
624  private void syncViewMode() {
625  ViewMode newViewMode = controller.getViewMode();
626 
627  //clear out old view.
628  if (hostedView != null) {
629  hostedView.dispose();
630  }
631 
632  //Set a new AbstractTimeLineView as the one hosted by this ViewFrame.
633  switch (newViewMode) {
634  case LIST:
636  //TODO: should remove listeners from events tree
637  break;
638  case COUNTS:
640  //TODO: should remove listeners from events tree
641  break;
642  case DETAIL:
643  DetailViewPane detailViewPane = new DetailViewPane(controller);
644  //link events tree to detailview instance.
646  eventsTree.setDetailViewPane(detailViewPane);
647  hostedView = detailViewPane;
648  break;
649  default:
650  throw new IllegalArgumentException("Unknown ViewMode: " + newViewMode.toString());//NON-NLS
651  }
652  notificationPane.getActions().setAll(new Refresh());
654  controller.getAutopsyCase().getSleuthkitCase().registerForEvents(this);
655 
656  viewModeToggleGroup.setValue(newViewMode); //this selects the right toggle automatically
657 
658  //configure settings and time navigation nodes
663 
664  //do further setup of new view.
665  ActionUtils.unconfigureButton(refreshButton);
666  ActionUtils.configureButton(new Refresh(), refreshButton);//configure new refresh action for new view
668  notificationPane.setContent(hostedView);
669  //listen to has events property and show "dialog" if it is false.
670  hostedView.hasVisibleEventsProperty().addListener(hasEvents -> {
672  ? hostedView
673  : new StackPane(hostedView,
674  NO_EVENTS_BACKGROUND,
675  new NoEventsDialog(() -> notificationPane.setContent(hostedView))
676  )
677  );
678  });
679  }
680 
688  private void setViewSettingsControls(List<Node> newSettingsNodes) {
689  toolBar.getItems().removeAll(this.settingsNodes); //remove old nodes
690  this.settingsNodes.setAll(newSettingsNodes);
691  toolBar.getItems().addAll(SETTINGS_TOOLBAR_INSERTION_INDEX, settingsNodes);
692  }
693 
702  timeRangeToolBar.getItems().removeAll(this.timeNavigationNodes); //remove old nodes
703  this.timeNavigationNodes.setAll(timeNavigationNodes);
704  timeRangeToolBar.getItems().addAll(TIME_TOOLBAR_INSERTION_INDEX, timeNavigationNodes);
705 
706  }
707 
708  @NbBundle.Messages("NoEventsDialog.titledPane.text=No Visible Events")
709  private class NoEventsDialog extends StackPane {
710 
711  @FXML
712  private TitledPane titledPane;
713  @FXML
714  private Button backButton;
715  @FXML
716  private Button resetFiltersButton;
717  @FXML
718  private Button dismissButton;
719  @FXML
720  private Button zoomButton;
721  @FXML
722  private Label noEventsDialogLabel;
723 
724  private final Runnable closeCallback;
725 
726  private NoEventsDialog(Runnable closeCallback) {
727  this.closeCallback = closeCallback;
728  FXMLConstructor.construct(this, "NoEventsDialog.fxml"); //NON-NLS
729  }
730 
731  @FXML
732  @NbBundle.Messages("ViewFrame.noEventsDialogLabel.text=There are no events visible with the current zoom / filter settings.")
733  void initialize() {
734  assert resetFiltersButton != null : "fx:id=\"resetFiltersButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; //NON-NLS
735  assert dismissButton != null : "fx:id=\"dismissButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; //NON-NLS
736  assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; //NON-NLS
737 
738  titledPane.setText(Bundle.NoEventsDialog_titledPane_text());
739  noEventsDialogLabel.setText(Bundle.ViewFrame_noEventsDialogLabel_text());
740 
741  dismissButton.setOnAction(actionEvent -> closeCallback.run());
742 
743  ActionUtils.configureButton(new ZoomToEvents(controller), zoomButton);
744  ActionUtils.configureButton(new Back(controller), backButton);
745  ActionUtils.configureButton(new ResetFilters(controller), resetFiltersButton);
746  }
747  }
748 
753  private class PickerListener implements InvalidationListener {
754 
755  private final BiFunction< Interval, Long, Interval> intervalMapper;
756  private final Supplier<LocalDateTimeTextField> pickerSupplier;
757 
758  PickerListener(Supplier<LocalDateTimeTextField> pickerSupplier, BiFunction<Interval, Long, Interval> intervalMapper) {
759  this.pickerSupplier = pickerSupplier;
760  this.intervalMapper = intervalMapper;
761  }
762 
763  @NbBundle.Messages({"ViewFrame.pickerListener.errorMessage=Error responding to date/time picker change."})
764  @Override
765  public void invalidated(Observable observable) {
766  LocalDateTime pickerTime = pickerSupplier.get().getLocalDateTime();
767  if (pickerTime != null) {
768  try {
769  controller.pushTimeRange(intervalMapper.apply(filteredEvents.getTimeRange(), localDateTimeToEpochMilli(pickerTime)));
770  } catch (TskCoreException ex) {
771  Notifications.create().owner(getScene().getWindow())
772  .text(Bundle.ViewFrame_pickerListener_errorMessage())
773  .showError();
774  logger.log(Level.WARNING, "Error responding to date/time picker change.", ex); //NON-NLS
775  } catch (IllegalArgumentException ex ) {
776  logger.log(Level.INFO, "Timeline: User supplied invalid time range."); //NON-NLS
777  }
778 
779  Platform.runLater(ViewFrame.this::refreshTimeUI);
780  }
781  }
782  }
783 
787  private class LocalDateDisabler implements Callback<LocalDateTimePicker.LocalDateTimeRange, Void> {
788 
789  @NbBundle.Messages({
790  "ViewFrame.localDateDisabler.errorMessage=Error getting spanning interval."})
791  @Override
792  public Void call(LocalDateTimePicker.LocalDateTimeRange viewedRange) {
793 
794  startPicker.disabledLocalDateTimes().clear();
795  endPicker.disabledLocalDateTimes().clear();
796  try {
797  //all events in the case are contained in this interval
798  Interval spanningInterval = filteredEvents.getSpanningInterval();
799  long spanStartMillis = spanningInterval.getStartMillis();
800  long spaneEndMillis = spanningInterval.getEndMillis();
801 
802  LocalDate rangeStartLocalDate = viewedRange.getStartLocalDateTime().toLocalDate();
803  LocalDate rangeEndLocalDate = viewedRange.getEndLocalDateTime().toLocalDate().plusDays(1);
804  //iterate over days of the displayed range and disable ones not in spanning interval
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();
808  //if no part of day is within spanning interval, add that date the list of disabled dates.
809  if (endOfDay < spanStartMillis || startOfDay > spaneEndMillis) {
810  startPicker.disabledLocalDateTimes().add(dt.atStartOfDay());
811  endPicker.disabledLocalDateTimes().add(dt.atStartOfDay());
812  }
813  }
814 
815  } catch (TskCoreException ex) {
816  Notifications.create().owner(getScene().getWindow())
817  .text(Bundle.ViewFrame_localDateDisabler_errorMessage())
818  .showError();
819  logger.log(Level.SEVERE, "Error getting spanning interval.", ex);
820  }
821  return null;
822  }
823  }
824 
830  private class LocalDateTimeValidator implements Callback<LocalDateTime, Boolean> {
831 
835  private final LocalDateTimeTextField picker;
836 
837  LocalDateTimeValidator(LocalDateTimeTextField picker) {
838  this.picker = picker;
839  }
840 
841  @NbBundle.Messages({
842  "ViewFrame.dateTimeValidator.errorMessage=Error getting spanning interval."})
843  @Override
844  public Boolean call(LocalDateTime param) {
845  long epochMilli = localDateTimeToEpochMilli(param);
846  try {
847  if (filteredEvents.getSpanningInterval().contains(epochMilli)) {
848  return true;
849  } else {
850  if (picker.isPickerShowing() == false) {
851  //if the user typed an in valid date, reset the text box to the selected date.
852  picker.setDisplayedLocalDateTime(picker.getLocalDateTime());
853  }
854  return false;
855  }
856  } catch (TskCoreException ex) {
857  Notifications.create().owner(getScene().getWindow())
858  .text(Bundle.ViewFrame_dateTimeValidator_errorMessage())
859  .showError();
860  logger.log(Level.SEVERE, "Error getting spanning interval.", ex);
861  return false;
862  }
863  }
864  }
865 
869  private class Refresh extends Action {
870 
871  @NbBundle.Messages({
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."})
874  Refresh() {
875  super(Bundle.ViewFrame_refresh_text());
876  setLongText(Bundle.ViewFrame_refresh_longText());
877  setGraphic(new ImageView(REFRESH));
878  setEventHandler(actionEvent -> filteredEvents.postRefreshRequest());
879  disabledProperty().bind(hostedView.needsRefreshProperty().not());
880  }
881  }
882 }
Void call(LocalDateTimePicker.LocalDateTimeRange viewedRange)
Definition: ViewFrame.java:792
void setHighLightedEvents(ObservableList< DetailViewEvent > highlightedEvents)
final InvalidationListener zoomListener
Definition: ViewFrame.java:272
void setViewSettingsControls(List< Node > newSettingsNodes)
Definition: ViewFrame.java:688
void handleCacheInvalidated(EventsModel.CacheInvalidatedEvent event)
Definition: ViewFrame.java:477
abstract ImmutableList< Node > getTimeNavigationControls()
final ObservableList< Node > settingsNodes
Definition: ViewFrame.java:224
synchronized void setViewMode(ViewMode viewMode)
final ObservableList< Node > timeNavigationNodes
Definition: ViewFrame.java:230
abstract ImmutableList< Node > getSettingsControls()
final ReadOnlyObjectWrapper< Interval > timeRangeProperty
void handleTimeLineTagUpdate(TagsUpdatedEvent event)
Definition: ViewFrame.java:444
synchronized boolean pushTimeRange(Interval timeRange)
void setTimeNavigationControls(List< Node > timeNavigationNodes)
Definition: ViewFrame.java:701
Interval getSpanningInterval(DateTimeZone timeZone)
final InvalidationListener endListener
Definition: ViewFrame.java:277
final InvalidationListener rangeSliderListener
Definition: ViewFrame.java:247
synchronized void registerForEvents(Object listener)
final ReadOnlyObjectWrapper< EventsModelParams > modelParamsProperty
synchronized void monitorTask(final Task<?> task)
synchronized void pushPeriod(ReadablePeriod period)
ObservableList< DetailViewEvent > getSelectedEvents()
final InvalidationListener startListener
Definition: ViewFrame.java:282
ViewFrame(@Nonnull TimeLineController controller,@Nonnull EventsTree eventsTree)
Definition: ViewFrame.java:315
static LocalDateTime epochMillisToLocalDateTime(long millis)
Definition: ViewFrame.java:305
void handleRefreshRequested(RefreshRequestedEvent event)
Definition: ViewFrame.java:461
ToggleGroupValue< ViewMode > viewModeToggleGroup
Definition: ViewFrame.java:194
final BiFunction< Interval, Long, Interval > intervalMapper
Definition: ViewFrame.java:755
synchronized void registerForEvents(Object subscriber)
synchronized ReadOnlyObjectProperty< ViewMode > viewModeProperty()
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
void setDetailViewPane(DetailViewPane detailViewPane)
Definition: EventsTree.java:88
final Supplier< LocalDateTimeTextField > pickerSupplier
Definition: ViewFrame.java:756
static void construct(Node node, String fxmlFileName)
ImmutableList< Node > defaultTimeNavigationNodes
Definition: ViewFrame.java:218
static RangeDivision getRangeDivision(Interval timeRange, DateTimeZone timeZone)
Map< TimelineEventType, Long > getEventCounts(Interval timeRange)
static long localDateTimeToEpochMilli(LocalDateTime localDateTime)
Definition: ViewFrame.java:293

Copyright © 2012-2022 Basis Technology. Generated on: Tue Jun 27 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.