details view node / tree clean up and bugfixes

reinstate wrongly removed layout constraints;  bind subnodepane children to subnodes list; keep tree and main visualization in sync better
This commit is contained in:
jmillman 2015-11-24 12:49:02 -05:00
parent c534d1dc51
commit 883e48b043
6 changed files with 61 additions and 25 deletions

View File

@ -180,6 +180,4 @@ public final class EventStripe implements EventBundle<EventCluster> {
public String toString() {
return "EventStripe{" + "description=" + description + ", eventIDs=" + eventIDs.size() + '}';
}
}

View File

@ -18,7 +18,6 @@
*/
package org.sleuthkit.autopsy.timeline.ui.detailview;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@ -32,7 +31,10 @@ import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
@ -120,7 +122,7 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
final Background defaultBackground;
final Color evtColor;
final List<ParentNodeType> subNodes = new ArrayList<>();
final ObservableList<ParentNodeType> subNodes = FXCollections.observableArrayList();
final Pane subNodePane = new Pane();
final Label descrLabel = new Label();
final Label countLabel = new Label();
@ -152,7 +154,11 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
setBackground(defaultBackground);
setAlignment(Pos.TOP_LEFT);
setMaxWidth(USE_PREF_SIZE);
infoHBox.setMaxWidth(USE_PREF_SIZE);
subNodePane.setPrefWidth(USE_COMPUTED_SIZE);
subNodePane.setMinWidth(USE_PREF_SIZE);
subNodePane.setMaxWidth(USE_PREF_SIZE);
/*
* This triggers the layout when a mousover causes the action buttons to
* interesect with another node, forcing it down.
@ -185,6 +191,8 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
setOnMouseClicked(new ClickHandler());
descVisibility.addListener(observable -> setDescriptionVisibiltiyImpl(descVisibility.get()));
descVisibility.set(DescriptionVisibility.SHOWN); //trigger listener for initial value
Bindings.bindContent(subNodePane.getChildren(), subNodes);
}
final DescriptionLoD getDescriptionLoD() {
@ -342,6 +350,8 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
super.layoutChildren();
}
abstract ParentNodeType createChildNode(ParentType rawChild);
/**
* @param w the maximum width the description label should have
*/

View File

