19 package org.sleuthkit.autopsy.timeline.ui.countsview;
 
   21 import java.util.ArrayList;
 
   22 import java.util.Arrays;
 
   24 import java.util.concurrent.ConcurrentHashMap;
 
   25 import java.util.function.Function;
 
   26 import javafx.application.Platform;
 
   27 import javafx.beans.Observable;
 
   28 import javafx.beans.property.SimpleObjectProperty;
 
   29 import javafx.collections.FXCollections;
 
   30 import javafx.concurrent.Task;
 
   31 import javafx.event.ActionEvent;
 
   32 import javafx.event.EventHandler;
 
   33 import javafx.fxml.FXML;
 
   34 import javafx.scene.Cursor;
 
   35 import javafx.scene.Node;
 
   36 import javafx.scene.chart.BarChart;
 
   37 import javafx.scene.chart.CategoryAxis;
 
   38 import javafx.scene.chart.NumberAxis;
 
   39 import javafx.scene.chart.StackedBarChart;
 
   40 import javafx.scene.chart.XYChart;
 
   41 import javafx.scene.control.*;
 
   42 import javafx.scene.effect.DropShadow;
 
   43 import javafx.scene.effect.Effect;
 
   44 import javafx.scene.effect.Lighting;
 
   45 import javafx.scene.image.ImageView;
 
   46 import javafx.scene.input.MouseButton;
 
   47 import javafx.scene.input.MouseEvent;
 
   48 import javafx.scene.layout.HBox;
 
   49 import javafx.scene.layout.Pane;
 
   50 import javafx.scene.layout.Region;
 
   51 import javax.swing.JOptionPane;
 
   52 import org.controlsfx.control.action.ActionGroup;
 
   53 import org.controlsfx.control.action.ActionUtils;
 
   54 import org.joda.time.DateTime;
 
   55 import org.joda.time.Interval;
 
   56 import org.joda.time.Seconds;
 
   57 import org.openide.util.NbBundle;
 
  102     private final CategoryAxis 
