mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-15 09:17:42 +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-autoupdate-ui-actions-PluginManagerAction.shadow"/>
|
||||||
<file name="org-netbeans-modules-favorites-templates-TemplatesAction.shadow_hidden"/>
|
<file name="org-netbeans-modules-favorites-templates-TemplatesAction.shadow_hidden"/>
|
||||||
<file name="org-netbeans-modules-options-OptionsWindowAction.shadow"/>
|
<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-openide-actions-ToolsAction.shadow_hidden"/>
|
||||||
<file name="org-sleuthkit-autopsy-filesearch-FileSearchAction.shadow">
|
<file name="org-sleuthkit-autopsy-filesearch-FileSearchAction.shadow">
|
||||||
<attr name="originalFile" stringvalue="Actions/Tools/org-sleuthkit-autopsy-filesearch-FileSearchAction.instance"/>
|
<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.EventQueue;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -174,6 +175,9 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi
|
|||||||
borderpane.setCenter(fxImageView);
|
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) {
|
} catch (IllegalArgumentException | IOException ex) {
|
||||||
LOGGER.log(Level.WARNING, "Could not load image file into media view: " + file.getName(), ex); //NON-NLS
|
LOGGER.log(Level.WARNING, "Could not load image file into media view: " + file.getName(), ex); //NON-NLS
|
||||||
borderpane.setCenter(errorLabel);
|
borderpane.setCenter(errorLabel);
|
||||||
|
@ -26,6 +26,7 @@ import com.google.common.io.Files;
|
|||||||
import java.awt.Image;
|
import java.awt.Image;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.EOFException;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -135,8 +136,8 @@ public class ImageUtils {
|
|||||||
/**
|
/**
|
||||||
* thread that saves generated thumbnails to disk in the background
|
* thread that saves generated thumbnails to disk in the background
|
||||||
*/
|
*/
|
||||||
private static final Executor imageSaver
|
private static final Executor imageSaver =
|
||||||
= Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
|
Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
|
||||||
.namingPattern("icon saver-%d").build());
|
.namingPattern("icon saver-%d").build());
|
||||||
|
|
||||||
public static List<String> getSupportedImageExtensions() {
|
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()));
|
return ScalrWrapper.cropImage(bi, Math.min(iconSize, bi.getWidth()), Math.min(iconSize, bi.getHeight()));
|
||||||
}
|
}
|
||||||
} catch (OutOfMemoryError e) {
|
} catch (OutOfMemoryError e) {
|
||||||
LOGGER.log(Level.WARNING, "Could not scale image (too large): " + content.getName(), e); //NON-NLS
|
LOGGER.log(Level.WARNING, "Could not scale image (too large) " + content.getName(), e); //NON-NLS
|
||||||
|
} catch (EOFException e) {
|
||||||
return null;
|
LOGGER.log(Level.WARNING, "Could not load image (EOF) {0}", content.getName()); //NON-NLS
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.log(Level.WARNING, "Could not load image: " + content.getName(), e); //NON-NLS
|
LOGGER.log(Level.WARNING, "Could not load image " + content.getName(), e); //NON-NLS
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,10 +24,7 @@ TimelinePanel.jButton7.text=3d
|
|||||||
TimelinePanel.jButton2.text=1m
|
TimelinePanel.jButton2.text=1m
|
||||||
TimelinePanel.jButton3.text=3m
|
TimelinePanel.jButton3.text=3m
|
||||||
TimelinePanel.jButton4.text=2w
|
TimelinePanel.jButton4.text=2w
|
||||||
TimeLineTopComponent.eventsTab.name=Events
|
|
||||||
TimeLineTopComponent.filterTab.name=Filters
|
|
||||||
OpenTimelineAction.title=Timeline
|
OpenTimelineAction.title=Timeline
|
||||||
OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources.
|
OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources.
|
||||||
TimeLineTopComponent.timeZonePanel.text=Display Times In\:
|
|
||||||
ProgressWindow.progressHeader.text=\
|
ProgressWindow.progressHeader.text=\
|
||||||
|
|
||||||
|
@ -42,8 +42,7 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
|||||||
* intended only to remove the boilerplate initialization code when defining a
|
* intended only to remove the boilerplate initialization code when defining a
|
||||||
* relatively static layout
|
* relatively static layout
|
||||||
*
|
*
|
||||||
* TODO: find a way to move this to CoreUtils and remove duplicate verison in
|
* TODO: move this to CoreUtils and remove duplicate verison in image analyzer
|
||||||
* image analyzer
|
|
||||||
*/
|
*/
|
||||||
public class FXMLConstructor {
|
public class FXMLConstructor {
|
||||||
|
|
||||||
@ -55,12 +54,28 @@ public class FXMLConstructor {
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @param node a node to initialize from a loaded FXML
|
* @param node a node to initialize from a loaded FXML
|
||||||
* @param fxmlFileName the the file name of the FXML to load, relative to
|
* @param fxmlFileName the file name of the FXML to load, relative to the
|
||||||
* the package that the class of node is defined in.
|
* package that the class of node is defined in.
|
||||||
*/
|
*/
|
||||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||||
static public void construct(Node node, String fxmlFileName) {
|
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 {
|
try {
|
||||||
FXMLLoader fxmlLoader = new FXMLLoader(new URL(name));
|
FXMLLoader fxmlLoader = new FXMLLoader(new URL(name));
|
||||||
|
@ -64,13 +64,13 @@ import org.openide.windows.WindowManager;
|
|||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE;
|
import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE;
|
||||||
import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_ADDED;
|
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.BlackBoardArtifactTagAddedEvent;
|
||||||
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent;
|
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent;
|
||||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
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.coreutils.ThreadConfined;
|
||||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
@ -135,6 +135,21 @@ public class TimeLineController {
|
|||||||
|
|
||||||
private final ReadOnlyStringWrapper taskTitle = new ReadOnlyStringWrapper();
|
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;
|
private final Case autoCase;
|
||||||
|
|
||||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||||
@ -301,9 +316,11 @@ public class TimeLineController {
|
|||||||
LOGGER.log(Level.INFO, "Beginning generation of timeline"); // NON-NLS
|
LOGGER.log(Level.INFO, "Beginning generation of timeline"); // NON-NLS
|
||||||
try {
|
try {
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
synchronized (TimeLineController.this) {
|
||||||
if (isWindowOpen()) {
|
if (isWindowOpen()) {
|
||||||
mainFrame.close();
|
mainFrame.close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase();
|
final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase();
|
||||||
final long lastObjId = sleuthkitCase.getLastObjectId();
|
final long lastObjId = sleuthkitCase.getLastObjectId();
|
||||||
@ -347,9 +364,11 @@ public class TimeLineController {
|
|||||||
void rebuildTagsTable() {
|
void rebuildTagsTable() {
|
||||||
LOGGER.log(Level.INFO, "starting to rebuild tags table"); // NON-NLS
|
LOGGER.log(Level.INFO, "starting to rebuild tags table"); // NON-NLS
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
synchronized (TimeLineController.this) {
|
||||||
if (isWindowOpen()) {
|
if (isWindowOpen()) {
|
||||||
mainFrame.close();
|
mainFrame.close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
synchronized (eventsRepository) {
|
synchronized (eventsRepository) {
|
||||||
eventsRepository.rebuildTags(() -> {
|
eventsRepository.rebuildTags(() -> {
|
||||||
@ -373,16 +392,19 @@ public class TimeLineController {
|
|||||||
IngestManager.getInstance().removeIngestModuleEventListener(ingestModuleListener);
|
IngestManager.getInstance().removeIngestModuleEventListener(ingestModuleListener);
|
||||||
IngestManager.getInstance().removeIngestJobEventListener(ingestJobListener);
|
IngestManager.getInstance().removeIngestJobEventListener(ingestJobListener);
|
||||||
Case.removePropertyChangeListener(caseListener);
|
Case.removePropertyChangeListener(caseListener);
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
synchronized (TimeLineController.this) {
|
||||||
mainFrame.close();
|
mainFrame.close();
|
||||||
mainFrame.setVisible(false);
|
|
||||||
mainFrame = null;
|
mainFrame = null;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* show the timeline window and prompt for rebuilding database if necessary.
|
* 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).
|
// listen for case changes (specifically images being added, and case changes).
|
||||||
if (Case.isCaseOpen() && !listeningToAutopsy) {
|
if (Case.isCaseOpen() && !listeningToAutopsy) {
|
||||||
IngestManager.getInstance().addIngestModuleEventListener(ingestModuleListener);
|
IngestManager.getInstance().addIngestModuleEventListener(ingestModuleListener);
|
||||||
@ -524,20 +546,20 @@ public class TimeLineController {
|
|||||||
/**
|
/**
|
||||||
* private method to build gui if necessary and make it visible.
|
* private method to build gui if necessary and make it visible.
|
||||||
*/
|
*/
|
||||||
synchronized private void showWindow() {
|
private void showWindow() {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
synchronized (TimeLineController.this) {
|
||||||
if (mainFrame == null) {
|
if (mainFrame == null) {
|
||||||
LOGGER.log(Level.WARNING, "Tried to show timeline with invalid window. Rebuilding GUI."); // NON-NLS
|
LOGGER.log(Level.WARNING, "Tried to show timeline with invalid window. Rebuilding GUI."); // NON-NLS
|
||||||
mainFrame = (TimeLineTopComponent) WindowManager.getDefault().findTopComponent(
|
mainFrame = (TimeLineTopComponent) WindowManager.getDefault().findTopComponent(
|
||||||
NbBundle.getMessage(TimeLineController.class, "CTL_TimeLineTopComponentAction"));
|
NbBundle.getMessage(TimeLineController.class, "CTL_TimeLineTopComponentAction"));
|
||||||
if (mainFrame == null) {
|
if (mainFrame == null) {
|
||||||
mainFrame = new TimeLineTopComponent();
|
mainFrame = new TimeLineTopComponent(this);
|
||||||
}
|
}
|
||||||
mainFrame.setController(this);
|
|
||||||
}
|
}
|
||||||
SwingUtilities.invokeLater(() -> {
|
|
||||||
mainFrame.open();
|
mainFrame.open();
|
||||||
mainFrame.setVisible(true);
|
|
||||||
mainFrame.toFront();
|
mainFrame.toFront();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.embed.swing.JFXPanel;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.control.SplitPane;
|
import javafx.scene.control.SplitPane;
|
||||||
import javafx.scene.control.Tab;
|
import javafx.scene.control.Tab;
|
||||||
@ -34,7 +34,6 @@ import javafx.scene.input.KeyCodeCombination;
|
|||||||
import javafx.scene.input.KeyEvent;
|
import javafx.scene.input.KeyEvent;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import org.netbeans.api.settings.ConvertAsProperties;
|
|
||||||
import org.openide.explorer.ExplorerManager;
|
import org.openide.explorer.ExplorerManager;
|
||||||
import org.openide.explorer.ExplorerUtils;
|
import org.openide.explorer.ExplorerUtils;
|
||||||
import org.openide.util.NbBundle;
|
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.TimeLineResultView;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.TimeZonePanel;
|
import org.sleuthkit.autopsy.timeline.ui.TimeZonePanel;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.VisualizationPanel;
|
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.ui.filtering.FilterSetPanel;
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.ZoomSettingsPane;
|
import org.sleuthkit.autopsy.timeline.zooming.ZoomSettingsPane;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TopComponent for the timeline feature.
|
* TopComponent for the timeline feature.
|
||||||
*/
|
*/
|
||||||
@ConvertAsProperties(
|
|
||||||
dtd = "-//org.sleuthkit.autopsy.timeline//TimeLine//EN",
|
|
||||||
autostore = false)
|
|
||||||
@TopComponent.Description(
|
@TopComponent.Description(
|
||||||
preferredID = "TimeLineTopComponent",
|
preferredID = "TimeLineTopComponent",
|
||||||
//iconBase="SET/PATH/TO/ICON/HERE",
|
//iconBase="SET/PATH/TO/ICON/HERE",
|
||||||
persistenceType = TopComponent.PERSISTENCE_NEVER)
|
persistenceType = TopComponent.PERSISTENCE_NEVER)
|
||||||
@TopComponent.Registration(mode = "timeline", openAtStartup = false)
|
@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 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 final ExplorerManager em = new ExplorerManager();
|
||||||
|
|
||||||
private TimeLineController controller;
|
private final TimeLineController controller;
|
||||||
|
|
||||||
////jfx componenets that make up the interface
|
public TimeLineTopComponent(TimeLineController controller) {
|
||||||
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() {
|
|
||||||
initComponents();
|
initComponents();
|
||||||
|
this.controller = controller;
|
||||||
associateLookup(ExplorerUtils.createLookup(em, getActionMap()));
|
associateLookup(ExplorerUtils.createLookup(em, getActionMap()));
|
||||||
|
|
||||||
setName(NbBundle.getMessage(TimeLineTopComponent.class, "CTL_TimeLineTopComponent"));
|
setName(NbBundle.getMessage(TimeLineTopComponent.class, "CTL_TimeLineTopComponent"));
|
||||||
setToolTipText(NbBundle.getMessage(TimeLineTopComponent.class, "HINT_TimeLineTopComponent"));
|
setToolTipText(NbBundle.getMessage(TimeLineTopComponent.class, "HINT_TimeLineTopComponent"));
|
||||||
setIcon(WindowManager.getDefault().getMainWindow().getIconImage()); //use the same icon as main application
|
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();
|
dataContentPanel = DataContentPanel.createInstance();
|
||||||
this.contentViewerContainerPanel.add(dataContentPanel, BorderLayout.CENTER);
|
this.contentViewerContainerPanel.add(dataContentPanel, BorderLayout.CENTER);
|
||||||
tlrv = new TimeLineResultView(dataContentPanel);
|
tlrv = new TimeLineResultView(controller, dataContentPanel);
|
||||||
DataResultPanel dataResultPanel = tlrv.getDataResultPanel();
|
DataResultPanel dataResultPanel = tlrv.getDataResultPanel();
|
||||||
this.resultContainerPanel.add(dataResultPanel, BorderLayout.CENTER);
|
this.resultContainerPanel.add(dataResultPanel, BorderLayout.CENTER);
|
||||||
dataResultPanel.open();
|
dataResultPanel.open();
|
||||||
|
customizeFXComponents();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NbBundle.Messages({"TimeLineTopComponent.eventsTab.name=Events",
|
||||||
|
"TimeLineTopComponent.filterTab.name=Filters"})
|
||||||
|
void customizeFXComponents() {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
//assemble ui componenets together
|
|
||||||
jFXstatusPanel.setScene(new Scene(statusBar));
|
|
||||||
jFXVizPanel.setScene(new Scene(splitPane));
|
|
||||||
|
|
||||||
splitPane.setDividerPositions(0);
|
|
||||||
|
|
||||||
|
//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.setClosable(false);
|
||||||
filterTab.setContent(filtersPanel);
|
|
||||||
filterTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/funnel.png")); // NON-NLS
|
filterTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/funnel.png")); // NON-NLS
|
||||||
|
|
||||||
eventsTab.setClosable(false);
|
final EventsTree eventsTree = new EventsTree(controller);
|
||||||
eventsTab.setContent(navPanel);
|
final VisualizationPanel visualizationPanel = new VisualizationPanel(controller, eventsTree);
|
||||||
eventsTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/timeline_marker.png")); // NON-NLS
|
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));
|
||||||
|
|
||||||
tabPane.getTabs().addAll(filterTab, eventsTab);
|
final TabPane leftTabPane = new TabPane(filterTab, eventsTreeTab);
|
||||||
VBox.setVgrow(tabPane, Priority.ALWAYS);
|
VBox.setVgrow(leftTabPane, Priority.ALWAYS);
|
||||||
|
|
||||||
VBox.setVgrow(timeZonePanel, Priority.SOMETIMES);
|
|
||||||
leftVBox.getChildren().addAll(timeZonePanel, zoomSettingsPane, tabPane);
|
|
||||||
|
|
||||||
SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE);
|
|
||||||
splitPane.getItems().addAll(leftVBox, visualizationPanel);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void setController(TimeLineController controller) {
|
|
||||||
this.controller = controller;
|
|
||||||
|
|
||||||
tlrv.setController(controller);
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
controller.viewModeProperty().addListener((Observable observable) -> {
|
controller.viewModeProperty().addListener((Observable observable) -> {
|
||||||
if (controller.viewModeProperty().get().equals(VisualizationMode.COUNTS)) {
|
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);
|
final TimeZonePanel timeZonePanel = new TimeZonePanel();
|
||||||
navPanel.setController(controller);
|
VBox.setVgrow(timeZonePanel, Priority.SOMETIMES);
|
||||||
filtersPanel.setController(controller);
|
|
||||||
zoomSettingsPane.setController(controller);
|
final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane(controller);
|
||||||
statusBar.setController(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
|
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||||
private void initComponents() {
|
private void initComponents() {
|
||||||
|
|
||||||
jFXstatusPanel = new javafx.embed.swing.JFXPanel();
|
jFXstatusPanel = new JFXPanel();
|
||||||
splitYPane = new javax.swing.JSplitPane();
|
splitYPane = new javax.swing.JSplitPane();
|
||||||
jFXVizPanel = new javafx.embed.swing.JFXPanel();
|
jFXVizPanel = new JFXPanel();
|
||||||
lowerSplitXPane = new javax.swing.JSplitPane();
|
lowerSplitXPane = new javax.swing.JSplitPane();
|
||||||
resultContainerPanel = new javax.swing.JPanel();
|
resultContainerPanel = new javax.swing.JPanel();
|
||||||
contentViewerContainerPanel = 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
|
Back.actions.name.text=Back
|
||||||
DefaultFilters.action.name.text=apply default filters
|
DefaultFilters.action.name.text=apply default filters
|
||||||
Forward.action.name.text=Forward
|
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 HTML_EXT = ".html";
|
||||||
|
|
||||||
private static final String REPORT_IMAGE_EXTENSION = ".png";
|
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;
|
@NbBundle.Messages({"SaveSnapshot.action.name.text=save snapshot",
|
||||||
|
"SaveSnapshot.fileChoose.title.text=Save snapshot to"})
|
||||||
private final WritableImage snapshot;
|
public SaveSnapshotAsReport(TimeLineController controller, WritableImage snapshot) {
|
||||||
|
super(Bundle.SaveSnapshot_action_name_text());
|
||||||
public SaveSnapshot(TimeLineController controller, WritableImage snapshot) {
|
|
||||||
super(NbBundle.getMessage(SaveSnapshot.class, "SaveSnapshot.action.name.text"));
|
|
||||||
this.controller = controller;
|
|
||||||
this.snapshot = snapshot;
|
|
||||||
setEventHandler(new Consumer<ActionEvent>() {
|
setEventHandler(new Consumer<ActionEvent>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void accept(ActionEvent t) {
|
public void accept(ActionEvent t) {
|
||||||
//choose location/name
|
//choose location/name
|
||||||
DirectoryChooser fileChooser = new DirectoryChooser();
|
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()));
|
fileChooser.setInitialDirectory(new File(Case.getCurrentCase().getReportDirectory()));
|
||||||
File outFolder = fileChooser.showDialog(null);
|
File reportDirectory = fileChooser.showDialog(null);
|
||||||
if (outFolder == null) {
|
if (reportDirectory == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
outFolder.mkdir();
|
reportDirectory.mkdir();
|
||||||
String name = outFolder.getName();
|
String reportName = reportDirectory.getName();
|
||||||
|
String reportPath = reportDirectory.getPath();
|
||||||
|
|
||||||
//gather metadata
|
//gather metadata
|
||||||
List<Pair<String, String>> reportMetaData = new ArrayList<>();
|
List<Pair<String, String>> reportMetaData = new ArrayList<>();
|
||||||
|
|
||||||
reportMetaData.add(new Pair<>("Case", Case.getCurrentCase().getName())); // NON-NLS
|
reportMetaData.add(new Pair<>("Case", Case.getCurrentCase().getName())); // NON-NLS
|
||||||
|
|
||||||
ZoomParams get = controller.getEventsModel().zoomParametersProperty().get();
|
ZoomParams zoomParams = controller.getEventsModel().zoomParametersProperty().get();
|
||||||
reportMetaData.add(new Pair<>("Time Range", get.getTimeRange().toString())); // NON-NLS
|
reportMetaData.add(new Pair<>("Time Range", zoomParams.getTimeRange().toString())); // NON-NLS
|
||||||
reportMetaData.add(new Pair<>("Description Level of Detail", get.getDescriptionLOD().getDisplayName())); // NON-NLS
|
reportMetaData.add(new Pair<>("Description Level of Detail", zoomParams.getDescriptionLOD().getDisplayName())); // NON-NLS
|
||||||
reportMetaData.add(new Pair<>("Event Type Zoom Level", get.getTypeZoomLevel().getDisplayName())); // NON-NLS
|
reportMetaData.add(new Pair<>("Event Type Zoom Level", zoomParams.getTypeZoomLevel().getDisplayName())); // NON-NLS
|
||||||
reportMetaData.add(new Pair<>("Filters", get.getFilter().getHTMLReportString())); // NON-NLS
|
reportMetaData.add(new Pair<>("Filters", zoomParams.getFilter().getHTMLReportString())); // NON-NLS
|
||||||
|
|
||||||
//save snapshot as png
|
//save snapshot as png
|
||||||
try {
|
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) {
|
} catch (IOException ex) {
|
||||||
LOGGER.log(Level.WARNING, "failed to write snapshot to disk", ex); // NON-NLS
|
LOGGER.log(Level.WARNING, "failed to write snapshot to disk", ex); // NON-NLS
|
||||||
return;
|
return;
|
||||||
@ -99,17 +96,18 @@ public class SaveSnapshot extends Action {
|
|||||||
//build html string
|
//build html string
|
||||||
StringBuilder wrapper = new StringBuilder();
|
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("<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("<div id=\"content\">\n<h1>").append(reportDirectory.getName()).append("</h1>\n"); // NON-NLS
|
||||||
wrapper.append("<img src = \"").append(outFolder.getName()).append(REPORT_IMAGE_EXTENSION + "\" alt = \"snaphot\">"); // NON-NLS
|
wrapper.append("<img src = \"").append(reportDirectory.getName()).append(REPORT_IMAGE_EXTENSION + "\" alt = \"snaphot\">"); // NON-NLS
|
||||||
wrapper.append("<table>\n"); // NON-NLS
|
wrapper.append("<table>\n"); // NON-NLS
|
||||||
for (Pair<String, String> pair : reportMetaData) {
|
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("<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("</table>\n"); // NON-NLS
|
||||||
wrapper.append("</div>\n</body>\n</html>"); // NON-NLS
|
wrapper.append("</div>\n</body>\n</html>"); // NON-NLS
|
||||||
|
File reportHTMLFIle = new File(reportDirectory, reportName + HTML_EXT);
|
||||||
|
|
||||||
//write html wrapper
|
//write html wrapper
|
||||||
try (Writer htmlWriter = new FileWriter(new File(outFolder, name + HTML_EXT))) {
|
try (Writer htmlWriter = new FileWriter(reportHTMLFIle)) {
|
||||||
htmlWriter.write(wrapper.toString());
|
htmlWriter.write(wrapper.toString());
|
||||||
} catch (FileNotFoundException ex) {
|
} catch (FileNotFoundException ex) {
|
||||||
LOGGER.log(Level.WARNING, "failed to open html wrapper file for writing ", ex); // NON-NLS
|
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
|
//copy css
|
||||||
try (InputStream resource = this.getClass().getResourceAsStream("/org/sleuthkit/autopsy/timeline/index.css")) { // NON-NLS
|
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) {
|
} catch (IOException ex) {
|
||||||
LOGGER.log(Level.WARNING, "failed to copy css file", ex); // NON-NLS
|
LOGGER.log(Level.WARNING, "failed to copy css file", ex); // NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
//add html file as report to case
|
//add html file as report to case
|
||||||
try {
|
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) {
|
} catch (TskCoreException ex) {
|
||||||
LOGGER.log(Level.WARNING, "failed add html wrapper as a report", ex); // NON-NLS
|
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
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2014 Basis Technology Corp.
|
* Copyright 2015 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -19,7 +19,8 @@
|
|||||||
package org.sleuthkit.autopsy.timeline.actions;
|
package org.sleuthkit.autopsy.timeline.actions;
|
||||||
|
|
||||||
import javafx.beans.binding.BooleanBinding;
|
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.controlsfx.control.action.Action;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
@ -30,15 +31,22 @@ import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
|||||||
*/
|
*/
|
||||||
public class ZoomOut extends Action {
|
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) {
|
//disable action when the current time range already encompases the entire case.
|
||||||
super(NbBundle.getMessage(ZoomOut.class, "ZoomOut.action.name.text"));
|
|
||||||
this.controller = controller;
|
|
||||||
eventsModel = controller.getEventsModel();
|
|
||||||
disabledProperty().bind(new BooleanBinding() {
|
disabledProperty().bind(new BooleanBinding() {
|
||||||
|
private final FilteredEventsModel eventsModel = controller.getEventsModel();
|
||||||
|
|
||||||
{
|
{
|
||||||
bind(eventsModel.zoomParametersProperty());
|
bind(eventsModel.zoomParametersProperty());
|
||||||
}
|
}
|
||||||
@ -48,8 +56,5 @@ public class ZoomOut extends Action {
|
|||||||
return eventsModel.zoomParametersProperty().getValue().getTimeRange().contains(eventsModel.getSpanningInterval());
|
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;
|
||||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent.DeletedContentTagInfo;
|
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent.DeletedContentTagInfo;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
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.EventType;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
||||||
import org.sleuthkit.autopsy.timeline.db.EventsRepository;
|
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.chart.XYChart;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.OverrunStyle;
|
import javafx.scene.control.OverrunStyle;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.scene.effect.Effect;
|
import javafx.scene.effect.Effect;
|
||||||
import javafx.scene.input.MouseButton;
|
import javafx.scene.input.MouseButton;
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
@ -50,12 +51,11 @@ import javafx.scene.text.Font;
|
|||||||
import javafx.scene.text.FontWeight;
|
import javafx.scene.text.FontWeight;
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
import javafx.scene.text.TextAlignment;
|
import javafx.scene.text.TextAlignment;
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
|
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
|
* {@link XYChart} doing the rendering. Is this a good idea? -jm TODO: pull up
|
||||||
* common history context menu items out of derived classes? -jm
|
* 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 SimpleBooleanProperty hasEvents = new SimpleBooleanProperty(true);
|
||||||
|
|
||||||
protected final ObservableList<BarChart.Series<X, Y>> dataSets = FXCollections.<BarChart.Series<X, Y>>observableArrayList();
|
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;
|
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() {
|
public ReadOnlyListProperty<N> getSelectedNodes() {
|
||||||
return selectedNodes.getReadOnlyProperty();
|
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
|
* Primarily this invokes the background {@link Task} returned by
|
||||||
* {@link #getUpdateTask()} which derived classes must implement.
|
* {@link #getUpdateTask()} which derived classes must implement.
|
||||||
*/
|
*/
|
||||||
synchronized public void update() {
|
final synchronized public void update() {
|
||||||
if (updateTask != null) {
|
if (updateTask != null) {
|
||||||
updateTask.cancel(true);
|
updateTask.cancel(true);
|
||||||
updateTask = null;
|
updateTask = null;
|
||||||
@ -195,7 +206,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
|||||||
try {
|
try {
|
||||||
this.hasEvents.set(updateTask.get());
|
this.hasEvents.set(updateTask.get());
|
||||||
} catch (InterruptedException | ExecutionException ex) {
|
} 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;
|
break;
|
||||||
}
|
}
|
||||||
@ -203,7 +214,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
|||||||
controller.monitorTask(updateTask);
|
controller.monitorTask(updateTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized public void dispose() {
|
final synchronized public void dispose() {
|
||||||
if (updateTask != null) {
|
if (updateTask != null) {
|
||||||
updateTask.cancel(true);
|
updateTask.cancel(true);
|
||||||
}
|
}
|
||||||
@ -211,7 +222,12 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
|||||||
invalidationListener = null;
|
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.leafPane = partPane;
|
||||||
this.branchPane = contextPane;
|
this.branchPane = contextPane;
|
||||||
this.spacer = spacer;
|
this.spacer = spacer;
|
||||||
@ -226,31 +242,17 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 setController(TimeLineController controller) {
|
|
||||||
this.controller = controller;
|
|
||||||
chart.setController(controller);
|
|
||||||
|
|
||||||
setModel(controller.getEventsModel());
|
|
||||||
TimeLineController.getTimeZone().addListener((Observable observable) -> {
|
|
||||||
update();
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
@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();
|
update();
|
||||||
}
|
}
|
||||||
@ -260,10 +262,6 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
|||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected InvalidationListener invalidationListener = (Observable observable) -> {
|
|
||||||
update();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* iterate through the list of tick-marks building a two level structure of
|
* iterate through the list of tick-marks building a two level structure of
|
||||||
* replacement tick marl labels. (Visually) upper level has most
|
* 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) {
|
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.setTextAlignment(TextAlignment.CENTER);
|
||||||
label.setFont(Font.font(null, bold ? FontWeight.BOLD : FontWeight.NORMAL, 10));
|
label.setFont(Font.font(null, bold ? FontWeight.BOLD : FontWeight.NORMAL, 10));
|
||||||
//position label accounting for width
|
//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);
|
label.relocate(labelX, 0);
|
||||||
|
|
||||||
if (labelX == 0) { // first label has no border
|
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
|
} 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);
|
branchPane.getChildren().add(label);
|
||||||
@ -446,10 +444,10 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
|||||||
|
|
||||||
TwoPartDateTime(String dateString) {
|
TwoPartDateTime(String dateString) {
|
||||||
//find index of separator to spit on
|
//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
|
if (splitIndex < 0) { // there is only one part
|
||||||
leaf = dateString;
|
leaf = dateString;
|
||||||
branch = "";
|
branch = ""; //NOI18N
|
||||||
} else { //split at index
|
} else { //split at index
|
||||||
leaf = StringUtils.substring(dateString, splitIndex + 1);
|
leaf = StringUtils.substring(dateString, splitIndex + 1);
|
||||||
branch = StringUtils.substring(dateString, 0, splitIndex);
|
branch = StringUtils.substring(dateString, 0, splitIndex);
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013 Basis Technology Corp.
|
* Copyright 2013-15 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* 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.fiveyears.text=Five Years
|
||||||
Timeline.ui.ZoomRanges.tenyears.text=Ten Years
|
Timeline.ui.ZoomRanges.tenyears.text=Ten Years
|
||||||
Timeline.ui.ZoomRanges.all.text=All
|
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}
|
TimeLineResultView.startDateToEndDate.text={0} to {1}
|
||||||
VisualizationPanel.histogramTask.title=Rebuild Histogram
|
VisualizationPanel.histogramTask.title=Rebuild Histogram
|
||||||
VisualizationPanel.histogramTask.preparing=preparing
|
VisualizationPanel.histogramTask.preparing=preparing
|
||||||
|
@ -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.image.*?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?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>
|
<items>
|
||||||
<Label fx:id="refreshLabel">
|
<Label fx:id="refreshLabel">
|
||||||
<graphic>
|
<graphic>
|
||||||
@ -17,6 +17,15 @@
|
|||||||
</graphic>
|
</graphic>
|
||||||
</Label>
|
</Label>
|
||||||
<Separator orientation="VERTICAL" />
|
<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" />
|
<Region fx:id="spacer" maxWidth="1.7976931348623157E308" />
|
||||||
<Separator orientation="VERTICAL" />
|
<Separator orientation="VERTICAL" />
|
||||||
<Label fx:id="taskLabel" contentDisplay="RIGHT">
|
<Label fx:id="taskLabel" contentDisplay="RIGHT">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2014 Basis Technology Corp.
|
* Copyright 2014-15 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* 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.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineUI;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* simple status bar that only shows one possible message determined by
|
* simple status bar that only shows one possible message determined by
|
||||||
* {@link TimeLineController#newEventsFlag}
|
* {@link TimeLineController#newEventsFlag}
|
||||||
*/
|
*/
|
||||||
public class StatusBar extends ToolBar implements TimeLineUI {
|
public class StatusBar extends ToolBar {
|
||||||
|
|
||||||
private TimeLineController controller;
|
private TimeLineController controller;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label refreshLabel;
|
private Label refreshLabel;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label statusLabel;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ProgressBar progressBar;
|
private ProgressBar progressBar;
|
||||||
|
|
||||||
@ -53,7 +55,8 @@ public class StatusBar extends ToolBar implements TimeLineUI {
|
|||||||
@FXML
|
@FXML
|
||||||
private Label messageLabel;
|
private Label messageLabel;
|
||||||
|
|
||||||
public StatusBar() {
|
public StatusBar(TimeLineController controller) {
|
||||||
|
this.controller = controller;
|
||||||
FXMLConstructor.construct(this, "StatusBar.fxml"); // NON-NLS
|
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.setText(NbBundle.getMessage(this.getClass(), "StatusBar.taskLabel.text"));
|
||||||
taskLabel.setVisible(false);
|
taskLabel.setVisible(false);
|
||||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setController(TimeLineController controller) {
|
|
||||||
this.controller = controller;
|
|
||||||
refreshLabel.visibleProperty().bind(this.controller.getNewEventsFlag());
|
refreshLabel.visibleProperty().bind(this.controller.getNewEventsFlag());
|
||||||
|
refreshLabel.managedProperty().bind(this.controller.getNewEventsFlag());
|
||||||
taskLabel.textProperty().bind(this.controller.getTaskTitle());
|
taskLabel.textProperty().bind(this.controller.getTaskTitle());
|
||||||
messageLabel.textProperty().bind(this.controller.getMessage());
|
messageLabel.textProperty().bind(this.controller.getMessage());
|
||||||
progressBar.progressProperty().bind(this.controller.getProgress());
|
progressBar.progressProperty().bind(this.controller.getProgress());
|
||||||
taskLabel.visibleProperty().bind(this.controller.getTasks().emptyProperty().not());
|
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
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2014 Basis Technology Corp.
|
* Copyright 2014-15 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -19,28 +19,27 @@
|
|||||||
package org.sleuthkit.autopsy.timeline.ui;
|
package org.sleuthkit.autopsy.timeline.ui;
|
||||||
|
|
||||||
import javafx.event.EventHandler;
|
import javafx.event.EventHandler;
|
||||||
import javafx.geometry.Point2D;
|
import javafx.event.EventType;
|
||||||
import javafx.scene.Cursor;
|
import javafx.scene.Cursor;
|
||||||
import javafx.scene.chart.Axis;
|
import javafx.scene.chart.Axis;
|
||||||
import javafx.scene.chart.Chart;
|
import javafx.scene.chart.Chart;
|
||||||
import javafx.scene.control.Tooltip;
|
import javafx.scene.control.ContextMenu;
|
||||||
import javafx.scene.input.MouseButton;
|
import javafx.scene.input.MouseButton;
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.scene.paint.Color;
|
import org.controlsfx.control.action.ActionGroup;
|
||||||
import javafx.scene.shape.Rectangle;
|
|
||||||
import org.joda.time.DateTime;
|
|
||||||
import org.joda.time.Interval;
|
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
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'.
|
* Interface for TimeLineViews that are 'charts'.
|
||||||
*
|
*
|
||||||
* @param <X> the type of values along the horizontal axis
|
* @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();
|
IntervalSelector<? extends X> getIntervalSelector();
|
||||||
|
|
||||||
void setIntervalSelector(IntervalSelector<? extends X> newIntervalSelector);
|
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
|
* derived classes should implement this so as to supply an appropriate
|
||||||
* subclass of {@link IntervalSelector}
|
* 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
|
* @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
|
* clear any references to previous interval selectors , including removing
|
||||||
@ -62,6 +58,14 @@ public interface TimeLineChart<X> extends TimeLineView {
|
|||||||
*/
|
*/
|
||||||
void clearIntervalSelector();
|
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
|
* drag handler class used by {@link TimeLineChart}s to create
|
||||||
* {@link IntervalSelector}s
|
* {@link IntervalSelector}s
|
||||||
@ -69,228 +73,87 @@ public interface TimeLineChart<X> extends TimeLineView {
|
|||||||
* @param <X> the type of values along the horizontal axis
|
* @param <X> the type of values along the horizontal axis
|
||||||
* @param <Y> the type of chart this is a drag handler for
|
* @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 Y chart;
|
||||||
|
|
||||||
private final Axis<X> dateAxis;
|
|
||||||
|
|
||||||
private double startX; //hanlder mainstains position of drag start
|
private double startX; //hanlder mainstains position of drag start
|
||||||
|
|
||||||
public ChartDragHandler(Y chart, Axis<X> dateAxis) {
|
public ChartDragHandler(Y chart) {
|
||||||
this.chart = chart;
|
this.chart = chart;
|
||||||
this.dateAxis = dateAxis;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(MouseEvent t) {
|
public void handle(MouseEvent mouseEvent) {
|
||||||
if (t.getButton() == MouseButton.SECONDARY) {
|
EventType<? extends MouseEvent> mouseEventType = mouseEvent.getEventType();
|
||||||
|
if (mouseEventType == MouseEvent.MOUSE_PRESSED) {
|
||||||
if (t.getEventType() == MouseEvent.MOUSE_PRESSED) {
|
|
||||||
//caputure x-position, incase we are repositioning existing selector
|
//caputure x-position, incase we are repositioning existing selector
|
||||||
startX = t.getX();
|
startX = mouseEvent.getX();
|
||||||
chart.setCursor(Cursor.E_RESIZE);
|
chart.setCursor(Cursor.H_RESIZE);
|
||||||
} else if (t.getEventType() == MouseEvent.MOUSE_DRAGGED) {
|
} else if (mouseEventType == MouseEvent.MOUSE_DRAGGED) {
|
||||||
if (chart.getIntervalSelector() == null) {
|
if (chart.getIntervalSelector() == null) {
|
||||||
//make new interval selector
|
//make new interval selector
|
||||||
chart.setIntervalSelector(chart.newIntervalSelector(t.getX(), dateAxis));
|
chart.setIntervalSelector(chart.newIntervalSelector());
|
||||||
chart.getIntervalSelector().heightProperty().bind(chart.heightProperty().subtract(dateAxis.heightProperty().subtract(dateAxis.tickLengthProperty())));
|
chart.getIntervalSelector().prefHeightProperty().bind(chart.heightProperty());
|
||||||
chart.getIntervalSelector().addEventHandler(MouseEvent.MOUSE_CLICKED, (MouseEvent event) -> {
|
startX = mouseEvent.getX();
|
||||||
if (event.getButton() == MouseButton.SECONDARY) {
|
chart.getIntervalSelector().relocate(startX, 0);
|
||||||
chart.clearIntervalSelector();
|
|
||||||
event.consume();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
startX = t.getX();
|
|
||||||
} else {
|
} else {
|
||||||
//resize/position existing selector
|
//resize/position existing selector
|
||||||
if (t.getX() > startX) {
|
if (mouseEvent.getX() > startX) {
|
||||||
chart.getIntervalSelector().setX(startX);
|
chart.getIntervalSelector().relocate(startX, 0);
|
||||||
chart.getIntervalSelector().setWidth(t.getX() - startX);
|
chart.getIntervalSelector().setPrefWidth(mouseEvent.getX() - startX);
|
||||||
} else {
|
} else {
|
||||||
chart.getIntervalSelector().setX(t.getX());
|
chart.getIntervalSelector().relocate(mouseEvent.getX(), 0);
|
||||||
chart.getIntervalSelector().setWidth(startX - t.getX());
|
chart.getIntervalSelector().setPrefWidth(startX - mouseEvent.getX());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (t.getEventType() == MouseEvent.MOUSE_RELEASED) {
|
chart.getIntervalSelector().autosize();
|
||||||
|
} else if (mouseEventType == MouseEvent.MOUSE_RELEASED) {
|
||||||
|
chart.setCursor(Cursor.DEFAULT);
|
||||||
|
} else if (mouseEventType == MouseEvent.MOUSE_CLICKED) {
|
||||||
chart.setCursor(Cursor.DEFAULT);
|
chart.setCursor(Cursor.DEFAULT);
|
||||||
}
|
}
|
||||||
t.consume();
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
* enum to represent whether the drag is a left/right-edge modification or a
|
||||||
* interactions with it.
|
* horizontal slide triggered by dragging the center
|
||||||
*
|
|
||||||
* @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
|
|
||||||
*/
|
*/
|
||||||
static abstract class IntervalSelector<X> extends Rectangle {
|
enum DragPosition {
|
||||||
|
|
||||||
private static final double STROKE_WIDTH = 3;
|
LEFT,
|
||||||
|
CENTER,
|
||||||
private static final double HALF_STROKE = STROKE_WIDTH / 2;
|
RIGHT
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@NbBundle.Messages({"TimeLineChart.zoomHistoryActionGroup.name=Zoom History"})
|
||||||
*
|
static ActionGroup newZoomHistoyActionGroup(TimeLineController controller) {
|
||||||
* @param i the interval represented by this selector
|
return new ActionGroup(Bundle.TimeLineChart_zoomHistoryActionGroup_name(),
|
||||||
*
|
new Back(controller),
|
||||||
* @return a modified version of {@code i} adjusted to suite the needs
|
new Forward(controller));
|
||||||
* 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,12 +25,11 @@ import javax.swing.SwingUtilities;
|
|||||||
import org.joda.time.format.DateTimeFormatter;
|
import org.joda.time.format.DateTimeFormatter;
|
||||||
import org.openide.nodes.Node;
|
import org.openide.nodes.Node;
|
||||||
import org.openide.util.NbBundle;
|
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.corecomponentinterfaces.DataContent;
|
||||||
import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
|
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
|
* 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
|
* {@link DataResultPanel}. That is, this class acts as a sort of bridge/adapter
|
||||||
* between a FilteredEventsModel instance and a DataResultPanel instance.
|
* 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
|
* the {@link DataResultPanel} that is the real view proxied by this class
|
||||||
*/
|
*/
|
||||||
private final DataResultPanel dataResultPanel;
|
private final DataResultPanel dataResultPanel;
|
||||||
|
|
||||||
private TimeLineController controller;
|
private final TimeLineController controller;
|
||||||
|
|
||||||
private FilteredEventsModel filteredEvents;
|
private final FilteredEventsModel filteredEvents;
|
||||||
|
|
||||||
private Set<Long> selectedEventIDs = new HashSet<>();
|
private Set<Long> selectedEventIDs = new HashSet<>();
|
||||||
|
|
||||||
@ -56,19 +55,11 @@ public class TimeLineResultView implements TimeLineView {
|
|||||||
return dataResultPanel;
|
return dataResultPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeLineResultView(DataContent dataContent) {
|
public TimeLineResultView(TimeLineController controller, DataContent dataContent) {
|
||||||
dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, 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.controller = controller;
|
||||||
|
this.filteredEvents = controller.getEventsModel();
|
||||||
|
dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, dataContent);
|
||||||
|
|
||||||
//set up listeners on relevant properties
|
//set up listeners on relevant properties
|
||||||
TimeLineController.getTimeZone().addListener((Observable observable) -> {
|
TimeLineController.getTimeZone().addListener((Observable observable) -> {
|
||||||
@ -78,18 +69,7 @@ public class TimeLineResultView implements TimeLineView {
|
|||||||
controller.getSelectedEventIDs().addListener((Observable o) -> {
|
controller.getSelectedEventIDs().addListener((Observable o) -> {
|
||||||
refresh();
|
refresh();
|
||||||
});
|
});
|
||||||
|
refresh();
|
||||||
setModel(controller.getEventsModel());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the Model for this View
|
|
||||||
*
|
|
||||||
* @param filteredEvents
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
synchronized public void setModel(final FilteredEventsModel filteredEvents) {
|
|
||||||
this.filteredEvents = filteredEvents;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,7 +23,10 @@ import java.util.Date;
|
|||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.fxml.FXML;
|
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.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
@ -50,8 +53,9 @@ public class TimeZonePanel extends TitledPane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
|
@NbBundle.Messages({"TimeZonePanel.title=Display Times In:"})
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
|
setText(Bundle.TimeZonePanel_title());
|
||||||
// localRadio.setText("Local Time Zone: " + getTimeZoneString(TimeZone.getDefault()));
|
// localRadio.setText("Local Time Zone: " + getTimeZoneString(TimeZone.getDefault()));
|
||||||
localRadio.setText(NbBundle.getMessage(this.getClass(), "TimeZonePanel.localRadio.text"));
|
localRadio.setText(NbBundle.getMessage(this.getClass(), "TimeZonePanel.localRadio.text"));
|
||||||
otherRadio.setText(NbBundle.getMessage(this.getClass(), "TimeZonePanel.otherRadio.text"));
|
otherRadio.setText(NbBundle.getMessage(this.getClass(), "TimeZonePanel.otherRadio.text"));
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<?import jfxtras.scene.control.*?>
|
<?import jfxtras.scene.control.*?>
|
||||||
<?import org.controlsfx.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>
|
<top>
|
||||||
<ToolBar fx:id="toolBar" prefWidth="200.0" BorderPane.alignment="CENTER">
|
<ToolBar fx:id="toolBar" prefWidth="200.0" BorderPane.alignment="CENTER">
|
||||||
<items>
|
<items>
|
||||||
@ -115,7 +115,7 @@
|
|||||||
</Separator>
|
</Separator>
|
||||||
<HBox>
|
<HBox>
|
||||||
<children>
|
<children>
|
||||||
<Button fx:id="zoomOutButton" mnemonicParsing="false">
|
<Button fx:id="zoomOutButton" contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false">
|
||||||
<graphic>
|
<graphic>
|
||||||
<ImageView pickOnBounds="true" preserveRatio="true">
|
<ImageView pickOnBounds="true" preserveRatio="true">
|
||||||
<image>
|
<image>
|
||||||
@ -127,7 +127,7 @@
|
|||||||
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
|
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
|
||||||
</HBox.margin>
|
</HBox.margin>
|
||||||
</Button>
|
</Button>
|
||||||
<Button fx:id="zoomInButton" mnemonicParsing="false">
|
<Button fx:id="zoomInButton" contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false">
|
||||||
<graphic>
|
<graphic>
|
||||||
<ImageView pickOnBounds="true" preserveRatio="true">
|
<ImageView pickOnBounds="true" preserveRatio="true">
|
||||||
<image>
|
<image>
|
||||||
@ -143,7 +143,7 @@
|
|||||||
</HBox>
|
</HBox>
|
||||||
<MenuButton fx:id="zoomMenuButton" mnemonicParsing="false">
|
<MenuButton fx:id="zoomMenuButton" mnemonicParsing="false">
|
||||||
<graphic>
|
<graphic>
|
||||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
<ImageView pickOnBounds="true" preserveRatio="true">
|
||||||
<image>
|
<image>
|
||||||
<Image url="@../images/magnifier-left.png" />
|
<Image url="@../images/magnifier-left.png" />
|
||||||
</image>
|
</image>
|
||||||
|
@ -33,8 +33,6 @@ import javafx.beans.value.ObservableValue;
|
|||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Rectangle2D;
|
|
||||||
import javafx.scene.SnapshotParameters;
|
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.MenuButton;
|
import javafx.scene.control.MenuButton;
|
||||||
@ -48,7 +46,6 @@ import javafx.scene.control.Tooltip;
|
|||||||
import javafx.scene.effect.Lighting;
|
import javafx.scene.effect.Lighting;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.image.WritableImage;
|
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.scene.layout.Background;
|
import javafx.scene.layout.Background;
|
||||||
import javafx.scene.layout.BackgroundFill;
|
import javafx.scene.layout.BackgroundFill;
|
||||||
@ -66,6 +63,7 @@ import jfxtras.scene.control.LocalDateTimeTextField;
|
|||||||
import org.controlsfx.control.NotificationPane;
|
import org.controlsfx.control.NotificationPane;
|
||||||
import org.controlsfx.control.RangeSlider;
|
import org.controlsfx.control.RangeSlider;
|
||||||
import org.controlsfx.control.action.Action;
|
import org.controlsfx.control.action.Action;
|
||||||
|
import org.controlsfx.control.action.ActionUtils;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.joda.time.DateTimeZone;
|
import org.joda.time.DateTimeZone;
|
||||||
import org.joda.time.Interval;
|
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.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
|
||||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
|
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.ZoomOut;
|
||||||
|
import org.sleuthkit.autopsy.timeline.actions.ZoomToEvents;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.events.TagsUpdatedEvent;
|
import org.sleuthkit.autopsy.timeline.events.TagsUpdatedEvent;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.TagsFilter;
|
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 static org.sleuthkit.autopsy.timeline.ui.Bundle.VisualizationPanel_tagsAddedOrDeleted;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane;
|
import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
|
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;
|
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Container for an {@link AbstractVisualization}, has a toolbar on top to
|
* A container for an {@link AbstractVisualizationPane}, has a toolbar on top to
|
||||||
* hold settings widgets supplied by contained {@link AbstractVisualization},
|
* hold settings widgets supplied by contained {@link AbstAbstractVisualization}
|
||||||
* and the histogram / timeselection on bottom. Also supplies containers for
|
* 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
|
* 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 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
|
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")
|
@GuardedBy("this")
|
||||||
private LoggedTask<Void> histogramTask;
|
private LoggedTask<Void> histogramTask;
|
||||||
|
|
||||||
private final NavPanel navPanel;
|
private final EventsTree eventsTree;
|
||||||
|
|
||||||
private AbstractVisualization<?, ?, ?, ?> visualization;
|
private AbstractVisualizationPane<?, ?, ?, ?> visualization;
|
||||||
|
|
||||||
//// range slider and histogram componenets
|
//// range slider and histogram componenets
|
||||||
@FXML
|
@FXML
|
||||||
@ -178,8 +177,8 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
|
|
||||||
private FilteredEventsModel filteredEvents;
|
private FilteredEventsModel filteredEvents;
|
||||||
|
|
||||||
private final ChangeListener<Object> rangeSliderListener
|
private final ChangeListener<Object> rangeSliderListener =
|
||||||
= (observable1, oldValue, newValue) -> {
|
(observable1, oldValue, newValue) -> {
|
||||||
if (rangeSlider.isHighValueChanging() == false && rangeSlider.isLowValueChanging() == false) {
|
if (rangeSlider.isHighValueChanging() == false && rangeSlider.isLowValueChanging() == false) {
|
||||||
Long minTime = filteredEvents.getMinTime() * 1000;
|
Long minTime = filteredEvents.getMinTime() * 1000;
|
||||||
controller.pushTimeRange(new Interval(
|
controller.pushTimeRange(new Interval(
|
||||||
@ -207,14 +206,15 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
|
|
||||||
static private final Lighting lighting = new Lighting();
|
static private final Lighting lighting = new Lighting();
|
||||||
|
|
||||||
public VisualizationPanel(NavPanel navPanel) {
|
public VisualizationPanel(TimeLineController controller, EventsTree eventsTree) {
|
||||||
this.navPanel = navPanel;
|
this.controller = controller;
|
||||||
|
this.eventsTree = eventsTree;
|
||||||
FXMLConstructor.construct(this, "VisualizationPanel.fxml"); // NON-NLS
|
FXMLConstructor.construct(this, "VisualizationPanel.fxml"); // NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML // This method is called by the FXMLLoader when initialization is complete
|
@FXML // This method is called by the FXMLLoader when initialization is complete
|
||||||
@NbBundle.Messages("VisualizationPanel.refresh=refresh")
|
@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 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 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
|
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
|
zoomMenuButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomMenuButton.text")); // NON-NLS
|
||||||
|
|
||||||
zoomOutButton.setOnAction(e -> {
|
snapShotButton.setOnAction(event ->
|
||||||
controller.pushZoomOutTime();
|
this.snapshot(snapShotResult -> {
|
||||||
});
|
new SaveSnapshotAsReport(controller, snapShotResult.getImage()).handle(event);
|
||||||
zoomInButton.setOnAction(e -> {
|
return null;
|
||||||
controller.pushZoomInTime();
|
}, null, null)
|
||||||
});
|
);
|
||||||
|
|
||||||
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.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapShotButton.text")); // NON-NLS
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
this.filteredEvents = controller.getEventsModel();
|
||||||
public synchronized void setController(TimeLineController controller) {
|
refreshTimeUI(controller.getEventsModel().timeRangeProperty().get());
|
||||||
this.controller = controller;
|
ActionUtils.configureButton(new ZoomOut(controller), zoomOutButton);
|
||||||
setModel(controller.getEventsModel());
|
ActionUtils.configureButton(new ZoomIn(controller), zoomInButton);
|
||||||
|
|
||||||
setViewMode(controller.viewModeProperty().get());
|
setViewMode(controller.viewModeProperty().get());
|
||||||
controller.getNeedsHistogramRebuild().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
|
controller.getNeedsHistogramRebuild().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
@ -329,39 +327,20 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
refreshHistorgram();
|
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) {
|
private void setViewMode(VisualizationMode visualizationMode) {
|
||||||
switch (visualizationMode) {
|
switch (visualizationMode) {
|
||||||
case COUNTS:
|
case COUNTS:
|
||||||
setVisualization(new CountsViewPane(partPane, contextPane, spacer));
|
setVisualization(new CountsViewPane(controller, partPane, contextPane, spacer));
|
||||||
countsToggle.setSelected(true);
|
countsToggle.setSelected(true);
|
||||||
break;
|
break;
|
||||||
case DETAIL:
|
case DETAIL:
|
||||||
setVisualization(new DetailViewPane(partPane, contextPane, spacer));
|
setVisualization(new DetailViewPane(controller, partPane, contextPane, spacer));
|
||||||
detailsToggle.setSelected(true);
|
detailsToggle.setSelected(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void setVisualization(final AbstractVisualization<?, ?, ?, ?> newViz) {
|
private synchronized void setVisualization(final AbstractVisualizationPane<?, ?, ?, ?> newViz) {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
synchronized (VisualizationPanel.this) {
|
synchronized (VisualizationPanel.this) {
|
||||||
if (visualization != null) {
|
if (visualization != null) {
|
||||||
@ -370,24 +349,25 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
visualization = newViz;
|
visualization = newViz;
|
||||||
|
visualization.update();
|
||||||
toolBar.getItems().addAll(newViz.getSettingsNodes());
|
toolBar.getItems().addAll(newViz.getSettingsNodes());
|
||||||
|
|
||||||
visualization.setController(controller);
|
|
||||||
notificationPane.setContent(visualization);
|
notificationPane.setContent(visualization);
|
||||||
if (visualization instanceof DetailViewPane) {
|
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) {
|
if (newValue == false) {
|
||||||
|
|
||||||
notificationPane.setContent(new StackPane(visualization, new Region() {
|
notificationPane.setContent(
|
||||||
|
new StackPane(visualization,
|
||||||
|
new Region() {
|
||||||
{
|
{
|
||||||
setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)));
|
setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||||
setOpacity(.3);
|
setOpacity(.3);
|
||||||
}
|
}
|
||||||
}, new NoEventsDialog(() -> {
|
},
|
||||||
notificationPane.setContent(visualization);
|
new NoEventsDialog(() -> notificationPane.setContent(visualization))));
|
||||||
})));
|
|
||||||
} else {
|
} else {
|
||||||
notificationPane.setContent(visualization);
|
notificationPane.setContent(visualization);
|
||||||
}
|
}
|
||||||
@ -552,7 +532,6 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
private NoEventsDialog(Runnable closeCallback) {
|
private NoEventsDialog(Runnable closeCallback) {
|
||||||
this.closeCallback = closeCallback;
|
this.closeCallback = closeCallback;
|
||||||
FXMLConstructor.construct(this, "NoEventsDialog.fxml"); // NON-NLS
|
FXMLConstructor.construct(this, "NoEventsDialog.fxml"); // NON-NLS
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@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
|
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
|
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);
|
dismissButton.setOnAction(actionEvent -> closeCallback.run());
|
||||||
zoomButton.setOnAction(zoomOutAction);
|
|
||||||
zoomButton.disableProperty().bind(zoomOutAction.disabledProperty());
|
|
||||||
|
|
||||||
dismissButton.setOnAction(e -> {
|
|
||||||
closeCallback.run();
|
|
||||||
});
|
|
||||||
Action defaultFiltersAction = new ResetFilters(controller);
|
Action defaultFiltersAction = new ResetFilters(controller);
|
||||||
resetFiltersButton.setOnAction(defaultFiltersAction);
|
resetFiltersButton.setOnAction(defaultFiltersAction);
|
||||||
resetFiltersButton.disableProperty().bind(defaultFiltersAction.disabledProperty());
|
resetFiltersButton.disableProperty().bind(defaultFiltersAction.disabledProperty());
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013 Basis Technology Corp.
|
* Copyright 2013-15 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* 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.selectEventType=Select Event Type
|
||||||
Timeline.ui.countsview.menuItem.selectTimeandType=Select Time and Type
|
Timeline.ui.countsview.menuItem.selectTimeandType=Select Time and Type
|
||||||
Timeline.ui.countsview.menuItem.zoomIntoTimeRange=Zoom into Time Range
|
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.name=Updating Counts Graph
|
||||||
CountsViewPane.loggedTask.prepUpdate=preparing update
|
CountsViewPane.loggedTask.prepUpdate=preparing update
|
||||||
CountsViewPane.loggedTask.resetUI=resetting ui
|
CountsViewPane.loggedTask.resetUI=resetting ui
|
||||||
CountsViewPane.tooltip.text={0} {1} events\nbetween {2}\nand {3}
|
CountsViewPane.tooltip.text={0} {1} events\nbetween {2}\nand {3}
|
||||||
CountsViewPane.loggedTask.updatingCounts=updating counts
|
CountsViewPane.loggedTask.updatingCounts=updating counts
|
||||||
CountsViewPane.loggedTask.wrappingUp=wrapping up
|
CountsViewPane.loggedTask.wrappingUp=wrapping up
|
||||||
EventCountsChart.contextMenu.zoomHistory.name=Zoom History
|
|
||||||
CountsViewPane.scaleLabel.text=Scale\:
|
CountsViewPane.scaleLabel.text=Scale\:
|
||||||
CountsViewPane.logRadio.text=Logarithmic
|
CountsViewPane.logRadio.text=Logarithmic
|
||||||
CountsViewPane.linearRadio.text=Linear
|
CountsViewPane.linearRadio.text=Linear
|
||||||
|
@ -66,14 +66,13 @@ import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
|||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
|
||||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
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;
|
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
|
* TODO: refactor common code out of this class and ClusterChartPane into
|
||||||
* AbstractChartView
|
* 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();
|
private static final Effect SELECTED_NODE_EFFECT = new Lighting();
|
||||||
|
|
||||||
@ -213,8 +212,6 @@ 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.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);
|
node.setCursor(Cursor.HAND);
|
||||||
|
|
||||||
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(
|
final Tooltip tooltip = new Tooltip(
|
||||||
NbBundle.getMessage(this.getClass(), "CountsViewPane.tooltip.text",
|
NbBundle.getMessage(this.getClass(), "CountsViewPane.tooltip.text",
|
||||||
count,
|
count,
|
||||||
@ -224,6 +221,8 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
|
|||||||
rangeInfo.getTickFormatter())));
|
rangeInfo.getTickFormatter())));
|
||||||
tooltip.setGraphic(new ImageView(et.getFXImage()));
|
tooltip.setGraphic(new ImageView(et.getFXImage()));
|
||||||
Tooltip.install(node, tooltip);
|
Tooltip.install(node, tooltip);
|
||||||
|
|
||||||
|
node.setOnMouseEntered((MouseEvent event) -> {
|
||||||
node.setEffect(new DropShadow(10, et.getColor()));
|
node.setEffect(new DropShadow(10, et.getColor()));
|
||||||
});
|
});
|
||||||
node.setOnMouseExited((MouseEvent event) -> {
|
node.setOnMouseExited((MouseEvent event) -> {
|
||||||
@ -279,13 +278,15 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public CountsViewPane(Pane partPane, Pane contextPane, Region spacer) {
|
public CountsViewPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) {
|
||||||
super(partPane, contextPane, spacer);
|
super(controller, partPane, contextPane, spacer);
|
||||||
chart = new EventCountsChart(dateAxis, countAxis);
|
chart = new EventCountsChart(controller, dateAxis, countAxis);
|
||||||
setChartClickHandler();
|
setChartClickHandler();
|
||||||
chart.setData(dataSets);
|
chart.setData(dataSets);
|
||||||
setCenter(chart);
|
setCenter(chart);
|
||||||
|
|
||||||
|
Tooltip.install(chart, getDragTooltip());
|
||||||
|
|
||||||
settingsNodes = new ArrayList<>(new CountsViewSettingsPane().getChildrenUnmodifiable());
|
settingsNodes = new ArrayList<>(new CountsViewSettingsPane().getChildrenUnmodifiable());
|
||||||
|
|
||||||
dateAxis.getTickMarks().addListener((Observable observable) -> {
|
dateAxis.getTickMarks().addListener((Observable observable) -> {
|
||||||
@ -310,9 +311,6 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected NumberAxis getYAxis() {
|
protected NumberAxis getYAxis() {
|
||||||
return countAxis;
|
return countAxis;
|
||||||
@ -534,7 +532,7 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
|
|||||||
scaleLabel.setText(NbBundle.getMessage(this.getClass(), "CountsViewPane.scaleLabel.text"));
|
scaleLabel.setText(NbBundle.getMessage(this.getClass(), "CountsViewPane.scaleLabel.text"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public CountsViewSettingsPane() {
|
CountsViewSettingsPane() {
|
||||||
FXMLConstructor.construct(this, "CountsViewSettingsPane.fxml"); // NON-NLS
|
FXMLConstructor.construct(this, "CountsViewSettingsPane.fxml"); // NON-NLS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2014 Basis Technology Corp.
|
* Copyright 2014-15 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -19,24 +19,19 @@
|
|||||||
package org.sleuthkit.autopsy.timeline.ui.countsview;
|
package org.sleuthkit.autopsy.timeline.ui.countsview;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.MissingResourceException;
|
||||||
import javafx.scene.chart.Axis;
|
|
||||||
import javafx.scene.chart.CategoryAxis;
|
import javafx.scene.chart.CategoryAxis;
|
||||||
import javafx.scene.chart.NumberAxis;
|
import javafx.scene.chart.NumberAxis;
|
||||||
import javafx.scene.chart.StackedBarChart;
|
import javafx.scene.chart.StackedBarChart;
|
||||||
import javafx.scene.control.ContextMenu;
|
import javafx.scene.control.ContextMenu;
|
||||||
import javafx.scene.input.MouseButton;
|
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
import org.controlsfx.control.action.ActionGroup;
|
|
||||||
import org.controlsfx.control.action.ActionUtils;
|
import org.controlsfx.control.action.ActionUtils;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.joda.time.Interval;
|
import org.joda.time.Interval;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
import org.sleuthkit.autopsy.timeline.ui.IntervalSelector;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
|
||||||
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
|
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
|
||||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
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
|
* Customized {@link StackedBarChart<String, Number>} used to display the event
|
||||||
* counts in {@link CountsViewPane}
|
* 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;
|
private IntervalSelector<? extends String> intervalSelector;
|
||||||
|
|
||||||
@ -59,8 +59,9 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
|||||||
*/
|
*/
|
||||||
private RangeDivisionInfo rangeInfo;
|
private RangeDivisionInfo rangeInfo;
|
||||||
|
|
||||||
EventCountsChart(CategoryAxis dateAxis, NumberAxis countAxis) {
|
EventCountsChart(TimeLineController controller, CategoryAxis dateAxis, NumberAxis countAxis) {
|
||||||
super(dateAxis, countAxis);
|
super(dateAxis, countAxis);
|
||||||
|
this.controller = controller;
|
||||||
//configure constant properties on axes and chart
|
//configure constant properties on axes and chart
|
||||||
dateAxis.setAnimated(true);
|
dateAxis.setAnimated(true);
|
||||||
dateAxis.setLabel(null);
|
dateAxis.setLabel(null);
|
||||||
@ -80,19 +81,13 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
|||||||
setAnimated(true);
|
setAnimated(true);
|
||||||
setTitle(null);
|
setTitle(null);
|
||||||
|
|
||||||
//use one handler with an if chain because it maintains state
|
ChartDragHandler<String, EventCountsChart> chartDragHandler = new ChartDragHandler<>(this);
|
||||||
ChartDragHandler<String, EventCountsChart> dragHandler = new ChartDragHandler<>(this, getXAxis());
|
setOnMousePressed(chartDragHandler);
|
||||||
setOnMousePressed(dragHandler);
|
setOnMouseReleased(chartDragHandler);
|
||||||
setOnMouseReleased(dragHandler);
|
setOnMouseDragged(chartDragHandler);
|
||||||
setOnMouseDragged(dragHandler);
|
|
||||||
|
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
|
@Override
|
||||||
@ -102,16 +97,21 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final synchronized void setController(TimeLineController controller) {
|
public ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException {
|
||||||
this.controller = controller;
|
if (chartContextMenu != null) {
|
||||||
setModel(this.controller.getEventsModel());
|
chartContextMenu.hide();
|
||||||
//we have defered creating context menu until control is available
|
}
|
||||||
contextMenu = ActionUtils.createContextMenu(
|
|
||||||
Arrays.asList(new ActionGroup(
|
chartContextMenu = ActionUtils.createContextMenu(
|
||||||
NbBundle.getMessage(this.getClass(), "EventCountsChart.contextMenu.zoomHistory.name"),
|
Arrays.asList(
|
||||||
new Back(controller),
|
TimeLineChart.newZoomHistoyActionGroup(controller)));
|
||||||
new Forward(controller))));
|
chartContextMenu.setAutoHide(true);
|
||||||
contextMenu.setAutoHide(true);
|
return chartContextMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TimeLineController getController() {
|
||||||
|
return controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -126,16 +126,8 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setModel(FilteredEventsModel filteredEvents) {
|
public CountsIntervalSelector newIntervalSelector() {
|
||||||
filteredEvents.zoomParametersProperty().addListener(o -> {
|
return new CountsIntervalSelector(this);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -145,7 +137,7 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
|||||||
* @return the context menu for this chart
|
* @return the context menu for this chart
|
||||||
*/
|
*/
|
||||||
ContextMenu getContextMenu() {
|
ContextMenu getContextMenu() {
|
||||||
return contextMenu;
|
return chartContextMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setRangeInfo(RangeDivisionInfo rangeInfo) {
|
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
|
* Interval Selector for the counts chart, adjusts interval based on
|
||||||
* rangeInfo to include final period
|
* 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) {
|
private final EventCountsChart countsChart;
|
||||||
super(x, height, axis, controller);
|
|
||||||
|
CountsIntervalSelector(EventCountsChart chart) {
|
||||||
|
super(chart);
|
||||||
|
this.countsChart = chart;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -195,12 +190,13 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
|||||||
final DateTime lowerDate = new DateTime(lowerBound, TimeLineController.getJodaTimeZone());
|
final DateTime lowerDate = new DateTime(lowerBound, TimeLineController.getJodaTimeZone());
|
||||||
final DateTime upperDate = new DateTime(upperBound, TimeLineController.getJodaTimeZone());
|
final DateTime upperDate = new DateTime(upperBound, TimeLineController.getJodaTimeZone());
|
||||||
//add extra block to end that gets cut of by conversion from string/category.
|
//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
|
@Override
|
||||||
protected DateTime parseDateTime(String date) {
|
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,24 +73,23 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
|
|||||||
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
|
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
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.utils.RangeDivisionInfo;
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
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.
|
* TimeLineView.
|
||||||
*
|
*
|
||||||
* This class listens to changes in the assigned {@link FilteredEventsModel} and
|
* 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.
|
* requested events.
|
||||||
*
|
*
|
||||||
* Concurrency Policy: Access to the private members clusterChart, dateAxis,
|
* Concurrency Policy: Access to the private members clusterChart, dateAxis,
|
||||||
* EventTypeMap, and dataSets is all linked directly to the ClusterChart which
|
* EventTypeMap, and dataSets is all linked directly to the ClusterChart which
|
||||||
* must only be manipulated on the JavaFx thread.
|
* 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 final static Logger LOGGER = Logger.getLogger(DetailViewPane.class.getName());
|
||||||
|
|
||||||
@ -114,13 +113,15 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
|
|||||||
return chart.getEventBundles();
|
return chart.getEventBundles();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DetailViewPane(Pane partPane, Pane contextPane, Region spacer) {
|
|
||||||
super(partPane, contextPane, spacer);
|
public DetailViewPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) {
|
||||||
chart = new EventDetailChart(dateAxis, verticalAxis, selectedNodes);
|
super(controller, partPane, contextPane, spacer);
|
||||||
|
chart = new EventDetailsChart(controller, dateAxis, verticalAxis, selectedNodes);
|
||||||
setChartClickHandler();
|
setChartClickHandler();
|
||||||
chart.setData(dataSets);
|
chart.setData(dataSets);
|
||||||
setCenter(chart);
|
setCenter(chart);
|
||||||
|
|
||||||
|
|
||||||
chart.setPrefHeight(USE_COMPUTED_SIZE);
|
chart.setPrefHeight(USE_COMPUTED_SIZE);
|
||||||
|
|
||||||
settingsNodes = new ArrayList<>(new DetailViewSettingsPane().getChildrenUnmodifiable());
|
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) {
|
private void incrementScrollValue(int factor) {
|
||||||
vertScrollBar.valueProperty().set(Math.max(0, Math.min(100, vertScrollBar.getValue() + factor * (chart.getHeight() / chart.maxVScrollProperty().get()))));
|
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);
|
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.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
|
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
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 static org.sleuthkit.autopsy.timeline.ui.detailview.EventBundleNodeBase.show;
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
||||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||||
@ -96,7 +97,7 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
|||||||
b.setManaged(show);
|
b.setManaged(show);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final EventDetailChart chart;
|
protected final EventDetailsChart chart;
|
||||||
final SimpleObjectProperty<DescriptionLoD> descLOD = new SimpleObjectProperty<>();
|
final SimpleObjectProperty<DescriptionLoD> descLOD = new SimpleObjectProperty<>();
|
||||||
final SimpleObjectProperty<DescriptionVisibility> descVisibility = new SimpleObjectProperty<>(DescriptionVisibility.SHOWN);
|
final SimpleObjectProperty<DescriptionVisibility> descVisibility = new SimpleObjectProperty<>(DescriptionVisibility.SHOWN);
|
||||||
protected final BundleType eventBundle;
|
protected final BundleType eventBundle;
|
||||||
@ -119,9 +120,9 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
|||||||
final ImageView tagIV = new ImageView(TAG);
|
final ImageView tagIV = new ImageView(TAG);
|
||||||
final HBox infoHBox = new HBox(5, descrLabel, countLabel, hashIV, tagIV);
|
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.eventBundle = eventBundle;
|
||||||
this.parentNode = parentNode;
|
this.parentNode = parentNode;
|
||||||
this.chart = chart;
|
this.chart = chart;
|
||||||
@ -156,7 +157,6 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
|||||||
infoHBox.setMaxWidth(USE_PREF_SIZE);
|
infoHBox.setMaxWidth(USE_PREF_SIZE);
|
||||||
infoHBox.setPadding(new Insets(2, 5, 2, 5));
|
infoHBox.setPadding(new Insets(2, 5, 2, 5));
|
||||||
infoHBox.setAlignment(Pos.TOP_LEFT);
|
infoHBox.setAlignment(Pos.TOP_LEFT);
|
||||||
infoHBox.setPickOnBounds(true);
|
|
||||||
|
|
||||||
//set up subnode pane sizing contraints
|
//set up subnode pane sizing contraints
|
||||||
subNodePane.setPrefHeight(USE_COMPUTED_SIZE);
|
subNodePane.setPrefHeight(USE_COMPUTED_SIZE);
|
||||||
@ -165,27 +165,34 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
|||||||
subNodePane.setMinWidth(USE_PREF_SIZE);
|
subNodePane.setMinWidth(USE_PREF_SIZE);
|
||||||
subNodePane.setMaxWidth(USE_PREF_SIZE);
|
subNodePane.setMaxWidth(USE_PREF_SIZE);
|
||||||
|
|
||||||
|
Tooltip.install(this, this.tooltip);
|
||||||
|
|
||||||
//set up mouse hover effect and tooltip
|
//set up mouse hover effect and tooltip
|
||||||
setOnMouseEntered((MouseEvent e) -> {
|
setOnMouseEntered((MouseEvent e) -> {
|
||||||
/*
|
/*
|
||||||
* defer tooltip creation till needed, this had a surprisingly large
|
* defer tooltip content creation till needed, this had a
|
||||||
* impact on speed of loading the chart
|
* surprisingly large impact on speed of loading the chart
|
||||||
*/
|
*/
|
||||||
installTooltip();
|
installTooltip();
|
||||||
|
Tooltip.uninstall(chart, AbstractVisualizationPane.getDragTooltip());
|
||||||
showHoverControls(true);
|
showHoverControls(true);
|
||||||
toFront();
|
toFront();
|
||||||
|
|
||||||
});
|
});
|
||||||
setOnMouseExited((MouseEvent event) -> {
|
setOnMouseExited((MouseEvent event) -> {
|
||||||
showHoverControls(false);
|
showHoverControls(false);
|
||||||
if (parentNode != null) {
|
if (parentNode != null) {
|
||||||
parentNode.showHoverControls(true);
|
parentNode.showHoverControls(true);
|
||||||
|
} else {
|
||||||
|
Tooltip.install(chart, AbstractVisualizationPane.getDragTooltip());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setDescriptionVisibility(DescriptionVisibility.SHOWN);
|
|
||||||
descVisibility.addListener((ObservableValue<? extends DescriptionVisibility> observable, DescriptionVisibility oldValue, DescriptionVisibility newValue) -> {
|
descVisibility.addListener((ObservableValue<? extends DescriptionVisibility> observable, DescriptionVisibility oldValue, DescriptionVisibility newValue) -> {
|
||||||
setDescriptionVisibility(newValue);
|
setDescriptionVisibiltiyImpl(newValue);
|
||||||
});
|
});
|
||||||
|
setDescriptionVisibiltiyImpl(DescriptionVisibility.SHOWN);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final DescriptionLoD getDescriptionLoD() {
|
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}"})
|
"EventBundleNodeBase.tooltip.text={0} {1} events\n{2}\nbetween\t{3}\nand \t{4}"})
|
||||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||||
private void installTooltip() {
|
private void installTooltip() {
|
||||||
if (tooltip == null) {
|
if (tooltip.getText().equalsIgnoreCase("loading...")) {
|
||||||
final Task<String> tooltTipTask = new Task<String>() {
|
final Task<String> tooltTipTask = new Task<String>() {
|
||||||
|
{
|
||||||
|
updateTitle("loading tooltip");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String call() throws Exception {
|
protected String call() throws Exception {
|
||||||
@ -252,13 +262,10 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
|||||||
protected void succeeded() {
|
protected void succeeded() {
|
||||||
super.succeeded();
|
super.succeeded();
|
||||||
try {
|
try {
|
||||||
tooltip = new Tooltip(get());
|
tooltip.setText(get());
|
||||||
tooltip.setAutoHide(true);
|
tooltip.setGraphic(null);
|
||||||
Tooltip.install(EventBundleNodeBase.this, tooltip);
|
|
||||||
} catch (InterruptedException | ExecutionException ex) {
|
} catch (InterruptedException | ExecutionException ex) {
|
||||||
LOGGER.log(Level.SEVERE, "Tooltip generation failed.", 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;
|
return subNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract void setDescriptionVisibility(DescriptionVisibility get);
|
abstract void setDescriptionVisibiltiyImpl(DescriptionVisibility get);
|
||||||
|
|
||||||
void showHoverControls(final boolean showControls) {
|
void showHoverControls(final boolean showControls) {
|
||||||
Effect dropShadow = dropShadowMap.computeIfAbsent(getEventType(),
|
Effect dropShadow = dropShadowMap.computeIfAbsent(getEventType(),
|
||||||
eventType -> new DropShadow(-10, eventType.getColor()));
|
eventType -> new DropShadow(-10, eventType.getColor()));
|
||||||
setEffect(showControls ? dropShadow : null);
|
setEffect(showControls ? dropShadow : null);
|
||||||
|
enableTooltip(showControls);
|
||||||
if (parentNode != null) {
|
if (parentNode != null) {
|
||||||
|
parentNode.enableTooltip(false);
|
||||||
parentNode.showHoverControls(false);
|
parentNode.showHoverControls(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final EventType getEventType() {
|
final EventType getEventType() {
|
||||||
@ -330,8 +340,15 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
|||||||
*/
|
*/
|
||||||
abstract void setDescriptionWidth(double w);
|
abstract void setDescriptionWidth(double w);
|
||||||
|
|
||||||
void setDescriptionVisibilityLevel(DescriptionVisibility get) {
|
void setDescriptionVisibility(DescriptionVisibility get) {
|
||||||
descVisibility.set(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 plusButton = ActionUtils.createButton(new ExpandClusterAction(), ActionUtils.ActionTextBehavior.HIDE);
|
||||||
final Button minusButton = ActionUtils.createButton(new CollapseClusterAction(), 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);
|
super(chart, eventCluster, parentNode);
|
||||||
setMinHeight(24);
|
setMinHeight(24);
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setDescriptionVisibility(DescriptionVisibility descrVis) {
|
void setDescriptionVisibiltiyImpl(DescriptionVisibility descrVis) {
|
||||||
final int size = getEventBundle().getEventIDs().size();
|
final int size = getEventBundle().getEventIDs().size();
|
||||||
switch (descrVis) {
|
switch (descrVis) {
|
||||||
case HIDDEN:
|
case HIDDEN:
|
||||||
|
@ -53,6 +53,7 @@ import javafx.scene.chart.Axis;
|
|||||||
import javafx.scene.chart.NumberAxis;
|
import javafx.scene.chart.NumberAxis;
|
||||||
import javafx.scene.chart.XYChart;
|
import javafx.scene.chart.XYChart;
|
||||||
import javafx.scene.control.ContextMenu;
|
import javafx.scene.control.ContextMenu;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.input.MouseButton;
|
import javafx.scene.input.MouseButton;
|
||||||
@ -62,14 +63,11 @@ import javafx.scene.shape.StrokeLineCap;
|
|||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||||
import org.controlsfx.control.action.Action;
|
import org.controlsfx.control.action.Action;
|
||||||
import org.controlsfx.control.action.ActionGroup;
|
|
||||||
import org.controlsfx.control.action.ActionUtils;
|
import org.controlsfx.control.action.ActionUtils;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.joda.time.Interval;
|
import org.joda.time.Interval;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
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.EventBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
|
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
|
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.datamodel.eventtype.EventType;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
|
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.ui.TimeLineChart;
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
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
|
* //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 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
|
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_Y_OFFSET = 5;
|
||||||
private static final int PROJECTED_LINE_STROKE_WIDTH = 5;
|
private static final int PROJECTED_LINE_STROKE_WIDTH = 5;
|
||||||
private static final int MINIMUM_EVENT_NODE_GAP = 4;
|
private static final int MINIMUM_EVENT_NODE_GAP = 4;
|
||||||
|
|
||||||
|
|
||||||
|
private final TimeLineController controller;
|
||||||
|
private final FilteredEventsModel filteredEvents;
|
||||||
|
|
||||||
private ContextMenu chartContextMenu;
|
private ContextMenu chartContextMenu;
|
||||||
|
|
||||||
private TimeLineController controller;
|
|
||||||
|
|
||||||
private FilteredEventsModel filteredEvents;
|
|
||||||
|
public ContextMenu getChartContextMenu() {
|
||||||
|
return chartContextMenu;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* a user positionable vertical line to help compare events
|
* 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);
|
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);
|
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);
|
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
|
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);
|
truncateAll.addListener(layoutInvalidationListener);
|
||||||
truncateWidth.addListener(layoutInvalidationListener);
|
truncateWidth.addListener(layoutInvalidationListener);
|
||||||
descrVisibility.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
|
//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
|
//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());
|
setPrefHeight(boundsInLocalProperty().get().getHeight());
|
||||||
});
|
});
|
||||||
|
|
||||||
///////set up mouse listeners
|
ChartDragHandler<DateTime, EventDetailsChart> chartDragHandler = new ChartDragHandler<>(this);
|
||||||
setOnMouseClicked((MouseEvent clickEvent) -> {
|
setOnMousePressed(chartDragHandler);
|
||||||
if (chartContextMenu != null) {
|
setOnMouseReleased(chartDragHandler);
|
||||||
chartContextMenu.hide();
|
setOnMouseDragged(chartDragHandler);
|
||||||
}
|
|
||||||
if (clickEvent.getButton() == MouseButton.SECONDARY && clickEvent.isStillSincePress()) {
|
setOnMouseClicked(new MouseClickedHandler<>(this));
|
||||||
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);
|
|
||||||
|
|
||||||
this.selectedNodes = selectedNodes;
|
this.selectedNodes = selectedNodes;
|
||||||
this.selectedNodes.addListener(new SelectionChangeHandler());
|
this.selectedNodes.addListener(new SelectionChangeHandler());
|
||||||
@ -236,21 +247,20 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
return bundles;
|
return bundles;
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeLineController getController() {
|
@Override
|
||||||
|
public TimeLineController getController() {
|
||||||
return controller;
|
return controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NbBundle.Messages({"EventDetailChart.chartContextMenu.placeMarker.name=Place Marker",
|
@Override
|
||||||
"EventDetailChart.contextMenu.zoomHistory.name=Zoom History"})
|
public ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException {
|
||||||
ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException {
|
|
||||||
if (chartContextMenu != null) {
|
if (chartContextMenu != null) {
|
||||||
chartContextMenu.hide();
|
chartContextMenu.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
chartContextMenu = ActionUtils.createContextMenu(Arrays.asList(new PlaceMarkerAction(clickEvent),
|
chartContextMenu = ActionUtils.createContextMenu(Arrays.asList(new PlaceMarkerAction(clickEvent),
|
||||||
new ActionGroup(Bundle.EventDetailChart_contextMenu_zoomHistory_name(),
|
// new StartIntervalSelectionAction(clickEvent, dragHandler),
|
||||||
new Back(controller),
|
TimeLineChart.newZoomHistoyActionGroup(controller)));
|
||||||
new Forward(controller))));
|
|
||||||
chartContextMenu.setAutoHide(true);
|
chartContextMenu.setAutoHide(true);
|
||||||
return chartContextMenu;
|
return chartContextMenu;
|
||||||
}
|
}
|
||||||
@ -266,32 +276,8 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void setController(TimeLineController controller) {
|
public IntervalSelector<DateTime> newIntervalSelector() {
|
||||||
this.controller = controller;
|
return new DetailIntervalSelector(this);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void setBandByType(Boolean t1) {
|
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
|
* 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
|
* @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) {
|
public DateTime getDateTimeForPosition(double x) {
|
||||||
return getXAxis().getValueForDisplay(getXAxis().parentToLocal(x, 0).getX());
|
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);
|
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);
|
stripeNodeMap.put(eventStripe, stripeNode);
|
||||||
nodeGroup.getChildren().add(stripeNode);
|
nodeGroup.getChildren().add(stripeNode);
|
||||||
data.setNode(stripeNode);
|
data.setNode(stripeNode);
|
||||||
@ -498,7 +484,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
bundleNode.setVisible(true);
|
bundleNode.setVisible(true);
|
||||||
bundleNode.setManaged(true);
|
bundleNode.setManaged(true);
|
||||||
//apply advanced layout description visibility options
|
//apply advanced layout description visibility options
|
||||||
bundleNode.setDescriptionVisibilityLevel(descrVisibility.get());
|
bundleNode.setDescriptionVisibility(descrVisibility.get());
|
||||||
bundleNode.setDescriptionWidth(truncateAll.get() ? truncateWidth.get() : USE_PREF_SIZE);
|
bundleNode.setDescriptionWidth(truncateAll.get() ? truncateWidth.get() : USE_PREF_SIZE);
|
||||||
|
|
||||||
//do recursive layout
|
//do recursive layout
|
||||||
@ -586,8 +572,8 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
|
|
||||||
static private class DetailIntervalSelector extends IntervalSelector<DateTime> {
|
static private class DetailIntervalSelector extends IntervalSelector<DateTime> {
|
||||||
|
|
||||||
DetailIntervalSelector(double x, double height, Axis<DateTime> axis, TimeLineController controller) {
|
DetailIntervalSelector(EventDetailsChart chart) {
|
||||||
super(x, height, axis, controller);
|
super(chart);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -608,6 +594,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
|
|
||||||
private class PlaceMarkerAction extends Action {
|
private class PlaceMarkerAction extends Action {
|
||||||
|
|
||||||
|
@NbBundle.Messages({"EventDetailChart.chartContextMenu.placeMarker.name=Place Marker"})
|
||||||
PlaceMarkerAction(MouseEvent clickEvent) {
|
PlaceMarkerAction(MouseEvent clickEvent) {
|
||||||
super(Bundle.EventDetailChart_chartContextMenu_placeMarker_name());
|
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())
|
.flatMap(detailNode -> detailNode.getEventIDs().stream())
|
||||||
.collect(Collectors.toList()));
|
.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;
|
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> {
|
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 HBox clustersHBox = new HBox();
|
||||||
private final ImageView eventTypeImageView = new ImageView();
|
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);
|
super(chart, eventStripe, parentNode);
|
||||||
|
|
||||||
setMinHeight(48);
|
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);
|
hideButton = ActionUtils.createButton(hideClusterAction, ActionUtils.ActionTextBehavior.HIDE);
|
||||||
configureLoDButton(hideButton);
|
configureLoDButton(hideButton);
|
||||||
|
|
||||||
@ -115,7 +115,7 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setDescriptionVisibility(DescriptionVisibility descrVis) {
|
void setDescriptionVisibiltiyImpl(DescriptionVisibility descrVis) {
|
||||||
final int size = getEventStripe().getEventIDs().size();
|
final int size = getEventStripe().getEventIDs().size();
|
||||||
|
|
||||||
switch (descrVis) {
|
switch (descrVis) {
|
||||||
@ -167,7 +167,7 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
|
|||||||
contextMenu = new ContextMenu();
|
contextMenu = new ContextMenu();
|
||||||
contextMenu.setAutoHide(true);
|
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);
|
MenuItem hideDescriptionMenuItem = ActionUtils.createMenuItem(hideClusterAction);
|
||||||
contextMenu.getItems().addAll(hideDescriptionMenuItem);
|
contextMenu.getItems().addAll(hideDescriptionMenuItem);
|
||||||
contextMenu.getItems().addAll(chartContextMenu.getItems());
|
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.coreutils.ThreadConfined;
|
||||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
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.EventBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
|
||||||
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
|
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
|
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
|
* out. Right clicking on a item in the tree shows a context menu to show/hide
|
||||||
* it.
|
* it.
|
||||||
*/
|
*/
|
||||||
public class NavPanel extends BorderPane implements TimeLineView {
|
final public class EventsTree extends BorderPane {
|
||||||
|
|
||||||
private TimeLineController controller;
|
private final TimeLineController controller;
|
||||||
|
|
||||||
private FilteredEventsModel filteredEvents;
|
|
||||||
|
|
||||||
private DetailViewPane detailViewPane;
|
private DetailViewPane detailViewPane;
|
||||||
|
|
||||||
@ -76,8 +72,10 @@ public class NavPanel extends BorderPane implements TimeLineView {
|
|||||||
@FXML
|
@FXML
|
||||||
private ComboBox<Comparator<TreeItem<EventBundle<?>>>> sortByBox;
|
private ComboBox<Comparator<TreeItem<EventBundle<?>>>> sortByBox;
|
||||||
|
|
||||||
public NavPanel() {
|
public EventsTree(TimeLineController controller) {
|
||||||
FXMLConstructor.construct(this, "NavPanel.fxml"); // NON-NLS
|
this.controller = controller;
|
||||||
|
|
||||||
|
FXMLConstructor.construct(this, "EventsTree.fxml"); // NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDetailViewPane(DetailViewPane detailViewPane) {
|
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
|
@FXML
|
||||||
|
@NbBundle.Messages("EventsTree.Label.text=Sort By:")
|
||||||
void initialize() {
|
void initialize() {
|
||||||
assert sortByBox != null : "fx:id=\"sortByBox\" was not injected: check your FXML file 'NavPanel.fxml'."; // NON-NLS
|
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.setCellFactory((TreeView<EventBundle<?>> p) -> new EventBundleTreeCell());
|
||||||
eventsTree.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
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);
|
setContextMenu(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -44,7 +44,6 @@ import org.controlsfx.control.action.ActionUtils;
|
|||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
|
||||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
|
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
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
|
* This also implements {@link TimeLineView} since it dynamically updates its
|
||||||
* filters based on the contents of a {@link FilteredEventsModel}
|
* 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");
|
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.setCellValueFactory(param -> param.getValue().valueProperty());
|
||||||
legendColumn.setCellFactory(col -> new LegendCell(this.controller));
|
legendColumn.setCellFactory(col -> new LegendCell(this.controller));
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public FilterSetPanel() {
|
|
||||||
FXMLConstructor.construct(this, "FilterSetPanel.fxml"); // NON-NLS
|
|
||||||
expansionMap.put(new TypeFilter(RootEventType.getInstance()).getDisplayName(), true);
|
expansionMap.put(new TypeFilter(RootEventType.getInstance()).getDisplayName(), true);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setController(TimeLineController timeLineController) {
|
|
||||||
this.controller = timeLineController;
|
|
||||||
Action defaultFiltersAction = new ResetFilters(controller);
|
Action defaultFiltersAction = new ResetFilters(controller);
|
||||||
defaultButton.setOnAction(defaultFiltersAction);
|
defaultButton.setOnAction(defaultFiltersAction);
|
||||||
defaultButton.disableProperty().bind(defaultFiltersAction.disabledProperty());
|
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.setItems(controller.getQuickHideFilters());
|
||||||
hiddenDescriptionsListView.setCellFactory((ListView<DescriptionFilter> param) -> {
|
hiddenDescriptionsListView.setCellFactory((ListView<DescriptionFilter> param) -> {
|
||||||
@ -237,25 +241,13 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView {
|
|||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public FilterSetPanel(TimeLineController controller) {
|
||||||
|
this.controller = controller;
|
||||||
public void setModel(FilteredEventsModel filteredEvents) {
|
this.filteredEvents = controller.getEventsModel();
|
||||||
this.filteredEvents = filteredEvents;
|
FXMLConstructor.construct(this, "FilterSetPanel.fxml"); // NON-NLS
|
||||||
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();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,6 @@ import javafx.scene.paint.Color;
|
|||||||
import javafx.scene.shape.Rectangle;
|
import javafx.scene.shape.Rectangle;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
|
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
|
* A TreeTableCell that shows an icon and color corresponding to the represented
|
||||||
* filter
|
* 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 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
|
//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);
|
setEditable(false);
|
||||||
setController(controller);
|
this.controller = controller;
|
||||||
|
this.filteredEvents = this.controller.getEventsModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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;
|
package org.sleuthkit.autopsy.timeline.zooming;
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.ResourceBundle;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.InvalidationListener;
|
import javafx.beans.InvalidationListener;
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.fxml.FXML;
|
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 javafx.util.StringConverter;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
|
||||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
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,
|
* has sliders to provide context/control over three axes of zooming (timescale,
|
||||||
* event hierarchy, and description detail).
|
* event hierarchy, and description detail).
|
||||||
*/
|
*/
|
||||||
public class ZoomSettingsPane extends TitledPane implements TimeLineView {
|
public class ZoomSettingsPane extends TitledPane {
|
||||||
|
|
||||||
@FXML
|
|
||||||
private ResourceBundle resources;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private URL location;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button backButton;
|
private Button backButton;
|
||||||
@ -105,35 +100,6 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
|
|||||||
timeUnitLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.timeUnitLabel.text"));
|
timeUnitLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.timeUnitLabel.text"));
|
||||||
zoomLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.zoomLabel.text"));
|
zoomLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.zoomLabel.text"));
|
||||||
historyLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.historyLabel.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,
|
initializeSlider(timeUnitSlider,
|
||||||
() -> {
|
() -> {
|
||||||
@ -151,18 +117,14 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
|
|||||||
|
|
||||||
timeUnitSlider.setValue(TimeUnits.fromChronoUnit(chronoUnit).ordinal() - 1);
|
timeUnitSlider.setValue(TimeUnits.fromChronoUnit(chronoUnit).ordinal() - 1);
|
||||||
});
|
});
|
||||||
|
initializeSlider(descrLODSlider, () -> {
|
||||||
initializeSlider(descrLODSlider,
|
|
||||||
() -> {
|
|
||||||
DescriptionLoD newLOD = DescriptionLoD.values()[Math.round(descrLODSlider.valueProperty().floatValue())];
|
DescriptionLoD newLOD = DescriptionLoD.values()[Math.round(descrLODSlider.valueProperty().floatValue())];
|
||||||
if (controller.pushDescrLOD(newLOD) == false) {
|
if (controller.pushDescrLOD(newLOD) == false) {
|
||||||
descrLODSlider.setValue(new DescrLODConverter().fromString(filteredEvents.getDescriptionLOD().toString()));
|
descrLODSlider.setValue(new DescrLODConverter().fromString(controller.getEventsModel().getDescriptionLOD().toString()));
|
||||||
}
|
}
|
||||||
}, this.filteredEvents.descriptionLODProperty(),
|
}, this.filteredEvents.descriptionLODProperty(), () -> {
|
||||||
() -> {
|
|
||||||
descrLODSlider.setValue(this.filteredEvents.descriptionLODProperty().get().ordinal());
|
descrLODSlider.setValue(this.filteredEvents.descriptionLODProperty().get().ordinal());
|
||||||
});
|
});
|
||||||
|
|
||||||
initializeSlider(typeZoomSlider,
|
initializeSlider(typeZoomSlider,
|
||||||
() -> {
|
() -> {
|
||||||
EventTypeZoomLevel newZoomLevel = EventTypeZoomLevel.values()[Math.round(typeZoomSlider.valueProperty().floatValue())];
|
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());
|
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.Region;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.netbeans.api.progress.ProgressHandle;
|
import org.netbeans.api.progress.ProgressHandle;
|
||||||
import org.netbeans.api.progress.ProgressHandleFactory;
|
import org.netbeans.api.progress.ProgressHandleFactory;
|
||||||
|
import org.openide.util.Cancellable;
|
||||||
import org.openide.util.Exceptions;
|
import org.openide.util.Exceptions;
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||||
@ -395,6 +397,9 @@ public final class ImageGalleryController {
|
|||||||
tagsManager.clearFollowUpTagName();
|
tagsManager.clearFollowUpTagName();
|
||||||
tagsManager.unregisterListener(groupManager);
|
tagsManager.unregisterListener(groupManager);
|
||||||
tagsManager.unregisterListener(categoryManager);
|
tagsManager.unregisterListener(categoryManager);
|
||||||
|
dbWorkerThread.cancelAllTasks();
|
||||||
|
dbWorkerThread = null;
|
||||||
|
restartWorker();
|
||||||
|
|
||||||
Toolbar.getDefault(this).reset();
|
Toolbar.getDefault(this).reset();
|
||||||
groupManager.clear();
|
groupManager.clear();
|
||||||
@ -418,7 +423,12 @@ public final class ImageGalleryController {
|
|||||||
dbWorkerThread.addTask(innerTask);
|
dbWorkerThread.addTask(innerTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
synchronized public DrawableFile<?> getFileFromId(Long fileID) throws TskCoreException {
|
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);
|
return db.getFileFromID(fileID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -587,7 +597,7 @@ public final class ImageGalleryController {
|
|||||||
try {
|
try {
|
||||||
InnerTask it = workQueue.take();
|
InnerTask it = workQueue.take();
|
||||||
|
|
||||||
if (it.cancelled == false) {
|
if (it.isCancelled() == false) {
|
||||||
it.run();
|
it.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -609,7 +619,7 @@ public final class ImageGalleryController {
|
|||||||
/**
|
/**
|
||||||
* Abstract base class for task to be done on {@link DBWorkerThread}
|
* 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() {
|
public double getProgress() {
|
||||||
return progress.get();
|
return progress.get();
|
||||||
@ -653,13 +663,13 @@ public final class ImageGalleryController {
|
|||||||
protected InnerTask() {
|
protected InnerTask() {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected volatile boolean cancelled = false;
|
@Override
|
||||||
|
synchronized public boolean cancel() {
|
||||||
public void cancel() {
|
|
||||||
updateState(Worker.State.CANCELLED);
|
updateState(Worker.State.CANCELLED);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isCancelled() {
|
synchronized protected boolean isCancelled() {
|
||||||
return getState() == Worker.State.CANCELLED;
|
return getState() == Worker.State.CANCELLED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -693,7 +703,7 @@ public final class ImageGalleryController {
|
|||||||
*/
|
*/
|
||||||
static private class UpdateFileTask extends FileTask {
|
static private class UpdateFileTask extends FileTask {
|
||||||
|
|
||||||
public UpdateFileTask(AbstractFile f, DrawableDB taskDB) {
|
UpdateFileTask(AbstractFile f, DrawableDB taskDB) {
|
||||||
super(f, taskDB);
|
super(f, taskDB);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -720,7 +730,7 @@ public final class ImageGalleryController {
|
|||||||
*/
|
*/
|
||||||
static private class RemoveFileTask extends FileTask {
|
static private class RemoveFileTask extends FileTask {
|
||||||
|
|
||||||
public RemoveFileTask(AbstractFile f, DrawableDB taskDB) {
|
RemoveFileTask(AbstractFile f, DrawableDB taskDB) {
|
||||||
super(f, taskDB);
|
super(f, taskDB);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -756,7 +766,7 @@ public final class ImageGalleryController {
|
|||||||
private final DrawableDB taskDB;
|
private final DrawableDB taskDB;
|
||||||
private final SleuthkitCase tskCase;
|
private final SleuthkitCase tskCase;
|
||||||
|
|
||||||
public CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
|
CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
this.taskDB = taskDB;
|
this.taskDB = taskDB;
|
||||||
this.tskCase = tskCase;
|
this.tskCase = tskCase;
|
||||||
@ -766,8 +776,8 @@ public final class ImageGalleryController {
|
|||||||
+ StringUtils.join(FileTypeUtils.getAllSupportedExtensions(),
|
+ StringUtils.join(FileTypeUtils.getAllSupportedExtensions(),
|
||||||
"' or name LIKE '%.")
|
"' or name LIKE '%.")
|
||||||
+ "')";
|
+ "')";
|
||||||
static private final String MIMETYPE_CLAUSE
|
static private final String MIMETYPE_CLAUSE =
|
||||||
= "blackboard_attributes.value_text LIKE '"
|
"blackboard_attributes.value_text LIKE '"
|
||||||
+ StringUtils.join(FileTypeUtils.getAllSupportedMimeTypes(),
|
+ StringUtils.join(FileTypeUtils.getAllSupportedMimeTypes(),
|
||||||
"' OR blackboard_attributes.value_text LIKE '") + "' ";
|
"' OR blackboard_attributes.value_text LIKE '") + "' ";
|
||||||
|
|
||||||
@ -801,7 +811,7 @@ public final class ImageGalleryController {
|
|||||||
DrawableDB.DrawableTransaction tr = taskDB.beginTransaction();
|
DrawableDB.DrawableTransaction tr = taskDB.beginTransaction();
|
||||||
int units = 0;
|
int units = 0;
|
||||||
for (final AbstractFile f : files) {
|
for (final AbstractFile f : files) {
|
||||||
if (cancelled) {
|
if (isCancelled()) {
|
||||||
LOGGER.log(Level.WARNING, "task cancelled: not all contents may be transfered to database");
|
LOGGER.log(Level.WARNING, "task cancelled: not all contents may be transfered to database");
|
||||||
progressHandle.finish();
|
progressHandle.finish();
|
||||||
break;
|
break;
|
||||||
@ -849,11 +859,11 @@ public final class ImageGalleryController {
|
|||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
Logger.getLogger(CopyAnalyzedFiles.class.getName()).log(Level.WARNING, "failed to transfer all database contents", ex);
|
Logger.getLogger(CopyAnalyzedFiles.class.getName()).log(Level.WARNING, "failed to transfer all database contents", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
progressHandle.finish();
|
progressHandle.finish();
|
||||||
|
|
||||||
updateMessage("");
|
updateMessage("");
|
||||||
updateProgress(-1.0);
|
updateProgress(-1.0);
|
||||||
|
|
||||||
controller.setStale(false);
|
controller.setStale(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -877,13 +887,13 @@ public final class ImageGalleryController {
|
|||||||
// (name like '.jpg' or name like '.png' ...)
|
// (name like '.jpg' or name like '.png' ...)
|
||||||
private final String DRAWABLE_QUERY = "(name LIKE '%." + StringUtils.join(FileTypeUtils.getAllSupportedExtensions(), "' OR name LIKE '%.") + "') ";
|
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
|
* @param dataSourceId Data source object ID
|
||||||
*/
|
*/
|
||||||
public PrePopulateDataSourceFiles(Content dataSource) {
|
PrePopulateDataSourceFiles(Content dataSource) {
|
||||||
super();
|
super();
|
||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
}
|
}
|
||||||
@ -933,7 +943,7 @@ public final class ImageGalleryController {
|
|||||||
DrawableDB.DrawableTransaction tr = db.beginTransaction();
|
DrawableDB.DrawableTransaction tr = db.beginTransaction();
|
||||||
int units = 0;
|
int units = 0;
|
||||||
for (final AbstractFile f : files) {
|
for (final AbstractFile f : files) {
|
||||||
if (cancelled) {
|
if (isCancelled()) {
|
||||||
LOGGER.log(Level.WARNING, "task cancelled: not all contents may be transfered to database");
|
LOGGER.log(Level.WARNING, "task cancelled: not all contents may be transfered to database");
|
||||||
progressHandle.finish();
|
progressHandle.finish();
|
||||||
break;
|
break;
|
||||||
|
@ -31,7 +31,7 @@ import org.openide.util.Lookup;
|
|||||||
*/
|
*/
|
||||||
@OptionsPanelController.TopLevelRegistration(
|
@OptionsPanelController.TopLevelRegistration(
|
||||||
categoryName = "#OptionsCategory_Name_Options",
|
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",
|
keywords = "#OptionsCategory_Keywords_Options",
|
||||||
keywordsCategory = "Options",
|
keywordsCategory = "Options",
|
||||||
position = 10
|
position = 10
|
||||||
|
@ -89,7 +89,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
|||||||
return fileOpt;
|
return fileOpt;
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
fileOpt = Optional.of(getController().getFileFromId(fileIDOpt.get()));
|
fileOpt = Optional.ofNullable(getController().getFileFromId(fileIDOpt.get()));
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
Logger.getAnonymousLogger().log(Level.WARNING, "failed to get DrawableFile for obj_id" + fileIDOpt.get(), ex);
|
Logger.getAnonymousLogger().log(Level.WARNING, "failed to get DrawableFile for obj_id" + fileIDOpt.get(), ex);
|
||||||
fileOpt = Optional.empty();
|
fileOpt = Optional.empty();
|
||||||
@ -207,7 +207,6 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
|||||||
LOGGER.log(Level.SEVERE, "Failed to cache content for" + file.getName(), getException());
|
LOGGER.log(Level.SEVERE, "Failed to cache content for" + file.getName(), getException());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
abstract void saveToCache(X result);
|
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="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="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="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="win64.TskLib.exists" type="dir" file="${win64.TskLib.path}" />
|
||||||
<available property="win32.TskLib.exists" type="dir" file="${win32.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="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>
|
</target>
|
||||||
|
|
||||||
<!-- The following copy the libtsk_jni dependencies to the Autopsy
|
<!-- The following copy the libtsk_jni dependencies to the Autopsy
|
||||||
@ -71,20 +73,32 @@
|
|||||||
<include name="libewf.dll"/>
|
<include name="libewf.dll"/>
|
||||||
</fileset>
|
</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">
|
<copy todir="${i386}" overwrite="true">
|
||||||
<fileset refid="win32dlls" />
|
<fileset refid="win32dlls" />
|
||||||
|
<fileset refid="postgres32dlls" />
|
||||||
</copy>
|
</copy>
|
||||||
|
|
||||||
<copy todir="${x86}" overwrite="true">
|
<copy todir="${x86}" overwrite="true">
|
||||||
<fileset refid="win32dlls" />
|
<fileset refid="win32dlls" />
|
||||||
|
<fileset refid="postgres32dlls" />
|
||||||
</copy>
|
</copy>
|
||||||
|
|
||||||
<copy todir="${i586}" overwrite="true">
|
<copy todir="${i586}" overwrite="true">
|
||||||
<fileset refid="win32dlls" />
|
<fileset refid="win32dlls" />
|
||||||
|
<fileset refid="postgres32dlls" />
|
||||||
</copy>
|
</copy>
|
||||||
|
|
||||||
<copy todir="${i686}" overwrite="true">
|
<copy todir="${i686}" overwrite="true">
|
||||||
<fileset refid="win32dlls" />
|
<fileset refid="win32dlls" />
|
||||||
|
<fileset refid="postgres32dlls" />
|
||||||
</copy>
|
</copy>
|
||||||
</target>
|
</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.
|
-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:
|
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).
|
-# 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.
|
-# 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.
|
-# In NetBeans go to Tools->Plugins. In Downloaded 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/)
|
-# 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:
|
To install IntelliJ IDEA + Python plug-in:
|
||||||
-# Download and install IDEA https://www.jetbrains.com/idea/download/
|
-# Download java JDK depending on platform. Install to desired location (http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html).
|
||||||
-# In File->Settings->Plugins-> install Python Community Edition
|
-# Download and install IDEA Community Edition to desired location (https://www.jetbrains.com/idea/download/).
|
||||||
-# In File->Project Structure->Project-> Project SDK-> choose IntelliJ IDEA Community Edition
|
-# Open IDEA and choose desired UI theme. Continue with default settings.
|
||||||
-# In Libraries->add new libraries->choose desired autopsy modules (usually in C:\Program Files\Autopsy-3.1.3\autopsy\modules)
|
-# 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
|
\section mod_dev_py_create Creating a Basic Python Module
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user