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