install actions/buttons lazily

This commit is contained in:
jmillman 2015-11-20 11:57:04 -05:00
parent 5c6714b2f3
commit 5c3adb549e
5 changed files with 68 additions and 62 deletions

View File

@ -348,7 +348,7 @@ public final class FilteredEventsModel {
* range and pass the requested filter, using the given aggregation * range and pass the requested filter, using the given aggregation
* to control the grouping of events * to control the grouping of events
*/ */
public List<EventStripe> getEventClusters(ZoomParams params) { public List<EventStripe> getEventStripes(ZoomParams params) {
return repo.getEventStripes(params); return repo.getEventStripes(params);
} }

View File

@ -30,9 +30,7 @@ import java.util.stream.Collectors;
import javafx.animation.KeyFrame; import javafx.animation.KeyFrame;
import javafx.animation.KeyValue; import javafx.animation.KeyValue;
import javafx.animation.Timeline; import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Orientation; import javafx.geometry.Orientation;
@ -104,7 +102,7 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
protected final EventDetailsChart chart; protected final EventDetailsChart chart;
final SimpleObjectProperty<DescriptionLoD> descLOD = new SimpleObjectProperty<>(); final SimpleObjectProperty<DescriptionLoD> descLOD = new SimpleObjectProperty<>();
final SimpleObjectProperty<DescriptionVisibility> descVisibility = new SimpleObjectProperty<>(DescriptionVisibility.SHOWN); final SimpleObjectProperty<DescriptionVisibility> descVisibility = new SimpleObjectProperty<>();
protected final BundleType eventBundle; protected final BundleType eventBundle;
protected final ParentNodeType parentNode; protected final ParentNodeType parentNode;
@ -150,9 +148,13 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
setAlignment(Pos.TOP_LEFT); setAlignment(Pos.TOP_LEFT);
setPrefHeight(USE_COMPUTED_SIZE); setPrefHeight(USE_COMPUTED_SIZE);
heightProperty().addListener(heightProp -> {
chart.requestChartLayout(); /*
}); * This triggers the layout when a mousover causes the action buttons to
* interesect with another node, forcing it down.
*/
heightProperty().addListener(heightProp -> chart.requestChartLayout());
setMaxHeight(USE_PREF_SIZE); setMaxHeight(USE_PREF_SIZE);
setMinWidth(USE_PREF_SIZE); setMinWidth(USE_PREF_SIZE);
setMaxWidth(USE_PREF_SIZE); setMaxWidth(USE_PREF_SIZE);
@ -176,11 +178,7 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
//set up mouse hover effect and tooltip //set up mouse hover effect and tooltip
setOnMouseEntered((MouseEvent e) -> { setOnMouseEntered((MouseEvent e) -> {
/*
* defer tooltip content creation till needed, this had a
* surprisingly large impact on speed of loading the chart
*/
installTooltip();
Tooltip.uninstall(chart, AbstractVisualizationPane.getDefaultTooltip()); Tooltip.uninstall(chart, AbstractVisualizationPane.getDefaultTooltip());
showHoverControls(true); showHoverControls(true);
toFront(); toFront();
@ -194,11 +192,8 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
} }
}); });
descVisibility.addListener((ObservableValue<? extends DescriptionVisibility> observable, DescriptionVisibility oldValue, DescriptionVisibility newValue) -> { descVisibility.addListener(observable -> setDescriptionVisibiltiyImpl(descVisibility.get()));
setDescriptionVisibiltiyImpl(newValue); descVisibility.set(DescriptionVisibility.SHOWN); //trigger listener for initial value
});
setDescriptionVisibiltiyImpl(DescriptionVisibility.SHOWN);
} }
final DescriptionLoD getDescriptionLoD() { final DescriptionLoD getDescriptionLoD() {
@ -215,6 +210,17 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
: 0; : 0;
} }
/**
* install whatever buttons are visible on hover for this node. likes
* tooltips, this had a surprisingly large impact on speed of loading the
* chart
*/
abstract void installActionButtons();
/**
* defer tooltip content creation till needed, this had a surprisingly large
* impact on speed of loading the chart
*/
@NbBundle.Messages({"# {0} - counts", @NbBundle.Messages({"# {0} - counts",
"# {1} - event type", "# {1} - event type",
"# {2} - description", "# {2} - description",
@ -307,12 +313,11 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
Effect dropShadow = dropShadowMap.computeIfAbsent(getEventType(), Effect dropShadow = dropShadowMap.computeIfAbsent(getEventType(),
eventType -> new DropShadow(-10, eventType.getColor())); eventType -> new DropShadow(-10, eventType.getColor()));
setEffect(showControls ? dropShadow : null); setEffect(showControls ? dropShadow : null);
installTooltip();
enableTooltip(showControls); enableTooltip(showControls);
if (parentNode != null) { if (parentNode != null) {
parentNode.enableTooltip(false);
parentNode.showHoverControls(false); parentNode.showHoverControls(false);
} }
} }
final EventType getEventType() { final EventType getEventType() {
@ -366,12 +371,15 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
void animateTo(double xLeft, double yTop) { void animateTo(double xLeft, double yTop) {
if (timeline != null) { if (timeline != null) {
timeline.stop(); timeline.stop();
chart.requestChartLayout();
} }
timeline = new Timeline(new KeyFrame(Duration.millis(100), timeline = new Timeline(new KeyFrame(Duration.millis(100),
new KeyValue(layoutXProperty(), xLeft), new KeyValue(layoutXProperty(), xLeft),
new KeyValue(layoutYProperty(), yTop)) new KeyValue(layoutYProperty(), yTop))
); );
timeline.setOnFinished(finished -> Platform.runLater(chart::requestChartLayout)); timeline.setOnFinished(finished -> chart.requestChartLayout());
timeline.play(); timeline.play();
} }
} }

View File

@ -18,16 +18,15 @@
*/ */
package org.sleuthkit.autopsy.timeline.ui.detailview; package org.sleuthkit.autopsy.timeline.ui.detailview;
import com.google.common.collect.Lists;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import static java.util.Objects.nonNull; import static java.util.Objects.nonNull;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Cursor; import javafx.scene.Cursor;
@ -71,8 +70,20 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
private static final Image MINUS = new Image("/org/sleuthkit/autopsy/timeline/images/minus-button.png"); // NON-NLS //NOI18N private static final Image MINUS = new Image("/org/sleuthkit/autopsy/timeline/images/minus-button.png"); // NON-NLS //NOI18N
private final Border clusterBorder = new Border(new BorderStroke(evtColor.deriveColor(0, 1, 1, .4), BorderStrokeStyle.SOLID, CORNER_RADII_1, CLUSTER_BORDER_WIDTHS)); private final Border clusterBorder = new Border(new BorderStroke(evtColor.deriveColor(0, 1, 1, .4), BorderStrokeStyle.SOLID, CORNER_RADII_1, CLUSTER_BORDER_WIDTHS));
final Button plusButton = ActionUtils.createButton(new ExpandClusterAction(), ActionUtils.ActionTextBehavior.HIDE); private Button plusButton;
final Button minusButton = ActionUtils.createButton(new CollapseClusterAction(), ActionUtils.ActionTextBehavior.HIDE); private Button minusButton;
@Override
void installActionButtons() {
if (plusButton == null) {
plusButton = ActionUtils.createButton(new ExpandClusterAction(), ActionUtils.ActionTextBehavior.HIDE);
minusButton = ActionUtils.createButton(new CollapseClusterAction(), ActionUtils.ActionTextBehavior.HIDE);
configureLoDButton(plusButton);
configureLoDButton(minusButton);
infoHBox.getChildren().addAll(minusButton, plusButton);
}
}
public EventClusterNode(EventDetailsChart chart, EventCluster eventCluster, EventStripeNode parentNode) { public EventClusterNode(EventDetailsChart chart, EventCluster eventCluster, EventStripeNode parentNode) {
super(chart, eventCluster, parentNode); super(chart, eventCluster, parentNode);
@ -87,11 +98,8 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
setCursor(Cursor.HAND); setCursor(Cursor.HAND);
setOnMouseClicked(new MouseClickHandler()); setOnMouseClicked(new MouseClickHandler());
configureLoDButton(plusButton);
configureLoDButton(minusButton);
setAlignment(Pos.CENTER_LEFT); setAlignment(Pos.CENTER_LEFT);
infoHBox.getChildren().addAll(minusButton, plusButton);
getChildren().addAll(subNodePane, infoHBox); getChildren().addAll(subNodePane, infoHBox);
} }
@ -99,6 +107,7 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
@Override @Override
void showHoverControls(final boolean showControls) { void showHoverControls(final boolean showControls) {
super.showHoverControls(showControls); super.showHoverControls(showControls);
installActionButtons();
show(plusButton, showControls); show(plusButton, showControls);
show(minusButton, showControls); show(minusButton, showControls);
} }
@ -141,10 +150,7 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
@NbBundle.Messages(value = "EventStripeNode.loggedTask.name=Load sub clusters") @NbBundle.Messages(value = "EventStripeNode.loggedTask.name=Load sub clusters")
private synchronized void loadSubBundles(DescriptionLoD.RelativeDetail relativeDetail) { private synchronized void loadSubBundles(DescriptionLoD.RelativeDetail relativeDetail) {
chart.setCursor(Cursor.WAIT); chart.setCursor(Cursor.WAIT);
chart.getEventStripes().removeIf(bundle -> chart.getEventStripes().removeAll(Lists.transform(subNodes, EventStripeNode::getEventStripe));
subNodes.stream().anyMatch(subNode ->
bundle.equals(subNode.getEventStripe()))
);
subNodes.clear(); subNodes.clear();
/* /*
@ -169,29 +175,22 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
@Override @Override
protected Collection<EventStripe> call() throws Exception { protected Collection<EventStripe> call() throws Exception {
Collection<EventStripe> bundles = null; Collection<EventStripe> bundles;
DescriptionLoD next = loadedDescriptionLoD; DescriptionLoD next = loadedDescriptionLoD;
do { do {
loadedDescriptionLoD = next; loadedDescriptionLoD = next;
if (loadedDescriptionLoD == getEventBundle().getDescriptionLoD()) { if (loadedDescriptionLoD == getEventBundle().getDescriptionLoD()) {
return Collections.emptySet(); return Collections.emptySet();
} }
bundles = eventsModel.getEventClusters(zoomParams.withDescrLOD(loadedDescriptionLoD)).stream() bundles = eventsModel.getEventStripes(zoomParams.withDescrLOD(loadedDescriptionLoD));
.collect(Collectors.toMap(EventStripe::getDescription, //key
eventStripe -> eventStripe.withParent(getEventCluster()), //value
EventStripe::merge) //merge method
).values();
next = loadedDescriptionLoD.withRelativeDetail(relativeDetail); next = loadedDescriptionLoD.withRelativeDetail(relativeDetail);
} while (bundles.size() == 1 && nonNull(next)); } while (bundles.size() == 1 && nonNull(next));
// return list of AbstractEventStripeNodes representing sub-bundles // return list of AbstractEventStripeNodes representing sub-bundles
return bundles; return bundles;
} }
@Override @Override
protected void succeeded() { protected void succeeded() {
try { try {
Collection<EventStripe> bundles = get(); Collection<EventStripe> bundles = get();
@ -203,7 +202,6 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
chart.getEventStripes().addAll(bundles); chart.getEventStripes().addAll(bundles);
subNodes.addAll(bundles.stream() subNodes.addAll(bundles.stream()
.map(EventClusterNode.this::createStripeNode) .map(EventClusterNode.this::createStripeNode)
.sorted(Comparator.comparing(EventStripeNode::getStartMillis))
.collect(Collectors.toList())); .collect(Collectors.toList()));
subNodePane.getChildren().setAll(subNodes); subNodePane.getChildren().setAll(subNodes);
getChildren().setAll(new VBox(infoHBox, subNodePane)); getChildren().setAll(new VBox(infoHBox, subNodePane));
@ -303,9 +301,8 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
super(Bundle.ExpandClusterAction_text()); super(Bundle.ExpandClusterAction_text());
setGraphic(new ImageView(PLUS)); setGraphic(new ImageView(PLUS));
setEventHandler((ActionEvent t) -> { setEventHandler(actionEvent -> {
final DescriptionLoD next = descLOD.get().moreDetailed(); if (descLOD.get().moreDetailed() != null) {
if (next != null) {
loadSubBundles(DescriptionLoD.RelativeDetail.MORE); loadSubBundles(DescriptionLoD.RelativeDetail.MORE);
} }
}); });
@ -320,9 +317,8 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
super(Bundle.CollapseClusterAction_text()); super(Bundle.CollapseClusterAction_text());
setGraphic(new ImageView(MINUS)); setGraphic(new ImageView(MINUS));
setEventHandler((ActionEvent t) -> { setEventHandler(actionEvent -> {
final DescriptionLoD previous = descLOD.get().lessDetailed(); if (descLOD.get().lessDetailed() != null) {
if (previous != null) {
loadSubBundles(DescriptionLoD.RelativeDetail.LESS); loadSubBundles(DescriptionLoD.RelativeDetail.LESS);
} }
}); });

View File

@ -493,9 +493,7 @@ public final class EventDetailsChart extends XYChart<DateTime, EventStripe> impl
localMax = Math.max(yTop + h, localMax); localMax = Math.max(yTop + h, localMax);
if ((xLeft != bundleNode.getLayoutX()) || (yTop != bundleNode.getLayoutY())) { if ((xLeft != bundleNode.getLayoutX()) || (yTop != bundleNode.getLayoutY())) {
// bundleNode.relocate(xLeft, yTop); //animate node to new position
// requestChartLayout();
// //animate node to new position
bundleNode.animateTo(xLeft, yTop); bundleNode.animateTo(xLeft, yTop);
} }
} }
@ -517,7 +515,7 @@ public final class EventDetailsChart extends XYChart<DateTime, EventStripe> impl
@Override @Override
public void requestChartLayout() { public void requestChartLayout() {
super.requestChartLayout(); //To change body of generated methods, choose Tools | Templates. super.requestChartLayout();
} }
private double getXForEpochMillis(Long millis) { private double getXForEpochMillis(Long millis) {
@ -647,9 +645,7 @@ public final class EventDetailsChart extends XYChart<DateTime, EventStripe> impl
DescriptionFilter descriptionFilter = getController().getQuickHideFilters().stream() DescriptionFilter descriptionFilter = getController().getQuickHideFilters().stream()
.filter(testFilter::equals) .filter(testFilter::equals)
.findFirst().orElseGet(() -> { .findFirst().orElseGet(() -> {
testFilter.selectedProperty().addListener((Observable observable) -> { testFilter.selectedProperty().addListener(observable -> requestChartLayout());
requestChartLayout();
});
getController().getQuickHideFilters().add(testFilter); getController().getQuickHideFilters().add(testFilter);
return testFilter; return testFilter;
}); });

View File

@ -42,7 +42,7 @@ import static org.sleuthkit.autopsy.timeline.ui.detailview.EventBundleNodeBase.c
final public class EventStripeNode extends EventBundleNodeBase<EventStripe, EventCluster, EventClusterNode> { final public class EventStripeNode extends EventBundleNodeBase<EventStripe, EventCluster, EventClusterNode> {
private static final Logger LOGGER = Logger.getLogger(EventStripeNode.class.getName()); private static final Logger LOGGER = Logger.getLogger(EventStripeNode.class.getName());
final Button hideButton; private Button hideButton;
/** /**
* Pane that contains EventStripeNodes for any 'subevents' if they are * Pane that contains EventStripeNodes for any 'subevents' if they are
* displayed * displayed
@ -53,16 +53,22 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
// private final HBox clustersHBox = new HBox(); // private final HBox clustersHBox = new HBox();
private final ImageView eventTypeImageView = new ImageView(); private final ImageView eventTypeImageView = new ImageView();
@Override
void installActionButtons() {
if (hideButton == null) {
hideButton = ActionUtils.createButton(chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD()),
ActionUtils.ActionTextBehavior.HIDE);
configureLoDButton(hideButton);
infoHBox.getChildren().add(hideButton);
}
}
public EventStripeNode(EventDetailsChart chart, EventStripe eventStripe, EventClusterNode parentNode) { public EventStripeNode(EventDetailsChart chart, EventStripe eventStripe, EventClusterNode parentNode) {
super(chart, eventStripe, parentNode); super(chart, eventStripe, parentNode);
setMinHeight(48); setMinHeight(48);
EventDetailsChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD());
hideButton = ActionUtils.createButton(hideClusterAction, ActionUtils.ActionTextBehavior.HIDE);
configureLoDButton(hideButton);
infoHBox.getChildren().add(hideButton);
//setup description label //setup description label
eventTypeImageView.setImage(getEventType().getFXImage()); eventTypeImageView.setImage(getEventType().getFXImage());
descrLabel.setPrefWidth(USE_COMPUTED_SIZE); descrLabel.setPrefWidth(USE_COMPUTED_SIZE);
@ -83,6 +89,7 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
@Override @Override
void showHoverControls(final boolean showControls) { void showHoverControls(final boolean showControls) {
super.showHoverControls(showControls); super.showHoverControls(showControls);
installActionButtons();
show(hideButton, showControls); show(hideButton, showControls);
} }
@ -178,5 +185,4 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
} }
} }
} }