allow user to drag interval selector with any mouse button

This commit is contained in:
jmillman 2015-10-02 17:11:15 -04:00
parent 3c235d9bac
commit 84a31f3738
4 changed files with 116 additions and 61 deletions

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013 Basis Technology Corp. * Copyright 2013-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -35,7 +35,6 @@ Timeline.ui.ZoomRanges.threeyears.text=Three Years
Timeline.ui.ZoomRanges.fiveyears.text=Five Years Timeline.ui.ZoomRanges.fiveyears.text=Five Years
Timeline.ui.ZoomRanges.tenyears.text=Ten Years Timeline.ui.ZoomRanges.tenyears.text=Ten Years
Timeline.ui.ZoomRanges.all.text=All Timeline.ui.ZoomRanges.all.text=All
Timeline.ui.TimeLineChart.tooltip.text=Double-click to zoom into range\:\n{0} to {1}\nRight-click to clear.
TimeLineResultView.startDateToEndDate.text={0} to {1} TimeLineResultView.startDateToEndDate.text={0} to {1}
VisualizationPanel.histogramTask.title=Rebuild Histogram VisualizationPanel.histogramTask.title=Rebuild Histogram
VisualizationPanel.histogramTask.preparing=preparing VisualizationPanel.histogramTask.preparing=preparing

View File

