diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewLayoutSettings.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewLayoutSettings.java new file mode 100644 index 0000000000..b8485c3590 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewLayoutSettings.java @@ -0,0 +1,91 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.timeline.ui.detailview; + +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleObjectProperty; + +/** + * + */ +public class DetailViewLayoutSettings { + + /** + * true == truncate all the labels to the greater of the size of their + * timespan indicator or the value of truncateWidth. false == don't truncate + * the labels, alow them to extend past the timespan indicator and off the + * edge of the screen + */ + final SimpleBooleanProperty truncateAll = new SimpleBooleanProperty(false); + /** + * the width to truncate all labels to if truncateAll is true. adjustable + * via slider if truncateAll is true + */ + final SimpleDoubleProperty truncateWidth = new SimpleDoubleProperty(200.0); + /** + * true == layout each event type in its own band, false == mix all the + * events together during layout + */ + private final SimpleBooleanProperty bandByType = new SimpleBooleanProperty(false); + + /** + * true == enforce that no two events can share the same 'row', leading to + * sparser but possibly clearer layout. false == put unrelated events in the + * same 'row', creating a denser more compact layout + */ + private final SimpleBooleanProperty oneEventPerRow = new SimpleBooleanProperty(false); + + /** + * how much detail of the description to show in the ui + */ + private final SimpleObjectProperty descrVisibility = + new SimpleObjectProperty<>(DescriptionVisibility.SHOWN); + + public synchronized SimpleBooleanProperty bandByTypeProperty() { + return bandByType; + } + + SimpleBooleanProperty oneEventPerRowProperty() { + return oneEventPerRow; + } + + SimpleDoubleProperty truncateWidthProperty() { + return truncateWidth; + } + + SimpleBooleanProperty truncateAllProperty() { + return truncateAll; + } + + SimpleObjectProperty< DescriptionVisibility> descrVisibilityProperty() { + return descrVisibility; + } + + synchronized void setBandByType(Boolean t1) { + bandByType.set(t1); + } + + boolean getBandByType() { + return bandByType.get(); + } + + boolean getTruncateAll() { + return truncateAll.get(); + } + + double getTruncateWidth() { + return truncateWidth.get(); + } + + boolean getOneEventPerRow() { + return oneEventPerRow.get(); + } + + DescriptionVisibility getDescrVisibility() { + return descrVisibility.get(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java index b0490b143c..b841c35d73 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java @@ -89,6 +89,7 @@ public class DetailViewPane extends AbstractVisualizationPane> highlightedNodes = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); private final ScrollingWrapper mainView; private final ScrollingWrapper pinnedView; + private final DetailViewLayoutSettings layoutSettings; public ObservableList getEventStripes() { return chart.getEventStripes(); @@ -107,10 +108,12 @@ public class DetailViewPane extends AbstractVisualizationPane(chart); - PinnedEventsChart pinnedChart = new PinnedEventsChart(controller, pinnedDateAxis, new EventAxis<>()); + PinnedEventsChart pinnedChart = new PinnedEventsChart(controller, pinnedDateAxis, new EventAxis<>(), selectedNodes, layoutSettings); pinnedView = new ScrollingWrapper<>(pinnedChart); pinnedChart.setMinSize(100, 100); setChartClickHandler(); //can we push this into chart @@ -270,14 +273,14 @@ public class DetailViewPane extends AbstractVisualizationPane { if (truncateWidthSlider.isValueChanging() == false) { - chart.getTruncateWidth().set(truncateWidthSlider.getValue()); + layoutSettings.truncateWidthProperty().set(truncateWidthSlider.getValue()); } }; truncateWidthSlider.valueProperty().addListener(sliderListener); @@ -285,11 +288,11 @@ public class DetailViewPane extends AbstractVisualizationPane { if (newToggle == countsRadio) { - chart.descrVisibilityProperty().set(DescriptionVisibility.COUNT_ONLY); + layoutSettings.descrVisibilityProperty().set(DescriptionVisibility.COUNT_ONLY); } else if (newToggle == showRadio) { - chart.descrVisibilityProperty().set(DescriptionVisibility.SHOWN); + layoutSettings.descrVisibilityProperty().set(DescriptionVisibility.SHOWN); } else if (newToggle == hiddenRadio) { - chart.descrVisibilityProperty().set(DescriptionVisibility.HIDDEN); + layoutSettings.descrVisibilityProperty().set(DescriptionVisibility.HIDDEN); } }); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java index 4cc3ac8d62..a62f230e4c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventClusterNode.java @@ -101,16 +101,10 @@ final public class EventClusterNode extends MultiEventNodeBase impl private Set activeQuickHidefilters; @ThreadConfined(type = ThreadConfined.ThreadType.JFX)//at start of layout pass private double descriptionWidth; + private final DetailViewLayoutSettings layoutSettings; @Override public ContextMenu getChartContextMenu() { @@ -157,40 +155,9 @@ public final class EventDetailsChart extends XYChart impl private final ObservableList< EventNodeBase> sortedStripeNodes = stripeNodes.sorted(Comparator.comparing(EventNodeBase::getStartMillis)); private final Map projectionMap = new ConcurrentHashMap<>(); - /** - * true == layout each event type in its own band, false == mix all the - * events together during layout - */ - private final SimpleBooleanProperty bandByType = new SimpleBooleanProperty(false); - /** - * true == enforce that no two events can share the same 'row', leading to - * sparser but possibly clearer layout. false == put unrelated events in the - * same 'row', creating a denser more compact layout - */ - private final SimpleBooleanProperty oneEventPerRow = new SimpleBooleanProperty(false); - - /** - * how much detail of the description to show in the ui - */ - private final SimpleObjectProperty descrVisibility = - new SimpleObjectProperty<>(DescriptionVisibility.SHOWN); - - /** - * true == truncate all the labels to the greater of the size of their - * timespan indicator or the value of truncateWidth. false == don't truncate - * the labels, alow them to extend past the timespan indicator and off the - * edge of the screen - */ - final SimpleBooleanProperty truncateAll = new SimpleBooleanProperty(false); - - /** - * the width to truncate all labels to if truncateAll is true. adjustable - * via slider if truncateAll is true - */ - final SimpleDoubleProperty truncateWidth = new SimpleDoubleProperty(200.0); - - EventDetailsChart(TimeLineController controller, DateAxis dateAxis, final Axis verticalAxis, ObservableList> selectedNodes) { + EventDetailsChart(TimeLineController controller, DateAxis dateAxis, final Axis verticalAxis, ObservableList> selectedNodes, DetailViewLayoutSettings layoutSettings) { super(dateAxis, verticalAxis); + this.layoutSettings = layoutSettings; this.controller = controller; this.filteredEvents = this.controller.getEventsModel(); @@ -224,11 +191,11 @@ public final class EventDetailsChart extends XYChart impl getPlotChildren().add(nodeGroup); //add listener for events that should trigger layout - bandByType.addListener(layoutInvalidationListener); - oneEventPerRow.addListener(layoutInvalidationListener); - truncateAll.addListener(layoutInvalidationListener); - truncateWidth.addListener(layoutInvalidationListener); - descrVisibility.addListener(layoutInvalidationListener); + layoutSettings.bandByTypeProperty().addListener(layoutInvalidationListener); + layoutSettings.oneEventPerRowProperty().addListener(layoutInvalidationListener); + layoutSettings.truncateAllProperty().addListener(layoutInvalidationListener); + layoutSettings.truncateAllProperty().addListener(layoutInvalidationListener); + layoutSettings.descrVisibilityProperty().addListener(layoutInvalidationListener); getController().getQuickHideFilters().addListener(layoutInvalidationListener); //this is needed to allow non circular binding of the guideline and timerangeRect heights to the height of the chart @@ -289,19 +256,11 @@ public final class EventDetailsChart extends XYChart impl intervalSelector = null; } - public synchronized SimpleBooleanProperty bandByTypeProperty() { - return bandByType; - } - @Override public IntervalSelector newIntervalSelector() { return new DetailIntervalSelector(this); } - synchronized void setBandByType(Boolean t1) { - bandByType.set(t1); - } - /** * get the DateTime along the x-axis that corresponds to the given * x-coordinate in the coordinate system of this {@link EventDetailsChart} @@ -326,22 +285,6 @@ public final class EventDetailsChart extends XYChart impl getChartChildren().add(getIntervalSelector()); } - SimpleBooleanProperty oneEventPerRowProperty() { - return oneEventPerRow; - } - - SimpleDoubleProperty getTruncateWidth() { - return truncateWidth; - } - - SimpleBooleanProperty truncateAllProperty() { - return truncateAll; - } - - SimpleObjectProperty< DescriptionVisibility> descrVisibilityProperty() { - return descrVisibility; - } - /** * @see note in main section of class JavaDoc * @@ -446,9 +389,9 @@ public final class EventDetailsChart extends XYChart impl .collect(Collectors.toSet()); //This dosn't change during a layout pass and is expensive to compute per node. So we do it once at the start - descriptionWidth = truncateAll.get() ? truncateWidth.get() : USE_PREF_SIZE; + descriptionWidth = layoutSettings.getTruncateAll() ? layoutSettings.getTruncateWidth() : USE_PREF_SIZE; - if (bandByType.get()) { + if (layoutSettings.getBandByType()) { sortedStripeNodes.stream() .collect(Collectors.groupingBy(EventNodeBase::getEventType)).values() .forEach(inputNodes -> maxY.set(layoutEventBundleNodes(inputNodes, maxY.get()))); @@ -547,7 +490,7 @@ public final class EventDetailsChart extends XYChart impl double xRight = xLeft + w + MINIMUM_EVENT_NODE_GAP; //initial test position - double yTop = (oneEventPerRow.get()) + double yTop = (layoutSettings.getOneEventPerRow()) ? (localMax + MINIMUM_EVENT_NODE_GAP)// if onePerRow, just put it at end : computeYTop(minY, h, maxXatY, xLeft, xRight); @@ -607,19 +550,19 @@ public final class EventDetailsChart extends XYChart impl * * Set layout paramaters on the given node and layout its children * - * @param bundleNode the Node to layout + * @param eventNode the Node to layout * @param descriptionWdith the maximum width for the description text */ - private void layoutBundleHelper(final EventNodeBase< ?> bundleNode) { + private void layoutBundleHelper(final EventNodeBase< ?> eventNode) { //make sure it is shown - bundleNode.setVisible(true); - bundleNode.setManaged(true); + eventNode.setVisible(true); + eventNode.setManaged(true); //apply advanced layout description visibility options - bundleNode.setDescriptionVisibility(descrVisibility.get()); - bundleNode.setMaxDescriptionWidth(descriptionWidth); + eventNode.setDescriptionVisibility(layoutSettings.getDescrVisibility()); + eventNode.setMaxDescriptionWidth(descriptionWidth); //do recursive layout - bundleNode.layoutChildren(); + eventNode.layoutChildren(); } /** diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventNodeBase.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventNodeBase.java index 156cdfe72b..a9e111c73a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventNodeBase.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventNodeBase.java @@ -277,7 +277,20 @@ public abstract class EventNodeBase extends StackPan abstract void setDescriptionVisibiltiyImpl(DescriptionVisibility get); - abstract void applyHighlightEffect(boolean b); + /** + * apply the 'effect' to visually indicate highlighted nodes + * + * @param applied true to apply the highlight 'effect', false to remove it + */ + synchronized void applyHighlightEffect(boolean applied) { + if (applied) { + descrLabel.setStyle("-fx-font-weight: bold;"); // NON-NLS + setBackground(highlightedBackground); + } else { + descrLabel.setStyle("-fx-font-weight: normal;"); // NON-NLS + setBackground(defaultBackground); + } + } void applyHighlightEffect() { applyHighlightEffect(true); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java index f0418ed20e..100be6b0d2 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java @@ -84,21 +84,7 @@ final public class EventStripeNode extends MultiEventNodeBase> getSubNodes() { return subNodes; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/PinnedEventsChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/PinnedEventsChart.java index 2732e1cb40..e28b80c2c6 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/PinnedEventsChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/PinnedEventsChart.java @@ -26,7 +26,10 @@ import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.MissingResourceException; +import java.util.stream.Collectors; import javafx.application.Platform; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyDoubleWrapper; import javafx.collections.FXCollections; @@ -94,6 +97,14 @@ public final class PinnedEventsChart extends XYChart im * the maximum y value used so far during the most recent layout pass */ private final ReadOnlyDoubleWrapper maxY = new ReadOnlyDoubleWrapper(0.0); + private final ObservableList> selectedNodes; + private final DetailViewLayoutSettings layoutSettings; + /** + * listener that triggers chart layout pass + */ + private final InvalidationListener layoutInvalidationListener = (Observable o) -> { + layoutPlotChildren(); + }; /** * @@ -102,9 +113,9 @@ public final class PinnedEventsChart extends XYChart im * @param verticalAxis the value of verticalAxis * @param selectedNodes1 the value of selectedNodes1 */ - PinnedEventsChart(TimeLineController controller, DateAxis dateAxis, final Axis verticalAxis) { + PinnedEventsChart(TimeLineController controller, DateAxis dateAxis, final Axis verticalAxis, ObservableList> selectedNodes, DetailViewLayoutSettings layoutSettings) { super(dateAxis, verticalAxis); - + this.layoutSettings = layoutSettings; this.controller = controller; this.filteredEvents = this.controller.getEventsModel(); @@ -134,11 +145,12 @@ public final class PinnedEventsChart extends XYChart im setData(FXCollections.observableArrayList()); getData().add(series); // //add listener for events that should trigger layout -// bandByType.addListener(layoutInvalidationListener); -// oneEventPerRow.addListener(layoutInvalidationListener); -// truncateAll.addListener(layoutInvalidationListener); -// truncateWidth.addListener(layoutInvalidationListener); -// descrVisibility.addListener(layoutInvalidationListener); + layoutSettings.bandByTypeProperty().addListener(layoutInvalidationListener); + layoutSettings.oneEventPerRowProperty().addListener(layoutInvalidationListener); + layoutSettings.truncateAllProperty().addListener(layoutInvalidationListener); + layoutSettings.truncateAllProperty().addListener(layoutInvalidationListener); + layoutSettings.descrVisibilityProperty().addListener(layoutInvalidationListener); + getController().getQuickHideFilters().addListener(layoutInvalidationListener); // getController().getQuickHideFilters().addListener(layoutInvalidationListener); // //this is needed to allow non circular binding of the guideline and timerangeRect heights to the height of the chart @@ -168,9 +180,7 @@ public final class PinnedEventsChart extends XYChart im requestChartLayout(); }); - -// this.selectedNodes = selectedNodes; -// selectedNodes.addListener(new SelectionChangeHandler()); + this.selectedNodes = selectedNodes; } @Override @@ -233,7 +243,7 @@ public final class PinnedEventsChart extends XYChart im @Override public ObservableList> getSelectedNodes() { - return FXCollections.observableArrayList(); + return selectedNodes; } @Override @@ -257,7 +267,9 @@ public final class PinnedEventsChart extends XYChart im double xRight = xLeft + w + MINIMUM_EVENT_NODE_GAP; //initial test position - double yTop = computeYTop(minY, h, maxXatY, xLeft, xRight); + double yTop = (layoutSettings.getOneEventPerRow()) + ? (localMax + MINIMUM_EVENT_NODE_GAP)// if onePerRow, just put it at end + : computeYTop(minY, h, maxXatY, xLeft, xRight); localMax = Math.max(yTop + h, localMax); @@ -301,17 +313,15 @@ public final class PinnedEventsChart extends XYChart im // .map(DescriptionFilter::getDescription) // .collect(Collectors.toSet()); //This dosn't change during a layout pass and is expensive to compute per node. So we do it once at the start - descriptionWidth = /* - * truncateAll.get() ? truncateWidth.get() : - */ USE_PREF_SIZE; + descriptionWidth = layoutSettings.getTruncateAll() ? layoutSettings.getTruncateWidth() : USE_PREF_SIZE; -// if (bandByType.get()) { -// sortedStripeNodes.stream() -// .collect(Collectors.groupingBy(EventStripeNode::getEventType)).values() -// .forEach(inputNodes -> maxY.set(layoutEventBundleNodes(inputNodes, maxY.get()))); -// } else { - maxY.set(layoutEventBundleNodes(sortedEventNodes.sorted(Comparator.comparing(EventNodeBase::getStartMillis)), 0)); -// } + if (layoutSettings.getBandByType()) { + sortedEventNodes.stream() + .collect(Collectors.groupingBy(EventNodeBase::getEventType)).values() + .forEach(inputNodes -> maxY.set(layoutEventBundleNodes(inputNodes, maxY.get()))); + } else { + maxY.set(layoutEventBundleNodes(sortedEventNodes.sorted(Comparator.comparing(EventNodeBase::getStartMillis)), 0)); + } setCursor(null); } @@ -326,6 +336,7 @@ public final class PinnedEventsChart extends XYChart im public synchronized void setVScroll(double vScrollValue) { nodeGroup.setTranslateY(-vScrollValue); } + public ReadOnlyDoubleProperty maxVScrollProperty() { return maxY.getReadOnlyProperty(); } @@ -402,8 +413,8 @@ public final class PinnedEventsChart extends XYChart im eventNode.setVisible(true); eventNode.setManaged(true); //apply advanced layout description visibility options - eventNode.setDescriptionVisibility(DescriptionVisibility.SHOWN); - eventNode.setMaxDescriptionWidth(USE_PREF_SIZE); + eventNode.setDescriptionVisibility(layoutSettings.getDescrVisibility()); + eventNode.setMaxDescriptionWidth(descriptionWidth); //do recursive layout eventNode.layoutChildren(); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/SingleEventNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/SingleEventNode.java index 2a501bd212..a209545e45 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/SingleEventNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/SingleEventNode.java @@ -127,21 +127,7 @@ final class SingleEventNode extends EventNodeBase { chart.requestTimelineChartLayout(); } - /** - * apply the 'effect' to visually indicate highlighted nodes - * - * @param applied true to apply the highlight 'effect', false to remove it - */ - @Override - public synchronized void applyHighlightEffect(boolean applied) { - if (applied) { - descrLabel.setStyle("-fx-font-weight: bold;"); // NON-NLS - setBackground(highlightedBackground); - } else { - descrLabel.setStyle("-fx-font-weight: normal;"); // NON-NLS - setBackground(defaultBackground); - } - } + /** * @param w the maximum width the description label should have