Package org.sleuthkit.autopsy.timeline.ui.detailview

Source Code of org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane$DetailViewSettingsPane

/*
* Autopsy Forensic Browser
*
* Copyright 2014 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.timeline.ui.detailview;

import java.net.URL;
import java.util.ArrayList;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.concurrent.ConcurrentHashMap;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.geometry.Orientation;
import javafx.scene.Cursor;
import javafx.scene.chart.Axis;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.MultipleSelectionModel;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.Slider;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.TreeItem;
import javafx.scene.effect.Effect;
import static javafx.scene.input.KeyCode.DOWN;
import static javafx.scene.input.KeyCode.KP_DOWN;
import static javafx.scene.input.KeyCode.KP_UP;
import static javafx.scene.input.KeyCode.PAGE_DOWN;
import static javafx.scene.input.KeyCode.PAGE_UP;
import static javafx.scene.input.KeyCode.UP;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import org.joda.time.DateTime;
import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.events.type.EventType;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization;
import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane;
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.NavTreeNode;
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;

/**
* FXML Controller class for a {@link EventDetailChart} based implementation of
* a TimeLineView.
*
* This class listens to changes in the assigned {@link FilteredEventsModel} and
* updates the internal {@link EventDetailChart} to reflect the currently
* requested events.
*
* This class captures input from the user in the form of mouse clicks on graph
* bars, and forwards them to the assigned {@link TimeLineController}
*
* Concurrency Policy: Access to the private members clusterChart, dateAxis,
* EventTypeMap, and dataSets is all linked directly to the ClusterChart which
* must only be manipulated on the JavaFx thread (through {@link Platform#runLater(java.lang.Runnable)
* }
*
* {@link CountsChartPane#filteredEvents} should encapsulate all needed
* synchronization internally.
*
* TODO: refactor common code out of this class and CountsChartPane into
* {@link AbstractVisualization}
*/
public class DetailViewPane extends AbstractVisualization<DateTime, AggregateEvent, AggregateEventNode, EventDetailChart> {

    private final static Logger LOGGER = Logger.getLogger(CountsViewPane.class.getName());

    private MultipleSelectionModel<TreeItem<NavTreeNode>> treeSelectionModel;

    @FXML
    protected ResourceBundle resources;

    @FXML
    protected URL location;

    //these three could be injected from fxml but it was causing npe's
    private final DateAxis dateAxis = new DateAxis();

    private final Axis<AggregateEvent> verticalAxis = new EventAxis();

    //private access to barchart data
    private final Map<EventType, XYChart.Series<DateTime, AggregateEvent>> eventTypeToSeriesMap = new ConcurrentHashMap<>();

    private final ScrollBar vertScrollBar = new ScrollBar();

    private final Region region = new Region();

    private final ObservableList<AggregateEvent> aggregatedEvents = FXCollections.synchronizedObservableList(FXCollections.observableArrayList());

    private final ObservableList<AggregateEventNode> highlightedNodes = FXCollections.synchronizedObservableList(FXCollections.observableArrayList());

    public ObservableList<AggregateEvent> getAggregatedEvents() {
        return aggregatedEvents;
    }

