19 package org.sleuthkit.autopsy.timeline.ui.detailview;
21 import java.util.Arrays;
22 import java.util.MissingResourceException;
23 import java.util.function.Predicate;
24 import javafx.beans.property.SimpleObjectProperty;
25 import javafx.collections.FXCollections;
26 import javafx.collections.ObservableList;
27 import javafx.collections.ObservableSet;
28 import javafx.collections.SetChangeListener;
29 import javafx.geometry.Side;
30 import javafx.scene.chart.Axis;
31 import javafx.scene.control.ContextMenu;
32 import javafx.scene.control.Control;
33 import javafx.scene.control.Skin;
34 import javafx.scene.control.SkinBase;
35 import javafx.scene.image.Image;
36 import javafx.scene.image.ImageView;
37 import javafx.scene.input.MouseEvent;
38 import javafx.scene.layout.Pane;
39 import org.controlsfx.control.MasterDetailPane;
40 import org.controlsfx.control.action.Action;
41 import org.controlsfx.control.action.ActionUtils;
42 import org.joda.time.DateTime;
43 import org.joda.time.Interval;
44 import org.openide.util.NbBundle;
56 final class DetailsChart
extends Control implements TimeLineChart<DateTime> {
59 private final DateAxis detailsChartDateAxis;
60 private final DateAxis pinnedDateAxis;
61 private final Axis<EventStripe> verticalAxis;
66 private final SimpleObjectProperty<IntervalSelector<? extends DateTime>> intervalSelectorProp =
new SimpleObjectProperty<>();
71 private final ObservableSet<GuideLine> guideLines = FXCollections.observableSet();
79 private final SimpleObjectProperty<Predicate<EventNodeBase<?>>> highlightPredicate =
new SimpleObjectProperty<>((x) ->
false);
84 private final ObservableList<EventNodeBase<?>> selectedNodes;
91 private final ObservableList<TimeLineEvent> nestedEvents = FXCollections.observableArrayList();
97 private final DetailsChartLayoutSettings layoutSettings;
102 private final TimeLineController controller;
107 @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
108 private final ObservableList<EventStripe> rootEventStripes = FXCollections.observableArrayList();
124 DetailsChart(TimeLineController controller, DateAxis detailsChartDateAxis, DateAxis pinnedDateAxis, Axis<EventStripe> verticalAxis, ObservableList<EventNodeBase<?>> selectedNodes) {
125 this.controller = controller;
126 this.layoutSettings =
new DetailsChartLayoutSettings(controller);
127 this.detailsChartDateAxis = detailsChartDateAxis;
128 this.verticalAxis = verticalAxis;
129 this.pinnedDateAxis = pinnedDateAxis;
130 this.selectedNodes = selectedNodes;
132 FilteredEventsModel eventsModel = getController().getEventsModel();
138 eventsModel.timeRangeProperty().addListener(o -> clearTimeBasedUIElements());
141 eventsModel.zoomParametersProperty().addListener(o -> getSelectedNodes().clear());
152 DateTime getDateTimeForPosition(
double xPos) {
153 return getXAxis().getValueForDisplay(getXAxis().parentToLocal(xPos, 0).getX());
161 @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
162 void addStripe(EventStripe stripe) {
163 rootEventStripes.add(stripe);
164 nestedEvents.add(stripe);
172 void clearGuideLine(GuideLine guideLine) {
173 guideLines.remove(guideLine);
177 public ObservableList<EventNodeBase<?>> getSelectedNodes() {
178 return selectedNodes;
186 DetailsChartLayoutSettings getLayoutSettings() {
187 return layoutSettings;
199 void setHighlightPredicate(Predicate<
EventNodeBase<?>> highlightPredicate) {
200 this.highlightPredicate.set(highlightPredicate);
206 @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
208 rootEventStripes.clear();
209 nestedEvents.clear();
215 public ObservableList<TimeLineEvent> getAllNestedEvents() {
223 private void clearTimeBasedUIElements() {
225 clearIntervalSelector();
229 public void clearIntervalSelector() {
230 intervalSelectorProp.set(null);
234 public IntervalSelector<DateTime> newIntervalSelector() {
235 return new DetailIntervalSelector(
this);
239 public IntervalSelector<? extends DateTime> getIntervalSelector() {
240 return intervalSelectorProp.get();
244 public void setIntervalSelector(IntervalSelector<? extends DateTime> newIntervalSelector) {
245 intervalSelectorProp.set(newIntervalSelector);
249 public Axis<DateTime> getXAxis() {
250 return detailsChartDateAxis;
254 public TimeLineController getController() {
259 public void clearContextMenu() {
260 setContextMenu(null);
264 public ContextMenu getContextMenu(MouseEvent mouseEvent)
throws MissingResourceException {
266 ContextMenu contextMenu = getContextMenu();
267 if (contextMenu != null) {
272 setContextMenu(ActionUtils.createContextMenu(Arrays.asList(
273 new PlaceMarkerAction(
this, mouseEvent),
274 ActionUtils.ACTION_SEPARATOR,
275 TimeLineChart.newZoomHistoyActionGroup(getController())
277 return getContextMenu();
281 protected Skin<?> createDefaultSkin() {
282 return new DetailsChartSkin(
this);
290 ObservableList<EventStripe> getRootEventStripes() {
291 return rootEventStripes;
329 private static final Image
MARKER =
new Image(
"/org/sleuthkit/autopsy/timeline/images/marker.png", 16, 16,
true,
true,
true);
332 @NbBundle.Messages({
"PlaceMArkerAction.name=Place Marker"})
334 super(Bundle.PlaceMArkerAction_name());
336 setGraphic(
new ImageView(MARKER));
337 setEventHandler(actionEvent -> {
338 if (guideLine == null) {
339 guideLine =
new GuideLine(chart);
340 guideLine.relocate(chart.sceneToLocal(clickEvent.getSceneX(), 0).getX(), 0);
341 chart.guideLines.add(guideLine);
344 guideLine.relocate(chart.sceneToLocal(clickEvent.getSceneX(), 0).getX(), 0);
402 @NbBundle.Messages(
"DetailViewPane.pinnedLaneLabel.text=Pinned Events")
406 primaryLane =
new PrimaryDetailsChartLane(chart, getSkinnable().detailsChartDateAxis, getSkinnable().verticalAxis);
407 primaryView =
new ScrollingLaneWrapper(primaryLane);
408 pinnedLane =
new PinnedEventsChartLane(chart, getSkinnable().pinnedDateAxis,
new EventAxis<>(Bundle.DetailViewPane_pinnedLaneLabel_text()));
409 pinnedView =
new ScrollingLaneWrapper(pinnedLane);
411 pinnedLane.setMinHeight(MIN_PINNED_LANE_HEIGHT);
412 pinnedLane.maxVScrollProperty().addListener(maxVScroll ->
syncPinnedHeight());
415 masterDetailPane =
new MasterDetailPane(Side.TOP, primaryView, pinnedView,
false);
416 masterDetailPane.setDividerPosition(dividerPosition);
417 masterDetailPane.prefHeightProperty().bind(getSkinnable().heightProperty());
418 masterDetailPane.prefWidthProperty().bind(getSkinnable().widthProperty());
419 rootPane =
new Pane(masterDetailPane);
420 getChildren().add(rootPane);
423 getSkinnable().highlightPredicate.addListener((observable, oldPredicate, newPredicate) -> {
424 primaryLane.getAllNodes().forEach(primaryNode -> primaryNode.applyHighlightEffect(newPredicate.test(primaryNode)));
425 pinnedLane.getAllNodes().forEach(pinnedNode -> pinnedNode.applyHighlightEffect(newPredicate.test(pinnedNode)));
435 getSkinnable().getLayoutSettings().pinnedLaneShowing().addListener(observable ->
syncPinnedLaneShowing());
439 getSkinnable().intervalSelectorProp.addListener((observable, oldIntervalSelector, newIntervalSelector) -> {
440 rootPane.getChildren().remove(oldIntervalSelector);
441 if (null != newIntervalSelector) {
442 rootPane.getChildren().add(newIntervalSelector);
447 getSkinnable().guideLines.addListener((SetChangeListener.Change<? extends GuideLine> change) -> {
448 if (change.wasRemoved()) {
449 rootPane.getChildren().remove(change.getElementRemoved());
451 if (change.wasAdded()) {
452 rootPane.getChildren().add(change.getElementAdded());
462 pinnedView.setMinHeight(MIN_PINNED_LANE_HEIGHT);
463 pinnedView.setMaxHeight(pinnedLane.maxVScrollProperty().get() + 30);
476 chartLane.setOnMousePressed(chartDragHandler);
477 chartLane.setOnMouseReleased(chartDragHandler);
478 chartLane.setOnMouseDragged(chartDragHandler);
479 chartLane.setOnMouseClicked(chartDragHandler);
480 chartLane.addEventHandler(MouseEvent.MOUSE_CLICKED, mouseClickedHandler);
488 boolean pinnedLaneShowing = getSkinnable().getLayoutSettings().isPinnedLaneShowing();
489 if (pinnedLaneShowing ==
false) {
491 dividerPosition = masterDetailPane.getDividerPosition();
494 masterDetailPane.setShowDetailNode(pinnedLaneShowing);
496 if (pinnedLaneShowing) {
499 masterDetailPane.setDividerPosition(dividerPosition);
final MasterDetailPane masterDetailPane
static final int MIN_PINNED_LANE_HEIGHT
final ScrollingLaneWrapper pinnedView
final ScrollingLaneWrapper primaryView
static void configureMouseListeners(final DetailsChartLane<?> chartLane, final TimeLineChart.MouseClickedHandler< DateTime, DetailsChart > mouseClickedHandler, final TimeLineChart.ChartDragHandler< DateTime, DetailsChart > chartDragHandler)
Interval adjustInterval(Interval i)
void syncPinnedLaneShowing()
DateTime parseDateTime(DateTime date)
final PinnedEventsChartLane pinnedLane
String formatSpan(DateTime date)
static final Image MARKER
final IntervalSelectorProvider< X > chart
static DateTimeFormatter getZonedFormatter()
final PrimaryDetailsChartLane primaryLane
void setIntervalSelector(IntervalSelector<?extends X > newIntervalSelector)