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