@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.timeline.ui; package org.sleuthkit.autopsy.timeline.ui;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.geometry.Point2D; import javafx.geometry.Point2D;
import javafx.scene.Cursor; import javafx.scene.Cursor;
import javafx.scene.chart.Axis; import javafx.scene.chart.Axis;
@ -69,7 +70,7 @@ public interface TimeLineChart<X> extends TimeLineView {
* @param <X> the type of values along the horizontal axis * @param <X> the type of values along the horizontal axis
* @param <Y> the type of chart this is a drag handler for * @param <Y> the type of chart this is a drag handler for
*/ */
class ChartDragHandler<X, Y extends Chart & TimeLineChart<X>> implements EventHandler<MouseEvent> { static class ChartDragHandler<X, Y extends Chart & TimeLineChart<X>> implements EventHandler<MouseEvent> {
private final Y chart; private final Y chart;
@ -77,46 +78,80 @@ public interface TimeLineChart<X> extends TimeLineView {
private double startX; //hanlder mainstains position of drag start private double startX; //hanlder mainstains position of drag start
private boolean requireDrag = true;
public boolean isRequireDrag() {
return requireDrag;
}
public void setRequireDrag(boolean requireDrag) {
this.requireDrag = requireDrag;
}
public ChartDragHandler(Y chart, Axis<X> dateAxis) { public ChartDragHandler(Y chart, Axis<X> dateAxis) {
this.chart = chart; this.chart = chart;
this.dateAxis = dateAxis; this.dateAxis = dateAxis;
} }
@Override @Override
public void handle(MouseEvent t) { public void handle(MouseEvent mouseEvent) {
if (t.getButton() == MouseButton.SECONDARY) { EventType<? extends MouseEvent> mouseEventType = mouseEvent.getEventType();
if (mouseEventType == MouseEvent.MOUSE_PRESSED) {
if (t.getEventType() == MouseEvent.MOUSE_PRESSED) {
//caputure x-position, incase we are repositioning existing selector //caputure x-position, incase we are repositioning existing selector
startX = t.getX(); startX = mouseEvent.getX();
chart.setCursor(Cursor.E_RESIZE); // chart.setCursor(Cursor.H_RESIZE);
} else if (t.getEventType() == MouseEvent.MOUSE_DRAGGED) { } else if ((mouseEventType == MouseEvent.MOUSE_DRAGGED)
|| mouseEventType == MouseEvent.MOUSE_MOVED && (requireDrag == false)) {
if (chart.getIntervalSelector() == null) { if (chart.getIntervalSelector() == null) {
//make new interval selector //make new interval selector
chart.setIntervalSelector(chart.newIntervalSelector(t.getX(), dateAxis)); chart.setIntervalSelector(chart.newIntervalSelector(mouseEvent.getX(), dateAxis));
chart.getIntervalSelector().heightProperty().bind(chart.heightProperty().subtract(dateAxis.heightProperty().subtract(dateAxis.tickLengthProperty()))); chart.getIntervalSelector().heightProperty().bind(chart.heightProperty().subtract(dateAxis.heightProperty().subtract(dateAxis.tickLengthProperty())));
chart.getIntervalSelector().addEventHandler(MouseEvent.MOUSE_CLICKED, (MouseEvent event) -> { IntervalSelector<? extends X> intervalSelector = chart.getIntervalSelector();
if (intervalSelector != null) {
intervalSelector.addEventHandler(MouseEvent.MOUSE_CLICKED, (MouseEvent event) -> {
if (event.getButton() == MouseButton.SECONDARY) { if (event.getButton() == MouseButton.SECONDARY) {
chart.clearIntervalSelector(); chart.clearIntervalSelector();
event.consume(); event.consume();
} }
}); });
startX = t.getX(); }
startX = mouseEvent.getX();
} else { } else {
//resize/position existing selector //resize/position existing selector
if (t.getX() > startX) { if (mouseEvent.getX() > startX) {
chart.getIntervalSelector().setX(startX); chart.getIntervalSelector().setX(startX);
chart.getIntervalSelector().setWidth(t.getX() - startX); chart.getIntervalSelector().setWidth(mouseEvent.getX() - startX);
} else { } else {
chart.getIntervalSelector().setX(t.getX()); chart.getIntervalSelector().setX(mouseEvent.getX());
chart.getIntervalSelector().setWidth(startX - t.getX()); chart.getIntervalSelector().setWidth(startX - mouseEvent.getX());
} }
} }
} else if (t.getEventType() == MouseEvent.MOUSE_RELEASED) { } else if (mouseEventType == MouseEvent.MOUSE_RELEASED) {
chart.setCursor(Cursor.DEFAULT); // chart.setCursor(Cursor.DEFAULT);
requireDrag = true;
IntervalSelector<? extends X> intervalSelector = chart.getIntervalSelector();
if (intervalSelector != null) {
intervalSelector.addEventHandler(MouseEvent.MOUSE_CLICKED, (MouseEvent event) -> {
if (event.getButton() == MouseButton.SECONDARY) {
chart.clearIntervalSelector();
event.consume();
} }
t.consume(); });
} }
} else if (mouseEventType == MouseEvent.MOUSE_CLICKED) {
// chart.setCursor(Cursor.DEFAULT);
requireDrag = true;
IntervalSelector<? extends X> intervalSelector = chart.getIntervalSelector();
if (intervalSelector != null) {
intervalSelector.addEventHandler(MouseEvent.MOUSE_CLICKED, (MouseEvent event) -> {
if (event.getButton() == MouseButton.SECONDARY) {
chart.clearIntervalSelector();
event.consume();
}
});
}
}
// mouseEvent.consume();
} }
} }
@ -140,15 +175,12 @@ public interface TimeLineChart<X> extends TimeLineView {
*/ */
private final Axis<X> dateAxis; private final Axis<X> dateAxis;
protected Tooltip tooltip; private Tooltip tooltip;
/////////drag state /////////drag state
private DragPosition dragPosition; private DragPosition dragPosition;
private double startLeft; private double startLeft;
private double startX; private double startX;
private double startWidth; private double startWidth;
/////////end drag state /////////end drag state
@ -179,7 +211,8 @@ public interface TimeLineChart<X> extends TimeLineView {
Point2D localMouse = sceneToLocal(new Point2D(event.getSceneX(), event.getSceneY())); Point2D localMouse = sceneToLocal(new Point2D(event.getSceneX(), event.getSceneY()));
final double diffX = getX() - localMouse.getX(); final double diffX = getX() - localMouse.getX();
if (Math.abs(diffX) <= HALF_STROKE || Math.abs(diffX + getWidth()) <= HALF_STROKE) { if (Math.abs(diffX) <= HALF_STROKE || Math.abs(diffX + getWidth()) <= HALF_STROKE) {
setCursor(Cursor.E_RESIZE); //if the mouse is over the stroke, show the resize cursor
setCursor(Cursor.H_RESIZE);
} else { } else {
setCursor(Cursor.HAND); setCursor(Cursor.HAND);
} }
@ -221,9 +254,7 @@ public interface TimeLineChart<X> extends TimeLineView {
//convert to DateTimes, using max/min if null(off axis) //convert to DateTimes, using max/min if null(off axis)
DateTime start = parseDateTime(getSpanStart()); DateTime start = parseDateTime(getSpanStart());
DateTime end = parseDateTime(getSpanEnd()); DateTime end = parseDateTime(getSpanEnd());
Interval i = adjustInterval(start.isBefore(end) ? new Interval(start, end) : new Interval(end, start)); Interval i = adjustInterval(start.isBefore(end) ? new Interval(start, end) : new Interval(end, start));
controller.pushTimeRange(i); controller.pushTimeRange(i);
} }
} }
@ -258,13 +289,15 @@ public interface TimeLineChart<X> extends TimeLineView {
*/ */
protected abstract DateTime parseDateTime(X date); protected abstract DateTime parseDateTime(X date);
@NbBundle.Messages({"# {0} - start timestamp",
"# {1} - end timestamp",
"Timeline.ui.TimeLineChart.tooltip.text=Double-click to zoom into range:\n{0} to {1}\nRight-click to clear."})
private void setTooltip() { private void setTooltip() {
final X start = getSpanStart(); final X start = getSpanStart();
final X end = getSpanEnd(); final X end = getSpanEnd();
Tooltip.uninstall(this, tooltip); Tooltip.uninstall(this, tooltip);
tooltip = new Tooltip( tooltip = new Tooltip(
NbBundle.getMessage(TimeLineChart.class, "Timeline.ui.TimeLineChart.tooltip.text", formatSpan(start), Bundle.Timeline_ui_TimeLineChart_tooltip_text(formatSpan(start), formatSpan(end)));
formatSpan(end)));
Tooltip.install(this, tooltip); Tooltip.install(this, tooltip);
} }
@ -290,7 +323,9 @@ public interface TimeLineChart<X> extends TimeLineView {
*/ */
private enum DragPosition { private enum DragPosition {
LEFT, CENTER, RIGHT LEFT,
CENTER,
RIGHT
} }
} }
} }

