Autopsy  4.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
VisualizationPanel.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2013-15 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.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;
90 
99 final public class VisualizationPanel extends BorderPane {
100 
101  private static final Logger LOGGER = Logger.getLogger(VisualizationPanel.class.getName());
102 
103  private static final Image INFORMATION = new Image("org/sleuthkit/autopsy/timeline/images/information.png", 16, 16, true, true); // NON-NLS
104  private static final Image REFRESH = new Image("org/sleuthkit/autopsy/timeline/images/arrow-circle-double-135.png"); // NON-NLS
105  private static final Background background = new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY));
106 
107  @GuardedBy("this")
108  private LoggedTask<Void> histogramTask;
109 
110  private final EventsTree eventsTree;
113 
117  @FXML
118  private HBox histogramBox;
122  @FXML
123  private StackPane rangeHistogramStack;
124 
125  private final RangeSlider rangeSlider = new RangeSlider(0, 1.0, .25, .75);
126 
128  @FXML
129  private MenuButton zoomMenuButton;
130 
131  @FXML
132  private Button zoomOutButton;
133  @FXML
134  private Button zoomInButton;
135  @FXML
136  private LocalDateTimeTextField startPicker;
137  @FXML
138  private LocalDateTimeTextField endPicker;
139  @FXML
140  private Label startLabel;
141  @FXML
142  private Label endLabel;
143 
145  @FXML
146  private Pane partPane;
147  @FXML
148  private Pane contextPane;
149  @FXML
150  private Region spacer;
151 
153  @FXML
154  private ToolBar toolBar;
155  @FXML
156  private ToggleButton countsToggle;
157  @FXML
158  private ToggleButton detailsToggle;
159  @FXML
160  private Button snapShotButton;
161  @FXML
162  private Label visualizationModeLabel;
163 
167  private final NotificationPane notificationPane = new NotificationPane();
168 
171 
176  private final InvalidationListener rangeSliderListener = new InvalidationListener() {
177  @Override
178  public void invalidated(Observable observable) {
179  if (rangeSlider.isHighValueChanging() == false
180  && rangeSlider.isLowValueChanging() == false) {
181  Long minTime = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.getSpanningInterval()).getLowerBound();
182  if (false == controller.pushTimeRange(new Interval(
183  (long) (rangeSlider.getLowValue() + minTime),
184  (long) (rangeSlider.getHighValue() + minTime + 1000)))) {
185  refreshTimeUI();
186  }
187  }
188  }
189  };
190 
194  private final InvalidationListener zoomListener = any -> notificationPane.hide();
195 
199  private final InvalidationListener endListener = new PickerListener(() -> endPicker, Interval::withEndMillis);
200 
204  private final InvalidationListener startListener = new PickerListener(() -> startPicker, Interval::withStartMillis);
205 
214  private static long localDateTimeToEpochMilli(LocalDateTime localDateTime) {
215  return localDateTime.atZone(TimeLineController.getTimeZoneID()).toInstant().toEpochMilli();
216  }
217 
226  private static LocalDateTime epochMillisToLocalDateTime(long millis) {
227  return LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), TimeLineController.getTimeZoneID());
228  }
229 
231  this.controller = controller;
232  this.filteredEvents = controller.getEventsModel();
233  this.eventsTree = eventsTree;
234  FXMLConstructor.construct(this, "VisualizationPanel.fxml"); // NON-NLS
235  }
236 
237  @FXML // This method is called by the FXMLLoader when initialization is complete
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"})
245  void initialize() {
246  assert endPicker != null : "fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
247  assert histogramBox != null : "fx:id=\"histogramBox\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
248  assert startPicker != null : "fx:id=\"startPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
249  assert rangeHistogramStack != null : "fx:id=\"rangeHistogramStack\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
250  assert countsToggle != null : "fx:id=\"countsToggle\" was not injected: check your FXML file 'VisToggle.fxml'."; // NON-NLS
251  assert detailsToggle != null : "fx:id=\"eventsToggle\" was not injected: check your FXML file 'VisToggle.fxml'."; // NON-NLS
252 
253  //configure notification pane
254  notificationPane.getStyleClass().add(NotificationPane.STYLE_CLASS_DARK);
255  notificationPane.getActions().setAll(new Action(Bundle.VisualizationPanel_refresh()) {
256  {
257  setGraphic(new ImageView(REFRESH));
258  setEventHandler((ActionEvent t) -> {
260  notificationPane.hide();
261  });
262  }
263  });
264  setCenter(notificationPane);
265 
266  //configure visualization mode toggle
267  visualizationModeLabel.setText(Bundle.VisualizationPanel_visualizationModeLabel_text());
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) {
272  countsToggle.getToggleGroup().selectToggle(oldValue != null ? oldValue : countsToggle);
273  } else if (newValue == countsToggle && oldValue != null) {
274  controller.setViewMode(VisualizationMode.COUNTS);
275  } else if (newValue == detailsToggle && oldValue != null) {
276  controller.setViewMode(VisualizationMode.DETAIL);
277  }
278  };
279 
280  if (countsToggle.getToggleGroup() != null) {
281  countsToggle.getToggleGroup().selectedToggleProperty().addListener(toggleListener);
282  } else {
283  countsToggle.toggleGroupProperty().addListener((Observable observable) -> {
284  countsToggle.getToggleGroup().selectedToggleProperty().addListener(toggleListener);
285  });
286  }
287  controller.viewModeProperty().addListener(observable -> setViewMode(controller.viewModeProperty().get()));
289 
290  //configure snapshor button / action
291  ActionUtils.configureButton(new SaveSnapshotAsReport(controller, VisualizationPanel.this), snapShotButton);
292 
294  startLabel.setText(Bundle.VisualizationPanel_startLabel_text());
295  endLabel.setText(Bundle.VisualizationPanel_endLabel_text());
296 
297  //suppress stacktraces on malformed input
298  //TODO: should we do anything else? show a warning?
299  startPicker.setParseErrorCallback(throwable -> null);
300  endPicker.setParseErrorCallback(throwable -> null);
301 
302  //disable dates outside scope of case
303  LocalDateDisabler localDateDisabler = new LocalDateDisabler();
304  startPicker.setLocalDateTimeRangeCallback(localDateDisabler);
305  endPicker.setLocalDateTimeRangeCallback(localDateDisabler);
306 
307  //prevent selection of (date/)times outside the scope of this case
308  startPicker.setValueValidationCallback(new LocalDateTimeValidator(startPicker));
309  endPicker.setValueValidationCallback(new LocalDateTimeValidator(endPicker));
310 
311  //setup rangeslider
312  rangeSlider.setOpacity(.7);
313  rangeSlider.setMin(0);
314  rangeSlider.setBlockIncrement(1);
315  rangeHistogramStack.getChildren().add(rangeSlider);
316 
317  /*
318  * this padding attempts to compensates for the fact that the
319  * rangeslider track doesn't extend to edge of node,and so the
320  * histrogram doesn't quite line up with the rangeslider
321  */
322  histogramBox.setStyle(" -fx-padding: 0,0.5em,0,.5em; "); // NON-NLS
323 
324  //configure zoom buttons
325  zoomMenuButton.getItems().clear();
326  for (ZoomRanges zoomRange : ZoomRanges.values()) {
327  zoomMenuButton.getItems().add(ActionUtils.createMenuItem(
328  new Action(zoomRange.getDisplayName(), event -> {
329  if (zoomRange != ZoomRanges.ALL) {
330  controller.pushPeriod(zoomRange.getPeriod());
331  } else {
333  }
334  })));
335  }
336  zoomMenuButton.setText(Bundle.VisualizationPanel_zoomMenuButton_text());
337  ActionUtils.configureButton(new ZoomOut(controller), zoomOutButton);
338  ActionUtils.configureButton(new ZoomIn(controller), zoomInButton);
339 
340  //register for EventBus events (tags)
342 
343  //listen for changes in the time range / zoom params
344  TimeLineController.getTimeZone().addListener(timeZoneProp -> refreshTimeUI());
345  filteredEvents.timeRangeProperty().addListener(timeRangeProp -> refreshTimeUI());
346  filteredEvents.zoomParametersProperty().addListener(zoomListener);
347  refreshTimeUI(); //populate the viz
348 
349  //this should use an event(EventBus) , not this weird observable pattern
350  controller.eventsDBStaleProperty().addListener(staleProperty -> {
351  if (controller.isEventsDBStale()) {
352  Platform.runLater(VisualizationPanel.this::refreshHistorgram);
353  }
354  });
356 
357  }
358 
359  private void setViewMode(VisualizationMode visualizationMode) {
360  switch (visualizationMode) {
361  case COUNTS:
363  countsToggle.setSelected(true);
364  break;
365  case DETAIL:
367  detailsToggle.setSelected(true);
368  break;
369  }
370  }
371 
372  private synchronized void setVisualization(final AbstractVisualizationPane<?, ?, ?, ?> newViz) {
373  Platform.runLater(() -> {
374  synchronized (VisualizationPanel.this) {
375  if (visualization != null) {
376  toolBar.getItems().removeAll(visualization.getSettingsNodes());
378  }
379 
380  visualization = newViz;
382  toolBar.getItems().addAll(newViz.getSettingsNodes());
383 
384  notificationPane.setContent(visualization);
385  if (visualization instanceof DetailViewPane) {
386  eventsTree.setDetailViewPane((DetailViewPane) visualization);
387  }
388  visualization.hasEvents.addListener((observable, oldValue, newValue) -> {
389  if (newValue == false) {
390 
391  notificationPane.setContent(
392  new StackPane(visualization,
393  new Region() {
394  {
395  setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)));
396  setOpacity(.3);
397  }
398  },
399  new NoEventsDialog(() -> notificationPane.setContent(visualization))));
400  } else {
401  notificationPane.setContent(visualization);
402  }
403  });
404  }
405  });
406  }
407 
408  @Subscribe
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));
415  });
416  }
417  }
418 
419  synchronized private void refreshHistorgram() {
420 
421  if (histogramTask != null) {
422  histogramTask.cancel(true);
423  }
424 
426  NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.title"), true) { // NON-NLS
427  private final Lighting lighting = new Lighting();
428 
429  @Override
430  protected Void call() throws Exception {
431 
432  updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.preparing")); // NON-NLS
433 
434  long max = 0;
436  final long lowerBound = rangeInfo.getLowerBound();
437  final long upperBound = rangeInfo.getUpperBound();
438  Interval timeRange = new Interval(new DateTime(lowerBound, TimeLineController.getJodaTimeZone()), new DateTime(upperBound, TimeLineController.getJodaTimeZone()));
439 
440  //extend range to block bounderies (ie day, month, year)
441  int p = 0; // progress counter
442 
443  //clear old data, and reset ranges and series
444  Platform.runLater(() -> {
445  updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.resetUI")); // NON-NLS
446 
447  });
448 
449  ArrayList<Long> bins = new ArrayList<>();
450 
451  DateTime start = timeRange.getStart();
452  while (timeRange.contains(start)) {
453  if (isCancelled()) {
454  return null;
455  }
456  DateTime end = start.plus(rangeInfo.getPeriodSize().getPeriod());
457  final Interval interval = new Interval(start, end);
458  //increment for next iteration
459 
460  start = end;
461 
462  updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.queryDb")); // NON-NLS
463  //query for current range
464  long count = filteredEvents.getEventCounts(interval).values().stream().mapToLong(Long::valueOf).sum();
465  bins.add(count);
466 
467  max = Math.max(count, max);
468 
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")); // NON-NLS
473 
474  histogramBox.getChildren().clear();
475 
476  for (Long bin : fbins) {
477  if (isCancelled()) {
478  break;
479  }
480  Region bar = new Region();
481  //scale them to fit in histogram height
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()));
488  });
489  bar.setEffect(lighting);
490  //they each get equal width to fill the histogram horizontally
491  HBox.setHgrow(bar, Priority.ALWAYS);
492  histogramBox.getChildren().add(bar);
493  }
494  });
495  }
496  return null;
497  }
498 
499  };
500  new Thread(histogramTask).start();
502  }
503 
504  private void refreshTimeUI() {
506  }
507 
508  private void refreshTimeUI(Interval interval) {
509 
511 
512  final long minTime = rangeDivisionInfo.getLowerBound();
513  final long maxTime = rangeDivisionInfo.getUpperBound();
514 
515  long startMillis = interval.getStartMillis();
516  long endMillis = interval.getEndMillis();
517 
518  if (minTime > 0 && maxTime > minTime) {
519 
520  Platform.runLater(() -> {
521  startPicker.localDateTimeProperty().removeListener(startListener);
522  endPicker.localDateTimeProperty().removeListener(endListener);
523  rangeSlider.highValueChangingProperty().removeListener(rangeSliderListener);
524  rangeSlider.lowValueChangingProperty().removeListener(rangeSliderListener);
525 
526  rangeSlider.setMax((maxTime - minTime));
527 
528  rangeSlider.setLowValue(startMillis - minTime);
529  rangeSlider.setHighValue(endMillis - minTime);
530  startPicker.setLocalDateTime(epochMillisToLocalDateTime(startMillis));
531  endPicker.setLocalDateTime(epochMillisToLocalDateTime(endMillis));
532 
533  rangeSlider.highValueChangingProperty().addListener(rangeSliderListener);
534  rangeSlider.lowValueChangingProperty().addListener(rangeSliderListener);
535  startPicker.localDateTimeProperty().addListener(startListener);
536  endPicker.localDateTimeProperty().addListener(endListener);
537  });
538  }
539  }
540 
541  @NbBundle.Messages("NoEventsDialog.titledPane.text=No Visible Events")
542  private class NoEventsDialog extends StackPane {
543 
544  @FXML
545  private TitledPane titledPane;
546  @FXML
547  private Button backButton;
548  @FXML
549  private Button resetFiltersButton;
550  @FXML
551  private Button dismissButton;
552  @FXML
553  private Button zoomButton;
554  @FXML
555  private Label noEventsDialogLabel;
556 
557  private final Runnable closeCallback;
558 
559  private NoEventsDialog(Runnable closeCallback) {
560  this.closeCallback = closeCallback;
561  FXMLConstructor.construct(this, "NoEventsDialog.fxml"); // NON-NLS
562  }
563 
564  @FXML
565  void initialize() {
566  assert resetFiltersButton != null : "fx:id=\"resetFiltersButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
567  assert dismissButton != null : "fx:id=\"dismissButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
568  assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
569 
570  titledPane.setText(Bundle.NoEventsDialog_titledPane_text());
571  noEventsDialogLabel.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.noEventsDialogLabel.text")); // NON-NLS
572 
573  dismissButton.setOnAction(actionEvent -> closeCallback.run());
574 
575  ActionUtils.configureButton(new ZoomToEvents(controller), zoomButton);
576  ActionUtils.configureButton(new Back(controller), backButton);
577  ActionUtils.configureButton(new ResetFilters(controller), resetFiltersButton);
578  }
579  }
580 
585  private class PickerListener implements InvalidationListener {
586 
587  private final BiFunction< Interval, Long, Interval> intervalMapper;
588  private final Supplier<LocalDateTimeTextField> pickerSupplier;
589 
590  PickerListener(Supplier<LocalDateTimeTextField> pickerSupplier, BiFunction<Interval, Long, Interval> intervalMapper) {
591  this.pickerSupplier = pickerSupplier;
592  this.intervalMapper = intervalMapper;
593  }
594 
595  @Override
596  public void invalidated(Observable observable) {
597  LocalDateTime pickerTime = pickerSupplier.get().getLocalDateTime();
598  if (pickerTime != null) {
599  controller.pushTimeRange(intervalMapper.apply(filteredEvents.timeRangeProperty().get(), localDateTimeToEpochMilli(pickerTime)));
600  Platform.runLater(VisualizationPanel.this::refreshTimeUI);
601  }
602  }
603  }
604 
608  private class LocalDateDisabler implements Callback<LocalDateTimePicker.LocalDateTimeRange, Void> {
609 
610  @Override
611  public Void call(LocalDateTimePicker.LocalDateTimeRange viewedRange) {
612  startPicker.disabledLocalDateTimes().clear();
613  endPicker.disabledLocalDateTimes().clear();
614 
615  //all events in the case are contained in this interval
616  Interval spanningInterval = filteredEvents.getSpanningInterval();
617  long spanStartMillis = spanningInterval.getStartMillis();
618  long spaneEndMillis = spanningInterval.getEndMillis();
619 
620  LocalDate rangeStartLocalDate = viewedRange.getStartLocalDateTime().toLocalDate();
621  LocalDate rangeEndLocalDate = viewedRange.getEndLocalDateTime().toLocalDate().plusDays(1);
622  //iterate over days of the displayed range and disable ones not in spanning interval
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();
626  //if no part of day is within spanning interval, add that date the list of disabled dates.
627  if (endOfDay < spanStartMillis || startOfDay > spaneEndMillis) {
628  startPicker.disabledLocalDateTimes().add(dt.atStartOfDay());
629  endPicker.disabledLocalDateTimes().add(dt.atStartOfDay());
630  }
631  }
632  return null;
633  }
634  }
635 
641  private class LocalDateTimeValidator implements Callback<LocalDateTime, Boolean> {
642 
646  private final LocalDateTimeTextField picker;
647 
648  LocalDateTimeValidator(LocalDateTimeTextField picker) {
649  this.picker = picker;
650  }
651 
652  @Override
653  public Boolean call(LocalDateTime param) {
654  long epochMilli = localDateTimeToEpochMilli(param);
655  if (filteredEvents.getSpanningInterval().contains(epochMilli)) {
656  return true;
657  } else {
658  if (picker.isPickerShowing() == false) {
659  //if the user typed an in valid date, reset the text box to the selected date.
660  picker.setDisplayedLocalDateTime(picker.getLocalDateTime());
661  }
662  return false;
663  }
664  }
665  }
666 }
synchronized ReadOnlyObjectProperty< VisualizationMode > viewModeProperty()
synchronized void setVisualization(final AbstractVisualizationPane<?,?,?,?> newViz)
Map< EventType, Long > getEventCounts(Interval timeRange)
synchronized ReadOnlyObjectProperty< ZoomParams > zoomParametersProperty()
static long localDateTimeToEpochMilli(LocalDateTime localDateTime)
Void call(LocalDateTimePicker.LocalDateTimeRange viewedRange)
synchronized boolean pushTimeRange(Interval timeRange)
static RangeDivisionInfo getRangeDivisionInfo(Interval timeRange)
VisualizationPanel(@Nonnull TimeLineController controller,@Nonnull EventsTree eventsTree)
synchronized void monitorTask(final Task<?> task)
synchronized void pushPeriod(ReadablePeriod period)
synchronized static Logger getLogger(String name)
Definition: Logger.java:166
void setDetailViewPane(DetailViewPane detailViewPane)
Definition: EventsTree.java:83
static LocalDateTime epochMillisToLocalDateTime(long millis)
void setViewMode(VisualizationMode visualizationMode)
static void construct(Node node, String fxmlFileName)
synchronized ReadOnlyObjectProperty< Interval > timeRangeProperty()
synchronized void setViewMode(VisualizationMode visualizationMode)

Copyright © 2012-2015 Basis Technology. Generated on: Wed Apr 6 2016
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.