/*
* Autopsy Forensic Browser
*
* Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.timeline.ui.detailview;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javafx.collections.ListChangeListener;
import javafx.scene.chart.Axis;
import javafx.scene.shape.Line;
import javafx.scene.shape.StrokeLineCap;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
import org.sleuthkit.autopsy.timeline.ui.ContextMenuProvider;
/**
* Custom implementation of XYChart to graph events on a horizontal
* timeline.
*
* The horizontal DateAxis controls the tick-marks and the horizontal
* layout of the nodes representing events. The vertical NumberAxis does
* nothing (although a custom implementation could help with the vertical
* layout?)
*
* Series help organize events for the banding by event type, we could add a
* node to contain each band if we need a place for per band controls.
*
* //TODO: refactor the projected lines to a separate class. -jm
*/
public final class PrimaryDetailsChartLane extends DetailsChartLane<EventStripe> implements ContextMenuProvider {
private static final int PROJECTED_LINE_Y_OFFSET = 5;
private static final int PROJECTED_LINE_STROKE_WIDTH = 5;
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private final Map<EventCluster, Line> projectionMap = new ConcurrentHashMap<>();
PrimaryDetailsChartLane(DetailsChart parentChart, DateAxis dateAxis, final Axis<EventStripe> verticalAxis) {
super(parentChart, dateAxis, verticalAxis, true);
//add listener for events that should trigger layout
getController().getQuickHideFilters().addListener(layoutInvalidationListener);
parentChart.getRootEventStripes().addListener((ListChangeListener.Change<? extends EventStripe> change) -> {
while (change.next()) {
change.getAddedSubList().stream().forEach(this::addEvent);
change.getRemoved().stream().forEach(this::removeEvent);
}
requestChartLayout();
});
parentChart.getRootEventStripes().stream().forEach(this::addEvent);
requestChartLayout();
getSelectedNodes().addListener((ListChangeListener.Change<? extends EventNodeBase<?>> change) -> {
while (change.next()) {
change.getRemoved().forEach(removedNode -> {
removedNode.getEvent().getClusters().forEach(cluster -> {
Line removedLine = projectionMap.remove(cluster);
getChartChildren().removeAll(removedLine);
});
});
change.getAddedSubList().forEach(addedNode -> {
for (EventCluster range : addedNode.getEvent().getClusters()) {
double y = dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET;
Line line =
new Line(dateAxis.localToParent(getXForEpochMillis(range.getStartMillis()), 0).getX(), y,
dateAxis.localToParent(getXForEpochMillis(range.getEndMillis()), 0).getX(), y);
line.setStroke(addedNode.getEventType().getColor().deriveColor(0, 1, 1, .5));
line.setStrokeWidth(PROJECTED_LINE_STROKE_WIDTH);
line.setStrokeLineCap(StrokeLineCap.ROUND);
projectionMap.put(range, line);
getChartChildren().add(line);
}
});
}
});
}
private double getParentXForEpochMillis(Long epochMillis) {
return getXAxis().localToParent(getXForEpochMillis(epochMillis), 0).getX();
}
@Override
void doAdditionalLayout() {
for (final Map.Entry<EventCluster, Line> entry : projectionMap.entrySet()) {
final EventCluster cluster = entry.getKey();
final Line line = entry.getValue();
line.setStartX(getParentXForEpochMillis(cluster.getStartMillis()));
line.setEndX(getParentXForEpochMillis(cluster.getEndMillis()));
line.setStartY(getXAxis().getLayoutY() + PROJECTED_LINE_Y_OFFSET);
line.setEndY(getXAxis().getLayoutY() + PROJECTED_LINE_Y_OFFSET);
}
}
}