19 package org.sleuthkit.autopsy.timeline.ui.detailview;
21 import java.util.ArrayList;
22 import java.util.List;
23 import javafx.application.Platform;
24 import javafx.beans.InvalidationListener;
25 import javafx.beans.Observable;
26 import javafx.collections.FXCollections;
27 import javafx.collections.ListChangeListener;
28 import javafx.collections.ObservableList;
29 import javafx.concurrent.Task;
30 import javafx.fxml.FXML;
31 import javafx.geometry.Orientation;
32 import javafx.scene.chart.Axis;
33 import javafx.scene.chart.XYChart;
34 import javafx.scene.control.Alert;
35 import javafx.scene.control.ButtonBar;
36 import javafx.scene.control.ButtonType;
37 import javafx.scene.control.CheckBox;
38 import javafx.scene.control.CustomMenuItem;
39 import javafx.scene.control.Label;
40 import javafx.scene.control.MenuButton;
41 import javafx.scene.control.MultipleSelectionModel;
42 import javafx.scene.control.RadioButton;
43 import javafx.scene.control.ScrollBar;
44 import javafx.scene.control.SeparatorMenuItem;
45 import javafx.scene.control.Slider;
46 import javafx.scene.control.ToggleGroup;
47 import javafx.scene.control.TreeItem;
48 import javafx.scene.effect.Effect;
49 import static javafx.scene.input.KeyCode.DOWN;
50 import static javafx.scene.input.KeyCode.KP_DOWN;
51 import static javafx.scene.input.KeyCode.KP_UP;
52 import static javafx.scene.input.KeyCode.PAGE_DOWN;
53 import static javafx.scene.input.KeyCode.PAGE_UP;
54 import static javafx.scene.input.KeyCode.UP;
55 import javafx.scene.input.KeyEvent;
56 import javafx.scene.layout.HBox;
57 import javafx.scene.layout.Pane;
58 import javafx.scene.layout.Priority;
59 import javafx.scene.layout.Region;
60 import javafx.scene.layout.VBox;
61 import javafx.stage.Modality;
62 import org.controlsfx.control.action.Action;
63 import org.joda.time.DateTime;
64 import org.joda.time.Interval;
65 import org.openide.util.NbBundle;
94 private final DateAxis
dateAxis =
new DateAxis();
100 private final ObservableList<EventBundleNodeBase<?, ?, ?>>
highlightedNodes = FXCollections.synchronizedObservableList(FXCollections.observableArrayList());
103 return chart.getEventStripes();
109 s.getData().forEach(chart::removeDataItem);
112 Platform.runLater(() -> {
113 vertScrollBar.setValue(0);
119 super(controller, partPane, contextPane, bottomLeftSpacer);
128 dateAxis.setTickLabelGap(0);
129 dateAxis.setAutoRanging(
false);
130 dateAxis.setTickLabelsVisible(
false);
131 dateAxis.getTickMarks().addListener((Observable observable) ->
layoutDateLabels());
134 verticalAxis.setAutoRanging(
false);
135 bottomLeftSpacer.minWidthProperty().bind(verticalAxis.widthProperty().add(verticalAxis.tickLengthProperty()));
136 bottomLeftSpacer.prefWidthProperty().bind(verticalAxis.widthProperty().add(verticalAxis.tickLengthProperty()));
137 bottomLeftSpacer.maxWidthProperty().bind(verticalAxis.widthProperty().add(verticalAxis.tickLengthProperty()));
139 scrollBarSpacer.minHeightProperty().bind(dateAxis.heightProperty());
142 vertScrollBar.setOrientation(Orientation.VERTICAL);
143 vertScrollBar.maxProperty().bind(
chart.maxVScrollProperty().subtract(
chart.heightProperty()));
144 vertScrollBar.visibleAmountProperty().bind(
chart.heightProperty());
145 vertScrollBar.visibleProperty().bind(vertScrollBar.visibleAmountProperty().greaterThanOrEqualTo(0));
146 VBox.setVgrow(vertScrollBar, Priority.ALWAYS);
147 setRight(
new VBox(vertScrollBar, scrollBarSpacer));
150 this.setOnScroll(scrollEvent ->
151 vertScrollBar.valueProperty().set(
clampScroll(vertScrollBar.getValue() - scrollEvent.getDeltaY())));
154 setOnMouseClicked(mouseEvent -> requestFocus());
157 this.setOnKeyPressed((KeyEvent t) -> {
158 switch (t.getCode()) {
181 this.vertScrollBar.valueProperty().addListener(observable ->
chart.setVScroll(vertScrollBar.getValue()));
185 while (change.next()) {
186 change.getAddedSubList().forEach(node -> {
187 node.applyHighlightEffect(true);
189 change.getRemoved().forEach(node -> {
190 node.applyHighlightEffect(false);
196 highlightedNodes.clear();
199 t.getDescription().equals(tn.getDescription()))) {
200 highlightedNodes.add(n);
207 vertScrollBar.valueProperty().set(clampScroll(vertScrollBar.getValue() + factor * chart.getHeight()));
211 return Math.max(0, Math.min(vertScrollBar.getMax() + 50, value));
215 this.treeSelectionModel = selectionModel;
217 treeSelectionModel.getSelectedItems().addListener((Observable observable) -> {
218 highlightedNodes.clear();
219 for (TreeItem<
EventBundle<?>> tn : treeSelectionModel.getSelectedItems()) {
222 t.getDescription().equals(tn.getValue().getDescription()))) {
223 highlightedNodes.add(n);
246 return dateAxis.getTickSpacing().get();
251 return dateAxis.getTickMarkLabel(value);
331 assert bandByTypeBox != null :
"fx:id=\"bandByTypeBox\" was not injected: check your FXML file 'DetailViewSettings.fxml'.";
332 assert oneEventPerRowBox != null :
"fx:id=\"oneEventPerRowBox\" was not injected: check your FXML file 'DetailViewSettings.fxml'.";
333 assert truncateAllBox != null :
"fx:id=\"truncateAllBox\" was not injected: check your FXML file 'DetailViewSettings.fxml'.";
334 assert truncateWidthSlider != null :
"fx:id=\"truncateAllSlider\" was not injected: check your FXML file 'DetailViewSettings.fxml'.";
335 bandByTypeBox.selectedProperty().bindBidirectional(chart.bandByTypeProperty());
336 truncateAllBox.selectedProperty().bindBidirectional(chart.truncateAllProperty());
337 oneEventPerRowBox.selectedProperty().bindBidirectional(chart.oneEventPerRowProperty());
338 truncateSliderLabel.disableProperty().bind(truncateAllBox.selectedProperty().not());
339 truncateSliderLabel.setText(NbBundle.getMessage(
DetailViewPane.class,
"DetailViewPane.truncateSliderLabel.text"));
340 final InvalidationListener sliderListener = o -> {
341 if (truncateWidthSlider.isValueChanging() ==
false) {
342 chart.getTruncateWidth().set(truncateWidthSlider.getValue());
345 truncateWidthSlider.valueProperty().addListener(sliderListener);
346 truncateWidthSlider.valueChangingProperty().addListener(sliderListener);
348 descrVisibility.selectedToggleProperty().addListener((observable, oldToggle, newToggle) -> {
349 if (newToggle == countsRadio) {
350 chart.descrVisibilityProperty().set(DescriptionVisibility.COUNT_ONLY);
351 }
else if (newToggle == showRadio) {
352 chart.descrVisibilityProperty().set(DescriptionVisibility.SHOWN);
353 }
else if (newToggle == hiddenRadio) {
354 chart.descrVisibilityProperty().set(DescriptionVisibility.HIDDEN);
358 advancedLayoutOptionsButtonLabel.setText(
359 NbBundle.getMessage(DetailViewPane.class,
"DetailViewPane.advancedLayoutOptionsButtonLabel.text"));
360 bandByTypeBox.setText(NbBundle.getMessage(DetailViewPane.class,
"DetailViewPane.bandByTypeBox.text"));
361 bandByTypeBoxMenuItem.setText(
362 NbBundle.getMessage(DetailViewPane.class,
"DetailViewPane.bandByTypeBoxMenuItem.text"));
363 oneEventPerRowBox.setText(NbBundle.getMessage(DetailViewPane.class,
"DetailViewPane.oneEventPerRowBox.text"));
364 oneEventPerRowBoxMenuItem.setText(
365 NbBundle.getMessage(DetailViewPane.class,
"DetailViewPane.oneEventPerRowBoxMenuItem.text"));
366 truncateAllBox.setText(NbBundle.getMessage(DetailViewPane.class,
"DetailViewPan.truncateAllBox.text"));
367 truncateAllBoxMenuItem.setText(
368 NbBundle.getMessage(DetailViewPane.class,
"DetailViewPan.truncateAllBoxMenuItem.text"));
369 truncateSliderLabelMenuItem.setText(
370 NbBundle.getMessage(DetailViewPane.class,
"DetailViewPane.truncateSlideLabelMenuItem.text"));
371 descVisibilitySeparatorMenuItem.setText(
372 NbBundle.getMessage(DetailViewPane.class,
"DetailViewPane.descVisSeparatorMenuItem.text"));
373 showRadioMenuItem.setText(NbBundle.getMessage(DetailViewPane.class,
"DetailViewPane.showRadioMenuItem.text"));
374 showRadio.setText(NbBundle.getMessage(DetailViewPane.class,
"DetailViewPane.showRadio.text"));
375 countsRadioMenuItem.setText(NbBundle.getMessage(DetailViewPane.class,
"DetailViewPane.countsRadioMenuItem.text"));
376 countsRadio.setText(NbBundle.getMessage(DetailViewPane.class,
"DetailViewPane.countsRadio.text"));
377 hiddenRadioMenuItem.setText(NbBundle.getMessage(DetailViewPane.class,
"DetailViewPane.hiddenRadioMenuItem.text"));
378 hiddenRadio.setText(NbBundle.getMessage(DetailViewPane.class,
"DetailViewPane.hiddenRadio.text"));
383 return chart.new UnhideDescriptionAction(description, descriptionLoD);
387 return chart.new HideDescriptionAction(description, descriptionLoD);
391 "DetailViewPane.loggedTask.queryDb=Retreiving event data",
392 "DetailViewPane.loggedTask.name=Updating Details View",
393 "DetailViewPane.loggedTask.updateUI=Populating visualization",
394 "DetailViewPane.loggedTask.continueButton=Continue",
395 "DetailViewPane.loggedTask.backButton=Back (Cancel)",
396 "# {0} - number of events",
397 "DetailViewPane.loggedTask.prompt=You are about to show details for {0} events. This might be very slow or even crash Autopsy.\n\nDo you want to continue?"})
401 super(Bundle.DetailViewPane_loggedTask_name(),
true);
405 protected Boolean
call() throws Exception {
411 resetChart(getTimeRange());
413 updateMessage(Bundle.DetailViewPane_loggedTask_queryDb());
414 List<EventStripe> eventStripes = filteredEvents.getEventStripes();
415 if (eventStripes.size() > 2000) {
416 Task<ButtonType> task =
new Task<ButtonType>() {
419 protected ButtonType call()
throws Exception {
420 ButtonType ContinueButtonType =
new ButtonType(Bundle.DetailViewPane_loggedTask_continueButton(), ButtonBar.ButtonData.OK_DONE);
421 ButtonType back =
new ButtonType(Bundle.DetailViewPane_loggedTask_backButton(), ButtonBar.ButtonData.CANCEL_CLOSE);
423 Alert alert =
new Alert(Alert.AlertType.WARNING, Bundle.DetailViewPane_loggedTask_prompt(eventStripes.size()), ContinueButtonType, back);
424 alert.setHeaderText(
"");
425 alert.initModality(Modality.APPLICATION_MODAL);
426 alert.initOwner(getScene().getWindow());
427 ButtonType orElse = alert.showAndWait().orElse(back);
428 if (orElse == back) {
434 Platform.runLater(task);
435 ButtonType
get = task.get();
437 updateMessage(Bundle.DetailViewPane_loggedTask_updateUI());
438 final int size = eventStripes.size();
439 for (
int i = 0; i < size; i++) {
443 updateProgress(i, size);
445 final XYChart.Data<DateTime,
EventStripe> dataItem =
new XYChart.Data<>(
new DateTime(cluster.
getStartMillis()), cluster);
446 getSeries(cluster.
getEventType()).getData().add(dataItem);
447 chart.addDataItem(dataItem);
450 return eventStripes.isEmpty() ==
false;
456 controller.retreat();
461 dateAxis.setRange(timeRange,
true);
Action newHideDescriptionAction(String description, DescriptionLoD descriptionLoD)
void setDateAxisValues(Interval timeRange)
final ObservableList< NodeType > selectedNodes
SeparatorMenuItem descVisibilitySeparatorMenuItem
void setChartClickHandler()
Double clampScroll(Double value)
CustomMenuItem showRadioMenuItem
void applySelectionEffect(boolean applied)
final ObservableList< EventBundleNodeBase<?,?,?> > highlightedNodes
static final double LINE_SCROLL_PERCENTAGE
final ObservableList< XYChart.Series< X, Y > > dataSeries
final Region scrollBarSpacer
static final double PAGE_SCROLL_PERCENTAGE
DetailViewPane(TimeLineController controller, Pane partPane, Pane contextPane, Region bottomLeftSpacer)
CustomMenuItem truncateSliderLabelMenuItem
List< Node > settingsNodes
Effect getSelectionEffect()
synchronized void layoutDateLabels()
ToggleGroup descrVisibility
final TimeLineController controller
MenuButton advancedLayoutOptionsButtonLabel
CustomMenuItem hiddenRadioMenuItem
Boolean isTickBold(DateTime value)
CustomMenuItem bandByTypeBoxMenuItem
void applySelectionEffect(EventBundleNodeBase<?,?,?> c1, Boolean selected)
Label truncateSliderLabel
CustomMenuItem countsRadioMenuItem
String getTickMarkLabel(DateTime value)
ObservableList< EventStripe > getEventStripes()
MultipleSelectionModel< TreeItem< EventBundle<?> > > treeSelectionModel
Axis< DateTime > getXAxis()
CheckBox oneEventPerRowBox
CustomMenuItem truncateAllBoxMenuItem
synchronized static Logger getLogger(String name)
CustomMenuItem oneEventPerRowBoxMenuItem
Task< Boolean > getUpdateTask()
static final Logger LOGGER
Axis< EventStripe > getYAxis()
final ScrollBar vertScrollBar
static void construct(Node node, String fxmlFileName)
void incrementScrollValue(double factor)
void setSelectionModel(MultipleSelectionModel< TreeItem< EventBundle<?>>> selectionModel)
final Axis< EventStripe > verticalAxis
Slider truncateWidthSlider
Action newUnhideDescriptionAction(String description, DescriptionLoD descriptionLoD)