@ -25,7 +25,10 @@ import java.util.Collections;
import java.util.List;
import static java.util.Objects.nonNull;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.beans.binding.Bindings;
import javafx.concurrent.Task;
import javafx.event.EventHandler;
@ -45,7 +48,9 @@ import org.controlsfx.control.action.ActionUtils;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
@ -63,6 +68,25 @@ import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
final public class EventClusterNode extends EventBundleNodeBase<EventCluster, EventStripe, EventStripeNode> {
private static final Logger LOGGER = Logger.getLogger(EventClusterNode.class.getName());
/**
* Use this recursive function to flatten a tree of nodes into an single
* stream. More specifically it takes an EventStripeNode and produces a
* stream of EventStripes conaiting the stripes for the given node and all
* child eventStripes, ignoring intervening EventCluster nodes.
*
* @see
* #loadSubBundles(org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD.RelativeDetail)
* for usage
*/
private final static Function<EventStripeNode, Stream<EventStripe>> stripeFlattener = new Function<EventStripeNode, Stream<EventStripe>>() {
@Override
public Stream<EventStripe> apply(EventStripeNode node) {
return Stream.concat(
Stream.of(node.getEventStripe()),
node.getSubNodes().stream().flatMap(clusterNode -> clusterNode.getSubNodes().stream().flatMap(this)));
}
};
private static final BorderWidths CLUSTER_BORDER_WIDTHS = new BorderWidths(2, 1, 2, 1);
private static final Image PLUS = new Image("/org/sleuthkit/autopsy/timeline/images/plus-button.png"); // NON-NLS //NOI18N
private static final Image MINUS = new Image("/org/sleuthkit/autopsy/timeline/images/minus-button.png"); // NON-NLS //NOI18N
@ -89,6 +113,7 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
subNodePane.setBorder(clusterBorder);
subNodePane.setBackground(defaultBackground);
subNodePane.setMinWidth(1);
subNodePane.setMaxWidth(USE_PREF_SIZE);
setMinHeight(24);
setAlignment(Pos.CENTER_LEFT);
@ -142,10 +167,10 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
* @param expand
*/
@NbBundle.Messages(value = "EventStripeNode.loggedTask.name=Load sub clusters")
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private synchronized void loadSubBundles(DescriptionLoD.RelativeDetail relativeDetail) {
chart.setCursor(Cursor.WAIT);
chart.getEventStripes().removeAll(Lists.transform(subNodes, EventStripeNode::getEventStripe));
subNodes.clear();
/*
* make new ZoomParams to query with
@ -159,14 +184,10 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
final EventTypeZoomLevel eventTypeZoomLevel = eventsModel.eventTypeZoomProperty().get();
final ZoomParams zoomParams = new ZoomParams(subClusterSpan, eventTypeZoomLevel, subClusterFilter, getDescriptionLoD());
Task<List<EventStripe>> loggedTask = new Task<List<EventStripe>>() {
Task<List<EventStripe>> loggedTask = new LoggedTask<List<EventStripe>>(Bundle.EventStripeNode_loggedTask_name(), false) {
private volatile DescriptionLoD loadedDescriptionLoD = getDescriptionLoD().withRelativeDetail(relativeDetail);
{
updateTitle(Bundle.EventStripeNode_loggedTask_name());
}
@Override
protected List<EventStripe> call() throws Exception {
List<EventStripe> bundles;
@ -182,7 +203,9 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
} while (bundles.size() == 1 && nonNull(next));
// return list of EventStripes representing sub-bundles
return Lists.transform(bundles, eventStripe -> eventStripe.withParent(getEventCluster()));
return bundles.stream()
.map(eventStripe -> eventStripe.withParent(getEventCluster()))
.collect(Collectors.toList());
}
@Override
@ -190,14 +213,16 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
try {
List<EventStripe> bundles = get();
//clear the existing subnodes
List<EventStripe> transform = subNodes.stream().flatMap(stripeFlattener).collect(Collectors.toList());
chart.getEventStripes().removeAll(transform);
subNodes.clear();
if (bundles.isEmpty()) {
subNodePane.getChildren().clear();
getChildren().setAll(subNodePane, infoHBox);
descLOD.set(getEventBundle().getDescriptionLoD());
} else {
chart.getEventStripes().addAll(bundles);
subNodes.addAll(Lists.transform(bundles, EventClusterNode.this::createStripeNode));
subNodePane.getChildren().setAll(subNodes);
subNodes.addAll(Lists.transform(bundles, EventClusterNode.this::createChildNode));
getChildren().setAll(new VBox(infoHBox, subNodePane));
descLOD.set(loadedDescriptionLoD);
}
@ -214,7 +239,8 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
chart.getController().monitorTask(loggedTask);
}
private EventStripeNode createStripeNode(EventStripe stripe) {
@Override
EventStripeNode createChildNode(EventStripe stripe) {
return new EventStripeNode(chart, stripe, this);
}

View File

@ -388,7 +388,7 @@ public final class EventDetailsChart extends XYChart<DateTime, EventStripe> impl
* @return all the nodes that pass the given predicate
*/
synchronized Iterable<EventBundleNodeBase<?, ?, ?>> getNodes(Predicate<EventBundleNodeBase<?, ?, ?>> p) {
//use this recursive function to flatten the tree of nodes into an iterable.
//use this recursive function to flatten the tree of nodes into an single stream.
Function<EventBundleNodeBase<?, ?, ?>, Stream<EventBundleNodeBase<?, ?, ?>>> stripeFlattener =
new Function<EventBundleNodeBase<?, ?, ?>, Stream<EventBundleNodeBase<?, ?, ?>>>() {
@Override

View File

@ -73,17 +73,20 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
eventTypeImageView.setImage(getEventType().getFXImage());
descrLabel.setTextOverrun(OverrunStyle.CENTER_ELLIPSIS);
descrLabel.setGraphic(eventTypeImageView);
descrLabel.setPrefWidth(USE_COMPUTED_SIZE);
setAlignment(subNodePane, Pos.BOTTOM_LEFT);
for (EventCluster cluster : eventStripe.getClusters()) {
EventClusterNode clusterNode = new EventClusterNode(chart, cluster, this);
subNodes.add(clusterNode);
subNodePane.getChildren().addAll(clusterNode);
subNodes.add(createChildNode(cluster));
}
getChildren().addAll(new VBox(infoHBox, subNodePane));
}
@Override
EventClusterNode createChildNode(EventCluster cluster) {
return new EventClusterNode(chart, cluster, this);
}
@Override
void showHoverControls(final boolean showControls) {
super.showHoverControls(showControls);

View File

@ -64,16 +64,15 @@ class EventTypeTreeItem extends NavTreeItem {
}
void remove(Deque<EventBundle<?>> path) {
EventBundle<?> head = path.removeFirst();
EventDescriptionTreeItem descTreeItem = childMap.get(head.getDescription());
if (descTreeItem != null) {
if (path.isEmpty() == false) {
descTreeItem.remove(path);
} else if (descTreeItem.getChildren().isEmpty()) {
}
if (descTreeItem.getChildren().isEmpty()) {
childMap.remove(head.getDescription());
getChildren().remove(descTreeItem);
}
}
}