    public DetailViewPane(Pane partPane, Pane contextPane, Region spacer) {
        super(partPane, contextPane, spacer);
        chart = new EventDetailChart(dateAxis, verticalAxis, selectedNodes);
        setChartClickHandler();
        chart.setData(dataSets);
        setCenter(chart);

        chart.setPrefHeight(USE_COMPUTED_SIZE);

        settingsNodes = new ArrayList<>(new DetailViewSettingsPane().getChildrenUnmodifiable());

        vertScrollBar.setOrientation(Orientation.VERTICAL);
        VBox vBox = new VBox();
        VBox.setVgrow(vertScrollBar, Priority.ALWAYS);
        vBox.getChildren().add(vertScrollBar);
        vBox.getChildren().add(region);
        setRight(vBox);

        dateAxis.setAutoRanging(false);
        region.minHeightProperty().bind(dateAxis.heightProperty());
        vertScrollBar.visibleAmountProperty().bind(chart.heightProperty().multiply(100).divide(chart.getMaxVScroll()));
        requestLayout();

        highlightedNodes.addListener((ListChangeListener.Change<? extends AggregateEventNode> change) -> {
            while (change.next()) {
                change.getAddedSubList().forEach(aeNode -> {
                    aeNode.applyHighlightEffect(true);
                });
                change.getRemoved().forEach(aeNode -> {
                    aeNode.applyHighlightEffect(false);
                });
            }
        });
        //request focus for keyboard scrolling
        setOnMouseClicked((MouseEvent t) -> {
            requestFocus();
        });

        //These scroll related handlers don't affect any other view or the model, so they are handled internally
        //mouse wheel scroll handler
        this.onScrollProperty().set((EventHandler<ScrollEvent>) (ScrollEvent t) -> {
            vertScrollBar.valueProperty().set(Math.max(0, Math.min(100, vertScrollBar.getValue() - t.getDeltaY() / 200.0)));
        });

        this.setOnKeyPressed((KeyEvent t) -> {
            switch (t.getCode()) {
                case PAGE_UP:
                    incrementScrollValue(-70);
                    break;
                case PAGE_DOWN:
                    incrementScrollValue(70);
                    break;
                case KP_UP:
                case UP:
                    incrementScrollValue(-10);
                    break;
                case KP_DOWN:
                case DOWN:
                    incrementScrollValue(10);
                    break;
            }
            t.consume();
        });

        //scrollbar handler
        this.vertScrollBar.valueProperty().addListener((o, oldValue, newValue) -> {
            chart.setVScroll(newValue.doubleValue() / 100.0);
        });
        spacer.minWidthProperty().bind(verticalAxis.widthProperty().add(verticalAxis.tickLengthProperty()));
        spacer.prefWidthProperty().bind(verticalAxis.widthProperty().add(verticalAxis.tickLengthProperty()));
        spacer.maxWidthProperty().bind(verticalAxis.widthProperty().add(verticalAxis.tickLengthProperty()));

        dateAxis.setTickLabelsVisible(false);

        dateAxis.getTickMarks().addListener((Observable observable) -> {
            layoutDateLabels();
        });
        dateAxis.getTickSpacing().addListener((Observable observable) -> {
            layoutDateLabels();
        });

        dateAxis.setTickLabelGap(0);

        selectedNodes.addListener((Observable observable) -> {
            highlightedNodes.clear();
            selectedNodes.stream().forEach((tn) -> {
                for (AggregateEventNode n : chart.getNodes((
                        AggregateEventNode t) -> t.getEvent().getDescription().equals(tn.getEvent().getDescription()))) {
                    highlightedNodes.add(n);
                }
            });
        });

    }

    private void incrementScrollValue(int factor) {
        vertScrollBar.valueProperty().set(Math.max(0, Math.min(100, vertScrollBar.getValue() + factor * (chart.getHeight() / chart.getMaxVScroll().get()))));
    }

    public void setSelectionModel(MultipleSelectionModel<TreeItem<NavTreeNode>> selectionModel) {
        this.treeSelectionModel = selectionModel;

        treeSelectionModel.getSelectedItems().addListener((Observable observable) -> {
            highlightedNodes.clear();
            for (TreeItem<NavTreeNode> tn : treeSelectionModel.getSelectedItems()) {
                for (AggregateEventNode n : chart.getNodes((
                        AggregateEventNode t)
                        -> t.getEvent().getDescription().equals(tn.getValue().getDescription()))) {
                    highlightedNodes.add(n);
                }
            }
        });
    }

    @Override
    protected Boolean isTickBold(DateTime value) {
        return false;
    }

    @Override
    protected Axis<AggregateEvent> getYAxis() {
        return verticalAxis;
    }

    @Override
    protected Axis<DateTime> getXAxis() {
        return dateAxis;
    }

    @Override
    protected double getTickSpacing() {
        return dateAxis.getTickSpacing().get();
    }

    @Override
    protected String getTickMarkLabel(DateTime value) {
        return dateAxis.getTickMarkLabel(value);
    }

    /** NOTE: Because this method modifies data directly used by the chart,
     * this method should only be called from JavaFX thread!
     *
     * @param et the EventType to get the series for
     *
     * @return a Series object to contain all the events with the given
     *         EventType */
    private XYChart.Series<DateTime, AggregateEvent> getSeries(final EventType et) {
        XYChart.Series<DateTime, AggregateEvent> series = eventTypeToSeriesMap.get(et);
        if (series == null) {
            series = new XYChart.Series<>();
            series.setName(et.getDisplayName());
            eventTypeToSeriesMap.put(et, series);
            dataSets.add(series);
        }
        return series;
    }

