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

Copyright © 2012-2016 Basis Technology. Generated on: Tue Jun 13 2017
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.