View File

@ -33,7 +33,5 @@ CountsViewPane.loggedTask.resetUI=resetting ui
CountsViewPane.tooltip.text={0} {1} events\nbetween {2}\nand {3} CountsViewPane.tooltip.text={0} {1} events\nbetween {2}\nand {3}
CountsViewPane.loggedTask.updatingCounts=updating counts CountsViewPane.loggedTask.updatingCounts=updating counts
CountsViewPane.loggedTask.wrappingUp=wrapping up CountsViewPane.loggedTask.wrappingUp=wrapping up
EventCountsChart.contextMenu.zoomHistory.name=Zoom History
CountsViewPane.scaleLabel.text=Scale\:
CountsViewPane.logRadio.text=Logarithmic CountsViewPane.logRadio.text=Logarithmic
CountsViewPane.linearRadio.text=Linear CountsViewPane.linearRadio.text=Linear

View File

@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.timeline.ui.countsview;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.MissingResourceException;
import javafx.scene.chart.Axis; import javafx.scene.chart.Axis;
import javafx.scene.chart.CategoryAxis; import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis; import javafx.scene.chart.NumberAxis;
@ -44,9 +45,9 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
* Customized {@link StackedBarChart<String, Number>} used to display the event * Customized {@link StackedBarChart<String, Number>} used to display the event
* counts in {@link CountsViewPane} * counts in {@link CountsViewPane}
*/ */
class EventCountsChart extends StackedBarChart<String, Number> implements TimeLineChart<String> { final class EventCountsChart extends StackedBarChart<String, Number> implements TimeLineChart<String> {
private ContextMenu contextMenu; private ContextMenu chartContextMenu;
private TimeLineController controller; private TimeLineController controller;
@ -58,6 +59,7 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
* by padding the end with one 'period' * by padding the end with one 'period'
*/ */
private RangeDivisionInfo rangeInfo; private RangeDivisionInfo rangeInfo;
private final ChartDragHandler<String, EventCountsChart> dragHandler;
EventCountsChart(CategoryAxis dateAxis, NumberAxis countAxis) { EventCountsChart(CategoryAxis dateAxis, NumberAxis countAxis) {
super(dateAxis, countAxis); super(dateAxis, countAxis);
@ -81,15 +83,19 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
setTitle(null); setTitle(null);
//use one handler with an if chain because it maintains state //use one handler with an if chain because it maintains state
ChartDragHandler<String, EventCountsChart> dragHandler = new ChartDragHandler<>(this, getXAxis()); dragHandler = new ChartDragHandler<>(this, getXAxis());
setOnMousePressed(dragHandler); setOnMousePressed(dragHandler);
setOnMouseReleased(dragHandler); setOnMouseReleased(dragHandler);
setOnMouseDragged(dragHandler); setOnMouseDragged(dragHandler);
setOnMouseMoved(dragHandler);
setOnMouseClicked((MouseEvent clickEvent) -> { setOnMouseClicked((MouseEvent clickEvent) -> {
contextMenu.hide(); if (chartContextMenu != null) {
chartContextMenu.hide();
}
if (clickEvent.getButton() == MouseButton.SECONDARY && clickEvent.isStillSincePress()) { if (clickEvent.getButton() == MouseButton.SECONDARY && clickEvent.isStillSincePress()) {
contextMenu.show(EventCountsChart.this, clickEvent.getScreenX(), clickEvent.getScreenY()); getChartContextMenu(clickEvent);
chartContextMenu.show(EventCountsChart.this, clickEvent.getScreenX(), clickEvent.getScreenY());
clickEvent.consume(); clickEvent.consume();
} }
}); });
@ -101,17 +107,34 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
intervalSelector = null; intervalSelector = null;
} }
@Override @NbBundle.Messages({"EventCountsChart.contextMenu.zoomHistory.name=Zoom History"})
public final synchronized void setController(TimeLineController controller) { ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException {
this.controller = controller; if (chartContextMenu != null) {
setModel(this.controller.getEventsModel()); chartContextMenu.hide();
//we have defered creating context menu until control is available }
contextMenu = ActionUtils.createContextMenu(
Arrays.asList(new ActionGroup( chartContextMenu = ActionUtils.createContextMenu(Arrays.asList(
NbBundle.getMessage(this.getClass(), "EventCountsChart.contextMenu.zoomHistory.name"), // new Action("Select Interval") {
// {
// setEventHandler((ActionEvent t) -> {
// dragHandler.setRequireDrag(false);
// dragHandler.handle(clickEvent.copyFor(clickEvent.getSource(), clickEvent.getTarget(), MouseEvent.MOUSE_DRAGGED));
// });
// }
//
// },
new ActionGroup(
Bundle.EventCountsChart_contextMenu_zoomHistory_name(),
new Back(controller), new Back(controller),
new Forward(controller)))); new Forward(controller))));
contextMenu.setAutoHide(true); chartContextMenu.setAutoHide(true);
return chartContextMenu;
}
@Override
public synchronized void setController(TimeLineController controller) {
this.controller = controller;
setModel(this.controller.getEventsModel());
} }
@Override @Override
@ -145,7 +168,7 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
* @return the context menu for this chart * @return the context menu for this chart
*/ */
ContextMenu getContextMenu() { ContextMenu getContextMenu() {
return contextMenu; return chartContextMenu;
} }
void setRangeInfo(RangeDivisionInfo rangeInfo) { void setRangeInfo(RangeDivisionInfo rangeInfo) {
@ -177,7 +200,7 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
*/ */
private class CountsIntervalSelector extends IntervalSelector<String> { private class CountsIntervalSelector extends IntervalSelector<String> {
public CountsIntervalSelector(double x, double height, Axis<String> axis, TimeLineController controller) { CountsIntervalSelector(double x, double height, Axis<String> axis, TimeLineController controller) {
super(x, height, axis, controller); super(x, height, axis, controller);
} }