Autopsy  4.9.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
ListTimeline.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2016 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.listvew;
20 
21 import com.google.common.collect.Iterables;
22 import com.google.common.math.DoubleMath;
23 import java.math.RoundingMode;
24 import java.time.Instant;
25 import java.time.ZoneId;
26 import java.time.ZonedDateTime;
27 import java.time.temporal.ChronoField;
28 import java.time.temporal.TemporalUnit;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.Comparator;
34 import java.util.List;
35 import java.util.Objects;
36 import java.util.Set;
37 import java.util.SortedSet;
38 import java.util.TreeSet;
39 import java.util.concurrent.ConcurrentSkipListSet;
40 import java.util.function.Consumer;
41 import java.util.function.Function;
42 import java.util.logging.Level;
43 import java.util.stream.Collectors;
44 import javafx.application.Platform;
45 import javafx.beans.binding.Bindings;
46 import javafx.beans.binding.IntegerBinding;
47 import javafx.beans.binding.StringBinding;
48 import javafx.beans.property.SimpleObjectProperty;
49 import javafx.beans.value.ObservableValue;
50 import javafx.collections.ListChangeListener;
51 import javafx.event.ActionEvent;
52 import javafx.fxml.FXML;
53 import javafx.geometry.Pos;
54 import javafx.scene.Node;
55 import javafx.scene.control.Button;
56 import javafx.scene.control.ComboBox;
57 import javafx.scene.control.ContextMenu;
58 import javafx.scene.control.Label;
59 import javafx.scene.control.MenuItem;
60 import javafx.scene.control.OverrunStyle;
61 import javafx.scene.control.SelectionMode;
62 import javafx.scene.control.SeparatorMenuItem;
63 import javafx.scene.control.TableCell;
64 import javafx.scene.control.TableColumn;
65 import javafx.scene.control.TableRow;
66 import javafx.scene.control.TableView;
67 import javafx.scene.control.Tooltip;
68 import javafx.scene.image.Image;
69 import javafx.scene.image.ImageView;
70 import javafx.scene.layout.BorderPane;
71 import javafx.scene.layout.HBox;
72 import javafx.scene.layout.VBox;
73 import javafx.util.Callback;
74 import javax.swing.Action;
75 import javax.swing.JMenuItem;
76 import org.controlsfx.control.Notifications;
77 import org.controlsfx.control.action.ActionUtils;
78 import org.openide.awt.Actions;
79 import org.openide.util.NbBundle;
80 import org.openide.util.actions.Presenter;
95 import org.sleuthkit.datamodel.AbstractFile;
96 import org.sleuthkit.datamodel.BlackboardArtifact;
97 import org.sleuthkit.datamodel.SleuthkitCase;
98 import org.sleuthkit.datamodel.TskCoreException;
99 
103 class ListTimeline extends BorderPane {
104 
105  private static final Logger LOGGER = Logger.getLogger(ListTimeline.class.getName());
106 
107  private static final Image HASH_HIT = new Image("/org/sleuthkit/autopsy/images/hashset_hits.png"); //NON-NLS
108  private static final Image TAG = new Image("/org/sleuthkit/autopsy/images/green-tag-icon-16.png"); //NON-NLS
109  private static final Image FIRST = new Image("/org/sleuthkit/autopsy/timeline/images/resultset_first.png"); //NON-NLS
110  private static final Image PREVIOUS = new Image("/org/sleuthkit/autopsy/timeline/images/resultset_previous.png"); //NON-NLS
111  private static final Image NEXT = new Image("/org/sleuthkit/autopsy/timeline/images/resultset_next.png"); //NON-NLS
112  private static final Image LAST = new Image("/org/sleuthkit/autopsy/timeline/images/resultset_last.png"); //NON-NLS
113 
117  private static final Callback<TableColumn.CellDataFeatures<CombinedEvent, CombinedEvent>, ObservableValue<CombinedEvent>> CELL_VALUE_FACTORY = param -> new SimpleObjectProperty<>(param.getValue());
118 
119  private static final List<ChronoField> SCROLL_BY_UNITS = Arrays.asList(
120  ChronoField.YEAR,
121  ChronoField.MONTH_OF_YEAR,
122  ChronoField.DAY_OF_MONTH,
123  ChronoField.HOUR_OF_DAY,
124  ChronoField.MINUTE_OF_HOUR,
125  ChronoField.SECOND_OF_MINUTE);
126 
127  private static final int DEFAULT_ROW_HEIGHT = 24;
128 
129  @FXML
130  private HBox navControls;
131 
132  @FXML
133  private ComboBox<ChronoField> scrollInrementComboBox;
134 
135  @FXML
136  private Button firstButton;
137 
138  @FXML
139  private Button previousButton;
140 
141  @FXML
142  private Button nextButton;
143 
144  @FXML
145  private Button lastButton;
146 
147  @FXML
148  private Label eventCountLabel;
149  @FXML
150  private TableView<CombinedEvent> table;
151  @FXML
152  private TableColumn<CombinedEvent, CombinedEvent> idColumn;
153  @FXML
154  private TableColumn<CombinedEvent, CombinedEvent> dateTimeColumn;
155  @FXML
156  private TableColumn<CombinedEvent, CombinedEvent> descriptionColumn;
157  @FXML
158  private TableColumn<CombinedEvent, CombinedEvent> typeColumn;
159  @FXML
160  private TableColumn<CombinedEvent, CombinedEvent> knownColumn;
161  @FXML
162  private TableColumn<CombinedEvent, CombinedEvent> taggedColumn;
163  @FXML
164  private TableColumn<CombinedEvent, CombinedEvent> hashHitColumn;
165 
170  private final SortedSet<CombinedEvent> visibleEvents;
171 
172  private final TimeLineController controller;
173  private final SleuthkitCase sleuthkitCase;
174  private final TagsManager tagsManager;
175 
181  private final ListChangeListener<CombinedEvent> selectedEventListener = new ListChangeListener<CombinedEvent>() {
182  @Override
183  public void onChanged(ListChangeListener.Change<? extends CombinedEvent> c) {
184  controller.selectEventIDs(table.getSelectionModel().getSelectedItems().stream()
185  .filter(Objects::nonNull)
187  .collect(Collectors.toSet()));
188  }
189  };
190 
196  ListTimeline(TimeLineController controller) {
197  this.controller = controller;
198  sleuthkitCase = controller.getAutopsyCase().getSleuthkitCase();
199  tagsManager = controller.getAutopsyCase().getServices().getTagsManager();
200  FXMLConstructor.construct(this, ListTimeline.class, "ListTimeline.fxml"); //NON-NLS
201  this.visibleEvents = new ConcurrentSkipListSet<>(Comparator.comparing(table.getItems()::indexOf));
202  }
203 
204  @FXML
205  @NbBundle.Messages({
206  "# {0} - the number of events",
207  "ListTimeline.eventCountLabel.text={0} events"})
208  void initialize() {
209  assert eventCountLabel != null : "fx:id=\"eventCountLabel\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
210  assert table != null : "fx:id=\"table\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
211  assert idColumn != null : "fx:id=\"idColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
212  assert dateTimeColumn != null : "fx:id=\"dateTimeColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
213  assert descriptionColumn != null : "fx:id=\"descriptionColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
214  assert typeColumn != null : "fx:id=\"typeColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
215  assert knownColumn != null : "fx:id=\"knownColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
216 
217  //configure scroll controls
218  scrollInrementComboBox.setButtonCell(new ChronoFieldListCell());
219  scrollInrementComboBox.setCellFactory(comboBox -> new ChronoFieldListCell());
220  scrollInrementComboBox.getItems().setAll(SCROLL_BY_UNITS);
221  scrollInrementComboBox.getSelectionModel().select(ChronoField.YEAR);
222  ActionUtils.configureButton(new ScrollToFirst(), firstButton);
223  ActionUtils.configureButton(new ScrollToPrevious(), previousButton);
224  ActionUtils.configureButton(new ScrollToNext(), nextButton);
225  ActionUtils.configureButton(new ScrollToLast(), lastButton);
226 
227  //override default table row with one that provides context menus
228  table.setRowFactory(tableView -> new EventRow());
229 
230  //remove idColumn (can be restored for debugging).
231  table.getColumns().remove(idColumn);
232 
234  dateTimeColumn.setCellValueFactory(CELL_VALUE_FACTORY);
235  dateTimeColumn.setCellFactory(col -> new TextEventTableCell(singleEvent ->
236  TimeLineController.getZonedFormatter().print(singleEvent.getStartMillis())));
237 
238  descriptionColumn.setCellValueFactory(CELL_VALUE_FACTORY);
239  descriptionColumn.setCellFactory(col -> new TextEventTableCell(singleEvent ->
240  singleEvent.getDescription(DescriptionLoD.FULL)));
241 
242  typeColumn.setCellValueFactory(CELL_VALUE_FACTORY);
243  typeColumn.setCellFactory(col -> new EventTypeCell());
244 
245  knownColumn.setCellValueFactory(CELL_VALUE_FACTORY);
246  knownColumn.setCellFactory(col -> new TextEventTableCell(singleEvent ->
247  singleEvent.getKnown().getName()));
248 
249  taggedColumn.setCellValueFactory(CELL_VALUE_FACTORY);
250  taggedColumn.setCellFactory(col -> new TaggedCell());
251 
252  hashHitColumn.setCellValueFactory(CELL_VALUE_FACTORY);
253  hashHitColumn.setCellFactory(col -> new HashHitCell());
254 
255  //bind event count label to number of items in the table
256  eventCountLabel.textProperty().bind(new StringBinding() {
257  {
258  bind(table.getItems());
259  }
260 
261  @Override
262  protected String computeValue() {
263  return Bundle.ListTimeline_eventCountLabel_text(table.getItems().size());
264  }
265  });
266 
267  // use listener to keep controller selection in sync with table selection.
268  table.getSelectionModel().getSelectedItems().addListener(selectedEventListener);
269  table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
270  selectEvents(controller.getSelectedEventIDs()); //grab initial selection
271  }
272 
279  void setCombinedEvents(Collection<CombinedEvent> events) {
280  table.getItems().setAll(events);
281  }
282 
288  void selectEvents(Collection<Long> selectedEventIDs) {
289  if (selectedEventIDs.isEmpty()) {
290  //this is the final selection, so we don't need to mess with the listener
291  table.getSelectionModel().clearSelection();
292  } else {
293  /*
294  * Changes in the table selection are propogated to the controller
295  * by a listener. There is no API on TableView's selection model to
296  * clear the selection and select multiple rows as one "action".
297  * Therefore we clear the selection and then make the new selection,
298  * but we don't want this intermediate state of no selection to be
299  * pushed to the controller as it interferes with maintaining the
300  * right selection. To avoid notifying the controller, we remove the
301  * listener, clear the selection, then re-attach it.
302  */
303  table.getSelectionModel().getSelectedItems().removeListener(selectedEventListener);
304 
305  table.getSelectionModel().clearSelection();
306 
307  table.getSelectionModel().getSelectedItems().addListener(selectedEventListener);
308 
309  //find the indices of the CombinedEvents that will be selected
310  int[] selectedIndices = table.getItems().stream()
311  .filter(combinedEvent -> Collections.disjoint(combinedEvent.getEventIDs(), selectedEventIDs) == false)
312  .mapToInt(table.getItems()::indexOf)
313  .toArray();
314 
315  //select indices and scroll to the first one
316  if (selectedIndices.length > 0) {
317  Integer firstSelectedIndex = selectedIndices[0];
318  table.getSelectionModel().selectIndices(firstSelectedIndex, selectedIndices);
319  scrollTo(firstSelectedIndex);
320  table.requestFocus(); //grab focus so selection is clearer to user
321  }
322  }
323  }
324 
332  List<Node> getTimeNavigationControls() {
333  return Collections.singletonList(navControls);
334  }
335 
343  private void scrollToAndFocus(Integer index) {
344  table.requestFocus();
345  scrollTo(index);
346  table.getFocusModel().focus(index);
347  }
348 
354  private void scrollTo(Integer index) {
355  if (visibleEvents.contains(table.getItems().get(index)) == false) {
356  table.scrollTo(DoubleMath.roundToInt(index - ((table.getHeight() / DEFAULT_ROW_HEIGHT)) / 2, RoundingMode.HALF_EVEN));
357  }
358  }
359 
363  private class EventTypeCell extends EventTableCell {
364 
365  @NbBundle.Messages({
366  "ListView.EventTypeCell.modifiedTooltip=File Modified ( M )",
367  "ListView.EventTypeCell.accessedTooltip=File Accessed ( A )",
368  "ListView.EventTypeCell.createdTooltip=File Created ( B, for Born )",
369  "ListView.EventTypeCell.changedTooltip=File Changed ( C )"
370  })
371  @Override
372  protected void updateItem(CombinedEvent item, boolean empty) {
373  super.updateItem(item, empty);
374 
375  if (empty || item == null) {
376  setText(null);
377  setGraphic(null);
378  setTooltip(null);
379  } else {
380  if (item.getEventTypes().stream().allMatch(eventType -> eventType instanceof FileSystemTypes)) {
381  String typeString = ""; //NON-NLS
382  VBox toolTipVbox = new VBox(5);
383 
384  for (FileSystemTypes type : Arrays.asList(FileSystemTypes.FILE_MODIFIED, FileSystemTypes.FILE_ACCESSED, FileSystemTypes.FILE_CHANGED, FileSystemTypes.FILE_CREATED)) {
385  if (item.getEventTypes().contains(type)) {
386  switch (type) {
387  case FILE_MODIFIED:
388  typeString += "M"; //NON-NLS
389  toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_modifiedTooltip(), new ImageView(type.getFXImage())));
390  break;
391  case FILE_ACCESSED:
392  typeString += "A"; //NON-NLS
393  toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_accessedTooltip(), new ImageView(type.getFXImage())));
394  break;
395  case FILE_CREATED:
396  typeString += "B"; //NON-NLS
397  toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_createdTooltip(), new ImageView(type.getFXImage())));
398  break;
399  case FILE_CHANGED:
400  typeString += "C"; //NON-NLS
401  toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_changedTooltip(), new ImageView(type.getFXImage())));
402  break;
403  default:
404  throw new UnsupportedOperationException("Unknown FileSystemType: " + type.name()); //NON-NLS
405  }
406  } else {
407  typeString += "_"; //NON-NLS
408  }
409  }
410  setText(typeString);
411  setGraphic(new ImageView(BaseTypes.FILE_SYSTEM.getFXImage()));
412  Tooltip tooltip = new Tooltip();
413  tooltip.setGraphic(toolTipVbox);
414  setTooltip(tooltip);
415 
416  } else {
417  EventType eventType = Iterables.getOnlyElement(item.getEventTypes());
418  setText(eventType.getDisplayName());
419  setGraphic(new ImageView(eventType.getFXImage()));
420  setTooltip(new Tooltip(eventType.getDisplayName()));
421  };
422  }
423  }
424  }
425 
429  private class TaggedCell extends EventTableCell {
430 
434  TaggedCell() {
435  setAlignment(Pos.CENTER);
436  }
437 
438  @NbBundle.Messages({
439  "ListTimeline.taggedTooltip.error=There was a problem getting the tag names for the selected event.",
440  "# {0} - tag names",
441  "ListTimeline.taggedTooltip.text=Tags:\n{0}"})
442  @Override
443  protected void updateItem(CombinedEvent item, boolean empty) {
444  super.updateItem(item, empty);
445 
446  if (empty || item == null || (getEvent().isTagged() == false)) {
447  setGraphic(null);
448  setTooltip(null);
449  } else {
450  /*
451  * if the cell is not empty and the event is tagged, show the
452  * tagged icon, and show a list of tag names in the tooltip
453  */
454  setGraphic(new ImageView(TAG));
455 
456  SortedSet<String> tagNames = new TreeSet<>();
457  try {
458  //get file tags
459  AbstractFile abstractFileById = sleuthkitCase.getAbstractFileById(getEvent().getFileID());
460  tagsManager.getContentTagsByContent(abstractFileById).stream()
461  .map(tag -> tag.getName().getDisplayName())
462  .forEach(tagNames::add);
463 
464  } catch (TskCoreException ex) {
465  LOGGER.log(Level.SEVERE, "Failed to lookup tags for obj id " + getEvent().getFileID(), ex); //NON-NLS
466  Platform.runLater(() -> {
467  Notifications.create()
468  .owner(getScene().getWindow())
469  .text(Bundle.ListTimeline_taggedTooltip_error())
470  .showError();
471  });
472  }
473  getEvent().getArtifactID().ifPresent(artifactID -> {
474  //get artifact tags, if there is an artifact associated with the event.
475  try {
476  BlackboardArtifact artifact = sleuthkitCase.getBlackboardArtifact(artifactID);
477  tagsManager.getBlackboardArtifactTagsByArtifact(artifact).stream()
478  .map(tag -> tag.getName().getDisplayName())
479  .forEach(tagNames::add);
480  } catch (TskCoreException ex) {
481  LOGGER.log(Level.SEVERE, "Failed to lookup tags for artifact id " + artifactID, ex); //NON-NLS
482  Platform.runLater(() -> {
483  Notifications.create()
484  .owner(getScene().getWindow())
485  .text(Bundle.ListTimeline_taggedTooltip_error())
486  .showError();
487  });
488  }
489  });
490  Tooltip tooltip = new Tooltip(Bundle.ListTimeline_taggedTooltip_text(String.join("\n", tagNames))); //NON-NLS
491  tooltip.setGraphic(new ImageView(TAG));
492  setTooltip(tooltip);
493  }
494  }
495  }
496 
501  private class HashHitCell extends EventTableCell {
502 
506  HashHitCell() {
507  setAlignment(Pos.CENTER);
508  }
509 
510  @NbBundle.Messages({
511  "ListTimeline.hashHitTooltip.error=There was a problem getting the hash set names for the selected event.",
512  "# {0} - hash set names",
513  "ListTimeline.hashHitTooltip.text=Hash Sets:\n{0}"})
514  @Override
515  protected void updateItem(CombinedEvent item, boolean empty) {
516  super.updateItem(item, empty);
517 
518  if (empty || item == null || (getEvent().isHashHit() == false)) {
519  setGraphic(null);
520  setTooltip(null);
521  } else {
522  /*
523  * If the cell is not empty and the event's file is a hash hit,
524  * show the hash hit icon, and show a list of hash set names in
525  * the tooltip
526  */
527  setGraphic(new ImageView(HASH_HIT));
528  try {
529  Set<String> hashSetNames = new TreeSet<>(sleuthkitCase.getAbstractFileById(getEvent().getFileID()).getHashSetNames());
530  Tooltip tooltip = new Tooltip(Bundle.ListTimeline_hashHitTooltip_text(String.join("\n", hashSetNames))); //NON-NLS
531  tooltip.setGraphic(new ImageView(HASH_HIT));
532  setTooltip(tooltip);
533  } catch (TskCoreException ex) {
534  LOGGER.log(Level.SEVERE, "Failed to lookup hash set names for obj id " + getEvent().getFileID(), ex); //NON-NLS
535  Platform.runLater(() -> {
536  Notifications.create()
537  .owner(getScene().getWindow())
538  .text(Bundle.ListTimeline_hashHitTooltip_error())
539  .showError();
540  });
541  }
542  }
543  }
544  }
545 
549  private class TextEventTableCell extends EventTableCell {
550 
551  private final Function<SingleEvent, String> textSupplier;
552 
559  TextEventTableCell(Function<SingleEvent, String> textSupplier) {
560  this.textSupplier = textSupplier;
561  setTextOverrun(OverrunStyle.CENTER_ELLIPSIS);
562  setEllipsisString(" ... "); //NON-NLS
563  }
564 
565  @Override
566  protected void updateItem(CombinedEvent item, boolean empty) {
567  super.updateItem(item, empty);
568  if (empty || item == null) {
569  setText(null);
570  setTooltip(null);
571  } else {
572  String text = textSupplier.apply(getEvent());
573  setText(text);
574  setTooltip(new Tooltip(text));
575  }
576  }
577  }
578 
583  private abstract class EventTableCell extends TableCell<CombinedEvent, CombinedEvent> {
584 
586 
592  SingleEvent getEvent() {
593  return event;
594  }
595 
596  @Override
597  protected void updateItem(CombinedEvent item, boolean empty) {
598  super.updateItem(item, empty);
599 
600  if (empty || item == null) {
601  event = null;
602  } else {
603  //stash the event in the cell for derived classed to use.
604  event = controller.getEventsModel().getEventById(item.getRepresentativeEventID());
605  }
606  }
607  }
608 
612  private class EventRow extends TableRow<CombinedEvent> {
613 
615 
621  SingleEvent getEvent() {
622  return event;
623  }
624 
625  @NbBundle.Messages({
626  "ListChart.errorMsg=There was a problem getting the content for the selected event."})
627  @Override
628  protected void updateItem(CombinedEvent item, boolean empty) {
629  CombinedEvent oldItem = getItem();
630  if (oldItem != null) {
631  visibleEvents.remove(oldItem);
632  }
633  super.updateItem(item, empty);
634 
635  if (empty || item == null) {
636  event = null;
637  } else {
638  visibleEvents.add(item);
639  event = controller.getEventsModel().getEventById(item.getRepresentativeEventID());
640 
641  setOnContextMenuRequested(contextMenuEvent -> {
642  //make a new context menu on each request in order to include uptodate tag names and hash sets
643  try {
645  List<MenuItem> menuItems = new ArrayList<>();
646 
647  //for each actions avaialable on node, make a menu item.
648  for (Action action : node.getActions(false)) {
649  if (action == null) {
650  // swing/netbeans uses null action to represent separator in menu
651  menuItems.add(new SeparatorMenuItem());
652  } else {
653  String actionName = Objects.toString(action.getValue(Action.NAME));
654  //for now, suppress properties and tools actions, by ignoring them
655  if (Arrays.asList("&Properties", "Tools").contains(actionName) == false) { //NON-NLS
656  if (action instanceof Presenter.Popup) {
657  /*
658  * If the action is really the root of a
659  * set of actions (eg, tagging). Make a
660  * menu that parallels the action's
661  * menu.
662  */
663  JMenuItem submenu = ((Presenter.Popup) action).getPopupPresenter();
664  menuItems.add(SwingFXMenuUtils.createFXMenu(submenu));
665  } else {
666  menuItems.add(SwingFXMenuUtils.createFXMenu(new Actions.MenuItem(action, false)));
667  }
668  }
669  }
670  };
671 
672  //show new context menu.
673  new ContextMenu(menuItems.toArray(new MenuItem[menuItems.size()]))
674  .show(this, contextMenuEvent.getScreenX(), contextMenuEvent.getScreenY());
675  } catch (NoCurrentCaseException ex) {
676  //Since the case is closed, the user probably doesn't care about this, just log it as a precaution.
677  LOGGER.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); //NON-NLS
678  } catch (TskCoreException ex) {
679  LOGGER.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); //NON-NLS
680  Platform.runLater(() -> {
681  Notifications.create()
682  .owner(getScene().getWindow())
683  .text(Bundle.ListChart_errorMsg())
684  .showError();
685  });
686  }
687  });
688 
689  }
690  }
691  }
692 
693  private class ScrollToFirst extends org.controlsfx.control.action.Action {
694 
695  ScrollToFirst() {
696  super("", new Consumer<ActionEvent>() { //do not make this a lambda function see issue 2147
697  @Override
698  public void accept(ActionEvent actionEvent) {
699  scrollToAndFocus(0);
700  }
701  });
702  setGraphic(new ImageView(FIRST));
703  disabledProperty().bind(table.getFocusModel().focusedIndexProperty().lessThan(1));
704  }
705  }
706 
707  private class ScrollToLast extends org.controlsfx.control.action.Action {
708 
709  ScrollToLast() {
710  super("", new Consumer<ActionEvent>() { //do not make this a lambda function see issue 2147
711  @Override
712  public void accept(ActionEvent actionEvent) {
713  scrollToAndFocus(table.getItems().size() - 1);
714  }
715  });
716  setGraphic(new ImageView(LAST));
717  IntegerBinding size = Bindings.size(table.getItems());
718  disabledProperty().bind(size.isEqualTo(0).or(
719  table.getFocusModel().focusedIndexProperty().greaterThanOrEqualTo(size.subtract(1))));
720  }
721  }
722 
723  private class ScrollToNext extends org.controlsfx.control.action.Action {
724 
725  ScrollToNext() {
726  super("", new Consumer<ActionEvent>() { //do not make this a lambda function see issue 2147
727  @Override
728  public void accept(ActionEvent actionEvent) {
729  ChronoField selectedChronoField = scrollInrementComboBox.getSelectionModel().getSelectedItem();
730  ZoneId timeZoneID = TimeLineController.getTimeZoneID();
731  TemporalUnit selectedUnit = selectedChronoField.getBaseUnit();
732 
733  int focusedIndex = table.getFocusModel().getFocusedIndex();
734  CombinedEvent focusedItem = table.getFocusModel().getFocusedItem();
735  if (-1 == focusedIndex || null == focusedItem) {
736  focusedItem = visibleEvents.first();
737  focusedIndex = table.getItems().indexOf(focusedItem);
738  }
739 
740  ZonedDateTime focusedDateTime = Instant.ofEpochMilli(focusedItem.getStartMillis()).atZone(timeZoneID);
741  ZonedDateTime nextDateTime = focusedDateTime.plus(1, selectedUnit);//
742  for (ChronoField field : SCROLL_BY_UNITS) {
743  if (field.getBaseUnit().getDuration().compareTo(selectedUnit.getDuration()) < 0) {
744  nextDateTime = nextDateTime.with(field, field.rangeRefinedBy(nextDateTime).getMinimum());//
745  }
746  }
747  long nextMillis = nextDateTime.toInstant().toEpochMilli();
748 
749  int nextIndex = table.getItems().size() - 1;
750  for (int i = focusedIndex; i < table.getItems().size(); i++) {
751  if (table.getItems().get(i).getStartMillis() >= nextMillis) {
752  nextIndex = i;
753  break;
754  }
755  }
756  scrollToAndFocus(nextIndex);
757  }
758  });
759  setGraphic(new ImageView(NEXT));
760  IntegerBinding size = Bindings.size(table.getItems());
761  disabledProperty().bind(size.isEqualTo(0).or(
762  table.getFocusModel().focusedIndexProperty().greaterThanOrEqualTo(size.subtract(1))));
763  }
764 
765  }
766 
767  private class ScrollToPrevious extends org.controlsfx.control.action.Action {
768 
769  ScrollToPrevious() {
770  super("", new Consumer<ActionEvent>() { //do not make this a lambda function see issue 2147
771  @Override
772  public void accept(ActionEvent actionEvent) {
773  ZoneId timeZoneID = TimeLineController.getTimeZoneID();
774  ChronoField selectedChronoField = scrollInrementComboBox.getSelectionModel().getSelectedItem();
775  TemporalUnit selectedUnit = selectedChronoField.getBaseUnit();
776 
777  int focusedIndex = table.getFocusModel().getFocusedIndex();
778  CombinedEvent focusedItem = table.getFocusModel().getFocusedItem();
779  if (-1 == focusedIndex || null == focusedItem) {
780  focusedItem = visibleEvents.last();
781  focusedIndex = table.getItems().indexOf(focusedItem);
782  }
783 
784  ZonedDateTime focusedDateTime = Instant.ofEpochMilli(focusedItem.getStartMillis()).atZone(timeZoneID);
785  ZonedDateTime previousDateTime = focusedDateTime.minus(1, selectedUnit);//
786 
787  for (ChronoField field : SCROLL_BY_UNITS) {
788  if (field.getBaseUnit().getDuration().compareTo(selectedUnit.getDuration()) < 0) {
789  previousDateTime = previousDateTime.with(field, field.rangeRefinedBy(previousDateTime).getMaximum());//
790  }
791  }
792  long previousMillis = previousDateTime.toInstant().toEpochMilli();
793 
794  int previousIndex = 0;
795  for (int i = focusedIndex; i > 0; i--) {
796  if (table.getItems().get(i).getStartMillis() <= previousMillis) {
797  previousIndex = i;
798  break;
799  }
800  }
801 
802  scrollToAndFocus(previousIndex);
803  }
804  });
805  setGraphic(new ImageView(PREVIOUS));
806  disabledProperty().bind(table.getFocusModel().focusedIndexProperty().lessThan(1));
807  }
808  }
809 
810 }
static EventNode createEventNode(final Long eventID, FilteredEventsModel eventsModel)
Definition: EventNode.java:219
List< ContentTag > getContentTagsByContent(Content content)
synchronized void selectEventIDs(Collection< Long > eventIDs)
synchronized ObservableList< Long > getSelectedEventIDs()
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void construct(Node node, String fxmlFileName)
List< BlackboardArtifactTag > getBlackboardArtifactTagsByArtifact(BlackboardArtifact artifact)

Copyright © 2012-2018 Basis Technology. Generated on: Tue Dec 18 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.