    @Override
    protected Task<Boolean> getUpdateTask() {

        return new LoggedTask<Boolean>("Update Details", true) {

            @Override
            protected Boolean call() throws Exception {
                if (isCancelled()) {
                    return null;
                }
                Platform.runLater(() -> {
                    if (isCancelled() == false) {
                        setCursor(Cursor.WAIT);
                    }
                });

                updateProgress(-1, 1);
                updateMessage("preparing");

                final RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.timeRange().get());
                final long lowerBound = rangeInfo.getLowerBound();
                final long upperBound = rangeInfo.getUpperBound();

                updateMessage("querying db");
                aggregatedEvents.setAll(filteredEvents.getAggregatedEvents());

                Platform.runLater(() -> {
                    if (isCancelled()) {
                        return;
                    }
                    dateAxis.setLowerBound(new DateTime(lowerBound, TimeLineController.getJodaTimeZone()));
                    dateAxis.setUpperBound(new DateTime(upperBound, TimeLineController.getJodaTimeZone()));
//                    if (chart == null) {
//                        initializeClusterChart();
//                    }
                    vertScrollBar.setValue(0);
                    eventTypeToSeriesMap.clear();
                    dataSets.clear();
                });
                final int size = aggregatedEvents.size();
                int i = 0;
                for (final AggregateEvent e : aggregatedEvents) {
                    if (isCancelled()) {
                        break;
                    }
                    updateProgress(i++, size);
                    updateMessage("updating ui");
                    final XYChart.Data<DateTime, AggregateEvent> xyData = new BarChart.Data<>(new DateTime(e.getSpan().getStartMillis()), e);

                    Platform.runLater(() -> {
                        if (isCancelled() == false) {
                            getSeries(e.getType()).getData().add(xyData);
                        }
                    });
                }

                Platform.runLater(() -> {
                    setCursor(Cursor.NONE);
                    layoutDateLabels();
                    updateProgress(1, 1);
                });
                return aggregatedEvents.isEmpty() == false;
            }
        };
    }

    @Override
    protected Effect getSelectionEffect() {
        return null;
    }

    @Override
    protected void applySelectionEffect(AggregateEventNode c1, Boolean applied) {
        c1.applySelectionEffect(applied);
    }

    private class DetailViewSettingsPane extends HBox {

        @FXML
        private RadioButton hiddenRadio;

        @FXML
        private RadioButton showRadio;

        @FXML
        private ToggleGroup descrVisibility;

        @FXML
        private RadioButton countsRadio;

        @FXML
        private ResourceBundle resources;

        @FXML
        private URL location;

        @FXML
        private CheckBox bandByTypeBox;

        @FXML
        private CheckBox oneEventPerRowBox;

        @FXML
        private CheckBox truncateAllBox;

        @FXML
        private Slider truncateWidthSlider;

        @FXML
        private Label truncateSliderLabel;

        public DetailViewSettingsPane() {
            FXMLConstructor.construct(this, "DetailViewSettingsPane.fxml");
        }

        @FXML
        void initialize() {
            assert bandByTypeBox != null : "fx:id=\"bandByTypeBox\" was not injected: check your FXML file 'DetailViewSettings.fxml'.";
            assert oneEventPerRowBox != null : "fx:id=\"oneEventPerRowBox\" was not injected: check your FXML file 'DetailViewSettings.fxml'.";
            assert truncateAllBox != null : "fx:id=\"truncateAllBox\" was not injected: check your FXML file 'DetailViewSettings.fxml'.";
            assert truncateWidthSlider != null : "fx:id=\"truncateAllSlider\" was not injected: check your FXML file 'DetailViewSettings.fxml'.";
            bandByTypeBox.selectedProperty().bindBidirectional(chart.getBandByType());
            truncateAllBox.selectedProperty().bindBidirectional(chart.getTruncateAll());
            oneEventPerRowBox.selectedProperty().bindBidirectional(chart.getOneEventPerRow());
            truncateSliderLabel.disableProperty().bind(truncateAllBox.selectedProperty().not());
            final InvalidationListener sliderListener = o -> {
                if (truncateWidthSlider.isValueChanging() == false) {
                    chart.getTruncateWidth().set(truncateWidthSlider.getValue());
                }
            };
            truncateWidthSlider.valueProperty().addListener(sliderListener);
            truncateWidthSlider.valueChangingProperty().addListener(sliderListener);

            descrVisibility.selectedToggleProperty().addListener((observable, oldToggle, newToggle) -> {
                if (newToggle == countsRadio) {
                    chart.getDescrVisibility().set(DescriptionVisibility.COUNT_ONLY);
                } else if (newToggle == showRadio) {
                    chart.getDescrVisibility().set(DescriptionVisibility.SHOWN);
                } else if (newToggle == hiddenRadio) {
                    chart.getDescrVisibility().set(DescriptionVisibility.HIDDEN);
                }
            });
        }

    }
}
TOP

Related Classes of org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane$DetailViewSettingsPane

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.