19 package org.sleuthkit.autopsy.timeline.ui;
21 import java.util.Comparator;
22 import java.util.HashMap;
24 import javafx.application.Platform;
25 import javafx.beans.binding.DoubleBinding;
26 import javafx.collections.FXCollections;
27 import javafx.collections.ListChangeListener;
28 import javafx.collections.ObservableList;
29 import javafx.collections.transformation.SortedList;
30 import javafx.geometry.Pos;
31 import javafx.scene.Node;
32 import javafx.scene.chart.Axis;
33 import javafx.scene.chart.XYChart;
34 import javafx.scene.control.Label;
35 import javafx.scene.control.OverrunStyle;
36 import javafx.scene.control.Tooltip;
37 import javafx.scene.layout.Border;
38 import javafx.scene.layout.BorderStroke;
39 import javafx.scene.layout.BorderStrokeStyle;
40 import javafx.scene.layout.BorderWidths;
41 import javafx.scene.layout.CornerRadii;
42 import javafx.scene.layout.HBox;
43 import javafx.scene.layout.Pane;
44 import javafx.scene.layout.Region;
45 import javafx.scene.layout.VBox;
46 import javafx.scene.paint.Color;
47 import javafx.scene.text.Font;
48 import javafx.scene.text.FontWeight;
49 import javafx.scene.text.Text;
50 import javafx.scene.text.TextAlignment;
51 import javax.annotation.concurrent.Immutable;
52 import org.apache.commons.lang3.StringUtils;
53 import org.openide.util.NbBundle;
77 @NbBundle.Messages(
"AbstractTimelineChart.defaultTooltip.text=Drag the mouse to select a time interval to zoom into.\nRight-click for more actions.")
78 private static final Tooltip
DEFAULT_TOOLTIP =
new Tooltip(Bundle.AbstractTimelineChart_defaultTooltip_text());
79 private static final Border
ONLY_LEFT_BORDER =
new Border(
new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY,
new BorderWidths(0, 0, 0, 1)));
104 protected final ObservableList<XYChart.Series<X, Y>>
dataSeries = FXCollections.<XYChart.Series<X, Y>>observableArrayList();
113 private final Region
spacer =
new Region();
115 final private ObservableList<NodeType>
selectedNodes = FXCollections.observableArrayList();
176 abstract protected Boolean
isTickBold(X value);
210 abstract protected Axis<X>
getXAxis();
217 abstract protected Axis<Y>
getYAxis();
232 for (EventType eventType : EventType.allTypes) {
233 XYChart.Series<X, Y> series =
new XYChart.Series<>();
235 eventTypeToSeriesMap.put(eventType, series);
236 dataSeries.add(series);
248 protected final XYChart.Series<X, Y>
getSeries(
final EventType et) {
249 return eventTypeToSeriesMap.get(et);
259 Platform.runLater(() -> {
261 vBox.setFillWidth(
false);
263 hBox.setFillHeight(
false);
266 getSpacer().minWidthProperty().bind(spacerSize);
267 getSpacer().prefWidthProperty().bind(spacerSize);
268 getSpacer().maxWidthProperty().bind(spacerSize);
273 selectedNodes.addListener((ListChangeListener.Change<? extends NodeType> change) -> {
274 while (change.next()) {
275 change.getRemoved().forEach(node -> applySelectionEffect(node, false));
276 change.getAddedSubList().forEach(node -> applySelectionEffect(node, true));
304 protected synchronized
void layoutDateLabels() {
306 contextLabelPane.getChildren().clear();
307 specificLabelPane.getChildren().clear();
310 SortedList<Axis.TickMark<X>> tickMarks = getXAxis().getTickMarks().sorted(Comparator.comparing(Axis.TickMark::getPosition));
312 if (tickMarks.isEmpty()) {
319 Platform.runLater(this::layoutDateLabels);
322 double spacing = getTickSpacing();
325 TwoPartDateTime dateTime =
new TwoPartDateTime(getTickMarkLabel(tickMarks.get(0).getValue()));
326 String lastSeenContextLabel = dateTime.context;
329 double specificLabelX = 0;
331 if (dateTime.context.isEmpty()) {
333 for (Axis.TickMark<X> t : tickMarks) {
334 addSpecificLabel(
new TwoPartDateTime(getTickMarkLabel(t.getValue())).specifics,
337 isTickBold(t.getValue())
339 specificLabelX += spacing;
344 double contextLabelX = 0;
345 double contextLabelWidth = 0;
347 for (Axis.TickMark<X> t : tickMarks) {
349 dateTime =
new TwoPartDateTime(getTickMarkLabel(t.getValue()));
352 if (lastSeenContextLabel.equals(dateTime.context)) {
354 contextLabelWidth += spacing;
356 addContextLabel(lastSeenContextLabel, contextLabelWidth, contextLabelX);
358 lastSeenContextLabel = dateTime.context;
359 contextLabelX += contextLabelWidth;
360 contextLabelWidth = spacing;
363 addSpecificLabel(dateTime.specifics, spacing, specificLabelX, isTickBold(t.getValue()));
366 specificLabelX += spacing;
369 addContextLabel(lastSeenContextLabel, contextLabelWidth, contextLabelX);
373 requestParentLayout();
387 private synchronized void addSpecificLabel(String labelText,
double labelWidth,
double labelX,
boolean bold) {
388 Text label =
new Text(
" " + labelText +
" ");
389 label.setTextAlignment(TextAlignment.CENTER);
390 label.setFont(Font.font(null, bold ? FontWeight.BOLD : FontWeight.NORMAL, 10));
392 label.relocate(labelX + labelWidth / 2 - label.getBoundsInLocal().getWidth() / 2, 0);
395 if (specificLabelPane.getChildren().isEmpty()) {
397 specificLabelPane.getChildren().add(label);
401 final Node lastLabel = specificLabelPane.getChildren().get(specificLabelPane.getChildren().size() - 1);
403 if (
false == lastLabel.getBoundsInParent().intersects(label.getBoundsInParent())) {
404 specificLabelPane.getChildren().add(label);
418 private synchronized void addContextLabel(String labelText,
double labelWidth,
double labelX) {
419 Label label =
new Label(labelText);
420 label.setAlignment(Pos.CENTER);
421 label.setTextAlignment(TextAlignment.CENTER);
422 label.setFont(Font.font(10));
425 label.setTextOverrun(OverrunStyle.LEADING_ELLIPSIS);
427 label.setMinWidth(labelWidth);
428 label.setPrefWidth(labelWidth);
429 label.setMaxWidth(labelWidth);
430 label.relocate(labelX, 0);
433 label.setBorder(null);
435 label.setBorder(ONLY_LEFT_BORDER);
438 contextLabelPane.getChildren().add(label);
469 int splitIndex = StringUtils.lastIndexOfAny(dateString,
" ",
"-",
":");
470 if (splitIndex < 0) {
471 specifics = dateString;
474 specifics = StringUtils.substring(dateString, splitIndex + 1);
475 context = StringUtils.substring(dateString, 0, splitIndex);
final XYChart.Series< X, Y > getSeries(final EventType et)
final Map< EventType, XYChart.Series< X, Y > > eventTypeToSeriesMap
Pane getContextLabelPane()
static final Logger LOGGER
abstract double getTickSpacing()
synchronized void addSpecificLabel(String labelText, double labelWidth, double labelX, boolean bold)
Pane getSpecificLabelPane()
abstract Axis< Y > getYAxis()
void removeSelectionEffect(NodeType node)
AbstractTimelineChart(TimeLineController controller)
final ObservableList< NodeType > selectedNodes
static Tooltip getDefaultTooltip()
synchronized void addContextLabel(String labelText, double labelWidth, double labelX)
static final Tooltip DEFAULT_TOOLTIP
ObservableList< NodeType > getSelectedNodes()
final ObservableList< XYChart.Series< X, Y > > dataSeries
abstract double getAxisMargin()
void applySelectionEffect(NodeType node)
final TimeLineController controller
final Pane contextLabelPane
synchronized static Logger getLogger(String name)
abstract String getTickMarkLabel(X tickValue)
abstract Axis< X > getXAxis()
final void createSeries()
void setChart(ChartType chart)
static final Border ONLY_LEFT_BORDER
abstract Boolean isTickBold(X value)
void setStatusMessage(String string)
final Pane specificLabelPane