Autopsy  4.9.1
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-16 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.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.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;
47 
57 public abstract class IntervalSelector<X> extends BorderPane {
58 
59  private static final Image CLEAR_INTERVAL_ICON = new Image("/org/sleuthkit/autopsy/timeline/images/cross-script.png", 16, 16, true, true, true); //NON-NLS
60  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
61  private static final double STROKE_WIDTH = 3;
62  private static final double HALF_STROKE = STROKE_WIDTH / 2;
63 
68 
69  private Tooltip tooltip;
71  private DragPosition dragPosition;
72  private double startLeft;
73  private double startDragX;
74  private double startWidth;
75 
76  private final BooleanProperty isDragging = new SimpleBooleanProperty(false);
79 
80  @FXML
81  private Label startLabel;
82 
83  @FXML
84  private Label endLabel;
85 
86  @FXML
87  private Button closeButton;
88 
89  @FXML
90  private Button zoomButton;
91 
92  @FXML
93  private BorderPane bottomBorder;
94 
96  this.chart = chart;
97  this.controller = chart.getController();
98  FXMLConstructor.construct(this, IntervalSelector.class, "IntervalSelector.fxml"); // NON-NLS
99  }
100 
101  @FXML
102  void initialize() {
103  assert startLabel != null : "fx:id=\"startLabel\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
104  assert endLabel != null : "fx:id=\"endLabel\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
105  assert closeButton != null : "fx:id=\"closeButton\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
106  assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
107 
108  setMaxHeight(USE_PREF_SIZE);
109  setMinHeight(USE_PREF_SIZE);
110  setMaxWidth(USE_PREF_SIZE);
111  setMinWidth(USE_PREF_SIZE);
112 
113  BooleanBinding showingControls = zoomButton.hoverProperty().or(bottomBorder.hoverProperty().or(hoverProperty())).and(isDragging.not());
114  closeButton.visibleProperty().bind(showingControls);
115  closeButton.managedProperty().bind(showingControls);
116  zoomButton.visibleProperty().bind(showingControls);
117  zoomButton.managedProperty().bind(showingControls);
118 
119  widthProperty().addListener(o -> {
121  if (startLabel.getWidth() + zoomButton.getWidth() + endLabel.getWidth() > getWidth() - 10) {
122  this.setCenter(zoomButton);
123  bottomBorder.setCenter(new Rectangle(10, 10, Color.TRANSPARENT));
124  } else {
125  bottomBorder.setCenter(zoomButton);
126  }
127  BorderPane.setAlignment(zoomButton, Pos.BOTTOM_CENTER);
128  });
129  layoutXProperty().addListener(o -> this.updateStartAndEnd());
131 
132  setOnMouseMoved(mouseMove -> {
133  Point2D parentMouse = getLocalMouseCoords(mouseMove);
134  final double diffX = getLayoutX() - parentMouse.getX();
135  if (Math.abs(diffX) <= HALF_STROKE) {
136  setCursor(Cursor.W_RESIZE);
137  } else if (Math.abs(diffX + getWidth()) <= HALF_STROKE) {
138  setCursor(Cursor.E_RESIZE);
139  } else {
140  setCursor(Cursor.HAND);
141  }
142  mouseMove.consume();
143  });
144 
145  setOnMousePressed(mousePress -> {
146  Point2D parentMouse = getLocalMouseCoords(mousePress);
147  final double diffX = getLayoutX() - parentMouse.getX();
148  startDragX = mousePress.getScreenX();
149  startWidth = getWidth();
150  startLeft = getLayoutX();
151  if (Math.abs(diffX) <= HALF_STROKE) {
152  dragPosition = IntervalSelector.DragPosition.LEFT;
153  } else if (Math.abs(diffX + getWidth()) <= HALF_STROKE) {
154  dragPosition = IntervalSelector.DragPosition.RIGHT;
155  } else {
156  dragPosition = IntervalSelector.DragPosition.CENTER;
157  }
158  mousePress.consume();
159  });
160 
161  setOnMouseReleased((MouseEvent mouseRelease) -> {
162  isDragging.set(false);
163  mouseRelease.consume();;
164  });
165 
166  setOnMouseDragged(mouseDrag -> {
167  isDragging.set(true);
168  double dX = mouseDrag.getScreenX() - startDragX;
169  switch (dragPosition) {
170  case CENTER:
171  setLayoutX(startLeft + dX);
172  break;
173  case LEFT:
174  if (dX > startWidth) {
175  startDragX = mouseDrag.getScreenX();
176  startWidth = 0;
177  dragPosition = DragPosition.RIGHT;
178  } else {
179  setLayoutX(startLeft + dX);
180  setPrefWidth(startWidth - dX);
181  autosize();
182  }
183  break;
184  case RIGHT:
185  Point2D parentMouse = getLocalMouseCoords(mouseDrag);
186  if (parentMouse.getX() < startLeft) {
187  dragPosition = DragPosition.LEFT;
188  startDragX = mouseDrag.getScreenX();
189  startWidth = 0;
190  } else {
191  setPrefWidth(startWidth + dX);
192  autosize();
193  }
194  break;
195  }
196  mouseDrag.consume();
197  });
198 
199  setOnMouseClicked(mouseClick -> {
200  if (mouseClick.getButton() == MouseButton.SECONDARY) {
201  chart.clearIntervalSelector();
202  } else if (mouseClick.getClickCount() >= 2) {
204  mouseClick.consume();
205  }
206  });
207 
208  ActionUtils.configureButton(new ZoomToSelectedIntervalAction(), zoomButton);
209  ActionUtils.configureButton(new ClearSelectedIntervalAction(), closeButton);
210  }
211 
212  private Point2D getLocalMouseCoords(MouseEvent mouseEvent) {
213  return getParent().sceneToLocal(new Point2D(mouseEvent.getSceneX(), mouseEvent.getSceneY()));
214  }
215 
216  private void zoomToSelectedInterval() {
217  //convert to DateTimes, using max/min if null(off axis)
218  DateTime start = parseDateTime(getSpanStart());
219  DateTime end = parseDateTime(getSpanEnd());
220  Interval i = adjustInterval(start.isBefore(end) ? new Interval(start, end) : new Interval(end, start));
221  controller.pushTimeRange(i);
222  }
223 
231  protected abstract Interval adjustInterval(Interval i);
232 
241  protected abstract String formatSpan(final X date);
242 
250  protected abstract DateTime parseDateTime(X date);
251 
252  @NbBundle.Messages(value = {"# {0} - start timestamp",
253  "# {1} - end timestamp",
254  "Timeline.ui.TimeLineChart.tooltip.text=Double-click to zoom into range:\n{0} to {1}.\n\nRight-click to close."})
255  private void updateStartAndEnd() {
256  String startString = formatSpan(getSpanStart());
257  String endString = formatSpan(getSpanEnd());
258  startLabel.setText(startString);
259  endLabel.setText(endString);
260 
261  Tooltip.uninstall(this, tooltip);
262  tooltip = new Tooltip(Bundle.Timeline_ui_TimeLineChart_tooltip_text(startString, endString));
263  Tooltip.install(this, tooltip);
264  }
265 
270  public X getSpanEnd() {
271  return getValueForDisplay(getBoundsInParent().getMaxX());
272  }
273 
278  public X getSpanStart() {
279  return getValueForDisplay(getBoundsInParent().getMinX());
280  }
281 
282  private X getValueForDisplay(final double display) {
283  return chart.getXAxis().getValueForDisplay(chart.getXAxis().parentToLocal(display, 0).getX());
284  }
285 
290  private enum DragPosition {
291 
294  RIGHT
295  }
296 
297  private class ZoomToSelectedIntervalAction extends Action {
298 
299  @NbBundle.Messages("IntervalSelector.ZoomAction.name=Zoom")
301  super(Bundle.IntervalSelector_ZoomAction_name());
302  setGraphic(new ImageView(ZOOM_TO_INTERVAL_ICON));
303  setEventHandler((ActionEvent t) -> {
305  });
306  }
307  }
308 
309  private class ClearSelectedIntervalAction extends Action {
310 
311  @NbBundle.Messages("IntervalSelector.ClearSelectedIntervalAction.tooltTipText=Clear Selected Interval")
313  super("");
314  setLongText(Bundle.IntervalSelector_ClearSelectedIntervalAction_tooltTipText());
315  setGraphic(new ImageView(CLEAR_INTERVAL_ICON));
316  setEventHandler((ActionEvent t) -> {
317  chart.clearIntervalSelector();
318  });
319  }
320  }
321 
322  public interface IntervalSelectorProvider<X> {
323 
325 
327 
329 
337 
342  void clearIntervalSelector();
343 
344  public Axis<X> getXAxis();
345  }
346 }
void setIntervalSelector(IntervalSelector<?extends X > newIntervalSelector)
abstract Interval adjustInterval(Interval i)
synchronized boolean pushTimeRange(Interval timeRange)
IntervalSelector(IntervalSelectorProvider< X > chart)
static void construct(Node node, String fxmlFileName)

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