Autopsy  4.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-15 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 javafx.beans.binding.BooleanBinding;
22 import javafx.beans.property.BooleanProperty;
23 import javafx.beans.property.SimpleBooleanProperty;
24 import javafx.event.ActionEvent;
25 import javafx.fxml.FXML;
26 import javafx.geometry.Point2D;
27 import javafx.geometry.Pos;
28 import javafx.scene.Cursor;
29 import javafx.scene.control.Button;
30 import javafx.scene.control.Label;
31 import javafx.scene.control.Tooltip;
32 import javafx.scene.image.Image;
33 import javafx.scene.image.ImageView;
34 import javafx.scene.input.MouseButton;
35 import javafx.scene.input.MouseEvent;
36 import javafx.scene.layout.BorderPane;
37 import org.controlsfx.control.action.Action;
38 import org.controlsfx.control.action.ActionUtils;
39 import org.joda.time.DateTime;
40 import org.joda.time.Interval;
41 import org.openide.util.NbBundle;
44 
54 public abstract class IntervalSelector<X> extends BorderPane {
55 
56  private static final Image ClEAR_INTERVAL_ICON = new Image("/org/sleuthkit/autopsy/timeline/images/cross-script.png", 16, 16, true, true, true); //NON-NLS
57  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
58  private static final double STROKE_WIDTH = 3;
59  private static final double HALF_STROKE = STROKE_WIDTH / 2;
60 
64  public final TimeLineChart<X> chart;
65 
66  private Tooltip tooltip;
68  private DragPosition dragPosition;
69  private double startLeft;
70  private double startDragX;
71  private double startWidth;
72 
73  private final BooleanProperty isDragging = new SimpleBooleanProperty(false);
76 
77  @FXML
78  private Label startLabel;
79 
80  @FXML
81  private Label endLabel;
82 
83  @FXML
84  private Button closeButton;
85 
86  @FXML
87  private Button zoomButton;
88 
89  @FXML
90  private BorderPane bottomBorder;
91 
93  this.chart = chart;
94  this.controller = chart.getController();
95  FXMLConstructor.construct(this, IntervalSelector.class, "IntervalSelector.fxml"); // NON-NLS
96  }
97 
98  @FXML
99  void initialize() {
100  assert startLabel != null : "fx:id=\"startLabel\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
101  assert endLabel != null : "fx:id=\"endLabel\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
102  assert closeButton != null : "fx:id=\"closeButton\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
103  assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
104 
105  setMaxHeight(USE_PREF_SIZE);
106  setMinHeight(USE_PREF_SIZE);
107  setMaxWidth(USE_PREF_SIZE);
108  setMinWidth(USE_PREF_SIZE);
109 
110  BooleanBinding showingControls = hoverProperty().and(isDragging.not());
111  closeButton.visibleProperty().bind(showingControls);
112  closeButton.managedProperty().bind(showingControls);
113  zoomButton.visibleProperty().bind(showingControls);
114  zoomButton.managedProperty().bind(showingControls);
115 
116  widthProperty().addListener(o -> {
118  if (startLabel.getWidth() + zoomButton.getWidth() + endLabel.getWidth() > getWidth()) {
119  this.setCenter(zoomButton);
120  } else {
121  bottomBorder.setCenter(zoomButton);
122  }
123  BorderPane.setAlignment(zoomButton, Pos.BOTTOM_CENTER);
124  });
125  layoutXProperty().addListener(o -> this.updateStartAndEnd());
127 
128  setOnMouseMoved(mouseMove -> {
129  Point2D parentMouse = getLocalMouseCoords(mouseMove);
130  final double diffX = getLayoutX() - parentMouse.getX();
131  if (Math.abs(diffX) <= HALF_STROKE) {
132  setCursor(Cursor.W_RESIZE);
133  } else if (Math.abs(diffX + getWidth()) <= HALF_STROKE) {
134  setCursor(Cursor.E_RESIZE);
135  } else {
136  setCursor(Cursor.HAND);
137  }
138  mouseMove.consume();
139  });
140 
141  setOnMousePressed(mousePress -> {
142  Point2D parentMouse = getLocalMouseCoords(mousePress);
143  final double diffX = getLayoutX() - parentMouse.getX();
144  startDragX = mousePress.getScreenX();
145  startWidth = getWidth();
146  startLeft = getLayoutX();
147  if (Math.abs(diffX) <= HALF_STROKE) {
148  dragPosition = IntervalSelector.DragPosition.LEFT;
149  } else if (Math.abs(diffX + getWidth()) <= HALF_STROKE) {
150  dragPosition = IntervalSelector.DragPosition.RIGHT;
151  } else {
152  dragPosition = IntervalSelector.DragPosition.CENTER;
153  }
154  mousePress.consume();
155  });
156 
157  setOnMouseReleased(mouseRelease -> isDragging.set(false));
158  setOnMouseDragged(mouseDrag -> {
159  isDragging.set(true);
160  double dX = mouseDrag.getScreenX() - startDragX;
161  switch (dragPosition) {
162  case CENTER:
163  setLayoutX(startLeft + dX);
164  break;
165  case LEFT:
166  if (dX > startWidth) {
167  startDragX = mouseDrag.getScreenX();
168  startWidth = 0;
169  dragPosition = DragPosition.RIGHT;
170  } else {
171  setLayoutX(startLeft + dX);
172  setPrefWidth(startWidth - dX);
173  autosize();
174  }
175  break;
176  case RIGHT:
177  Point2D parentMouse = getLocalMouseCoords(mouseDrag);
178  if (parentMouse.getX() < startLeft) {
179  dragPosition = DragPosition.LEFT;
180  startDragX = mouseDrag.getScreenX();
181  startWidth = 0;
182  } else {
183  setPrefWidth(startWidth + dX);
184  autosize();
185  }
186  break;
187  }
188  mouseDrag.consume();
189  });
190 
191  ActionUtils.configureButton(new ZoomToSelectedIntervalAction(), zoomButton);
192  ActionUtils.configureButton(new ClearSelectedIntervalAction(), closeButton);
193 
194  //have to add handler rather than use convenience methods so that charts can listen for dismisal click
195  setOnMouseClicked(mosueClick -> {
196  if (mosueClick.getButton() == MouseButton.SECONDARY) {
197  chart.clearIntervalSelector();
198  mosueClick.consume();
199  }
200  if (mosueClick.getClickCount() >= 2) {
202  mosueClick.consume();
203  }
204  });
205  }
206 
207  private Point2D getLocalMouseCoords(MouseEvent mouseEvent) {
208  return getParent().sceneToLocal(new Point2D(mouseEvent.getSceneX(), mouseEvent.getSceneY()));
209  }
210 
211  private void zoomToSelectedInterval() {
212  //convert to DateTimes, using max/min if null(off axis)
213  DateTime start = parseDateTime(getSpanStart());
214  DateTime end = parseDateTime(getSpanEnd());
215  Interval i = adjustInterval(start.isBefore(end) ? new Interval(start, end) : new Interval(end, start));
216  controller.pushTimeRange(i);
217  }
218 
226  protected abstract Interval adjustInterval(Interval i);
227 
236  protected abstract String formatSpan(final X date);
237 
245  protected abstract DateTime parseDateTime(X date);
246 
247  @NbBundle.Messages(value = {"# {0} - start timestamp",
248  "# {1} - end timestamp",
249  "Timeline.ui.TimeLineChart.tooltip.text=Double-click to zoom into range:\n{0} to {1}\nRight-click to clear."})
250  private void updateStartAndEnd() {
251  String startString = formatSpan(getSpanStart());
252  String endString = formatSpan(getSpanEnd());
253  startLabel.setText(startString);
254  endLabel.setText(endString);
255 
256  Tooltip.uninstall(this, tooltip);
257  tooltip = new Tooltip(Bundle.Timeline_ui_TimeLineChart_tooltip_text(startString, endString));
258  Tooltip.install(this, tooltip);
259  }
260 
265  public X getSpanEnd() {
266  return getValueForDisplay(getBoundsInParent().getMaxX());
267  }
268 
273  public X getSpanStart() {
274  return getValueForDisplay(getBoundsInParent().getMinX());
275  }
276 
277  private X getValueForDisplay(final double display) {
278  return chart.getXAxis().getValueForDisplay(chart.getXAxis().parentToLocal(display, 0).getX());
279  }
280 
285  private enum DragPosition {
286 
289  RIGHT
290  }
291 
292  private class ZoomToSelectedIntervalAction extends Action {
293 
294  @NbBundle.Messages("IntervalSelector.ZoomAction.name=Zoom")
296  super(Bundle.IntervalSelector_ZoomAction_name());
297  setGraphic(new ImageView(ZOOM_TO_INTERVAL_ICON));
298  setEventHandler((ActionEvent t) -> {
300  });
301  }
302  }
303 
304  private class ClearSelectedIntervalAction extends Action {
305 
306  @NbBundle.Messages("IntervalSelector.ClearSelectedIntervalAction.tooltTipText=Clear Selected Interval")
308  super("");
309  setLongText(Bundle.IntervalSelector_ClearSelectedIntervalAction_tooltTipText());
310  setGraphic(new ImageView(ClEAR_INTERVAL_ICON));
311  setEventHandler((ActionEvent t) -> {
312  chart.clearIntervalSelector();
313  });
314  }
315  }
316 }
abstract Interval adjustInterval(Interval i)
synchronized boolean pushTimeRange(Interval timeRange)
static void construct(Node node, String fxmlFileName)

Copyright © 2012-2015 Basis Technology. Generated on: Wed Apr 6 2016
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.