20 package org.sleuthkit.autopsy.timeline.ui.detailview;
 
   22 import com.google.common.collect.Lists;
 
   23 import com.google.common.collect.Sets;
 
   24 import com.google.common.eventbus.Subscribe;
 
   25 import java.util.Arrays;
 
   26 import java.util.Collection;
 
   27 import java.util.HashMap;
 
   28 import java.util.List;
 
   30 import java.util.Optional;
 
   32 import java.util.concurrent.ConcurrentHashMap;
 
   33 import java.util.concurrent.ExecutionException;
 
   34 import java.util.logging.Level;
 
   35 import java.util.stream.Collectors;
 
   36 import javafx.animation.KeyFrame;
 
   37 import javafx.animation.KeyValue;
 
   38 import javafx.animation.Timeline;
 
   39 import javafx.application.Platform;
 
   40 import javafx.concurrent.Task;
 
   41 import javafx.event.EventHandler;
 
   42 import javafx.geometry.Insets;
 
   43 import javafx.scene.Node;
 
   44 import javafx.scene.control.Button;
 
   45 import javafx.scene.control.ButtonBase;
 
   46 import javafx.scene.control.ContextMenu;
 
   47 import javafx.scene.control.Label;
 
   48 import javafx.scene.control.SeparatorMenuItem;
 
   49 import javafx.scene.control.Tooltip;
 
   50 import javafx.scene.effect.DropShadow;
 
   51 import javafx.scene.effect.Effect;
 
   52 import javafx.scene.image.Image;
 
   53 import javafx.scene.image.ImageView;
 
   54 import javafx.scene.input.MouseButton;
 
   55 import javafx.scene.input.MouseEvent;
 
   56 import javafx.scene.layout.Background;
 
   57 import javafx.scene.layout.BackgroundFill;
 
   58 import javafx.scene.layout.Border;
 
   59 import javafx.scene.layout.BorderStroke;
 
   60 import javafx.scene.layout.BorderStrokeStyle;
 
   61 import javafx.scene.layout.BorderWidths;
 
   62 import javafx.scene.layout.HBox;
 
   63 import javafx.scene.layout.StackPane;
 
   64 import javafx.scene.paint.Color;
 
   65 import javafx.util.Duration;
 
   66 import org.apache.commons.lang3.StringUtils;
 
   67 import org.controlsfx.control.action.Action;
 
   68 import org.controlsfx.control.action.ActionUtils;
 
   69 import org.joda.time.DateTime;
 
   70 import org.openide.util.NbBundle;
 
   95     private static final Image 
HASH_HIT = 
new Image(
"/org/sleuthkit/autopsy/images/hashset_hits.png"); 
 
   96     private static final Image 
TAG = 
new Image(
"/org/sleuthkit/autopsy/images/green-tag-icon-16.png"); 
 
   97     private static final Image 
PIN = 
new Image(
"/org/sleuthkit/autopsy/timeline/images/marker--plus.png"); 
 
   98     private static final Image 
UNPIN = 
new Image(
"/org/sleuthkit/autopsy/timeline/images/marker--minus.png"); 
 
  100     private static final Map<EventType, Effect> 
