Autopsy 4.22.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
DetailViewPane.java
Go to the documentation of this file.
1/*
2 * Autopsy Forensic Browser
3 *
4 * Copyright 2011-2019 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.detailview;
20
21import com.google.common.collect.ImmutableList;
22import java.util.List;
23import java.util.Objects;
24import java.util.function.Function;
25import java.util.function.Predicate;
26import java.util.logging.Level;
27import java.util.stream.Collectors;
28import javafx.application.Platform;
29import javafx.beans.InvalidationListener;
30import javafx.beans.Observable;
31import javafx.collections.ObservableList;
32import javafx.concurrent.Task;
33import javafx.fxml.FXML;
34import javafx.scene.Node;
35import javafx.scene.chart.Axis;
36import javafx.scene.control.Alert;
37import javafx.scene.control.ButtonBar;
38import javafx.scene.control.ButtonType;
39import javafx.scene.control.CheckBox;
40import javafx.scene.control.Label;
41import javafx.scene.control.MenuButton;
42import javafx.scene.control.RadioButton;
43import javafx.scene.control.Slider;
44import javafx.scene.control.ToggleButton;
45import javafx.scene.control.ToggleGroup;
46import javafx.scene.layout.HBox;
47import javafx.stage.Modality;
48import org.apache.commons.lang3.StringUtils;
49import org.controlsfx.control.action.Action;
50import org.joda.time.DateTime;
51import org.joda.time.Interval;
52import org.openide.util.NbBundle;
53import org.sleuthkit.autopsy.coreutils.Logger;
54import org.sleuthkit.autopsy.coreutils.ThreadConfined;
55import org.sleuthkit.autopsy.timeline.FXMLConstructor;
56import org.sleuthkit.autopsy.timeline.EventsModel;
57import org.sleuthkit.autopsy.timeline.TimeLineController;
58import org.sleuthkit.autopsy.timeline.ViewMode;
59import org.sleuthkit.autopsy.timeline.ui.AbstractTimelineChart;
60import org.sleuthkit.autopsy.timeline.ui.detailview.datamodel.DetailViewEvent;
61import org.sleuthkit.autopsy.timeline.ui.detailview.datamodel.DetailsViewModel;
62import org.sleuthkit.autopsy.timeline.ui.detailview.datamodel.EventStripe;
63import org.sleuthkit.autopsy.timeline.utils.MappedList;
64import org.sleuthkit.autopsy.timeline.zooming.EventsModelParams;
65import org.sleuthkit.datamodel.TimelineLevelOfDetail;
66import org.sleuthkit.datamodel.TskCoreException;
67
82final public class DetailViewPane extends AbstractTimelineChart<DateTime, EventStripe, EventNodeBase<?>, DetailsChart> {
83
84 private final static Logger logger = Logger.getLogger(DetailViewPane.class.getName());
85
86 private final DateAxis detailsChartDateAxis = new DateAxis();
87 private final DateAxis pinnedDateAxis = new DateAxis();
88
89 @NbBundle.Messages("DetailViewPane.primaryLaneLabel.text=All Events (Filtered)")
90 private final Axis<EventStripe> verticalAxis = new EventAxis<>(Bundle.DetailViewPane_primaryLaneLabel_text());
91
97
104
111 super(controller);
112 this.detailsViewModel = new DetailsViewModel(getEventsModel());
113 this.selectedEvents = new MappedList<>(getSelectedNodes(), EventNodeBase<?>::getEvent);
114
115 //initialize chart;
117
118 //bind layout fo axes and spacers
119 detailsChartDateAxis.getTickMarks().addListener((Observable observable) -> layoutDateLabels());
120 detailsChartDateAxis.getTickSpacing().addListener(observable -> layoutDateLabels());
121 verticalAxis.setAutoRanging(false); //prevent XYChart.updateAxisRange() from accessing dataSeries on JFX thread causing ConcurrentModificationException
122
123 getSelectedNodes().addListener((Observable observable) -> {
124 //update selected nodes highlight
125 getChart().setHighlightPredicate(getSelectedNodes()::contains);
126
127 try {
128 //update controllers list of selected event ids when view's selection changes.
130 .flatMap(detailNode -> detailNode.getEventIDs().stream())
131 .collect(Collectors.toList()));
132 } catch (TskCoreException ex) {
133 logger.log(Level.SEVERE, "Error selecting nodes.", ex);
134 new Alert(Alert.AlertType.ERROR, "Error selecting nodes").showAndWait();
135 }
136 });
137 }
138
139 /*
140 * Get all the trees of events flattened into a single list, but only
141 * including EventStripes and any leaf SingleEvents, since, EventClusters
142 * contain no interesting non-time related information.
143 */
144 public ObservableList<DetailViewEvent> getAllNestedEvents() {
145 return getChart().getAllNestedEvents();
146 }
147
148 /*
149 * Get a list of the events that are selected in thes view.
150 */
151 public ObservableList<DetailViewEvent> getSelectedEvents() {
152 return selectedEvents;
153 }
154
162 public void setHighLightedEvents(ObservableList<DetailViewEvent> highlightedEvents) {
163 highlightedEvents.addListener((Observable observable) -> {
164 /*
165 * build a predicate that matches events with the same description
166 * as any of the events in highlightedEvents or which are selected
167 */
168 Predicate<EventNodeBase<?>> highlightPredicate
169 = highlightedEvents.stream() // => events
170 .map(DetailViewEvent::getDescription)// => event descriptions
171 .map(new Function<String, Predicate<EventNodeBase<?>>>() {
172 @Override
173 public Predicate<EventNodeBase<?>> apply(String description) {
174 return eventNode -> StringUtils.equalsIgnoreCase(eventNode.getDescription(), description);
175 }
176 })// => predicates that match strings agains the descriptions of the events in highlightedEvents
177 .reduce(getSelectedNodes()::contains, Predicate::or); // => predicate that matches an of the descriptions or selected nodes
178 getChart().setHighlightPredicate(highlightPredicate); //use this predicate to highlight nodes
179 });
180 }
181
182 @Override
183 final protected DateAxis getXAxis() {
185 }
186
195 public Action newUnhideDescriptionAction(String description, TimelineLevelOfDetail descriptionLoD) {
196 return new UnhideDescriptionAction(description, descriptionLoD, getChart());
197 }
198
207 public Action newHideDescriptionAction(String description, TimelineLevelOfDetail descriptionLoD) {
208 return new HideDescriptionAction(description, descriptionLoD, getChart());
209 }
210
212 @Override
213 protected void clearData() {
214 getChart().reset();
215 }
216
217 @Override
218 protected Boolean isTickBold(DateTime value) {
219 return false;
220 }
221
222 @Override
223 final protected Axis<EventStripe> getYAxis() {
224 return verticalAxis;
225 }
226
227 @Override
228 protected double getTickSpacing() {
229 return detailsChartDateAxis.getTickSpacing().get();
230 }
231
232 @Override
233 protected String getTickMarkLabel(DateTime value) {
234 return detailsChartDateAxis.getTickMarkLabel(value);
235 }
236
237 @Override
238 protected Task<Boolean> getNewUpdateTask() {
239 return new DetailsUpdateTask();
240 }
241
242 @Override
243 protected void applySelectionEffect(EventNodeBase<?> c1, Boolean selected) {
244 c1.applySelectionEffect(selected);
245 }
246
247 @Override
248 protected double getAxisMargin() {
249 return 0;
250 }
251
252 @Override
253 final protected ViewMode getViewMode() {
254 return ViewMode.DETAIL;
255 }
256
257 @Override
258 protected ImmutableList<Node> getSettingsControls() {
259 return ImmutableList.copyOf(new DetailViewSettingsPane(getChart().getLayoutSettings()).getChildrenUnmodifiable());
260 }
261
262 @Override
264 return false;
265 }
266
267 @Override
268 protected ImmutableList<Node> getTimeNavigationControls() {
269 return ImmutableList.of();
270 }
271
276 static private class DetailViewSettingsPane extends HBox {
277
278 @FXML
279 private RadioButton hiddenRadio;
280
281 @FXML
282 private RadioButton showRadio;
283
284 @FXML
285 private ToggleGroup descrVisibility;
286
287 @FXML
288 private RadioButton countsRadio;
289
290 @FXML
291 private CheckBox bandByTypeBox;
292
293 @FXML
294 private CheckBox oneEventPerRowBox;
295
296 @FXML
297 private CheckBox truncateAllBox;
298
299 @FXML
300 private Slider truncateWidthSlider;
301
302 @FXML
303 private Label truncateSliderLabel;
304
305 @FXML
307
308 @FXML
309 private ToggleButton pinnedEventsToggle;
310
311 private final DetailsChartLayoutSettings layoutSettings;
312
313 DetailViewSettingsPane(DetailsChartLayoutSettings layoutSettings) {
314 this.layoutSettings = layoutSettings;
315 FXMLConstructor.construct(DetailViewSettingsPane.this, "DetailViewSettingsPane.fxml"); //NON-NLS
316 }
317
318 @NbBundle.Messages({
319 "DetailViewPane.truncateSliderLabel.text=max description width (px):",
320 "DetailViewPane.advancedLayoutOptionsButtonLabel.text=Advanced Layout Options",
321 "DetailViewPane.bandByTypeBox.text=Band by Type",
322 "DetailViewPane.oneEventPerRowBox.text=One Per Row",
323 "DetailViewPane.truncateAllBox.text=Truncate Descriptions",
324 "DetailViewPane.showRadio.text=Show Full Description",
325 "DetailViewPane.countsRadio.text=Show Counts Only",
326 "DetailViewPane.hiddenRadio.text=Hide Description"})
327 @FXML
328 void initialize() {
329 assert bandByTypeBox != null : "fx:id=\"bandByTypeBox\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; //NON-NLS
330 assert oneEventPerRowBox != null : "fx:id=\"oneEventPerRowBox\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; //NON-NLS
331 assert truncateAllBox != null : "fx:id=\"truncateAllBox\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; //NON-NLS
332 assert truncateWidthSlider != null : "fx:id=\"truncateAllSlider\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; //NON-NLS
333 assert pinnedEventsToggle != null : "fx:id=\"pinnedEventsToggle\" was not injected: check your FXML file 'DetailViewSettings.fxml'."; //NON-NLS
334
335 //bind widgets to settings object properties
336 bandByTypeBox.selectedProperty().bindBidirectional(layoutSettings.bandByTypeProperty());
337
338 oneEventPerRowBox.selectedProperty().bindBidirectional(layoutSettings.oneEventPerRowProperty());
339 truncateAllBox.selectedProperty().bindBidirectional(layoutSettings.truncateAllProperty());
340 truncateSliderLabel.disableProperty().bind(truncateAllBox.selectedProperty().not());
341 pinnedEventsToggle.selectedProperty().bindBidirectional(layoutSettings.pinnedLaneShowing());
342
343 final InvalidationListener sliderListener = observable -> {
344 if (truncateWidthSlider.isValueChanging() == false) {
345 layoutSettings.truncateWidthProperty().set(truncateWidthSlider.getValue());
346 }
347 };
348 truncateWidthSlider.valueProperty().addListener(sliderListener);
349 truncateWidthSlider.valueChangingProperty().addListener(sliderListener);
350
351 descrVisibility.selectedToggleProperty().addListener((observable, oldToggle, newToggle) -> {
352 if (newToggle == countsRadio) {
353 layoutSettings.descrVisibilityProperty().set(DescriptionVisibility.COUNT_ONLY);
354 } else if (newToggle == showRadio) {
355 layoutSettings.descrVisibilityProperty().set(DescriptionVisibility.SHOWN);
356 } else if (newToggle == hiddenRadio) {
357 layoutSettings.descrVisibilityProperty().set(DescriptionVisibility.HIDDEN);
358 }
359 });
360
361 //Assign localized labels
362 truncateSliderLabel.setText(Bundle.DetailViewPane_truncateSliderLabel_text());
363 advancedLayoutOptionsButtonLabel.setText(Bundle.DetailViewPane_advancedLayoutOptionsButtonLabel_text());
364 bandByTypeBox.setText(Bundle.DetailViewPane_bandByTypeBox_text());
365 oneEventPerRowBox.setText(Bundle.DetailViewPane_oneEventPerRowBox_text());
366 truncateAllBox.setText(Bundle.DetailViewPane_truncateAllBox_text());
367 showRadio.setText(Bundle.DetailViewPane_showRadio_text());
368 countsRadio.setText(Bundle.DetailViewPane_countsRadio_text());
369 hiddenRadio.setText(Bundle.DetailViewPane_hiddenRadio_text());
370 }
371 }
372
373 @NbBundle.Messages({
374 "DetailViewPane.loggedTask.queryDb=Retrieving event data",
375 "DetailViewPane.loggedTask.name=Updating Details View",
376 "DetailViewPane.loggedTask.updateUI=Populating view",
377 "DetailViewPane.loggedTask.continueButton=Continue",
378 "DetailViewPane.loggedTask.backButton=Back (Cancel)",
379 "# {0} - number of events",
380 "DetailViewPane.loggedTask.prompt=You are about to show details for {0} events. This might be very slow and could exhaust available memory.\n\nDo you want to continue?"})
381 private class DetailsUpdateTask extends ViewRefreshTask<Interval> {
382
383 DetailsUpdateTask() {
384 super(Bundle.DetailViewPane_loggedTask_name(), true);
385 }
386
387 @Override
388 protected Boolean call() throws Exception {
389 super.call();
390
391 if (isCancelled()) {
392 return null;
393 }
394 EventsModel eventsModel = getEventsModel();
395 EventsModelParams newZoom = eventsModel.getModelParams();
396
397 //If the view doesn't need refreshing or if the ZoomState hasn't actually changed, just bail
398 if (needsRefresh() == false && Objects.equals(currentZoom, newZoom)) {
399 return true;
400 }
401
402 updateMessage(Bundle.DetailViewPane_loggedTask_queryDb());
403
404 //get the event stripes to be displayed
405 List<EventStripe> eventStripes = detailsViewModel.getEventStripes(newZoom);
406 final int size = eventStripes.size();
407 //if there are too many stipes show a confirmation dialog
408 if (size > 2000) {
409 Task<ButtonType> task = new Task<ButtonType>() {
410 @Override
411 protected ButtonType call() throws Exception {
412 ButtonType ContinueButtonType = new ButtonType(Bundle.DetailViewPane_loggedTask_continueButton(), ButtonBar.ButtonData.OK_DONE);
413 ButtonType back = new ButtonType(Bundle.DetailViewPane_loggedTask_backButton(), ButtonBar.ButtonData.CANCEL_CLOSE);
414
415 Alert alert = new Alert(Alert.AlertType.WARNING, Bundle.DetailViewPane_loggedTask_prompt(size), ContinueButtonType, back);
416 alert.setHeaderText("");
417 alert.initModality(Modality.APPLICATION_MODAL);
418 alert.initOwner(getScene().getWindow());
419 ButtonType userResponse = alert.showAndWait().orElse(back);
420 if (userResponse == back) {
421 DetailsUpdateTask.this.cancel();
422 }
423 return userResponse;
424 }
425 };
426 //show dialog on JFX thread and block this thread until the dialog is dismissed.
427 Platform.runLater(task);
428 task.get();
429 }
430 if (isCancelled()) {
431 return null;
432 }
433 //we are going to accept the new zoom
434 currentZoom = newZoom;
435
436 //clear the chart and set the horixontal axis
437 resetView(eventsModel.getTimeRange());
438
439 updateMessage(Bundle.DetailViewPane_loggedTask_updateUI());
440
441 //add all the stripes
442 for (int i = 0; i < size; i++) {
443 if (isCancelled()) {
444 return null;
445 }
446 updateProgress(i, size);
447 final EventStripe stripe = eventStripes.get(i);
448 Platform.runLater(() -> getChart().addStripe(stripe));
449 }
450
451 return eventStripes.isEmpty() == false;
452 }
453
454 @Override
455 protected void cancelled() {
456 super.cancelled();
458 }
459
460 @Override
461 protected void setDateValues(Interval timeRange) {
462 detailsChartDateAxis.setRange(timeRange, true);
463 pinnedDateAxis.setRange(timeRange, true);
464 }
465
466 @Override
467 protected void succeeded() {
468 super.succeeded();
470 }
471 }
472}
synchronized static Logger getLogger(String name)
Definition Logger.java:124
synchronized EventsModelParams getModelParams()
static void construct(Node node, String fxmlFileName)
final synchronized void selectEventIDs(Collection< Long > eventIDs)
Action newHideDescriptionAction(String description, TimelineLevelOfDetail descriptionLoD)
final MappedList< DetailViewEvent, EventNodeBase<?> > selectedEvents
Action newUnhideDescriptionAction(String description, TimelineLevelOfDetail descriptionLoD)
void setHighLightedEvents(ObservableList< DetailViewEvent > highlightedEvents)
void applySelectionEffect(EventNodeBase<?> c1, Boolean selected)

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