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

Copyright © 2012-2021 Basis Technology. Generated on: Thu Sep 30 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.