mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-14 17:06:16 +00:00
Merge branch 'develop' of https://github.com/sleuthkit/autopsy into develop
This commit is contained in:
commit
0cfee9e2a0
@ -197,6 +197,7 @@
|
||||
<file name="org-netbeans-modules-autoupdate-ui-actions-PluginManagerAction.shadow"/>
|
||||
<file name="org-netbeans-modules-favorites-templates-TemplatesAction.shadow_hidden"/>
|
||||
<file name="org-netbeans-modules-options-OptionsWindowAction.shadow"/>
|
||||
<file name="org-netbeans-modules-templates-actions-TemplatesAction.shadow_hidden"/>
|
||||
<file name="org-openide-actions-ToolsAction.shadow_hidden"/>
|
||||
<file name="org-sleuthkit-autopsy-filesearch-FileSearchAction.shadow">
|
||||
<attr name="originalFile" stringvalue="Actions/Tools/org-sleuthkit-autopsy-filesearch-FileSearchAction.instance"/>
|
||||
|
@ -22,6 +22,7 @@ import java.awt.Dimension;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
@ -174,6 +175,9 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi
|
||||
borderpane.setCenter(fxImageView);
|
||||
}
|
||||
}
|
||||
} catch (EOFException ex) {
|
||||
LOGGER.log(Level.WARNING, "Could not load image file into media view (EOF): {0}", file.getName()); //NON-NLS
|
||||
borderpane.setCenter(errorLabel);
|
||||
} catch (IllegalArgumentException | IOException ex) {
|
||||
LOGGER.log(Level.WARNING, "Could not load image file into media view: " + file.getName(), ex); //NON-NLS
|
||||
borderpane.setCenter(errorLabel);
|
||||
|
@ -26,6 +26,7 @@ import com.google.common.io.Files;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@ -135,8 +136,8 @@ public class ImageUtils {
|
||||
/**
|
||||
* thread that saves generated thumbnails to disk in the background
|
||||
*/
|
||||
private static final Executor imageSaver
|
||||
= Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
|
||||
private static final Executor imageSaver =
|
||||
Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
|
||||
.namingPattern("icon saver-%d").build());
|
||||
|
||||
public static List<String> getSupportedImageExtensions() {
|
||||
@ -524,14 +525,13 @@ public class ImageUtils {
|
||||
return ScalrWrapper.cropImage(bi, Math.min(iconSize, bi.getWidth()), Math.min(iconSize, bi.getHeight()));
|
||||
}
|
||||
} catch (OutOfMemoryError e) {
|
||||
LOGGER.log(Level.WARNING, "Could not scale image (too large): " + content.getName(), e); //NON-NLS
|
||||
|
||||
return null;
|
||||
LOGGER.log(Level.WARNING, "Could not scale image (too large) " + content.getName(), e); //NON-NLS
|
||||
} catch (EOFException e) {
|
||||
LOGGER.log(Level.WARNING, "Could not load image (EOF) {0}", content.getName()); //NON-NLS
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.WARNING, "Could not load image: " + content.getName(), e); //NON-NLS
|
||||
return null;
|
||||
|
||||
LOGGER.log(Level.WARNING, "Could not load image " + content.getName(), e); //NON-NLS
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,10 +24,7 @@ TimelinePanel.jButton7.text=3d
|
||||
TimelinePanel.jButton2.text=1m
|
||||
TimelinePanel.jButton3.text=3m
|
||||
TimelinePanel.jButton4.text=2w
|
||||
TimeLineTopComponent.eventsTab.name=Events
|
||||
TimeLineTopComponent.filterTab.name=Filters
|
||||
OpenTimelineAction.title=Timeline
|
||||
OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources.
|
||||
TimeLineTopComponent.timeZonePanel.text=Display Times In\:
|
||||
ProgressWindow.progressHeader.text=\
|
||||
|
||||
|
@ -42,8 +42,7 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
* intended only to remove the boilerplate initialization code when defining a
|
||||
* relatively static layout
|
||||
*
|
||||
* TODO: find a way to move this to CoreUtils and remove duplicate verison in
|
||||
* image analyzer
|
||||
* TODO: move this to CoreUtils and remove duplicate verison in image analyzer
|
||||
*/
|
||||
public class FXMLConstructor {
|
||||
|
||||
@ -55,12 +54,28 @@ public class FXMLConstructor {
|
||||
*
|
||||
*
|
||||
* @param node a node to initialize from a loaded FXML
|
||||
* @param fxmlFileName the the file name of the FXML to load, relative to
|
||||
* the package that the class of node is defined in.
|
||||
* @param fxmlFileName the file name of the FXML to load, relative to the
|
||||
* package that the class of node is defined in.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
static public void construct(Node node, String fxmlFileName) {
|
||||
final String name = "nbres:/" + StringUtils.replace(node.getClass().getPackage().getName(), ".", "/") + "/" + fxmlFileName; // NON-NLS
|
||||
construct(node, node.getClass(), fxmlFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an fxml file and initialize a node with it. Since this manipulates
|
||||
* the node, it must be called on the JFX thread.
|
||||
*
|
||||
*
|
||||
* @param node a node to initialize from a loaded FXML
|
||||
* @param clazz a class to use for relative location of the fxml
|
||||
* @param fxmlFileName the file name of the FXML to load, relative to the
|
||||
* package of clazz.
|
||||
*
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
static public void construct(Node node, Class<? extends Node> clazz, String fxmlFileName) {
|
||||
final String name = "nbres:/" + StringUtils.replace(clazz.getPackage().getName(), ".", "/") + "/" + fxmlFileName; // NON-NLS
|
||||
|
||||
try {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(new URL(name));
|
||||
|
@ -64,13 +64,13 @@ import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE;
|
||||
import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_ADDED;
|
||||
import org.sleuthkit.autopsy.coreutils.History;
|
||||
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.coreutils.History;
|
||||
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
@ -135,6 +135,21 @@ public class TimeLineController {
|
||||
|
||||
private final ReadOnlyStringWrapper taskTitle = new ReadOnlyStringWrapper();
|
||||
|
||||
private final ReadOnlyStringWrapper status = new ReadOnlyStringWrapper();
|
||||
|
||||
/**
|
||||
* status is a string that will be displayed in the status bar as a kind of
|
||||
* user hint/information when it is not empty
|
||||
*
|
||||
* @return the status property
|
||||
*/
|
||||
public ReadOnlyStringProperty getStatusProperty() {
|
||||
return status.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public void setStatus(String string) {
|
||||
status.set(string);
|
||||
}
|
||||
private final Case autoCase;
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
@ -301,8 +316,10 @@ public class TimeLineController {
|
||||
LOGGER.log(Level.INFO, "Beginning generation of timeline"); // NON-NLS
|
||||
try {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (isWindowOpen()) {
|
||||
mainFrame.close();
|
||||
synchronized (TimeLineController.this) {
|
||||
if (isWindowOpen()) {
|
||||
mainFrame.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase();
|
||||
@ -347,8 +364,10 @@ public class TimeLineController {
|
||||
void rebuildTagsTable() {
|
||||
LOGGER.log(Level.INFO, "starting to rebuild tags table"); // NON-NLS
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (isWindowOpen()) {
|
||||
mainFrame.close();
|
||||
synchronized (TimeLineController.this) {
|
||||
if (isWindowOpen()) {
|
||||
mainFrame.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
synchronized (eventsRepository) {
|
||||
@ -373,16 +392,19 @@ public class TimeLineController {
|
||||
IngestManager.getInstance().removeIngestModuleEventListener(ingestModuleListener);
|
||||
IngestManager.getInstance().removeIngestJobEventListener(ingestJobListener);
|
||||
Case.removePropertyChangeListener(caseListener);
|
||||
mainFrame.close();
|
||||
mainFrame.setVisible(false);
|
||||
mainFrame = null;
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
synchronized (TimeLineController.this) {
|
||||
mainFrame.close();
|
||||
mainFrame = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* show the timeline window and prompt for rebuilding database if necessary.
|
||||
*/
|
||||
synchronized void openTimeLine() {
|
||||
void openTimeLine() {
|
||||
// listen for case changes (specifically images being added, and case changes).
|
||||
if (Case.isCaseOpen() && !listeningToAutopsy) {
|
||||
IngestManager.getInstance().addIngestModuleEventListener(ingestModuleListener);
|
||||
@ -524,20 +546,20 @@ public class TimeLineController {
|
||||
/**
|
||||
* private method to build gui if necessary and make it visible.
|
||||
*/
|
||||
synchronized private void showWindow() {
|
||||
if (mainFrame == null) {
|
||||
LOGGER.log(Level.WARNING, "Tried to show timeline with invalid window. Rebuilding GUI."); // NON-NLS
|
||||
mainFrame = (TimeLineTopComponent) WindowManager.getDefault().findTopComponent(
|
||||
NbBundle.getMessage(TimeLineController.class, "CTL_TimeLineTopComponentAction"));
|
||||
if (mainFrame == null) {
|
||||
mainFrame = new TimeLineTopComponent();
|
||||
}
|
||||
mainFrame.setController(this);
|
||||
}
|
||||
private void showWindow() {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
mainFrame.open();
|
||||
mainFrame.setVisible(true);
|
||||
mainFrame.toFront();
|
||||
synchronized (TimeLineController.this) {
|
||||
if (mainFrame == null) {
|
||||
LOGGER.log(Level.WARNING, "Tried to show timeline with invalid window. Rebuilding GUI."); // NON-NLS
|
||||
mainFrame = (TimeLineTopComponent) WindowManager.getDefault().findTopComponent(
|
||||
NbBundle.getMessage(TimeLineController.class, "CTL_TimeLineTopComponentAction"));
|
||||
if (mainFrame == null) {
|
||||
mainFrame = new TimeLineTopComponent(this);
|
||||
}
|
||||
}
|
||||
mainFrame.open();
|
||||
mainFrame.toFront();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.SplitPane;
|
||||
import javafx.scene.control.Tab;
|
||||
@ -34,7 +34,6 @@ import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.netbeans.api.settings.ConvertAsProperties;
|
||||
import org.openide.explorer.ExplorerManager;
|
||||
import org.openide.explorer.ExplorerUtils;
|
||||
import org.openide.util.NbBundle;
|
||||
@ -51,135 +50,103 @@ import org.sleuthkit.autopsy.timeline.ui.StatusBar;
|
||||
import org.sleuthkit.autopsy.timeline.ui.TimeLineResultView;
|
||||
import org.sleuthkit.autopsy.timeline.ui.TimeZonePanel;
|
||||
import org.sleuthkit.autopsy.timeline.ui.VisualizationPanel;
|
||||
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.NavPanel;
|
||||
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
|
||||
import org.sleuthkit.autopsy.timeline.ui.filtering.FilterSetPanel;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.ZoomSettingsPane;
|
||||
|
||||
/**
|
||||
* TopComponent for the timeline feature.
|
||||
*/
|
||||
@ConvertAsProperties(
|
||||
dtd = "-//org.sleuthkit.autopsy.timeline//TimeLine//EN",
|
||||
autostore = false)
|
||||
@TopComponent.Description(
|
||||
preferredID = "TimeLineTopComponent",
|
||||
//iconBase="SET/PATH/TO/ICON/HERE",
|
||||
persistenceType = TopComponent.PERSISTENCE_NEVER)
|
||||
@TopComponent.Registration(mode = "timeline", openAtStartup = false)
|
||||
public final class TimeLineTopComponent extends TopComponent implements ExplorerManager.Provider, TimeLineUI {
|
||||
public final class TimeLineTopComponent extends TopComponent implements ExplorerManager.Provider {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(TimeLineTopComponent.class.getName());
|
||||
|
||||
private DataContentPanel dataContentPanel;
|
||||
private final DataContentPanel dataContentPanel;
|
||||
|
||||
private TimeLineResultView tlrv;
|
||||
private final TimeLineResultView tlrv;
|
||||
|
||||
private final ExplorerManager em = new ExplorerManager();
|
||||
|
||||
private TimeLineController controller;
|
||||
private final TimeLineController controller;
|
||||
|
||||
////jfx componenets that make up the interface
|
||||
private final FilterSetPanel filtersPanel = new FilterSetPanel();
|
||||
|
||||
private final Tab eventsTab = new Tab(
|
||||
NbBundle.getMessage(TimeLineTopComponent.class, "TimeLineTopComponent.eventsTab.name"));
|
||||
|
||||
private final Tab filterTab = new Tab(
|
||||
NbBundle.getMessage(TimeLineTopComponent.class, "TimeLineTopComponent.filterTab.name"));
|
||||
|
||||
private final VBox leftVBox = new VBox(5);
|
||||
|
||||
private final NavPanel navPanel = new NavPanel();
|
||||
|
||||
private final StatusBar statusBar = new StatusBar();
|
||||
|
||||
private final TabPane tabPane = new TabPane();
|
||||
|
||||
private final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane();
|
||||
|
||||
private final VisualizationPanel visualizationPanel = new VisualizationPanel(navPanel);
|
||||
|
||||
private final SplitPane splitPane = new SplitPane();
|
||||
|
||||
private final TimeZonePanel timeZonePanel = new TimeZonePanel();
|
||||
|
||||
public TimeLineTopComponent() {
|
||||
public TimeLineTopComponent(TimeLineController controller) {
|
||||
initComponents();
|
||||
|
||||
this.controller = controller;
|
||||
associateLookup(ExplorerUtils.createLookup(em, getActionMap()));
|
||||
|
||||
setName(NbBundle.getMessage(TimeLineTopComponent.class, "CTL_TimeLineTopComponent"));
|
||||
setToolTipText(NbBundle.getMessage(TimeLineTopComponent.class, "HINT_TimeLineTopComponent"));
|
||||
setIcon(WindowManager.getDefault().getMainWindow().getIconImage()); //use the same icon as main application
|
||||
|
||||
timeZonePanel.setText(NbBundle.getMessage(this.getClass(), "TimeLineTopComponent.timeZonePanel.text"));
|
||||
customizeComponents();
|
||||
}
|
||||
|
||||
synchronized private void customizeComponents() {
|
||||
|
||||
dataContentPanel = DataContentPanel.createInstance();
|
||||
this.contentViewerContainerPanel.add(dataContentPanel, BorderLayout.CENTER);
|
||||
tlrv = new TimeLineResultView(dataContentPanel);
|
||||
tlrv = new TimeLineResultView(controller, dataContentPanel);
|
||||
DataResultPanel dataResultPanel = tlrv.getDataResultPanel();
|
||||
this.resultContainerPanel.add(dataResultPanel, BorderLayout.CENTER);
|
||||
dataResultPanel.open();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
//assemble ui componenets together
|
||||
jFXstatusPanel.setScene(new Scene(statusBar));
|
||||
jFXVizPanel.setScene(new Scene(splitPane));
|
||||
|
||||
splitPane.setDividerPositions(0);
|
||||
|
||||
filterTab.setClosable(false);
|
||||
filterTab.setContent(filtersPanel);
|
||||
filterTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/funnel.png")); // NON-NLS
|
||||
|
||||
eventsTab.setClosable(false);
|
||||
eventsTab.setContent(navPanel);
|
||||
eventsTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/timeline_marker.png")); // NON-NLS
|
||||
|
||||
tabPane.getTabs().addAll(filterTab, eventsTab);
|
||||
VBox.setVgrow(tabPane, Priority.ALWAYS);
|
||||
|
||||
VBox.setVgrow(timeZonePanel, Priority.SOMETIMES);
|
||||
leftVBox.getChildren().addAll(timeZonePanel, zoomSettingsPane, tabPane);
|
||||
|
||||
SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE);
|
||||
splitPane.getItems().addAll(leftVBox, visualizationPanel);
|
||||
});
|
||||
customizeFXComponents();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setController(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
|
||||
tlrv.setController(controller);
|
||||
@NbBundle.Messages({"TimeLineTopComponent.eventsTab.name=Events",
|
||||
"TimeLineTopComponent.filterTab.name=Filters"})
|
||||
void customizeFXComponents() {
|
||||
Platform.runLater(() -> {
|
||||
jFXVizPanel.getScene().addEventFilter(KeyEvent.KEY_PRESSED,
|
||||
(KeyEvent event) -> {
|
||||
if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(event)) {
|
||||
new Back(controller).handle(new ActionEvent());
|
||||
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE).match(event)) {
|
||||
new Back(controller).handle(new ActionEvent());
|
||||
} else if (new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN).match(event)) {
|
||||
new Forward(controller).handle(new ActionEvent());
|
||||
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE, KeyCodeCombination.SHIFT_DOWN).match(event)) {
|
||||
new Forward(controller).handle(new ActionEvent());
|
||||
}
|
||||
});
|
||||
|
||||
//create and wire up jfx componenets that make up the interface
|
||||
final Tab filterTab = new Tab(Bundle.TimeLineTopComponent_filterTab_name(), new FilterSetPanel(controller));
|
||||
filterTab.setClosable(false);
|
||||
filterTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/funnel.png")); // NON-NLS
|
||||
|
||||
final EventsTree eventsTree = new EventsTree(controller);
|
||||
final VisualizationPanel visualizationPanel = new VisualizationPanel(controller, eventsTree);
|
||||
final Tab eventsTreeTab = new Tab(Bundle.TimeLineTopComponent_eventsTab_name(), eventsTree);
|
||||
eventsTreeTab.setClosable(false);
|
||||
eventsTreeTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/timeline_marker.png")); // NON-NLS
|
||||
eventsTreeTab.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS));
|
||||
|
||||
final TabPane leftTabPane = new TabPane(filterTab, eventsTreeTab);
|
||||
VBox.setVgrow(leftTabPane, Priority.ALWAYS);
|
||||
controller.viewModeProperty().addListener((Observable observable) -> {
|
||||
if (controller.viewModeProperty().get().equals(VisualizationMode.COUNTS)) {
|
||||
tabPane.getSelectionModel().select(filterTab);
|
||||
//if view mode is counts, make sure events tabd is not active
|
||||
leftTabPane.getSelectionModel().select(filterTab);
|
||||
}
|
||||
});
|
||||
eventsTab.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS));
|
||||
visualizationPanel.setController(controller);
|
||||
navPanel.setController(controller);
|
||||
filtersPanel.setController(controller);
|
||||
zoomSettingsPane.setController(controller);
|
||||
statusBar.setController(controller);
|
||||
|
||||
final TimeZonePanel timeZonePanel = new TimeZonePanel();
|
||||
VBox.setVgrow(timeZonePanel, Priority.SOMETIMES);
|
||||
|
||||
final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane(controller);
|
||||
|
||||
final VBox leftVBox = new VBox(5, timeZonePanel, zoomSettingsPane, leftTabPane);
|
||||
SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE);
|
||||
|
||||
final SplitPane mainSplitPane = new SplitPane(leftVBox, visualizationPanel);
|
||||
mainSplitPane.setDividerPositions(0);
|
||||
|
||||
final Scene scene = new Scene(mainSplitPane);
|
||||
scene.addEventFilter(KeyEvent.KEY_PRESSED,
|
||||
(KeyEvent event) -> {
|
||||
if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(event)) {
|
||||
new Back(controller).handle(null);
|
||||
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE).match(event)) {
|
||||
new Back(controller).handle(null);
|
||||
} else if (new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN).match(event)) {
|
||||
new Forward(controller).handle(null);
|
||||
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE, KeyCodeCombination.SHIFT_DOWN).match(event)) {
|
||||
new Forward(controller).handle(null);
|
||||
}
|
||||
});
|
||||
|
||||
//add ui componenets to JFXPanels
|
||||
jFXVizPanel.setScene(scene);
|
||||
jFXstatusPanel.setScene(new Scene(new StatusBar(controller)));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@ -196,9 +163,9 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
jFXstatusPanel = new javafx.embed.swing.JFXPanel();
|
||||
jFXstatusPanel = new JFXPanel();
|
||||
splitYPane = new javax.swing.JSplitPane();
|
||||
jFXVizPanel = new javafx.embed.swing.JFXPanel();
|
||||
jFXVizPanel = new JFXPanel();
|
||||
lowerSplitXPane = new javax.swing.JSplitPane();
|
||||
resultContainerPanel = new javax.swing.JPanel();
|
||||
contentViewerContainerPanel = new javax.swing.JPanel();
|
||||
|
@ -1,27 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public interface TimeLineUI {
|
||||
|
||||
void setController(TimeLineController controller);
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013 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;
|
||||
|
||||
/**
|
||||
* Interface to be implemented by views of the data.
|
||||
*
|
||||
* Most implementations should install the relevant listeners in their
|
||||
* {@link #setController} and {@link #setModel} methods
|
||||
*/
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
|
||||
public interface TimeLineView extends TimeLineUI {
|
||||
|
||||
@Override
|
||||
void setController(TimeLineController controller);
|
||||
|
||||
void setModel(final FilteredEventsModel filteredEvents);
|
||||
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
Back.actions.name.text=Back
|
||||
DefaultFilters.action.name.text=apply default filters
|
||||
Forward.action.name.text=Forward
|
||||
SaveSnapshot.action.name.text=save snapshot
|
||||
SaveSnapshot.fileChoose.title.text=Save snapshot to
|
||||
ZoomOut.action.name.text=apply default filters
|
||||
|
||||
|
@ -46,51 +46,48 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class SaveSnapshot extends Action {
|
||||
public class SaveSnapshotAsReport extends Action {
|
||||
|
||||
private static final String HTML_EXT = ".html";
|
||||
|
||||
private static final String REPORT_IMAGE_EXTENSION = ".png";
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(SaveSnapshot.class.getName());
|
||||
private static final Logger LOGGER = Logger.getLogger(SaveSnapshotAsReport.class.getName());
|
||||
|
||||
private final TimeLineController controller;
|
||||
|
||||
private final WritableImage snapshot;
|
||||
|
||||
public SaveSnapshot(TimeLineController controller, WritableImage snapshot) {
|
||||
super(NbBundle.getMessage(SaveSnapshot.class, "SaveSnapshot.action.name.text"));
|
||||
this.controller = controller;
|
||||
this.snapshot = snapshot;
|
||||
@NbBundle.Messages({"SaveSnapshot.action.name.text=save snapshot",
|
||||
"SaveSnapshot.fileChoose.title.text=Save snapshot to"})
|
||||
public SaveSnapshotAsReport(TimeLineController controller, WritableImage snapshot) {
|
||||
super(Bundle.SaveSnapshot_action_name_text());
|
||||
setEventHandler(new Consumer<ActionEvent>() {
|
||||
|
||||
@Override
|
||||
public void accept(ActionEvent t) {
|
||||
//choose location/name
|
||||
DirectoryChooser fileChooser = new DirectoryChooser();
|
||||
fileChooser.setTitle(NbBundle.getMessage(this.getClass(), "SaveSnapshot.fileChoose.title.text"));
|
||||
fileChooser.setTitle(Bundle.SaveSnapshot_fileChoose_title_text());
|
||||
fileChooser.setInitialDirectory(new File(Case.getCurrentCase().getReportDirectory()));
|
||||
File outFolder = fileChooser.showDialog(null);
|
||||
if (outFolder == null) {
|
||||
File reportDirectory = fileChooser.showDialog(null);
|
||||
if (reportDirectory == null) {
|
||||
return;
|
||||
}
|
||||
outFolder.mkdir();
|
||||
String name = outFolder.getName();
|
||||
reportDirectory.mkdir();
|
||||
String reportName = reportDirectory.getName();
|
||||
String reportPath = reportDirectory.getPath();
|
||||
|
||||
//gather metadata
|
||||
List<Pair<String, String>> reportMetaData = new ArrayList<>();
|
||||
|
||||
reportMetaData.add(new Pair<>("Case", Case.getCurrentCase().getName())); // NON-NLS
|
||||
|
||||
ZoomParams get = controller.getEventsModel().zoomParametersProperty().get();
|
||||
reportMetaData.add(new Pair<>("Time Range", get.getTimeRange().toString())); // NON-NLS
|
||||
reportMetaData.add(new Pair<>("Description Level of Detail", get.getDescriptionLOD().getDisplayName())); // NON-NLS
|
||||
reportMetaData.add(new Pair<>("Event Type Zoom Level", get.getTypeZoomLevel().getDisplayName())); // NON-NLS
|
||||
reportMetaData.add(new Pair<>("Filters", get.getFilter().getHTMLReportString())); // NON-NLS
|
||||
ZoomParams zoomParams = controller.getEventsModel().zoomParametersProperty().get();
|
||||
reportMetaData.add(new Pair<>("Time Range", zoomParams.getTimeRange().toString())); // NON-NLS
|
||||
reportMetaData.add(new Pair<>("Description Level of Detail", zoomParams.getDescriptionLOD().getDisplayName())); // NON-NLS
|
||||
reportMetaData.add(new Pair<>("Event Type Zoom Level", zoomParams.getTypeZoomLevel().getDisplayName())); // NON-NLS
|
||||
reportMetaData.add(new Pair<>("Filters", zoomParams.getFilter().getHTMLReportString())); // NON-NLS
|
||||
|
||||
//save snapshot as png
|
||||
try {
|
||||
ImageIO.write(SwingFXUtils.fromFXImage(snapshot, null), "png", new File(outFolder.getPath() + File.separator + outFolder.getName() + REPORT_IMAGE_EXTENSION)); // NON-NLS
|
||||
ImageIO.write(SwingFXUtils.fromFXImage(snapshot, null), "png",
|
||||
new File(reportPath, reportName + REPORT_IMAGE_EXTENSION)); // NON-NLS
|
||||
} catch (IOException ex) {
|
||||
LOGGER.log(Level.WARNING, "failed to write snapshot to disk", ex); // NON-NLS
|
||||
return;
|
||||
@ -99,17 +96,18 @@ public class SaveSnapshot extends Action {
|
||||
//build html string
|
||||
StringBuilder wrapper = new StringBuilder();
|
||||
wrapper.append("<html>\n<head>\n\t<title>").append("timeline snapshot").append("</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n</head>\n<body>\n"); // NON-NLS
|
||||
wrapper.append("<div id=\"content\">\n<h1>").append(outFolder.getName()).append("</h1>\n"); // NON-NLS
|
||||
wrapper.append("<img src = \"").append(outFolder.getName()).append(REPORT_IMAGE_EXTENSION + "\" alt = \"snaphot\">"); // NON-NLS
|
||||
wrapper.append("<div id=\"content\">\n<h1>").append(reportDirectory.getName()).append("</h1>\n"); // NON-NLS
|
||||
wrapper.append("<img src = \"").append(reportDirectory.getName()).append(REPORT_IMAGE_EXTENSION + "\" alt = \"snaphot\">"); // NON-NLS
|
||||
wrapper.append("<table>\n"); // NON-NLS
|
||||
for (Pair<String, String> pair : reportMetaData) {
|
||||
wrapper.append("<tr><td>").append(pair.getKey()).append(": </td><td>").append(pair.getValue()).append("</td></tr>\n"); // NON-NLS
|
||||
}
|
||||
wrapper.append("</table>\n"); // NON-NLS
|
||||
wrapper.append("</div>\n</body>\n</html>"); // NON-NLS
|
||||
File reportHTMLFIle = new File(reportDirectory, reportName + HTML_EXT);
|
||||
|
||||
//write html wrapper
|
||||
try (Writer htmlWriter = new FileWriter(new File(outFolder, name + HTML_EXT))) {
|
||||
try (Writer htmlWriter = new FileWriter(reportHTMLFIle)) {
|
||||
htmlWriter.write(wrapper.toString());
|
||||
} catch (FileNotFoundException ex) {
|
||||
LOGGER.log(Level.WARNING, "failed to open html wrapper file for writing ", ex); // NON-NLS
|
||||
@ -121,14 +119,14 @@ public class SaveSnapshot extends Action {
|
||||
|
||||
//copy css
|
||||
try (InputStream resource = this.getClass().getResourceAsStream("/org/sleuthkit/autopsy/timeline/index.css")) { // NON-NLS
|
||||
Files.copy(resource, Paths.get(outFolder.getPath(), "index.css")); // NON-NLS
|
||||
Files.copy(resource, Paths.get(reportPath, "index.css")); // NON-NLS
|
||||
} catch (IOException ex) {
|
||||
LOGGER.log(Level.WARNING, "failed to copy css file", ex); // NON-NLS
|
||||
}
|
||||
|
||||
//add html file as report to case
|
||||
try {
|
||||
Case.getCurrentCase().addReport(outFolder.getPath() + File.separator + outFolder.getName() + HTML_EXT, "Timeline", outFolder.getName() + HTML_EXT); // NON-NLS
|
||||
Case.getCurrentCase().addReport(reportHTMLFIle.getPath(), "Timeline", reportName + HTML_EXT); // NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.WARNING, "failed add html wrapper as a report", ex); // NON-NLS
|
||||
}
|
44
Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomIn.java
Normal file
44
Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomIn.java
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015 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.actions;
|
||||
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ZoomIn extends Action {
|
||||
|
||||
private static final Image MAGNIFIER_IN = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-in-green.png"); //NOI18N
|
||||
|
||||
@NbBundle.Messages({"ZoomIn.longText=Zoom in to view half as much time.",
|
||||
"ZoomIn.action.text=Zoom in"})
|
||||
public ZoomIn(TimeLineController controller) {
|
||||
super(Bundle.ZoomIn_action_text());
|
||||
setLongText(Bundle.ZoomIn_longText());
|
||||
setGraphic(new ImageView(MAGNIFIER_IN));
|
||||
setEventHandler(actionEvent -> {
|
||||
controller.pushZoomInTime();
|
||||
});
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014 Basis Technology Corp.
|
||||
* Copyright 2015 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -19,7 +19,8 @@
|
||||
package org.sleuthkit.autopsy.timeline.actions;
|
||||
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
@ -30,15 +31,22 @@ import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
*/
|
||||
public class ZoomOut extends Action {
|
||||
|
||||
private final TimeLineController controller;
|
||||
private static final Image MAGNIFIER_OUT = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-out-red.png"); //NOI18N
|
||||
|
||||
private final FilteredEventsModel eventsModel;
|
||||
@NbBundle.Messages({"ZoomOut.longText=Zoom out to view 50% more time.",
|
||||
"ZoomOut.action.text=Zoom out"})
|
||||
public ZoomOut(TimeLineController controller) {
|
||||
super(Bundle.ZoomOut_action_text());
|
||||
setLongText(Bundle.ZoomOut_longText());
|
||||
setGraphic(new ImageView(MAGNIFIER_OUT));
|
||||
setEventHandler(actionEvent -> {
|
||||
controller.pushZoomOutTime();
|
||||
});
|
||||
|
||||
public ZoomOut(final TimeLineController controller) {
|
||||
super(NbBundle.getMessage(ZoomOut.class, "ZoomOut.action.name.text"));
|
||||
this.controller = controller;
|
||||
eventsModel = controller.getEventsModel();
|
||||
//disable action when the current time range already encompases the entire case.
|
||||
disabledProperty().bind(new BooleanBinding() {
|
||||
private final FilteredEventsModel eventsModel = controller.getEventsModel();
|
||||
|
||||
{
|
||||
bind(eventsModel.zoomParametersProperty());
|
||||
}
|
||||
@ -48,8 +56,5 @@ public class ZoomOut extends Action {
|
||||
return eventsModel.zoomParametersProperty().getValue().getTimeRange().contains(eventsModel.getSpanningInterval());
|
||||
}
|
||||
});
|
||||
setEventHandler((ActionEvent t) -> {
|
||||
controller.zoomOutToActivity();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014-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.actions;
|
||||
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ZoomToEvents extends Action {
|
||||
|
||||
private static final Image MAGNIFIER_OUT = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-out-red.png", 16, 16, true, true); //NOI18N
|
||||
|
||||
@NbBundle.Messages({"ZoomToEvents.action.text=Zoom to events",
|
||||
"ZoomToEvents.longText=Zoom out to show the nearest events."})
|
||||
public ZoomToEvents(final TimeLineController controller) {
|
||||
super(Bundle.ZoomToEvents_action_text());
|
||||
setLongText(Bundle.ZoomToEvents_longText());
|
||||
setGraphic(new ImageView(MAGNIFIER_OUT));
|
||||
setEventHandler(actionEvent -> {
|
||||
controller.zoomOutToActivity();
|
||||
});
|
||||
|
||||
//disable action when the current time range already encompases the entire case.
|
||||
disabledProperty().bind(new BooleanBinding() {
|
||||
private final FilteredEventsModel eventsModel = controller.getEventsModel();
|
||||
|
||||
{
|
||||
bind(eventsModel.zoomParametersProperty());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean computeValue() {
|
||||
//TODO: do a db query to see if using this action will actually result in viewable events
|
||||
return eventsModel.zoomParametersProperty().getValue().getTimeRange().contains(eventsModel.getSpanningInterval());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -41,7 +41,6 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent.DeletedContentTagInfo;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
||||
import org.sleuthkit.autopsy.timeline.db.EventsRepository;
|
||||
|
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/cross-script.png
Normal file
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/cross-script.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 623 B |
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/select.png
Normal file
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/select.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 800 B |
@ -40,6 +40,7 @@ import javafx.scene.chart.Chart;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.OverrunStyle;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.effect.Effect;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
@ -50,12 +51,11 @@ import javafx.scene.text.Font;
|
||||
import javafx.scene.text.FontWeight;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
|
||||
|
||||
@ -73,8 +73,15 @@ import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
|
||||
* {@link XYChart} doing the rendering. Is this a good idea? -jm TODO: pull up
|
||||
* common history context menu items out of derived classes? -jm
|
||||
*/
|
||||
public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & TimeLineChart<X>> extends BorderPane implements TimeLineView {
|
||||
public abstract class AbstractVisualizationPane<X, Y, N, C extends XYChart<X, Y> & TimeLineChart<X>> extends BorderPane {
|
||||
|
||||
@NbBundle.Messages("AbstractVisualization.Drag_Tooltip.text=Drag the mouse to select a time interval to zoom into.")
|
||||
private static final Tooltip DRAG_TOOLTIP = new Tooltip(Bundle.AbstractVisualization_Drag_Tooltip_text());
|
||||
private static final Logger LOGGER = Logger.getLogger(AbstractVisualizationPane.class.getName());
|
||||
|
||||
public static Tooltip getDragTooltip() {
|
||||
return DRAG_TOOLTIP;
|
||||
}
|
||||
protected final SimpleBooleanProperty hasEvents = new SimpleBooleanProperty(true);
|
||||
|
||||
protected final ObservableList<BarChart.Series<X, Y>> dataSets = FXCollections.<BarChart.Series<X, Y>>observableArrayList();
|
||||
@ -93,11 +100,15 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
||||
*/
|
||||
private Task<Boolean> updateTask;
|
||||
|
||||
protected TimeLineController controller;
|
||||
final protected TimeLineController controller;
|
||||
|
||||
protected FilteredEventsModel filteredEvents;
|
||||
final protected FilteredEventsModel filteredEvents;
|
||||
|
||||
protected ReadOnlyListWrapper<N> selectedNodes = new ReadOnlyListWrapper<>(FXCollections.observableArrayList());
|
||||
final protected ReadOnlyListWrapper<N> selectedNodes = new ReadOnlyListWrapper<>(FXCollections.observableArrayList());
|
||||
|
||||
private InvalidationListener invalidationListener = (Observable observable) -> {
|
||||
update();
|
||||
};
|
||||
|
||||
public ReadOnlyListProperty<N> getSelectedNodes() {
|
||||
return selectedNodes.getReadOnlyProperty();
|
||||
@ -177,7 +188,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
||||
* Primarily this invokes the background {@link Task} returned by
|
||||
* {@link #getUpdateTask()} which derived classes must implement.
|
||||
*/
|
||||
synchronized public void update() {
|
||||
final synchronized public void update() {
|
||||
if (updateTask != null) {
|
||||
updateTask.cancel(true);
|
||||
updateTask = null;
|
||||
@ -195,7 +206,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
||||
try {
|
||||
this.hasEvents.set(updateTask.get());
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
Logger.getLogger(AbstractVisualization.class.getName()).log(Level.SEVERE, "Unexpected exception updating visualization", ex);
|
||||
LOGGER.log(Level.SEVERE, "Unexpected exception updating visualization", ex); //NOI18N
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -203,7 +214,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
||||
controller.monitorTask(updateTask);
|
||||
}
|
||||
|
||||
synchronized public void dispose() {
|
||||
final synchronized public void dispose() {
|
||||
if (updateTask != null) {
|
||||
updateTask.cancel(true);
|
||||
}
|
||||
@ -211,7 +222,12 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
||||
invalidationListener = null;
|
||||
}
|
||||
|
||||
protected AbstractVisualization(Pane partPane, Pane contextPane, Region spacer) {
|
||||
protected AbstractVisualizationPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) {
|
||||
this.controller = controller;
|
||||
|
||||
this.filteredEvents = controller.getEventsModel();
|
||||
this.filteredEvents.registerForEvents(this);
|
||||
this.filteredEvents.zoomParametersProperty().addListener(invalidationListener);
|
||||
this.leafPane = partPane;
|
||||
this.branchPane = contextPane;
|
||||
this.spacer = spacer;
|
||||
@ -226,32 +242,18 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public void setController(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
chart.setController(controller);
|
||||
|
||||
setModel(controller.getEventsModel());
|
||||
TimeLineController.getTimeZone().addListener((Observable observable) -> {
|
||||
update();
|
||||
TimeLineController.getTimeZone().addListener(invalidationListener);
|
||||
|
||||
//show tooltip text in status bar
|
||||
hoverProperty().addListener((observable, oldActivated, newActivated) -> {
|
||||
if (newActivated) {
|
||||
controller.setStatus(DRAG_TOOLTIP.getText());
|
||||
} else {
|
||||
controller.setStatus("");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public void setModel(@Nonnull FilteredEventsModel filteredEvents) {
|
||||
|
||||
if (this.filteredEvents != null && this.filteredEvents != filteredEvents) {
|
||||
this.filteredEvents.unRegisterForEvents(this);
|
||||
this.filteredEvents.zoomParametersProperty().removeListener(invalidationListener);
|
||||
}
|
||||
if (this.filteredEvents != filteredEvents) {
|
||||
filteredEvents.registerForEvents(this);
|
||||
filteredEvents.zoomParametersProperty().addListener(invalidationListener);
|
||||
}
|
||||
this.filteredEvents = filteredEvents;
|
||||
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
@ -260,10 +262,6 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
||||
update();
|
||||
}
|
||||
|
||||
protected InvalidationListener invalidationListener = (Observable observable) -> {
|
||||
update();
|
||||
};
|
||||
|
||||
/**
|
||||
* iterate through the list of tick-marks building a two level structure of
|
||||
* replacement tick marl labels. (Visually) upper level has most
|
||||
@ -370,7 +368,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
||||
*/
|
||||
private synchronized void assignLeafLabel(String labelText, double labelWidth, double labelX, boolean bold) {
|
||||
|
||||
Text label = new Text(" " + labelText + " ");
|
||||
Text label = new Text(" " + labelText + " "); //NOI18N
|
||||
label.setTextAlignment(TextAlignment.CENTER);
|
||||
label.setFont(Font.font(null, bold ? FontWeight.BOLD : FontWeight.NORMAL, 10));
|
||||
//position label accounting for width
|
||||
@ -414,9 +412,9 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
||||
label.relocate(labelX, 0);
|
||||
|
||||
if (labelX == 0) { // first label has no border
|
||||
label.setStyle("-fx-border-width: 0 0 0 0 ; -fx-border-color:black;"); // NON-NLS
|
||||
label.setStyle("-fx-border-width: 0 0 0 0 ; -fx-border-color:black;"); // NON-NLS //NOI18N
|
||||
} else { // subsequent labels have border on left to create dividers
|
||||
label.setStyle("-fx-border-width: 0 0 0 1; -fx-border-color:black;"); // NON-NLS
|
||||
label.setStyle("-fx-border-width: 0 0 0 1; -fx-border-color:black;"); // NON-NLS //NOI18N
|
||||
}
|
||||
|
||||
branchPane.getChildren().add(label);
|
||||
@ -446,10 +444,10 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
||||
|
||||
TwoPartDateTime(String dateString) {
|
||||
//find index of separator to spit on
|
||||
int splitIndex = StringUtils.lastIndexOfAny(dateString, " ", "-", ":");
|
||||
int splitIndex = StringUtils.lastIndexOfAny(dateString, " ", "-", ":"); //NOI18N
|
||||
if (splitIndex < 0) { // there is only one part
|
||||
leaf = dateString;
|
||||
branch = "";
|
||||
branch = ""; //NOI18N
|
||||
} else { //split at index
|
||||
leaf = StringUtils.substring(dateString, splitIndex + 1);
|
||||
branch = StringUtils.substring(dateString, 0, splitIndex);
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013 Basis Technology Corp.
|
||||
* Copyright 2013-15 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -35,7 +35,6 @@ Timeline.ui.ZoomRanges.threeyears.text=Three Years
|
||||
Timeline.ui.ZoomRanges.fiveyears.text=Five Years
|
||||
Timeline.ui.ZoomRanges.tenyears.text=Ten Years
|
||||
Timeline.ui.ZoomRanges.all.text=All
|
||||
Timeline.ui.TimeLineChart.tooltip.text=Double-click to zoom into range\:\n{0} to {1}\nRight-click to clear.
|
||||
TimeLineResultView.startDateToEndDate.text={0} to {1}
|
||||
VisualizationPanel.histogramTask.title=Rebuild Histogram
|
||||
VisualizationPanel.histogramTask.preparing=preparing
|
||||
@ -56,4 +55,4 @@ VisualizationPanel.zoomMenuButton.text=Zoom in/out to
|
||||
VisualizationPanel.snapShotButton.text=Screenshot
|
||||
VisualizationPanel.detailsToggle.text=Details
|
||||
VisualizationPanel.countsToggle.text=Counts
|
||||
VisualizationPanel.resetFiltersButton.text=Reset all filters
|
||||
VisualizationPanel.resetFiltersButton.text=Reset all filters
|
@ -0,0 +1,16 @@
|
||||
.intervalSelector{
|
||||
-fx-background-color: rgba(0,0,255,.25);
|
||||
-fx-border-color: rgba(0,0,255,.25);
|
||||
-fx-border-width: 0 3 0 3;
|
||||
}
|
||||
|
||||
.closeButton{
|
||||
}
|
||||
|
||||
.closeButton:hover{
|
||||
-fx-opacity: 1;
|
||||
}
|
||||
|
||||
.zoomButton:hover{
|
||||
-fx-opacity: 1;
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.*?>
|
||||
<?import javafx.scene.image.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import java.lang.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" styleClass="intervalSelector" stylesheets="@IntervalSelector.css" type="BorderPane" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<top>
|
||||
<Button fx:id="closeButton" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" opacity="0.5" prefHeight="16.0" prefWidth="16.0" styleClass="closeButton" BorderPane.alignment="CENTER_RIGHT">
|
||||
<graphic>
|
||||
<ImageView opacity="0.5" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../images/cross-script.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
</top>
|
||||
<padding>
|
||||
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
|
||||
</padding>
|
||||
<bottom>
|
||||
<BorderPane fx:id="bottomBorder" minHeight="32.0" BorderPane.alignment="BOTTOM_LEFT">
|
||||
<left>
|
||||
<HBox alignment="BOTTOM_LEFT" BorderPane.alignment="BOTTOM_LEFT">
|
||||
<children>
|
||||
<Label fx:id="startLabel" alignment="BOTTOM_LEFT" minWidth="0.0" text="Label" />
|
||||
<Region HBox.hgrow="ALWAYS" />
|
||||
</children>
|
||||
</HBox>
|
||||
</left>
|
||||
<right>
|
||||
<HBox alignment="BOTTOM_RIGHT" BorderPane.alignment="CENTER">
|
||||
<children>
|
||||
<Region HBox.hgrow="ALWAYS" />
|
||||
<Label fx:id="endLabel" minWidth="0.0" text="Label" />
|
||||
</children>
|
||||
</HBox>
|
||||
</right>
|
||||
<center>
|
||||
<Button fx:id="zoomButton" alignment="CENTER" mnemonicParsing="false" opacity="0.66" styleClass="zoomButton" text="Zoom" BorderPane.alignment="CENTER">
|
||||
<graphic>
|
||||
<ImageView pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../images/magnifier-zoom-fit.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
</center>
|
||||
</BorderPane>
|
||||
</bottom>
|
||||
</fx:root>
|
316
Core/src/org/sleuthkit/autopsy/timeline/ui/IntervalSelector.java
Normal file
316
Core/src/org/sleuthkit/autopsy/timeline/ui/IntervalSelector.java
Normal file
@ -0,0 +1,316 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014-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;
|
||||
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.controlsfx.control.action.ActionUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Interval;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
|
||||
/**
|
||||
* Visually represents a 'selected' time range, and allows mouse interactions
|
||||
* with it.
|
||||
*
|
||||
* @param <X> the type of values along the x axis this is a selector for
|
||||
*
|
||||
* This abstract class requires concrete implementations to implement template
|
||||
* methods to handle formating and date 'lookup' of the generic x-axis type
|
||||
*/
|
||||
public abstract class IntervalSelector<X> extends BorderPane {
|
||||
|
||||
private static final Image ClEAR_INTERVAL_ICON = new Image("/org/sleuthkit/autopsy/timeline/images/cross-script.png", 16, 16, true, true, true);
|
||||
private static final Image ZOOM_TO_INTERVAL_ICON = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-fit.png", 16, 16, true, true, true);
|
||||
private static final double STROKE_WIDTH = 3;
|
||||
private static final double HALF_STROKE = STROKE_WIDTH / 2;
|
||||
|
||||
/**
|
||||
* the Axis this is a selector over
|
||||
*/
|
||||
public final TimeLineChart<X> chart;
|
||||
|
||||
private Tooltip tooltip;
|
||||
/////////drag state
|
||||
private DragPosition dragPosition;
|
||||
private double startLeft;
|
||||
private double startDragX;
|
||||
private double startWidth;
|
||||
|
||||
private final BooleanProperty isDragging = new SimpleBooleanProperty(false);
|
||||
/////////end drag state
|
||||
private final TimeLineController controller;
|
||||
|
||||
@FXML
|
||||
private Label startLabel;
|
||||
|
||||
@FXML
|
||||
private Label endLabel;
|
||||
|
||||
@FXML
|
||||
private Button closeButton;
|
||||
|
||||
@FXML
|
||||
private Button zoomButton;
|
||||
|
||||
@FXML
|
||||
private BorderPane bottomBorder;
|
||||
|
||||
public IntervalSelector(TimeLineChart<X> chart) {
|
||||
this.chart = chart;
|
||||
this.controller = chart.getController();
|
||||
FXMLConstructor.construct(this, IntervalSelector.class, "IntervalSelector.fxml"); // NON-NLS
|
||||
}
|
||||
|
||||
@FXML
|
||||
void initialize() {
|
||||
assert startLabel != null : "fx:id=\"startLabel\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
|
||||
assert endLabel != null : "fx:id=\"endLabel\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
|
||||
assert closeButton != null : "fx:id=\"closeButton\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
|
||||
assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
|
||||
|
||||
setMaxHeight(USE_PREF_SIZE);
|
||||
setMinHeight(USE_PREF_SIZE);
|
||||
setMaxWidth(USE_PREF_SIZE);
|
||||
setMinWidth(USE_PREF_SIZE);
|
||||
|
||||
BooleanBinding showingControls = hoverProperty().and(isDragging.not());
|
||||
closeButton.visibleProperty().bind(showingControls);
|
||||
closeButton.managedProperty().bind(showingControls);
|
||||
zoomButton.visibleProperty().bind(showingControls);
|
||||
zoomButton.managedProperty().bind(showingControls);
|
||||
|
||||
widthProperty().addListener(o -> {
|
||||
IntervalSelector.this.updateStartAndEnd();
|
||||
if (startLabel.getWidth() + zoomButton.getWidth() + endLabel.getWidth() > getWidth()) {
|
||||
this.setCenter(zoomButton);
|
||||
} else {
|
||||
bottomBorder.setCenter(zoomButton);
|
||||
}
|
||||
BorderPane.setAlignment(zoomButton, Pos.BOTTOM_CENTER);
|
||||
});
|
||||
layoutXProperty().addListener(o -> this.updateStartAndEnd());
|
||||
updateStartAndEnd();
|
||||
|
||||
setOnMouseMoved(mouseMove -> {
|
||||
Point2D parentMouse = getLocalMouseCoords(mouseMove);
|
||||
final double diffX = getLayoutX() - parentMouse.getX();
|
||||
if (Math.abs(diffX) <= HALF_STROKE) {
|
||||
setCursor(Cursor.W_RESIZE);
|
||||
} else if (Math.abs(diffX + getWidth()) <= HALF_STROKE) {
|
||||
setCursor(Cursor.E_RESIZE);
|
||||
} else {
|
||||
setCursor(Cursor.HAND);
|
||||
}
|
||||
mouseMove.consume();
|
||||
});
|
||||
|
||||
setOnMousePressed(mousePress -> {
|
||||
Point2D parentMouse = getLocalMouseCoords(mousePress);
|
||||
final double diffX = getLayoutX() - parentMouse.getX();
|
||||
startDragX = mousePress.getScreenX();
|
||||
startWidth = getWidth();
|
||||
startLeft = getLayoutX();
|
||||
if (Math.abs(diffX) <= HALF_STROKE) {
|
||||
dragPosition = IntervalSelector.DragPosition.LEFT;
|
||||
} else if (Math.abs(diffX + getWidth()) <= HALF_STROKE) {
|
||||
dragPosition = IntervalSelector.DragPosition.RIGHT;
|
||||
} else {
|
||||
dragPosition = IntervalSelector.DragPosition.CENTER;
|
||||
}
|
||||
mousePress.consume();
|
||||
});
|
||||
|
||||
setOnMouseReleased(mouseRelease -> isDragging.set(false));
|
||||
setOnMouseDragged(mouseDrag -> {
|
||||
isDragging.set(true);
|
||||
double dX = mouseDrag.getScreenX() - startDragX;
|
||||
switch (dragPosition) {
|
||||
case CENTER:
|
||||
setLayoutX(startLeft + dX);
|
||||
break;
|
||||
case LEFT:
|
||||
if (dX > startWidth) {
|
||||
startDragX = mouseDrag.getScreenX();
|
||||
startWidth = 0;
|
||||
dragPosition = DragPosition.RIGHT;
|
||||
} else {
|
||||
setLayoutX(startLeft + dX);
|
||||
setPrefWidth(startWidth - dX);
|
||||
autosize();
|
||||
}
|
||||
break;
|
||||
case RIGHT:
|
||||
Point2D parentMouse = getLocalMouseCoords(mouseDrag);
|
||||
if (parentMouse.getX() < startLeft) {
|
||||
dragPosition = DragPosition.LEFT;
|
||||
startDragX = mouseDrag.getScreenX();
|
||||
startWidth = 0;
|
||||
} else {
|
||||
setPrefWidth(startWidth + dX);
|
||||
autosize();
|
||||
}
|
||||
break;
|
||||
}
|
||||
mouseDrag.consume();
|
||||
});
|
||||
|
||||
ActionUtils.configureButton(new ZoomToSelectedIntervalAction(), zoomButton);
|
||||
ActionUtils.configureButton(new ClearSelectedIntervalAction(), closeButton);
|
||||
|
||||
//have to add handler rather than use convenience methods so that charts can listen for dismisal click
|
||||
setOnMouseClicked(mosueClick -> {
|
||||
if (mosueClick.getButton() == MouseButton.SECONDARY) {
|
||||
chart.clearIntervalSelector();
|
||||
mosueClick.consume();
|
||||
}
|
||||
if (mosueClick.getClickCount() >= 2) {
|
||||
zoomToSelectedInterval();
|
||||
mosueClick.consume();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Point2D getLocalMouseCoords(MouseEvent mouseEvent) {
|
||||
return getParent().sceneToLocal(new Point2D(mouseEvent.getSceneX(), mouseEvent.getSceneY()));
|
||||
}
|
||||
|
||||
private void zoomToSelectedInterval() {
|
||||
//convert to DateTimes, using max/min if null(off axis)
|
||||
DateTime start = parseDateTime(getSpanStart());
|
||||
DateTime end = parseDateTime(getSpanEnd());
|
||||
Interval i = adjustInterval(start.isBefore(end) ? new Interval(start, end) : new Interval(end, start));
|
||||
controller.pushTimeRange(i);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param i the interval represented by this selector
|
||||
*
|
||||
* @return a modified version of {@code i} adjusted to suite the needs of
|
||||
* the concrete implementation
|
||||
*/
|
||||
protected abstract Interval adjustInterval(Interval i);
|
||||
|
||||
/**
|
||||
* format a string representation of the given x-axis value to use in the
|
||||
* tooltip
|
||||
*
|
||||
* @param date a x-axis value of type X
|
||||
*
|
||||
* @return a string representation of the given x-axis value
|
||||
*/
|
||||
protected abstract String formatSpan(final X date);
|
||||
|
||||
/**
|
||||
* parse an x-axis value to a {@link DateTime}
|
||||
*
|
||||
* @param date a x-axis value of type X
|
||||
*
|
||||
* @return a {@link DateTime} corresponding to the given x-axis value
|
||||
*/
|
||||
protected abstract DateTime parseDateTime(X date);
|
||||
|
||||
@NbBundle.Messages(value = {"# {0} - start timestamp",
|
||||
"# {1} - end timestamp",
|
||||
"Timeline.ui.TimeLineChart.tooltip.text=Double-click to zoom into range:\n{0} to {1}\nRight-click to clear."})
|
||||
private void updateStartAndEnd() {
|
||||
String startString = formatSpan(getSpanStart());
|
||||
String endString = formatSpan(getSpanEnd());
|
||||
startLabel.setText(startString);
|
||||
endLabel.setText(endString);
|
||||
|
||||
Tooltip.uninstall(this, tooltip);
|
||||
tooltip = new Tooltip(Bundle.Timeline_ui_TimeLineChart_tooltip_text(startString, endString));
|
||||
Tooltip.install(this, tooltip);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value along the x-axis corresponding to the left edge of the
|
||||
* selector
|
||||
*/
|
||||
public X getSpanEnd() {
|
||||
return getValueForDisplay(getBoundsInParent().getMaxX());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value along the x-axis corresponding to the right edge of the
|
||||
* selector
|
||||
*/
|
||||
public X getSpanStart() {
|
||||
return getValueForDisplay(getBoundsInParent().getMinX());
|
||||
}
|
||||
|
||||
private X getValueForDisplay(final double display) {
|
||||
return chart.getXAxis().getValueForDisplay(chart.getXAxis().parentToLocal(display, 0).getX());
|
||||
}
|
||||
|
||||
/**
|
||||
* enum to represent whether the drag is a left/right-edge modification or a
|
||||
* horizontal slide triggered by dragging the center
|
||||
*/
|
||||
private enum DragPosition {
|
||||
|
||||
LEFT,
|
||||
CENTER,
|
||||
RIGHT
|
||||
}
|
||||
|
||||
private class ZoomToSelectedIntervalAction extends Action {
|
||||
|
||||
@NbBundle.Messages("IntervalSelector.ZoomAction.name=Zoom")
|
||||
ZoomToSelectedIntervalAction() {
|
||||
super(Bundle.IntervalSelector_ZoomAction_name());
|
||||
setGraphic(new ImageView(ZOOM_TO_INTERVAL_ICON));
|
||||
setEventHandler((ActionEvent t) -> {
|
||||
zoomToSelectedInterval();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class ClearSelectedIntervalAction extends Action {
|
||||
|
||||
@NbBundle.Messages("IntervalSelector.ClearSelectedIntervalAction.tooltTipText=Clear Selected Interval")
|
||||
ClearSelectedIntervalAction() {
|
||||
super("");
|
||||
setLongText(Bundle.IntervalSelector_ClearSelectedIntervalAction_tooltTipText());
|
||||
setGraphic(new ImageView(ClEAR_INTERVAL_ICON));
|
||||
setEventHandler((ActionEvent t) -> {
|
||||
chart.clearIntervalSelector();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
<?import javafx.scene.image.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" type="ToolBar" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" type="ToolBar" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<items>
|
||||
<Label fx:id="refreshLabel">
|
||||
<graphic>
|
||||
@ -17,6 +17,15 @@
|
||||
</graphic>
|
||||
</Label>
|
||||
<Separator orientation="VERTICAL" />
|
||||
<Label fx:id="statusLabel" layoutX="10.0" layoutY="11.0">
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../images/information-gray.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Label>
|
||||
<Region fx:id="spacer" maxWidth="1.7976931348623157E308" />
|
||||
<Separator orientation="VERTICAL" />
|
||||
<Label fx:id="taskLabel" contentDisplay="RIGHT">
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014 Basis Technology Corp.
|
||||
* Copyright 2014-15 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -28,19 +28,21 @@ import javafx.scene.layout.Region;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineUI;
|
||||
|
||||
/**
|
||||
* simple status bar that only shows one possible message determined by
|
||||
* {@link TimeLineController#newEventsFlag}
|
||||
*/
|
||||
public class StatusBar extends ToolBar implements TimeLineUI {
|
||||
public class StatusBar extends ToolBar {
|
||||
|
||||
private TimeLineController controller;
|
||||
|
||||
@FXML
|
||||
private Label refreshLabel;
|
||||
|
||||
@FXML
|
||||
private Label statusLabel;
|
||||
|
||||
@FXML
|
||||
private ProgressBar progressBar;
|
||||
|
||||
@ -53,7 +55,8 @@ public class StatusBar extends ToolBar implements TimeLineUI {
|
||||
@FXML
|
||||
private Label messageLabel;
|
||||
|
||||
public StatusBar() {
|
||||
public StatusBar(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
FXMLConstructor.construct(this, "StatusBar.fxml"); // NON-NLS
|
||||
}
|
||||
|
||||
@ -70,15 +73,16 @@ public class StatusBar extends ToolBar implements TimeLineUI {
|
||||
taskLabel.setText(NbBundle.getMessage(this.getClass(), "StatusBar.taskLabel.text"));
|
||||
taskLabel.setVisible(false);
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setController(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
refreshLabel.visibleProperty().bind(this.controller.getNewEventsFlag());
|
||||
refreshLabel.managedProperty().bind(this.controller.getNewEventsFlag());
|
||||
taskLabel.textProperty().bind(this.controller.getTaskTitle());
|
||||
messageLabel.textProperty().bind(this.controller.getMessage());
|
||||
progressBar.progressProperty().bind(this.controller.getProgress());
|
||||
taskLabel.visibleProperty().bind(this.controller.getTasks().emptyProperty().not());
|
||||
|
||||
statusLabel.textProperty().bind(this.controller.getStatusProperty());
|
||||
statusLabel.visibleProperty().bind(statusLabel.textProperty().isNotEmpty());
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014 Basis Technology Corp.
|
||||
* Copyright 2014-15 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -19,28 +19,27 @@
|
||||
package org.sleuthkit.autopsy.timeline.ui;
|
||||
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.event.EventType;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.chart.Axis;
|
||||
import javafx.scene.chart.Chart;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Interval;
|
||||
import org.controlsfx.control.action.ActionGroup;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||
|
||||
/**
|
||||
* Interface for TimeLineViews that are 'charts'.
|
||||
*
|
||||
* @param <X> the type of values along the horizontal axis
|
||||
*/
|
||||
public interface TimeLineChart<X> extends TimeLineView {
|
||||
public interface TimeLineChart<X> {
|
||||
|
||||
// void setController(TimeLineController controller);
|
||||
IntervalSelector<? extends X> getIntervalSelector();
|
||||
|
||||
void setIntervalSelector(IntervalSelector<? extends X> newIntervalSelector);
|
||||
@ -49,12 +48,9 @@ public interface TimeLineChart<X> extends TimeLineView {
|
||||
* derived classes should implement this so as to supply an appropriate
|
||||
* subclass of {@link IntervalSelector}
|
||||
*
|
||||
* @param x the initial x position of the new interval selector
|
||||
* @param axis the axis the new interval selector will be over
|
||||
*
|
||||
* @return a new interval selector
|
||||
*/
|
||||
IntervalSelector<X> newIntervalSelector(double x, Axis<X> axis);
|
||||
IntervalSelector<X> newIntervalSelector();
|
||||
|
||||
/**
|
||||
* clear any references to previous interval selectors , including removing
|
||||
@ -62,6 +58,14 @@ public interface TimeLineChart<X> extends TimeLineView {
|
||||
*/
|
||||
void clearIntervalSelector();
|
||||
|
||||
public Axis<X> getXAxis();
|
||||
|
||||
public TimeLineController getController();
|
||||
|
||||
ContextMenu getChartContextMenu();
|
||||
|
||||
ContextMenu getChartContextMenu(MouseEvent m);
|
||||
|
||||
/**
|
||||
* drag handler class used by {@link TimeLineChart}s to create
|
||||
* {@link IntervalSelector}s
|
||||
@ -69,228 +73,87 @@ public interface TimeLineChart<X> extends TimeLineView {
|
||||
* @param <X> the type of values along the horizontal axis
|
||||
* @param <Y> the type of chart this is a drag handler for
|
||||
*/
|
||||
class ChartDragHandler<X, Y extends Chart & TimeLineChart<X>> implements EventHandler<MouseEvent> {
|
||||
static class ChartDragHandler<X, Y extends Chart & TimeLineChart<X>> implements EventHandler<MouseEvent> {
|
||||
|
||||
private final Y chart;
|
||||
|
||||
private final Axis<X> dateAxis;
|
||||
|
||||
private double startX; //hanlder mainstains position of drag start
|
||||
|
||||
public ChartDragHandler(Y chart, Axis<X> dateAxis) {
|
||||
public ChartDragHandler(Y chart) {
|
||||
this.chart = chart;
|
||||
this.dateAxis = dateAxis;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MouseEvent t) {
|
||||
if (t.getButton() == MouseButton.SECONDARY) {
|
||||
|
||||
if (t.getEventType() == MouseEvent.MOUSE_PRESSED) {
|
||||
//caputure x-position, incase we are repositioning existing selector
|
||||
startX = t.getX();
|
||||
chart.setCursor(Cursor.E_RESIZE);
|
||||
} else if (t.getEventType() == MouseEvent.MOUSE_DRAGGED) {
|
||||
if (chart.getIntervalSelector() == null) {
|
||||
//make new interval selector
|
||||
chart.setIntervalSelector(chart.newIntervalSelector(t.getX(), dateAxis));
|
||||
chart.getIntervalSelector().heightProperty().bind(chart.heightProperty().subtract(dateAxis.heightProperty().subtract(dateAxis.tickLengthProperty())));
|
||||
chart.getIntervalSelector().addEventHandler(MouseEvent.MOUSE_CLICKED, (MouseEvent event) -> {
|
||||
if (event.getButton() == MouseButton.SECONDARY) {
|
||||
chart.clearIntervalSelector();
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
startX = t.getX();
|
||||
public void handle(MouseEvent mouseEvent) {
|
||||
EventType<? extends MouseEvent> mouseEventType = mouseEvent.getEventType();
|
||||
if (mouseEventType == MouseEvent.MOUSE_PRESSED) {
|
||||
//caputure x-position, incase we are repositioning existing selector
|
||||
startX = mouseEvent.getX();
|
||||
chart.setCursor(Cursor.H_RESIZE);
|
||||
} else if (mouseEventType == MouseEvent.MOUSE_DRAGGED) {
|
||||
if (chart.getIntervalSelector() == null) {
|
||||
//make new interval selector
|
||||
chart.setIntervalSelector(chart.newIntervalSelector());
|
||||
chart.getIntervalSelector().prefHeightProperty().bind(chart.heightProperty());
|
||||
startX = mouseEvent.getX();
|
||||
chart.getIntervalSelector().relocate(startX, 0);
|
||||
} else {
|
||||
//resize/position existing selector
|
||||
if (mouseEvent.getX() > startX) {
|
||||
chart.getIntervalSelector().relocate(startX, 0);
|
||||
chart.getIntervalSelector().setPrefWidth(mouseEvent.getX() - startX);
|
||||
} else {
|
||||
//resize/position existing selector
|
||||
if (t.getX() > startX) {
|
||||
chart.getIntervalSelector().setX(startX);
|
||||
chart.getIntervalSelector().setWidth(t.getX() - startX);
|
||||
} else {
|
||||
chart.getIntervalSelector().setX(t.getX());
|
||||
chart.getIntervalSelector().setWidth(startX - t.getX());
|
||||
}
|
||||
chart.getIntervalSelector().relocate(mouseEvent.getX(), 0);
|
||||
chart.getIntervalSelector().setPrefWidth(startX - mouseEvent.getX());
|
||||
}
|
||||
} else if (t.getEventType() == MouseEvent.MOUSE_RELEASED) {
|
||||
chart.setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
t.consume();
|
||||
chart.getIntervalSelector().autosize();
|
||||
} else if (mouseEventType == MouseEvent.MOUSE_RELEASED) {
|
||||
chart.setCursor(Cursor.DEFAULT);
|
||||
} else if (mouseEventType == MouseEvent.MOUSE_CLICKED) {
|
||||
chart.setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class MouseClickedHandler<X, C extends Chart & TimeLineChart<X>> implements EventHandler<MouseEvent> {
|
||||
|
||||
private final C chart;
|
||||
|
||||
public MouseClickedHandler(C chart) {
|
||||
this.chart = chart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MouseEvent clickEvent) {
|
||||
if (chart.getChartContextMenu() != null) {
|
||||
chart.getChartContextMenu().hide();
|
||||
}
|
||||
if (clickEvent.getButton() == MouseButton.SECONDARY && clickEvent.isStillSincePress()) {
|
||||
chart.getChartContextMenu(clickEvent);
|
||||
chart.setOnMouseMoved(this);
|
||||
chart.getChartContextMenu().show(chart, clickEvent.getScreenX(), clickEvent.getScreenY());
|
||||
clickEvent.consume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visually represents a 'selected' time range, and allows mouse
|
||||
* interactions with it.
|
||||
*
|
||||
* @param <X> the type of values along the x axis this is a selector for
|
||||
*
|
||||
* This abstract class requires concrete implementations to implement hook
|
||||
* methods to handle formating and date 'lookup' of the generic x-axis type
|
||||
* enum to represent whether the drag is a left/right-edge modification or a
|
||||
* horizontal slide triggered by dragging the center
|
||||
*/
|
||||
static abstract class IntervalSelector<X> extends Rectangle {
|
||||
enum DragPosition {
|
||||
|
||||
private static final double STROKE_WIDTH = 3;
|
||||
LEFT,
|
||||
CENTER,
|
||||
RIGHT
|
||||
}
|
||||
|
||||
private static final double HALF_STROKE = STROKE_WIDTH / 2;
|
||||
|
||||
/**
|
||||
* the Axis this is a selector over
|
||||
*/
|
||||
private final Axis<X> dateAxis;
|
||||
|
||||
protected Tooltip tooltip;
|
||||
|
||||
/////////drag state
|
||||
private DragPosition dragPosition;
|
||||
|
||||
private double startLeft;
|
||||
|
||||
private double startX;
|
||||
|
||||
private double startWidth;
|
||||
/////////end drag state
|
||||
|
||||
/**
|
||||
*
|
||||
* @param x the initial x position of this selector
|
||||
* @param height the initial height of this selector
|
||||
* @param axis the {@link Axis<X>} this is a selector over
|
||||
* @param controller the controller to invoke when this selector is
|
||||
* double clicked
|
||||
*/
|
||||
public IntervalSelector(double x, double height, Axis<X> axis, TimeLineController controller) {
|
||||
super(x, 0, x, height);
|
||||
dateAxis = axis;
|
||||
setStroke(Color.BLUE);
|
||||
setStrokeWidth(STROKE_WIDTH);
|
||||
setFill(Color.BLUE.deriveColor(0, 1, 1, 0.5));
|
||||
setOpacity(0.5);
|
||||
widthProperty().addListener(o -> {
|
||||
setTooltip();
|
||||
});
|
||||
xProperty().addListener(o -> {
|
||||
setTooltip();
|
||||
});
|
||||
setTooltip();
|
||||
|
||||
setOnMouseMoved((MouseEvent event) -> {
|
||||
Point2D localMouse = sceneToLocal(new Point2D(event.getSceneX(), event.getSceneY()));
|
||||
final double diffX = getX() - localMouse.getX();
|
||||
if (Math.abs(diffX) <= HALF_STROKE || Math.abs(diffX + getWidth()) <= HALF_STROKE) {
|
||||
setCursor(Cursor.E_RESIZE);
|
||||
} else {
|
||||
setCursor(Cursor.HAND);
|
||||
}
|
||||
});
|
||||
setOnMousePressed((MouseEvent event) -> {
|
||||
Point2D localMouse = sceneToLocal(new Point2D(event.getSceneX(), event.getSceneY()));
|
||||
final double diffX = getX() - localMouse.getX();
|
||||
startX = event.getX();
|
||||
startWidth = getWidth();
|
||||
startLeft = getX();
|
||||
if (Math.abs(diffX) <= HALF_STROKE) {
|
||||
dragPosition = IntervalSelector.DragPosition.LEFT;
|
||||
} else if (Math.abs(diffX + getWidth()) <= HALF_STROKE) {
|
||||
dragPosition = IntervalSelector.DragPosition.RIGHT;
|
||||
} else {
|
||||
dragPosition = IntervalSelector.DragPosition.CENTER;
|
||||
}
|
||||
});
|
||||
setOnMouseDragged((MouseEvent event) -> {
|
||||
double dX = event.getX() - startX;
|
||||
switch (dragPosition) {
|
||||
case CENTER:
|
||||
setX(startLeft + dX);
|
||||
break;
|
||||
case LEFT:
|
||||
setX(startLeft + dX);
|
||||
setWidth(startWidth - dX);
|
||||
break;
|
||||
case RIGHT:
|
||||
setWidth(startWidth + dX);
|
||||
break;
|
||||
}
|
||||
});
|
||||
//have to add handler rather than use convenience methods so that charts can listen for dismisal click
|
||||
addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
|
||||
|
||||
public void handle(MouseEvent event) {
|
||||
if (event.getClickCount() >= 2) {
|
||||
//convert to DateTimes, using max/min if null(off axis)
|
||||
DateTime start = parseDateTime(getSpanStart());
|
||||
DateTime end = parseDateTime(getSpanEnd());
|
||||
|
||||
Interval i = adjustInterval(start.isBefore(end) ? new Interval(start, end) : new Interval(end, start));
|
||||
|
||||
controller.pushTimeRange(i);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param i the interval represented by this selector
|
||||
*
|
||||
* @return a modified version of {@code i} adjusted to suite the needs
|
||||
* of the concrete implementation
|
||||
*/
|
||||
protected abstract Interval adjustInterval(Interval i);
|
||||
|
||||
/**
|
||||
* format a string representation of the given x-axis value to use in
|
||||
* the tooltip
|
||||
*
|
||||
* @param date a x-axis value of type X
|
||||
*
|
||||
* @return a string representation of the given x-axis value
|
||||
*/
|
||||
protected abstract String formatSpan(final X date);
|
||||
|
||||
/**
|
||||
* parse an x-axis value to a {@link DateTime}
|
||||
*
|
||||
* @param date a x-axis value of type X
|
||||
*
|
||||
* @return a {@link DateTime} corresponding to the given x-axis value
|
||||
*/
|
||||
protected abstract DateTime parseDateTime(X date);
|
||||
|
||||
private void setTooltip() {
|
||||
final X start = getSpanStart();
|
||||
final X end = getSpanEnd();
|
||||
Tooltip.uninstall(this, tooltip);
|
||||
tooltip = new Tooltip(
|
||||
NbBundle.getMessage(TimeLineChart.class, "Timeline.ui.TimeLineChart.tooltip.text", formatSpan(start),
|
||||
formatSpan(end)));
|
||||
Tooltip.install(this, tooltip);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value along the x-axis corresponding to the left edge of
|
||||
* the selector
|
||||
*/
|
||||
public X getSpanEnd() {
|
||||
return dateAxis.getValueForDisplay(dateAxis.parentToLocal(getBoundsInParent().getMaxX(), 0).getX());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value along the x-axis corresponding to the right edge of
|
||||
* the selector
|
||||
*/
|
||||
public X getSpanStart() {
|
||||
return dateAxis.getValueForDisplay(dateAxis.parentToLocal(getBoundsInParent().getMinX(), 0).getX());
|
||||
}
|
||||
|
||||
/**
|
||||
* enum to represent whether the drag is a left/right-edge modification
|
||||
* or a horizontal slide triggered by dragging the center
|
||||
*/
|
||||
private enum DragPosition {
|
||||
|
||||
LEFT, CENTER, RIGHT
|
||||
}
|
||||
@NbBundle.Messages({"TimeLineChart.zoomHistoryActionGroup.name=Zoom History"})
|
||||
static ActionGroup newZoomHistoyActionGroup(TimeLineController controller) {
|
||||
return new ActionGroup(Bundle.TimeLineChart_zoomHistoryActionGroup_name(),
|
||||
new Back(controller),
|
||||
new Forward(controller));
|
||||
}
|
||||
}
|
||||
|
@ -25,12 +25,11 @@ import javax.swing.SwingUtilities;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.explorernodes.EventRootNode;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.explorernodes.EventRootNode;
|
||||
|
||||
/**
|
||||
* Since it was too hard to derive from {@link DataResultPanel}, this class
|
||||
@ -39,16 +38,16 @@ import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
|
||||
* {@link DataResultPanel}. That is, this class acts as a sort of bridge/adapter
|
||||
* between a FilteredEventsModel instance and a DataResultPanel instance.
|
||||
*/
|
||||
public class TimeLineResultView implements TimeLineView {
|
||||
public class TimeLineResultView {
|
||||
|
||||
/**
|
||||
* the {@link DataResultPanel} that is the real view proxied by this class
|
||||
*/
|
||||
private final DataResultPanel dataResultPanel;
|
||||
|
||||
private TimeLineController controller;
|
||||
private final TimeLineController controller;
|
||||
|
||||
private FilteredEventsModel filteredEvents;
|
||||
private final FilteredEventsModel filteredEvents;
|
||||
|
||||
private Set<Long> selectedEventIDs = new HashSet<>();
|
||||
|
||||
@ -56,19 +55,11 @@ public class TimeLineResultView implements TimeLineView {
|
||||
return dataResultPanel;
|
||||
}
|
||||
|
||||
public TimeLineResultView(DataContent dataContent) {
|
||||
dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, dataContent);
|
||||
}
|
||||
public TimeLineResultView(TimeLineController controller, DataContent dataContent) {
|
||||
|
||||
/**
|
||||
* Set the Controller for this class. Also sets the model provided by the
|
||||
* controller as the model for this view.
|
||||
*
|
||||
* @param controller
|
||||
*/
|
||||
@Override
|
||||
public void setController(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
this.filteredEvents = controller.getEventsModel();
|
||||
dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, dataContent);
|
||||
|
||||
//set up listeners on relevant properties
|
||||
TimeLineController.getTimeZone().addListener((Observable observable) -> {
|
||||
@ -78,18 +69,7 @@ public class TimeLineResultView implements TimeLineView {
|
||||
controller.getSelectedEventIDs().addListener((Observable o) -> {
|
||||
refresh();
|
||||
});
|
||||
|
||||
setModel(controller.getEventsModel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Model for this View
|
||||
*
|
||||
* @param filteredEvents
|
||||
*/
|
||||
@Override
|
||||
synchronized public void setModel(final FilteredEventsModel filteredEvents) {
|
||||
this.filteredEvents = filteredEvents;
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,7 +23,10 @@ import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.TitledPane;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
@ -50,8 +53,9 @@ public class TimeZonePanel extends TitledPane {
|
||||
}
|
||||
|
||||
@FXML
|
||||
@NbBundle.Messages({"TimeZonePanel.title=Display Times In:"})
|
||||
public void initialize() {
|
||||
|
||||
setText(Bundle.TimeZonePanel_title());
|
||||
// localRadio.setText("Local Time Zone: " + getTimeZoneString(TimeZone.getDefault()));
|
||||
localRadio.setText(NbBundle.getMessage(this.getClass(), "TimeZonePanel.localRadio.text"));
|
||||
otherRadio.setText(NbBundle.getMessage(this.getClass(), "TimeZonePanel.otherRadio.text"));
|
||||
|
@ -9,7 +9,7 @@
|
||||
<?import jfxtras.scene.control.*?>
|
||||
<?import org.controlsfx.control.*?>
|
||||
|
||||
<fx:root prefHeight="-1.0" prefWidth="-1.0" type="javafx.scene.layout.BorderPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<fx:root prefHeight="-1.0" prefWidth="-1.0" type="javafx.scene.layout.BorderPane" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<top>
|
||||
<ToolBar fx:id="toolBar" prefWidth="200.0" BorderPane.alignment="CENTER">
|
||||
<items>
|
||||
@ -115,7 +115,7 @@
|
||||
</Separator>
|
||||
<HBox>
|
||||
<children>
|
||||
<Button fx:id="zoomOutButton" mnemonicParsing="false">
|
||||
<Button fx:id="zoomOutButton" contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false">
|
||||
<graphic>
|
||||
<ImageView pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
@ -127,7 +127,7 @@
|
||||
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
|
||||
</HBox.margin>
|
||||
</Button>
|
||||
<Button fx:id="zoomInButton" mnemonicParsing="false">
|
||||
<Button fx:id="zoomInButton" contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false">
|
||||
<graphic>
|
||||
<ImageView pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
@ -143,7 +143,7 @@
|
||||
</HBox>
|
||||
<MenuButton fx:id="zoomMenuButton" mnemonicParsing="false">
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<ImageView pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../images/magnifier-left.png" />
|
||||
</image>
|
||||
|
@ -33,8 +33,6 @@ import javafx.beans.value.ObservableValue;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.SnapshotParameters;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuButton;
|
||||
@ -48,7 +46,6 @@ import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.effect.Lighting;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
@ -66,6 +63,7 @@ import jfxtras.scene.control.LocalDateTimeTextField;
|
||||
import org.controlsfx.control.NotificationPane;
|
||||
import org.controlsfx.control.RangeSlider;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.controlsfx.control.action.ActionUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.joda.time.Interval;
|
||||
@ -74,11 +72,12 @@ 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.TimeLineView;
|
||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
|
||||
import org.sleuthkit.autopsy.timeline.actions.SaveSnapshot;
|
||||
import org.sleuthkit.autopsy.timeline.actions.SaveSnapshotAsReport;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ZoomIn;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ZoomOut;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ZoomToEvents;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.events.TagsUpdatedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.filters.TagsFilter;
|
||||
@ -86,18 +85,18 @@ import static org.sleuthkit.autopsy.timeline.ui.Bundle.VisualizationPanel_refres
|
||||
import static org.sleuthkit.autopsy.timeline.ui.Bundle.VisualizationPanel_tagsAddedOrDeleted;
|
||||
import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.NavPanel;
|
||||
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
|
||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
|
||||
/**
|
||||
* A Container for an {@link AbstractVisualization}, has a toolbar on top to
|
||||
* hold settings widgets supplied by contained {@link AbstractVisualization},
|
||||
* A container for an {@link AbstractVisualizationPane}, has a toolbar on top to
|
||||
* hold settings widgets supplied by contained {@link AbstAbstractVisualization}
|
||||
* and the histogram / timeselection on bottom. Also supplies containers for
|
||||
* replacement axis to contained {@link AbstractVisualization}
|
||||
* replacement axis to contained {@link AbstractAbstractVisualization}
|
||||
*
|
||||
* TODO: refactor common code out of histogram and CountsView? -jm
|
||||
*/
|
||||
public class VisualizationPanel extends BorderPane implements TimeLineView {
|
||||
final public class VisualizationPanel extends BorderPane {
|
||||
|
||||
private static final Image INFORMATION = new Image("org/sleuthkit/autopsy/timeline/images/information.png", 16, 16, true, true); // NON-NLS
|
||||
private static final Image REFRESH = new Image("org/sleuthkit/autopsy/timeline/images/arrow-circle-double-135.png"); // NON-NLS
|
||||
@ -107,9 +106,9 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
||||
@GuardedBy("this")
|
||||
private LoggedTask<Void> histogramTask;
|
||||
|
||||
private final NavPanel navPanel;
|
||||
private final EventsTree eventsTree;
|
||||
|
||||
private AbstractVisualization<?, ?, ?, ?> visualization;
|
||||
private AbstractVisualizationPane<?, ?, ?, ?> visualization;
|
||||
|
||||
//// range slider and histogram componenets
|
||||
@FXML
|
||||
@ -178,8 +177,8 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
||||
|
||||
private FilteredEventsModel filteredEvents;
|
||||
|
||||
private final ChangeListener<Object> rangeSliderListener
|
||||
= (observable1, oldValue, newValue) -> {
|
||||
private final ChangeListener<Object> rangeSliderListener =
|
||||
(observable1, oldValue, newValue) -> {
|
||||
if (rangeSlider.isHighValueChanging() == false && rangeSlider.isLowValueChanging() == false) {
|
||||
Long minTime = filteredEvents.getMinTime() * 1000;
|
||||
controller.pushTimeRange(new Interval(
|
||||
@ -207,14 +206,15 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
||||
|
||||
static private final Lighting lighting = new Lighting();
|
||||
|
||||
public VisualizationPanel(NavPanel navPanel) {
|
||||
this.navPanel = navPanel;
|
||||
public VisualizationPanel(TimeLineController controller, EventsTree eventsTree) {
|
||||
this.controller = controller;
|
||||
this.eventsTree = eventsTree;
|
||||
FXMLConstructor.construct(this, "VisualizationPanel.fxml"); // NON-NLS
|
||||
}
|
||||
|
||||
@FXML // This method is called by the FXMLLoader when initialization is complete
|
||||
@NbBundle.Messages("VisualizationPanel.refresh=refresh")
|
||||
protected void initialize() {
|
||||
void initialize() {
|
||||
assert endPicker != null : "fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
|
||||
assert histogramBox != null : "fx:id=\"histogramBox\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
|
||||
assert startPicker != null : "fx:id=\"startPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
|
||||
@ -289,32 +289,30 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
||||
}
|
||||
zoomMenuButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomMenuButton.text")); // NON-NLS
|
||||
|
||||
zoomOutButton.setOnAction(e -> {
|
||||
controller.pushZoomOutTime();
|
||||
});
|
||||
zoomInButton.setOnAction(e -> {
|
||||
controller.pushZoomInTime();
|
||||
});
|
||||
|
||||
snapShotButton.setOnAction((ActionEvent event) -> {
|
||||
//take snapshot
|
||||
final SnapshotParameters snapshotParameters = new SnapshotParameters();
|
||||
snapshotParameters.setViewport(new Rectangle2D(visualization.getBoundsInParent().getMinX(), visualization.getBoundsInParent().getMinY(),
|
||||
visualization.getBoundsInParent().getWidth(),
|
||||
contextPane.getLayoutBounds().getHeight() + visualization.getLayoutBounds().getHeight() + partPane.getLayoutBounds().getHeight()
|
||||
));
|
||||
WritableImage snapshot = this.snapshot(snapshotParameters, null);
|
||||
//pass snapshot to save action
|
||||
new SaveSnapshot(controller, snapshot).handle(event);
|
||||
});
|
||||
snapShotButton.setOnAction(event ->
|
||||
this.snapshot(snapShotResult -> {
|
||||
new SaveSnapshotAsReport(controller, snapShotResult.getImage()).handle(event);
|
||||
return null;
|
||||
}, null, null)
|
||||
);
|
||||
|
||||
snapShotButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapShotButton.text")); // NON-NLS
|
||||
}
|
||||
if (this.filteredEvents != null && this.filteredEvents != controller.getEventsModel()) {
|
||||
this.filteredEvents.unRegisterForEvents(this);
|
||||
this.filteredEvents.timeRangeProperty().removeListener(timeRangeInvalidationListener);
|
||||
this.filteredEvents.zoomParametersProperty().removeListener(zoomListener);
|
||||
}
|
||||
if (this.filteredEvents != controller.getEventsModel()) {
|
||||
controller.getEventsModel().registerForEvents(this);
|
||||
controller.getEventsModel().timeRangeProperty().addListener(timeRangeInvalidationListener);
|
||||
controller.getEventsModel().zoomParametersProperty().addListener(zoomListener);
|
||||
}
|
||||
|
||||
this.filteredEvents = controller.getEventsModel();
|
||||
refreshTimeUI(controller.getEventsModel().timeRangeProperty().get());
|
||||
ActionUtils.configureButton(new ZoomOut(controller), zoomOutButton);
|
||||
ActionUtils.configureButton(new ZoomIn(controller), zoomInButton);
|
||||
|
||||
@Override
|
||||
public synchronized void setController(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
setModel(controller.getEventsModel());
|
||||
setViewMode(controller.viewModeProperty().get());
|
||||
controller.getNeedsHistogramRebuild().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
|
||||
if (newValue) {
|
||||
@ -329,39 +327,20 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
||||
refreshHistorgram();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModel(FilteredEventsModel filteredEvents) {
|
||||
if (this.filteredEvents != null && this.filteredEvents != filteredEvents) {
|
||||
this.filteredEvents.unRegisterForEvents(this);
|
||||
this.filteredEvents.timeRangeProperty().removeListener(timeRangeInvalidationListener);
|
||||
this.filteredEvents.zoomParametersProperty().removeListener(zoomListener);
|
||||
}
|
||||
if (this.filteredEvents != filteredEvents) {
|
||||
filteredEvents.registerForEvents(this);
|
||||
filteredEvents.timeRangeProperty().addListener(timeRangeInvalidationListener);
|
||||
filteredEvents.zoomParametersProperty().addListener(zoomListener);
|
||||
}
|
||||
|
||||
this.filteredEvents = filteredEvents;
|
||||
|
||||
refreshTimeUI(filteredEvents.timeRangeProperty().get());
|
||||
|
||||
}
|
||||
|
||||
private void setViewMode(VisualizationMode visualizationMode) {
|
||||
switch (visualizationMode) {
|
||||
case COUNTS:
|
||||
setVisualization(new CountsViewPane(partPane, contextPane, spacer));
|
||||
setVisualization(new CountsViewPane(controller, partPane, contextPane, spacer));
|
||||
countsToggle.setSelected(true);
|
||||
break;
|
||||
case DETAIL:
|
||||
setVisualization(new DetailViewPane(partPane, contextPane, spacer));
|
||||
setVisualization(new DetailViewPane(controller, partPane, contextPane, spacer));
|
||||
detailsToggle.setSelected(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void setVisualization(final AbstractVisualization<?, ?, ?, ?> newViz) {
|
||||
private synchronized void setVisualization(final AbstractVisualizationPane<?, ?, ?, ?> newViz) {
|
||||
Platform.runLater(() -> {
|
||||
synchronized (VisualizationPanel.this) {
|
||||
if (visualization != null) {
|
||||
@ -370,24 +349,25 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
||||
}
|
||||
|
||||
visualization = newViz;
|
||||
visualization.update();
|
||||
toolBar.getItems().addAll(newViz.getSettingsNodes());
|
||||
|
||||
visualization.setController(controller);
|
||||
notificationPane.setContent(visualization);
|
||||
if (visualization instanceof DetailViewPane) {
|
||||
navPanel.setDetailViewPane((DetailViewPane) visualization);
|
||||
eventsTree.setDetailViewPane((DetailViewPane) visualization);
|
||||
}
|
||||
visualization.hasEvents.addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
|
||||
visualization.hasEvents.addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue == false) {
|
||||
|
||||
notificationPane.setContent(new StackPane(visualization, new Region() {
|
||||
{
|
||||
setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
setOpacity(.3);
|
||||
}
|
||||
}, new NoEventsDialog(() -> {
|
||||
notificationPane.setContent(visualization);
|
||||
})));
|
||||
notificationPane.setContent(
|
||||
new StackPane(visualization,
|
||||
new Region() {
|
||||
{
|
||||
setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
setOpacity(.3);
|
||||
}
|
||||
},
|
||||
new NoEventsDialog(() -> notificationPane.setContent(visualization))));
|
||||
} else {
|
||||
notificationPane.setContent(visualization);
|
||||
}
|
||||
@ -552,7 +532,6 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
||||
private NoEventsDialog(Runnable closeCallback) {
|
||||
this.closeCallback = closeCallback;
|
||||
FXMLConstructor.construct(this, "NoEventsDialog.fxml"); // NON-NLS
|
||||
|
||||
}
|
||||
|
||||
@FXML
|
||||
@ -562,15 +541,9 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
||||
assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
|
||||
|
||||
noEventsDialogLabel.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.noEventsDialogLabel.text")); // NON-NLS
|
||||
zoomButton.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.zoomButton.text")); // NON-NLS
|
||||
ActionUtils.configureButton(new ZoomToEvents(controller), zoomButton);
|
||||
|
||||
Action zoomOutAction = new ZoomOut(controller);
|
||||
zoomButton.setOnAction(zoomOutAction);
|
||||
zoomButton.disableProperty().bind(zoomOutAction.disabledProperty());
|
||||
|
||||
dismissButton.setOnAction(e -> {
|
||||
closeCallback.run();
|
||||
});
|
||||
dismissButton.setOnAction(actionEvent -> closeCallback.run());
|
||||
Action defaultFiltersAction = new ResetFilters(controller);
|
||||
resetFiltersButton.setOnAction(defaultFiltersAction);
|
||||
resetFiltersButton.disableProperty().bind(defaultFiltersAction.disabledProperty());
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013 Basis Technology Corp.
|
||||
* Copyright 2013-15 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -26,14 +26,12 @@ Timeline.ui.countsview.menuItem.selectTimeRange=Select Time Range
|
||||
Timeline.ui.countsview.menuItem.selectEventType=Select Event Type
|
||||
Timeline.ui.countsview.menuItem.selectTimeandType=Select Time and Type
|
||||
Timeline.ui.countsview.menuItem.zoomIntoTimeRange=Zoom into Time Range
|
||||
Timeline.ui.countsview.contextMenu.ActionGroup.zoomHistory.title=Zoom History
|
||||
CountsViewPane.loggedTask.name=Updating Counts Graph
|
||||
CountsViewPane.loggedTask.prepUpdate=preparing update
|
||||
CountsViewPane.loggedTask.resetUI=resetting ui
|
||||
CountsViewPane.tooltip.text={0} {1} events\nbetween {2}\nand {3}
|
||||
CountsViewPane.loggedTask.updatingCounts=updating counts
|
||||
CountsViewPane.loggedTask.wrappingUp=wrapping up
|
||||
EventCountsChart.contextMenu.zoomHistory.name=Zoom History
|
||||
CountsViewPane.scaleLabel.text=Scale\:
|
||||
CountsViewPane.logRadio.text=Logarithmic
|
||||
CountsViewPane.linearRadio.text=Linear
|
||||
|
@ -66,14 +66,13 @@ 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.TimeLineView;
|
||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
|
||||
/**
|
||||
@ -97,7 +96,7 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
* TODO: refactor common code out of this class and ClusterChartPane into
|
||||
* AbstractChartView
|
||||
*/
|
||||
public class CountsViewPane extends AbstractVisualization<String, Number, Node, EventCountsChart> {
|
||||
public class CountsViewPane extends AbstractVisualizationPane<String, Number, Node, EventCountsChart> {
|
||||
|
||||
private static final Effect SELECTED_NODE_EFFECT = new Lighting();
|
||||
|
||||
@ -213,17 +212,17 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
|
||||
node.setStyle("-fx-border-width: 2; -fx-border-color: " + ColorUtilities.getRGBCode(et.getSuperType().getColor()) + "; -fx-bar-fill: " + ColorUtilities.getRGBCode(et.getColor())); // NON-NLS
|
||||
node.setCursor(Cursor.HAND);
|
||||
|
||||
final Tooltip tooltip = new Tooltip(
|
||||
NbBundle.getMessage(this.getClass(), "CountsViewPane.tooltip.text",
|
||||
count,
|
||||
et.getDisplayName(),
|
||||
dateString,
|
||||
interval.getEnd().toString(
|
||||
rangeInfo.getTickFormatter())));
|
||||
tooltip.setGraphic(new ImageView(et.getFXImage()));
|
||||
Tooltip.install(node, tooltip);
|
||||
|
||||
node.setOnMouseEntered((MouseEvent event) -> {
|
||||
//defer tooltip creation till needed, this had a surprisingly large impact on speed of loading the chart
|
||||
final Tooltip tooltip = new Tooltip(
|
||||
NbBundle.getMessage(this.getClass(), "CountsViewPane.tooltip.text",
|
||||
count,
|
||||
et.getDisplayName(),
|
||||
dateString,
|
||||
interval.getEnd().toString(
|
||||
rangeInfo.getTickFormatter())));
|
||||
tooltip.setGraphic(new ImageView(et.getFXImage()));
|
||||
Tooltip.install(node, tooltip);
|
||||
node.setEffect(new DropShadow(10, et.getColor()));
|
||||
});
|
||||
node.setOnMouseExited((MouseEvent event) -> {
|
||||
@ -279,13 +278,15 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
|
||||
};
|
||||
}
|
||||
|
||||
public CountsViewPane(Pane partPane, Pane contextPane, Region spacer) {
|
||||
super(partPane, contextPane, spacer);
|
||||
chart = new EventCountsChart(dateAxis, countAxis);
|
||||
public CountsViewPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) {
|
||||
super(controller, partPane, contextPane, spacer);
|
||||
chart = new EventCountsChart(controller, dateAxis, countAxis);
|
||||
setChartClickHandler();
|
||||
chart.setData(dataSets);
|
||||
setCenter(chart);
|
||||
|
||||
Tooltip.install(chart, getDragTooltip());
|
||||
|
||||
settingsNodes = new ArrayList<>(new CountsViewSettingsPane().getChildrenUnmodifiable());
|
||||
|
||||
dateAxis.getTickMarks().addListener((Observable observable) -> {
|
||||
@ -310,9 +311,6 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected NumberAxis getYAxis() {
|
||||
return countAxis;
|
||||
@ -534,7 +532,7 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
|
||||
scaleLabel.setText(NbBundle.getMessage(this.getClass(), "CountsViewPane.scaleLabel.text"));
|
||||
}
|
||||
|
||||
public CountsViewSettingsPane() {
|
||||
CountsViewSettingsPane() {
|
||||
FXMLConstructor.construct(this, "CountsViewSettingsPane.fxml"); // NON-NLS
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014 Basis Technology Corp.
|
||||
* Copyright 2014-15 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -19,24 +19,19 @@
|
||||
package org.sleuthkit.autopsy.timeline.ui.countsview;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import javafx.scene.chart.Axis;
|
||||
import java.util.MissingResourceException;
|
||||
import javafx.scene.chart.CategoryAxis;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.StackedBarChart;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.util.StringConverter;
|
||||
import org.controlsfx.control.action.ActionGroup;
|
||||
import org.controlsfx.control.action.ActionUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Interval;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.ui.IntervalSelector;
|
||||
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
|
||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
|
||||
@ -44,11 +39,16 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
* Customized {@link StackedBarChart<String, Number>} used to display the event
|
||||
* counts in {@link CountsViewPane}
|
||||
*/
|
||||
class EventCountsChart extends StackedBarChart<String, Number> implements TimeLineChart<String> {
|
||||
final class EventCountsChart extends StackedBarChart<String, Number> implements TimeLineChart<String> {
|
||||
|
||||
private ContextMenu contextMenu;
|
||||
private ContextMenu chartContextMenu;
|
||||
|
||||
private TimeLineController controller;
|
||||
@Override
|
||||
public ContextMenu getChartContextMenu() {
|
||||
return chartContextMenu;
|
||||
}
|
||||
|
||||
private final TimeLineController controller;
|
||||
|
||||
private IntervalSelector<? extends String> intervalSelector;
|
||||
|
||||
@ -59,8 +59,9 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
||||
*/
|
||||
private RangeDivisionInfo rangeInfo;
|
||||
|
||||
EventCountsChart(CategoryAxis dateAxis, NumberAxis countAxis) {
|
||||
EventCountsChart(TimeLineController controller, CategoryAxis dateAxis, NumberAxis countAxis) {
|
||||
super(dateAxis, countAxis);
|
||||
this.controller = controller;
|
||||
//configure constant properties on axes and chart
|
||||
dateAxis.setAnimated(true);
|
||||
dateAxis.setLabel(null);
|
||||
@ -80,19 +81,13 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
||||
setAnimated(true);
|
||||
setTitle(null);
|
||||
|
||||
//use one handler with an if chain because it maintains state
|
||||
ChartDragHandler<String, EventCountsChart> dragHandler = new ChartDragHandler<>(this, getXAxis());
|
||||
setOnMousePressed(dragHandler);
|
||||
setOnMouseReleased(dragHandler);
|
||||
setOnMouseDragged(dragHandler);
|
||||
ChartDragHandler<String, EventCountsChart> chartDragHandler = new ChartDragHandler<>(this);
|
||||
setOnMousePressed(chartDragHandler);
|
||||
setOnMouseReleased(chartDragHandler);
|
||||
setOnMouseDragged(chartDragHandler);
|
||||
|
||||
setOnMouseClicked(new MouseClickedHandler<>(this));
|
||||
|
||||
setOnMouseClicked((MouseEvent clickEvent) -> {
|
||||
contextMenu.hide();
|
||||
if (clickEvent.getButton() == MouseButton.SECONDARY && clickEvent.isStillSincePress()) {
|
||||
contextMenu.show(EventCountsChart.this, clickEvent.getScreenX(), clickEvent.getScreenY());
|
||||
clickEvent.consume();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -102,16 +97,21 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
||||
}
|
||||
|
||||
@Override
|
||||
public final synchronized void setController(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
setModel(this.controller.getEventsModel());
|
||||
//we have defered creating context menu until control is available
|
||||
contextMenu = ActionUtils.createContextMenu(
|
||||
Arrays.asList(new ActionGroup(
|
||||
NbBundle.getMessage(this.getClass(), "EventCountsChart.contextMenu.zoomHistory.name"),
|
||||
new Back(controller),
|
||||
new Forward(controller))));
|
||||
contextMenu.setAutoHide(true);
|
||||
public ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException {
|
||||
if (chartContextMenu != null) {
|
||||
chartContextMenu.hide();
|
||||
}
|
||||
|
||||
chartContextMenu = ActionUtils.createContextMenu(
|
||||
Arrays.asList(
|
||||
TimeLineChart.newZoomHistoyActionGroup(controller)));
|
||||
chartContextMenu.setAutoHide(true);
|
||||
return chartContextMenu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeLineController getController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -126,16 +126,8 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModel(FilteredEventsModel filteredEvents) {
|
||||
filteredEvents.zoomParametersProperty().addListener(o -> {
|
||||
clearIntervalSelector();
|
||||
controller.selectEventIDs(Collections.emptyList());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CountsIntervalSelector newIntervalSelector(double x, Axis<String> dateAxis) {
|
||||
return new CountsIntervalSelector(x, getHeight() - dateAxis.getHeight() - dateAxis.getTickLength(), dateAxis, controller);
|
||||
public CountsIntervalSelector newIntervalSelector() {
|
||||
return new CountsIntervalSelector(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -145,7 +137,7 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
||||
* @return the context menu for this chart
|
||||
*/
|
||||
ContextMenu getContextMenu() {
|
||||
return contextMenu;
|
||||
return chartContextMenu;
|
||||
}
|
||||
|
||||
void setRangeInfo(RangeDivisionInfo rangeInfo) {
|
||||
@ -175,10 +167,13 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
||||
* Interval Selector for the counts chart, adjusts interval based on
|
||||
* rangeInfo to include final period
|
||||
*/
|
||||
private class CountsIntervalSelector extends IntervalSelector<String> {
|
||||
final static private class CountsIntervalSelector extends IntervalSelector<String> {
|
||||
|
||||
public CountsIntervalSelector(double x, double height, Axis<String> axis, TimeLineController controller) {
|
||||
super(x, height, axis, controller);
|
||||
private final EventCountsChart countsChart;
|
||||
|
||||
CountsIntervalSelector(EventCountsChart chart) {
|
||||
super(chart);
|
||||
this.countsChart = chart;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -195,12 +190,13 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
||||
final DateTime lowerDate = new DateTime(lowerBound, TimeLineController.getJodaTimeZone());
|
||||
final DateTime upperDate = new DateTime(upperBound, TimeLineController.getJodaTimeZone());
|
||||
//add extra block to end that gets cut of by conversion from string/category.
|
||||
return new Interval(lowerDate, upperDate.plus(rangeInfo.getPeriodSize().getPeriod()));
|
||||
return new Interval(lowerDate, upperDate.plus(countsChart.rangeInfo.getPeriodSize().getPeriod()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DateTime parseDateTime(String date) {
|
||||
return date == null ? new DateTime(rangeInfo.getLowerBound()) : rangeInfo.getTickFormatter().parseDateTime(date);
|
||||
return date == null ? new DateTime(countsChart.rangeInfo.getLowerBound()) : countsChart.rangeInfo.getTickFormatter().parseDateTime(date);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -73,25 +73,24 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
||||
|
||||
/**
|
||||
* Controller class for a {@link EventDetailChart} based implementation of a
|
||||
* Controller class for a {@link EventDetailsChart} 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
|
||||
* updates the internal {@link EventDetailsChart} to reflect the currently
|
||||
* requested events.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster, EventBundleNodeBase<?, ?, ?>, EventDetailChart> {
|
||||
public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventCluster, EventBundleNodeBase<?, ?, ?>, EventDetailsChart> {
|
||||
|
||||
|
||||
private final static Logger LOGGER = Logger.getLogger(DetailViewPane.class.getName());
|
||||
|
||||
private MultipleSelectionModel<TreeItem<EventBundle<?>>> treeSelectionModel;
|
||||
@ -114,13 +113,15 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
|
||||
return chart.getEventBundles();
|
||||
}
|
||||
|
||||
public DetailViewPane(Pane partPane, Pane contextPane, Region spacer) {
|
||||
super(partPane, contextPane, spacer);
|
||||
chart = new EventDetailChart(dateAxis, verticalAxis, selectedNodes);
|
||||
|
||||
public DetailViewPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) {
|
||||
super(controller, partPane, contextPane, spacer);
|
||||
chart = new EventDetailsChart(controller, dateAxis, verticalAxis, selectedNodes);
|
||||
setChartClickHandler();
|
||||
chart.setData(dataSets);
|
||||
setCenter(chart);
|
||||
|
||||
|
||||
chart.setPrefHeight(USE_COMPUTED_SIZE);
|
||||
|
||||
settingsNodes = new ArrayList<>(new DetailViewSettingsPane().getChildrenUnmodifiable());
|
||||
@ -211,11 +212,6 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setModel(FilteredEventsModel filteredEvents) {
|
||||
super.setModel(filteredEvents);
|
||||
}
|
||||
|
||||
private void incrementScrollValue(int factor) {
|
||||
vertScrollBar.valueProperty().set(Math.max(0, Math.min(100, vertScrollBar.getValue() + factor * (chart.getHeight() / chart.maxVScrollProperty().get()))));
|
||||
}
|
||||
@ -477,5 +473,4 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
|
||||
return chart.new HideDescriptionAction(description, descriptionLoD);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||
import static org.sleuthkit.autopsy.timeline.ui.detailview.EventBundleNodeBase.show;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
@ -96,7 +97,7 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
||||
b.setManaged(show);
|
||||
}
|
||||
|
||||
protected final EventDetailChart chart;
|
||||
protected final EventDetailsChart chart;
|
||||
final SimpleObjectProperty<DescriptionLoD> descLOD = new SimpleObjectProperty<>();
|
||||
final SimpleObjectProperty<DescriptionVisibility> descVisibility = new SimpleObjectProperty<>(DescriptionVisibility.SHOWN);
|
||||
protected final BundleType eventBundle;
|
||||
@ -119,9 +120,9 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
||||
final ImageView tagIV = new ImageView(TAG);
|
||||
final HBox infoHBox = new HBox(5, descrLabel, countLabel, hashIV, tagIV);
|
||||
|
||||
private Tooltip tooltip;
|
||||
private final Tooltip tooltip = new Tooltip("loading...");
|
||||
|
||||
public EventBundleNodeBase(EventDetailChart chart, BundleType eventBundle, ParentNodeType parentNode) {
|
||||
public EventBundleNodeBase(EventDetailsChart chart, BundleType eventBundle, ParentNodeType parentNode) {
|
||||
this.eventBundle = eventBundle;
|
||||
this.parentNode = parentNode;
|
||||
this.chart = chart;
|
||||
@ -156,7 +157,6 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
||||
infoHBox.setMaxWidth(USE_PREF_SIZE);
|
||||
infoHBox.setPadding(new Insets(2, 5, 2, 5));
|
||||
infoHBox.setAlignment(Pos.TOP_LEFT);
|
||||
infoHBox.setPickOnBounds(true);
|
||||
|
||||
//set up subnode pane sizing contraints
|
||||
subNodePane.setPrefHeight(USE_COMPUTED_SIZE);
|
||||
@ -165,27 +165,34 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
||||
subNodePane.setMinWidth(USE_PREF_SIZE);
|
||||
subNodePane.setMaxWidth(USE_PREF_SIZE);
|
||||
|
||||
Tooltip.install(this, this.tooltip);
|
||||
|
||||
//set up mouse hover effect and tooltip
|
||||
setOnMouseEntered((MouseEvent e) -> {
|
||||
/*
|
||||
* defer tooltip creation till needed, this had a surprisingly large
|
||||
* impact on speed of loading the chart
|
||||
* defer tooltip content creation till needed, this had a
|
||||
* surprisingly large impact on speed of loading the chart
|
||||
*/
|
||||
installTooltip();
|
||||
Tooltip.uninstall(chart, AbstractVisualizationPane.getDragTooltip());
|
||||
showHoverControls(true);
|
||||
toFront();
|
||||
|
||||
});
|
||||
setOnMouseExited((MouseEvent event) -> {
|
||||
showHoverControls(false);
|
||||
if (parentNode != null) {
|
||||
parentNode.showHoverControls(true);
|
||||
} else {
|
||||
Tooltip.install(chart, AbstractVisualizationPane.getDragTooltip());
|
||||
}
|
||||
});
|
||||
|
||||
setDescriptionVisibility(DescriptionVisibility.SHOWN);
|
||||
descVisibility.addListener((ObservableValue<? extends DescriptionVisibility> observable, DescriptionVisibility oldValue, DescriptionVisibility newValue) -> {
|
||||
setDescriptionVisibility(newValue);
|
||||
setDescriptionVisibiltiyImpl(newValue);
|
||||
});
|
||||
setDescriptionVisibiltiyImpl(DescriptionVisibility.SHOWN);
|
||||
|
||||
}
|
||||
|
||||
final DescriptionLoD getDescriptionLoD() {
|
||||
@ -210,8 +217,11 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
||||
"EventBundleNodeBase.tooltip.text={0} {1} events\n{2}\nbetween\t{3}\nand \t{4}"})
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
private void installTooltip() {
|
||||
if (tooltip == null) {
|
||||
if (tooltip.getText().equalsIgnoreCase("loading...")) {
|
||||
final Task<String> tooltTipTask = new Task<String>() {
|
||||
{
|
||||
updateTitle("loading tooltip");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String call() throws Exception {
|
||||
@ -252,13 +262,10 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
||||
protected void succeeded() {
|
||||
super.succeeded();
|
||||
try {
|
||||
tooltip = new Tooltip(get());
|
||||
tooltip.setAutoHide(true);
|
||||
Tooltip.install(EventBundleNodeBase.this, tooltip);
|
||||
tooltip.setText(get());
|
||||
tooltip.setGraphic(null);
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Tooltip generation failed.", ex);
|
||||
Tooltip.uninstall(EventBundleNodeBase.this, tooltip);
|
||||
tooltip = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -288,15 +295,18 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
||||
return subNodes;
|
||||
}
|
||||
|
||||
abstract void setDescriptionVisibility(DescriptionVisibility get);
|
||||
abstract void setDescriptionVisibiltiyImpl(DescriptionVisibility get);
|
||||
|
||||
void showHoverControls(final boolean showControls) {
|
||||
Effect dropShadow = dropShadowMap.computeIfAbsent(getEventType(),
|
||||
eventType -> new DropShadow(-10, eventType.getColor()));
|
||||
setEffect(showControls ? dropShadow : null);
|
||||
enableTooltip(showControls);
|
||||
if (parentNode != null) {
|
||||
parentNode.enableTooltip(false);
|
||||
parentNode.showHoverControls(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
final EventType getEventType() {
|
||||
@ -330,8 +340,15 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
||||
*/
|
||||
abstract void setDescriptionWidth(double w);
|
||||
|
||||
void setDescriptionVisibilityLevel(DescriptionVisibility get) {
|
||||
void setDescriptionVisibility(DescriptionVisibility get) {
|
||||
descVisibility.set(get);
|
||||
}
|
||||
|
||||
void enableTooltip(boolean toolTipEnabled) {
|
||||
if (toolTipEnabled) {
|
||||
Tooltip.install(this, tooltip);
|
||||
} else {
|
||||
Tooltip.uninstall(this, tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
|
||||
final Button plusButton = ActionUtils.createButton(new ExpandClusterAction(), ActionUtils.ActionTextBehavior.HIDE);
|
||||
final Button minusButton = ActionUtils.createButton(new CollapseClusterAction(), ActionUtils.ActionTextBehavior.HIDE);
|
||||
|
||||
public EventClusterNode(EventDetailChart chart, EventCluster eventCluster, EventStripeNode parentNode) {
|
||||
public EventClusterNode(EventDetailsChart chart, EventCluster eventCluster, EventStripeNode parentNode) {
|
||||
super(chart, eventCluster, parentNode);
|
||||
setMinHeight(24);
|
||||
|
||||
@ -114,7 +114,7 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescriptionVisibility(DescriptionVisibility descrVis) {
|
||||
void setDescriptionVisibiltiyImpl(DescriptionVisibility descrVis) {
|
||||
final int size = getEventBundle().getEventIDs().size();
|
||||
switch (descrVis) {
|
||||
case HIDDEN:
|
||||
|
@ -53,6 +53,7 @@ import javafx.scene.chart.Axis;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseButton;
|
||||
@ -62,14 +63,11 @@ import javafx.scene.shape.StrokeLineCap;
|
||||
import javafx.util.Duration;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.controlsfx.control.action.ActionGroup;
|
||||
import org.controlsfx.control.action.ActionUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Interval;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
|
||||
@ -77,6 +75,8 @@ import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.IntervalSelector;
|
||||
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
||||
|
||||
@ -94,7 +94,7 @@ import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
||||
*
|
||||
* //TODO: refactor the projected lines to a separate class. -jm
|
||||
*/
|
||||
public final class EventDetailChart extends XYChart<DateTime, EventCluster> implements TimeLineChart<DateTime> {
|
||||
public final class EventDetailsChart extends XYChart<DateTime, EventCluster> implements TimeLineChart<DateTime> {
|
||||
|
||||
private static final Image HIDE = new Image("/org/sleuthkit/autopsy/timeline/images/eye--minus.png"); // NON-NLS
|
||||
private static final Image SHOW = new Image("/org/sleuthkit/autopsy/timeline/images/eye--plus.png"); // NON-NLS
|
||||
@ -102,11 +102,18 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
private static final int PROJECTED_LINE_Y_OFFSET = 5;
|
||||
private static final int PROJECTED_LINE_STROKE_WIDTH = 5;
|
||||
private static final int MINIMUM_EVENT_NODE_GAP = 4;
|
||||
|
||||
|
||||
private final TimeLineController controller;
|
||||
private final FilteredEventsModel filteredEvents;
|
||||
|
||||
private ContextMenu chartContextMenu;
|
||||
|
||||
private TimeLineController controller;
|
||||
|
||||
private FilteredEventsModel filteredEvents;
|
||||
|
||||
public ContextMenu getChartContextMenu() {
|
||||
return chartContextMenu;
|
||||
}
|
||||
|
||||
/**
|
||||
* a user positionable vertical line to help compare events
|
||||
@ -183,8 +190,21 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
*/
|
||||
final SimpleDoubleProperty truncateWidth = new SimpleDoubleProperty(200.0);
|
||||
|
||||
EventDetailChart(DateAxis dateAxis, final Axis<EventCluster> verticalAxis, ObservableList<EventBundleNodeBase<?, ?, ?>> selectedNodes) {
|
||||
EventDetailsChart(TimeLineController controller, DateAxis dateAxis, final Axis<EventCluster> verticalAxis, ObservableList<EventBundleNodeBase<?, ?, ?>> selectedNodes) {
|
||||
super(dateAxis, verticalAxis);
|
||||
this.controller = controller;
|
||||
this.filteredEvents = this.controller.getEventsModel();
|
||||
|
||||
filteredEvents.zoomParametersProperty().addListener(o -> {
|
||||
clearGuideLine();
|
||||
clearIntervalSelector();
|
||||
selectedNodes.clear();
|
||||
projectionMap.clear();
|
||||
controller.selectEventIDs(Collections.emptyList());
|
||||
});
|
||||
Tooltip.install(this, AbstractVisualizationPane.getDragTooltip());
|
||||
|
||||
|
||||
dateAxis.setAutoRanging(false);
|
||||
|
||||
verticalAxis.setVisible(false);//TODO: why doesn't this hide the vertical axis, instead we have to turn off all parts individually? -jm
|
||||
@ -204,6 +224,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
truncateAll.addListener(layoutInvalidationListener);
|
||||
truncateWidth.addListener(layoutInvalidationListener);
|
||||
descrVisibility.addListener(layoutInvalidationListener);
|
||||
getController().getQuickHideFilters().addListener(layoutInvalidationListener);
|
||||
|
||||
//this is needed to allow non circular binding of the guideline and timerangeRect heights to the height of the chart
|
||||
//TODO: seems like a hack, can we remove? -jm
|
||||
@ -211,22 +232,12 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
setPrefHeight(boundsInLocalProperty().get().getHeight());
|
||||
});
|
||||
|
||||
///////set up mouse listeners
|
||||
setOnMouseClicked((MouseEvent clickEvent) -> {
|
||||
if (chartContextMenu != null) {
|
||||
chartContextMenu.hide();
|
||||
}
|
||||
if (clickEvent.getButton() == MouseButton.SECONDARY && clickEvent.isStillSincePress()) {
|
||||
getChartContextMenu(clickEvent);
|
||||
chartContextMenu.show(EventDetailChart.this, clickEvent.getScreenX(), clickEvent.getScreenY());
|
||||
clickEvent.consume();
|
||||
}
|
||||
});
|
||||
//use one handler with an if chain because it maintains state
|
||||
final ChartDragHandler<DateTime, EventDetailChart> dragHandler = new ChartDragHandler<>(this, getXAxis());
|
||||
setOnMousePressed(dragHandler);
|
||||
setOnMouseReleased(dragHandler);
|
||||
setOnMouseDragged(dragHandler);
|
||||
ChartDragHandler<DateTime, EventDetailsChart> chartDragHandler = new ChartDragHandler<>(this);
|
||||
setOnMousePressed(chartDragHandler);
|
||||
setOnMouseReleased(chartDragHandler);
|
||||
setOnMouseDragged(chartDragHandler);
|
||||
|
||||
setOnMouseClicked(new MouseClickedHandler<>(this));
|
||||
|
||||
this.selectedNodes = selectedNodes;
|
||||
this.selectedNodes.addListener(new SelectionChangeHandler());
|
||||
@ -236,21 +247,20 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
return bundles;
|
||||
}
|
||||
|
||||
TimeLineController getController() {
|
||||
@Override
|
||||
public TimeLineController getController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
@NbBundle.Messages({"EventDetailChart.chartContextMenu.placeMarker.name=Place Marker",
|
||||
"EventDetailChart.contextMenu.zoomHistory.name=Zoom History"})
|
||||
ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException {
|
||||
@Override
|
||||
public ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException {
|
||||
if (chartContextMenu != null) {
|
||||
chartContextMenu.hide();
|
||||
}
|
||||
|
||||
chartContextMenu = ActionUtils.createContextMenu(Arrays.asList(new PlaceMarkerAction(clickEvent),
|
||||
new ActionGroup(Bundle.EventDetailChart_contextMenu_zoomHistory_name(),
|
||||
new Back(controller),
|
||||
new Forward(controller))));
|
||||
// new StartIntervalSelectionAction(clickEvent, dragHandler),
|
||||
TimeLineChart.newZoomHistoyActionGroup(controller)));
|
||||
chartContextMenu.setAutoHide(true);
|
||||
return chartContextMenu;
|
||||
}
|
||||
@ -266,32 +276,8 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setController(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
setModel(this.controller.getEventsModel());
|
||||
getController().getQuickHideFilters().addListener(layoutInvalidationListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModel(FilteredEventsModel filteredEvents) {
|
||||
|
||||
if (this.filteredEvents != filteredEvents) {
|
||||
filteredEvents.zoomParametersProperty().addListener(o -> {
|
||||
clearGuideLine();
|
||||
clearIntervalSelector();
|
||||
|
||||
selectedNodes.clear();
|
||||
projectionMap.clear();
|
||||
controller.selectEventIDs(Collections.emptyList());
|
||||
});
|
||||
}
|
||||
this.filteredEvents = filteredEvents;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntervalSelector<DateTime> newIntervalSelector(double x, Axis<DateTime> axis) {
|
||||
return new DetailIntervalSelector(x, getHeight() - axis.getHeight() - axis.getTickLength(), axis, controller);
|
||||
public IntervalSelector<DateTime> newIntervalSelector() {
|
||||
return new DetailIntervalSelector(this);
|
||||
}
|
||||
|
||||
synchronized void setBandByType(Boolean t1) {
|
||||
@ -300,12 +286,12 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
|
||||
/**
|
||||
* get the DateTime along the x-axis that corresponds to the given
|
||||
* x-coordinate in the coordinate system of this {@link EventDetailChart}
|
||||
* x-coordinate in the coordinate system of this {@link EventDetailsChart}
|
||||
*
|
||||
* @param x a x-coordinate in the space of this {@link EventDetailChart}
|
||||
* @param x a x-coordinate in the space of this {@link EventDetailsChart}
|
||||
*
|
||||
* @return the DateTime along the x-axis corresponding to the given x value
|
||||
* (in the space of this {@link EventDetailChart}
|
||||
* (in the space of this {@link EventDetailsChart}
|
||||
*/
|
||||
public DateTime getDateTimeForPosition(double x) {
|
||||
return getXAxis().getValueForDisplay(getXAxis().parentToLocal(x, 0).getX());
|
||||
@ -352,7 +338,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
return EventStripe.merge(u, v);
|
||||
}
|
||||
);
|
||||
EventStripeNode stripeNode = new EventStripeNode(EventDetailChart.this, eventStripe, null);
|
||||
EventStripeNode stripeNode = new EventStripeNode(EventDetailsChart.this, eventStripe, null);
|
||||
stripeNodeMap.put(eventStripe, stripeNode);
|
||||
nodeGroup.getChildren().add(stripeNode);
|
||||
data.setNode(stripeNode);
|
||||
@ -498,7 +484,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
bundleNode.setVisible(true);
|
||||
bundleNode.setManaged(true);
|
||||
//apply advanced layout description visibility options
|
||||
bundleNode.setDescriptionVisibilityLevel(descrVisibility.get());
|
||||
bundleNode.setDescriptionVisibility(descrVisibility.get());
|
||||
bundleNode.setDescriptionWidth(truncateAll.get() ? truncateWidth.get() : USE_PREF_SIZE);
|
||||
|
||||
//do recursive layout
|
||||
@ -586,8 +572,8 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
|
||||
static private class DetailIntervalSelector extends IntervalSelector<DateTime> {
|
||||
|
||||
DetailIntervalSelector(double x, double height, Axis<DateTime> axis, TimeLineController controller) {
|
||||
super(x, height, axis, controller);
|
||||
DetailIntervalSelector(EventDetailsChart chart) {
|
||||
super(chart);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -608,6 +594,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
|
||||
private class PlaceMarkerAction extends Action {
|
||||
|
||||
@NbBundle.Messages({"EventDetailChart.chartContextMenu.placeMarker.name=Place Marker"})
|
||||
PlaceMarkerAction(MouseEvent clickEvent) {
|
||||
super(Bundle.EventDetailChart_chartContextMenu_placeMarker_name());
|
||||
|
||||
@ -664,7 +651,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
}
|
||||
});
|
||||
}
|
||||
EventDetailChart.this.controller.selectEventIDs(selectedNodes.stream()
|
||||
EventDetailsChart.this.controller.selectEventIDs(selectedNodes.stream()
|
||||
.flatMap(detailNode -> detailNode.getEventIDs().stream())
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
@ -708,4 +695,5 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -37,7 +37,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
|
||||
import static org.sleuthkit.autopsy.timeline.ui.detailview.EventBundleNodeBase.configureLoDButton;
|
||||
|
||||
/**
|
||||
* Node used in {@link EventDetailChart} to represent an EventStripe.
|
||||
* Node used in {@link EventDetailsChart} to represent an EventStripe.
|
||||
*/
|
||||
final public class EventStripeNode extends EventBundleNodeBase<EventStripe, EventCluster, EventClusterNode> {
|
||||
|
||||
@ -53,12 +53,12 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
|
||||
// private final HBox clustersHBox = new HBox();
|
||||
private final ImageView eventTypeImageView = new ImageView();
|
||||
|
||||
public EventStripeNode(EventDetailChart chart, EventStripe eventStripe, EventClusterNode parentNode) {
|
||||
public EventStripeNode(EventDetailsChart chart, EventStripe eventStripe, EventClusterNode parentNode) {
|
||||
super(chart, eventStripe, parentNode);
|
||||
|
||||
setMinHeight(48);
|
||||
|
||||
EventDetailChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD());
|
||||
EventDetailsChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD());
|
||||
hideButton = ActionUtils.createButton(hideClusterAction, ActionUtils.ActionTextBehavior.HIDE);
|
||||
configureLoDButton(hideButton);
|
||||
|
||||
@ -115,7 +115,7 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescriptionVisibility(DescriptionVisibility descrVis) {
|
||||
void setDescriptionVisibiltiyImpl(DescriptionVisibility descrVis) {
|
||||
final int size = getEventStripe().getEventIDs().size();
|
||||
|
||||
switch (descrVis) {
|
||||
@ -150,7 +150,7 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
|
||||
public void handle(MouseEvent t) {
|
||||
|
||||
if (t.getButton() == MouseButton.PRIMARY) {
|
||||
|
||||
|
||||
if (t.isShiftDown()) {
|
||||
if (chart.selectedNodes.contains(EventStripeNode.this) == false) {
|
||||
chart.selectedNodes.add(EventStripeNode.this);
|
||||
@ -167,7 +167,7 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
|
||||
contextMenu = new ContextMenu();
|
||||
contextMenu.setAutoHide(true);
|
||||
|
||||
EventDetailChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD());
|
||||
EventDetailsChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD());
|
||||
MenuItem hideDescriptionMenuItem = ActionUtils.createMenuItem(hideClusterAction);
|
||||
contextMenu.getItems().addAll(hideDescriptionMenuItem);
|
||||
contextMenu.getItems().addAll(chartContextMenu.getItems());
|
||||
|
@ -1 +0,0 @@
|
||||
NavPanel.eventsTreeLabel.text=Sort By\:
|
@ -1 +1 @@
|
||||
NavPanel.eventsTreeLabel.text=\u4E0B\u8A18\u306B\u5F93\u3044\u4E26\u3079\u66FF\u3048\uFF1A
|
||||
EventsTree.Label.text=\u4e0b\u8a18\u306b\u5f93\u3044\u4e26\u3079\u66ff\u3048\uff1a
|
@ -46,9 +46,7 @@ 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.TimeLineView;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
|
||||
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
|
||||
@ -59,11 +57,9 @@ import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
|
||||
* out. Right clicking on a item in the tree shows a context menu to show/hide
|
||||
* it.
|
||||
*/
|
||||
public class NavPanel extends BorderPane implements TimeLineView {
|
||||
final public class EventsTree extends BorderPane {
|
||||
|
||||
private TimeLineController controller;
|
||||
|
||||
private FilteredEventsModel filteredEvents;
|
||||
private final TimeLineController controller;
|
||||
|
||||
private DetailViewPane detailViewPane;
|
||||
|
||||
@ -76,8 +72,10 @@ public class NavPanel extends BorderPane implements TimeLineView {
|
||||
@FXML
|
||||
private ComboBox<Comparator<TreeItem<EventBundle<?>>>> sortByBox;
|
||||
|
||||
public NavPanel() {
|
||||
FXMLConstructor.construct(this, "NavPanel.fxml"); // NON-NLS
|
||||
public EventsTree(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
|
||||
FXMLConstructor.construct(this, "EventsTree.fxml"); // NON-NLS
|
||||
}
|
||||
|
||||
public void setDetailViewPane(DetailViewPane detailViewPane) {
|
||||
@ -112,19 +110,8 @@ public class NavPanel extends BorderPane implements TimeLineView {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setController(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
setModel(controller.getEventsModel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModel(FilteredEventsModel filteredEvents) {
|
||||
this.filteredEvents = filteredEvents;
|
||||
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@ -137,7 +124,7 @@ public class NavPanel extends BorderPane implements TimeLineView {
|
||||
eventsTree.setCellFactory((TreeView<EventBundle<?>> p) -> new EventBundleTreeCell());
|
||||
eventsTree.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
|
||||
eventsTreeLabel.setText(NbBundle.getMessage(this.getClass(), "NavPanel.eventsTreeLabel.text"));
|
||||
eventsTreeLabel.setText(Bundle.EventsTree_Label_text());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -232,6 +219,5 @@ public class NavPanel extends BorderPane implements TimeLineView {
|
||||
setContextMenu(null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -44,7 +44,6 @@ import org.controlsfx.control.action.ActionUtils;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
@ -64,7 +63,7 @@ import static org.sleuthkit.autopsy.timeline.ui.filtering.Bundle.Timeline_ui_fil
|
||||
* This also implements {@link TimeLineView} since it dynamically updates its
|
||||
* filters based on the contents of a {@link FilteredEventsModel}
|
||||
*/
|
||||
final public class FilterSetPanel extends BorderPane implements TimeLineView {
|
||||
final public class FilterSetPanel extends BorderPane {
|
||||
|
||||
private static final Image TICK = new Image("org/sleuthkit/autopsy/timeline/images/tick.png");
|
||||
|
||||
@ -171,20 +170,25 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView {
|
||||
legendColumn.setCellValueFactory(param -> param.getValue().valueProperty());
|
||||
legendColumn.setCellFactory(col -> new LegendCell(this.controller));
|
||||
|
||||
}
|
||||
|
||||
public FilterSetPanel() {
|
||||
FXMLConstructor.construct(this, "FilterSetPanel.fxml"); // NON-NLS
|
||||
expansionMap.put(new TypeFilter(RootEventType.getInstance()).getDisplayName(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setController(TimeLineController timeLineController) {
|
||||
this.controller = timeLineController;
|
||||
Action defaultFiltersAction = new ResetFilters(controller);
|
||||
defaultButton.setOnAction(defaultFiltersAction);
|
||||
defaultButton.disableProperty().bind(defaultFiltersAction.disabledProperty());
|
||||
this.setModel(timeLineController.getEventsModel());
|
||||
|
||||
this.filteredEvents.eventTypeZoomProperty().addListener((Observable observable) -> {
|
||||
applyFilters();
|
||||
});
|
||||
this.filteredEvents.descriptionLODProperty().addListener((Observable observable1) -> {
|
||||
applyFilters();
|
||||
});
|
||||
this.filteredEvents.timeRangeProperty().addListener((Observable observable2) -> {
|
||||
applyFilters();
|
||||
});
|
||||
this.filteredEvents.filterProperty().addListener((Observable o) -> {
|
||||
refresh();
|
||||
});
|
||||
refresh();
|
||||
|
||||
hiddenDescriptionsListView.setItems(controller.getQuickHideFilters());
|
||||
hiddenDescriptionsListView.setCellFactory((ListView<DescriptionFilter> param) -> {
|
||||
@ -237,25 +241,13 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
public void setModel(FilteredEventsModel filteredEvents) {
|
||||
this.filteredEvents = filteredEvents;
|
||||
this.filteredEvents.eventTypeZoomProperty().addListener((Observable observable) -> {
|
||||
applyFilters();
|
||||
});
|
||||
this.filteredEvents.descriptionLODProperty().addListener((Observable observable) -> {
|
||||
applyFilters();
|
||||
});
|
||||
this.filteredEvents.timeRangeProperty().addListener((Observable observable) -> {
|
||||
applyFilters();
|
||||
});
|
||||
this.filteredEvents.filterProperty().addListener((Observable o) -> {
|
||||
refresh();
|
||||
});
|
||||
refresh();
|
||||
public FilterSetPanel(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
this.filteredEvents = controller.getEventsModel();
|
||||
FXMLConstructor.construct(this, "FilterSetPanel.fxml"); // NON-NLS
|
||||
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,6 @@ import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
|
||||
@ -40,18 +39,19 @@ import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
|
||||
* A TreeTableCell that shows an icon and color corresponding to the represented
|
||||
* filter
|
||||
*/
|
||||
class LegendCell extends TreeTableCell<AbstractFilter, AbstractFilter> implements TimeLineView {
|
||||
final class LegendCell extends TreeTableCell<AbstractFilter, AbstractFilter> {
|
||||
|
||||
private static final Color CLEAR = Color.rgb(0, 0, 0, 0);
|
||||
|
||||
private TimeLineController controller;
|
||||
private final TimeLineController controller;
|
||||
|
||||
private FilteredEventsModel filteredEvents;
|
||||
private final FilteredEventsModel filteredEvents;
|
||||
|
||||
//We need a controller so we can listen to changes in EventTypeZoom to show/hide legends
|
||||
public LegendCell(TimeLineController controller) {
|
||||
LegendCell(TimeLineController controller) {
|
||||
setEditable(false);
|
||||
setController(controller);
|
||||
this.controller = controller;
|
||||
this.filteredEvents = this.controller.getEventsModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -119,15 +119,4 @@ class LegendCell extends TreeTableCell<AbstractFilter, AbstractFilter> implement
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public final void setController(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
setModel(this.controller.getEventsModel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModel(FilteredEventsModel filteredEvents) {
|
||||
this.filteredEvents = filteredEvents;
|
||||
}
|
||||
}
|
||||
|
@ -18,19 +18,20 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline.zooming;
|
||||
|
||||
import java.net.URL;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ResourceBundle;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.control.TitledPane;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.util.StringConverter;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||
@ -44,13 +45,7 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
* has sliders to provide context/control over three axes of zooming (timescale,
|
||||
* event hierarchy, and description detail).
|
||||
*/
|
||||
public class ZoomSettingsPane extends TitledPane implements TimeLineView {
|
||||
|
||||
@FXML
|
||||
private ResourceBundle resources;
|
||||
|
||||
@FXML
|
||||
private URL location;
|
||||
public class ZoomSettingsPane extends TitledPane {
|
||||
|
||||
@FXML
|
||||
private Button backButton;
|
||||
@ -105,35 +100,6 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
|
||||
timeUnitLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.timeUnitLabel.text"));
|
||||
zoomLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.zoomLabel.text"));
|
||||
historyLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.historyLabel.text"));
|
||||
}
|
||||
|
||||
public ZoomSettingsPane() {
|
||||
FXMLConstructor.construct(this, "ZoomSettingsPane.fxml"); // NON-NLS
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public void setController(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
setModel(controller.getEventsModel());
|
||||
descrLODSlider.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS));
|
||||
Back back = new Back(controller);
|
||||
backButton.disableProperty().bind(back.disabledProperty());
|
||||
backButton.setOnAction(back);
|
||||
backButton.setTooltip(new Tooltip(
|
||||
NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.backButton.toolTip.text",
|
||||
back.getAccelerator().getName())));
|
||||
Forward forward = new Forward(controller);
|
||||
forwardButton.disableProperty().bind(forward.disabledProperty());
|
||||
forwardButton.setOnAction(forward);
|
||||
forwardButton.setTooltip(new Tooltip(
|
||||
NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.forwardButton.toolTip.text",
|
||||
forward.getAccelerator().getName())));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModel(FilteredEventsModel filteredEvents) {
|
||||
this.filteredEvents = filteredEvents;
|
||||
|
||||
initializeSlider(timeUnitSlider,
|
||||
() -> {
|
||||
@ -151,18 +117,14 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
|
||||
|
||||
timeUnitSlider.setValue(TimeUnits.fromChronoUnit(chronoUnit).ordinal() - 1);
|
||||
});
|
||||
|
||||
initializeSlider(descrLODSlider,
|
||||
() -> {
|
||||
DescriptionLoD newLOD = DescriptionLoD.values()[Math.round(descrLODSlider.valueProperty().floatValue())];
|
||||
if (controller.pushDescrLOD(newLOD) == false) {
|
||||
descrLODSlider.setValue(new DescrLODConverter().fromString(filteredEvents.getDescriptionLOD().toString()));
|
||||
}
|
||||
}, this.filteredEvents.descriptionLODProperty(),
|
||||
() -> {
|
||||
descrLODSlider.setValue(this.filteredEvents.descriptionLODProperty().get().ordinal());
|
||||
});
|
||||
|
||||
initializeSlider(descrLODSlider, () -> {
|
||||
DescriptionLoD newLOD = DescriptionLoD.values()[Math.round(descrLODSlider.valueProperty().floatValue())];
|
||||
if (controller.pushDescrLOD(newLOD) == false) {
|
||||
descrLODSlider.setValue(new DescrLODConverter().fromString(controller.getEventsModel().getDescriptionLOD().toString()));
|
||||
}
|
||||
}, this.filteredEvents.descriptionLODProperty(), () -> {
|
||||
descrLODSlider.setValue(this.filteredEvents.descriptionLODProperty().get().ordinal());
|
||||
});
|
||||
initializeSlider(typeZoomSlider,
|
||||
() -> {
|
||||
EventTypeZoomLevel newZoomLevel = EventTypeZoomLevel.values()[Math.round(typeZoomSlider.valueProperty().floatValue())];
|
||||
@ -172,6 +134,26 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
|
||||
() -> {
|
||||
typeZoomSlider.setValue(this.filteredEvents.eventTypeZoomProperty().get().ordinal());
|
||||
});
|
||||
descrLODSlider.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS));
|
||||
Back back = new Back(controller);
|
||||
backButton.disableProperty().bind(back.disabledProperty());
|
||||
backButton.setOnAction(back);
|
||||
backButton.setTooltip(new Tooltip(
|
||||
NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.backButton.toolTip.text",
|
||||
back.getAccelerator().getName())));
|
||||
Forward forward = new Forward(controller);
|
||||
forwardButton.disableProperty().bind(forward.disabledProperty());
|
||||
forwardButton.setOnAction(forward);
|
||||
forwardButton.setTooltip(new Tooltip(
|
||||
NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.forwardButton.toolTip.text",
|
||||
forward.getAccelerator().getName())));
|
||||
|
||||
}
|
||||
|
||||
public ZoomSettingsPane(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
this.filteredEvents = controller.getEventsModel();
|
||||
FXMLConstructor.construct(this, "ZoomSettingsPane.fxml"); // NON-NLS
|
||||
}
|
||||
|
||||
/**
|
||||
|
34
CoreLibs/src/com/sun/javafx/Utils.java
Normal file
34
CoreLibs/src/com/sun/javafx/Utils.java
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015 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 com.sun.javafx;
|
||||
|
||||
/**
|
||||
* This class takes the place of the one in Java8 versions prior to u60. As of
|
||||
* u60 com.sun.javafx.Utils was moved to the com.sun.javafx.util package and
|
||||
* code, specifically ControlsFX, that depended on it broke. ControlsFX has
|
||||
* removed their dependency on this class, but their fix will not be released
|
||||
* until version 8.60.10 of ControlsFX. Until then, this shim class allows
|
||||
* version 8.40.9 to run on Java 8u60. This class (and package) should and will
|
||||
* be removed once we upgrade to ControlsFX 8.60.x.
|
||||
*/
|
||||
@Deprecated // DO NOT USE
|
||||
public class Utils extends com.sun.javafx.util.Utils {
|
||||
|
||||
//Does nothing but expose com.sun.javafx.utila.Utils in the old package (com.sun.javafx.Utils)
|
||||
}
|
@ -49,10 +49,12 @@ import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.netbeans.api.progress.ProgressHandle;
|
||||
import org.netbeans.api.progress.ProgressHandleFactory;
|
||||
import org.openide.util.Cancellable;
|
||||
import org.openide.util.Exceptions;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
@ -395,6 +397,9 @@ public final class ImageGalleryController {
|
||||
tagsManager.clearFollowUpTagName();
|
||||
tagsManager.unregisterListener(groupManager);
|
||||
tagsManager.unregisterListener(categoryManager);
|
||||
dbWorkerThread.cancelAllTasks();
|
||||
dbWorkerThread = null;
|
||||
restartWorker();
|
||||
|
||||
Toolbar.getDefault(this).reset();
|
||||
groupManager.clear();
|
||||
@ -418,7 +423,12 @@ public final class ImageGalleryController {
|
||||
dbWorkerThread.addTask(innerTask);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
synchronized public DrawableFile<?> getFileFromId(Long fileID) throws TskCoreException {
|
||||
if (Objects.isNull(db)) {
|
||||
LOGGER.log(Level.WARNING, "Could not get file from id, no DB set. The case is probably closed.");
|
||||
return null;
|
||||
}
|
||||
return db.getFileFromID(fileID);
|
||||
}
|
||||
|
||||
@ -587,7 +597,7 @@ public final class ImageGalleryController {
|
||||
try {
|
||||
InnerTask it = workQueue.take();
|
||||
|
||||
if (it.cancelled == false) {
|
||||
if (it.isCancelled() == false) {
|
||||
it.run();
|
||||
}
|
||||
|
||||
@ -609,7 +619,7 @@ public final class ImageGalleryController {
|
||||
/**
|
||||
* Abstract base class for task to be done on {@link DBWorkerThread}
|
||||
*/
|
||||
static public abstract class InnerTask implements Runnable {
|
||||
static public abstract class InnerTask implements Runnable, Cancellable {
|
||||
|
||||
public double getProgress() {
|
||||
return progress.get();
|
||||
@ -653,13 +663,13 @@ public final class ImageGalleryController {
|
||||
protected InnerTask() {
|
||||
}
|
||||
|
||||
protected volatile boolean cancelled = false;
|
||||
|
||||
public void cancel() {
|
||||
@Override
|
||||
synchronized public boolean cancel() {
|
||||
updateState(Worker.State.CANCELLED);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean isCancelled() {
|
||||
synchronized protected boolean isCancelled() {
|
||||
return getState() == Worker.State.CANCELLED;
|
||||
}
|
||||
}
|
||||
@ -693,7 +703,7 @@ public final class ImageGalleryController {
|
||||
*/
|
||||
static private class UpdateFileTask extends FileTask {
|
||||
|
||||
public UpdateFileTask(AbstractFile f, DrawableDB taskDB) {
|
||||
UpdateFileTask(AbstractFile f, DrawableDB taskDB) {
|
||||
super(f, taskDB);
|
||||
}
|
||||
|
||||
@ -720,7 +730,7 @@ public final class ImageGalleryController {
|
||||
*/
|
||||
static private class RemoveFileTask extends FileTask {
|
||||
|
||||
public RemoveFileTask(AbstractFile f, DrawableDB taskDB) {
|
||||
RemoveFileTask(AbstractFile f, DrawableDB taskDB) {
|
||||
super(f, taskDB);
|
||||
}
|
||||
|
||||
@ -756,7 +766,7 @@ public final class ImageGalleryController {
|
||||
private final DrawableDB taskDB;
|
||||
private final SleuthkitCase tskCase;
|
||||
|
||||
public CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
|
||||
CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
|
||||
this.controller = controller;
|
||||
this.taskDB = taskDB;
|
||||
this.tskCase = tskCase;
|
||||
@ -766,8 +776,8 @@ public final class ImageGalleryController {
|
||||
+ StringUtils.join(FileTypeUtils.getAllSupportedExtensions(),
|
||||
"' or name LIKE '%.")
|
||||
+ "')";
|
||||
static private final String MIMETYPE_CLAUSE
|
||||
= "blackboard_attributes.value_text LIKE '"
|
||||
static private final String MIMETYPE_CLAUSE =
|
||||
"blackboard_attributes.value_text LIKE '"
|
||||
+ StringUtils.join(FileTypeUtils.getAllSupportedMimeTypes(),
|
||||
"' OR blackboard_attributes.value_text LIKE '") + "' ";
|
||||
|
||||
@ -801,7 +811,7 @@ public final class ImageGalleryController {
|
||||
DrawableDB.DrawableTransaction tr = taskDB.beginTransaction();
|
||||
int units = 0;
|
||||
for (final AbstractFile f : files) {
|
||||
if (cancelled) {
|
||||
if (isCancelled()) {
|
||||
LOGGER.log(Level.WARNING, "task cancelled: not all contents may be transfered to database");
|
||||
progressHandle.finish();
|
||||
break;
|
||||
@ -848,12 +858,12 @@ public final class ImageGalleryController {
|
||||
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(CopyAnalyzedFiles.class.getName()).log(Level.WARNING, "failed to transfer all database contents", ex);
|
||||
}
|
||||
|
||||
}
|
||||
progressHandle.finish();
|
||||
|
||||
updateMessage("");
|
||||
updateProgress(-1.0);
|
||||
|
||||
controller.setStale(false);
|
||||
}
|
||||
}
|
||||
@ -877,13 +887,13 @@ public final class ImageGalleryController {
|
||||
// (name like '.jpg' or name like '.png' ...)
|
||||
private final String DRAWABLE_QUERY = "(name LIKE '%." + StringUtils.join(FileTypeUtils.getAllSupportedExtensions(), "' OR name LIKE '%.") + "') ";
|
||||
|
||||
private ProgressHandle progressHandle = ProgressHandleFactory.createHandle("prepopulating image/video database");
|
||||
private ProgressHandle progressHandle = ProgressHandleFactory.createHandle("prepopulating image/video database", this);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param dataSourceId Data source object ID
|
||||
*/
|
||||
public PrePopulateDataSourceFiles(Content dataSource) {
|
||||
PrePopulateDataSourceFiles(Content dataSource) {
|
||||
super();
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
@ -933,7 +943,7 @@ public final class ImageGalleryController {
|
||||
DrawableDB.DrawableTransaction tr = db.beginTransaction();
|
||||
int units = 0;
|
||||
for (final AbstractFile f : files) {
|
||||
if (cancelled) {
|
||||
if (isCancelled()) {
|
||||
LOGGER.log(Level.WARNING, "task cancelled: not all contents may be transfered to database");
|
||||
progressHandle.finish();
|
||||
break;
|
||||
@ -951,7 +961,7 @@ public final class ImageGalleryController {
|
||||
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(PrePopulateDataSourceFiles.class.getName()).log(Level.WARNING, "failed to transfer all database contents", ex);
|
||||
}
|
||||
}
|
||||
|
||||
progressHandle.finish();
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ import org.openide.util.Lookup;
|
||||
*/
|
||||
@OptionsPanelController.TopLevelRegistration(
|
||||
categoryName = "#OptionsCategory_Name_Options",
|
||||
iconBase = "org/sleuthkit/autopsy/imagegallery/images/polaroid_48_silhouette.png",
|
||||
iconBase = "org/sleuthkit/autopsy/imagegallery/images/polaroid_32_silhouette.png",
|
||||
keywords = "#OptionsCategory_Keywords_Options",
|
||||
keywordsCategory = "Options",
|
||||
position = 10
|
||||
|
@ -89,7 +89,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
||||
return fileOpt;
|
||||
} else {
|
||||
try {
|
||||
fileOpt = Optional.of(getController().getFileFromId(fileIDOpt.get()));
|
||||
fileOpt = Optional.ofNullable(getController().getFileFromId(fileIDOpt.get()));
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getAnonymousLogger().log(Level.WARNING, "failed to get DrawableFile for obj_id" + fileIDOpt.get(), ex);
|
||||
fileOpt = Optional.empty();
|
||||
@ -206,7 +206,6 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
||||
super.failed();
|
||||
LOGGER.log(Level.SEVERE, "Failed to cache content for" + file.getName(), getException());
|
||||
}
|
||||
|
||||
|
||||
abstract void saveToCache(X result);
|
||||
}
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
@ -30,9 +30,11 @@
|
||||
<property name="win64.TskLib.path" value="${env.TSK_HOME}/win32/x64/Release_PostgreSQL"/>
|
||||
<property name="win32.TskLib.path" value="${env.TSK_HOME}/win32/Release_PostgreSQL" />
|
||||
<property name="win64.TskLib.postgres_path" value="${env.TSK_HOME}/win32/x64/Release_PostgreSQL"/>
|
||||
<property name="win32.TskLib.postgres_path" value="${env.TSK_HOME}/win32/Release_PostgreSQL"/>
|
||||
<available property="win64.TskLib.exists" type="dir" file="${win64.TskLib.path}" />
|
||||
<available property="win32.TskLib.exists" type="dir" file="${win32.TskLib.path}" />
|
||||
<available property="win64.TskLib_postgres.exists" type="dir" file="{win64.TskLib.postgres_path}" />
|
||||
<available property="win32.TskLib_postgres.exists" type="dir" file="{win32.TskLib.postgres_path}" />
|
||||
</target>
|
||||
|
||||
<!-- The following copy the libtsk_jni dependencies to the Autopsy
|
||||
@ -68,23 +70,35 @@
|
||||
<target name="copyWinTskLibs32ToBaseDir" if="win32.TskLib.exists">
|
||||
<fileset dir="${win32.TskLib.path}" id="win32dlls">
|
||||
<include name="zlib.dll" />
|
||||
<include name="libewf.dll"/>
|
||||
<include name="libewf.dll"/>
|
||||
</fileset>
|
||||
|
||||
<fileset dir="${win32.TskLib.postgres_path}" id="postgres32dlls">
|
||||
<include name="libeay32.dll"/>
|
||||
<include name="ssleay32.dll"/>
|
||||
<include name="intl.dll"/>
|
||||
<include name="libpq.dll"/>
|
||||
<include name="msvcr120.dll"/>
|
||||
</fileset>
|
||||
|
||||
<copy todir="${i386}" overwrite="true">
|
||||
<fileset refid="win32dlls" />
|
||||
<fileset refid="postgres32dlls" />
|
||||
</copy>
|
||||
|
||||
<copy todir="${x86}" overwrite="true">
|
||||
<fileset refid="win32dlls" />
|
||||
<fileset refid="postgres32dlls" />
|
||||
</copy>
|
||||
|
||||
<copy todir="${i586}" overwrite="true">
|
||||
<fileset refid="win32dlls" />
|
||||
<fileset refid="postgres32dlls" />
|
||||
</copy>
|
||||
|
||||
<copy todir="${i686}" overwrite="true">
|
||||
<fileset refid="win32dlls" />
|
||||
<fileset refid="postgres32dlls" />
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
|
@ -26,17 +26,25 @@ There are also a set of tutorials that Basis Technology published on their blog:
|
||||
-You don't really need anything to develop a python Autopsy module except for the standard Autopsy and your favorite text editor. We recommend IntelliJ IDEA or the Jython plug-in to NetBeans.
|
||||
|
||||
To install NetBeans' plug-in:
|
||||
-# Download and install the Jython 2.7 installer (http://www.jython.org/downloads.html).
|
||||
-# Download and install the Jython 2.7 installer to desired location (http://www.jython.org/downloads.html).
|
||||
-# Download NetBeans Python plug-in zip file (http://plugins.netbeans.org/plugin/56795/python4netbeans802).
|
||||
-# Unpack the content (.nbm files) of the zip file to the desired location.
|
||||
-# In NetBeans go to Tools->Plugins. In Download tab, click on Add Plugins, then choose extracted .nbm files.
|
||||
-# Setup Jython path from Tools->Python Platform, click on new, then choose Jython.exe (usually in C:/Program files/Jython2.7/bin/)
|
||||
-# In NetBeans go to Tools->Plugins. In Downloaded tab, click on Add Plugins, then choose extracted .nbm files.
|
||||
-# Setup Jython path from Tools->Python Platforms, click on new, then choose Jython.exe (usually in C:/Program files/Jython2.7/bin/)
|
||||
|
||||
To install IntelliJ IDEA + Python plug-in:
|
||||
-# Download and install IDEA https://www.jetbrains.com/idea/download/
|
||||
-# In File->Settings->Plugins-> install Python Community Edition
|
||||
-# In File->Project Structure->Project-> Project SDK-> choose IntelliJ IDEA Community Edition
|
||||
-# In Libraries->add new libraries->choose desired autopsy modules (usually in C:\Program Files\Autopsy-3.1.3\autopsy\modules)
|
||||
-# Download java JDK depending on platform. Install to desired location (http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html).
|
||||
-# Download and install IDEA Community Edition to desired location (https://www.jetbrains.com/idea/download/).
|
||||
-# Open IDEA and choose desired UI theme. Continue with default settings.
|
||||
-# Choose to either create a new empty project or open an existing one.
|
||||
-# It will ask you to modify Project Structure. Leave that for now and click OK.
|
||||
-# In File->Settings. Go to Plugins tab and click on Install JetBrains Plugin.
|
||||
-# Look for and install Python Community Edition. After the installation, it will ask you restart. Restart IDEA.
|
||||
-# In File->Project Structure. In Project tab, Project SDK, click on New and choose IntelliJ Platform Plugin SKD.
|
||||
-# It will ask you to configure the JKD first, click OK and navigate to the JDK folder location and click OK.
|
||||
-# After that it will ask you to choose the IntelliJ Platform Plugin SKD. It will most likely take you to it's locaation automatically. (Usually in C:\Program Files (x86)\JetBrains\IntelliJ IDEA Community Edition 14.1.5)
|
||||
-# In the drop down menu next to New button, choose IntelliJ IDEA Community Edition.
|
||||
-# Still in Project STructure, In Libraries tab, click on '+' to add new libraries. Choose desired autopsy modules (usually in C:\Program Files\Autopsy-3.1.3\autopsy\modules if you have executable version).
|
||||
|
||||
\section mod_dev_py_create Creating a Basic Python Module
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user