org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree.java Source code

Java tutorial

Introduction

Here is the source code for org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree.java

Source

/*
 * Autopsy Forensic Browser
 *
 * Copyright 2013-15 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.tree;

import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.Tooltip;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import org.apache.commons.lang3.StringUtils;
import org.controlsfx.control.action.Action;
import org.controlsfx.control.action.ActionUtils;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;

/**
 * Shows all EventBundles from the assigned DetailViewPane in a
 * tree organized by type and then description. Hidden bundles are shown grayed
 * out. Right clicking on a item in the tree shows a context menu to show/hide
 * it.
 */
final public class EventsTree extends BorderPane {

    private final TimeLineController controller;

    private DetailViewPane detailViewPane;

    @FXML
    private TreeView<TimeLineEvent> eventsTree;

    @FXML
    private Label eventsTreeLabel;

    @FXML
    private ComboBox<TreeComparator> sortByBox;
    private final ObservableList<TimeLineEvent> selectedEvents = FXCollections.observableArrayList();

    public EventsTree(TimeLineController controller) {
        this.controller = controller;
        FXMLConstructor.construct(this, "EventsTree.fxml"); // NON-NLS 
    }

    public void setDetailViewPane(DetailViewPane detailViewPane) {
        this.detailViewPane = detailViewPane;

        detailViewPane.getAllNestedEvents().addListener((ListChangeListener.Change<? extends TimeLineEvent> c) -> {
            //on jfx thread
            while (c.next()) {
                c.getRemoved().forEach(getRoot()::remove);
                c.getAddedSubList().forEach(getRoot()::insert);
            }
        });

        setRoot();

        detailViewPane.getSelectedEvents().addListener((Observable observable) -> {
            eventsTree.getSelectionModel().clearSelection();
            detailViewPane.getSelectedEvents().forEach(event -> {
                eventsTree.getSelectionModel().select(getRoot().findTreeItemForEvent(event));
            });
        });

    }

    private RootItem getRoot() {
        return (RootItem) eventsTree.getRoot();
    }

    @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
    private void setRoot() {
        RootItem root = new RootItem(
                TreeComparator.Type.reversed().thenComparing(sortByBox.getSelectionModel().getSelectedItem()));
        detailViewPane.getAllNestedEvents().forEach(root::insert);
        eventsTree.setRoot(root);
    }

    @FXML
    @NbBundle.Messages("EventsTree.Label.text=Sort By:")
    void initialize() {
        assert sortByBox != null : "fx:id=\"sortByBox\" was not injected: check your FXML file 'NavPanel.fxml'."; // NON-NLS

        sortByBox.getItems().setAll(Arrays.asList(TreeComparator.Description, TreeComparator.Count));
        sortByBox.getSelectionModel().select(TreeComparator.Description);
        sortByBox.setCellFactory(listView -> new TreeComparatorCell());
        sortByBox.setButtonCell(new TreeComparatorCell());
        sortByBox.getSelectionModel().selectedItemProperty().addListener(selectedItemProperty -> {
            getRoot().sort(
                    TreeComparator.Type.reversed().thenComparing(sortByBox.getSelectionModel().getSelectedItem()),
                    true);
        });

        eventsTree.setShowRoot(false);
        eventsTree.setCellFactory(treeView -> new EventTreeCell());
        eventsTree.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);

        eventsTree.getSelectionModel().getSelectedItems()
                .addListener((ListChangeListener.Change<? extends TreeItem<TimeLineEvent>> change) -> {
                    while (change.next()) {
                        change.getRemoved().stream().map(TreeItem<TimeLineEvent>::getValue)
                                .forEach(selectedEvents::remove);
                        change.getAddedSubList().stream().map(TreeItem<TimeLineEvent>::getValue)
                                .filter(Objects::nonNull).forEach(selectedEvents::add);
                    }
                });

