extend DetailViewNode interface to support more features of old visualization in new mode.

This commit is contained in:
jmillman 2015-09-02 15:56:46 -04:00
parent 874703030d
commit 2250b01a56
6 changed files with 163 additions and 140 deletions

View File

@ -22,8 +22,9 @@ package org.sleuthkit.autopsy.timeline.ui.detailview;
* Level of description shown in UI NOTE: this is a separate concept form * Level of description shown in UI NOTE: this is a separate concept form
* {@link DescriptionLOD} * {@link DescriptionLOD}
*/ */
enum DescriptionVisibility { public enum DescriptionVisibility {
HIDDEN, COUNT_ONLY, SHOWN;
HIDDEN,
COUNT_ONLY,
SHOWN;
} }

View File

@ -7,22 +7,21 @@ package org.sleuthkit.autopsy.timeline.ui.detailview;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import javafx.scene.layout.Pane;
import org.sleuthkit.autopsy.timeline.datamodel.EventBundle; import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
/** /**
* *
*/ */
public interface DetailViewNode { public interface DetailViewNode<S extends DetailViewNode<S>> {
long getStartMillis(); public long getStartMillis();
long getEndMillis(); public long getEndMillis();
public void setDescriptionVisibility(DescriptionVisibility get); public void setDescriptionVisibility(DescriptionVisibility get);
public Pane getSubNodePane(); public List<S> getSubNodes();
public void setSpanWidths(List<Double> spanWidths); public void setSpanWidths(List<Double> spanWidths);
@ -32,10 +31,16 @@ public interface DetailViewNode {
public Set<Long> getEventIDs(); public Set<Long> getEventIDs();
public void applySelectionEffect(boolean applied);
public String getDescription(); public String getDescription();
public EventBundle getBundleDescriptor(); public EventBundle getBundleDescriptor();
/**
* apply the 'effect' to visually indicate highlighted nodes
*
* @param applied true to apply the highlight 'effect', false to remove it
*/
void applyHighlightEffect(boolean applied);
public void applySelectionEffect(boolean applied);
} }

View File

@ -30,7 +30,6 @@ import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import javafx.event.EventHandler;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.Orientation; import javafx.geometry.Orientation;
import javafx.scene.Cursor; import javafx.scene.Cursor;
@ -101,7 +100,7 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
* TODO: refactor common code out of this class and CountsChartPane into * TODO: refactor common code out of this class and CountsChartPane into
* {@link AbstractVisualization} * {@link AbstractVisualization}
*/ */
public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster, DetailViewNode, EventDetailChart> { public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster, DetailViewNode<?>, EventDetailChart> {
private final static Logger LOGGER = Logger.getLogger(CountsViewPane.class.getName()); private final static Logger LOGGER = Logger.getLogger(CountsViewPane.class.getName());
@ -121,7 +120,7 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
private final ObservableList<EventCluster> aggregatedEvents = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); private final ObservableList<EventCluster> aggregatedEvents = FXCollections.synchronizedObservableList(FXCollections.observableArrayList());
private final ObservableList<EventClusterNode> highlightedNodes = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); private final ObservableList<DetailViewNode<?>> highlightedNodes = FXCollections.synchronizedObservableList(FXCollections.observableArrayList());
public ObservableList<EventCluster> getAggregatedEvents() { public ObservableList<EventCluster> getAggregatedEvents() {
return aggregatedEvents; return aggregatedEvents;
@ -150,7 +149,7 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
vertScrollBar.visibleAmountProperty().bind(chart.heightProperty().multiply(100).divide(chart.getMaxVScroll())); vertScrollBar.visibleAmountProperty().bind(chart.heightProperty().multiply(100).divide(chart.getMaxVScroll()));
requestLayout(); requestLayout();
highlightedNodes.addListener((ListChangeListener.Change<? extends EventClusterNode> change) -> { highlightedNodes.addListener((ListChangeListener.Change<? extends DetailViewNode<?>> change) -> {
while (change.next()) { while (change.next()) {
change.getAddedSubList().forEach(aeNode -> { change.getAddedSubList().forEach(aeNode -> {
aeNode.applyHighlightEffect(true); aeNode.applyHighlightEffect(true);
@ -167,7 +166,7 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
//These scroll related handlers don't affect any other view or the model, so they are handled internally //These scroll related handlers don't affect any other view or the model, so they are handled internally
//mouse wheel scroll handler //mouse wheel scroll handler
this.onScrollProperty().set((EventHandler<ScrollEvent>) (ScrollEvent t) -> { this.onScrollProperty().set((ScrollEvent t) -> {
vertScrollBar.valueProperty().set(Math.max(0, Math.min(100, vertScrollBar.getValue() - t.getDeltaY() / 200.0))); vertScrollBar.valueProperty().set(Math.max(0, Math.min(100, vertScrollBar.getValue() - t.getDeltaY() / 200.0)));
}); });
@ -213,8 +212,8 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
selectedNodes.addListener((Observable observable) -> { selectedNodes.addListener((Observable observable) -> {
highlightedNodes.clear(); highlightedNodes.clear();
selectedNodes.stream().forEach((tn) -> { selectedNodes.stream().forEach((tn) -> {
for (EventClusterNode n : chart.getNodes((EventClusterNode t) for (DetailViewNode<?> n : chart.getNodes((DetailViewNode<?> t)
-> t.getEvent().getDescription().equals(tn.getDescription()))) { -> t.getDescription().equals(tn.getDescription()))) {
highlightedNodes.add(n); highlightedNodes.add(n);
} }
}); });
@ -237,8 +236,8 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
treeSelectionModel.getSelectedItems().addListener((Observable observable) -> { treeSelectionModel.getSelectedItems().addListener((Observable observable) -> {
highlightedNodes.clear(); highlightedNodes.clear();
for (TreeItem<NavTreeNode> tn : treeSelectionModel.getSelectedItems()) { for (TreeItem<NavTreeNode> tn : treeSelectionModel.getSelectedItems()) {
for (EventClusterNode n : chart.getNodes((EventClusterNode t) for (DetailViewNode<?> n : chart.getNodes((DetailViewNode<?> t)
-> t.getEvent().getDescription().equals(tn.getValue().getDescription()))) { -> t.getDescription().equals(tn.getValue().getDescription()))) {
highlightedNodes.add(n); highlightedNodes.add(n);
} }
} }
@ -358,7 +357,7 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
} }
@Override @Override
protected void applySelectionEffect(DetailViewNode c1, Boolean applied) { protected void applySelectionEffect(DetailViewNode<?> c1, Boolean applied) {
c1.applySelectionEffect(applied); c1.applySelectionEffect(applied);
} }

View File

@ -80,7 +80,7 @@ import org.sleuthkit.datamodel.TskCoreException;
/** /**
* Represents an {@link EventCluster} in a {@link EventDetailChart}. * Represents an {@link EventCluster} in a {@link EventDetailChart}.
*/ */
public class EventClusterNode extends StackPane implements DetailViewNode { public class EventClusterNode extends StackPane implements DetailViewNode<EventClusterNode> {
private static final Logger LOGGER = Logger.getLogger(EventClusterNode.class.getName()); private static final Logger LOGGER = Logger.getLogger(EventClusterNode.class.getName());
@ -148,18 +148,23 @@ public class EventClusterNode extends StackPane implements DetailViewNode {
private final Button plusButton = new Button(null, new ImageView(PLUS)) { private final Button plusButton = new Button(null, new ImageView(PLUS)) {
{ {
setMinSize(16, 16); configureLODButton(this);
setMaxSize(16, 16);
setPrefSize(16, 16);
} }
}; };
private final Button minusButton = new Button(null, new ImageView(MINUS)) { private final Button minusButton = new Button(null, new ImageView(MINUS)) {
{ {
setMinSize(16, 16); configureLODButton(this);
setMaxSize(16, 16);
setPrefSize(16, 16);
} }
}; };
private static void configureLODButton(Button b) {
b.setMinSize(16, 16);
b.setMaxSize(16, 16);
b.setPrefSize(16, 16);
b.setVisible(false);
b.setManaged(false);
}
private final EventDetailChart chart; private final EventDetailChart chart;
private SimpleObjectProperty<DescriptionLOD> descLOD = new SimpleObjectProperty<>(); private SimpleObjectProperty<DescriptionLOD> descLOD = new SimpleObjectProperty<>();
@ -196,10 +201,6 @@ public class EventClusterNode extends StackPane implements DetailViewNode {
hBox.setPadding(new Insets(2, 5, 2, 5)); hBox.setPadding(new Insets(2, 5, 2, 5));
hBox.setAlignment(Pos.CENTER_LEFT); hBox.setAlignment(Pos.CENTER_LEFT);
minusButton.setVisible(false);
plusButton.setVisible(false);
minusButton.setManaged(false);
plusButton.setManaged(false);
final BorderPane borderPane = new BorderPane(subNodePane, hBox, null, null, null); final BorderPane borderPane = new BorderPane(subNodePane, hBox, null, null, null);
BorderPane.setAlignment(subNodePane, Pos.TOP_LEFT); BorderPane.setAlignment(subNodePane, Pos.TOP_LEFT);
borderPane.setPrefWidth(USE_COMPUTED_SIZE); borderPane.setPrefWidth(USE_COMPUTED_SIZE);
@ -322,8 +323,10 @@ public class EventClusterNode extends StackPane implements DetailViewNode {
} }
@Override @Override
public Pane getSubNodePane() { public List<EventClusterNode> getSubNodes() {
return subNodePane; return subNodePane.getChildrenUnmodifiable().stream()
.map(EventClusterNode.class::cast)
.collect(Collectors.toList());
} }
synchronized public EventCluster getEvent() { synchronized public EventCluster getEvent() {
@ -413,7 +416,7 @@ public class EventClusterNode extends StackPane implements DetailViewNode {
* *
* @param applied true to apply the highlight 'effect', false to remove it * @param applied true to apply the highlight 'effect', false to remove it
*/ */
synchronized void applyHighlightEffect(boolean applied) { public synchronized void applyHighlightEffect(boolean applied) {
if (applied) { if (applied) {
descrLabel.setStyle("-fx-font-weight: bold;"); // NON-NLS descrLabel.setStyle("-fx-font-weight: bold;"); // NON-NLS
@ -457,7 +460,7 @@ public class EventClusterNode extends StackPane implements DetailViewNode {
* @param newDescriptionLOD * @param newDescriptionLOD
*/ */
synchronized private void loadSubClusters(DescriptionLOD newDescriptionLOD) { synchronized private void loadSubClusters(DescriptionLOD newDescriptionLOD) {
getSubNodePane().getChildren().clear(); subNodePane.getChildren().clear();
if (newDescriptionLOD == aggEvent.getDescriptionLOD()) { if (newDescriptionLOD == aggEvent.getDescriptionLOD()) {
chart.setRequiresLayout(true); chart.setRequiresLayout(true);
chart.requestChartLayout(); chart.requestChartLayout();
@ -494,7 +497,7 @@ public class EventClusterNode extends StackPane implements DetailViewNode {
try { try {
chart.setCursor(Cursor.WAIT); chart.setCursor(Cursor.WAIT);
//assign subNodes and request chart layout //assign subNodes and request chart layout
getSubNodePane().getChildren().setAll(get()); subNodePane.getChildren().setAll(get());
setDescriptionVisibility(descrVis); setDescriptionVisibility(descrVis);
chart.setRequiresLayout(true); chart.setRequiresLayout(true);
chart.requestChartLayout(); chart.requestChartLayout();

View File

@ -31,6 +31,7 @@ import java.util.Map;
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;
import java.util.stream.Stream;
import javafx.animation.KeyFrame; import javafx.animation.KeyFrame;
import javafx.animation.KeyValue; import javafx.animation.KeyValue;
import javafx.animation.Timeline; import javafx.animation.Timeline;
@ -167,7 +168,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
*/ */
private final SimpleBooleanProperty oneEventPerRow = new SimpleBooleanProperty(false); private final SimpleBooleanProperty oneEventPerRow = new SimpleBooleanProperty(false);
private final ObservableMap<DetailViewNode, Line> projectionMap = FXCollections.observableHashMap(); private final ObservableMap<DetailViewNode<?>, Line> projectionMap = FXCollections.observableHashMap();
/** /**
* flag indicating whether this chart actually needs a layout pass * flag indicating whether this chart actually needs a layout pass
@ -175,7 +176,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
@GuardedBy(value = "this") @GuardedBy(value = "this")
private boolean requiresLayout = true; private boolean requiresLayout = true;
final ObservableList<DetailViewNode> selectedNodes; final ObservableList<DetailViewNode<?>> selectedNodes;
/** /**
* list of series of data added to this chart TODO: replace this with a map * list of series of data added to this chart TODO: replace this with a map
@ -205,7 +206,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
private final SimpleDoubleProperty truncateWidth = new SimpleDoubleProperty(200.0); private final SimpleDoubleProperty truncateWidth = new SimpleDoubleProperty(200.0);
private final SimpleBooleanProperty alternateLayout = new SimpleBooleanProperty(true); private final SimpleBooleanProperty alternateLayout = new SimpleBooleanProperty(true);
EventDetailChart(DateAxis dateAxis, final Axis<EventCluster> verticalAxis, ObservableList<DetailViewNode> selectedNodes) { EventDetailChart(DateAxis dateAxis, final Axis<EventCluster> verticalAxis, ObservableList<DetailViewNode<?>> selectedNodes) {
super(dateAxis, verticalAxis); super(dateAxis, verticalAxis);
dateAxis.setAutoRanging(false); dateAxis.setAutoRanging(false);
@ -285,7 +286,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
setOnMouseReleased(dragHandler); setOnMouseReleased(dragHandler);
setOnMouseDragged(dragHandler); setOnMouseDragged(dragHandler);
projectionMap.addListener((MapChangeListener.Change<? extends DetailViewNode, ? extends Line> change) -> { projectionMap.addListener((MapChangeListener.Change<? extends DetailViewNode<?>, ? extends Line> change) -> {
final Line valueRemoved = change.getValueRemoved(); final Line valueRemoved = change.getValueRemoved();
if (valueRemoved != null) { if (valueRemoved != null) {
getChartChildren().removeAll(valueRemoved); getChartChildren().removeAll(valueRemoved);
@ -298,12 +299,12 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
this.selectedNodes = selectedNodes; this.selectedNodes = selectedNodes;
this.selectedNodes.addListener(( this.selectedNodes.addListener((
ListChangeListener.Change<? extends DetailViewNode> c) -> { ListChangeListener.Change<? extends DetailViewNode<?>> c) -> {
while (c.next()) { while (c.next()) {
c.getRemoved().forEach((DetailViewNode t) -> { c.getRemoved().forEach((DetailViewNode<?> t) -> {
projectionMap.remove(t); projectionMap.remove(t);
}); });
c.getAddedSubList().forEach((DetailViewNode t) -> { c.getAddedSubList().forEach((DetailViewNode<?> t) -> {
Line line = new Line(dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(t.getStartMillis(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET, Line line = new Line(dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(t.getStartMillis(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET,
dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(t.getEndMillis(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(t.getEndMillis(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET
); );
@ -316,7 +317,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
} }
this.controller.selectEventIDs(selectedNodes.stream() this.controller.selectEventIDs(selectedNodes.stream()
.flatMap((DetailViewNode aggNode) -> aggNode.getEventIDs().stream()) .flatMap(detailNode -> detailNode.getEventIDs().stream())
.collect(Collectors.toList())); .collect(Collectors.toList()));
}); });
@ -420,7 +421,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
return EventStripe.merge(u, v); return EventStripe.merge(u, v);
} }
); );
EventStripeNode clusterNode = new EventStripeNode(eventCluster,null, EventDetailChart.this); EventStripeNode clusterNode = new EventStripeNode(eventCluster, null, EventDetailChart.this);
stripeNodeMap.put(eventCluster, clusterNode); stripeNodeMap.put(eventCluster, clusterNode);
nodeGroup.getChildren().add(clusterNode); nodeGroup.getChildren().add(clusterNode);
} else { } else {
@ -486,11 +487,11 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
if (bandByType.get() == false) { if (bandByType.get() == false) {
if (alternateLayout.get() == true) { if (alternateLayout.get() == true) {
List<EventStripeNode> nodes = new ArrayList<>(stripeNodeMap.values()); List<EventStripeNode> nodes = new ArrayList<>(stripeNodeMap.values());
Collections.sort(nodes, Comparator.comparing(DetailViewNode::getStartMillis)); nodes.sort(Comparator.comparing(DetailViewNode<?>::getStartMillis));
layoutNodes(nodes, minY, 0); layoutNodes(nodes, minY, 0);
} else { } else {
List<EventClusterNode> nodes = new ArrayList<>(nodeMap.values()); List<EventClusterNode> nodes = new ArrayList<>(nodeMap.values());
Collections.sort(nodes, Comparator.comparing(DetailViewNode::getStartMillis)); nodes.sort(Comparator.comparing(DetailViewNode<?>::getStartMillis));
layoutNodes(nodes, minY, 0); layoutNodes(nodes, minY, 0);
} }
@ -545,22 +546,28 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
return descrVisibility; return descrVisibility;
} }
synchronized ReadOnlyDoubleProperty synchronized ReadOnlyDoubleProperty getMaxVScroll() {
getMaxVScroll() {
return maxY.getReadOnlyProperty(); return maxY.getReadOnlyProperty();
} }
Iterable<EventClusterNode> getNodes(Predicate<EventClusterNode> p) { Iterable<DetailViewNode<?>> getNodes(Predicate<DetailViewNode<?>> p) {
List<EventClusterNode> nodes = new ArrayList<>(); Collection<? extends DetailViewNode<?>> values = alternateLayout.get()
? stripeNodeMap.values()
: nodeMap.values();
for (EventClusterNode node : nodeMap.values()) { //collapse tree of DetailViewNoeds to list and then filter on given predicate
checkNode(node, p, nodes); return values.stream()
} .flatMap(EventDetailChart::flatten)
.filter(p).collect(Collectors.toList());
return nodes;
} }
Iterable<EventClusterNode> getAllNodes() { public static Stream<? extends DetailViewNode<?>> flatten(DetailViewNode<?> node) {
return Stream.concat(
Stream.of(node),
node.getSubNodes().stream().flatMap(EventDetailChart::flatten));
}
Iterable<DetailViewNode<?>> getAllNodes() {
return getNodes(x -> true); return getNodes(x -> true);
} }
@ -576,17 +583,6 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
nodeGroup.setTranslateY(-d * h); nodeGroup.setTranslateY(-d * h);
} }
private static void checkNode(EventClusterNode node, Predicate<EventClusterNode> p, List<EventClusterNode> nodes) {
if (node != null) {
if (p.test(node)) {
nodes.add(node);
}
for (Node n : node.getSubNodePane().getChildrenUnmodifiable()) {
checkNode((EventClusterNode) n, p, nodes);
}
}
}
private void clearGuideLine() { private void clearGuideLine() {
getChartChildren().remove(guideLine); getChartChildren().remove(guideLine);
guideLine = null; guideLine = null;
@ -599,12 +595,12 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
* @param nodes * @param nodes
* @param minY * @param minY
*/ */
private synchronized <D extends Region & DetailViewNode> double layoutNodes(final Collection<D> nodes, final double minY, final double xOffset) { private synchronized <DVRegion extends Region & DetailViewNode<DVRegion>> double layoutNodes(final Collection<DVRegion> nodes, final double minY, final double xOffset) {
//hash map from y value to right most occupied x value. This tells you for a given 'row' what is the first avaialable slot //hash map from y value to right most occupied x value. This tells you for a given 'row' what is the first avaialable slot
Map<Integer, Double> maxXatY = new HashMap<>(); Map<Integer, Double> maxXatY = new HashMap<>();
double localMax = minY; double localMax = minY;
//for each node lay size it and position it in first available slot //for each node lay size it and position it in first available slot
for (D n : nodes) { for (DVRegion n : nodes) {
n.setDescriptionVisibility(descrVisibility.get()); n.setDescriptionVisibility(descrVisibility.get());
double rawDisplayPosition = getXAxis().getDisplayPosition(new DateTime(n.getStartMillis())); double rawDisplayPosition = getXAxis().getDisplayPosition(new DateTime(n.getStartMillis()));
@ -613,27 +609,19 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
double layoutNodesResultHeight = 0; double layoutNodesResultHeight = 0;
double span = 0; double span = 0;
List<DVRegion> subNodes = n.getSubNodes();
if (subNodes.isEmpty() == false) {
subNodes.sort(Comparator.comparing((DVRegion t) -> t.getStartMillis()));
layoutNodesResultHeight = layoutNodes(subNodes, 0, rawDisplayPosition);
}
if (n instanceof EventClusterNode) { if (n instanceof EventClusterNode) {
if (n.getSubNodePane().getChildren().isEmpty() == false) {
List<EventClusterNode> children = n.getSubNodePane().getChildren().stream()
.map(EventClusterNode.class::cast)
.sorted(Comparator.comparing(DetailViewNode::getStartMillis))
.collect(Collectors.toList());
layoutNodesResultHeight = layoutNodes(children, 0, rawDisplayPosition);
}
double endX = getXAxis().getDisplayPosition(new DateTime(n.getEndMillis())) - xOffset; double endX = getXAxis().getDisplayPosition(new DateTime(n.getEndMillis())) - xOffset;
span = endX - startX; span = endX - startX;
//size timespan border //size timespan border
n.setSpanWidths(Arrays.asList(span)); n.setSpanWidths(Arrays.asList(span));
} else { } else {
if (n.getSubNodePane().getChildren().isEmpty() == false) {
List<EventStripeNode> children = n.getSubNodePane().getChildren().stream()
.map(EventStripeNode.class::cast)
.sorted(Comparator.comparing(DetailViewNode::getStartMillis))
.collect(Collectors.toList());
layoutNodesResultHeight = layoutNodes(children, 0, rawDisplayPosition);
}
EventStripeNode cn = (EventStripeNode) n; EventStripeNode cn = (EventStripeNode) n;
List<Double> spanWidths = new ArrayList<>(); List<Double> spanWidths = new ArrayList<>();
double x = getXAxis().getDisplayPosition(new DateTime(cn.getStartMillis()));; double x = getXAxis().getDisplayPosition(new DateTime(cn.getStartMillis()));;
@ -720,8 +708,8 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
private static final int DEFAULT_ROW_HEIGHT = 24; private static final int DEFAULT_ROW_HEIGHT = 24;
private void layoutProjectionMap() { private void layoutProjectionMap() {
for (final Map.Entry<DetailViewNode, Line> entry : projectionMap.entrySet()) { for (final Map.Entry<DetailViewNode<?>, Line> entry : projectionMap.entrySet()) {
final DetailViewNode aggNode = entry.getKey(); final DetailViewNode<?> aggNode = entry.getKey();
final Line line = entry.getValue(); final Line line = entry.getValue();
line.setStartX(getParentXForValue(new DateTime(aggNode.getStartMillis(), TimeLineController.getJodaTimeZone()))); line.setStartX(getParentXForValue(new DateTime(aggNode.getStartMillis(), TimeLineController.getJodaTimeZone())));
@ -760,7 +748,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
return alternateLayout; return alternateLayout;
} }
private static class StartTimeComparator<T extends Node & DetailViewNode> implements Comparator<T> { private static class StartTimeComparator<T extends Node & DetailViewNode<?>> implements Comparator<T> {
@Override @Override
public int compare(T n1, T n2) { public int compare(T n1, T n2) {

View File

@ -18,6 +18,7 @@ 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.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.OverrunStyle; import javafx.scene.control.OverrunStyle;
@ -29,7 +30,6 @@ import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background; import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.Border; import javafx.scene.layout.Border;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.BorderStroke; import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle; import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths; import javafx.scene.layout.BorderWidths;
@ -41,6 +41,7 @@ import javafx.scene.layout.Region;
import static javafx.scene.layout.Region.USE_COMPUTED_SIZE; import static javafx.scene.layout.Region.USE_COMPUTED_SIZE;
import static javafx.scene.layout.Region.USE_PREF_SIZE; import static javafx.scene.layout.Region.USE_PREF_SIZE;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime; import org.joda.time.DateTime;
@ -64,7 +65,7 @@ import org.sleuthkit.datamodel.SleuthkitCase;
/** /**
* *
*/ */
public class EventStripeNode extends StackPane implements DetailViewNode { public class EventStripeNode extends StackPane implements DetailViewNode<EventStripeNode> {
private static final Logger LOGGER = Logger.getLogger(EventClusterNode.class.getName()); private static final Logger LOGGER = Logger.getLogger(EventClusterNode.class.getName());
@ -94,20 +95,28 @@ public class EventStripeNode extends StackPane implements DetailViewNode {
private final ImageView tagIV = new ImageView(TAG); private final ImageView tagIV = new ImageView(TAG);
private final Button plusButton = new Button(null, new ImageView(PLUS)) { private final Button plusButton = new Button(null, new ImageView(PLUS)) {
{ {
setMinSize(16, 16); configureLODButton(this);
setMaxSize(16, 16);
setPrefSize(16, 16);
} }
}; };
private final Button minusButton = new Button(null, new ImageView(MINUS)) { private final Button minusButton = new Button(null, new ImageView(MINUS)) {
{ {
setMinSize(16, 16); configureLODButton(this);
setMaxSize(16, 16);
setPrefSize(16, 16);
} }
}; };
private static void configureLODButton(Button b) {
b.setMinSize(16, 16);
b.setMaxSize(16, 16);
b.setPrefSize(16, 16);
show(b, false);
}
private static void show(Node b, boolean show) {
b.setVisible(show);
b.setManaged(show);
}
private DescriptionVisibility descrVis; private DescriptionVisibility descrVis;
private final HBox spanRegion = new HBox(); private final HBox spansHBox = new HBox();
/** /**
* The IamgeView used to show the icon for this node's event's type * The IamgeView used to show the icon for this node's event's type
*/ */
@ -127,43 +136,36 @@ public class EventStripeNode extends StackPane implements DetailViewNode {
final Region spacer = new Region(); final Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS); HBox.setHgrow(spacer, Priority.ALWAYS);
final HBox hBox = new HBox(descrLabel, countLabel, spacer, hashIV, tagIV, minusButton, plusButton); final HBox header = new HBox(descrLabel, countLabel, hashIV, tagIV, spacer, minusButton, plusButton);
if (cluster.getEventIDsWithHashHits().isEmpty()) { if (cluster.getEventIDsWithHashHits().isEmpty()) {
hashIV.setManaged(false); show(hashIV, false);
hashIV.setVisible(false);
} }
if (cluster.getEventIDsWithTags().isEmpty()) { if (cluster.getEventIDsWithTags().isEmpty()) {
tagIV.setManaged(false); show(tagIV, false);
tagIV.setVisible(false);
} }
hBox.setPrefWidth(USE_COMPUTED_SIZE); header.setMinWidth(USE_PREF_SIZE);
hBox.setMinWidth(USE_PREF_SIZE); header.setPadding(new Insets(2, 5, 2, 5));
hBox.setPadding(new Insets(2, 5, 2, 5)); header.setAlignment(Pos.CENTER_LEFT);
hBox.setAlignment(Pos.CENTER_LEFT);
minusButton.setVisible(false); final VBox internalVBox = new VBox(header, subNodePane);
plusButton.setVisible(false); internalVBox.setAlignment(Pos.CENTER_LEFT);
minusButton.setManaged(false);
plusButton.setManaged(false);
final BorderPane borderPane = new BorderPane(subNodePane, hBox, null, null, null);
BorderPane.setAlignment(subNodePane, Pos.TOP_LEFT);
borderPane.setPrefWidth(USE_COMPUTED_SIZE);
final Color evtColor = cluster.getType().getColor(); final Color evtColor = cluster.getType().getColor();
spanFill = new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .2), CORNER_RADII, Insets.EMPTY)); spanFill = new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .2), CORNER_RADII, Insets.EMPTY));
for (Range<Long> r : cluster.getRanges()) { for (Range<Long> r : cluster.getRanges()) {
Region region = new Region(); Region spanRegion = new Region();
region.setStyle("-fx-border-width:2 1 2 1; -fx-border-radius: 1; -fx-border-color: " + ColorUtilities.getRGBCode(evtColor.deriveColor(0, 1, 1, .3)) + ";"); // NON-NLS spanRegion.setStyle("-fx-border-width:2 1 2 1; -fx-border-radius: 1; -fx-border-color: " + ColorUtilities.getRGBCode(evtColor.deriveColor(0, 1, 1, .3)) + ";"); // NON-NLS
region.setBackground(spanFill); spanRegion.setBackground(spanFill);
spanRegion.getChildren().addAll(region, new Region()); spansHBox.getChildren().addAll(spanRegion, new Region());
} }
spanRegion.getChildren().remove(spanRegion.getChildren().size() - 1); spansHBox.getChildren().remove(spansHBox.getChildren().size() - 1);
spansHBox.setMaxWidth(USE_PREF_SIZE);
getChildren().addAll(spanRegion, borderPane); setMaxWidth(USE_PREF_SIZE);
setBackground(new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY))); getChildren().addAll(spansHBox, internalVBox);
setBackground(new Background(new BackgroundFill(evtColor.deriveColor(0, 1, 1, .05), CORNER_RADII, Insets.EMPTY)));
setAlignment(Pos.TOP_LEFT); setAlignment(Pos.TOP_LEFT);
setMinHeight(24); setMinHeight(24);
minWidthProperty().bind(spanRegion.widthProperty()); minWidthProperty().bind(spansHBox.widthProperty());
setPrefHeight(USE_COMPUTED_SIZE); setPrefHeight(USE_COMPUTED_SIZE);
setMaxHeight(USE_PREF_SIZE); setMaxHeight(USE_PREF_SIZE);
@ -189,20 +191,20 @@ public class EventStripeNode extends StackPane implements DetailViewNode {
setOnMouseEntered((MouseEvent e) -> { setOnMouseEntered((MouseEvent e) -> {
//defer tooltip creation till needed, this had a surprisingly large impact on speed of loading the chart //defer tooltip creation till needed, this had a surprisingly large impact on speed of loading the chart
// installTooltip(); // installTooltip();
spanRegion.setEffect(new DropShadow(10, evtColor)); spansHBox.setEffect(new DropShadow(10, evtColor));
minusButton.setVisible(true); show(spacer, true);
plusButton.setVisible(true); show(minusButton, true);
minusButton.setManaged(true); show(plusButton, true);
plusButton.setManaged(true);
toFront(); toFront();
}); });
setOnMouseExited((MouseEvent e) -> { setOnMouseExited((MouseEvent e) -> {
spanRegion.setEffect(null); spansHBox.setEffect(null);
minusButton.setVisible(false); show(spacer, false);
plusButton.setVisible(false); show(minusButton, false);
minusButton.setManaged(false); show(plusButton, false);
plusButton.setManaged(false);
}); });
plusButton.disableProperty().bind(descLOD.isEqualTo(DescriptionLOD.FULL)); plusButton.disableProperty().bind(descLOD.isEqualTo(DescriptionLOD.FULL));
@ -232,11 +234,11 @@ public class EventStripeNode extends StackPane implements DetailViewNode {
@Override @Override
public void setSpanWidths(List<Double> spanWidths) { public void setSpanWidths(List<Double> spanWidths) {
for (int i = 0; i < spanWidths.size(); i++) { for (int i = 0; i < spanWidths.size(); i++) {
Region get = (Region) spanRegion.getChildren().get(i); Region spanRegion = (Region) spansHBox.getChildren().get(i);
Double w = spanWidths.get(i); Double w = spanWidths.get(i);
get.setPrefWidth(w); spanRegion.setPrefWidth(w);
get.setMaxWidth(w); spanRegion.setMaxWidth(w);
get.setMinWidth(Math.max(2, w)); spanRegion.setMinWidth(Math.max(2, w));
} }
} }
@ -280,8 +282,10 @@ public class EventStripeNode extends StackPane implements DetailViewNode {
} }
@Override @Override
public Pane getSubNodePane() { public List<EventStripeNode> getSubNodes() {
return subNodePane; return subNodePane.getChildrenUnmodifiable().stream()
.map(EventStripeNode.class::cast)
.collect(Collectors.toList());
} }
/** /**
@ -339,6 +343,26 @@ public class EventStripeNode extends StackPane implements DetailViewNode {
}); });
} }
/**
* apply the 'effect' to visually indicate highlighted nodes
*
* @param applied true to apply the highlight 'effect', false to remove it
*/
@Override
public synchronized void applyHighlightEffect(boolean applied) {
if (applied) {
descrLabel.setStyle("-fx-font-weight: bold;"); // NON-NLS
spanFill = new Background(new BackgroundFill(cluster.getType().getColor().deriveColor(0, 1, 1, .3), CORNER_RADII, Insets.EMPTY));
spansHBox.setBackground(spanFill);
setBackground(new Background(new BackgroundFill(cluster.getType().getColor().deriveColor(0, 1, 1, .2), CORNER_RADII, Insets.EMPTY)));
} else {
descrLabel.setStyle("-fx-font-weight: normal;"); // NON-NLS
spanFill = new Background(new BackgroundFill(cluster.getType().getColor().deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY));
spansHBox.setBackground(spanFill);
setBackground(new Background(new BackgroundFill(cluster.getType().getColor().deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY)));
}
}
@Override @Override
public String getDescription() { public String getDescription() {
return cluster.getDescription(); return cluster.getDescription();
@ -355,11 +379,14 @@ public class EventStripeNode extends StackPane implements DetailViewNode {
* @param newDescriptionLOD * @param newDescriptionLOD
*/ */
synchronized private void loadSubClusters(DescriptionLOD newDescriptionLOD) { synchronized private void loadSubClusters(DescriptionLOD newDescriptionLOD) {
getSubNodePane().getChildren().clear(); subNodePane.getChildren().clear();
if (newDescriptionLOD == cluster.getDescriptionLOD()) { if (newDescriptionLOD == cluster.getDescriptionLOD()) {
spansHBox.setVisible(true);
chart.setRequiresLayout(true); chart.setRequiresLayout(true);
chart.requestChartLayout(); chart.requestChartLayout();
} else { } else {
spansHBox.setVisible(false);
RootFilter combinedFilter = eventsModel.filterProperty().get().copyOf(); RootFilter combinedFilter = eventsModel.filterProperty().get().copyOf();
//make a new filter intersecting the global filter with text(description) and type filters to restrict sub-clusters //make a new filter intersecting the global filter with text(description) and type filters to restrict sub-clusters
combinedFilter.getSubFilters().addAll(new TextFilter(cluster.getDescription()), combinedFilter.getSubFilters().addAll(new TextFilter(cluster.getDescription()),
@ -402,7 +429,7 @@ public class EventStripeNode extends StackPane implements DetailViewNode {
try { try {
chart.setCursor(Cursor.WAIT); chart.setCursor(Cursor.WAIT);
//assign subNodes and request chart layout //assign subNodes and request chart layout
getSubNodePane().getChildren().setAll(get()); subNodePane.getChildren().setAll(get());
setDescriptionVisibility(descrVis); setDescriptionVisibility(descrVis);
chart.setRequiresLayout(true); chart.setRequiresLayout(true);
chart.requestChartLayout(); chart.requestChartLayout();