dateAxis = 
new CategoryAxis(FXCollections.<String>observableArrayList());
 
  111         return labelValueString;
 
  116         return dataSets.stream().flatMap((series) -> series.getData().stream())
 
  117                 .anyMatch((data) -> data.getXValue().equals(value) && data.getYValue().intValue() > 0);
 
  122         ContextMenu chartContextMenu = ActionUtils.createContextMenu(Arrays.asList(
new ActionGroup(
 
  123                 NbBundle.getMessage(
this.getClass(), 
"Timeline.ui.countsview.contextMenu.ActionGroup.zoomHistory.title"),
 
  125         chartContextMenu.setAutoHide(
true);
 
  126         return chartContextMenu;
 
  131         return new LoggedTask<Boolean>(NbBundle.getMessage(this.getClass(), 
"CountsViewPane.loggedTask.name"), 
true) {
 
  134             protected Boolean call() 
throws Exception {
 
  138                 updateProgress(-1, 1);
 
  139                 updateMessage(NbBundle.getMessage(
this.getClass(), 
"CountsViewPane.loggedTask.prepUpdate"));
 
  140                 Platform.runLater(() -> {
 
  141                     setCursor(Cursor.WAIT);
 
  145                 chart.setRangeInfo(rangeInfo);
 
  155                 Platform.runLater(() -> {
 
  156                     updateMessage(NbBundle.getMessage(
this.getClass(), 
"CountsViewPane.loggedTask.resetUI"));
 
  157                     eventTypeMap.clear();
 
  159                     dateAxis.getCategories().clear();
 
  161                     DateTime start = timeRange.getStart();
 
  162                     while (timeRange.contains(start)) {
 
  165                         dateAxis.getCategories().add(dateString);
 
  175                 DateTime start = timeRange.getStart();
 
  176                 while (timeRange.contains(start)) {
 
  180                     final Interval interval = 
new Interval(start, end);
 
  191                     for (
final EventType et : eventCounts.keySet()) {
 
  196                         final Long count = eventCounts.get(et);
 
  199                             final double adjustedCount = count == 0 ? 0 : scale.get().adjust(count);
 
  201                             dateMax += adjustedCount;
 
  202                             final XYChart.Data<String, Number> xyData = 
new BarChart.Data<>(dateString, adjustedCount);
 
  204                             xyData.nodeProperty().addListener((Observable o) -> {
 
  205                                 final Node node = xyData.getNode();
 
  208                                     node.setCursor(Cursor.HAND);
 
  210                                     node.setOnMouseEntered((MouseEvent event) -> {
 
  212                                         final Tooltip tooltip = 
new Tooltip(
 
  213                                                 NbBundle.getMessage(
this.getClass(), 
"CountsViewPane.tooltip.text",
 
  217                                                                     interval.getEnd().toString(
 
  219                                         tooltip.setGraphic(
new ImageView(et.getFXImage()));
 
  220                                         Tooltip.install(node, tooltip);
 
  221                                         node.setEffect(
new DropShadow(10, et.getColor()));
 
  223                                     node.setOnMouseExited((MouseEvent event) -> {
 
  225                                             node.setEffect(SELECTED_NODE_EFFECT);
 
  227                                             node.setEffect(null);
 
  231                                     node.addEventHandler(MouseEvent.MOUSE_CLICKED, 
new BarClickHandler(node, dateString, interval, et));
 
  235                             max = Math.max(max, dateMax);
 
  237                             final double fmax = max;
 
  239                             Platform.runLater(() -> {
 
  241                                         NbBundle.getMessage(
this.getClass(), 
"CountsViewPane.loggedTask.updatingCounts"));
 
  244                                     countAxis.setTickUnit(Math.pow(10, Math.max(0, Math.floor(Math.log10(fmax)) - 1)));
 
  246                                     countAxis.setTickUnit(Double.MAX_VALUE);
 
  248                                 countAxis.setUpperBound(1 + fmax * 1.2);
 
  253                             final double fmax = max;
 
  255                             Platform.runLater(() -> {
 
  257                                         NbBundle.getMessage(
this.getClass(), 
"CountsViewPane.loggedTask.updatingCounts"));
 
  264                 Platform.runLater(() -> {
 
  265                     updateMessage(NbBundle.getMessage(
this.getClass(), 
"CountsViewPane.loggedTask.wrappingUp"));
 
  266                     updateProgress(1, 1);
 
  268                     setCursor(Cursor.NONE);
 
  277         super(partPane, contextPane, spacer);
 
  278         chart = 
new EventCountsChart(dateAxis, countAxis);
 
  285         dateAxis.getTickMarks().addListener((Observable observable) -> {
 
  288         dateAxis.categorySpacingProperty().addListener((Observable observable) -> {
 
  291         dateAxis.getCategories().addListener((Observable observable) -> {
 
  295         spacer.minWidthProperty().bind(countAxis.widthProperty().add(countAxis.tickLengthProperty()).add(dateAxis.startMarginProperty().multiply(2)));
 
  296         spacer.prefWidthProperty().bind(countAxis.widthProperty().add(countAxis.tickLengthProperty()).add(dateAxis.startMarginProperty().multiply(2)));
 
  297         spacer.maxWidthProperty().bind(countAxis.widthProperty().add(countAxis.tickLengthProperty()).add(dateAxis.startMarginProperty().multiply(2)));
 
  299         scale.addListener(o -> {
 
  300             countAxis.tickLabelsVisibleProperty().bind(scale.isEqualTo(
ScaleType.
LINEAR));
 
  301             countAxis.tickMarkVisibleProperty().bind(scale.isEqualTo(
ScaleType.
LINEAR));
 
  302             countAxis.minorTickVisibleProperty().bind(scale.isEqualTo(
ScaleType.
LINEAR));
 
  319         return dateAxis.getCategorySpacing();
 
  345     private XYChart.Series<String, Number> 
getSeries(
final EventType et) {
 
  346         XYChart.Series<String, Number> series = eventTypeMap.get(et);
 
  347         if (series == null) {
 
  348             series = 
new XYChart.Series<>();
 
  350             eventTypeMap.put(et, series);
 
  380         public BarClickHandler(Node node, String dateString, Interval countInterval, EventType type) {
 
  381             this.interval = countInterval;
 
  384             this.startDateString = dateString;
 
  390             if (e.getClickCount() == 1) {     
 
  391                 if (e.getButton().equals(MouseButton.PRIMARY)) {
 
  394                 } 
else if (e.getButton().equals(MouseButton.SECONDARY)) {
 
  395                     Platform.runLater(() -> {
 
  396                         chart.getContextMenu().hide();
 
  398                         if (barContextMenu == null) {
 
  399                             barContextMenu = 
new ContextMenu();
 
  400                             barContextMenu.setAutoHide(
true);
 
  401                             barContextMenu.getItems().addAll(
 
  402                                     new MenuItem(NbBundle.getMessage(
this.getClass(),
 
  403                                                                      "Timeline.ui.countsview.menuItem.selectTimeRange")) {
 
  405                                             setOnAction((ActionEvent t) -> {
 
  409                                                 for (XYChart.Series<String, Number> s : 
dataSets) {
 
  410                                                     s.getData().forEach((XYChart.Data<String, Number> d) -> {
 
  411                                                         if (startDateString.contains(d.getXValue())) {
 
  412                                                             selectedNodes.add(d.getNode());
 
  419                                     new MenuItem(NbBundle.getMessage(
this.getClass(),
 
  420                                                                      "Timeline.ui.countsview.menuItem.selectEventType")) {
 
  422                                             setOnAction((ActionEvent t) -> {
 
  426                                                 eventTypeMap.get(type).getData().forEach((d) -> {
 
  433                                     new MenuItem(NbBundle.getMessage(
this.getClass(),
 
  434                                                                      "Timeline.ui.countsview.menuItem.selectTimeandType")) {
 
  436                                             setOnAction((ActionEvent t) -> {
 
  442                                     new SeparatorMenuItem(),
 
  443                                     new MenuItem(NbBundle.getMessage(
this.getClass(),
 
  444                                                                      "Timeline.ui.countsview.menuItem.zoomIntoTimeRange")) {
 
  446                                             setOnAction((ActionEvent t) -> {
 
  447                                                 if (interval.toDuration().isShorterThan(Seconds.ONE.toStandardDuration()) == 
false) {
 
  456                         barContextMenu.show(node, e.getScreenX(), e.getScreenY());
 
  460             } 
else if (e.getClickCount() >= 2) {  
 
  461                 if (interval.toDuration().isLongerThan(Seconds.ONE.toStandardDuration())) {
 
  465                     int showConfirmDialog = JOptionPane.showConfirmDialog(null,
 
  466                             NbBundle.getMessage(
CountsViewPane.class, 
"CountsViewPane.detailSwitchMessage"),
 
  467                             NbBundle.getMessage(
CountsViewPane.class, 
"CountsViewPane.detailSwitchTitle"), JOptionPane.YES_NO_OPTION);
 
  468                     if (showConfirmDialog == JOptionPane.YES_OPTION) {
 
  513             assert logRadio != null : 
"fx:id=\"logRadio\" was not injected: check your FXML file 'CountsViewSettingsPane.fxml'."; 
 
  514             assert linearRadio != null : 
"fx:id=\"linearRadio\" was not injected: check your FXML file 'CountsViewSettingsPane.fxml'."; 
 
  515             logRadio.setSelected(
true);
 
  516             scaleGroup.selectedToggleProperty().addListener(observable -> {
 
  517                 if (scaleGroup.getSelectedToggle() == linearRadio) {
 
  520                 if (scaleGroup.getSelectedToggle() == logRadio) {
 
  525             logRadio.setText(NbBundle.getMessage(
this.getClass(), 
"CountsViewPane.logRadio.text"));
 
  526             linearRadio.setText(NbBundle.getMessage(
this.getClass(), 
"CountsViewPane.linearRadio.text"));
 
  527             scaleLabel.setText(NbBundle.getMessage(
this.getClass(), 
"CountsViewPane.scaleLabel.text"));
 
  540         private final Function<Long, Double> 
func;
 
  547             return func.apply(c);
 
static String getRGBCode(Color color)
 
void setChartClickHandler()
 
FilteredEventsModel filteredEvents
 
final Function< Long, Double > func
 
final Map< EventType, XYChart.Series< String, Number > > eventTypeMap
 
Task< Boolean > getUpdateTask()
 
final CategoryAxis dateAxis
 
static final Logger LOGGER
 
ReadOnlyListWrapper< N > selectedNodes
 
final NumberAxis countAxis
 
CountsViewPane(Pane partPane, Pane contextPane, Region spacer)
 
final SimpleObjectProperty< ScaleType > scale
 
void selectTimeAndType(Interval interval, EventType type)
 
BarClickHandler(Node node, String dateString, Interval countInterval, EventType type)
 
DateTimeFormatter getTickFormatter()
 
TimeLineController controller
 
ContextMenu getContextMenu()
 
final ObservableList< BarChart.Series< X, Y > > dataSets
 
static void construct(Node n, String fxmlFileName)
 
static RangeDivisionInfo getRangeDivisionInfo(Interval timeRange)
 
synchronized ReadOnlyObjectProperty< Interval > timeRange()
 
static RootEventType getInstance()
 
Effect getSelectionEffect()
 
Boolean isTickBold(String value)
 
String getTickMarkLabel(String labelValueString)
 
final String startDateString
 
Map< EventType, Long > getEventCounts(Interval timeRange)
 
final Interval getSpanningInterval()
 
static DateTimeZone getJodaTimeZone()
 
static final Effect SELECTED_NODE_EFFECT
 
synchronized void layoutDateLabels()
 
ScaleType(Function< Long, Double > func)
 
XYChart.Series< String, Number > getSeries(final EventType et)
 
void handle(final MouseEvent e)
 
TimeUnits getPeriodSize()
 
void applySelectionEffect(Node c1, Boolean applied)
 
synchronized void pushTimeRange(Interval timeRange)
 
ContextMenu barContextMenu
 
synchronized void setViewMode(VisualizationMode visualizationMode)
 
static Logger getLogger(String name)
 
List< Node > settingsNodes
 
synchronized void update()