19 package org.sleuthkit.autopsy.timeline.ui.countsview;
 
   21 import java.util.Arrays;
 
   22 import javafx.beans.Observable;
 
   23 import javafx.collections.ObservableList;
 
   24 import javafx.event.EventHandler;
 
   25 import javafx.scene.Cursor;
 
   26 import javafx.scene.Node;
 
   27 import javafx.scene.chart.CategoryAxis;
 
   28 import javafx.scene.chart.NumberAxis;
 
   29 import javafx.scene.chart.StackedBarChart;
 
   30 import javafx.scene.chart.XYChart;
 
   31 import javafx.scene.control.ContextMenu;
 
   32 import javafx.scene.control.SeparatorMenuItem;
 
   33 import javafx.scene.control.Tooltip;
 
   34 import javafx.scene.effect.DropShadow;
 
   35 import javafx.scene.effect.Effect;
 
   36 import javafx.scene.effect.Lighting;
 
   37 import javafx.scene.image.ImageView;
 
   38 import javafx.scene.input.MouseButton;
 
   39 import javafx.scene.input.MouseEvent;
 
   40 import javafx.util.StringConverter;
 
   41 import javax.swing.JOptionPane;
 
   42 import org.controlsfx.control.action.Action;
 
   43 import org.controlsfx.control.action.ActionUtils;
 
   44 import org.joda.time.DateTime;
 
   45 import org.joda.time.Interval;
 
   46 import org.joda.time.Seconds;
 
   47 import org.openide.util.NbBundle;
 
   62 final class EventCountsChart 
