load tooltip info on background thread

This commit is contained in:
jmillman 2015-09-29 15:14:43 -04:00
parent eb0c72b43a
commit d8c78b56fa

View File

@ -19,13 +19,17 @@
package org.sleuthkit.autopsy.timeline.ui.detailview; package org.sleuthkit.autopsy.timeline.ui.detailview;
import com.google.common.collect.Range; import com.google.common.collect.Range;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static java.util.Objects.nonNull;
import java.util.Set; import java.util.Set;
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.Observable;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
@ -59,18 +63,28 @@ 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.layout.VBox;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import org.apache.commons.lang3.StringUtils;
import org.controlsfx.control.action.Action; import org.controlsfx.control.action.Action;
import org.controlsfx.control.action.ActionUtils; import org.controlsfx.control.action.ActionUtils;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.ColorUtilities; import org.sleuthkit.autopsy.coreutils.ColorUtilities;
import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe; import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent; import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
import static org.sleuthkit.autopsy.timeline.ui.detailview.Bundle.EventStripeNode_loggedTask_name;
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
@ -270,7 +284,8 @@ final public class EventStripeNode extends StackPane {
"EventStripeNode.tooltip.text={0} {1} events\n{2}\nbetween\t{3}\nand \t{4}"}) "EventStripeNode.tooltip.text={0} {1} events\n{2}\nbetween\t{3}\nand \t{4}"})
synchronized void installTooltip() { synchronized void installTooltip() {
if (tooltip == null) { if (tooltip == null) {
Task<String> tooltTipTask = new Task<String>() { setCursor(Cursor.WAIT);
final Task<String> tooltTipTask = new Task<String>() {
@Override @Override
protected String call() throws Exception { protected String call() throws Exception {
@ -312,20 +327,27 @@ final public class EventStripeNode extends StackPane {
super.succeeded(); super.succeeded();
try { try {
tooltip = new Tooltip(get()); tooltip = new Tooltip(get());
Tooltip.install(this, tooltip); Tooltip.install(EventStripeNode.this, tooltip);
} catch (InterruptedException | ExecutionException ex) { } catch (InterruptedException | ExecutionException ex) {
LOGGER.log(Level.SEVERE, "Tooltip generation failed.", ex); LOGGER.log(Level.SEVERE, "Tooltip generation failed.", ex);
Tooltip.uninstall(this, tooltip); Tooltip.uninstall(EventStripeNode.this, tooltip);
tooltip = null; tooltip = null;
} }
} }
}; };
tooltTipTask.stateProperty().addListener((Observable observable) -> {
if (tooltTipTask.isDone()) {
setCursor(null);
}
});
chart.getController().monitorTask(tooltTipTask); chart.getController().monitorTask(tooltTipTask);
} }
} }
}
EventStripeNode getNodeForBundle(EventStripe cluster) { EventStripeNode getNodeForBundle(EventStripe cluster
) {
return new EventStripeNode(chart, cluster, this); return new EventStripeNode(chart, cluster, this);
} }
@ -342,7 +364,7 @@ EventStripeNode getNodeForBundle(EventStripe cluster) {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public List<EventStripeNode> getSubNodes() { public List<EventStripeNode> getSubNodes() {
return subNodePane.getChildrenUnmodifiable().stream() return subNodePane.getChildrenUnmodifiable().stream()
.map(t -> (EventStripeNode) t) .map(t -> (EventStripeNode) t)
.collect(Collectors.toList()); .collect(Collectors.toList());
@ -405,7 +427,7 @@ EventStripeNode getNodeForBundle(EventStripe cluster) {
* @param expand * @param expand
*/ */
@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) {
subNodePane.getChildren().clear(); subNodePane.getChildren().clear();
if (descLOD.get().withRelativeDetail(relativeDetail) == eventStripe.getDescriptionLOD()) { if (descLOD.get().withRelativeDetail(relativeDetail) == eventStripe.getDescriptionLOD()) {
descLOD.set(eventStripe.getDescriptionLOD()); descLOD.set(eventStripe.getDescriptionLOD());
@ -433,7 +455,7 @@ EventStripeNode getNodeForBundle(EventStripe cluster) {
private DescriptionLOD next = loadedDescriptionLoD; private DescriptionLOD next = loadedDescriptionLoD;
@Override @Override
protected Set<EventStripeNode> call() throws Exception { protected Set<EventStripeNode> call() throws Exception {
do { do {
loadedDescriptionLoD = next; loadedDescriptionLoD = next;
if (loadedDescriptionLoD == eventStripe.getDescriptionLOD()) { if (loadedDescriptionLoD == eventStripe.getDescriptionLOD()) {
@ -455,7 +477,7 @@ EventStripeNode getNodeForBundle(EventStripe cluster) {
} }
@Override @Override
protected void succeeded() { protected void succeeded() {
chart.setCursor(Cursor.WAIT); chart.setCursor(Cursor.WAIT);
try { try {
Set<EventStripeNode> subBundleNodes = get(); Set<EventStripeNode> subBundleNodes = get();
@ -517,116 +539,87 @@ EventStripeNode getNodeForBundle(EventStripe cluster) {
Set<Long> getEventsIDs() { Set<Long> getEventsIDs() {
return eventStripe.getEventIDs(); return eventStripe.getEventIDs();
}
}
/** /**
* event handler used for mouse events on {@link EventStripeNode}s * event handler used for mouse events on {@link EventStripeNode}s
*/ */
private class MouseHandler implements EventHandler<MouseEvent> { private class MouseHandler implements EventHandler<MouseEvent> {
private ContextMenu contextMenu; private ContextMenu contextMenu;
@Override @Override
public void handle(MouseEvent t) { public void handle(MouseEvent t) {
if (t.getButton() == MouseButton.PRIMARY) { if (t.getButton() == MouseButton.PRIMARY) {
t.consume(); t.consume();
if (t.isShiftDown()) { if (t.isShiftDown()) {
if (chart.selectedNodes.contains(EventStripeNode.this) == false) { if (chart.selectedNodes.contains(EventStripeNode.this) == false) {
chart.selectedNodes.add(EventStripeNode.this); chart.selectedNodes.add(EventStripeNode.this);
}
} else if (t.isShortcutDown()) {
chart.selectedNodes.removeAll(EventStripeNode.this);
} else if (t.getClickCount() > 1) {
final DescriptionLOD next = descLOD.get().moreDetailed();
if (next != null) {
loadSubBundles(DescriptionLOD.RelativeDetail.MORE);
}
} else {
chart.selectedNodes.setAll(EventStripeNode.this);
} }
} else if (t.isShortcutDown()) { t.consume();
chart.selectedNodes.removeAll(EventStripeNode.this); } else if (t.getButton() == MouseButton.SECONDARY) {
} else if (t.getClickCount() > 1) { 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(EventStripeNode.this, t.getScreenX(), t.getScreenY());
t.consume();
}
}
}
private class ExpandClusterAction extends Action {
@NbBundle.Messages("ExpandClusterAction.text=Expand")
ExpandClusterAction() {
super(Bundle.ExpandClusterAction_text());
setGraphic(new ImageView(PLUS));
setEventHandler((ActionEvent t) -> {
final DescriptionLOD next = descLOD.get().moreDetailed(); final DescriptionLOD next = descLOD.get().moreDetailed();
if (next != null) { if (next != null) {
loadSubBundles(DescriptionLOD.RelativeDetail.MORE); loadSubBundles(DescriptionLOD.RelativeDetail.MORE);
} }
} else { });
chart.selectedNodes.setAll(EventStripeNode.this); disabledProperty().bind(descLOD.isEqualTo(DescriptionLOD.FULL));
} }
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)); private class CollapseClusterAction extends Action {
contextMenu.getItems().add(ActionUtils.createMenuItem(collapseClusterAction));
contextMenu.getItems().add(new SeparatorMenuItem()); @NbBundle.Messages("CollapseClusterAction.text=Collapse")
contextMenu.getItems().addAll(chartContextMenu.getItems()); CollapseClusterAction() {
} super(Bundle.CollapseClusterAction_text());
contextMenu.show(EventStripeNode.this, t.getScreenX(), t.getScreenY());
t.consume(); setGraphic(new ImageView(MINUS));
setEventHandler((ActionEvent t) -> {
final DescriptionLOD previous = descLOD.get().lessDetailed();
if (previous != null) {
loadSubBundles(DescriptionLOD.RelativeDetail.LESS);
}
});
disabledProperty().bind(descLOD.isEqualTo(eventStripe.getDescriptionLOD()));
} }
} }
} }
private class ExpandClusterAction extends Action {
@NbBundle.Messages("ExpandClusterAction.text=Expand")
ExpandClusterAction() {
super(Bundle.ExpandClusterAction_text());
setGraphic(new ImageView(PLUS));
setEventHandler((ActionEvent t) -> {
final DescriptionLOD next = descLOD.get().moreDetailed();
if (next != null) {
loadSubBundles(DescriptionLOD.RelativeDetail.MORE);
}
});
disabledProperty().bind(descLOD.isEqualTo(DescriptionLOD.FULL));
}
}
private class CollapseClusterAction extends Action {
@NbBundle.Messages("CollapseClusterAction.text=Collapse")
CollapseClusterAction() {
super(Bundle.CollapseClusterAction_text());
setGraphic(new ImageView(MINUS));
setEventHandler((ActionEvent t) -> {
final DescriptionLOD previous = descLOD.get().lessDetailed();
if (previous != null) {
loadSubBundles(DescriptionLOD.RelativeDetail.LESS);
}
});
disabledProperty().bind(descLOD.isEqualTo(eventStripe.getDescriptionLOD()));
}
}
}