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;
58 final class DetailsChart
extends Control implements TimeLineChart<DateTime> {
61 private final DateAxis detailsChartDateAxis;
62 private final DateAxis pinnedDateAxis;
63 private final Axis<EventStripe> verticalAxis;
68 private final SimpleObjectProperty<IntervalSelector<? extends DateTime>> intervalSelectorProp =
new SimpleObjectProperty<>();
73 private final ObservableSet<GuideLine> guideLines = FXCollections.observableSet();
81 private final SimpleObjectProperty<Predicate<EventNodeBase<?>>> highlightPredicate =
new SimpleObjectProperty<>(dummy ->
false);
86 private final ObservableList<EventNodeBase<?>> selectedNodes;
93 private final ObservableList<DetailViewEvent> nestedEvents = FXCollections.observableArrayList();
99 private final DetailsChartLayoutSettings layoutSettings;
100 private final DetailsViewModel detailsViewModel;
102 DetailsViewModel getDetailsViewModel() {
103 return detailsViewModel;
109 private final TimeLineController controller;
114 @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
115 private final ObservableList<EventStripe> rootEventStripes = FXCollections.observableArrayList();
132 DetailsChart(DetailsViewModel detailsViewModel, TimeLineController controller, DateAxis detailsChartDateAxis, DateAxis pinnedDateAxis, Axis<EventStripe> verticalAxis, ObservableList<EventNodeBase<?>> selectedNodes) {
133 this.detailsViewModel = detailsViewModel;
134 this.controller = controller;
135 this.layoutSettings =
new DetailsChartLayoutSettings(controller);
136 this.detailsChartDateAxis = detailsChartDateAxis;
137 this.verticalAxis = verticalAxis;
138 this.pinnedDateAxis = pinnedDateAxis;
139 this.selectedNodes = selectedNodes;
141 EventsModel eventsModel = getController().getEventsModel();
147 eventsModel.timeRangeProperty().addListener(observable -> clearTimeBasedUIElements());
150 eventsModel.modelParamsProperty().addListener(observable -> getSelectedNodes().clear());
161 DateTime getDateTimeForPosition(
double xPos) {
162 return getXAxis().getValueForDisplay(getXAxis().parentToLocal(xPos, 0).getX());
170 @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
171 void addStripe(EventStripe stripe) {
172 rootEventStripes.add(stripe);
173 nestedEvents.add(stripe);
181 void clearGuideLine(GuideLine guideLine) {
182 guideLines.remove(guideLine);
186 public ObservableList<EventNodeBase<?>> getSelectedNodes() {
187 return selectedNodes;
195 DetailsChartLayoutSettings getLayoutSettings() {
196 return layoutSettings;
208 void setHighlightPredicate(Predicate<
EventNodeBase<?>> highlightPredicate) {
209 this.highlightPredicate.set(highlightPredicate);
215 @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
217 rootEventStripes.clear();
218 nestedEvents.clear();
224 public ObservableList<DetailViewEvent> getAllNestedEvents() {
232 private void clearTimeBasedUIElements() {
234 clearIntervalSelector();
238 public void clearIntervalSelector() {
239 intervalSelectorProp.set(null);
243 public IntervalSelector<DateTime> newIntervalSelector() {
244 return new DetailIntervalSelector(
this);
248 public IntervalSelector<? extends DateTime> getIntervalSelector() {
249 return intervalSelectorProp.get();
253 public void setIntervalSelector(IntervalSelector<? extends DateTime> newIntervalSelector) {
254 intervalSelectorProp.set(newIntervalSelector);
258 public Axis<DateTime> getXAxis() {
259 return detailsChartDateAxis;
263 public TimeLineController getController() {
268 public void clearContextMenu() {
269 setContextMenu(null);
273 public ContextMenu getContextMenu(MouseEvent mouseEvent)
throws MissingResourceException {
275 ContextMenu contextMenu = getContextMenu();
276 if (contextMenu != null) {
280 long selectedTimeMillis = getXAxis().getValueForDisplay(getXAxis().parentToLocal(mouseEvent.getX(), 0).getX()).getMillis();
283 setContextMenu(ActionUtils.createContextMenu(Arrays.asList(
284 new PlaceMarkerAction(
this, mouseEvent),
285 new AddManualEvent(controller, selectedTimeMillis),
286 ActionUtils.ACTION_SEPARATOR,
287 TimeLineChart.newZoomHistoyActionGroup(getController())
289 return getContextMenu();
293 protected Skin<?> createDefaultSkin() {
294 return new DetailsChartSkin(
this);
302 ObservableList<EventStripe> getRootEventStripes() {
303 return rootEventStripes;
341 private static final Image
MARKER =
new Image(
"/org/sleuthkit/autopsy/timeline/images/marker.png", 16, 16,
true,
true,
true);
344 @NbBundle.Messages({
"PlaceMArkerAction.name=Place Marker"})
346 super(Bundle.PlaceMArkerAction_name());
348 setGraphic(
new ImageView(MARKER));
349 setEventHandler(actionEvent -> {
350 if (guideLine == null) {
351 guideLine =
new GuideLine(chart);
352 guideLine.relocate(chart.sceneToLocal(clickEvent.getSceneX(), 0).getX(), 0);
353 chart.guideLines.add(guideLine);
356 guideLine.relocate(chart.sceneToLocal(clickEvent.getSceneX(), 0).getX(), 0);
414 @NbBundle.Messages(
"DetailViewPane.pinnedLaneLabel.text=Pinned Events")
418 primaryLane =
new PrimaryDetailsChartLane(chart, getSkinnable().detailsChartDateAxis, getSkinnable().verticalAxis);
419 primaryView =
new ScrollingLaneWrapper(primaryLane);
420 pinnedLane =
new PinnedEventsChartLane(chart, getSkinnable().pinnedDateAxis,
new EventAxis<>(Bundle.DetailViewPane_pinnedLaneLabel_text()));
421 pinnedView =
new ScrollingLaneWrapper(pinnedLane);
423 pinnedLane.setMinHeight(MIN_PINNED_LANE_HEIGHT);
424 pinnedLane.maxVScrollProperty().addListener(maxVScroll ->
syncPinnedHeight());
427 masterDetailPane =
new MasterDetailPane(Side.TOP, primaryView, pinnedView,
false);
428 masterDetailPane.setDividerPosition(dividerPosition);
429 masterDetailPane.prefHeightProperty().bind(getSkinnable().heightProperty());
430 masterDetailPane.prefWidthProperty().bind(getSkinnable().widthProperty());
431 rootPane =
new Pane(masterDetailPane);
432 getChildren().add(rootPane);
435 getSkinnable().highlightPredicate.addListener((observable, oldPredicate, newPredicate) -> {
436 primaryLane.getAllNodes().forEach(primaryNode -> primaryNode.applyHighlightEffect(newPredicate.test(primaryNode)));
437 pinnedLane.getAllNodes().forEach(pinnedNode -> pinnedNode.applyHighlightEffect(newPredicate.test(pinnedNode)));
447 getSkinnable().getLayoutSettings().pinnedLaneShowing().addListener(observable ->
syncPinnedLaneShowing());
451 getSkinnable().intervalSelectorProp.addListener((observable, oldIntervalSelector, newIntervalSelector) -> {
452 rootPane.getChildren().remove(oldIntervalSelector);
453 if (null != newIntervalSelector) {
454 rootPane.getChildren().add(newIntervalSelector);
459 getSkinnable().guideLines.addListener((SetChangeListener.Change<? extends GuideLine> change) -> {
460 if (change.wasRemoved()) {
461 rootPane.getChildren().remove(change.getElementRemoved());
463 if (change.wasAdded()) {
464 rootPane.getChildren().add(change.getElementAdded());
474 pinnedView.setMinHeight(MIN_PINNED_LANE_HEIGHT);
475 pinnedView.setMaxHeight(pinnedLane.maxVScrollProperty().get() + 30);
488 chartLane.setOnMousePressed(chartDragHandler);
489 chartLane.setOnMouseReleased(chartDragHandler);
490 chartLane.setOnMouseDragged(chartDragHandler);
491 chartLane.setOnMouseClicked(chartDragHandler);
492 chartLane.addEventHandler(MouseEvent.MOUSE_CLICKED, mouseClickedHandler);
500 boolean pinnedLaneShowing = getSkinnable().getLayoutSettings().isPinnedLaneShowing();
501 if (pinnedLaneShowing ==
false) {
503 dividerPosition = masterDetailPane.getDividerPosition();
506 masterDetailPane.setShowDetailNode(pinnedLaneShowing);
508 if (pinnedLaneShowing) {
511 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)
void syncPinnedLaneShowing()
DateTime parseDateTime(DateTime date)
final PinnedEventsChartLane pinnedLane
String formatSpan(DateTime date)
static final Image MARKER
final IntervalSelectorProvider< X > chart
static DateTimeFormatter getZonedFormatter()
Interval adjustInterval(Interval interval)
final PrimaryDetailsChartLane primaryLane
void setIntervalSelector(IntervalSelector<?extends X > newIntervalSelector)