Autopsy 4.22.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 */
19package org.sleuthkit.autopsy.timeline.ui.listvew;
20
21import com.google.common.collect.Iterables;
22import com.google.common.math.DoubleMath;
23import java.math.RoundingMode;
24import java.time.Instant;
25import java.time.ZoneId;
26import java.time.ZonedDateTime;
27import java.time.temporal.ChronoField;
28import java.time.temporal.TemporalUnit;
29import java.util.ArrayList;
30import java.util.Arrays;
31import java.util.Collection;
32import java.util.Collections;
33import java.util.Comparator;
34import java.util.List;
35import java.util.Objects;
36import java.util.Set;
37import java.util.SortedSet;
38import java.util.TreeSet;
39import java.util.concurrent.ConcurrentSkipListSet;
40import java.util.function.Consumer;
41import java.util.function.Function;
42import java.util.logging.Level;
43import java.util.stream.Collectors;
44import javafx.application.Platform;
45import javafx.beans.binding.Bindings;
46import javafx.beans.binding.IntegerBinding;
47import javafx.beans.binding.StringBinding;
48import javafx.beans.property.SimpleObjectProperty;
49import javafx.beans.value.ObservableValue;
50import javafx.collections.ListChangeListener;
51import javafx.event.ActionEvent;
52import javafx.fxml.FXML;
53import javafx.geometry.Pos;
54import javafx.scene.Node;
55import javafx.scene.control.Button;
56import javafx.scene.control.ComboBox;
57import javafx.scene.control.ContextMenu;
58import javafx.scene.control.Label;
59import javafx.scene.control.MenuItem;
60import javafx.scene.control.OverrunStyle;
61import javafx.scene.control.SelectionMode;
62import javafx.scene.control.SeparatorMenuItem;
63import javafx.scene.control.TableCell;
64import javafx.scene.control.TableColumn;
65import javafx.scene.control.TableRow;
66import javafx.scene.control.TableView;
67import javafx.scene.control.Tooltip;
68import javafx.scene.image.Image;
69import javafx.scene.image.ImageView;
70import javafx.scene.layout.BorderPane;
71import javafx.scene.layout.HBox;
72import javafx.scene.layout.VBox;
73import javafx.util.Callback;
74import javax.swing.Action;
75import javax.swing.JMenuItem;
76import org.controlsfx.control.Notifications;
77import org.controlsfx.control.action.ActionUtils;
78import org.openide.awt.Actions;
79import org.openide.util.NbBundle;
80import org.openide.util.actions.Presenter;
81import org.sleuthkit.autopsy.casemodule.services.TagsManager;
82import org.sleuthkit.autopsy.coreutils.Logger;
83import org.sleuthkit.autopsy.coreutils.ThreadConfined;
84import org.sleuthkit.autopsy.timeline.ChronoFieldListCell;
85import org.sleuthkit.autopsy.timeline.FXMLConstructor;
86import org.sleuthkit.autopsy.timeline.TimeLineController;
87import org.sleuthkit.autopsy.timeline.explorernodes.EventNode;
88import static org.sleuthkit.autopsy.timeline.ui.EventTypeUtils.getImagePath;
89import org.sleuthkit.autopsy.timeline.ui.listvew.datamodel.CombinedEvent;
90import org.sleuthkit.datamodel.BlackboardArtifact;
91import org.sleuthkit.datamodel.Content;
92import org.sleuthkit.datamodel.SleuthkitCase;
93import org.sleuthkit.datamodel.TskCoreException;
94import org.sleuthkit.datamodel.TimelineEventType;
95import static org.sleuthkit.datamodel.TimelineEventType.FILE_ACCESSED;
96import static org.sleuthkit.datamodel.TimelineEventType.FILE_CHANGED;
97import static org.sleuthkit.datamodel.TimelineEventType.FILE_CREATED;
98import static org.sleuthkit.datamodel.TimelineEventType.FILE_MODIFIED;
99import static org.sleuthkit.datamodel.TimelineEventType.FILE_SYSTEM;
100import org.sleuthkit.datamodel.TimelineEvent;
101import org.sleuthkit.datamodel.TimelineLevelOfDetail;
102
106class 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 @SuppressWarnings("this-escape")
198 ListTimeline(TimeLineController controller) {
199 this.controller = controller;
200 sleuthkitCase = controller.getAutopsyCase().getSleuthkitCase();
201 tagsManager = controller.getAutopsyCase().getServices().getTagsManager();
202 FXMLConstructor.construct(this, ListTimeline.class, "ListTimeline.fxml"); //NON-NLS
203 this.visibleEvents = new ConcurrentSkipListSet<>(Comparator.comparing(CombinedEvent::getStartMillis));
204 }
205
206 @FXML
207 @NbBundle.Messages({
208 "# {0} - the number of events",
209 "ListTimeline.eventCountLabel.text={0} events"})
210 void initialize() {
211 assert eventCountLabel != null : "fx:id=\"eventCountLabel\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
212 assert table != null : "fx:id=\"table\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
213 assert idColumn != null : "fx:id=\"idColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
214 assert dateTimeColumn != null : "fx:id=\"dateTimeColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
215 assert descriptionColumn != null : "fx:id=\"descriptionColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
216 assert typeColumn != null : "fx:id=\"typeColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
217
218 //configure scroll controls
219 scrollInrementComboBox.setButtonCell(new ChronoFieldListCell());
220 scrollInrementComboBox.setCellFactory(comboBox -> new ChronoFieldListCell());
221 scrollInrementComboBox.getItems().setAll(SCROLL_BY_UNITS);
222 scrollInrementComboBox.getSelectionModel().select(ChronoField.YEAR);
223 ActionUtils.configureButton(new ScrollToFirst(), firstButton);
224 ActionUtils.configureButton(new ScrollToPrevious(), previousButton);
225 ActionUtils.configureButton(new ScrollToNext(), nextButton);
226 ActionUtils.configureButton(new ScrollToLast(), lastButton);
227
228 //override default table row with one that provides context menus
229 table.setRowFactory(tableView -> new EventRow());
230
231 //remove idColumn (can be restored for debugging).
232 table.getColumns().remove(idColumn);
233
235 dateTimeColumn.setCellValueFactory(CELL_VALUE_FACTORY);
236 dateTimeColumn.setCellFactory(col -> new TextEventTableCell(singleEvent
237 -> TimeLineController.getZonedFormatter().print(singleEvent.getEventTimeInMs())));
238
239 descriptionColumn.setCellValueFactory(CELL_VALUE_FACTORY);
240 descriptionColumn.setCellFactory(col -> new TextEventTableCell(singleEvent
241 -> singleEvent.getDescription(TimelineLevelOfDetail.HIGH)));
242
243 typeColumn.setCellValueFactory(CELL_VALUE_FACTORY);
244 typeColumn.setCellFactory(col -> new EventTypeCell());
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
275 @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
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
361 private abstract class EventTableCell extends TableCell<CombinedEvent, CombinedEvent> {
362
363 private TimelineEvent event;
364
370 TimelineEvent getEvent() {
371 return event;
372 }
373
374 @NbBundle.Messages({"EventTableCell.updateItem.errorMessage=Error getting event by id."})
375 @Override
376 protected void updateItem(CombinedEvent item, boolean empty) {
377 super.updateItem(item, empty);
378
379 if (empty || item == null) {
380 event = null;
381 } else {
382 try {
383 //stash the event in the cell for derived classed to use.
384 event = controller.getEventsModel().getEventById(item.getRepresentativeEventID());
385 } catch (TskCoreException ex) {
386 Notifications.create().owner(getScene().getWindow())
387 .text(Bundle.EventTableCell_updateItem_errorMessage()).showError();
388 logger.log(Level.SEVERE, "Error getting event by id.", ex);
389 }
390 }
391 }
392 }
393
397 private class EventTypeCell extends EventTableCell {
398
399 @NbBundle.Messages({
400 "ListView.EventTypeCell.modifiedTooltip=File Modified ( M )",
401 "ListView.EventTypeCell.accessedTooltip=File Accessed ( A )",
402 "ListView.EventTypeCell.createdTooltip=File Created ( B, for Born )",
403 "ListView.EventTypeCell.changedTooltip=File Changed ( C )"
404 })
405 @Override
406 protected void updateItem(CombinedEvent item, boolean empty) {
407 super.updateItem(item, empty);
408
409 if (empty || item == null) {
410 setText(null);
411 setGraphic(null);
412 setTooltip(null);
413 } else {
414 if (item.getEventTypes().stream().allMatch(TimelineEventType.FILE_SYSTEM.getChildren()::contains)) {
415 String typeString = ""; //NON-NLS
416 VBox toolTipVbox = new VBox(5);
417
418 for (TimelineEventType type : TimelineEventType.FILE_SYSTEM.getChildren()) {
419 if (item.getEventTypes().contains(type)) {
420 if (type.equals(FILE_MODIFIED)) {
421 typeString += "M"; //NON-NLS
422 toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_modifiedTooltip(),
423 new ImageView(getImagePath(type))));
424 } else if (type.equals(FILE_ACCESSED)) {
425 typeString += "A"; //NON-NLS
426 toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_accessedTooltip(),
427 new ImageView(getImagePath(type))));
428 } else if (type.equals(FILE_CREATED)) {
429 typeString += "B"; //NON-NLS
430 toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_createdTooltip(),
431 new ImageView(getImagePath(type))));
432 } else if (type.equals(FILE_CHANGED)) {
433 typeString += "C"; //NON-NLS
434 toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_changedTooltip(),
435 new ImageView(getImagePath(type))));
436 }
437 } else {
438 typeString += "_"; //NON-NLS
439 }
440 }
441 setText(typeString);
442 setGraphic(new ImageView(getImagePath(FILE_SYSTEM)));
443 Tooltip tooltip = new Tooltip();
444 tooltip.setGraphic(toolTipVbox);
445 setTooltip(tooltip);
446
447 } else {
448 TimelineEventType eventType = Iterables.getOnlyElement(item.getEventTypes());
449 setText(eventType.getDisplayName());
450 setGraphic(new ImageView(getImagePath(eventType)));
451 setTooltip(new Tooltip(eventType.getDisplayName()));
452 };
453 }
454 }
455 }
456
460 private class TaggedCell extends EventTableCell {
461
465 TaggedCell() {
466 setAlignment(Pos.CENTER);
467 }
468
469 @NbBundle.Messages({
470 "ListTimeline.taggedTooltip.error=There was a problem getting the tag names for the selected event.",
471 "# {0} - tag names",
472 "ListTimeline.taggedTooltip.text=Tags:\n{0}"})
473 @Override
474 protected void updateItem(CombinedEvent item, boolean empty) {
475 super.updateItem(item, empty);
476
477 if (empty || item == null || (getEvent().eventSourceIsTagged() == false)) {
478 setGraphic(null);
479 setTooltip(null);
480 } else {
481 /*
482 * if the cell is not empty and the event is tagged, show the
483 * tagged icon, and show a list of tag names in the tooltip
484 */
485 setGraphic(new ImageView(TAG));
486
487 SortedSet<String> tagNames = new TreeSet<>();
488 try {
489 //get file tags
490 Content file = sleuthkitCase.getContentById(getEvent().getContentObjID());
491 tagsManager.getContentTagsByContent(file).stream()
492 .map(tag -> tag.getName().getDisplayName())
493 .forEach(tagNames::add);
494
495 } catch (TskCoreException ex) {
496 logger.log(Level.SEVERE, "Failed to lookup tags for obj id " + getEvent().getContentObjID(), ex); //NON-NLS
497 Platform.runLater(() -> {
498 Notifications.create()
499 .owner(getScene().getWindow())
500 .text(Bundle.ListTimeline_taggedTooltip_error())
501 .showError();
502 });
503 }
504 getEvent().getArtifactID().ifPresent(artifactID -> {
505 //get artifact tags, if there is an artifact associated with the event.
506 try {
507 BlackboardArtifact artifact = sleuthkitCase.getBlackboardArtifact(artifactID);
508 tagsManager.getBlackboardArtifactTagsByArtifact(artifact).stream()
509 .map(tag -> tag.getName().getDisplayName())
510 .forEach(tagNames::add);
511 } catch (TskCoreException ex) {
512 logger.log(Level.SEVERE, "Failed to lookup tags for artifact id " + artifactID, ex); //NON-NLS
513 Platform.runLater(() -> {
514 Notifications.create()
515 .owner(getScene().getWindow())
516 .text(Bundle.ListTimeline_taggedTooltip_error())
517 .showError();
518 });
519 }
520 });
521 Tooltip tooltip = new Tooltip(Bundle.ListTimeline_taggedTooltip_text(String.join("\n", tagNames))); //NON-NLS
522 tooltip.setGraphic(new ImageView(TAG));
523 setTooltip(tooltip);
524 }
525 }
526 }
527
532 private class HashHitCell extends EventTableCell {
533
537 HashHitCell() {
538 setAlignment(Pos.CENTER);
539 }
540
541 @NbBundle.Messages({
542 "ListTimeline.hashHitTooltip.error=There was a problem getting the hash set names for the selected event.",
543 "# {0} - hash set names",
544 "ListTimeline.hashHitTooltip.text=Hash Sets:\n{0}"})
545 @Override
546 protected void updateItem(CombinedEvent item, boolean empty) {
547 super.updateItem(item, empty);
548
549 if (empty || item == null || (getEvent().eventSourceHasHashHits()== false)) {
550 setGraphic(null);
551 setTooltip(null);
552 } else {
553 /*
554 * If the cell is not empty and the event's file is a hash hit,
555 * show the hash hit icon, and show a list of hash set names in
556 * the tooltip
557 */
558 setGraphic(new ImageView(HASH_HIT));
559 try {
560 Set<String> hashSetNames = new TreeSet<>(sleuthkitCase.getContentById(getEvent().getContentObjID()).getHashSetNames());
561 Tooltip tooltip = new Tooltip(Bundle.ListTimeline_hashHitTooltip_text(String.join("\n", hashSetNames))); //NON-NLS
562 tooltip.setGraphic(new ImageView(HASH_HIT));
563 setTooltip(tooltip);
564 } catch (TskCoreException ex) {
565 logger.log(Level.SEVERE, "Failed to lookup hash set names for obj id " + getEvent().getContentObjID(), ex); //NON-NLS
566 Platform.runLater(() -> {
567 Notifications.create()
568 .owner(getScene().getWindow())
569 .text(Bundle.ListTimeline_hashHitTooltip_error())
570 .showError();
571 });
572 }
573 }
574 }
575 }
576
581 private class TextEventTableCell extends EventTableCell {
582
583 private final Function<TimelineEvent, String> textSupplier;
584
591 TextEventTableCell(Function<TimelineEvent, String> textSupplier) {
592 this.textSupplier = textSupplier;
593 setTextOverrun(OverrunStyle.CENTER_ELLIPSIS);
594 setEllipsisString(" ... "); //NON-NLS
595 }
596
597 @Override
598 protected void updateItem(CombinedEvent item, boolean empty) {
599 super.updateItem(item, empty);
600 if (empty || item == null) {
601 setText(null);
602 setTooltip(null);
603 } else {
604 String text = textSupplier.apply(getEvent());
605 setText(text);
606 setTooltip(new Tooltip(text));
607 }
608 }
609 }
610
614 private class EventRow extends TableRow<CombinedEvent> {
615
616 @NbBundle.Messages({
617 "ListChart.errorMsg=There was a problem getting the content for the selected event.",
618 "EventRow.updateItem.errorMessage=Error getting event by id."})
619 @Override
620 protected void updateItem(CombinedEvent item, boolean empty) {
621 CombinedEvent oldItem = getItem();
622 if (oldItem != null) {
623 visibleEvents.remove(oldItem);
624 }
625 super.updateItem(item, empty);
626
627 if (empty || item == null) {
628 setOnContextMenuRequested(ListTimeline::NOOPConsumer);
629 } else {
630 visibleEvents.add(item);
631 setOnContextMenuRequested(contextMenuEvent -> {
632 //make a new context menu on each request in order to include uptodate tag names and hash sets
633 try {
634 EventNode node = EventNode.createEventNode(item.getRepresentativeEventID(), controller.getEventsModel());
635 List<MenuItem> menuItems = new ArrayList<>();
636
637 //for each actions avaialable on node, make a menu item.
638 for (Action action : node.getActions(false)) {
639 if (action == null) {
640 // swing/netbeans uses null action to represent separator in menu
641 menuItems.add(new SeparatorMenuItem());
642 } else {
643 String actionName = Objects.toString(action.getValue(Action.NAME));
644 //for now, suppress properties and tools actions, by ignoring them
645 if (Arrays.asList("&Properties", "Tools").contains(actionName) == false) { //NON-NLS
646 if (action instanceof Presenter.Popup) {
647 /*
648 * If the action is really the root of a
649 * set of actions (eg, tagging). Make a
650 * menu that parallels the action's
651 * menu.
652 */
653 JMenuItem submenu = ((Presenter.Popup) action).getPopupPresenter();
654 menuItems.add(SwingFXMenuUtils.createFXMenu(submenu));
655 } else {
656 menuItems.add(SwingFXMenuUtils.createFXMenu(new Actions.MenuItem(action, false)));
657 }
658 }
659 }
660 }
661
662 //show new context menu.
663 new ContextMenu(menuItems.toArray(new MenuItem[menuItems.size()]))
664 .show(this, contextMenuEvent.getScreenX(), contextMenuEvent.getScreenY());
665 } catch (TskCoreException ex) {
666 logger.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a TimelineEvent.", ex); //NON-NLS
667 Platform.runLater(() -> {
668 Notifications.create()
669 .owner(getScene().getWindow())
670 .text(Bundle.ListChart_errorMsg())
671 .showError();
672 });
673 }
674 });
675 }
676 }
677 }
678
679 public static <X> void NOOPConsumer(X event) {
680 }
681
682 private class ScrollToFirst extends org.controlsfx.control.action.Action {
683
684 ScrollToFirst() {
685 super("", new Consumer<ActionEvent>() { //do not make this a lambda function see issue 2147
686 @Override
687 public void accept(ActionEvent actionEvent) {
688 scrollToAndFocus(0);
689 }
690 });
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("", new Consumer<ActionEvent>() { //do not make this a lambda function see issue 2147
700 @Override
701 public void accept(ActionEvent actionEvent) {
702 scrollToAndFocus(table.getItems().size() - 1);
703 }
704 });
705 setGraphic(new ImageView(LAST));
706 IntegerBinding size = Bindings.size(table.getItems());
707 disabledProperty().bind(size.isEqualTo(0).or(
708 table.getFocusModel().focusedIndexProperty().greaterThanOrEqualTo(size.subtract(1))));
709 }
710 }
711
712 private class ScrollToNext extends org.controlsfx.control.action.Action {
713
714 ScrollToNext() {
715 super("", new Consumer<ActionEvent>() { //do not make this a lambda function see issue 2147
716 @Override
717 public void accept(ActionEvent actionEvent) {
718 ChronoField selectedChronoField = scrollInrementComboBox.getSelectionModel().getSelectedItem();
719 ZoneId timeZoneID = TimeLineController.getTimeZoneID();
720 TemporalUnit selectedUnit = selectedChronoField.getBaseUnit();
721
722 int focusedIndex = table.getFocusModel().getFocusedIndex();
723 CombinedEvent focusedItem = table.getFocusModel().getFocusedItem();
724 if (-1 == focusedIndex || null == focusedItem) {
725 focusedItem = visibleEvents.first();
726 focusedIndex = table.getItems().indexOf(focusedItem);
727 }
728
729 ZonedDateTime focusedDateTime = Instant.ofEpochMilli(focusedItem.getStartMillis()).atZone(timeZoneID);
730 ZonedDateTime nextDateTime = focusedDateTime.plus(1, selectedUnit);//
731 for (ChronoField field : SCROLL_BY_UNITS) {
732 if (field.getBaseUnit().getDuration().compareTo(selectedUnit.getDuration()) < 0) {
733 nextDateTime = nextDateTime.with(field, field.rangeRefinedBy(nextDateTime).getMinimum());//
734 }
735 }
736 long nextMillis = nextDateTime.toInstant().toEpochMilli();
737
738 int nextIndex = table.getItems().size() - 1;
739 for (int i = focusedIndex; i < table.getItems().size(); i++) {
740 if (table.getItems().get(i).getStartMillis() >= nextMillis) {
741 nextIndex = i;
742 break;
743 }
744 }
745 scrollToAndFocus(nextIndex);
746 }
747 });
748 setGraphic(new ImageView(NEXT));
749 IntegerBinding size = Bindings.size(table.getItems());
750 disabledProperty().bind(size.isEqualTo(0).or(
751 table.getFocusModel().focusedIndexProperty().greaterThanOrEqualTo(size.subtract(1))));
752 }
753
754 }
755
756 private class ScrollToPrevious extends org.controlsfx.control.action.Action {
757
758 ScrollToPrevious() {
759 super("", new Consumer<ActionEvent>() { //do not make this a lambda function see issue 2147
760 @Override
761 public void accept(ActionEvent actionEvent) {
762 ZoneId timeZoneID = TimeLineController.getTimeZoneID();
763 ChronoField selectedChronoField = scrollInrementComboBox.getSelectionModel().getSelectedItem();
764 TemporalUnit selectedUnit = selectedChronoField.getBaseUnit();
765
766 int focusedIndex = table.getFocusModel().getFocusedIndex();
767 CombinedEvent focusedItem = table.getFocusModel().getFocusedItem();
768 if (-1 == focusedIndex || null == focusedItem) {
769 focusedItem = visibleEvents.last();
770 focusedIndex = table.getItems().indexOf(focusedItem);
771 }
772
773 ZonedDateTime focusedDateTime = Instant.ofEpochMilli(focusedItem.getStartMillis()).atZone(timeZoneID);
774 ZonedDateTime previousDateTime = focusedDateTime.minus(1, selectedUnit);//
775
776 for (ChronoField field : SCROLL_BY_UNITS) {
777 if (field.getBaseUnit().getDuration().compareTo(selectedUnit.getDuration()) < 0) {
778 previousDateTime = previousDateTime.with(field, field.rangeRefinedBy(previousDateTime).getMaximum());//
779 }
780 }
781 long previousMillis = previousDateTime.toInstant().toEpochMilli();
782
783 int previousIndex = 0;
784 for (int i = focusedIndex; i > 0; i--) {
785 if (table.getItems().get(i).getStartMillis() <= previousMillis) {
786 previousIndex = i;
787 break;
788 }
789 }
790
791 scrollToAndFocus(previousIndex);
792 }
793 });
794 setGraphic(new ImageView(PREVIOUS));
795 disabledProperty().bind(table.getFocusModel().focusedIndexProperty().lessThan(1));
796 }
797 }
798}
static EventNode createEventNode(final Long eventID, EventsModel eventsModel)

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.