mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-15 09:17:42 +00:00
use ControlsFX Actions to add expand/collapse menu items to AbstractDetailViewNode
This commit is contained in:
parent
40d1ec9a5e
commit
eadd7266ed
@ -14,14 +14,17 @@ import java.util.logging.Level;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
import javafx.event.EventHandler;
|
import javafx.event.EventHandler;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.Cursor;
|
import javafx.scene.Cursor;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.ContextMenu;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.OverrunStyle;
|
import javafx.scene.control.OverrunStyle;
|
||||||
|
import javafx.scene.control.SeparatorMenuItem;
|
||||||
import javafx.scene.effect.DropShadow;
|
import javafx.scene.effect.DropShadow;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
@ -43,6 +46,8 @@ import static javafx.scene.layout.Region.USE_PREF_SIZE;
|
|||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.controlsfx.control.action.Action;
|
||||||
|
import org.controlsfx.control.action.ActionUtils;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.joda.time.Interval;
|
import org.joda.time.Interval;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
@ -66,7 +71,12 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
|
|||||||
static final Image MINUS = new Image("/org/sleuthkit/autopsy/timeline/images/minus-button.png"); // NON-NLS
|
static final Image MINUS = new Image("/org/sleuthkit/autopsy/timeline/images/minus-button.png"); // NON-NLS
|
||||||
static final Image TAG = new Image("/org/sleuthkit/autopsy/images/green-tag-icon-16.png"); // NON-NLS
|
static final Image TAG = new Image("/org/sleuthkit/autopsy/images/green-tag-icon-16.png"); // NON-NLS
|
||||||
static final CornerRadii CORNER_RADII = new CornerRadii(3);
|
static final CornerRadii CORNER_RADII = new CornerRadii(3);
|
||||||
Map<EventType, DropShadow> dropShadowMap = new HashMap<>();
|
/**
|
||||||
|
* the border to apply when this node is 'selected'
|
||||||
|
*/
|
||||||
|
static final Border selectionBorder = new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CORNER_RADII, new BorderWidths(2)));
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(AbstractDetailViewNode.class
|
||||||
|
.getName());
|
||||||
|
|
||||||
static void configureLODButton(Button b) {
|
static void configureLODButton(Button b) {
|
||||||
b.setMinSize(16, 16);
|
b.setMinSize(16, 16);
|
||||||
@ -74,42 +84,14 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
|
|||||||
b.setPrefSize(16, 16);
|
b.setPrefSize(16, 16);
|
||||||
show(b, false);
|
show(b, false);
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* the border to apply when this node is 'selected'
|
|
||||||
*/
|
|
||||||
static final Border selectionBorder = new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CORNER_RADII, new BorderWidths(2)));
|
|
||||||
final Color evtColor;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public List<S> getSubNodes() {
|
|
||||||
return subNodePane.getChildrenUnmodifiable().stream()
|
|
||||||
.map(t -> (S) t)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* apply the 'effect' to visually indicate selection
|
|
||||||
*
|
|
||||||
* @param applied true to apply the selection 'effect', false to remove it
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void applySelectionEffect(boolean applied) {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
if (applied) {
|
|
||||||
setBorder(selectionBorder);
|
|
||||||
} else {
|
|
||||||
setBorder(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static void show(Node b, boolean show) {
|
static void show(Node b, boolean show) {
|
||||||
b.setVisible(show);
|
b.setVisible(show);
|
||||||
b.setManaged(show);
|
b.setManaged(show);
|
||||||
}
|
}
|
||||||
final ImageView hashIV = new ImageView(HASH_PIN);
|
Map<EventType, DropShadow> dropShadowMap = new HashMap<>();
|
||||||
final ImageView tagIV = new ImageView(TAG);
|
final Color evtColor;
|
||||||
|
|
||||||
private final S parentNode;
|
private final S parentNode;
|
||||||
DescriptionVisibility descrVis;
|
DescriptionVisibility descrVis;
|
||||||
|
|
||||||
@ -142,45 +124,16 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
|
|||||||
final SleuthkitCase sleuthkitCase;
|
final SleuthkitCase sleuthkitCase;
|
||||||
final FilteredEventsModel eventsModel;
|
final FilteredEventsModel eventsModel;
|
||||||
|
|
||||||
final Button plusButton = new Button(null, new ImageView(PLUS)) {
|
final Button plusButton;
|
||||||
{
|
final Button minusButton;
|
||||||
configureLODButton(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
final SimpleObjectProperty<DescriptionLOD> descLOD = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
final Button minusButton = new Button(null, new ImageView(MINUS)) {
|
|
||||||
{
|
|
||||||
configureLODButton(this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
SimpleObjectProperty<DescriptionLOD> descLOD = new SimpleObjectProperty<>();
|
|
||||||
final HBox header;
|
final HBox header;
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param showControls the value of par
|
|
||||||
*/
|
|
||||||
void showDescriptionLoDControls(final boolean showControls) {
|
|
||||||
DropShadow dropShadow = dropShadowMap.computeIfAbsent(getEventType(),
|
|
||||||
eventType -> new DropShadow(10, eventType.getColor()));
|
|
||||||
getSpanFillNode().setEffect(showControls ? dropShadow : null);
|
|
||||||
show(minusButton, showControls);
|
|
||||||
show(plusButton, showControls);
|
|
||||||
}
|
|
||||||
final Region spacer = new Region();
|
final Region spacer = new Region();
|
||||||
|
|
||||||
RootFilter getSubClusterFilter() {
|
private final CollapseClusterAction collapseClusterAction;
|
||||||
RootFilter combinedFilter = eventsModel.filterProperty().get().copyOf();
|
private final ExpandClusterAction expandClusterAction;
|
||||||
//make a new filter intersecting the global filter with text(description) and type filters to restrict sub-clusters
|
|
||||||
combinedFilter.getSubFilters().addAll(new TextFilter(getEventBundle().getDescription()),
|
|
||||||
new TypeFilter(getEventType()));
|
|
||||||
return combinedFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract Collection<T> makeBundlesFromClusters(List<EventCluster> eventClusters);
|
|
||||||
|
|
||||||
abstract void showSpans(final boolean showSpans);
|
|
||||||
|
|
||||||
public AbstractDetailViewNode(EventDetailChart chart, T bundle, S parentEventNode) {
|
public AbstractDetailViewNode(EventDetailChart chart, T bundle, S parentEventNode) {
|
||||||
this.eventBundle = bundle;
|
this.eventBundle = bundle;
|
||||||
@ -189,21 +142,34 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
|
|||||||
descLOD.set(bundle.getDescriptionLOD());
|
descLOD.set(bundle.getDescriptionLOD());
|
||||||
sleuthkitCase = chart.getController().getAutopsyCase().getSleuthkitCase();
|
sleuthkitCase = chart.getController().getAutopsyCase().getSleuthkitCase();
|
||||||
eventsModel = chart.getController().getEventsModel();
|
eventsModel = chart.getController().getEventsModel();
|
||||||
|
ImageView hashIV = new ImageView(HASH_PIN);
|
||||||
|
ImageView tagIV = new ImageView(TAG);
|
||||||
if (eventBundle.getEventIDsWithHashHits().isEmpty()) {
|
if (eventBundle.getEventIDsWithHashHits().isEmpty()) {
|
||||||
show(hashIV, false);
|
show(hashIV, false);
|
||||||
}
|
}
|
||||||
if (eventBundle.getEventIDsWithTags().isEmpty()) {
|
if (eventBundle.getEventIDsWithTags().isEmpty()) {
|
||||||
show(tagIV, false);
|
show(tagIV, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expandClusterAction = new ExpandClusterAction();
|
||||||
|
plusButton = ActionUtils.createButton(expandClusterAction, ActionUtils.ActionTextBehavior.HIDE);
|
||||||
|
configureLODButton(plusButton);
|
||||||
|
|
||||||
|
collapseClusterAction = new CollapseClusterAction();
|
||||||
|
minusButton = ActionUtils.createButton(collapseClusterAction, ActionUtils.ActionTextBehavior.HIDE);
|
||||||
|
configureLODButton(minusButton);
|
||||||
|
|
||||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||||
header = new HBox(getDescrLabel(), getCountLabel(), hashIV, tagIV, /*spacer,*/ minusButton, plusButton);
|
header = new HBox(getDescrLabel(), getCountLabel(), hashIV, tagIV, /*
|
||||||
|
* spacer,
|
||||||
|
*/ minusButton, plusButton);
|
||||||
|
|
||||||
header.setMinWidth(USE_PREF_SIZE);
|
header.setMinWidth(USE_PREF_SIZE);
|
||||||
header.setPadding(new Insets(2, 5, 2, 5));
|
header.setPadding(new Insets(2, 5, 2, 5));
|
||||||
header.setAlignment(Pos.CENTER_LEFT);
|
header.setAlignment(Pos.CENTER_LEFT);
|
||||||
//setup description label
|
//setup description label
|
||||||
evtColor = getEventType().getColor();
|
evtColor = getEventType().getColor();
|
||||||
|
|
||||||
eventTypeImageView.setImage(getEventType().getFXImage());
|
eventTypeImageView.setImage(getEventType().getFXImage());
|
||||||
descrLabel.setGraphic(eventTypeImageView);
|
descrLabel.setGraphic(eventTypeImageView);
|
||||||
descrLabel.setPrefWidth(USE_COMPUTED_SIZE);
|
descrLabel.setPrefWidth(USE_COMPUTED_SIZE);
|
||||||
@ -220,7 +186,6 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
|
|||||||
|
|
||||||
setAlignment(Pos.TOP_LEFT);
|
setAlignment(Pos.TOP_LEFT);
|
||||||
setMinHeight(24);
|
setMinHeight(24);
|
||||||
|
|
||||||
setPrefHeight(USE_COMPUTED_SIZE);
|
setPrefHeight(USE_COMPUTED_SIZE);
|
||||||
setMaxHeight(USE_PREF_SIZE);
|
setMaxHeight(USE_PREF_SIZE);
|
||||||
setOnMouseClicked(new EventMouseHandler());
|
setOnMouseClicked(new EventMouseHandler());
|
||||||
@ -238,29 +203,59 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
|
|||||||
});
|
});
|
||||||
setCursor(Cursor.HAND);
|
setCursor(Cursor.HAND);
|
||||||
|
|
||||||
plusButton.disableProperty().bind(descLOD.isEqualTo(DescriptionLOD.FULL));
|
|
||||||
minusButton.disableProperty().bind(descLOD.isEqualTo(getEventBundle().getDescriptionLOD()));
|
|
||||||
|
|
||||||
plusButton.setOnMouseClicked(e -> {
|
|
||||||
final DescriptionLOD next = descLOD.get().next();
|
|
||||||
if (next != null) {
|
|
||||||
loadSubClusters(next);
|
|
||||||
descLOD.set(next);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
minusButton.setOnMouseClicked(e -> {
|
|
||||||
final DescriptionLOD previous = descLOD.get().previous();
|
|
||||||
if (previous != null) {
|
|
||||||
loadSubClusters(previous);
|
|
||||||
descLOD.set(previous);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setBackground(new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY)));
|
setBackground(new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY)));
|
||||||
|
|
||||||
setLayoutX(getChart().getXAxis().getDisplayPosition(new DateTime(eventBundle.getStartMillis())) - getLayoutXCompensation());
|
setLayoutX(getChart().getXAxis().getDisplayPosition(new DateTime(eventBundle.getStartMillis())) - getLayoutXCompensation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public List<S> getSubNodes() {
|
||||||
|
return subNodePane.getChildrenUnmodifiable().stream()
|
||||||
|
.map(t -> (S) t)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apply the 'effect' to visually indicate selection
|
||||||
|
*
|
||||||
|
* @param applied true to apply the selection 'effect', false to remove it
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void applySelectionEffect(boolean applied) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
if (applied) {
|
||||||
|
setBorder(selectionBorder);
|
||||||
|
} else {
|
||||||
|
setBorder(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param showControls the value of par
|
||||||
|
*/
|
||||||
|
void showDescriptionLoDControls(final boolean showControls) {
|
||||||
|
DropShadow dropShadow = dropShadowMap.computeIfAbsent(getEventType(),
|
||||||
|
eventType -> new DropShadow(10, eventType.getColor()));
|
||||||
|
getSpanFillNode().setEffect(showControls ? dropShadow : null);
|
||||||
|
show(minusButton, showControls);
|
||||||
|
show(plusButton, showControls);
|
||||||
|
}
|
||||||
|
|
||||||
|
RootFilter getSubClusterFilter() {
|
||||||
|
RootFilter combinedFilter = eventsModel.filterProperty().get().copyOf();
|
||||||
|
//make a new filter intersecting the global filter with text(description) and type filters to restrict sub-clusters
|
||||||
|
combinedFilter.getSubFilters().addAll(new TextFilter(getEventBundle().getDescription()),
|
||||||
|
new TypeFilter(getEventType()));
|
||||||
|
return combinedFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract Collection<T> makeBundlesFromClusters(List<EventCluster> eventClusters);
|
||||||
|
|
||||||
|
abstract void showSpans(final boolean showSpans);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param w the maximum width the description label should have
|
* @param w the maximum width the description label should have
|
||||||
*/
|
*/
|
||||||
@ -307,7 +302,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
|
|||||||
return descrLabel;
|
return descrLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final Label getCountLabel() {
|
final public Label getCountLabel() {
|
||||||
return countLabel;
|
return countLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,7 +322,6 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
|
|||||||
public DescriptionLOD getDescLOD() {
|
public DescriptionLOD getDescLOD() {
|
||||||
return descLOD.get();
|
return descLOD.get();
|
||||||
}
|
}
|
||||||
private static final Logger LOGGER = Logger.getLogger(AbstractDetailViewNode.class.getName());
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* loads sub-clusters at the given Description LOD
|
* loads sub-clusters at the given Description LOD
|
||||||
@ -358,7 +352,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
|
|||||||
eventsModel.eventTypeZoomProperty().get(),
|
eventsModel.eventTypeZoomProperty().get(),
|
||||||
combinedFilter,
|
combinedFilter,
|
||||||
newDescriptionLOD));
|
newDescriptionLOD));
|
||||||
|
|
||||||
return makeBundlesFromClusters(aggregatedEvents).stream()
|
return makeBundlesFromClusters(aggregatedEvents).stream()
|
||||||
.map(aggEvent -> {
|
.map(aggEvent -> {
|
||||||
return getNodeForCluser(aggEvent);
|
return getNodeForCluser(aggEvent);
|
||||||
@ -392,7 +386,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
final public void setDescriptionVisibility(DescriptionVisibility descrVis) {
|
public final void setDescriptionVisibility(DescriptionVisibility descrVis) {
|
||||||
this.descrVis = descrVis;
|
this.descrVis = descrVis;
|
||||||
final int size = getEventBundle().getEventIDs().size();
|
final int size = getEventBundle().getEventIDs().size();
|
||||||
|
|
||||||
@ -417,11 +411,15 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract S getNodeForCluser(T cluster);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* event handler used for mouse events on {@link AggregateEventNode}s
|
* event handler used for mouse events on {@link AggregateEventNode}s
|
||||||
*/
|
*/
|
||||||
private class EventMouseHandler implements EventHandler<MouseEvent> {
|
private class EventMouseHandler implements EventHandler<MouseEvent> {
|
||||||
|
|
||||||
|
private ContextMenu contextMenu;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(MouseEvent t) {
|
public void handle(MouseEvent t) {
|
||||||
|
|
||||||
@ -442,11 +440,55 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
|
|||||||
} else {
|
} else {
|
||||||
chart.selectedNodes.setAll(AbstractDetailViewNode.this);
|
chart.selectedNodes.setAll(AbstractDetailViewNode.this);
|
||||||
}
|
}
|
||||||
|
t.consume();
|
||||||
|
} else if (t.getButton() == MouseButton.SECONDARY) {
|
||||||
|
ContextMenu chartContextMenu = chart.getChartContextMenu(t);
|
||||||
|
if (contextMenu == null) {
|
||||||
|
contextMenu = new ContextMenu();
|
||||||
|
contextMenu.setAutoHide(true);
|
||||||
|
|
||||||
|
contextMenu.getItems().add(ActionUtils.createMenuItem(expandClusterAction));
|
||||||
|
contextMenu.getItems().add(ActionUtils.createMenuItem(collapseClusterAction));
|
||||||
|
|
||||||
|
contextMenu.getItems().add(new SeparatorMenuItem());
|
||||||
|
contextMenu.getItems().addAll(chartContextMenu.getItems());
|
||||||
|
}
|
||||||
|
contextMenu.show(AbstractDetailViewNode.this, t.getScreenX(), t.getScreenY());
|
||||||
|
t.consume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract S getNodeForCluser(T cluster);
|
private class ExpandClusterAction extends Action {
|
||||||
|
|
||||||
|
public ExpandClusterAction() {
|
||||||
|
super("Expand");
|
||||||
|
setGraphic(new ImageView(PLUS));
|
||||||
|
setEventHandler((ActionEvent t) -> {
|
||||||
|
final DescriptionLOD next = descLOD.get().next();
|
||||||
|
if (next != null) {
|
||||||
|
loadSubClusters(next);
|
||||||
|
descLOD.set(next);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
disabledProperty().bind(descLOD.isEqualTo(DescriptionLOD.FULL));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CollapseClusterAction extends Action {
|
||||||
|
|
||||||
|
public CollapseClusterAction() {
|
||||||
|
super("Collapse");
|
||||||
|
|
||||||
|
setGraphic(new ImageView(MINUS));
|
||||||
|
setEventHandler((ActionEvent t) -> {
|
||||||
|
final DescriptionLOD previous = descLOD.get().previous();
|
||||||
|
if (previous != null) {
|
||||||
|
loadSubClusters(previous);
|
||||||
|
descLOD.set(previous);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
disabledProperty().bind(descLOD.isEqualTo(getEventBundle().getDescriptionLOD()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ import java.util.HashMap;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.MissingResourceException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -106,7 +107,6 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
*/
|
*/
|
||||||
private final SimpleBooleanProperty bandByType = new SimpleBooleanProperty(false);
|
private final SimpleBooleanProperty bandByType = new SimpleBooleanProperty(false);
|
||||||
|
|
||||||
// I don't like having these package visible, but it was the easiest way to
|
|
||||||
private ContextMenu chartContextMenu;
|
private ContextMenu chartContextMenu;
|
||||||
|
|
||||||
private TimeLineController controller;
|
private TimeLineController controller;
|
||||||
@ -239,36 +239,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
chartContextMenu.hide();
|
chartContextMenu.hide();
|
||||||
}
|
}
|
||||||
if (clickEvent.getButton() == MouseButton.SECONDARY && clickEvent.isStillSincePress()) {
|
if (clickEvent.getButton() == MouseButton.SECONDARY && clickEvent.isStillSincePress()) {
|
||||||
|
getChartContextMenu(clickEvent);
|
||||||
chartContextMenu = ActionUtils.createContextMenu(Arrays.asList(new Action(
|
|
||||||
NbBundle.getMessage(this.getClass(), "EventDetailChart.chartContextMenu.placeMarker.name")) {
|
|
||||||
{
|
|
||||||
setGraphic(new ImageView(new Image("/org/sleuthkit/autopsy/timeline/images/marker.png", 16, 16, true, true, true))); // NON-NLS
|
|
||||||
setEventHandler((ActionEvent t) -> {
|
|
||||||
if (guideLine == null) {
|
|
||||||
guideLine = new GuideLine(0, 0, 0, getHeight(), dateAxis);
|
|
||||||
guideLine.relocate(clickEvent.getX(), 0);
|
|
||||||
guideLine.endYProperty().bind(heightProperty().subtract(dateAxis.heightProperty().subtract(dateAxis.tickLengthProperty())));
|
|
||||||
|
|
||||||
getChartChildren().add(guideLine);
|
|
||||||
|
|
||||||
guideLine.setOnMouseClicked((MouseEvent event) -> {
|
|
||||||
if (event.getButton() == MouseButton.SECONDARY) {
|
|
||||||
clearGuideLine();
|
|
||||||
event.consume();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
guideLine.relocate(clickEvent.getX(), 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}, new ActionGroup(
|
|
||||||
NbBundle.getMessage(this.getClass(), "EventDetailChart.contextMenu.zoomHistory.name"),
|
|
||||||
new Back(controller),
|
|
||||||
new Forward(controller))));
|
|
||||||
chartContextMenu.setAutoHide(true);
|
|
||||||
chartContextMenu.show(EventDetailChart.this, clickEvent.getScreenX(), clickEvent.getScreenY());
|
chartContextMenu.show(EventDetailChart.this, clickEvent.getScreenX(), clickEvent.getScreenY());
|
||||||
clickEvent.consume();
|
clickEvent.consume();
|
||||||
}
|
}
|
||||||
@ -317,6 +288,42 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
requestChartLayout();
|
requestChartLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException {
|
||||||
|
if (chartContextMenu != null) {
|
||||||
|
chartContextMenu.hide();
|
||||||
|
}
|
||||||
|
chartContextMenu = ActionUtils.createContextMenu(Arrays.asList(new Action(
|
||||||
|
NbBundle.getMessage(this.getClass(), "EventDetailChart.chartContextMenu.placeMarker.name")) {
|
||||||
|
{
|
||||||
|
setGraphic(new ImageView(new Image("/org/sleuthkit/autopsy/timeline/images/marker.png", 16, 16, true, true, true))); // NON-NLS
|
||||||
|
setEventHandler((ActionEvent t) -> {
|
||||||
|
if (guideLine == null) {
|
||||||
|
guideLine = new GuideLine(0, 0, 0, getHeight(), getXAxis());
|
||||||
|
guideLine.relocate(clickEvent.getX(), 0);
|
||||||
|
guideLine.endYProperty().bind(heightProperty().subtract(getXAxis().heightProperty().subtract(getXAxis().tickLengthProperty())));
|
||||||
|
|
||||||
|
getChartChildren().add(guideLine);
|
||||||
|
|
||||||
|
guideLine.setOnMouseClicked((MouseEvent event) -> {
|
||||||
|
if (event.getButton() == MouseButton.SECONDARY) {
|
||||||
|
clearGuideLine();
|
||||||
|
event.consume();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
guideLine.relocate(clickEvent.getX(), 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}, new ActionGroup(
|
||||||
|
NbBundle.getMessage(this.getClass(), "EventDetailChart.contextMenu.zoomHistory.name"),
|
||||||
|
new Back(controller),
|
||||||
|
new Forward(controller))));
|
||||||
|
chartContextMenu.setAutoHide(true);
|
||||||
|
return chartContextMenu;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearIntervalSelector() {
|
public void clearIntervalSelector() {
|
||||||
getChartChildren().remove(intervalSelector);
|
getChartChildren().remove(intervalSelector);
|
||||||
@ -736,13 +743,6 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
return filteredEvents;
|
return filteredEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the chartContextMenu
|
|
||||||
*/
|
|
||||||
public ContextMenu getChartContextMenu() {
|
|
||||||
return chartContextMenu;
|
|
||||||
}
|
|
||||||
|
|
||||||
Property<Boolean> alternateLayoutProperty() {
|
Property<Boolean> alternateLayoutProperty() {
|
||||||
return alternateLayout;
|
return alternateLayout;
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
package org.sleuthkit.autopsy.timeline.ui.detailview;
|
package org.sleuthkit.autopsy.timeline.ui.detailview;
|
||||||
|
|
||||||
import javafx.scene.Cursor;
|
import javafx.scene.Cursor;
|
||||||
|
import javafx.scene.chart.Axis;
|
||||||
import javafx.scene.control.Tooltip;
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
@ -32,7 +33,7 @@ import org.sleuthkit.autopsy.timeline.TimeLineController;
|
|||||||
*/
|
*/
|
||||||
class GuideLine extends Line {
|
class GuideLine extends Line {
|
||||||
|
|
||||||
private final DateAxis dateAxis;
|
private final Axis<DateTime> dateAxis;
|
||||||
|
|
||||||
private double startLayoutX;
|
private double startLayoutX;
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ class GuideLine extends Line {
|
|||||||
|
|
||||||
private double dragStartX = 0;
|
private double dragStartX = 0;
|
||||||
|
|
||||||
GuideLine(double startX, double startY, double endX, double endY, DateAxis axis) {
|
GuideLine(double startX, double startY, double endX, double endY, Axis<DateTime> axis) {
|
||||||
super(startX, startY, endX, endY);
|
super(startX, startY, endX, endY);
|
||||||
dateAxis = axis;
|
dateAxis = axis;
|
||||||
setCursor(Cursor.E_RESIZE);
|
setCursor(Cursor.E_RESIZE);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user