Autopsy  4.6.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-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  } else {
571  setText(textSupplier.apply(getEvent()));
572  }
573  }
574  }
575 
580  private abstract class EventTableCell extends TableCell<CombinedEvent, CombinedEvent> {
581 
583 
589  SingleEvent getEvent() {
590  return event;
591  }
592 
593  @Override
594  protected void updateItem(CombinedEvent item, boolean empty) {
595  super.updateItem(item, empty);
596 
597  if (empty || item == null) {
598  event = null;
599  } else {
600  //stash the event in the cell for derived classed to use.
601  event = controller.getEventsModel().getEventById(item.getRepresentativeEventID());
602  }
603  }
604  }
605 
609  private class EventRow extends TableRow<CombinedEvent> {
610 
612 
618  SingleEvent getEvent() {
619  return event;
620  }
621 
622  @NbBundle.Messages({
623  "ListChart.errorMsg=There was a problem getting the content for the selected event."})
624  @Override
625  protected void updateItem(CombinedEvent item, boolean empty) {
626  CombinedEvent oldItem = getItem();
627  if (oldItem != null) {
628  visibleEvents.remove(oldItem);
629  }
630  super.updateItem(item, empty);
631 
632  if (empty || item == null) {
633  event = null;
634  } else {
635  visibleEvents.add(item);
636  event = controller.getEventsModel().getEventById(item.getRepresentativeEventID());
637 
638  setOnContextMenuRequested(contextMenuEvent -> {
639  //make a new context menu on each request in order to include uptodate tag names and hash sets
640  try {
642  List<MenuItem> menuItems = new ArrayList<>();
643 
644  //for each actions avaialable on node, make a menu item.
645  for (Action action : node.getActions(false)) {
646  if (action == null) {
647  // swing/netbeans uses null action to represent separator in menu
648  menuItems.add(new SeparatorMenuItem());
649  } else {
650  String actionName = Objects.toString(action.getValue(Action.NAME));
651  //for now, suppress properties and tools actions, by ignoring them
652  if (Arrays.asList("&Properties", "Tools").contains(actionName) == false) { //NON-NLS
653  if (action instanceof Presenter.Popup) {
654  /*
655  * If the action is really the root of a
656  * set of actions (eg, tagging). Make a
657  * menu that parallels the action's
658  * menu.
659  */
660  JMenuItem submenu = ((Presenter.Popup) action).getPopupPresenter();
661  menuItems.add(SwingFXMenuUtils.createFXMenu(submenu));
662  } else {
663  menuItems.add(SwingFXMenuUtils.createFXMenu(new Actions.MenuItem(action, false)));
664  }
665  }
666  }
667  };
668 
669  //show new context menu.
670  new ContextMenu(menuItems.toArray(new MenuItem[menuItems.size()]))
671  .show(this, contextMenuEvent.getScreenX(), contextMenuEvent.getScreenY());
672  } catch (NoCurrentCaseException ex) {
673  //Since the case is closed, the user probably doesn't care about this, just log it as a precaution.
674  LOGGER.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); //NON-NLS
675  } catch (TskCoreException ex) {
676  LOGGER.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); //NON-NLS
677  Platform.runLater(() -> {
678  Notifications.create()
679  .owner(getScene().getWindow())
680  .text(Bundle.ListChart_errorMsg())
681  .showError();
682  });
683  }
684  });
685 
686  }
687  }
688  }
689 
690  private class ScrollToFirst extends org.controlsfx.control.action.Action {
691 
692  ScrollToFirst() {
693  super("", new Consumer<ActionEvent>() { //do not make this a lambda function see issue 2147
694  @Override
695  public void accept(ActionEvent actionEvent) {
696  scrollToAndFocus(0);
697  }
698  });
699  setGraphic(new ImageView(FIRST));
700  disabledProperty().bind(table.getFocusModel().focusedIndexProperty().lessThan(1));
701  }
702  }
703 
704  private class ScrollToLast extends org.controlsfx.control.action.Action {
705 
706  ScrollToLast() {
707  super("", new Consumer<ActionEvent>() { //do not make this a lambda function see issue 2147
708  @Override
709  public void accept(ActionEvent actionEvent) {
710  scrollToAndFocus(table.getItems().size() - 1);
711  }
712  });
713  setGraphic(new ImageView(LAST));
714  IntegerBinding size = Bindings.size(table.getItems());
715  disabledProperty().bind(size.isEqualTo(0).or(
716  table.getFocusModel().focusedIndexProperty().greaterThanOrEqualTo(size.subtract(1))));
717  }
718  }
719 
720  private class ScrollToNext extends org.controlsfx.control.action.Action {
721 
722  ScrollToNext() {
723  super("", new Consumer<ActionEvent>() { //do not make this a lambda function see issue 2147
724  @Override
725  public void accept(ActionEvent actionEvent) {
726  ChronoField selectedChronoField = scrollInrementComboBox.getSelectionModel().getSelectedItem();
727  ZoneId timeZoneID = TimeLineController.getTimeZoneID();
728  TemporalUnit selectedUnit = selectedChronoField.getBaseUnit();
729 
730  int focusedIndex = table.getFocusModel().getFocusedIndex();
731  CombinedEvent focusedItem = table.getFocusModel().getFocusedItem();
732  if (-1 == focusedIndex || null == focusedItem) {
733  focusedItem = visibleEvents.first();
734  focusedIndex = table.getItems().indexOf(focusedItem);
735  }
736 
737  ZonedDateTime focusedDateTime = Instant.ofEpochMilli(focusedItem.getStartMillis()).atZone(timeZoneID);
738  ZonedDateTime nextDateTime = focusedDateTime.plus(1, selectedUnit);//
739  for (ChronoField field : SCROLL_BY_UNITS) {
740  if (field.getBaseUnit().getDuration().compareTo(selectedUnit.getDuration()) < 0) {
741  nextDateTime = nextDateTime.with(field, field.rangeRefinedBy(nextDateTime).getMinimum());//
742  }
743  }
744  long nextMillis = nextDateTime.toInstant().toEpochMilli();
745 
746  int nextIndex = table.getItems().size() - 1;
747  for (int i = focusedIndex; i < table.getItems().size(); i++) {
748  if (table.getItems().get(i).getStartMillis() >= nextMillis) {
749  nextIndex = i;
750  break;
751  }
752  }
753  scrollToAndFocus(nextIndex);
754  }
755  });
756  setGraphic(new ImageView(NEXT));
757  IntegerBinding size = Bindings.size(table.getItems());
758  disabledProperty().bind(size.isEqualTo(0).or(
759  table.getFocusModel().focusedIndexProperty().greaterThanOrEqualTo(size.subtract(1))));
760  }
761 
762  }
763 
764  private class ScrollToPrevious extends org.controlsfx.control.action.Action {
765 
766  ScrollToPrevious() {
767  super("", new Consumer<ActionEvent>() { //do not make this a lambda function see issue 2147
768  @Override
769  public void accept(ActionEvent actionEvent) {
770  ZoneId timeZoneID = TimeLineController.getTimeZoneID();
771  ChronoField selectedChronoField = scrollInrementComboBox.getSelectionModel().getSelectedItem();
772  TemporalUnit selectedUnit = selectedChronoField.getBaseUnit();
773 
774  int focusedIndex = table.getFocusModel().getFocusedIndex();
775  CombinedEvent focusedItem = table.getFocusModel().getFocusedItem();
776  if (-1 == focusedIndex || null == focusedItem) {
777  focusedItem = visibleEvents.last();
778  focusedIndex = table.getItems().indexOf(focusedItem);
779  }
780 
781  ZonedDateTime focusedDateTime = Instant.ofEpochMilli(focusedItem.getStartMillis()).atZone(timeZoneID);
782  ZonedDateTime previousDateTime = focusedDateTime.minus(1, selectedUnit);//
783 
784  for (ChronoField field : SCROLL_BY_UNITS) {
785  if (field.getBaseUnit().getDuration().compareTo(selectedUnit.getDuration()) < 0) {
786  previousDateTime = previousDateTime.with(field, field.rangeRefinedBy(previousDateTime).getMaximum());//
787  }
788  }
789  long previousMillis = previousDateTime.toInstant().toEpochMilli();
790 
791  int previousIndex = 0;
792  for (int i = focusedIndex; i > 0; i--) {
793  if (table.getItems().get(i).getStartMillis() <= previousMillis) {
794  previousIndex = i;
795  break;
796  }
797  }
798 
799  scrollToAndFocus(previousIndex);
800  }
801  });
802  setGraphic(new ImageView(PREVIOUS));
803  disabledProperty().bind(table.getFocusModel().focusedIndexProperty().lessThan(1));
804  }
805  }
806 
807 }
static EventNode createEventNode(final Long eventID, FilteredEventsModel eventsModel)
Definition: EventNode.java:219
List< ContentTag > getContentTagsByContent(Content content)
synchronized void selectEventIDs(Collection< Long > eventIDs)
static MenuItem createFXMenu(MenuElement jMenuElement)
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-2016 Basis Technology. Generated on: Mon May 7 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.