        eventsTreeLabel.setText(Bundle.EventsTree_Label_text());
    }

    public ObservableList<TimeLineEvent> getSelectedEvents() {
        return selectedEvents;
    }

    /**
     * A tree cell to display TimeLineEvents. Shows the description, and count,
     * as well a a "legend icon" for the event type.
     */
    private class EventTreeCell extends TreeCell<TimeLineEvent> {

        private static final double HIDDEN_MULTIPLIER = .6;
        private final Rectangle rect = new Rectangle(24, 24);
        private final ImageView imageView = new ImageView();
        private InvalidationListener filterStateChangeListener;
        private final SimpleBooleanProperty hidden = new SimpleBooleanProperty(false);

        EventTreeCell() {
            rect.setArcHeight(5);
            rect.setArcWidth(5);
            rect.setStrokeWidth(2);
        }

        @Override
        protected void updateItem(TimeLineEvent item, boolean empty) {
            super.updateItem(item, empty);
            if (empty) {
                setText(null);
                setTooltip(null);
                setGraphic(null);
                deRegisterListeners(controller.getQuickHideFilters());
            } else {
                EventsTreeItem treeItem = (EventsTreeItem) getTreeItem();
                String text = treeItem.getDisplayText();
                setText(text);
                setTooltip(new Tooltip(text));

                imageView.setImage(treeItem.getEventType().getFXImage());
                setGraphic(new StackPane(rect, imageView));
                updateHiddenState(treeItem);
                deRegisterListeners(controller.getQuickHideFilters());

                if (item != null) {
                    filterStateChangeListener = (filterState) -> updateHiddenState(treeItem);
                    controller.getQuickHideFilters()
                            .addListener((ListChangeListener.Change<? extends DescriptionFilter> listChange) -> {
                                while (listChange.next()) {
                                    deRegisterListeners(listChange.getRemoved());
                                    registerListeners(listChange.getAddedSubList(), item);
                                }
                                updateHiddenState(treeItem);
                            });
                    registerListeners(controller.getQuickHideFilters(), item);
                    setOnMouseClicked((MouseEvent event) -> {
                        if (event.getButton() == MouseButton.SECONDARY) {
                            Action action = hidden.get()
                                    ? detailViewPane.newUnhideDescriptionAction(item.getDescription(),
                                            item.getDescriptionLoD())
                                    : detailViewPane.newHideDescriptionAction(item.getDescription(),
                                            item.getDescriptionLoD());

                            ActionUtils.createContextMenu(ImmutableList.of(action)).show(this, event.getScreenX(),
                                    event.getScreenY());
                        }
                    });
                } else {
                    setOnMouseClicked(null);
                }
            }
        }

        private void registerListeners(Collection<? extends DescriptionFilter> filters, TimeLineEvent item) {
            for (DescriptionFilter filter : filters) {
                if (filter.getDescription().equals(item.getDescription())) {
                    filter.activeProperty().addListener(filterStateChangeListener);
                }
            }
        }

        private void deRegisterListeners(Collection<? extends DescriptionFilter> filters) {
            if (Objects.nonNull(filterStateChangeListener)) {
                for (DescriptionFilter filter : filters) {
                    filter.activeProperty().removeListener(filterStateChangeListener);
                }
            }
        }

        private void updateHiddenState(EventsTreeItem treeItem) {
            TimeLineEvent event = treeItem.getValue();
            hidden.set(event != null && controller.getQuickHideFilters().stream()
                    .filter(DescriptionFilter::isActive).anyMatch(filter -> StringUtils
                            .equalsIgnoreCase(filter.getDescription(), event.getDescription())));
            if (hidden.get()) {
                treeItem.setExpanded(false);
                setTextFill(Color.gray(0, HIDDEN_MULTIPLIER));
                imageView.setOpacity(HIDDEN_MULTIPLIER);
                rect.setStroke(
                        treeItem.getEventType().getColor().deriveColor(0, HIDDEN_MULTIPLIER, 1, HIDDEN_MULTIPLIER));
                rect.setFill(treeItem.getEventType().getColor().deriveColor(0, HIDDEN_MULTIPLIER, HIDDEN_MULTIPLIER,
                        0.1));
            } else {
                setTextFill(Color.BLACK);
                imageView.setOpacity(1);
                rect.setStroke(treeItem.getEventType().getColor());
                rect.setFill(treeItem.getEventType().getColor().deriveColor(0, 1, 1, 0.1));
            }
        }
    }

    static private class TreeComparatorCell extends ListCell<TreeComparator> {

        @Override
        protected void updateItem(TreeComparator item, boolean empty) {
            super.updateItem(item, empty);
            if (empty || item == null) {
                setText(null);
            } else {
                setText(item.getDisplayName());
            }
        }
    }
}