dropShadowMap = 
new ConcurrentHashMap<>();
 
  102     static void configureActionButton(ButtonBase b) {
 
  103         b.setMinSize(16, 16);
 
  104         b.setMaxSize(16, 16);
 
  105         b.setPrefSize(16, 16);
 
  108     static void show(Node b, 
boolean show) {
 
  117     final DetailsChartLane<?> chartLane;
 
  118     final Background highlightedBackground;
 
  119     final Background defaultBackground;
 
  120     final Color evtColor;
 
  122     final Label countLabel = 
new Label();
 
  123     final Label descrLabel = 
new Label();
 
  124     final ImageView hashIV = 
new ImageView(HASH_HIT);
 
  125     final ImageView tagIV = 
new ImageView(TAG);
 
  126     final ImageView eventTypeImageView = 
new ImageView();
 
  128     final Tooltip tooltip = 
new Tooltip(Bundle.EventBundleNodeBase_toolTip_loading());
 
  130     final HBox controlsHBox = 
new HBox(5);
 
  131     final HBox infoHBox = 
new HBox(5, eventTypeImageView, hashIV, tagIV, descrLabel, countLabel, controlsHBox);
 
  132     final SleuthkitCase sleuthkitCase;
 
  139         this.chartLane = chartLane;
 
  141         this.parentNode = parent;
 
  143         sleuthkitCase = chartLane.getController().getAutopsyCase().getSleuthkitCase();
 
  144         eventsModel = chartLane.getController().getEventsModel();
 
  145         eventTypeImageView.setImage(getEventType().getFXImage());
 
  147         if (tlEvent.getEventIDsWithHashHits().isEmpty()) {
 
  151         if (tlEvent.getEventIDsWithTags().isEmpty()) {
 
  156             evtColor = getEventType().
getColor();
 
  160         SELECTION_BORDER = 
new Border(
new BorderStroke(evtColor.darker().desaturate(), BorderStrokeStyle.SOLID, CORNER_RADII_3, 
new BorderWidths(2)));
 
  162         defaultBackground = 
new Background(
new BackgroundFill(evtColor.deriveColor(0, 1, 1, .1), CORNER_RADII_3, Insets.EMPTY));
 
  163         highlightedBackground = 
new Background(
new BackgroundFill(evtColor.deriveColor(0, 1.1, 1.1, .3), CORNER_RADII_3, Insets.EMPTY));
 
  164         setBackground(defaultBackground);
 
  166         Tooltip.install(
this, this.tooltip);
 
  169         setOnMouseEntered(mouseEntered -> {
 
  171             showHoverControls(
true);
 
  174         setOnMouseExited(mouseExited -> {
 
  175             showHoverControls(
false);
 
  176             if (parentNode != null) {
 
  177                 parentNode.showHoverControls(
true);
 
  182         setOnMouseClicked(
new ClickHandler());
 
  183         show(controlsHBox, 
false);
 
  192         return chartLane.getController();
 
  196         return Optional.ofNullable(parentNode);
 
  199     DetailsChartLane<?> getChartLane() {
 
  207         descrLabel.setMaxWidth(w);
 
  210     public abstract List<EventNodeBase<?>> 
getSubNodes();
 
  218         setBorder(applied ? SELECTION_BORDER : null);
 
  222         super.layoutChildren();
 
  230     void installActionButtons() {
 
  231         if (pinButton == null) {
 
  232             pinButton = 
new Button();
 
  233             controlsHBox.getChildren().add(pinButton);
 
  234             configureActionButton(pinButton);
 
  238     final void showHoverControls(
final boolean showControls) {
 
  239         Effect dropShadow = dropShadowMap.computeIfAbsent(getEventType(),
 
  240                 eventType -> 
new DropShadow(-10, eventType.getColor()));
 
  241         setEffect(showControls ? dropShadow : null);
 
  243         enableTooltip(showControls);
 
  244         installActionButtons();
 
  249             pinButton.setOnAction(actionEvent -> {
 
  250                 new UnPinEventAction(controller, tlEvent).handle(actionEvent);
 
  251                 showHoverControls(
true);
 
  253             pinButton.setGraphic(
new ImageView(UNPIN));
 
  255             pinButton.setOnAction(actionEvent -> {
 
  256                 new PinEventAction(controller, tlEvent).handle(actionEvent);
 
  257                 showHoverControls(
true);
 
  259             pinButton.setGraphic(
new ImageView(PIN));
 
  262         show(controlsHBox, showControls);
 
  263         if (parentNode != null) {
 
  264             parentNode.showHoverControls(
false);
 
  272     @NbBundle.Messages({
"# {0} - counts",
 
  273         "# {1} - event type",
 
  274         "# {2} - description",
 
  275         "# {3} - start date/time",
 
  276         "# {4} - end date/time",
 
  277         "EventNodeBase.tooltip.text={0} {1} events\n{2}\nbetween\t{3}\nand   \t{4}",
 
  278         "EventNodeBase.toolTip.loading2=loading tooltip",
 
  279         "# {0} - hash set count string",
 
  280         "EventNodeBase.toolTip.hashSetHits=\n\nHash Set Hits\n{0}",
 
  281         "# {0} - tag count string",
 
  282         "EventNodeBase.toolTip.tags=\n\nTags\n{0}"})
 
  283     @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
 
  284     void installTooltip() {
 
  285         if (tooltip.getText().equalsIgnoreCase(Bundle.EventBundleNodeBase_toolTip_loading())) {
 
  286             final Task<String> tooltTipTask = 
new Task<String>() {
 
  288                     updateTitle(Bundle.EventNodeBase_toolTip_loading2());
 
  292                 protected String call() throws Exception {
 
  293                     HashMap<String, Long> hashSetCounts = 
new HashMap<>();
 
  294                     if (tlEvent.getEventIDsWithHashHits().isEmpty() == 
false) {
 
  297                             for (SingleEvent tle : eventsModel.
getEventsById(tlEvent.getEventIDsWithHashHits())) {
 
  298                                 Set<String> hashSetNames = sleuthkitCase.getAbstractFileById(tle.getFileID()).getHashSetNames();
 
  299                                 for (String hashSetName : hashSetNames) {
 
  300                                     hashSetCounts.merge(hashSetName, 1L, Long::sum);
 
  303                         } 
catch (TskCoreException ex) {
 
  304                             LOGGER.log(Level.SEVERE, 
"Error getting hashset hit info for event.", ex); 
 
  307                     String hashSetCountsString = hashSetCounts.entrySet().stream()
 
  308                             .map((Map.Entry<String, Long> t) -> t.getKey() + 
" : " + t.getValue())
 
  309                             .collect(Collectors.joining(
"\n"));
 
  311                     Map<String, Long> tagCounts = 
new HashMap<>();
 
  312                     if (tlEvent.getEventIDsWithTags().isEmpty() == 
false) {
 
  315                     String tagCountsString = tagCounts.entrySet().stream()
 
  316                             .map((Map.Entry<String, Long> t) -> t.getKey() + 
" : " + t.getValue())
 
  317                             .collect(Collectors.joining(
"\n"));
 
  319                     return Bundle.EventNodeBase_tooltip_text(getEventIDs().size(), getEventType(), getDescription(),
 
  320                             TimeLineController.getZonedFormatter().print(getStartMillis()),
 
  321                             TimeLineController.getZonedFormatter().print(getEndMillis() + 1000))
 
  322                             + (hashSetCountsString.isEmpty() ? 
"" : Bundle.EventNodeBase_toolTip_hashSetHits(hashSetCountsString))
 
  323                             + (tagCountsString.isEmpty() ? 
"" : Bundle.EventNodeBase_toolTip_tags(tagCountsString));
 
  327                 protected void succeeded() {
 
  330                         tooltip.setText(
get());
 
  331                         tooltip.setGraphic(null);
 
  332                     } 
catch (InterruptedException | ExecutionException ex) {
 
  333                         LOGGER.log(Level.SEVERE, 
"Tooltip generation failed.", ex); 
 
  337             new Thread(tooltTipTask).start();
 
  338             chartLane.getController().monitorTask(tooltTipTask);
 
  342     void enableTooltip(
boolean toolTipEnabled) {
 
  343         if (toolTipEnabled) {
 
  344             Tooltip.install(
this, tooltip);
 
  346             Tooltip.uninstall(
this, tooltip);
 
  350     final EventType getEventType() {
 
  351         return tlEvent.getEventType();
 
  354     long getStartMillis() {
 
  355         return tlEvent.getStartMillis();
 
  358     final long getEndMillis() {
 
  359         return tlEvent.getEndMillis();
 
  362     final double getLayoutXCompensation() {
 
  363         return parentNode != null
 
  364                 ? getChartLane().getXAxis().getDisplayPosition(
new DateTime(parentNode.getStartMillis()))
 
  368     abstract String getDescription();
 
  370     void animateTo(
double xLeft, 
double yTop) {
 
  371         if (timeline != null) {
 
  373             Platform.runLater(this::requestChartLayout);
 
  375         timeline = 
new Timeline(
new KeyFrame(Duration.millis(100),
 
  376                 new KeyValue(layoutXProperty(), xLeft),
 
  377                 new KeyValue(layoutYProperty(), yTop))
 
  379         timeline.setOnFinished(finished -> Platform.runLater(this::requestChartLayout));
 
  383     void requestChartLayout() {
 
  384         getChartLane().requestChartLayout();
 
  387     @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
 
  388     void setDescriptionVisibility(DescriptionVisibility descrVis) {
 
  389         final int size = 
getEvent().getSize();
 
  399                 showFullDescription(size);
 
  404     void showCountOnly(
final int size) {
 
  405         descrLabel.setText(
"");
 
  406         countLabel.setText(String.valueOf(size));
 
  409     void hideDescription() {
 
  410         countLabel.setText(
"");
 
  411         descrLabel.setText(
"");
 
  419     synchronized void applyHighlightEffect(
boolean applied) {
 
  421             descrLabel.setStyle(
"-fx-font-weight: bold;"); 
 
  422             setBackground(highlightedBackground);
 
  424             descrLabel.setStyle(
"-fx-font-weight: normal;"); 
 
  425             setBackground(defaultBackground);
 
  429     void applyHighlightEffect() {
 
  430         applyHighlightEffect(
true);
 
  433     void clearHighlightEffect() {
 
  434         applyHighlightEffect(
false);
 
  437     abstract Collection<Long> getEventIDs();
 
  439     abstract EventHandler<MouseEvent> getDoubleClickHandler();
 
  441     Iterable<? extends Action> getActions() {
 
  455         ContextMenu chartContextMenu = chartLane.getContextMenu(mouseEvent);
 
  457         ContextMenu contextMenu = ActionUtils.createContextMenu(Lists.newArrayList(getActions()));
 
  458         contextMenu.getItems().add(
new SeparatorMenuItem());
 
  459         contextMenu.getItems().addAll(chartContextMenu.getItems());
 
  460         contextMenu.setAutoHide(
true);
 
  464     void showFullDescription(
final int size) {
 
  465         countLabel.setText((size == 1) ? 
"" : 
" (" + size + 
")"); 
 
  467                 "    ..." + StringUtils.substringAfter(
getEvent().getDescription(), parentNode.getDescription()))
 
  468                 .orElseGet(
getEvent()::getDescription);
 
  470         descrLabel.setText(description);
 
  476             Platform.runLater(() -> {
 
  489         if (
false == difference.isEmpty()) {
 
  490             Platform.runLater(() -> {
 
  498         @NbBundle.Messages({
"PinEventAction.text=Pin"})
 
  500             super(Bundle.PinEventAction_text());
 
  501             setEventHandler(actionEvent -> controller.
pinEvent(event));
 
  502             setGraphic(
new ImageView(PIN));
 
  508         @NbBundle.Messages({
"UnPinEventAction.text=Unpin"})
 
  510             super(Bundle.UnPinEventAction_text());
 
  511             setEventHandler(actionEvent -> controller.
unPinEvent(event));
 
  512             setGraphic(
new ImageView(UNPIN));
 
  523             if (t.getButton() == MouseButton.PRIMARY) {
 
  524                 if (t.getClickCount() > 1) {
 
  525                     getDoubleClickHandler().handle(t);
 
  526                 } 
else if (t.isShiftDown()) {
 
  528                 } 
else if (t.isShortcutDown()) {
 
  534             } 
else if (t.isPopupTrigger() && t.isStillSincePress()) {
 
Optional< EventNodeBase<?> > getParentNode()
abstract List< EventNodeBase<?> > getSubNodes()
ObservableSet< TimeLineEvent > getPinnedEvents()
final void clearContextMenu()
void handleTimeLineTagEvent(TagsAddedEvent event)
static final Image HASH_HIT
static Tooltip getDefaultTooltip()
static final Logger LOGGER
final EventNodeBase<?> parentNode
void pinEvent(TimeLineEvent event)
void applySelectionEffect(boolean applied)
void handle(MouseEvent t)
final Border SELECTION_BORDER
void handleTimeLineTagEvent(TagsDeletedEvent event)
Set< SingleEvent > getEventsById(Collection< Long > eventIDs)
static final Map< EventType, Effect > dropShadowMap
Map< String, Long > getTagCountsByTagName(Set< Long > eventIDsWithTags)
default BaseTypes getBaseType()
synchronized static Logger getLogger(String name)
void setMaxDescriptionWidth(double w)
ContextMenu getContextMenu(MouseEvent mouseEvent)
void unPinEvent(TimeLineEvent event)
TimeLineController getController()