19 package org.sleuthkit.autopsy.timeline.ui;
 
   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;
 
   59     private static final Image 
CLEAR_INTERVAL_ICON = 
new Image(
"/org/sleuthkit/autopsy/timeline/images/cross-script.png", 16, 16, 
true, 
true, 
true); 
 
   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); 
 
   76     private final BooleanProperty 
isDragging = 
new SimpleBooleanProperty(
false);
 
  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'.";
 
  108         setMaxHeight(USE_PREF_SIZE);
 
  109         setMinHeight(USE_PREF_SIZE);
 
  110         setMaxWidth(USE_PREF_SIZE);
 
  111         setMinWidth(USE_PREF_SIZE);
 
  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);
 
  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));
 
  125                 bottomBorder.setCenter(zoomButton);
 
  127             BorderPane.setAlignment(zoomButton, Pos.BOTTOM_CENTER);
 
  132         setOnMouseMoved(mouseMove -> {
 
  134             final double diffX = getLayoutX() - parentMouse.getX();
 
  136                 setCursor(Cursor.W_RESIZE);
 
  137             } 
else if (Math.abs(diffX + getWidth()) <= 
HALF_STROKE) {
 
  138                 setCursor(Cursor.E_RESIZE);
 
  140                 setCursor(Cursor.HAND);
 
  145         setOnMousePressed(mousePress -> {
 
  147             final double diffX = getLayoutX() - parentMouse.getX();
 
  148             startDragX = mousePress.getScreenX();
 
  149             startWidth = getWidth();
 
  150             startLeft = getLayoutX();
 
  153             } 
else if (Math.abs(diffX + getWidth()) <= 
HALF_STROKE) {
 
  158             mousePress.consume();
 
  161         setOnMouseReleased((MouseEvent mouseRelease) -> {
 
  162             isDragging.set(
false);
 
  163             mouseRelease.consume();;
 
  166         setOnMouseDragged(mouseDrag -> {
 
  167             isDragging.set(
true);
 
  168             double dX = mouseDrag.getScreenX() - 
startDragX;
 
  169             switch (dragPosition) {
 
  171                     setLayoutX(startLeft + dX);
 
  174                     if (dX > startWidth) {
 
  175                         startDragX = mouseDrag.getScreenX();
 
  177                         dragPosition = DragPosition.RIGHT;
 
  179                         setLayoutX(startLeft + dX);
 
  180                         setPrefWidth(startWidth - dX);
 
  187                         dragPosition = DragPosition.LEFT;
 
  188                         startDragX = mouseDrag.getScreenX();
 
  191                         setPrefWidth(startWidth + dX);
 
  199         setOnMouseClicked(mouseClick -> {
 
  200             if (mouseClick.getButton() == MouseButton.SECONDARY) {
 
  202             } 
else if (mouseClick.getClickCount() >= 2) {
 
  204                 mouseClick.consume();
 
  208         ActionUtils.configureButton(
new ZoomToSelectedIntervalAction(), zoomButton);
 
  209         ActionUtils.configureButton(
new ClearSelectedIntervalAction(), closeButton);
 
  213         return getParent().sceneToLocal(
new Point2D(mouseEvent.getSceneX(), mouseEvent.getSceneY()));
 
  220         Interval i = 
adjustInterval(start.isBefore(end) ? 
new Interval(start, end) : 
new Interval(end, start));
 
  241     protected abstract String 
formatSpan(
final X date);
 
  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."})
 
  258         startLabel.setText(startString);
 
  259         endLabel.setText(endString);
 
  261         Tooltip.uninstall(
this, tooltip);
 
  262         tooltip = 
new Tooltip(Bundle.Timeline_ui_TimeLineChart_tooltip_text(startString, endString));
 
  263         Tooltip.install(
this, tooltip);
 
  283         return chart.
getXAxis().getValueForDisplay(chart.
getXAxis().parentToLocal(display, 0).getX());
 
  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) -> {
 
  311         @NbBundle.Messages(
"IntervalSelector.ClearSelectedIntervalAction.tooltTipText=Clear Selected Interval")
 
  314             setLongText(Bundle.IntervalSelector_ClearSelectedIntervalAction_tooltTipText());
 
  315             setGraphic(
new ImageView(CLEAR_INTERVAL_ICON));
 
  316             setEventHandler((ActionEvent t) -> {
 
void clearIntervalSelector()
final TimeLineController controller
void setIntervalSelector(IntervalSelector<?extends X > newIntervalSelector)
Point2D getLocalMouseCoords(MouseEvent mouseEvent)
TimeLineController getController()
abstract Interval adjustInterval(Interval i)
synchronized boolean pushTimeRange(Interval timeRange)
abstract DateTime parseDateTime(X date)
static final Image CLEAR_INTERVAL_ICON
final BooleanProperty isDragging
static final Image ZOOM_TO_INTERVAL_ICON
IntervalSelector<?extends X > getIntervalSelector()
IntervalSelector< X > newIntervalSelector()
static final double STROKE_WIDTH
DragPosition dragPosition
void zoomToSelectedInterval()
IntervalSelector(IntervalSelectorProvider< X > chart)
final IntervalSelectorProvider< X > chart
static void construct(Node node, String fxmlFileName)
static final double HALF_STROKE
X getValueForDisplay(final double display)
abstract String formatSpan(final X date)