Autopsy 4.22.1
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 */
19package org.sleuthkit.autopsy.timeline.ui;
20
21import com.google.common.collect.ImmutableList;
22import com.google.common.eventbus.Subscribe;
23import java.time.Instant;
24import java.time.LocalDate;
25import java.time.LocalDateTime;
26import java.time.ZoneOffset;
27import java.util.ArrayList;
28import java.util.List;
29import java.util.function.BiFunction;
30import java.util.function.Supplier;
31import java.util.logging.Level;
32import javafx.application.Platform;
33import javafx.beans.InvalidationListener;
34import javafx.beans.Observable;
35import javafx.collections.FXCollections;
36import javafx.collections.ObservableList;
37import javafx.fxml.FXML;
38import javafx.geometry.Insets;
39import javafx.scene.Node;
40import javafx.scene.control.Button;
41import javafx.scene.control.Label;
42import javafx.scene.control.MenuButton;
43import javafx.scene.control.TitledPane;
44import javafx.scene.control.ToggleButton;
45import javafx.scene.control.ToolBar;
46import javafx.scene.control.Tooltip;
47import javafx.scene.effect.Lighting;
48import javafx.scene.image.Image;
49import javafx.scene.image.ImageView;
50import javafx.scene.input.MouseEvent;
51import javafx.scene.layout.Background;
52import javafx.scene.layout.BackgroundFill;
53import javafx.scene.layout.BorderPane;
54import javafx.scene.layout.CornerRadii;
55import javafx.scene.layout.HBox;
56import javafx.scene.layout.Priority;
57import javafx.scene.layout.Region;
58import static javafx.scene.layout.Region.USE_PREF_SIZE;
59import javafx.scene.layout.StackPane;
60import javafx.scene.paint.Color;
61import javafx.util.Callback;
62import javax.annotation.Nonnull;
63import javax.annotation.concurrent.GuardedBy;
64import jfxtras.scene.control.LocalDateTimePicker;
65import jfxtras.scene.control.LocalDateTimeTextField;
66import jfxtras.scene.control.ToggleGroupValue;
67import org.controlsfx.control.NotificationPane;
68import org.controlsfx.control.Notifications;
69import org.controlsfx.control.RangeSlider;
70import org.controlsfx.control.SegmentedButton;
71import org.controlsfx.control.action.Action;
72import org.controlsfx.control.action.ActionUtils;
73import org.joda.time.DateTime;
74import org.joda.time.Interval;
75import org.openide.util.NbBundle;
76import org.sleuthkit.autopsy.coreutils.LoggedTask;
77import org.sleuthkit.autopsy.coreutils.Logger;
78import org.sleuthkit.autopsy.coreutils.ThreadConfined;
79import org.sleuthkit.autopsy.timeline.FXMLConstructor;
80import org.sleuthkit.autopsy.timeline.EventsModel;
81import org.sleuthkit.autopsy.timeline.TimeLineController;
82import org.sleuthkit.autopsy.timeline.ViewMode;
83import org.sleuthkit.autopsy.timeline.actions.AddManualEvent;
84import org.sleuthkit.autopsy.timeline.actions.Back;
85import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
86import org.sleuthkit.autopsy.timeline.actions.SaveSnapshotAsReport;
87import org.sleuthkit.autopsy.timeline.actions.ZoomIn;
88import org.sleuthkit.autopsy.timeline.actions.ZoomOut;
89import org.sleuthkit.autopsy.timeline.actions.ZoomToEvents;
90import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
91import org.sleuthkit.autopsy.timeline.events.TagsUpdatedEvent;
92import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane;
93import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
94import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
95import org.sleuthkit.autopsy.timeline.ui.listvew.ListViewPane;
96import org.sleuthkit.autopsy.timeline.utils.RangeDivision;
97import org.sleuthkit.datamodel.TskCoreException;
98
107final 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")
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)))) {
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
315 @SuppressWarnings("this-escape")
317 this.controller = controller;
318 this.filteredEvents = controller.getEventsModel();
319 this.eventsTree = eventsTree;
320 FXMLConstructor.construct(this, "ViewFrame.fxml"); //NON-NLS
321
322 }
323
324 @FXML
325 @NbBundle.Messages({
326 "ViewFrame.viewModeLabel.text=View Mode:",
327 "ViewFrame.startLabel.text=Start:",
328 "ViewFrame.endLabel.text=End:",
329 "ViewFrame.countsToggle.text=Counts",
330 "ViewFrame.detailsToggle.text=Details",
331 "ViewFrame.listToggle.text=List",
332 "ViewFrame.zoomMenuButton.text=Zoom in/out to",
333 "ViewFrame.zoomMenuButton.errorMessage=Error pushing time range.",
334 "ViewFrame.tagsAddedOrDeleted=Tags have been created and/or deleted. The view may not be up to date."
335 })
336 void initialize() {
337 assert endPicker != null : "fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; //NON-NLS
338 assert histogramBox != null : "fx:id=\"histogramBox\" was not injected: check your FXML file 'ViewWrapper.fxml'."; //NON-NLS
339 assert startPicker != null : "fx:id=\"startPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; //NON-NLS
340 assert rangeHistogramStack != null : "fx:id=\"rangeHistogramStack\" was not injected: check your FXML file 'ViewWrapper.fxml'."; //NON-NLS
341 assert countsToggle != null : "fx:id=\"countsToggle\" was not injected: check your FXML file 'VisToggle.fxml'."; //NON-NLS
342 assert detailsToggle != null : "fx:id=\"eventsToggle\" was not injected: check your FXML file 'VisToggle.fxml'."; //NON-NLS
343
346
347 //configure notification pane
348 notificationPane.getStyleClass().add(NotificationPane.STYLE_CLASS_DARK);
349
350 notificationPane.setGraphic(new ImageView(WARNING));
351 setCenter(notificationPane);
352
353 //configure view mode toggle
354 viewModeLabel.setText(Bundle.ViewFrame_viewModeLabel_text());
355 countsToggle.setText(Bundle.ViewFrame_countsToggle_text());
356 detailsToggle.setText(Bundle.ViewFrame_detailsToggle_text());
357 listToggle.setText(Bundle.ViewFrame_listToggle_text());
358 viewModeToggleGroup = new ToggleGroupValue<>();
362 modeSegButton.setToggleGroup(viewModeToggleGroup);
363 viewModeToggleGroup.valueProperty().addListener((observable, oldViewMode, newViewVode)
364 -> controller.setViewMode(newViewVode != null ? newViewVode : (oldViewMode != null ? oldViewMode : ViewMode.COUNTS))
365 );
366
367 controller.viewModeProperty().addListener(viewMode -> syncViewMode());
368 syncViewMode();
369
370 ActionUtils.configureButton(new AddManualEvent(controller), addEventButton);
371 ActionUtils.configureButton(new SaveSnapshotAsReport(controller, notificationPane::getContent), snapShotButton);
372
374 startLabel.setText(Bundle.ViewFrame_startLabel_text());
375 endLabel.setText(Bundle.ViewFrame_endLabel_text());
376
377 //suppress stacktraces on malformed input
378 //TODO: should we do anything else? show a warning?
379 startPicker.setParseErrorCallback(throwable -> null);
380 endPicker.setParseErrorCallback(throwable -> null);
381
382 //disable dates outside scope of case
383 LocalDateDisabler localDateDisabler = new LocalDateDisabler();
384 startPicker.setLocalDateTimeRangeCallback(localDateDisabler);
385 endPicker.setLocalDateTimeRangeCallback(localDateDisabler);
386
387 //prevent selection of (date/)times outside the scope of this case
388 startPicker.setValueValidationCallback(new LocalDateTimeValidator(startPicker));
389 endPicker.setValueValidationCallback(new LocalDateTimeValidator(endPicker));
390
391 //setup rangeslider
392 rangeSlider.setOpacity(.7);
393 rangeSlider.setMin(0);
394 rangeSlider.setBlockIncrement(1);
395 rangeHistogramStack.getChildren().add(rangeSlider);
396
397 /*
398 * This padding attempts to compensates for the fact that the
399 * rangeslider track doesn't extend to edge of node,and so the
400 * histrogram doesn't quite line up with the rangeslider
401 */
402 histogramBox.setStyle(" -fx-padding: 0,0.5em,0,.5em; "); //NON-NLS
403
404 //configure zoom buttons
405 zoomMenuButton.getItems().clear();
406 for (ZoomRanges zoomRange : ZoomRanges.values()) {
407 zoomMenuButton.getItems().add(ActionUtils.createMenuItem(
408 new Action(zoomRange.getDisplayName(), actionEvent -> {
409 try {
410 controller.pushPeriod(zoomRange.getPeriod());
411 } catch (TskCoreException ex) {
412 Notifications.create().owner(getScene().getWindow())
413 .text(Bundle.ViewFrame_zoomMenuButton_errorMessage())
414 .showError();
415 logger.log(Level.SEVERE, "Error pushing a time range.", ex);
416 }
417 })
418 ));
419 }
420 zoomMenuButton.setText(Bundle.ViewFrame_zoomMenuButton_text());
421 ActionUtils.configureButton(new ZoomOut(controller), zoomOutButton);
422 ActionUtils.configureButton(new ZoomIn(controller), zoomInButton);
423
424 //register for EventBus events (tags)
426
427 //listen for changes in the time range / zoom params
428 TimeLineController.timeZoneProperty().addListener(timeZoneProp -> refreshTimeUI());
429 filteredEvents.timeRangeProperty().addListener(timeRangeProp -> refreshTimeUI());
431 refreshTimeUI(); //populate the view
432
434 }
435
444 @Subscribe
446 Platform.runLater(() -> {
447 hostedView.setNeedsRefresh();
448 notificationPane.show(Bundle.ViewFrame_tagsAddedOrDeleted());
449 });
450 }
451
461 @Subscribe
463 Platform.runLater(() -> {
464 notificationPane.hide();
466 });
467 }
468
475 @Subscribe
476 @NbBundle.Messages({
477 "ViewFrame.notification.cacheInvalidated=The event data has been updated, the visualization may be out of date."})
479 Platform.runLater(() -> {
480 if (hostedView.needsRefresh() == false) {
481 hostedView.setNeedsRefresh();
482 notificationPane.show(Bundle.ViewFrame_notification_cacheInvalidated());
483 }
484 });
485 }
486
490 @NbBundle.Messages({"ViewFrame.histogramTask.title=Rebuilding Histogram",
491 "ViewFrame.histogramTask.preparing=Preparing",
492 "ViewFrame.histogramTask.resetUI=Resetting UI",
493 "ViewFrame.histogramTask.queryDb=Querying FB",
494 "ViewFrame.histogramTask.updateUI2=Updating UI"})
495 synchronized private void refreshHistorgram() {
496 if (histogramTask != null) {
497 histogramTask.cancel(true);
498 }
499
500 histogramTask = new LoggedTask<Void>(Bundle.ViewFrame_histogramTask_title(), true) {
501 private final Lighting lighting = new Lighting();
502
503 @Override
504 protected Void call() throws Exception {
505
506 updateMessage(Bundle.ViewFrame_histogramTask_preparing());
507
508 long max = 0;
510 final long lowerBound = rangeInfo.getLowerBound();
511 final long upperBound = rangeInfo.getUpperBound();
512 Interval timeRange = new Interval(new DateTime(lowerBound, TimeLineController.getJodaTimeZone()), new DateTime(upperBound, TimeLineController.getJodaTimeZone()));
513
514 //extend range to block bounderies (ie day, month, year)
515 int p = 0; // progress counter
516
517 //clear old data, and reset ranges and series
518 Platform.runLater(() -> {
519 updateMessage(Bundle.ViewFrame_histogramTask_resetUI());
520
521 });
522
523 ArrayList<Long> bins = new ArrayList<>();
524
525 DateTime start = timeRange.getStart();
526 while (timeRange.contains(start)) {
527 if (isCancelled()) {
528 return null;
529 }
530 DateTime end = start.plus(rangeInfo.getPeriodSize().toUnitPeriod());
531 final Interval interval = new Interval(start, end);
532 //increment for next iteration
533
534 start = end;
535
536 updateMessage(Bundle.ViewFrame_histogramTask_queryDb());
537 //query for current range
538 long count = filteredEvents.getEventCounts(interval).values().stream().mapToLong(Long::valueOf).sum();
539 bins.add(count);
540
541 max = Math.max(count, max);
542
543 final double fMax = Math.log(max);
544 final ArrayList<Long> fbins = new ArrayList<>(bins);
545 Platform.runLater(() -> {
546 updateMessage(Bundle.ViewFrame_histogramTask_updateUI2());
547
548 histogramBox.getChildren().clear();
549
550 for (Long bin : fbins) {
551 if (isCancelled()) {
552 break;
553 }
554 Region bar = new Region();
555 //scale them to fit in histogram height
556 bar.prefHeightProperty().bind(histogramBox.heightProperty().multiply(Math.log(bin)).divide(fMax));
557 bar.setMaxHeight(USE_PREF_SIZE);
558 bar.setMinHeight(USE_PREF_SIZE);
559 bar.setBackground(GRAY_BACKGROUND);
560 bar.setOnMouseEntered((MouseEvent event) -> {
561 Tooltip.install(bar, new Tooltip(bin.toString()));
562 });
563 bar.setEffect(lighting);
564 //they each get equal width to fill the histogram horizontally
565 HBox.setHgrow(bar, Priority.ALWAYS);
566 histogramBox.getChildren().add(bar);
567 }
568 });
569 }
570 return null;
571 }
572
573 };
574 new Thread(histogramTask).start();
575 controller.monitorTask(histogramTask);
576 }
577
581 @NbBundle.Messages({
582 "ViewFrame.refreshTimeUI.errorMessage=Error gettig the spanning interval."})
583 private void refreshTimeUI() {
584 try {
586 final long minTime = rangeDivisionInfo.getLowerBound();
587 final long maxTime = rangeDivisionInfo.getUpperBound();
588
589 long startMillis = filteredEvents.getTimeRange().getStartMillis();
590 long endMillis = filteredEvents.getTimeRange().getEndMillis();
591
592 if ( maxTime > minTime) {
593 Platform.runLater(() -> {
594 startPicker.localDateTimeProperty().removeListener(startListener);
595 endPicker.localDateTimeProperty().removeListener(endListener);
596 rangeSlider.highValueChangingProperty().removeListener(rangeSliderListener);
597 rangeSlider.lowValueChangingProperty().removeListener(rangeSliderListener);
598
599 rangeSlider.setMax((maxTime - minTime));
600
601 rangeSlider.setLowValue(startMillis - minTime);
602 rangeSlider.setHighValue(endMillis - minTime);
603 startPicker.setLocalDateTime(epochMillisToLocalDateTime(startMillis));
604 endPicker.setLocalDateTime(epochMillisToLocalDateTime(endMillis));
605
606 rangeSlider.highValueChangingProperty().addListener(rangeSliderListener);
607 rangeSlider.lowValueChangingProperty().addListener(rangeSliderListener);
608 startPicker.localDateTimeProperty().addListener(startListener);
609 endPicker.localDateTimeProperty().addListener(endListener);
610 });
611 }
612 } catch (TskCoreException ex) {
613 Notifications.create().owner(getScene().getWindow())
614 .text(Bundle.ViewFrame_refreshTimeUI_errorMessage())
615 .showError();
616 logger.log(Level.SEVERE, "Error gettig the spanning interval.", ex);
617 }
618 }
619
625 private void syncViewMode() {
626 ViewMode newViewMode = controller.getViewMode();
627
628 //clear out old view.
629 if (hostedView != null) {
630 hostedView.dispose();
631 }
632
633 //Set a new AbstractTimeLineView as the one hosted by this ViewFrame.
634 switch (newViewMode) {
635 case LIST:
637 //TODO: should remove listeners from events tree
638 break;
639 case COUNTS:
641 //TODO: should remove listeners from events tree
642 break;
643 case DETAIL:
644 DetailViewPane detailViewPane = new DetailViewPane(controller);
645 //link events tree to detailview instance.
646 detailViewPane.setHighLightedEvents(eventsTree.getSelectedEvents());
647 eventsTree.setDetailViewPane(detailViewPane);
648 hostedView = detailViewPane;
649 break;
650 default:
651 throw new IllegalArgumentException("Unknown ViewMode: " + newViewMode.toString());//NON-NLS
652 }
653 notificationPane.getActions().setAll(new Refresh());
654 controller.registerForEvents(hostedView);
655 controller.getAutopsyCase().getSleuthkitCase().registerForEvents(this);
656
657 viewModeToggleGroup.setValue(newViewMode); //this selects the right toggle automatically
658
659 //configure settings and time navigation nodes
660 setViewSettingsControls(hostedView.getSettingsControls());
661 setTimeNavigationControls(hostedView.hasCustomTimeNavigationControls()
662 ? hostedView.getTimeNavigationControls()
664
665 //do further setup of new view.
666 ActionUtils.unconfigureButton(refreshButton);
667 ActionUtils.configureButton(new Refresh(), refreshButton);//configure new refresh action for new view
668 hostedView.refresh();
669 notificationPane.setContent(hostedView);
670 //listen to has events property and show "dialog" if it is false.
671 hostedView.hasVisibleEventsProperty().addListener(hasEvents -> {
672 notificationPane.setContent(hostedView.hasVisibleEvents()
673 ? hostedView
674 : new StackPane(hostedView,
676 new NoEventsDialog(() -> notificationPane.setContent(hostedView))
677 )
678 );
679 });
680 }
681
689 private void setViewSettingsControls(List<Node> newSettingsNodes) {
690 toolBar.getItems().removeAll(this.settingsNodes); //remove old nodes
691 this.settingsNodes.setAll(newSettingsNodes);
693 }
694
703 timeRangeToolBar.getItems().removeAll(this.timeNavigationNodes); //remove old nodes
704 this.timeNavigationNodes.setAll(timeNavigationNodes);
706
707 }
708
709 @NbBundle.Messages("NoEventsDialog.titledPane.text=No Visible Events")
710 private class NoEventsDialog extends StackPane {
711
712 @FXML
713 private TitledPane titledPane;
714 @FXML
715 private Button backButton;
716 @FXML
717 private Button resetFiltersButton;
718 @FXML
719 private Button dismissButton;
720 @FXML
721 private Button zoomButton;
722 @FXML
723 private Label noEventsDialogLabel;
724
725 private final Runnable closeCallback;
726
727 @SuppressWarnings("this-escape")
728 private NoEventsDialog(Runnable closeCallback) {
729 this.closeCallback = closeCallback;
730 FXMLConstructor.construct(this, "NoEventsDialog.fxml"); //NON-NLS
731 }
732
733 @FXML
734 @NbBundle.Messages("ViewFrame.noEventsDialogLabel.text=There are no events visible with the current zoom / filter settings.")
735 void initialize() {
736 assert resetFiltersButton != null : "fx:id=\"resetFiltersButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; //NON-NLS
737 assert dismissButton != null : "fx:id=\"dismissButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; //NON-NLS
738 assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; //NON-NLS
739
740 titledPane.setText(Bundle.NoEventsDialog_titledPane_text());
741 noEventsDialogLabel.setText(Bundle.ViewFrame_noEventsDialogLabel_text());
742
743 dismissButton.setOnAction(actionEvent -> closeCallback.run());
744
745 ActionUtils.configureButton(new ZoomToEvents(controller), zoomButton);
746 ActionUtils.configureButton(new Back(controller), backButton);
747 ActionUtils.configureButton(new ResetFilters(controller), resetFiltersButton);
748 }
749 }
750
755 private class PickerListener implements InvalidationListener {
756
757 private final BiFunction< Interval, Long, Interval> intervalMapper;
758 private final Supplier<LocalDateTimeTextField> pickerSupplier;
759
760 PickerListener(Supplier<LocalDateTimeTextField> pickerSupplier, BiFunction<Interval, Long, Interval> intervalMapper) {
761 this.pickerSupplier = pickerSupplier;
762 this.intervalMapper = intervalMapper;
763 }
764
765 @NbBundle.Messages({"ViewFrame.pickerListener.errorMessage=Error responding to date/time picker change."})
766 @Override
767 public void invalidated(Observable observable) {
768 LocalDateTime pickerTime = pickerSupplier.get().getLocalDateTime();
769 if (pickerTime != null) {
770 try {
771 controller.pushTimeRange(intervalMapper.apply(filteredEvents.getTimeRange(), localDateTimeToEpochMilli(pickerTime)));
772 } catch (TskCoreException ex) {
773 Notifications.create().owner(getScene().getWindow())
774 .text(Bundle.ViewFrame_pickerListener_errorMessage())
775 .showError();
776 logger.log(Level.WARNING, "Error responding to date/time picker change.", ex); //NON-NLS
777 } catch (IllegalArgumentException ex ) {
778 logger.log(Level.INFO, "Timeline: User supplied invalid time range."); //NON-NLS
779 }
780
781 Platform.runLater(ViewFrame.this::refreshTimeUI);
782 }
783 }
784 }
785
789 private class LocalDateDisabler implements Callback<LocalDateTimePicker.LocalDateTimeRange, Void> {
790
791 @NbBundle.Messages({
792 "ViewFrame.localDateDisabler.errorMessage=Error getting spanning interval."})
793 @Override
794 public Void call(LocalDateTimePicker.LocalDateTimeRange viewedRange) {
795
796 startPicker.disabledLocalDateTimes().clear();
797 endPicker.disabledLocalDateTimes().clear();
798 try {
799 //all events in the case are contained in this interval
800 Interval spanningInterval = filteredEvents.getSpanningInterval();
801 long spanStartMillis = spanningInterval.getStartMillis();
802 long spaneEndMillis = spanningInterval.getEndMillis();
803
804 LocalDate rangeStartLocalDate = viewedRange.getStartLocalDateTime().toLocalDate();
805 LocalDate rangeEndLocalDate = viewedRange.getEndLocalDateTime().toLocalDate().plusDays(1);
806 //iterate over days of the displayed range and disable ones not in spanning interval
807 for (LocalDate dt = rangeStartLocalDate; false == dt.isAfter(rangeEndLocalDate); dt = dt.plusDays(1)) {
808 long startOfDay = dt.atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli();
809 long endOfDay = dt.plusDays(1).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli();
810 //if no part of day is within spanning interval, add that date the list of disabled dates.
811 if (endOfDay < spanStartMillis || startOfDay > spaneEndMillis) {
812 startPicker.disabledLocalDateTimes().add(dt.atStartOfDay());
813 endPicker.disabledLocalDateTimes().add(dt.atStartOfDay());
814 }
815 }
816
817 } catch (TskCoreException ex) {
818 Notifications.create().owner(getScene().getWindow())
819 .text(Bundle.ViewFrame_localDateDisabler_errorMessage())
820 .showError();
821 logger.log(Level.SEVERE, "Error getting spanning interval.", ex);
822 }
823 return null;
824 }
825 }
826
832 private class LocalDateTimeValidator implements Callback<LocalDateTime, Boolean> {
833
837 private final LocalDateTimeTextField picker;
838
839 LocalDateTimeValidator(LocalDateTimeTextField picker) {
840 this.picker = picker;
841 }
842
843 @NbBundle.Messages({
844 "ViewFrame.dateTimeValidator.errorMessage=Error getting spanning interval."})
845 @Override
846 public Boolean call(LocalDateTime param) {
847 long epochMilli = localDateTimeToEpochMilli(param);
848 try {
849 if (filteredEvents.getSpanningInterval().contains(epochMilli)) {
850 return true;
851 } else {
852 if (picker.isPickerShowing() == false) {
853 //if the user typed an in valid date, reset the text box to the selected date.
854 picker.setDisplayedLocalDateTime(picker.getLocalDateTime());
855 }
856 return false;
857 }
858 } catch (TskCoreException ex) {
859 Notifications.create().owner(getScene().getWindow())
860 .text(Bundle.ViewFrame_dateTimeValidator_errorMessage())
861 .showError();
862 logger.log(Level.SEVERE, "Error getting spanning interval.", ex);
863 return false;
864 }
865 }
866 }
867
871 private class Refresh extends Action {
872
873 @NbBundle.Messages({
874 "ViewFrame.refresh.text=Refresh View",
875 "ViewFrame.refresh.longText=Refresh the view to include information that is in the DB but not displayed, such as newly updated tags."})
876 Refresh() {
877 super(Bundle.ViewFrame_refresh_text());
878 setLongText(Bundle.ViewFrame_refresh_longText());
879 setGraphic(new ImageView(REFRESH));
880 setEventHandler(actionEvent -> filteredEvents.postRefreshRequest());
881 disabledProperty().bind(hostedView.needsRefreshProperty().not());
882 }
883 }
884}
synchronized static Logger getLogger(String name)
Definition Logger.java:124
synchronized void registerForEvents(Object subscriber)
final ReadOnlyObjectWrapper< Interval > timeRangeProperty
final ReadOnlyObjectWrapper< EventsModelParams > modelParamsProperty
static void construct(Node node, String fxmlFileName)
synchronized ReadOnlyObjectProperty< ViewMode > viewModeProperty()
synchronized void setViewMode(ViewMode viewMode)
Void call(LocalDateTimePicker.LocalDateTimeRange viewedRange)
final BiFunction< Interval, Long, Interval > intervalMapper
final Supplier< LocalDateTimeTextField > pickerSupplier
void handleTimeLineTagUpdate(TagsUpdatedEvent event)
final ObservableList< Node > timeNavigationNodes
final InvalidationListener zoomListener
ViewFrame(@Nonnull TimeLineController controller, @Nonnull EventsTree eventsTree)
void setViewSettingsControls(List< Node > newSettingsNodes)
void setTimeNavigationControls(List< Node > timeNavigationNodes)
final InvalidationListener endListener
ToggleGroupValue< ViewMode > viewModeToggleGroup
static long localDateTimeToEpochMilli(LocalDateTime localDateTime)
void handleCacheInvalidated(EventsModel.CacheInvalidatedEvent event)
static LocalDateTime epochMillisToLocalDateTime(long millis)
final InvalidationListener startListener
final InvalidationListener rangeSliderListener
final ObservableList< Node > settingsNodes
void handleRefreshRequested(RefreshRequestedEvent event)
ImmutableList< Node > defaultTimeNavigationNodes
void setHighLightedEvents(ObservableList< DetailViewEvent > highlightedEvents)
static RangeDivision getRangeDivision(Interval timeRange, DateTimeZone timeZone)

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.