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();
233 XYChart.Series<X, Y> series =
new XYChart.Series<>();
234 series.setName(eventType.getDisplayName());
235 eventTypeToSeriesMap.put(eventType, series);
236 dataSeries.add(series);
248 protected final XYChart.Series<X, Y>
getSeries(
final TimelineEventType eventType) {
249 return eventTypeToSeriesMap.get(eventType);
259 Platform.runLater(
new Runnable() {
263 vBox.setFillWidth(
false);
265 hBox.setFillHeight(
false);
268 getSpacer().minWidthProperty().bind(spacerSize);
269 getSpacer().prefWidthProperty().bind(spacerSize);
270 getSpacer().maxWidthProperty().bind(spacerSize);
276 selectedNodes.addListener((ListChangeListener.Change<? extends NodeType> change) -> {
277 while (change.next()) {
278 change.getRemoved().forEach(node -> applySelectionEffect(node, false));
279 change.getAddedSubList().forEach(node -> applySelectionEffect(node, true));
307 protected synchronized
void layoutDateLabels() {
309 contextLabelPane.getChildren().clear();
310 specificLabelPane.getChildren().clear();
313 SortedList<Axis.TickMark<X>> tickMarks = getXAxis().getTickMarks().sorted(Comparator.comparing(Axis.TickMark::getPosition));
315 if (tickMarks.isEmpty()) {
322 Platform.runLater(this::layoutDateLabels);
325 double spacing = getTickSpacing();
328 TwoPartDateTime dateTime =
new TwoPartDateTime(getTickMarkLabel(tickMarks.get(0).getValue()));
329 String lastSeenContextLabel = dateTime.context;
332 double specificLabelX = 0;
334 if (dateTime.context.isEmpty()) {
336 for (Axis.TickMark<X> t : tickMarks) {
337 addSpecificLabel(
new TwoPartDateTime(getTickMarkLabel(t.getValue())).specifics,
340 isTickBold(t.getValue())
342 specificLabelX += spacing;
347 double contextLabelX = 0;
348 double contextLabelWidth = 0;
350 for (Axis.TickMark<X> t : tickMarks) {
352 dateTime =
new TwoPartDateTime(getTickMarkLabel(t.getValue()));
355 if (lastSeenContextLabel.equals(dateTime.context)) {
357 contextLabelWidth += spacing;
359 addContextLabel(lastSeenContextLabel, contextLabelWidth, contextLabelX);
361 lastSeenContextLabel = dateTime.context;
362 contextLabelX += contextLabelWidth;
363 contextLabelWidth = spacing;
366 addSpecificLabel(dateTime.specifics, spacing, specificLabelX, isTickBold(t.getValue()));
369 specificLabelX += spacing;
372 addContextLabel(lastSeenContextLabel, contextLabelWidth, contextLabelX);
376 requestParentLayout();
390 private synchronized void addSpecificLabel(String labelText,
double labelWidth,
double labelX,
boolean bold) {
391 Text label =
new Text(
" " + labelText +
" ");
392 label.setTextAlignment(TextAlignment.CENTER);
393 label.setFont(Font.font(null, bold ? FontWeight.BOLD : FontWeight.NORMAL, 10));
395 label.relocate(labelX + labelWidth / 2 - label.getBoundsInLocal().getWidth() / 2, 0);
398 if (specificLabelPane.getChildren().isEmpty()) {
400 specificLabelPane.getChildren().add(label);
404 final Node lastLabel = specificLabelPane.getChildren().get(specificLabelPane.getChildren().size() - 1);
406 if (
false == lastLabel.getBoundsInParent().intersects(label.getBoundsInParent())) {
407 specificLabelPane.getChildren().add(label);
421 private synchronized void addContextLabel(String labelText,
double labelWidth,
double labelX) {
422 Label label =
new Label(labelText);
423 label.setAlignment(Pos.CENTER);
424 label.setTextAlignment(TextAlignment.CENTER);
425 label.setFont(Font.font(10));
428 label.setTextOverrun(OverrunStyle.LEADING_ELLIPSIS);
430 label.setMinWidth(labelWidth);
431 label.setPrefWidth(labelWidth);
432 label.setMaxWidth(labelWidth);
433 label.relocate(labelX, 0);
436 label.setBorder(null);
438 label.setBorder(ONLY_LEFT_BORDER);
441 contextLabelPane.getChildren().add(label);
472 int splitIndex = StringUtils.lastIndexOfAny(dateString,
" ",
"-",
":");
473 if (splitIndex < 0) {
474 specifics = dateString;
477 specifics = StringUtils.substring(dateString, splitIndex + 1);
478 context = StringUtils.substring(dateString, 0, splitIndex);
static final Logger logger
Pane getContextLabelPane()
TimeLineController getController()
final Map< TimelineEventType, XYChart.Series< X, Y > > eventTypeToSeriesMap
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
EventsModel getEventsModel()
static Tooltip getDefaultTooltip()
synchronized void addContextLabel(String labelText, double labelWidth, double labelX)
static final Tooltip DEFAULT_TOOLTIP
final XYChart.Series< X, Y > getSeries(final TimelineEventType eventType)
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