extends StackedBarChart<String, Number> implements TimeLineChart<String> {
 
   64     private static final Effect SELECTED_NODE_EFFECT = 
new Lighting();
 
   65     private ContextMenu chartContextMenu;
 
   67     private final TimeLineController controller;
 
   68     private final FilteredEventsModel filteredEvents;
 
   70     private IntervalSelector<? extends String> intervalSelector;
 
   72     final ObservableList<Node> selectedNodes;
 
   79     private RangeDivisionInfo rangeInfo;
 
   81     EventCountsChart(TimeLineController controller, CategoryAxis dateAxis, NumberAxis countAxis, ObservableList<Node> selectedNodes) {
 
   82         super(dateAxis, countAxis);
 
   83         this.controller = controller;
 
   84         this.filteredEvents = controller.getEventsModel();
 
   87         dateAxis.setAnimated(
true);
 
   88         dateAxis.setLabel(null);
 
   89         dateAxis.setTickLabelsVisible(
false);
 
   90         dateAxis.setTickLabelGap(0);
 
   92         countAxis.setAutoRanging(
false);
 
   93         countAxis.setLowerBound(0);
 
   94         countAxis.setAnimated(
true);
 
   95         countAxis.setMinorTickCount(0);
 
   96         countAxis.setTickLabelFormatter(
new IntegerOnlyStringConverter());
 
   98         setAlternativeRowFillVisible(
true);
 
  100         setLegendVisible(
false);
 
  104         ChartDragHandler<String, EventCountsChart> chartDragHandler = 
new ChartDragHandler<>(
this);
 
  105         setOnMousePressed(chartDragHandler);
 
  106         setOnMouseReleased(chartDragHandler);
 
  107         setOnMouseDragged(chartDragHandler);
 
  109         setOnMouseClicked(
new MouseClickedHandler<>(
this));
 
  111         this.selectedNodes = selectedNodes;
 
  113         getController().getEventsModel().timeRangeProperty().addListener(o -> {
 
  114             clearIntervalSelector();
 
  119     public void clearContextMenu() {
 
  120         chartContextMenu = null;
 
  124     public ContextMenu getContextMenu(MouseEvent clickEvent) {
 
  125         if (chartContextMenu != null) {
 
  126             chartContextMenu.hide();
 
  129         chartContextMenu = ActionUtils.createContextMenu(
 
  130                 Arrays.asList(TimeLineChart.newZoomHistoyActionGroup(controller)));
 
  131         chartContextMenu.setAutoHide(
true);
 
  132         return chartContextMenu;
 
  136     public TimeLineController getController() {
 
  141     public void clearIntervalSelector() {
 
  142         getChartChildren().remove(intervalSelector);
 
  143         intervalSelector = null;
 
  147     public IntervalSelector<? extends String> getIntervalSelector() {
 
  148         return intervalSelector;
 
  153         intervalSelector = newIntervalSelector;
 
  155         intervalSelector.prefHeightProperty().addListener(observable -> newIntervalSelector.autosize());
 
  156         getChartChildren().add(getIntervalSelector());
 
  160     public CountsIntervalSelector newIntervalSelector() {
 
  161         return new CountsIntervalSelector(
this);
 
  165     public ObservableList<Node> getSelectedNodes() {
 
  166         return selectedNodes;
 
  169     void setRangeInfo(RangeDivisionInfo rangeInfo) {
 
  170         this.rangeInfo = rangeInfo;
 
  173     Effect getSelectionEffect() {
 
  174         return SELECTED_NODE_EFFECT;
 
  187         "# {1} - event type displayname",
 
  188         "# {2} - start date time",
 
  189         "# {3} - end date time",
 
  190         "CountsViewPane.tooltip.text={0} {1} events\nbetween {2}\nand     {3}"})
 
  192     protected void dataItemAdded(Series<String, Number> series, 
int itemIndex, Data<String, Number> item) {
 
  193         ExtraData extraValue = (ExtraData) item.getExtraValue();
 
  194         EventType eventType = extraValue.getEventType();
 
  195         Interval interval = extraValue.getInterval();
 
  196         long count = extraValue.getRawCount();
 
  198         item.nodeProperty().addListener((Observable o) -> {
 
  199             final Node node = item.getNode();
 
  201                 node.setStyle(
"-fx-border-width: 2; -fx-border-color: " + ColorUtilities.getRGBCode(eventType.getSuperType().getColor()) + 
"; -fx-bar-fill: " + ColorUtilities.getRGBCode(eventType.getColor())); 
 
  202                 node.setCursor(Cursor.HAND);
 
  204                 final Tooltip tooltip = 
new Tooltip(Bundle.CountsViewPane_tooltip_text(
 
  205                         count, eventType.getDisplayName(),
 
  207                         interval.getEnd().toString(rangeInfo.getTickFormatter())));
 
  208                 tooltip.setGraphic(
new ImageView(eventType.getFXImage()));
 
  209                 Tooltip.install(node, tooltip);
 
  211                 node.setOnMouseEntered(mouseEntered -> node.setEffect(
new DropShadow(10, eventType.getColor())));
 
  212                 node.setOnMouseExited(mouseExited -> node.setEffect(selectedNodes.contains(node) ? SELECTED_NODE_EFFECT : null));
 
  213                 node.setOnMouseClicked(
new BarClickHandler(item));
 
  216         super.dataItemAdded(series, itemIndex, item); 
 
  227             return n.intValue() == n.doubleValue()
 
  228                     ? Integer.toString(n.intValue()) : 
"";
 
  234             return Double.valueOf(
string).intValue();
 
  248             this.countsChart = 
chart;
 
  265             return new Interval(lowerDate, upperDate.plus(countsChart.rangeInfo.getPeriodSize().getPeriod()));
 
  270             return date == null ? 
new DateTime(countsChart.rangeInfo.getLowerBound()) : countsChart.rangeInfo.getTickFormatter().parseDateTime(date);
 
  297             EventCountsChart.ExtraData extraData = (EventCountsChart.ExtraData) data.getExtraValue();
 
  298             this.interval = extraData.getInterval();
 
  299             this.type = extraData.getEventType();
 
  300             this.node = data.getNode();
 
  301             this.startDateString = data.getXValue();
 
  304         @NbBundle.Messages({
"Timeline.ui.countsview.menuItem.selectTimeRange=Select Time Range"})
 
  305         class SelectIntervalAction extends Action {
 
  307             SelectIntervalAction() {
 
  308                 super(Bundle.Timeline_ui_countsview_menuItem_selectTimeRange());
 
  309                 setEventHandler(action -> {
 
  311                     selectedNodes.clear();
 
  312                     for (XYChart.Series<String, Number> s : getData()) {
 
  313                         s.getData().forEach((XYChart.Data<String, Number> d) -> {
 
  314                             if (startDateString.contains(d.getXValue())) {
 
  315                                 selectedNodes.add(d.getNode());
 
  323         @NbBundle.Messages({
"Timeline.ui.countsview.menuItem.selectEventType=Select Event Type"})
 
  324         class SelectTypeAction extends Action {
 
  327                 super(Bundle.Timeline_ui_countsview_menuItem_selectEventType());
 
  328                 setEventHandler(action -> {
 
  329                     controller.selectTimeAndType(filteredEvents.getSpanningInterval(), type);
 
  330                     selectedNodes.clear();
 
  331                     getData().stream().filter(series -> series.getName().equals(type.
getDisplayName()))
 
  333                             .ifPresent(series -> series.getData().forEach(data -> selectedNodes.add(data.getNode())));
 
  338         @NbBundle.Messages({
"Timeline.ui.countsview.menuItem.selectTimeandType=Select Time and Type"})
 
  339         class SelectIntervalAndTypeAction extends Action {
 
  341             SelectIntervalAndTypeAction() {
 
  342                 super(Bundle.Timeline_ui_countsview_menuItem_selectTimeandType());
 
  343                 setEventHandler(action -> {
 
  344                     controller.selectTimeAndType(interval, type);
 
  345                     selectedNodes.setAll(node);
 
  350         @NbBundle.Messages({
"Timeline.ui.countsview.menuItem.zoomIntoTimeRange=Zoom into Time Range"})
 
  351         class ZoomToIntervalAction extends Action {
 
  353             ZoomToIntervalAction() {
 
  354                 super(Bundle.Timeline_ui_countsview_menuItem_zoomIntoTimeRange());
 
  355                 setEventHandler(action -> {
 
  356                     if (interval.toDuration().isShorterThan(Seconds.ONE.toStandardDuration()) == 
false) {
 
  357                         controller.pushTimeRange(interval);
 
  365             "CountsViewPane.detailSwitchMessage=There is no temporal resolution smaller than Seconds.\nWould you like to switch to the Details view instead?",
 
  366             "CountsViewPane.detailSwitchTitle=\"Switch to Details View?"})
 
  369             if (e.getClickCount() == 1) {     
 
  370                 if (e.getButton().equals(MouseButton.PRIMARY)) {
 
  371                     controller.selectTimeAndType(interval, type);
 
  372                     selectedNodes.setAll(node);
 
  373                 } 
else if (e.getButton().equals(MouseButton.SECONDARY)) {
 
  374                     getContextMenu(e).hide();
 
  376                     if (barContextMenu == null) {
 
  377                         barContextMenu = 
new ContextMenu();
 
  378                         barContextMenu.setAutoHide(
true);
 
  379                         barContextMenu.getItems().addAll(
 
  380                                 ActionUtils.createMenuItem(
new SelectIntervalAction()),
 
  381                                 ActionUtils.createMenuItem(
new SelectTypeAction()),
 
  382                                 ActionUtils.createMenuItem(
new SelectIntervalAndTypeAction()),
 
  383                                 new SeparatorMenuItem(),
 
  384                                 ActionUtils.createMenuItem(
new ZoomToIntervalAction()));
 
  386                         barContextMenu.getItems().addAll(getContextMenu(e).getItems());
 
  389                     barContextMenu.show(node, e.getScreenX(), e.getScreenY());
 
  392             } 
else if (e.getClickCount() >= 2) {  
 
  393                 if (interval.toDuration().isLongerThan(Seconds.ONE.toStandardDuration())) {
 
  394                     controller.pushTimeRange(interval);
 
  397                     int showConfirmDialog = JOptionPane.showConfirmDialog(null,
 
  398                             Bundle.CountsViewPane_detailSwitchMessage(),
 
  399                             Bundle.CountsViewPane_detailSwitchTitle(), JOptionPane.YES_NO_OPTION);
 
  400                     if (showConfirmDialog == JOptionPane.YES_OPTION) {
 
  412     static class ExtraData {
 
  414         private final Interval interval;
 
  416         private final long rawCount;
 
  418         ExtraData(Interval interval, 
EventType eventType, 
long rawCount) {
 
  419             this.interval = interval;
 
  420             this.eventType = eventType;
 
  421             this.rawCount = rawCount;
 
  424         public long getRawCount() {
 
  428         public Interval getInterval() {
 
  432         public EventType getEventType() {
 
Number fromString(String string)
String formatSpan(String date)
static RootEventType getInstance()
static RangeDivisionInfo getRangeDivisionInfo(Interval timeRange)
Interval adjustInterval(Interval i)
final EventCountsChart countsChart
String toString(Number n)
static DateTimeZone getJodaTimeZone()
DateTime parseDateTime(String date)
final IntervalSelectorProvider< X > chart
ContextMenu barContextMenu
final String startDateString
void setIntervalSelector(IntervalSelector<?extends X > newIntervalSelector)
void handle(final MouseEvent e)