Autopsy  4.12.0
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-2018 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;
90 import org.sleuthkit.datamodel.BlackboardArtifact;
91 import org.sleuthkit.datamodel.Content;
92 import org.sleuthkit.datamodel.SleuthkitCase;
93 import org.sleuthkit.datamodel.TskCoreException;
94 import org.sleuthkit.datamodel.TimelineEventType;
95 import static org.sleuthkit.datamodel.TimelineEventType.FILE_ACCESSED;
96 import static org.sleuthkit.datamodel.TimelineEventType.FILE_CHANGED;
97 import static org.sleuthkit.datamodel.TimelineEventType.FILE_CREATED;
98 import static org.sleuthkit.datamodel.TimelineEventType.FILE_MODIFIED;
99 import static org.sleuthkit.datamodel.TimelineEventType.FILE_SYSTEM;
100 import org.sleuthkit.datamodel.TimelineEvent;
101 
105 class ListTimeline extends BorderPane {
106 
107  private static final Logger logger = Logger.getLogger(ListTimeline.class.getName());
108 
109  private static final Image HASH_HIT = new Image("/org/sleuthkit/autopsy/images/hashset_hits.png"); //NON-NLS
110  private static final Image TAG = new Image("/org/sleuthkit/autopsy/images/green-tag-icon-16.png"); //NON-NLS
111  private static final Image FIRST = new Image("/org/sleuthkit/autopsy/timeline/images/resultset_first.png"); //NON-NLS
112  private static final Image PREVIOUS = new Image("/org/sleuthkit/autopsy/timeline/images/resultset_previous.png"); //NON-NLS
113  private static final Image NEXT = new Image("/org/sleuthkit/autopsy/timeline/images/resultset_next.png"); //NON-NLS
114  private static final Image LAST = new Image("/org/sleuthkit/autopsy/timeline/images/resultset_last.png"); //NON-NLS
115 
119  private static final Callback<TableColumn.CellDataFeatures<CombinedEvent, CombinedEvent>, ObservableValue<CombinedEvent>> CELL_VALUE_FACTORY = param -> new SimpleObjectProperty<>(param.getValue());
120 
121  private static final List<ChronoField> SCROLL_BY_UNITS = Arrays.asList(
122  ChronoField.YEAR,
123  ChronoField.MONTH_OF_YEAR,
124  ChronoField.DAY_OF_MONTH,
125  ChronoField.HOUR_OF_DAY,
126  ChronoField.MINUTE_OF_HOUR,
127  ChronoField.SECOND_OF_MINUTE);
128 
129  private static final int DEFAULT_ROW_HEIGHT = 24;
130 
131  @FXML
132  private HBox navControls;
133  @FXML
134  private ComboBox<ChronoField> scrollInrementComboBox;
135  @FXML
136  private Button firstButton;
137  @FXML
138  private Button previousButton;
139  @FXML
140  private Button nextButton;
141  @FXML
142  private Button lastButton;
143  @FXML
144  private Label eventCountLabel;
145  @FXML
146  private TableView<CombinedEvent> table;
147  @FXML
148  private TableColumn<CombinedEvent, CombinedEvent> idColumn;
149  @FXML
150  private TableColumn<CombinedEvent, CombinedEvent> dateTimeColumn;
151  @FXML
152  private TableColumn<CombinedEvent, CombinedEvent> descriptionColumn;
153  @FXML
154  private TableColumn<CombinedEvent, CombinedEvent> typeColumn;
155  @FXML
156  private TableColumn<CombinedEvent, CombinedEvent> taggedColumn;
157  @FXML
158  private TableColumn<CombinedEvent, CombinedEvent> hashHitColumn;
159 
164  private final SortedSet<CombinedEvent> visibleEvents;
165 
166  private final TimeLineController controller;
167  private final SleuthkitCase sleuthkitCase;
168  private final TagsManager tagsManager;
169 
175  private final ListChangeListener<CombinedEvent> selectedEventListener = new ListChangeListener<CombinedEvent>() {
176  @Override
177  public void onChanged(ListChangeListener.Change<? extends CombinedEvent> c) {
178  try {
179  controller.selectEventIDs(table.getSelectionModel().getSelectedItems().stream()
180  .filter(Objects::nonNull)
182  .collect(Collectors.toSet()));
183  } catch (TskCoreException ex) {
184  logger.log(Level.SEVERE, "Error selecting events.", ex);
185  Notifications.create().owner(getScene().getWindow())
186  .text("Error selecting events.").showError();
187  }
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(CombinedEvent::getStartMillis));
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 
216  //configure scroll controls
217  scrollInrementComboBox.setButtonCell(new ChronoFieldListCell());
218  scrollInrementComboBox.setCellFactory(comboBox -> new ChronoFieldListCell());
219  scrollInrementComboBox.getItems().setAll(SCROLL_BY_UNITS);
220  scrollInrementComboBox.getSelectionModel().select(ChronoField.YEAR);
221  ActionUtils.configureButton(new ScrollToFirst(), firstButton);
222  ActionUtils.configureButton(new ScrollToPrevious(), previousButton);
223  ActionUtils.configureButton(new ScrollToNext(), nextButton);
224  ActionUtils.configureButton(new ScrollToLast(), lastButton);
225 
226  //override default table row with one that provides context menus
227  table.setRowFactory(tableView -> new EventRow());
228 
229  //remove idColumn (can be restored for debugging).
230  table.getColumns().remove(idColumn);
231 
233  dateTimeColumn.setCellValueFactory(CELL_VALUE_FACTORY);
234  dateTimeColumn.setCellFactory(col -> new TextEventTableCell(singleEvent
235  -> TimeLineController.getZonedFormatter().print(singleEvent.getStartMillis())));
236 
237  descriptionColumn.setCellValueFactory(CELL_VALUE_FACTORY);
238  descriptionColumn.setCellFactory(col -> new TextEventTableCell(singleEvent
239  -> singleEvent.getDescription(TimelineEvent.DescriptionLevel.FULL)));
240 
241  typeColumn.setCellValueFactory(CELL_VALUE_FACTORY);
242  typeColumn.setCellFactory(col -> new EventTypeCell());
243 
244  taggedColumn.setCellValueFactory(CELL_VALUE_FACTORY);
245  taggedColumn.setCellFactory(col -> new TaggedCell());
246 
247  hashHitColumn.setCellValueFactory(CELL_VALUE_FACTORY);
248  hashHitColumn.setCellFactory(col -> new HashHitCell());
249 
250  //bind event count label to number of items in the table
251  eventCountLabel.textProperty().bind(new StringBinding() {
252  {
253  bind(table.getItems());
254  }
255 
256  @Override
257  protected String computeValue() {
258  return Bundle.ListTimeline_eventCountLabel_text(table.getItems().size());
259  }
260  });
261 
262  // use listener to keep controller selection in sync with table selection.
263  table.getSelectionModel().getSelectedItems().addListener(selectedEventListener);
264  table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
265  selectEvents(controller.getSelectedEventIDs()); //grab initial selection
266  }
267 
273  @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
274  void setCombinedEvents(Collection<CombinedEvent> events) {
275  table.getItems().setAll(events);
276  }
277 
283  void selectEvents(Collection<Long> selectedEventIDs) {
284  if (selectedEventIDs.isEmpty()) {
285  //this is the final selection, so we don't need to mess with the listener
286  table.getSelectionModel().clearSelection();
287  } else {
288  /*
289  * Changes in the table selection are propogated to the controller
290  * by a listener. There is no API on TableView's selection model to
291  * clear the selection and select multiple rows as one "action".
292  * Therefore we clear the selection and then make the new selection,
293  * but we don't want this intermediate state of no selection to be
294  * pushed to the controller as it interferes with maintaining the
295  * right selection. To avoid notifying the controller, we remove the
296  * listener, clear the selection, then re-attach it.
297  */
298  table.getSelectionModel().getSelectedItems().removeListener(selectedEventListener);
299 
300  table.getSelectionModel().clearSelection();
301 
302  table.getSelectionModel().getSelectedItems().addListener(selectedEventListener);
303 
304  //find the indices of the CombinedEvents that will be selected
305  int[] selectedIndices = table.getItems().stream()
306  .filter(combinedEvent -> Collections.disjoint(combinedEvent.getEventIDs(), selectedEventIDs) == false)
307  .mapToInt(table.getItems()::indexOf)
308  .toArray();
309 
310  //select indices and scroll to the first one
311  if (selectedIndices.length > 0) {
312  Integer firstSelectedIndex = selectedIndices[0];
313  table.getSelectionModel().selectIndices(firstSelectedIndex, selectedIndices);
314  scrollTo(firstSelectedIndex);
315  table.requestFocus(); //grab focus so selection is clearer to user
316  }
317  }
318  }
319 
327  List<Node> getTimeNavigationControls() {
328  return Collections.singletonList(navControls);
329  }
330 
338  private void scrollToAndFocus(Integer index) {
339  table.requestFocus();
340  scrollTo(index);
341  table.getFocusModel().focus(index);
342  }
343 
349  private void scrollTo(Integer index) {
350  if (visibleEvents.contains(table.getItems().get(index)) == false) {
351  table.scrollTo(DoubleMath.roundToInt(index - ((table.getHeight() / DEFAULT_ROW_HEIGHT)) / 2, RoundingMode.HALF_EVEN));
352  }
353  }
354 
359  private abstract class EventTableCell extends TableCell<CombinedEvent, CombinedEvent> {
360 
361  private TimelineEvent event;
362 
368  TimelineEvent getEvent() {
369  return event;
370  }
371 
372  @NbBundle.Messages({"EventTableCell.updateItem.errorMessage=Error getting event by id."})
373  @Override
374  protected void updateItem(CombinedEvent item, boolean empty) {
375  super.updateItem(item, empty);
376 
377  if (empty || item == null) {
378  event = null;
379  } else {
380  try {
381  //stash the event in the cell for derived classed to use.
382  event = controller.getEventsModel().getEventById(item.getRepresentativeEventID());
383  } catch (TskCoreException ex) {
384  Notifications.create().owner(getScene().getWindow())
385  .text(Bundle.EventTableCell_updateItem_errorMessage()).showError();
386  logger.log(Level.SEVERE, "Error getting event by id.", ex);
387  }
388  }
389  }
390  }
391 
395  private class EventTypeCell extends EventTableCell {
396 
397  @NbBundle.Messages({
398  "ListView.EventTypeCell.modifiedTooltip=File Modified ( M )",
399  "ListView.EventTypeCell.accessedTooltip=File Accessed ( A )",
400  "ListView.EventTypeCell.createdTooltip=File Created ( B, for Born )",
401  "ListView.EventTypeCell.changedTooltip=File Changed ( C )"
402  })
403  @Override
404  protected void updateItem(CombinedEvent item, boolean empty) {
405  super.updateItem(item, empty);
406 
407  if (empty || item == null) {
408  setText(null);
409  setGraphic(null);
410  setTooltip(null);
411  } else {
412  if (item.getEventTypes().stream().allMatch(TimelineEventType.FILE_SYSTEM.getSubTypes()::contains)) {
413  String typeString = ""; //NON-NLS
414  VBox toolTipVbox = new VBox(5);
415 
416  for (TimelineEventType type : TimelineEventType.FILE_SYSTEM.getSubTypes()) {
417  if (item.getEventTypes().contains(type)) {
418  if (type.equals(FILE_MODIFIED)) {
419  typeString += "M"; //NON-NLS
420  toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_modifiedTooltip(),
421  new ImageView(getImagePath(type))));
422  } else if (type.equals(FILE_ACCESSED)) {
423  typeString += "A"; //NON-NLS
424  toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_accessedTooltip(),
425  new ImageView(getImagePath(type))));
426  } else if (type.equals(FILE_CREATED)) {
427  typeString += "B"; //NON-NLS
428  toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_createdTooltip(),
429  new ImageView(getImagePath(type))));
430  } else if (type.equals(FILE_CHANGED)) {
431  typeString += "C"; //NON-NLS
432  toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_changedTooltip(),
433  new ImageView(getImagePath(type))));
434  }
435  } else {
436  typeString += "_"; //NON-NLS
437  }
438  }
439  setText(typeString);
440  setGraphic(new ImageView(getImagePath(FILE_SYSTEM)));
441  Tooltip tooltip = new Tooltip();
442  tooltip.setGraphic(toolTipVbox);
443  setTooltip(tooltip);
444 
445  } else {
446  TimelineEventType eventType = Iterables.getOnlyElement(item.getEventTypes());
447  setText(eventType.getDisplayName());
448  setGraphic(new ImageView(getImagePath(eventType)));
449  setTooltip(new Tooltip(eventType.getDisplayName()));
450  };
451  }
452  }
453  }
454 
458  private class TaggedCell extends EventTableCell {
459 
463  TaggedCell() {
464  setAlignment(Pos.CENTER);
465  }
466 
467  @NbBundle.Messages({
468  "ListTimeline.taggedTooltip.error=There was a problem getting the tag names for the selected event.",
469  "# {0} - tag names",
470  "ListTimeline.taggedTooltip.text=Tags:\n{0}"})
471  @Override
472  protected void updateItem(CombinedEvent item, boolean empty) {
473  super.updateItem(item, empty);
474 
475  if (empty || item == null || (getEvent().isTagged() == false)) {
476  setGraphic(null);
477  setTooltip(null);
478  } else {
479  /*
480  * if the cell is not empty and the event is tagged, show the
481  * tagged icon, and show a list of tag names in the tooltip
482  */
483  setGraphic(new ImageView(TAG));
484 
485  SortedSet<String> tagNames = new TreeSet<>();
486  try {
487  //get file tags
488  Content file = sleuthkitCase.getContentById(getEvent().getFileObjID());
489  tagsManager.getContentTagsByContent(file).stream()
490  .map(tag -> tag.getName().getDisplayName())
491  .forEach(tagNames::add);
492 
493  } catch (TskCoreException ex) {
494  logger.log(Level.SEVERE, "Failed to lookup tags for obj id " + getEvent().getFileObjID(), ex); //NON-NLS
495  Platform.runLater(() -> {
496  Notifications.create()
497  .owner(getScene().getWindow())
498  .text(Bundle.ListTimeline_taggedTooltip_error())
499  .showError();
500  });
501  }
502  getEvent().getArtifactID().ifPresent(artifactID -> {
503  //get artifact tags, if there is an artifact associated with the event.
504  try {
505  BlackboardArtifact artifact = sleuthkitCase.getBlackboardArtifact(artifactID);
506  tagsManager.getBlackboardArtifactTagsByArtifact(artifact).stream()
507  .map(tag -> tag.getName().getDisplayName())
508  .forEach(tagNames::add);
509  } catch (TskCoreException ex) {
510  logger.log(Level.SEVERE, "Failed to lookup tags for artifact id " + artifactID, ex); //NON-NLS
511  Platform.runLater(() -> {
512  Notifications.create()
513  .owner(getScene().getWindow())
514  .text(Bundle.ListTimeline_taggedTooltip_error())
515  .showError();
516  });
517  }
518  });
519  Tooltip tooltip = new Tooltip(Bundle.ListTimeline_taggedTooltip_text(String.join("\n", tagNames))); //NON-NLS
520  tooltip.setGraphic(new ImageView(TAG));
521  setTooltip(tooltip);
522  }
523  }
524  }
525 
530  private class HashHitCell extends EventTableCell {
531 
535  HashHitCell() {
536  setAlignment(Pos.CENTER);
537  }
538 
539  @NbBundle.Messages({
540  "ListTimeline.hashHitTooltip.error=There was a problem getting the hash set names for the selected event.",
541  "# {0} - hash set names",
542  "ListTimeline.hashHitTooltip.text=Hash Sets:\n{0}"})
543  @Override
544  protected void updateItem(CombinedEvent item, boolean empty) {
545  super.updateItem(item, empty);
546 
547  if (empty || item == null || (getEvent().isHashHit() == false)) {
548  setGraphic(null);
549  setTooltip(null);
550  } else {
551  /*
552  * If the cell is not empty and the event's file is a hash hit,
553  * show the hash hit icon, and show a list of hash set names in
554  * the tooltip
555  */
556  setGraphic(new ImageView(HASH_HIT));
557  try {
558  Set<String> hashSetNames = new TreeSet<>(sleuthkitCase.getContentById(getEvent().getFileObjID()).getHashSetNames());
559  Tooltip tooltip = new Tooltip(Bundle.ListTimeline_hashHitTooltip_text(String.join("\n", hashSetNames))); //NON-NLS
560  tooltip.setGraphic(new ImageView(HASH_HIT));
561  setTooltip(tooltip);
562  } catch (TskCoreException ex) {
563  logger.log(Level.SEVERE, "Failed to lookup hash set names for obj id " + getEvent().getFileObjID(), ex); //NON-NLS
564  Platform.runLater(() -> {
565  Notifications.create()
566  .owner(getScene().getWindow())
567  .text(Bundle.ListTimeline_hashHitTooltip_error())
568  .showError();
569  });
570  }
571  }
572  }
573  }
574 
579  private class TextEventTableCell extends EventTableCell {
580 
581  private final Function<TimelineEvent, String> textSupplier;
582 
589  TextEventTableCell(Function<TimelineEvent, String> textSupplier) {
590  this.textSupplier = textSupplier;
591  setTextOverrun(OverrunStyle.CENTER_ELLIPSIS);
592  setEllipsisString(" ... "); //NON-NLS
593  }
594 
595  @Override
596  protected void updateItem(CombinedEvent item, boolean empty) {
597  super.updateItem(item, empty);
598  if (empty || item == null) {
599  setText(null);
600  setTooltip(null);
601  } else {
602  String text = textSupplier.apply(getEvent());
603  setText(text);
604  setTooltip(new Tooltip(text));
605  }
606  }
607  }
608 
612  private class EventRow extends TableRow<CombinedEvent> {
613 
614  @NbBundle.Messages({
615  "ListChart.errorMsg=There was a problem getting the content for the selected event.",
616  "EventRow.updateItem.errorMessage=Error getting event by id."})
617  @Override
618  protected void updateItem(CombinedEvent item, boolean empty) {
619  CombinedEvent oldItem = getItem();
620  if (oldItem != null) {
621  visibleEvents.remove(oldItem);
622  }
623  super.updateItem(item, empty);
624 
625  if (empty || item == null) {
626  setOnContextMenuRequested(ListTimeline::NOOPConsumer);
627  } else {
628  visibleEvents.add(item);
629  setOnContextMenuRequested(contextMenuEvent -> {
630  //make a new context menu on each request in order to include uptodate tag names and hash sets
631  try {
632  EventNode node = EventNode.createEventNode(item.getRepresentativeEventID(), controller.getEventsModel());
633  List<MenuItem> menuItems = new ArrayList<>();
634 
635  //for each actions avaialable on node, make a menu item.
636  for (Action action : node.getActions(false)) {
637  if (action == null) {
638  // swing/netbeans uses null action to represent separator in menu
639  menuItems.add(new SeparatorMenuItem());
640  } else {
641  String actionName = Objects.toString(action.getValue(Action.NAME));
642  //for now, suppress properties and tools actions, by ignoring them
643  if (Arrays.asList("&Properties", "Tools").contains(actionName) == false) { //NON-NLS
644  if (action instanceof Presenter.Popup) {
645  /*
646  * If the action is really the root of a
647  * set of actions (eg, tagging). Make a
648  * menu that parallels the action's
649  * menu.
650  */
651  JMenuItem submenu = ((Presenter.Popup) action).getPopupPresenter();
652  menuItems.add(SwingFXMenuUtils.createFXMenu(submenu));
653  } else {
654  menuItems.add(SwingFXMenuUtils.createFXMenu(new Actions.MenuItem(action, false)));
655  }
656  }
657  }
658  }
659 
660  //show new context menu.
661  new ContextMenu(menuItems.toArray(new MenuItem[menuItems.size()]))
662  .show(this, contextMenuEvent.getScreenX(), contextMenuEvent.getScreenY());
663  } catch (TskCoreException ex) {
664  logger.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a TimelineEvent.", ex); //NON-NLS
665  Platform.runLater(() -> {
666  Notifications.create()
667  .owner(getScene().getWindow())
668  .text(Bundle.ListChart_errorMsg())
669  .showError();
670  });
671  }
672  });
673  }
674  }
675  }
676 
677  public static <X> void NOOPConsumer(X event) {
678  }
679 
680  private class ScrollToFirst extends org.controlsfx.control.action.Action {
681 
682  ScrollToFirst() {
683  super("", new Consumer<ActionEvent>() { //do not make this a lambda function see issue 2147
684  @Override
685  public void accept(ActionEvent actionEvent) {
686  scrollToAndFocus(0);
687  }
688  });
689  setGraphic(new ImageView(FIRST));
690  disabledProperty().bind(table.getFocusModel().focusedIndexProperty().lessThan(1));
691  }
692  }
693 
694  private class ScrollToLast extends org.controlsfx.control.action.Action {
695 
696  ScrollToLast() {
697  super("", new Consumer<ActionEvent>() { //do not make this a lambda function see issue 2147
698  @Override
699  public void accept(ActionEvent actionEvent) {
700  scrollToAndFocus(table.getItems().size() - 1);
701  }
702  });
703  setGraphic(new ImageView(LAST));
704  IntegerBinding size = Bindings.size(table.getItems());
705  disabledProperty().bind(size.isEqualTo(0).or(
706  table.getFocusModel().focusedIndexProperty().greaterThanOrEqualTo(size.subtract(1))));
707  }
708  }
709 
710  private class ScrollToNext extends org.controlsfx.control.action.Action {
711 
712  ScrollToNext() {
713  super("", new Consumer<ActionEvent>() { //do not make this a lambda function see issue 2147
714  @Override
715  public void accept(ActionEvent actionEvent) {
716  ChronoField selectedChronoField = scrollInrementComboBox.getSelectionModel().getSelectedItem();
717  ZoneId timeZoneID = TimeLineController.getTimeZoneID();
718  TemporalUnit selectedUnit = selectedChronoField.getBaseUnit();
719 
720  int focusedIndex = table.getFocusModel().getFocusedIndex();
721  CombinedEvent focusedItem = table.getFocusModel().getFocusedItem();
722  if (-1 == focusedIndex || null == focusedItem) {
723  focusedItem = visibleEvents.first();
724  focusedIndex = table.getItems().indexOf(focusedItem);
725  }
726 
727  ZonedDateTime focusedDateTime = Instant.ofEpochMilli(focusedItem.getStartMillis()).atZone(timeZoneID);
728  ZonedDateTime nextDateTime = focusedDateTime.plus(1, selectedUnit);//
729  for (ChronoField field : SCROLL_BY_UNITS) {
730  if (field.getBaseUnit().getDuration().compareTo(selectedUnit.getDuration()) < 0) {
731  nextDateTime = nextDateTime.with(field, field.rangeRefinedBy(nextDateTime).getMinimum());//
732  }
733  }
734  long nextMillis = nextDateTime.toInstant().toEpochMilli();
735 
736  int nextIndex = table.getItems().size() - 1;
737  for (int i = focusedIndex; i < table.getItems().size(); i++) {
738  if (table.getItems().get(i).getStartMillis() >= nextMillis) {
739  nextIndex = i;
740  break;
741  }
742  }
743  scrollToAndFocus(nextIndex);
744  }
745  });
746  setGraphic(new ImageView(NEXT));
747  IntegerBinding size = Bindings.size(table.getItems());
748  disabledProperty().bind(size.isEqualTo(0).or(
749  table.getFocusModel().focusedIndexProperty().greaterThanOrEqualTo(size.subtract(1))));
750  }
751 
752  }
753 
754  private class ScrollToPrevious extends org.controlsfx.control.action.Action {
755 
756  ScrollToPrevious() {
757  super("", new Consumer<ActionEvent>() { //do not make this a lambda function see issue 2147
758  @Override
759  public void accept(ActionEvent actionEvent) {
760  ZoneId timeZoneID = TimeLineController.getTimeZoneID();
761  ChronoField selectedChronoField = scrollInrementComboBox.getSelectionModel().getSelectedItem();
762  TemporalUnit selectedUnit = selectedChronoField.getBaseUnit();
763 
764  int focusedIndex = table.getFocusModel().getFocusedIndex();
765  CombinedEvent focusedItem = table.getFocusModel().getFocusedItem();
766  if (-1 == focusedIndex || null == focusedItem) {
767  focusedItem = visibleEvents.last();
768  focusedIndex = table.getItems().indexOf(focusedItem);
769  }
770 
771  ZonedDateTime focusedDateTime = Instant.ofEpochMilli(focusedItem.getStartMillis()).atZone(timeZoneID);
772  ZonedDateTime previousDateTime = focusedDateTime.minus(1, selectedUnit);//
773 
774  for (ChronoField field : SCROLL_BY_UNITS) {
775  if (field.getBaseUnit().getDuration().compareTo(selectedUnit.getDuration()) < 0) {
776  previousDateTime = previousDateTime.with(field, field.rangeRefinedBy(previousDateTime).getMaximum());//
777  }
778  }
779  long previousMillis = previousDateTime.toInstant().toEpochMilli();
780 
781  int previousIndex = 0;
782  for (int i = focusedIndex; i > 0; i--) {
783  if (table.getItems().get(i).getStartMillis() <= previousMillis) {
784  previousIndex = i;
785  break;
786  }
787  }
788 
789  scrollToAndFocus(previousIndex);
790  }
791  });
792  setGraphic(new ImageView(PREVIOUS));
793  disabledProperty().bind(table.getFocusModel().focusedIndexProperty().lessThan(1));
794  }
795  }
796 }
static EventNode createEventNode(final Long eventID, FilteredEventsModel eventsModel)
Definition: EventNode.java:243
static String getImagePath(TimelineEventType type)

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