19 package org.sleuthkit.autopsy.timeline.ui.detailview;
21 import com.google.common.collect.Lists;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.List;
26 import static java.util.Objects.nonNull;
27 import java.util.concurrent.ExecutionException;
28 import java.util.logging.Level;
29 import java.util.stream.Collectors;
30 import javafx.beans.binding.Bindings;
31 import javafx.concurrent.Task;
32 import javafx.event.EventHandler;
33 import javafx.geometry.Pos;
34 import javafx.scene.Cursor;
35 import javafx.scene.control.Button;
36 import javafx.scene.image.Image;
37 import javafx.scene.image.ImageView;
38 import javafx.scene.input.MouseEvent;
39 import javafx.scene.layout.Border;
40 import javafx.scene.layout.BorderStroke;
41 import javafx.scene.layout.BorderStrokeStyle;
42 import javafx.scene.layout.BorderWidths;
43 import javafx.scene.layout.VBox;
44 import org.controlsfx.control.action.Action;
45 import org.controlsfx.control.action.ActionUtils;
46 import org.joda.time.DateTime;
47 import org.joda.time.Interval;
48 import org.openide.util.NbBundle;
71 private static final Image
PLUS =
new Image(
"/org/sleuthkit/autopsy/timeline/images/plus-button.png");
72 private static final Image
MINUS =
new Image(
"/org/sleuthkit/autopsy/timeline/images/minus-button.png");
79 void installActionButtons() {
80 if (plusButton == null) {
81 plusButton = ActionUtils.createButton(
new ExpandClusterAction(), ActionUtils.ActionTextBehavior.HIDE);
82 minusButton = ActionUtils.createButton(
new CollapseClusterAction(), ActionUtils.ActionTextBehavior.HIDE);
84 configureLoDButton(plusButton);
85 configureLoDButton(minusButton);
86 infoHBox.getChildren().addAll(minusButton, plusButton);
91 super(chart, eventCluster, parentNode);
93 subNodePane.setBorder(clusterBorder);
94 subNodePane.setBackground(defaultBackground);
95 subNodePane.setMinWidth(1);
96 subNodePane.setMaxWidth(USE_PREF_SIZE);
98 setAlignment(Pos.CENTER_LEFT);
100 setCursor(Cursor.HAND);
102 getChildren().addAll(subNodePane, infoHBox);
107 void showHoverControls(
final boolean showControls) {
108 super.showHoverControls(showControls);
109 installActionButtons();
110 show(plusButton, showControls);
111 show(minusButton, showControls);
115 void applyHighlightEffect(
boolean applied) {
120 void setMaxDescriptionWidth(
double max) {
125 void setDescriptionVisibiltiyImpl(DescriptionVisibility descrVis) {
126 final int size = getEventCluster().
getCount();
129 countLabel.setText(
"");
130 descrLabel.setText(
"");
133 descrLabel.setText(
"");
134 countLabel.setText(String.valueOf(size));
138 countLabel.setText(String.valueOf(size));
149 @NbBundle.Messages(value =
"EventStripeNode.loggedTask.name=Load sub clusters")
150 @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
152 chart.setCursor(Cursor.WAIT);
162 final RootFilter subClusterFilter = getSubClusterFilter();
163 final Interval subClusterSpan =
new Interval(getStartMillis(), getEndMillis() + 1000);
165 final ZoomParams zoomParams =
new ZoomParams(subClusterSpan, eventTypeZoomLevel, subClusterFilter, getDescriptionLoD());
172 protected List<EventStripe> call()
throws Exception {
173 List<EventStripe> bundles;
176 loadedDescriptionLoD = next;
177 if (loadedDescriptionLoD ==
getEventBundle().getDescriptionLoD()) {
178 return Collections.emptyList();
183 }
while (bundles.size() == 1 && nonNull(next));
186 return bundles.stream()
187 .map(eventStripe -> eventStripe.withParent(getEventCluster()))
188 .collect(Collectors.toList());
192 protected void succeeded() {
194 List<EventStripe> bundles =
get();
197 List<EventStripe> transform = subNodes.stream().flatMap(
new StripeFlattener()).collect(Collectors.toList());
198 chart.getEventStripes().removeAll(transform);
200 if (bundles.isEmpty()) {
201 getChildren().setAll(subNodePane, infoHBox);
204 chart.getEventStripes().addAll(bundles);
205 subNodes.addAll(Lists.transform(bundles,
EventClusterNode.this::createChildNode));
206 getChildren().setAll(
new VBox(infoHBox, subNodePane));
207 descLOD.set(loadedDescriptionLoD);
209 }
catch (InterruptedException | ExecutionException ex) {
210 LOGGER.log(Level.SEVERE,
"Error loading subnodes", ex);
213 chart.setCursor(null);
217 new Thread(loggedTask).start();
227 EventCluster getEventCluster() {
233 double chartX =
chart.
getXAxis().getDisplayPosition(
new DateTime(getStartMillis()));
234 double w =
chart.
getXAxis().getDisplayPosition(
new DateTime(getEndMillis())) - chartX;
235 subNodePane.setPrefWidth(Math.max(1, w));
236 super.layoutChildren();
246 subClusterFilter.getSubFilters().addAll(
249 return subClusterFilter;
253 Collection<? extends Action> getActions() {
254 return Arrays.asList(
new ExpandClusterAction(),
255 new CollapseClusterAction());
259 EventHandler<MouseEvent> getDoubleClickHandler() {
260 return mouseEvent ->
new ExpandClusterAction().handle(null);
265 @NbBundle.Messages(value =
"ExpandClusterAction.text=Expand")
267 super(Bundle.ExpandClusterAction_text());
269 setGraphic(
new ImageView(PLUS));
270 setEventHandler(actionEvent -> {
271 if (descLOD.get().moreDetailed() != null) {
281 @NbBundle.Messages(value =
"CollapseClusterAction.text=Collapse")
283 super(Bundle.CollapseClusterAction_text());
285 setGraphic(
new ImageView(MINUS));
286 setEventHandler(actionEvent -> {
287 if (descLOD.get().lessDetailed() != null) {
291 disabledProperty().bind(Bindings.createBooleanBinding(() -> nonNull(getEventCluster()) && descLOD.get() == getEventCluster().getDescriptionLoD(), descLOD));
final ParentNodeType parentNode
TimeLineController getController()
ZoomParams withDescrLOD(DescriptionLoD descrLOD)
final BundleType getEventBundle()
List< EventStripe > getEventStripes()
synchronized ReadOnlyObjectProperty< EventTypeZoomLevel > eventTypeZoomProperty()
static final Logger LOGGER
synchronized void loadSubBundles(DescriptionLoD.RelativeDetail relativeDetail)
void requestChartLayout()
final EventDetailsChart chart
synchronized ReadOnlyObjectProperty< RootFilter > filterProperty()
synchronized void monitorTask(final Task<?> task)
EventClusterNode(EventDetailsChart chart, EventCluster eventCluster, EventStripeNode parentNode)
final Border clusterBorder
synchronized static Logger getLogger(String name)
DescriptionLoD withRelativeDetail(RelativeDetail relativeDetail)
static final BorderWidths CLUSTER_BORDER_WIDTHS