Autopsy  4.17.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
IntervalSelector.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2014-18 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  */
19 package org.sleuthkit.autopsy.timeline.ui;
20 
21 import java.util.logging.Level;
22 import javafx.beans.binding.BooleanBinding;
23 import javafx.beans.property.BooleanProperty;
24 import javafx.beans.property.SimpleBooleanProperty;
25 import javafx.fxml.FXML;
26 import javafx.geometry.Point2D;
27 import javafx.geometry.Pos;
28 import javafx.scene.Cursor;
29 import javafx.scene.chart.Axis;
30 import javafx.scene.control.Button;
31 import javafx.scene.control.Label;
32 import javafx.scene.control.Tooltip;
33 import javafx.scene.image.Image;
34 import javafx.scene.image.ImageView;
35 import javafx.scene.input.MouseButton;
36 import javafx.scene.input.MouseEvent;
37 import javafx.scene.layout.BorderPane;
38 import javafx.scene.paint.Color;
39 import javafx.scene.shape.Rectangle;
40 import org.controlsfx.control.Notifications;
41 import org.controlsfx.control.action.Action;
42 import org.controlsfx.control.action.ActionUtils;
43 import org.joda.time.DateTime;
44 import org.joda.time.Interval;
45 import org.openide.util.NbBundle;
49 import org.sleuthkit.datamodel.TskCoreException;
50 
60 public abstract class IntervalSelector<X> extends BorderPane {
61 
62  private static final Logger logger = Logger.getLogger(IntervalSelector.class.getName());
63 
64  private static final Image CLEAR_INTERVAL_ICON = new Image("/org/sleuthkit/autopsy/timeline/images/cross-script.png", 16, 16, true, true, true); //NON-NLS
65  private static final Image ZOOM_TO_INTERVAL_ICON = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-fit.png", 16, 16, true, true, true); //NON-NLS
66  private static final double STROKE_WIDTH = 3;
67  private static final double HALF_STROKE = STROKE_WIDTH / 2;
68 
73 
74  private Tooltip tooltip;
76  private DragPosition dragPosition;
77  private double startLeft;
78  private double startDragX;
79  private double startWidth;
80 
81  private final BooleanProperty isDragging = new SimpleBooleanProperty(false);
84 
85  @FXML
86  private Label startLabel;
87 
88  @FXML
89  private Label endLabel;
90 
91  @FXML
92  private Button closeButton;
93 
94  @FXML
95  private Button zoomButton;
96 
97  @FXML
98  private BorderPane bottomBorder;
99 
101  this.chart = chart;
102  this.controller = chart.getController();
103  FXMLConstructor.construct(this, IntervalSelector.class, "IntervalSelector.fxml"); // NON-NLS
104  }
105 
106  @FXML
107  void initialize() {
108  assert startLabel != null : "fx:id=\"startLabel\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
109  assert endLabel != null : "fx:id=\"endLabel\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
110  assert closeButton != null : "fx:id=\"closeButton\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
111  assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
112 
113  setMaxHeight(USE_PREF_SIZE);
114  setMinHeight(USE_PREF_SIZE);
115  setMaxWidth(USE_PREF_SIZE);
116  setMinWidth(USE_PREF_SIZE);
117 
118  BooleanBinding showingControls = zoomButton.hoverProperty().or(bottomBorder.hoverProperty().or(hoverProperty())).and(isDragging.not());
119  closeButton.visibleProperty().bind(showingControls);
120  closeButton.managedProperty().bind(showingControls);
121  zoomButton.visibleProperty().bind(showingControls);
122  zoomButton.managedProperty().bind(showingControls);
123 
124  widthProperty().addListener(observable -> {
126  if (startLabel.getWidth() + zoomButton.getWidth() + endLabel.getWidth() > getWidth() - 10) {
127  this.setCenter(zoomButton);
128  bottomBorder.setCenter(new Rectangle(10, 10, Color.TRANSPARENT));
129  } else {
130  bottomBorder.setCenter(zoomButton);
131  }
132  BorderPane.setAlignment(zoomButton, Pos.BOTTOM_CENTER);
133  });
134  layoutXProperty().addListener(observable -> this.updateStartAndEnd());
136 
137  setOnMouseMoved(mouseMove -> {
138  Point2D parentMouse = getLocalMouseCoords(mouseMove);
139  final double diffX = getLayoutX() - parentMouse.getX();
140  if (Math.abs(diffX) <= HALF_STROKE) {
141  setCursor(Cursor.W_RESIZE);
142  } else if (Math.abs(diffX + getWidth()) <= HALF_STROKE) {
143  setCursor(Cursor.E_RESIZE);
144  } else {
145  setCursor(Cursor.HAND);
146  }
147  mouseMove.consume();
148  });
149 
150  setOnMousePressed(mousePress -> {
151  Point2D parentMouse = getLocalMouseCoords(mousePress);
152  final double diffX = getLayoutX() - parentMouse.getX();
153  startDragX = mousePress.getScreenX();
154  startWidth = getWidth();
155  startLeft = getLayoutX();
156  if (Math.abs(diffX) <= HALF_STROKE) {
157  dragPosition = IntervalSelector.DragPosition.LEFT;
158  } else if (Math.abs(diffX + getWidth()) <= HALF_STROKE) {
159  dragPosition = IntervalSelector.DragPosition.RIGHT;
160  } else {
161  dragPosition = IntervalSelector.DragPosition.CENTER;
162  }
163  mousePress.consume();
164  });
165 
166  setOnMouseReleased((MouseEvent mouseRelease) -> {
167  isDragging.set(false);
168  mouseRelease.consume();
169  });
170 
171  setOnMouseDragged(mouseDrag -> {
172  isDragging.set(true);
173  double deltaX = mouseDrag.getScreenX() - startDragX;
174  switch (dragPosition) {
175  case CENTER:
176  setLayoutX(startLeft + deltaX);
177  break;
178  case LEFT:
179  if (deltaX > startWidth) {
180  startDragX = mouseDrag.getScreenX();
181  startWidth = 0;
182  dragPosition = DragPosition.RIGHT;
183  } else {
184  setLayoutX(startLeft + deltaX);
185  setPrefWidth(startWidth - deltaX);
186  autosize();
187  }
188  break;
189  case RIGHT:
190  Point2D parentMouse = getLocalMouseCoords(mouseDrag);
191  if (parentMouse.getX() < startLeft) {
192  dragPosition = DragPosition.LEFT;
193  startDragX = mouseDrag.getScreenX();
194  startWidth = 0;
195  } else {
196  setPrefWidth(startWidth + deltaX);
197  autosize();
198  }
199  break;
200  }
201  mouseDrag.consume();
202  });
203 
204  setOnMouseClicked(mouseClick -> {
205  if (mouseClick.getButton() == MouseButton.SECONDARY) {
206  chart.clearIntervalSelector();
207  } else if (mouseClick.getClickCount() >= 2) {
209  mouseClick.consume();
210  }
211  });
212 
213  ActionUtils.configureButton(new ZoomToSelectedIntervalAction(), zoomButton);
214  ActionUtils.configureButton(new ClearSelectedIntervalAction(), closeButton);
215  }
216 
217  private Point2D getLocalMouseCoords(MouseEvent mouseEvent) {
218  return getParent().sceneToLocal(new Point2D(mouseEvent.getSceneX(), mouseEvent.getSceneY()));
219  }
220 
221  @NbBundle.Messages({
222  "IntervalSelector.zoomToSelectedInterval.errorMessage=Error zooming in to the selected interval."})
223  private void zoomToSelectedInterval() {
224  //convert to DateTimes, using max/min if null(off axis)
225  DateTime start = parseDateTime(getSpanStart());
226  DateTime end = parseDateTime(getSpanEnd());
227  Interval interval = adjustInterval(start.isBefore(end) ? new Interval(start, end) : new Interval(end, start));
228  try {
229  controller.pushTimeRange(interval);
230  } catch (TskCoreException ex) {
231  Notifications.create().owner(getScene().getWindow())
232  .text(Bundle.IntervalSelector_zoomToSelectedInterval_errorMessage())
233  .showError();
234  logger.log(Level.SEVERE, "Error zooming in to the selected interval.");
235  }
236  }
237 
245  protected abstract Interval adjustInterval(Interval interval);
246 
255  protected abstract String formatSpan(final X date);
256 
264  protected abstract DateTime parseDateTime(X date);
265 
266  @NbBundle.Messages(value = {"# {0} - start timestamp",
267  "# {1} - end timestamp",
268  "Timeline.ui.TimeLineChart.tooltip.text=Double-click to zoom into range:\n{0} to {1}.\n\nRight-click to close."})
269  private void updateStartAndEnd() {
270  String startString = formatSpan(getSpanStart());
271  String endString = formatSpan(getSpanEnd());
272  startLabel.setText(startString);
273  endLabel.setText(endString);
274 
275  Tooltip.uninstall(this, tooltip);
276  tooltip = new Tooltip(Bundle.Timeline_ui_TimeLineChart_tooltip_text(startString, endString));
277  Tooltip.install(this, tooltip);
278  }
279 
284  public X getSpanEnd() {
285  return getValueForDisplay(getBoundsInParent().getMaxX());
286  }
287 
292  public X getSpanStart() {
293  return getValueForDisplay(getBoundsInParent().getMinX());
294  }
295 
296  private X getValueForDisplay(final double displayX) {
297  return chart.getXAxis().getValueForDisplay(chart.getXAxis().parentToLocal(displayX, 0).getX());
298  }
299 
304  private enum DragPosition {
305 
308  RIGHT
309  }
310 
311  private class ZoomToSelectedIntervalAction extends Action {
312 
313  @NbBundle.Messages("IntervalSelector.ZoomAction.name=Zoom")
315  super(Bundle.IntervalSelector_ZoomAction_name());
316  setGraphic(new ImageView(ZOOM_TO_INTERVAL_ICON));
317  setEventHandler(actionEvent -> zoomToSelectedInterval());
318  }
319  }
320 
321  private class ClearSelectedIntervalAction extends Action {
322 
323  @NbBundle.Messages("IntervalSelector.ClearSelectedIntervalAction.tooltTipText=Clear Selected Interval")
325  super("");
326  setLongText(Bundle.IntervalSelector_ClearSelectedIntervalAction_tooltTipText());
327  setGraphic(new ImageView(CLEAR_INTERVAL_ICON));
328  setEventHandler(ationEvent -> chart.clearIntervalSelector());
329  }
330  }
331 
332  public interface IntervalSelectorProvider<X> {
333 
335 
337 
339 
347 
352  void clearIntervalSelector();
353 
354  Axis<X> getXAxis();
355  }
356 }
abstract Interval adjustInterval(Interval interval)
void setIntervalSelector(IntervalSelector<?extends X > newIntervalSelector)
synchronized boolean pushTimeRange(Interval timeRange)
IntervalSelector(IntervalSelectorProvider< X > chart)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void construct(Node node, String fxmlFileName)

Copyright © 2012-2021 Basis Technology. Generated on: Tue Jan 19 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.