Merge branch 'develop' of https://github.com/sleuthkit/autopsy into develop

This commit is contained in:
U-Mhmdfy-PC\Mhmdfy 2015-10-22 15:06:00 -04:00
commit 0cfee9e2a0
52 changed files with 1224 additions and 954 deletions

View File

@ -197,6 +197,7 @@
<file name="org-netbeans-modules-autoupdate-ui-actions-PluginManagerAction.shadow"/>
<file name="org-netbeans-modules-favorites-templates-TemplatesAction.shadow_hidden"/>
<file name="org-netbeans-modules-options-OptionsWindowAction.shadow"/>
<file name="org-netbeans-modules-templates-actions-TemplatesAction.shadow_hidden"/>
<file name="org-openide-actions-ToolsAction.shadow_hidden"/>
<file name="org-sleuthkit-autopsy-filesearch-FileSearchAction.shadow">
<attr name="originalFile" stringvalue="Actions/Tools/org-sleuthkit-autopsy-filesearch-FileSearchAction.instance"/>

View File

@ -22,6 +22,7 @@ import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
@ -174,6 +175,9 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi
borderpane.setCenter(fxImageView);
}
}
} catch (EOFException ex) {
LOGGER.log(Level.WARNING, "Could not load image file into media view (EOF): {0}", file.getName()); //NON-NLS
borderpane.setCenter(errorLabel);
} catch (IllegalArgumentException | IOException ex) {
LOGGER.log(Level.WARNING, "Could not load image file into media view: " + file.getName(), ex); //NON-NLS
borderpane.setCenter(errorLabel);

View File

@ -26,6 +26,7 @@ import com.google.common.io.Files;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@ -135,8 +136,8 @@ public class ImageUtils {
/**
* thread that saves generated thumbnails to disk in the background
*/
private static final Executor imageSaver
= Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
private static final Executor imageSaver =
Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
.namingPattern("icon saver-%d").build());
public static List<String> getSupportedImageExtensions() {
@ -524,14 +525,13 @@ public class ImageUtils {
return ScalrWrapper.cropImage(bi, Math.min(iconSize, bi.getWidth()), Math.min(iconSize, bi.getHeight()));
}
} catch (OutOfMemoryError e) {
LOGGER.log(Level.WARNING, "Could not scale image (too large): " + content.getName(), e); //NON-NLS
return null;
LOGGER.log(Level.WARNING, "Could not scale image (too large) " + content.getName(), e); //NON-NLS
} catch (EOFException e) {
LOGGER.log(Level.WARNING, "Could not load image (EOF) {0}", content.getName()); //NON-NLS
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Could not load image: " + content.getName(), e); //NON-NLS
return null;
LOGGER.log(Level.WARNING, "Could not load image " + content.getName(), e); //NON-NLS
}
return null;
}
}

View File

@ -24,10 +24,7 @@ TimelinePanel.jButton7.text=3d
TimelinePanel.jButton2.text=1m
TimelinePanel.jButton3.text=3m
TimelinePanel.jButton4.text=2w
TimeLineTopComponent.eventsTab.name=Events
TimeLineTopComponent.filterTab.name=Filters
OpenTimelineAction.title=Timeline
OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources.
TimeLineTopComponent.timeZonePanel.text=Display Times In\:
ProgressWindow.progressHeader.text=\

View File

@ -42,8 +42,7 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined;
* intended only to remove the boilerplate initialization code when defining a
* relatively static layout
*
* TODO: find a way to move this to CoreUtils and remove duplicate verison in
* image analyzer
* TODO: move this to CoreUtils and remove duplicate verison in image analyzer
*/
public class FXMLConstructor {
@ -55,12 +54,28 @@ public class FXMLConstructor {
*
*
* @param node a node to initialize from a loaded FXML
* @param fxmlFileName the the file name of the FXML to load, relative to
* the package that the class of node is defined in.
* @param fxmlFileName the file name of the FXML to load, relative to the
* package that the class of node is defined in.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
static public void construct(Node node, String fxmlFileName) {
final String name = "nbres:/" + StringUtils.replace(node.getClass().getPackage().getName(), ".", "/") + "/" + fxmlFileName; // NON-NLS
construct(node, node.getClass(), fxmlFileName);
}
/**
* Load an fxml file and initialize a node with it. Since this manipulates
* the node, it must be called on the JFX thread.
*
*
* @param node a node to initialize from a loaded FXML
* @param clazz a class to use for relative location of the fxml
* @param fxmlFileName the file name of the FXML to load, relative to the
* package of clazz.
*
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
static public void construct(Node node, Class<? extends Node> clazz, String fxmlFileName) {
final String name = "nbres:/" + StringUtils.replace(clazz.getPackage().getName(), ".", "/") + "/" + fxmlFileName; // NON-NLS
try {
FXMLLoader fxmlLoader = new FXMLLoader(new URL(name));

View File

@ -64,13 +64,13 @@ import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case;
import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE;
import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_ADDED;
import org.sleuthkit.autopsy.coreutils.History;
import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.coreutils.History;
import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
@ -135,6 +135,21 @@ public class TimeLineController {
private final ReadOnlyStringWrapper taskTitle = new ReadOnlyStringWrapper();
private final ReadOnlyStringWrapper status = new ReadOnlyStringWrapper();
/**
* status is a string that will be displayed in the status bar as a kind of
* user hint/information when it is not empty
*
* @return the status property
*/
public ReadOnlyStringProperty getStatusProperty() {
return status.getReadOnlyProperty();
}
public void setStatus(String string) {
status.set(string);
}
private final Case autoCase;
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
@ -301,8 +316,10 @@ public class TimeLineController {
LOGGER.log(Level.INFO, "Beginning generation of timeline"); // NON-NLS
try {
SwingUtilities.invokeLater(() -> {
if (isWindowOpen()) {
mainFrame.close();
synchronized (TimeLineController.this) {
if (isWindowOpen()) {
mainFrame.close();
}
}
});
final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase();
@ -347,8 +364,10 @@ public class TimeLineController {
void rebuildTagsTable() {
LOGGER.log(Level.INFO, "starting to rebuild tags table"); // NON-NLS
SwingUtilities.invokeLater(() -> {
if (isWindowOpen()) {
mainFrame.close();
synchronized (TimeLineController.this) {
if (isWindowOpen()) {
mainFrame.close();
}
}
});
synchronized (eventsRepository) {
@ -373,16 +392,19 @@ public class TimeLineController {
IngestManager.getInstance().removeIngestModuleEventListener(ingestModuleListener);
IngestManager.getInstance().removeIngestJobEventListener(ingestJobListener);
Case.removePropertyChangeListener(caseListener);
mainFrame.close();
mainFrame.setVisible(false);
mainFrame = null;
SwingUtilities.invokeLater(() -> {
synchronized (TimeLineController.this) {
mainFrame.close();
mainFrame = null;
}
});
}
}
/**
* show the timeline window and prompt for rebuilding database if necessary.
*/
synchronized void openTimeLine() {
void openTimeLine() {
// listen for case changes (specifically images being added, and case changes).
if (Case.isCaseOpen() && !listeningToAutopsy) {
IngestManager.getInstance().addIngestModuleEventListener(ingestModuleListener);
@ -524,20 +546,20 @@ public class TimeLineController {
/**
* private method to build gui if necessary and make it visible.
*/
synchronized private void showWindow() {
if (mainFrame == null) {
LOGGER.log(Level.WARNING, "Tried to show timeline with invalid window. Rebuilding GUI."); // NON-NLS
mainFrame = (TimeLineTopComponent) WindowManager.getDefault().findTopComponent(
NbBundle.getMessage(TimeLineController.class, "CTL_TimeLineTopComponentAction"));
if (mainFrame == null) {
mainFrame = new TimeLineTopComponent();
}
mainFrame.setController(this);
}
private void showWindow() {
SwingUtilities.invokeLater(() -> {
mainFrame.open();
mainFrame.setVisible(true);
mainFrame.toFront();
synchronized (TimeLineController.this) {
if (mainFrame == null) {
LOGGER.log(Level.WARNING, "Tried to show timeline with invalid window. Rebuilding GUI."); // NON-NLS
mainFrame = (TimeLineTopComponent) WindowManager.getDefault().findTopComponent(
NbBundle.getMessage(TimeLineController.class, "CTL_TimeLineTopComponentAction"));
if (mainFrame == null) {
mainFrame = new TimeLineTopComponent(this);
}
}
mainFrame.open();
mainFrame.toFront();
}
});
}

View File

@ -23,7 +23,7 @@ import java.util.Collections;
import java.util.List;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.event.ActionEvent;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.control.SplitPane;
import javafx.scene.control.Tab;
@ -34,7 +34,6 @@ import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import org.netbeans.api.settings.ConvertAsProperties;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerUtils;
import org.openide.util.NbBundle;
@ -51,135 +50,103 @@ import org.sleuthkit.autopsy.timeline.ui.StatusBar;
import org.sleuthkit.autopsy.timeline.ui.TimeLineResultView;
import org.sleuthkit.autopsy.timeline.ui.TimeZonePanel;
import org.sleuthkit.autopsy.timeline.ui.VisualizationPanel;
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.NavPanel;
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
import org.sleuthkit.autopsy.timeline.ui.filtering.FilterSetPanel;
import org.sleuthkit.autopsy.timeline.zooming.ZoomSettingsPane;
/**
* TopComponent for the timeline feature.
*/
@ConvertAsProperties(
dtd = "-//org.sleuthkit.autopsy.timeline//TimeLine//EN",
autostore = false)
@TopComponent.Description(
preferredID = "TimeLineTopComponent",
//iconBase="SET/PATH/TO/ICON/HERE",
persistenceType = TopComponent.PERSISTENCE_NEVER)
@TopComponent.Registration(mode = "timeline", openAtStartup = false)
public final class TimeLineTopComponent extends TopComponent implements ExplorerManager.Provider, TimeLineUI {
public final class TimeLineTopComponent extends TopComponent implements ExplorerManager.Provider {
private static final Logger LOGGER = Logger.getLogger(TimeLineTopComponent.class.getName());
private DataContentPanel dataContentPanel;
private final DataContentPanel dataContentPanel;
private TimeLineResultView tlrv;
private final TimeLineResultView tlrv;
private final ExplorerManager em = new ExplorerManager();
private TimeLineController controller;
private final TimeLineController controller;
////jfx componenets that make up the interface
private final FilterSetPanel filtersPanel = new FilterSetPanel();
private final Tab eventsTab = new Tab(
NbBundle.getMessage(TimeLineTopComponent.class, "TimeLineTopComponent.eventsTab.name"));
private final Tab filterTab = new Tab(
NbBundle.getMessage(TimeLineTopComponent.class, "TimeLineTopComponent.filterTab.name"));
private final VBox leftVBox = new VBox(5);
private final NavPanel navPanel = new NavPanel();
private final StatusBar statusBar = new StatusBar();
private final TabPane tabPane = new TabPane();
private final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane();
private final VisualizationPanel visualizationPanel = new VisualizationPanel(navPanel);
private final SplitPane splitPane = new SplitPane();
private final TimeZonePanel timeZonePanel = new TimeZonePanel();
public TimeLineTopComponent() {
public TimeLineTopComponent(TimeLineController controller) {
initComponents();
this.controller = controller;
associateLookup(ExplorerUtils.createLookup(em, getActionMap()));
setName(NbBundle.getMessage(TimeLineTopComponent.class, "CTL_TimeLineTopComponent"));
setToolTipText(NbBundle.getMessage(TimeLineTopComponent.class, "HINT_TimeLineTopComponent"));
setIcon(WindowManager.getDefault().getMainWindow().getIconImage()); //use the same icon as main application
timeZonePanel.setText(NbBundle.getMessage(this.getClass(), "TimeLineTopComponent.timeZonePanel.text"));
customizeComponents();
}
synchronized private void customizeComponents() {
dataContentPanel = DataContentPanel.createInstance();
this.contentViewerContainerPanel.add(dataContentPanel, BorderLayout.CENTER);
tlrv = new TimeLineResultView(dataContentPanel);
tlrv = new TimeLineResultView(controller, dataContentPanel);
DataResultPanel dataResultPanel = tlrv.getDataResultPanel();
this.resultContainerPanel.add(dataResultPanel, BorderLayout.CENTER);
dataResultPanel.open();
Platform.runLater(() -> {
//assemble ui componenets together
jFXstatusPanel.setScene(new Scene(statusBar));
jFXVizPanel.setScene(new Scene(splitPane));
splitPane.setDividerPositions(0);
filterTab.setClosable(false);
filterTab.setContent(filtersPanel);
filterTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/funnel.png")); // NON-NLS
eventsTab.setClosable(false);
eventsTab.setContent(navPanel);
eventsTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/timeline_marker.png")); // NON-NLS
tabPane.getTabs().addAll(filterTab, eventsTab);
VBox.setVgrow(tabPane, Priority.ALWAYS);
VBox.setVgrow(timeZonePanel, Priority.SOMETIMES);
leftVBox.getChildren().addAll(timeZonePanel, zoomSettingsPane, tabPane);
SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE);
splitPane.getItems().addAll(leftVBox, visualizationPanel);
});
customizeFXComponents();
}
@Override
public synchronized void setController(TimeLineController controller) {
this.controller = controller;
tlrv.setController(controller);
@NbBundle.Messages({"TimeLineTopComponent.eventsTab.name=Events",
"TimeLineTopComponent.filterTab.name=Filters"})
void customizeFXComponents() {
Platform.runLater(() -> {
jFXVizPanel.getScene().addEventFilter(KeyEvent.KEY_PRESSED,
(KeyEvent event) -> {
if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(event)) {
new Back(controller).handle(new ActionEvent());
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE).match(event)) {
new Back(controller).handle(new ActionEvent());
} else if (new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN).match(event)) {
new Forward(controller).handle(new ActionEvent());
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE, KeyCodeCombination.SHIFT_DOWN).match(event)) {
new Forward(controller).handle(new ActionEvent());
}
});
//create and wire up jfx componenets that make up the interface
final Tab filterTab = new Tab(Bundle.TimeLineTopComponent_filterTab_name(), new FilterSetPanel(controller));
filterTab.setClosable(false);
filterTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/funnel.png")); // NON-NLS
final EventsTree eventsTree = new EventsTree(controller);
final VisualizationPanel visualizationPanel = new VisualizationPanel(controller, eventsTree);
final Tab eventsTreeTab = new Tab(Bundle.TimeLineTopComponent_eventsTab_name(), eventsTree);
eventsTreeTab.setClosable(false);
eventsTreeTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/timeline_marker.png")); // NON-NLS
eventsTreeTab.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS));
final TabPane leftTabPane = new TabPane(filterTab, eventsTreeTab);
VBox.setVgrow(leftTabPane, Priority.ALWAYS);
controller.viewModeProperty().addListener((Observable observable) -> {
if (controller.viewModeProperty().get().equals(VisualizationMode.COUNTS)) {
tabPane.getSelectionModel().select(filterTab);
//if view mode is counts, make sure events tabd is not active
leftTabPane.getSelectionModel().select(filterTab);
}
});
eventsTab.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS));
visualizationPanel.setController(controller);
navPanel.setController(controller);
filtersPanel.setController(controller);
zoomSettingsPane.setController(controller);
statusBar.setController(controller);
final TimeZonePanel timeZonePanel = new TimeZonePanel();
VBox.setVgrow(timeZonePanel, Priority.SOMETIMES);
final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane(controller);
final VBox leftVBox = new VBox(5, timeZonePanel, zoomSettingsPane, leftTabPane);
SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE);
final SplitPane mainSplitPane = new SplitPane(leftVBox, visualizationPanel);
mainSplitPane.setDividerPositions(0);
final Scene scene = new Scene(mainSplitPane);
scene.addEventFilter(KeyEvent.KEY_PRESSED,
(KeyEvent event) -> {
if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(event)) {
new Back(controller).handle(null);
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE).match(event)) {
new Back(controller).handle(null);
} else if (new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN).match(event)) {
new Forward(controller).handle(null);
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE, KeyCodeCombination.SHIFT_DOWN).match(event)) {
new Forward(controller).handle(null);
}
});
//add ui componenets to JFXPanels
jFXVizPanel.setScene(scene);
jFXstatusPanel.setScene(new Scene(new StatusBar(controller)));
});
}
@ -196,9 +163,9 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
jFXstatusPanel = new javafx.embed.swing.JFXPanel();
jFXstatusPanel = new JFXPanel();
splitYPane = new javax.swing.JSplitPane();
jFXVizPanel = new javafx.embed.swing.JFXPanel();
jFXVizPanel = new JFXPanel();
lowerSplitXPane = new javax.swing.JSplitPane();
resultContainerPanel = new javax.swing.JPanel();
contentViewerContainerPanel = new javax.swing.JPanel();

View File

@ -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);
}

View File

@ -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);
}

View File

@ -1,6 +1,4 @@
Back.actions.name.text=Back
DefaultFilters.action.name.text=apply default filters
Forward.action.name.text=Forward
SaveSnapshot.action.name.text=save snapshot
SaveSnapshot.fileChoose.title.text=Save snapshot to
ZoomOut.action.name.text=apply default filters

View File

@ -46,51 +46,48 @@ import org.sleuthkit.datamodel.TskCoreException;
/**
*/
public class SaveSnapshot extends Action {
public class SaveSnapshotAsReport extends Action {
private static final String HTML_EXT = ".html";
private static final String REPORT_IMAGE_EXTENSION = ".png";
private static final Logger LOGGER = Logger.getLogger(SaveSnapshot.class.getName());
private static final Logger LOGGER = Logger.getLogger(SaveSnapshotAsReport.class.getName());
private final TimeLineController controller;
private final WritableImage snapshot;
public SaveSnapshot(TimeLineController controller, WritableImage snapshot) {
super(NbBundle.getMessage(SaveSnapshot.class, "SaveSnapshot.action.name.text"));
this.controller = controller;
this.snapshot = snapshot;
@NbBundle.Messages({"SaveSnapshot.action.name.text=save snapshot",
"SaveSnapshot.fileChoose.title.text=Save snapshot to"})
public SaveSnapshotAsReport(TimeLineController controller, WritableImage snapshot) {
super(Bundle.SaveSnapshot_action_name_text());
setEventHandler(new Consumer<ActionEvent>() {
@Override
public void accept(ActionEvent t) {
//choose location/name
DirectoryChooser fileChooser = new DirectoryChooser();
fileChooser.setTitle(NbBundle.getMessage(this.getClass(), "SaveSnapshot.fileChoose.title.text"));
fileChooser.setTitle(Bundle.SaveSnapshot_fileChoose_title_text());
fileChooser.setInitialDirectory(new File(Case.getCurrentCase().getReportDirectory()));
File outFolder = fileChooser.showDialog(null);
if (outFolder == null) {
File reportDirectory = fileChooser.showDialog(null);
if (reportDirectory == null) {
return;
}
outFolder.mkdir();
String name = outFolder.getName();
reportDirectory.mkdir();
String reportName = reportDirectory.getName();
String reportPath = reportDirectory.getPath();
//gather metadata
List<Pair<String, String>> reportMetaData = new ArrayList<>();
reportMetaData.add(new Pair<>("Case", Case.getCurrentCase().getName())); // NON-NLS
ZoomParams get = controller.getEventsModel().zoomParametersProperty().get();
reportMetaData.add(new Pair<>("Time Range", get.getTimeRange().toString())); // NON-NLS
reportMetaData.add(new Pair<>("Description Level of Detail", get.getDescriptionLOD().getDisplayName())); // NON-NLS
reportMetaData.add(new Pair<>("Event Type Zoom Level", get.getTypeZoomLevel().getDisplayName())); // NON-NLS
reportMetaData.add(new Pair<>("Filters", get.getFilter().getHTMLReportString())); // NON-NLS
ZoomParams zoomParams = controller.getEventsModel().zoomParametersProperty().get();
reportMetaData.add(new Pair<>("Time Range", zoomParams.getTimeRange().toString())); // NON-NLS
reportMetaData.add(new Pair<>("Description Level of Detail", zoomParams.getDescriptionLOD().getDisplayName())); // NON-NLS
reportMetaData.add(new Pair<>("Event Type Zoom Level", zoomParams.getTypeZoomLevel().getDisplayName())); // NON-NLS
reportMetaData.add(new Pair<>("Filters", zoomParams.getFilter().getHTMLReportString())); // NON-NLS
//save snapshot as png
try {
ImageIO.write(SwingFXUtils.fromFXImage(snapshot, null), "png", new File(outFolder.getPath() + File.separator + outFolder.getName() + REPORT_IMAGE_EXTENSION)); // NON-NLS
ImageIO.write(SwingFXUtils.fromFXImage(snapshot, null), "png",
new File(reportPath, reportName + REPORT_IMAGE_EXTENSION)); // NON-NLS
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "failed to write snapshot to disk", ex); // NON-NLS
return;
@ -99,17 +96,18 @@ public class SaveSnapshot extends Action {
//build html string
StringBuilder wrapper = new StringBuilder();
wrapper.append("<html>\n<head>\n\t<title>").append("timeline snapshot").append("</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n</head>\n<body>\n"); // NON-NLS
wrapper.append("<div id=\"content\">\n<h1>").append(outFolder.getName()).append("</h1>\n"); // NON-NLS
wrapper.append("<img src = \"").append(outFolder.getName()).append(REPORT_IMAGE_EXTENSION + "\" alt = \"snaphot\">"); // NON-NLS
wrapper.append("<div id=\"content\">\n<h1>").append(reportDirectory.getName()).append("</h1>\n"); // NON-NLS
wrapper.append("<img src = \"").append(reportDirectory.getName()).append(REPORT_IMAGE_EXTENSION + "\" alt = \"snaphot\">"); // NON-NLS
wrapper.append("<table>\n"); // NON-NLS
for (Pair<String, String> pair : reportMetaData) {
wrapper.append("<tr><td>").append(pair.getKey()).append(": </td><td>").append(pair.getValue()).append("</td></tr>\n"); // NON-NLS
}
wrapper.append("</table>\n"); // NON-NLS
wrapper.append("</div>\n</body>\n</html>"); // NON-NLS
File reportHTMLFIle = new File(reportDirectory, reportName + HTML_EXT);
//write html wrapper
try (Writer htmlWriter = new FileWriter(new File(outFolder, name + HTML_EXT))) {
try (Writer htmlWriter = new FileWriter(reportHTMLFIle)) {
htmlWriter.write(wrapper.toString());
} catch (FileNotFoundException ex) {
LOGGER.log(Level.WARNING, "failed to open html wrapper file for writing ", ex); // NON-NLS
@ -121,14 +119,14 @@ public class SaveSnapshot extends Action {
//copy css
try (InputStream resource = this.getClass().getResourceAsStream("/org/sleuthkit/autopsy/timeline/index.css")) { // NON-NLS
Files.copy(resource, Paths.get(outFolder.getPath(), "index.css")); // NON-NLS
Files.copy(resource, Paths.get(reportPath, "index.css")); // NON-NLS
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "failed to copy css file", ex); // NON-NLS
}
//add html file as report to case
try {
Case.getCurrentCase().addReport(outFolder.getPath() + File.separator + outFolder.getName() + HTML_EXT, "Timeline", outFolder.getName() + HTML_EXT); // NON-NLS
Case.getCurrentCase().addReport(reportHTMLFIle.getPath(), "Timeline", reportName + HTML_EXT); // NON-NLS
} catch (TskCoreException ex) {
LOGGER.log(Level.WARNING, "failed add html wrapper as a report", ex); // NON-NLS
}

View 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();
});
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014 Basis Technology Corp.
* Copyright 2015 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -19,7 +19,8 @@
package org.sleuthkit.autopsy.timeline.actions;
import javafx.beans.binding.BooleanBinding;
import javafx.event.ActionEvent;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import org.controlsfx.control.action.Action;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
@ -30,15 +31,22 @@ import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
*/
public class ZoomOut extends Action {
private final TimeLineController controller;
private static final Image MAGNIFIER_OUT = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-out-red.png"); //NOI18N
private final FilteredEventsModel eventsModel;
@NbBundle.Messages({"ZoomOut.longText=Zoom out to view 50% more time.",
"ZoomOut.action.text=Zoom out"})
public ZoomOut(TimeLineController controller) {
super(Bundle.ZoomOut_action_text());
setLongText(Bundle.ZoomOut_longText());
setGraphic(new ImageView(MAGNIFIER_OUT));
setEventHandler(actionEvent -> {
controller.pushZoomOutTime();
});
public ZoomOut(final TimeLineController controller) {
super(NbBundle.getMessage(ZoomOut.class, "ZoomOut.action.name.text"));
this.controller = controller;
eventsModel = controller.getEventsModel();
//disable action when the current time range already encompases the entire case.
disabledProperty().bind(new BooleanBinding() {
private final FilteredEventsModel eventsModel = controller.getEventsModel();
{
bind(eventsModel.zoomParametersProperty());
}
@ -48,8 +56,5 @@ public class ZoomOut extends Action {
return eventsModel.zoomParametersProperty().getValue().getTimeRange().contains(eventsModel.getSpanningInterval());
}
});
setEventHandler((ActionEvent t) -> {
controller.zoomOutToActivity();
});
}
}

View File

@ -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());
}
});
}
}

View File

@ -41,7 +41,6 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent.DeletedContentTagInfo;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
import org.sleuthkit.autopsy.timeline.db.EventsRepository;

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

View File

@ -40,6 +40,7 @@ import javafx.scene.chart.Chart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Label;
import javafx.scene.control.OverrunStyle;
import javafx.scene.control.Tooltip;
import javafx.scene.effect.Effect;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
@ -50,12 +51,11 @@ import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
import org.apache.commons.lang3.StringUtils;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
@ -73,8 +73,15 @@ import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
* {@link XYChart} doing the rendering. Is this a good idea? -jm TODO: pull up
* common history context menu items out of derived classes? -jm
*/
public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & TimeLineChart<X>> extends BorderPane implements TimeLineView {
public abstract class AbstractVisualizationPane<X, Y, N, C extends XYChart<X, Y> & TimeLineChart<X>> extends BorderPane {
@NbBundle.Messages("AbstractVisualization.Drag_Tooltip.text=Drag the mouse to select a time interval to zoom into.")
private static final Tooltip DRAG_TOOLTIP = new Tooltip(Bundle.AbstractVisualization_Drag_Tooltip_text());
private static final Logger LOGGER = Logger.getLogger(AbstractVisualizationPane.class.getName());
public static Tooltip getDragTooltip() {
return DRAG_TOOLTIP;
}
protected final SimpleBooleanProperty hasEvents = new SimpleBooleanProperty(true);
protected final ObservableList<BarChart.Series<X, Y>> dataSets = FXCollections.<BarChart.Series<X, Y>>observableArrayList();
@ -93,11 +100,15 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
*/
private Task<Boolean> updateTask;
protected TimeLineController controller;
final protected TimeLineController controller;
protected FilteredEventsModel filteredEvents;
final protected FilteredEventsModel filteredEvents;
protected ReadOnlyListWrapper<N> selectedNodes = new ReadOnlyListWrapper<>(FXCollections.observableArrayList());
final protected ReadOnlyListWrapper<N> selectedNodes = new ReadOnlyListWrapper<>(FXCollections.observableArrayList());
private InvalidationListener invalidationListener = (Observable observable) -> {
update();
};
public ReadOnlyListProperty<N> getSelectedNodes() {
return selectedNodes.getReadOnlyProperty();
@ -177,7 +188,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
* Primarily this invokes the background {@link Task} returned by
* {@link #getUpdateTask()} which derived classes must implement.
*/
synchronized public void update() {
final synchronized public void update() {
if (updateTask != null) {
updateTask.cancel(true);
updateTask = null;
@ -195,7 +206,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
try {
this.hasEvents.set(updateTask.get());
} catch (InterruptedException | ExecutionException ex) {
Logger.getLogger(AbstractVisualization.class.getName()).log(Level.SEVERE, "Unexpected exception updating visualization", ex);
LOGGER.log(Level.SEVERE, "Unexpected exception updating visualization", ex); //NOI18N
}
break;
}
@ -203,7 +214,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
controller.monitorTask(updateTask);
}
synchronized public void dispose() {
final synchronized public void dispose() {
if (updateTask != null) {
updateTask.cancel(true);
}
@ -211,7 +222,12 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
invalidationListener = null;
}
protected AbstractVisualization(Pane partPane, Pane contextPane, Region spacer) {
protected AbstractVisualizationPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) {
this.controller = controller;
this.filteredEvents = controller.getEventsModel();
this.filteredEvents.registerForEvents(this);
this.filteredEvents.zoomParametersProperty().addListener(invalidationListener);
this.leafPane = partPane;
this.branchPane = contextPane;
this.spacer = spacer;
@ -226,32 +242,18 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
});
}
});
}
@Override
synchronized public void setController(TimeLineController controller) {
this.controller = controller;
chart.setController(controller);
setModel(controller.getEventsModel());
TimeLineController.getTimeZone().addListener((Observable observable) -> {
update();
TimeLineController.getTimeZone().addListener(invalidationListener);
//show tooltip text in status bar
hoverProperty().addListener((observable, oldActivated, newActivated) -> {
if (newActivated) {
controller.setStatus(DRAG_TOOLTIP.getText());
} else {
controller.setStatus("");
}
});
}
@Override
synchronized public void setModel(@Nonnull FilteredEventsModel filteredEvents) {
if (this.filteredEvents != null && this.filteredEvents != filteredEvents) {
this.filteredEvents.unRegisterForEvents(this);
this.filteredEvents.zoomParametersProperty().removeListener(invalidationListener);
}
if (this.filteredEvents != filteredEvents) {
filteredEvents.registerForEvents(this);
filteredEvents.zoomParametersProperty().addListener(invalidationListener);
}
this.filteredEvents = filteredEvents;
update();
}
@ -260,10 +262,6 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
update();
}
protected InvalidationListener invalidationListener = (Observable observable) -> {
update();
};
/**
* iterate through the list of tick-marks building a two level structure of
* replacement tick marl labels. (Visually) upper level has most
@ -370,7 +368,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
*/
private synchronized void assignLeafLabel(String labelText, double labelWidth, double labelX, boolean bold) {
Text label = new Text(" " + labelText + " ");
Text label = new Text(" " + labelText + " "); //NOI18N
label.setTextAlignment(TextAlignment.CENTER);
label.setFont(Font.font(null, bold ? FontWeight.BOLD : FontWeight.NORMAL, 10));
//position label accounting for width
@ -414,9 +412,9 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
label.relocate(labelX, 0);
if (labelX == 0) { // first label has no border
label.setStyle("-fx-border-width: 0 0 0 0 ; -fx-border-color:black;"); // NON-NLS
label.setStyle("-fx-border-width: 0 0 0 0 ; -fx-border-color:black;"); // NON-NLS //NOI18N
} else { // subsequent labels have border on left to create dividers
label.setStyle("-fx-border-width: 0 0 0 1; -fx-border-color:black;"); // NON-NLS
label.setStyle("-fx-border-width: 0 0 0 1; -fx-border-color:black;"); // NON-NLS //NOI18N
}
branchPane.getChildren().add(label);
@ -446,10 +444,10 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
TwoPartDateTime(String dateString) {
//find index of separator to spit on
int splitIndex = StringUtils.lastIndexOfAny(dateString, " ", "-", ":");
int splitIndex = StringUtils.lastIndexOfAny(dateString, " ", "-", ":"); //NOI18N
if (splitIndex < 0) { // there is only one part
leaf = dateString;
branch = "";
branch = ""; //NOI18N
} else { //split at index
leaf = StringUtils.substring(dateString, splitIndex + 1);
branch = StringUtils.substring(dateString, 0, splitIndex);

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 Basis Technology Corp.
* Copyright 2013-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -35,7 +35,6 @@ Timeline.ui.ZoomRanges.threeyears.text=Three Years
Timeline.ui.ZoomRanges.fiveyears.text=Five Years
Timeline.ui.ZoomRanges.tenyears.text=Ten Years
Timeline.ui.ZoomRanges.all.text=All
Timeline.ui.TimeLineChart.tooltip.text=Double-click to zoom into range\:\n{0} to {1}\nRight-click to clear.
TimeLineResultView.startDateToEndDate.text={0} to {1}
VisualizationPanel.histogramTask.title=Rebuild Histogram
VisualizationPanel.histogramTask.preparing=preparing
@ -56,4 +55,4 @@ VisualizationPanel.zoomMenuButton.text=Zoom in/out to
VisualizationPanel.snapShotButton.text=Screenshot
VisualizationPanel.detailsToggle.text=Details
VisualizationPanel.countsToggle.text=Counts
VisualizationPanel.resetFiltersButton.text=Reset all filters
VisualizationPanel.resetFiltersButton.text=Reset all filters

View File

@ -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;
}

View File

@ -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>

View 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();
});
}
}
}

View File

@ -5,7 +5,7 @@
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>
<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" type="ToolBar" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" type="ToolBar" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1">
<items>
<Label fx:id="refreshLabel">
<graphic>
@ -17,6 +17,15 @@
</graphic>
</Label>
<Separator orientation="VERTICAL" />
<Label fx:id="statusLabel" layoutX="10.0" layoutY="11.0">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/information-gray.png" />
</image>
</ImageView>
</graphic>
</Label>
<Region fx:id="spacer" maxWidth="1.7976931348623157E308" />
<Separator orientation="VERTICAL" />
<Label fx:id="taskLabel" contentDisplay="RIGHT">

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014 Basis Technology Corp.
* Copyright 2014-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -28,19 +28,21 @@ import javafx.scene.layout.Region;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineUI;
/**
* simple status bar that only shows one possible message determined by
* {@link TimeLineController#newEventsFlag}
*/
public class StatusBar extends ToolBar implements TimeLineUI {
public class StatusBar extends ToolBar {
private TimeLineController controller;
@FXML
private Label refreshLabel;
@FXML
private Label statusLabel;
@FXML
private ProgressBar progressBar;
@ -53,7 +55,8 @@ public class StatusBar extends ToolBar implements TimeLineUI {
@FXML
private Label messageLabel;
public StatusBar() {
public StatusBar(TimeLineController controller) {
this.controller = controller;
FXMLConstructor.construct(this, "StatusBar.fxml"); // NON-NLS
}
@ -70,15 +73,16 @@ public class StatusBar extends ToolBar implements TimeLineUI {
taskLabel.setText(NbBundle.getMessage(this.getClass(), "StatusBar.taskLabel.text"));
taskLabel.setVisible(false);
HBox.setHgrow(spacer, Priority.ALWAYS);
}
@Override
public void setController(TimeLineController controller) {
this.controller = controller;
refreshLabel.visibleProperty().bind(this.controller.getNewEventsFlag());
refreshLabel.managedProperty().bind(this.controller.getNewEventsFlag());
taskLabel.textProperty().bind(this.controller.getTaskTitle());
messageLabel.textProperty().bind(this.controller.getMessage());
progressBar.progressProperty().bind(this.controller.getProgress());
taskLabel.visibleProperty().bind(this.controller.getTasks().emptyProperty().not());
statusLabel.textProperty().bind(this.controller.getStatusProperty());
statusLabel.visibleProperty().bind(statusLabel.textProperty().isNotEmpty());
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014 Basis Technology Corp.
* Copyright 2014-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -19,28 +19,27 @@
package org.sleuthkit.autopsy.timeline.ui;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.event.EventType;
import javafx.scene.Cursor;
import javafx.scene.chart.Axis;
import javafx.scene.chart.Chart;
import javafx.scene.control.Tooltip;
import javafx.scene.control.ContextMenu;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.controlsfx.control.action.ActionGroup;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.actions.Back;
import org.sleuthkit.autopsy.timeline.actions.Forward;
/**
* Interface for TimeLineViews that are 'charts'.
*
* @param <X> the type of values along the horizontal axis
*/
public interface TimeLineChart<X> extends TimeLineView {
public interface TimeLineChart<X> {
// void setController(TimeLineController controller);
IntervalSelector<? extends X> getIntervalSelector();
void setIntervalSelector(IntervalSelector<? extends X> newIntervalSelector);
@ -49,12 +48,9 @@ public interface TimeLineChart<X> extends TimeLineView {
* derived classes should implement this so as to supply an appropriate
* subclass of {@link IntervalSelector}
*
* @param x the initial x position of the new interval selector
* @param axis the axis the new interval selector will be over
*
* @return a new interval selector
*/
IntervalSelector<X> newIntervalSelector(double x, Axis<X> axis);
IntervalSelector<X> newIntervalSelector();
/**
* clear any references to previous interval selectors , including removing
@ -62,6 +58,14 @@ public interface TimeLineChart<X> extends TimeLineView {
*/
void clearIntervalSelector();
public Axis<X> getXAxis();
public TimeLineController getController();
ContextMenu getChartContextMenu();
ContextMenu getChartContextMenu(MouseEvent m);
/**
* drag handler class used by {@link TimeLineChart}s to create
* {@link IntervalSelector}s
@ -69,228 +73,87 @@ public interface TimeLineChart<X> extends TimeLineView {
* @param <X> the type of values along the horizontal axis
* @param <Y> the type of chart this is a drag handler for
*/
class ChartDragHandler<X, Y extends Chart & TimeLineChart<X>> implements EventHandler<MouseEvent> {
static class ChartDragHandler<X, Y extends Chart & TimeLineChart<X>> implements EventHandler<MouseEvent> {
private final Y chart;
private final Axis<X> dateAxis;
private double startX; //hanlder mainstains position of drag start
public ChartDragHandler(Y chart, Axis<X> dateAxis) {
public ChartDragHandler(Y chart) {
this.chart = chart;
this.dateAxis = dateAxis;
}
@Override
public void handle(MouseEvent t) {
if (t.getButton() == MouseButton.SECONDARY) {
if (t.getEventType() == MouseEvent.MOUSE_PRESSED) {
//caputure x-position, incase we are repositioning existing selector
startX = t.getX();
chart.setCursor(Cursor.E_RESIZE);
} else if (t.getEventType() == MouseEvent.MOUSE_DRAGGED) {
if (chart.getIntervalSelector() == null) {
//make new interval selector
chart.setIntervalSelector(chart.newIntervalSelector(t.getX(), dateAxis));
chart.getIntervalSelector().heightProperty().bind(chart.heightProperty().subtract(dateAxis.heightProperty().subtract(dateAxis.tickLengthProperty())));
chart.getIntervalSelector().addEventHandler(MouseEvent.MOUSE_CLICKED, (MouseEvent event) -> {
if (event.getButton() == MouseButton.SECONDARY) {
chart.clearIntervalSelector();
event.consume();
}
});
startX = t.getX();
public void handle(MouseEvent mouseEvent) {
EventType<? extends MouseEvent> mouseEventType = mouseEvent.getEventType();
if (mouseEventType == MouseEvent.MOUSE_PRESSED) {
//caputure x-position, incase we are repositioning existing selector
startX = mouseEvent.getX();
chart.setCursor(Cursor.H_RESIZE);
} else if (mouseEventType == MouseEvent.MOUSE_DRAGGED) {
if (chart.getIntervalSelector() == null) {
//make new interval selector
chart.setIntervalSelector(chart.newIntervalSelector());
chart.getIntervalSelector().prefHeightProperty().bind(chart.heightProperty());
startX = mouseEvent.getX();
chart.getIntervalSelector().relocate(startX, 0);
} else {
//resize/position existing selector
if (mouseEvent.getX() > startX) {
chart.getIntervalSelector().relocate(startX, 0);
chart.getIntervalSelector().setPrefWidth(mouseEvent.getX() - startX);
} else {
//resize/position existing selector
if (t.getX() > startX) {
chart.getIntervalSelector().setX(startX);
chart.getIntervalSelector().setWidth(t.getX() - startX);
} else {
chart.getIntervalSelector().setX(t.getX());
chart.getIntervalSelector().setWidth(startX - t.getX());
}
chart.getIntervalSelector().relocate(mouseEvent.getX(), 0);
chart.getIntervalSelector().setPrefWidth(startX - mouseEvent.getX());
}
} else if (t.getEventType() == MouseEvent.MOUSE_RELEASED) {
chart.setCursor(Cursor.DEFAULT);
}
t.consume();
chart.getIntervalSelector().autosize();
} else if (mouseEventType == MouseEvent.MOUSE_RELEASED) {
chart.setCursor(Cursor.DEFAULT);
} else if (mouseEventType == MouseEvent.MOUSE_CLICKED) {
chart.setCursor(Cursor.DEFAULT);
}
}
}
static class MouseClickedHandler<X, C extends Chart & TimeLineChart<X>> implements EventHandler<MouseEvent> {
private final C chart;
public MouseClickedHandler(C chart) {
this.chart = chart;
}
@Override
public void handle(MouseEvent clickEvent) {
if (chart.getChartContextMenu() != null) {
chart.getChartContextMenu().hide();
}
if (clickEvent.getButton() == MouseButton.SECONDARY && clickEvent.isStillSincePress()) {
chart.getChartContextMenu(clickEvent);
chart.setOnMouseMoved(this);
chart.getChartContextMenu().show(chart, clickEvent.getScreenX(), clickEvent.getScreenY());
clickEvent.consume();
}
}
}
/**
* Visually represents a 'selected' time range, and allows mouse
* interactions with it.
*
* @param <X> the type of values along the x axis this is a selector for
*
* This abstract class requires concrete implementations to implement hook
* methods to handle formating and date 'lookup' of the generic x-axis type
* enum to represent whether the drag is a left/right-edge modification or a
* horizontal slide triggered by dragging the center
*/
static abstract class IntervalSelector<X> extends Rectangle {
enum DragPosition {
private static final double STROKE_WIDTH = 3;
LEFT,
CENTER,
RIGHT
}
private static final double HALF_STROKE = STROKE_WIDTH / 2;
/**
* the Axis this is a selector over
*/
private final Axis<X> dateAxis;
protected Tooltip tooltip;
/////////drag state
private DragPosition dragPosition;
private double startLeft;
private double startX;
private double startWidth;
/////////end drag state
/**
*
* @param x the initial x position of this selector
* @param height the initial height of this selector
* @param axis the {@link Axis<X>} this is a selector over
* @param controller the controller to invoke when this selector is
* double clicked
*/
public IntervalSelector(double x, double height, Axis<X> axis, TimeLineController controller) {
super(x, 0, x, height);
dateAxis = axis;
setStroke(Color.BLUE);
setStrokeWidth(STROKE_WIDTH);
setFill(Color.BLUE.deriveColor(0, 1, 1, 0.5));
setOpacity(0.5);
widthProperty().addListener(o -> {
setTooltip();
});
xProperty().addListener(o -> {
setTooltip();
});
setTooltip();
setOnMouseMoved((MouseEvent event) -> {
Point2D localMouse = sceneToLocal(new Point2D(event.getSceneX(), event.getSceneY()));
final double diffX = getX() - localMouse.getX();
if (Math.abs(diffX) <= HALF_STROKE || Math.abs(diffX + getWidth()) <= HALF_STROKE) {
setCursor(Cursor.E_RESIZE);
} else {
setCursor(Cursor.HAND);
}
});
setOnMousePressed((MouseEvent event) -> {
Point2D localMouse = sceneToLocal(new Point2D(event.getSceneX(), event.getSceneY()));
final double diffX = getX() - localMouse.getX();
startX = event.getX();
startWidth = getWidth();
startLeft = getX();
if (Math.abs(diffX) <= HALF_STROKE) {
dragPosition = IntervalSelector.DragPosition.LEFT;
} else if (Math.abs(diffX + getWidth()) <= HALF_STROKE) {
dragPosition = IntervalSelector.DragPosition.RIGHT;
} else {
dragPosition = IntervalSelector.DragPosition.CENTER;
}
});
setOnMouseDragged((MouseEvent event) -> {
double dX = event.getX() - startX;
switch (dragPosition) {
case CENTER:
setX(startLeft + dX);
break;
case LEFT:
setX(startLeft + dX);
setWidth(startWidth - dX);
break;
case RIGHT:
setWidth(startWidth + dX);
break;
}
});
//have to add handler rather than use convenience methods so that charts can listen for dismisal click
addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
if (event.getClickCount() >= 2) {
//convert to DateTimes, using max/min if null(off axis)
DateTime start = parseDateTime(getSpanStart());
DateTime end = parseDateTime(getSpanEnd());
Interval i = adjustInterval(start.isBefore(end) ? new Interval(start, end) : new Interval(end, start));
controller.pushTimeRange(i);
}
}
});
}
/**
*
* @param i the interval represented by this selector
*
* @return a modified version of {@code i} adjusted to suite the needs
* of the concrete implementation
*/
protected abstract Interval adjustInterval(Interval i);
/**
* format a string representation of the given x-axis value to use in
* the tooltip
*
* @param date a x-axis value of type X
*
* @return a string representation of the given x-axis value
*/
protected abstract String formatSpan(final X date);
/**
* parse an x-axis value to a {@link DateTime}
*
* @param date a x-axis value of type X
*
* @return a {@link DateTime} corresponding to the given x-axis value
*/
protected abstract DateTime parseDateTime(X date);
private void setTooltip() {
final X start = getSpanStart();
final X end = getSpanEnd();
Tooltip.uninstall(this, tooltip);
tooltip = new Tooltip(
NbBundle.getMessage(TimeLineChart.class, "Timeline.ui.TimeLineChart.tooltip.text", formatSpan(start),
formatSpan(end)));
Tooltip.install(this, tooltip);
}
/**
* @return the value along the x-axis corresponding to the left edge of
* the selector
*/
public X getSpanEnd() {
return dateAxis.getValueForDisplay(dateAxis.parentToLocal(getBoundsInParent().getMaxX(), 0).getX());
}
/**
* @return the value along the x-axis corresponding to the right edge of
* the selector
*/
public X getSpanStart() {
return dateAxis.getValueForDisplay(dateAxis.parentToLocal(getBoundsInParent().getMinX(), 0).getX());
}
/**
* enum to represent whether the drag is a left/right-edge modification
* or a horizontal slide triggered by dragging the center
*/
private enum DragPosition {
LEFT, CENTER, RIGHT
}
@NbBundle.Messages({"TimeLineChart.zoomHistoryActionGroup.name=Zoom History"})
static ActionGroup newZoomHistoyActionGroup(TimeLineController controller) {
return new ActionGroup(Bundle.TimeLineChart_zoomHistoryActionGroup_name(),
new Back(controller),
new Forward(controller));
}
}

View File

@ -25,12 +25,11 @@ import javax.swing.SwingUtilities;
import org.joda.time.format.DateTimeFormatter;
import org.openide.nodes.Node;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.explorernodes.EventRootNode;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.explorernodes.EventRootNode;
/**
* Since it was too hard to derive from {@link DataResultPanel}, this class
@ -39,16 +38,16 @@ import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
* {@link DataResultPanel}. That is, this class acts as a sort of bridge/adapter
* between a FilteredEventsModel instance and a DataResultPanel instance.
*/
public class TimeLineResultView implements TimeLineView {
public class TimeLineResultView {
/**
* the {@link DataResultPanel} that is the real view proxied by this class
*/
private final DataResultPanel dataResultPanel;
private TimeLineController controller;
private final TimeLineController controller;
private FilteredEventsModel filteredEvents;
private final FilteredEventsModel filteredEvents;
private Set<Long> selectedEventIDs = new HashSet<>();
@ -56,19 +55,11 @@ public class TimeLineResultView implements TimeLineView {
return dataResultPanel;
}
public TimeLineResultView(DataContent dataContent) {
dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, dataContent);
}
public TimeLineResultView(TimeLineController controller, DataContent dataContent) {
/**
* Set the Controller for this class. Also sets the model provided by the
* controller as the model for this view.
*
* @param controller
*/
@Override
public void setController(TimeLineController controller) {
this.controller = controller;
this.filteredEvents = controller.getEventsModel();
dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, dataContent);
//set up listeners on relevant properties
TimeLineController.getTimeZone().addListener((Observable observable) -> {
@ -78,18 +69,7 @@ public class TimeLineResultView implements TimeLineView {
controller.getSelectedEventIDs().addListener((Observable o) -> {
refresh();
});
setModel(controller.getEventsModel());
}
/**
* Set the Model for this View
*
* @param filteredEvents
*/
@Override
synchronized public void setModel(final FilteredEventsModel filteredEvents) {
this.filteredEvents = filteredEvents;
refresh();
}
/**

View File

@ -23,7 +23,10 @@ import java.util.Date;
import java.util.TimeZone;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TitledPane;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
@ -50,8 +53,9 @@ public class TimeZonePanel extends TitledPane {
}
@FXML
@NbBundle.Messages({"TimeZonePanel.title=Display Times In:"})
public void initialize() {
setText(Bundle.TimeZonePanel_title());
// localRadio.setText("Local Time Zone: " + getTimeZoneString(TimeZone.getDefault()));
localRadio.setText(NbBundle.getMessage(this.getClass(), "TimeZonePanel.localRadio.text"));
otherRadio.setText(NbBundle.getMessage(this.getClass(), "TimeZonePanel.otherRadio.text"));

View File

@ -9,7 +9,7 @@
<?import jfxtras.scene.control.*?>
<?import org.controlsfx.control.*?>
<fx:root prefHeight="-1.0" prefWidth="-1.0" type="javafx.scene.layout.BorderPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<fx:root prefHeight="-1.0" prefWidth="-1.0" type="javafx.scene.layout.BorderPane" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1">
<top>
<ToolBar fx:id="toolBar" prefWidth="200.0" BorderPane.alignment="CENTER">
<items>
@ -115,7 +115,7 @@
</Separator>
<HBox>
<children>
<Button fx:id="zoomOutButton" mnemonicParsing="false">
<Button fx:id="zoomOutButton" contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false">
<graphic>
<ImageView pickOnBounds="true" preserveRatio="true">
<image>
@ -127,7 +127,7 @@
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
</HBox.margin>
</Button>
<Button fx:id="zoomInButton" mnemonicParsing="false">
<Button fx:id="zoomInButton" contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false">
<graphic>
<ImageView pickOnBounds="true" preserveRatio="true">
<image>
@ -143,7 +143,7 @@
</HBox>
<MenuButton fx:id="zoomMenuButton" mnemonicParsing="false">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<ImageView pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/magnifier-left.png" />
</image>

View File

@ -33,8 +33,6 @@ import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.geometry.Rectangle2D;
import javafx.scene.SnapshotParameters;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.MenuButton;
@ -48,7 +46,6 @@ import javafx.scene.control.Tooltip;
import javafx.scene.effect.Lighting;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
@ -66,6 +63,7 @@ import jfxtras.scene.control.LocalDateTimeTextField;
import org.controlsfx.control.NotificationPane;
import org.controlsfx.control.RangeSlider;
import org.controlsfx.control.action.Action;
import org.controlsfx.control.action.ActionUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Interval;
@ -74,11 +72,12 @@ import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.VisualizationMode;
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
import org.sleuthkit.autopsy.timeline.actions.SaveSnapshot;
import org.sleuthkit.autopsy.timeline.actions.SaveSnapshotAsReport;
import org.sleuthkit.autopsy.timeline.actions.ZoomIn;
import org.sleuthkit.autopsy.timeline.actions.ZoomOut;
import org.sleuthkit.autopsy.timeline.actions.ZoomToEvents;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.events.TagsUpdatedEvent;
import org.sleuthkit.autopsy.timeline.filters.TagsFilter;
@ -86,18 +85,18 @@ import static org.sleuthkit.autopsy.timeline.ui.Bundle.VisualizationPanel_refres
import static org.sleuthkit.autopsy.timeline.ui.Bundle.VisualizationPanel_tagsAddedOrDeleted;
import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane;
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.NavPanel;
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
/**
* A Container for an {@link AbstractVisualization}, has a toolbar on top to
* hold settings widgets supplied by contained {@link AbstractVisualization},
* A container for an {@link AbstractVisualizationPane}, has a toolbar on top to
* hold settings widgets supplied by contained {@link AbstAbstractVisualization}
* and the histogram / timeselection on bottom. Also supplies containers for
* replacement axis to contained {@link AbstractVisualization}
* replacement axis to contained {@link AbstractAbstractVisualization}
*
* TODO: refactor common code out of histogram and CountsView? -jm
*/
public class VisualizationPanel extends BorderPane implements TimeLineView {
final public class VisualizationPanel extends BorderPane {
private static final Image INFORMATION = new Image("org/sleuthkit/autopsy/timeline/images/information.png", 16, 16, true, true); // NON-NLS
private static final Image REFRESH = new Image("org/sleuthkit/autopsy/timeline/images/arrow-circle-double-135.png"); // NON-NLS
@ -107,9 +106,9 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
@GuardedBy("this")
private LoggedTask<Void> histogramTask;
private final NavPanel navPanel;
private final EventsTree eventsTree;
private AbstractVisualization<?, ?, ?, ?> visualization;
private AbstractVisualizationPane<?, ?, ?, ?> visualization;
//// range slider and histogram componenets
@FXML
@ -178,8 +177,8 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
private FilteredEventsModel filteredEvents;
private final ChangeListener<Object> rangeSliderListener
= (observable1, oldValue, newValue) -> {
private final ChangeListener<Object> rangeSliderListener =
(observable1, oldValue, newValue) -> {
if (rangeSlider.isHighValueChanging() == false && rangeSlider.isLowValueChanging() == false) {
Long minTime = filteredEvents.getMinTime() * 1000;
controller.pushTimeRange(new Interval(
@ -207,14 +206,15 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
static private final Lighting lighting = new Lighting();
public VisualizationPanel(NavPanel navPanel) {
this.navPanel = navPanel;
public VisualizationPanel(TimeLineController controller, EventsTree eventsTree) {
this.controller = controller;
this.eventsTree = eventsTree;
FXMLConstructor.construct(this, "VisualizationPanel.fxml"); // NON-NLS
}
@FXML // This method is called by the FXMLLoader when initialization is complete
@NbBundle.Messages("VisualizationPanel.refresh=refresh")
protected void initialize() {
void initialize() {
assert endPicker != null : "fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
assert histogramBox != null : "fx:id=\"histogramBox\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
assert startPicker != null : "fx:id=\"startPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
@ -289,32 +289,30 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
}
zoomMenuButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomMenuButton.text")); // NON-NLS
zoomOutButton.setOnAction(e -> {
controller.pushZoomOutTime();
});
zoomInButton.setOnAction(e -> {
controller.pushZoomInTime();
});
snapShotButton.setOnAction((ActionEvent event) -> {
//take snapshot
final SnapshotParameters snapshotParameters = new SnapshotParameters();
snapshotParameters.setViewport(new Rectangle2D(visualization.getBoundsInParent().getMinX(), visualization.getBoundsInParent().getMinY(),
visualization.getBoundsInParent().getWidth(),
contextPane.getLayoutBounds().getHeight() + visualization.getLayoutBounds().getHeight() + partPane.getLayoutBounds().getHeight()
));
WritableImage snapshot = this.snapshot(snapshotParameters, null);
//pass snapshot to save action
new SaveSnapshot(controller, snapshot).handle(event);
});
snapShotButton.setOnAction(event ->
this.snapshot(snapShotResult -> {
new SaveSnapshotAsReport(controller, snapShotResult.getImage()).handle(event);
return null;
}, null, null)
);
snapShotButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapShotButton.text")); // NON-NLS
}
if (this.filteredEvents != null && this.filteredEvents != controller.getEventsModel()) {
this.filteredEvents.unRegisterForEvents(this);
this.filteredEvents.timeRangeProperty().removeListener(timeRangeInvalidationListener);
this.filteredEvents.zoomParametersProperty().removeListener(zoomListener);
}
if (this.filteredEvents != controller.getEventsModel()) {
controller.getEventsModel().registerForEvents(this);
controller.getEventsModel().timeRangeProperty().addListener(timeRangeInvalidationListener);
controller.getEventsModel().zoomParametersProperty().addListener(zoomListener);
}
this.filteredEvents = controller.getEventsModel();
refreshTimeUI(controller.getEventsModel().timeRangeProperty().get());
ActionUtils.configureButton(new ZoomOut(controller), zoomOutButton);
ActionUtils.configureButton(new ZoomIn(controller), zoomInButton);
@Override
public synchronized void setController(TimeLineController controller) {
this.controller = controller;
setModel(controller.getEventsModel());
setViewMode(controller.viewModeProperty().get());
controller.getNeedsHistogramRebuild().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
if (newValue) {
@ -329,39 +327,20 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
refreshHistorgram();
}
@Override
public void setModel(FilteredEventsModel filteredEvents) {
if (this.filteredEvents != null && this.filteredEvents != filteredEvents) {
this.filteredEvents.unRegisterForEvents(this);
this.filteredEvents.timeRangeProperty().removeListener(timeRangeInvalidationListener);
this.filteredEvents.zoomParametersProperty().removeListener(zoomListener);
}
if (this.filteredEvents != filteredEvents) {
filteredEvents.registerForEvents(this);
filteredEvents.timeRangeProperty().addListener(timeRangeInvalidationListener);
filteredEvents.zoomParametersProperty().addListener(zoomListener);
}
this.filteredEvents = filteredEvents;
refreshTimeUI(filteredEvents.timeRangeProperty().get());
}
private void setViewMode(VisualizationMode visualizationMode) {
switch (visualizationMode) {
case COUNTS:
setVisualization(new CountsViewPane(partPane, contextPane, spacer));
setVisualization(new CountsViewPane(controller, partPane, contextPane, spacer));
countsToggle.setSelected(true);
break;
case DETAIL:
setVisualization(new DetailViewPane(partPane, contextPane, spacer));
setVisualization(new DetailViewPane(controller, partPane, contextPane, spacer));
detailsToggle.setSelected(true);
break;
}
}
private synchronized void setVisualization(final AbstractVisualization<?, ?, ?, ?> newViz) {
private synchronized void setVisualization(final AbstractVisualizationPane<?, ?, ?, ?> newViz) {
Platform.runLater(() -> {
synchronized (VisualizationPanel.this) {
if (visualization != null) {
@ -370,24 +349,25 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
}
visualization = newViz;
visualization.update();
toolBar.getItems().addAll(newViz.getSettingsNodes());
visualization.setController(controller);
notificationPane.setContent(visualization);
if (visualization instanceof DetailViewPane) {
navPanel.setDetailViewPane((DetailViewPane) visualization);
eventsTree.setDetailViewPane((DetailViewPane) visualization);
}
visualization.hasEvents.addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
visualization.hasEvents.addListener((observable, oldValue, newValue) -> {
if (newValue == false) {
notificationPane.setContent(new StackPane(visualization, new Region() {
{
setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)));
setOpacity(.3);
}
}, new NoEventsDialog(() -> {
notificationPane.setContent(visualization);
})));
notificationPane.setContent(
new StackPane(visualization,
new Region() {
{
setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)));
setOpacity(.3);
}
},
new NoEventsDialog(() -> notificationPane.setContent(visualization))));
} else {
notificationPane.setContent(visualization);
}
@ -552,7 +532,6 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
private NoEventsDialog(Runnable closeCallback) {
this.closeCallback = closeCallback;
FXMLConstructor.construct(this, "NoEventsDialog.fxml"); // NON-NLS
}
@FXML
@ -562,15 +541,9 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
noEventsDialogLabel.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.noEventsDialogLabel.text")); // NON-NLS
zoomButton.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.zoomButton.text")); // NON-NLS
ActionUtils.configureButton(new ZoomToEvents(controller), zoomButton);
Action zoomOutAction = new ZoomOut(controller);
zoomButton.setOnAction(zoomOutAction);
zoomButton.disableProperty().bind(zoomOutAction.disabledProperty());
dismissButton.setOnAction(e -> {
closeCallback.run();
});
dismissButton.setOnAction(actionEvent -> closeCallback.run());
Action defaultFiltersAction = new ResetFilters(controller);
resetFiltersButton.setOnAction(defaultFiltersAction);
resetFiltersButton.disableProperty().bind(defaultFiltersAction.disabledProperty());

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 Basis Technology Corp.
* Copyright 2013-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -26,14 +26,12 @@ Timeline.ui.countsview.menuItem.selectTimeRange=Select Time Range
Timeline.ui.countsview.menuItem.selectEventType=Select Event Type
Timeline.ui.countsview.menuItem.selectTimeandType=Select Time and Type
Timeline.ui.countsview.menuItem.zoomIntoTimeRange=Zoom into Time Range
Timeline.ui.countsview.contextMenu.ActionGroup.zoomHistory.title=Zoom History
CountsViewPane.loggedTask.name=Updating Counts Graph
CountsViewPane.loggedTask.prepUpdate=preparing update
CountsViewPane.loggedTask.resetUI=resetting ui
CountsViewPane.tooltip.text={0} {1} events\nbetween {2}\nand {3}
CountsViewPane.loggedTask.updatingCounts=updating counts
CountsViewPane.loggedTask.wrappingUp=wrapping up
EventCountsChart.contextMenu.zoomHistory.name=Zoom History
CountsViewPane.scaleLabel.text=Scale\:
CountsViewPane.logRadio.text=Logarithmic
CountsViewPane.linearRadio.text=Linear

View File

@ -66,14 +66,13 @@ import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.VisualizationMode;
import org.sleuthkit.autopsy.timeline.actions.Back;
import org.sleuthkit.autopsy.timeline.actions.Forward;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
/**
@ -97,7 +96,7 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
* TODO: refactor common code out of this class and ClusterChartPane into
* AbstractChartView
*/
public class CountsViewPane extends AbstractVisualization<String, Number, Node, EventCountsChart> {
public class CountsViewPane extends AbstractVisualizationPane<String, Number, Node, EventCountsChart> {
private static final Effect SELECTED_NODE_EFFECT = new Lighting();
@ -213,17 +212,17 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
node.setStyle("-fx-border-width: 2; -fx-border-color: " + ColorUtilities.getRGBCode(et.getSuperType().getColor()) + "; -fx-bar-fill: " + ColorUtilities.getRGBCode(et.getColor())); // NON-NLS
node.setCursor(Cursor.HAND);
final Tooltip tooltip = new Tooltip(
NbBundle.getMessage(this.getClass(), "CountsViewPane.tooltip.text",
count,
et.getDisplayName(),
dateString,
interval.getEnd().toString(
rangeInfo.getTickFormatter())));
tooltip.setGraphic(new ImageView(et.getFXImage()));
Tooltip.install(node, tooltip);
node.setOnMouseEntered((MouseEvent event) -> {
//defer tooltip creation till needed, this had a surprisingly large impact on speed of loading the chart
final Tooltip tooltip = new Tooltip(
NbBundle.getMessage(this.getClass(), "CountsViewPane.tooltip.text",
count,
et.getDisplayName(),
dateString,
interval.getEnd().toString(
rangeInfo.getTickFormatter())));
tooltip.setGraphic(new ImageView(et.getFXImage()));
Tooltip.install(node, tooltip);
node.setEffect(new DropShadow(10, et.getColor()));
});
node.setOnMouseExited((MouseEvent event) -> {
@ -279,13 +278,15 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
};
}
public CountsViewPane(Pane partPane, Pane contextPane, Region spacer) {
super(partPane, contextPane, spacer);
chart = new EventCountsChart(dateAxis, countAxis);
public CountsViewPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) {
super(controller, partPane, contextPane, spacer);
chart = new EventCountsChart(controller, dateAxis, countAxis);
setChartClickHandler();
chart.setData(dataSets);
setCenter(chart);
Tooltip.install(chart, getDragTooltip());
settingsNodes = new ArrayList<>(new CountsViewSettingsPane().getChildrenUnmodifiable());
dateAxis.getTickMarks().addListener((Observable observable) -> {
@ -310,9 +311,6 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
});
}
@Override
protected NumberAxis getYAxis() {
return countAxis;
@ -534,7 +532,7 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
scaleLabel.setText(NbBundle.getMessage(this.getClass(), "CountsViewPane.scaleLabel.text"));
}
public CountsViewSettingsPane() {
CountsViewSettingsPane() {
FXMLConstructor.construct(this, "CountsViewSettingsPane.fxml"); // NON-NLS
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014 Basis Technology Corp.
* Copyright 2014-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -19,24 +19,19 @@
package org.sleuthkit.autopsy.timeline.ui.countsview;
import java.util.Arrays;
import java.util.Collections;
import javafx.scene.chart.Axis;
import java.util.MissingResourceException;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.StackedBarChart;
import javafx.scene.control.ContextMenu;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.util.StringConverter;
import org.controlsfx.control.action.ActionGroup;
import org.controlsfx.control.action.ActionUtils;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.actions.Back;
import org.sleuthkit.autopsy.timeline.actions.Forward;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.ui.IntervalSelector;
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
@ -44,11 +39,16 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
* Customized {@link StackedBarChart<String, Number>} used to display the event
* counts in {@link CountsViewPane}
*/
class EventCountsChart extends StackedBarChart<String, Number> implements TimeLineChart<String> {
final class EventCountsChart extends StackedBarChart<String, Number> implements TimeLineChart<String> {
private ContextMenu contextMenu;
private ContextMenu chartContextMenu;
private TimeLineController controller;
@Override
public ContextMenu getChartContextMenu() {
return chartContextMenu;
}
private final TimeLineController controller;
private IntervalSelector<? extends String> intervalSelector;
@ -59,8 +59,9 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
*/
private RangeDivisionInfo rangeInfo;
EventCountsChart(CategoryAxis dateAxis, NumberAxis countAxis) {
EventCountsChart(TimeLineController controller, CategoryAxis dateAxis, NumberAxis countAxis) {
super(dateAxis, countAxis);
this.controller = controller;
//configure constant properties on axes and chart
dateAxis.setAnimated(true);
dateAxis.setLabel(null);
@ -80,19 +81,13 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
setAnimated(true);
setTitle(null);
//use one handler with an if chain because it maintains state
ChartDragHandler<String, EventCountsChart> dragHandler = new ChartDragHandler<>(this, getXAxis());
setOnMousePressed(dragHandler);
setOnMouseReleased(dragHandler);
setOnMouseDragged(dragHandler);
ChartDragHandler<String, EventCountsChart> chartDragHandler = new ChartDragHandler<>(this);
setOnMousePressed(chartDragHandler);
setOnMouseReleased(chartDragHandler);
setOnMouseDragged(chartDragHandler);
setOnMouseClicked(new MouseClickedHandler<>(this));
setOnMouseClicked((MouseEvent clickEvent) -> {
contextMenu.hide();
if (clickEvent.getButton() == MouseButton.SECONDARY && clickEvent.isStillSincePress()) {
contextMenu.show(EventCountsChart.this, clickEvent.getScreenX(), clickEvent.getScreenY());
clickEvent.consume();
}
});
}
@Override
@ -102,16 +97,21 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
}
@Override
public final synchronized void setController(TimeLineController controller) {
this.controller = controller;
setModel(this.controller.getEventsModel());
//we have defered creating context menu until control is available
contextMenu = ActionUtils.createContextMenu(
Arrays.asList(new ActionGroup(
NbBundle.getMessage(this.getClass(), "EventCountsChart.contextMenu.zoomHistory.name"),
new Back(controller),
new Forward(controller))));
contextMenu.setAutoHide(true);
public ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException {
if (chartContextMenu != null) {
chartContextMenu.hide();
}
chartContextMenu = ActionUtils.createContextMenu(
Arrays.asList(
TimeLineChart.newZoomHistoyActionGroup(controller)));
chartContextMenu.setAutoHide(true);
return chartContextMenu;
}
@Override
public TimeLineController getController() {
return controller;
}
@Override
@ -126,16 +126,8 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
}
@Override
public void setModel(FilteredEventsModel filteredEvents) {
filteredEvents.zoomParametersProperty().addListener(o -> {
clearIntervalSelector();
controller.selectEventIDs(Collections.emptyList());
});
}
@Override
public CountsIntervalSelector newIntervalSelector(double x, Axis<String> dateAxis) {
return new CountsIntervalSelector(x, getHeight() - dateAxis.getHeight() - dateAxis.getTickLength(), dateAxis, controller);
public CountsIntervalSelector newIntervalSelector() {
return new CountsIntervalSelector(this);
}
/**
@ -145,7 +137,7 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
* @return the context menu for this chart
*/
ContextMenu getContextMenu() {
return contextMenu;
return chartContextMenu;
}
void setRangeInfo(RangeDivisionInfo rangeInfo) {
@ -175,10 +167,13 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
* Interval Selector for the counts chart, adjusts interval based on
* rangeInfo to include final period
*/
private class CountsIntervalSelector extends IntervalSelector<String> {
final static private class CountsIntervalSelector extends IntervalSelector<String> {
public CountsIntervalSelector(double x, double height, Axis<String> axis, TimeLineController controller) {
super(x, height, axis, controller);
private final EventCountsChart countsChart;
CountsIntervalSelector(EventCountsChart chart) {
super(chart);
this.countsChart = chart;
}
@Override
@ -195,12 +190,13 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
final DateTime lowerDate = new DateTime(lowerBound, TimeLineController.getJodaTimeZone());
final DateTime upperDate = new DateTime(upperBound, TimeLineController.getJodaTimeZone());
//add extra block to end that gets cut of by conversion from string/category.
return new Interval(lowerDate, upperDate.plus(rangeInfo.getPeriodSize().getPeriod()));
return new Interval(lowerDate, upperDate.plus(countsChart.rangeInfo.getPeriodSize().getPeriod()));
}
@Override
protected DateTime parseDateTime(String date) {
return date == null ? new DateTime(rangeInfo.getLowerBound()) : rangeInfo.getTickFormatter().parseDateTime(date);
return date == null ? new DateTime(countsChart.rangeInfo.getLowerBound()) : countsChart.rangeInfo.getTickFormatter().parseDateTime(date);
}
}
}

View File

@ -73,25 +73,24 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
/**
* Controller class for a {@link EventDetailChart} based implementation of a
* Controller class for a {@link EventDetailsChart} based implementation of a
* TimeLineView.
*
* This class listens to changes in the assigned {@link FilteredEventsModel} and
* updates the internal {@link EventDetailChart} to reflect the currently
* updates the internal {@link EventDetailsChart} to reflect the currently
* requested events.
*
* Concurrency Policy: Access to the private members clusterChart, dateAxis,
* EventTypeMap, and dataSets is all linked directly to the ClusterChart which
* must only be manipulated on the JavaFx thread.
*/
public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster, EventBundleNodeBase<?, ?, ?>, EventDetailChart> {
public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventCluster, EventBundleNodeBase<?, ?, ?>, EventDetailsChart> {
private final static Logger LOGGER = Logger.getLogger(DetailViewPane.class.getName());
private MultipleSelectionModel<TreeItem<EventBundle<?>>> treeSelectionModel;
@ -114,13 +113,15 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
return chart.getEventBundles();
}
public DetailViewPane(Pane partPane, Pane contextPane, Region spacer) {
super(partPane, contextPane, spacer);
chart = new EventDetailChart(dateAxis, verticalAxis, selectedNodes);
public DetailViewPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) {
super(controller, partPane, contextPane, spacer);
chart = new EventDetailsChart(controller, dateAxis, verticalAxis, selectedNodes);
setChartClickHandler();
chart.setData(dataSets);
setCenter(chart);
chart.setPrefHeight(USE_COMPUTED_SIZE);
settingsNodes = new ArrayList<>(new DetailViewSettingsPane().getChildrenUnmodifiable());
@ -211,11 +212,6 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
}
@Override
public synchronized void setModel(FilteredEventsModel filteredEvents) {
super.setModel(filteredEvents);
}
private void incrementScrollValue(int factor) {
vertScrollBar.valueProperty().set(Math.max(0, Math.min(100, vertScrollBar.getValue() + factor * (chart.getHeight() / chart.maxVScrollProperty().get()))));
}
@ -477,5 +473,4 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
return chart.new HideDescriptionAction(description, descriptionLoD);
}
}

View File

@ -64,6 +64,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
import static org.sleuthkit.autopsy.timeline.ui.detailview.EventBundleNodeBase.show;
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
import org.sleuthkit.datamodel.SleuthkitCase;
@ -96,7 +97,7 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
b.setManaged(show);
}
protected final EventDetailChart chart;
protected final EventDetailsChart chart;
final SimpleObjectProperty<DescriptionLoD> descLOD = new SimpleObjectProperty<>();
final SimpleObjectProperty<DescriptionVisibility> descVisibility = new SimpleObjectProperty<>(DescriptionVisibility.SHOWN);
protected final BundleType eventBundle;
@ -119,9 +120,9 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
final ImageView tagIV = new ImageView(TAG);
final HBox infoHBox = new HBox(5, descrLabel, countLabel, hashIV, tagIV);
private Tooltip tooltip;
private final Tooltip tooltip = new Tooltip("loading...");
public EventBundleNodeBase(EventDetailChart chart, BundleType eventBundle, ParentNodeType parentNode) {
public EventBundleNodeBase(EventDetailsChart chart, BundleType eventBundle, ParentNodeType parentNode) {
this.eventBundle = eventBundle;
this.parentNode = parentNode;
this.chart = chart;
@ -156,7 +157,6 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
infoHBox.setMaxWidth(USE_PREF_SIZE);
infoHBox.setPadding(new Insets(2, 5, 2, 5));
infoHBox.setAlignment(Pos.TOP_LEFT);
infoHBox.setPickOnBounds(true);
//set up subnode pane sizing contraints
subNodePane.setPrefHeight(USE_COMPUTED_SIZE);
@ -165,27 +165,34 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
subNodePane.setMinWidth(USE_PREF_SIZE);
subNodePane.setMaxWidth(USE_PREF_SIZE);
Tooltip.install(this, this.tooltip);
//set up mouse hover effect and tooltip
setOnMouseEntered((MouseEvent e) -> {
/*
* defer tooltip creation till needed, this had a surprisingly large
* impact on speed of loading the chart
* defer tooltip content creation till needed, this had a
* surprisingly large impact on speed of loading the chart
*/
installTooltip();
Tooltip.uninstall(chart, AbstractVisualizationPane.getDragTooltip());
showHoverControls(true);
toFront();
});
setOnMouseExited((MouseEvent event) -> {
showHoverControls(false);
if (parentNode != null) {
parentNode.showHoverControls(true);
} else {
Tooltip.install(chart, AbstractVisualizationPane.getDragTooltip());
}
});
setDescriptionVisibility(DescriptionVisibility.SHOWN);
descVisibility.addListener((ObservableValue<? extends DescriptionVisibility> observable, DescriptionVisibility oldValue, DescriptionVisibility newValue) -> {
setDescriptionVisibility(newValue);
setDescriptionVisibiltiyImpl(newValue);
});
setDescriptionVisibiltiyImpl(DescriptionVisibility.SHOWN);
}
final DescriptionLoD getDescriptionLoD() {
@ -210,8 +217,11 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
"EventBundleNodeBase.tooltip.text={0} {1} events\n{2}\nbetween\t{3}\nand \t{4}"})
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private void installTooltip() {
if (tooltip == null) {
if (tooltip.getText().equalsIgnoreCase("loading...")) {
final Task<String> tooltTipTask = new Task<String>() {
{
updateTitle("loading tooltip");
}
@Override
protected String call() throws Exception {
@ -252,13 +262,10 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
protected void succeeded() {
super.succeeded();
try {
tooltip = new Tooltip(get());
tooltip.setAutoHide(true);
Tooltip.install(EventBundleNodeBase.this, tooltip);
tooltip.setText(get());
tooltip.setGraphic(null);
} catch (InterruptedException | ExecutionException ex) {
LOGGER.log(Level.SEVERE, "Tooltip generation failed.", ex);
Tooltip.uninstall(EventBundleNodeBase.this, tooltip);
tooltip = null;
}
}
};
@ -288,15 +295,18 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
return subNodes;
}
abstract void setDescriptionVisibility(DescriptionVisibility get);
abstract void setDescriptionVisibiltiyImpl(DescriptionVisibility get);
void showHoverControls(final boolean showControls) {
Effect dropShadow = dropShadowMap.computeIfAbsent(getEventType(),
eventType -> new DropShadow(-10, eventType.getColor()));
setEffect(showControls ? dropShadow : null);
enableTooltip(showControls);
if (parentNode != null) {
parentNode.enableTooltip(false);
parentNode.showHoverControls(false);
}
}
final EventType getEventType() {
@ -330,8 +340,15 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
*/
abstract void setDescriptionWidth(double w);
void setDescriptionVisibilityLevel(DescriptionVisibility get) {
void setDescriptionVisibility(DescriptionVisibility get) {
descVisibility.set(get);
}
void enableTooltip(boolean toolTipEnabled) {
if (toolTipEnabled) {
Tooltip.install(this, tooltip);
} else {
Tooltip.uninstall(this, tooltip);
}
}
}

View File

@ -74,7 +74,7 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
final Button plusButton = ActionUtils.createButton(new ExpandClusterAction(), ActionUtils.ActionTextBehavior.HIDE);
final Button minusButton = ActionUtils.createButton(new CollapseClusterAction(), ActionUtils.ActionTextBehavior.HIDE);
public EventClusterNode(EventDetailChart chart, EventCluster eventCluster, EventStripeNode parentNode) {
public EventClusterNode(EventDetailsChart chart, EventCluster eventCluster, EventStripeNode parentNode) {
super(chart, eventCluster, parentNode);
setMinHeight(24);
@ -114,7 +114,7 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
}
@Override
public void setDescriptionVisibility(DescriptionVisibility descrVis) {
void setDescriptionVisibiltiyImpl(DescriptionVisibility descrVis) {
final int size = getEventBundle().getEventIDs().size();
switch (descrVis) {
case HIDDEN:

View File

@ -53,6 +53,7 @@ import javafx.scene.chart.Axis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
@ -62,14 +63,11 @@ import javafx.scene.shape.StrokeLineCap;
import javafx.util.Duration;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.controlsfx.control.action.Action;
import org.controlsfx.control.action.ActionGroup;
import org.controlsfx.control.action.ActionUtils;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.actions.Back;
import org.sleuthkit.autopsy.timeline.actions.Forward;
import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
@ -77,6 +75,8 @@ import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
import org.sleuthkit.autopsy.timeline.ui.IntervalSelector;
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
@ -94,7 +94,7 @@ import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
*
* //TODO: refactor the projected lines to a separate class. -jm
*/
public final class EventDetailChart extends XYChart<DateTime, EventCluster> implements TimeLineChart<DateTime> {
public final class EventDetailsChart extends XYChart<DateTime, EventCluster> implements TimeLineChart<DateTime> {
private static final Image HIDE = new Image("/org/sleuthkit/autopsy/timeline/images/eye--minus.png"); // NON-NLS
private static final Image SHOW = new Image("/org/sleuthkit/autopsy/timeline/images/eye--plus.png"); // NON-NLS
@ -102,11 +102,18 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
private static final int PROJECTED_LINE_Y_OFFSET = 5;
private static final int PROJECTED_LINE_STROKE_WIDTH = 5;
private static final int MINIMUM_EVENT_NODE_GAP = 4;
private final TimeLineController controller;
private final FilteredEventsModel filteredEvents;
private ContextMenu chartContextMenu;
private TimeLineController controller;
private FilteredEventsModel filteredEvents;
public ContextMenu getChartContextMenu() {
return chartContextMenu;
}
/**
* a user positionable vertical line to help compare events
@ -183,8 +190,21 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
*/
final SimpleDoubleProperty truncateWidth = new SimpleDoubleProperty(200.0);
EventDetailChart(DateAxis dateAxis, final Axis<EventCluster> verticalAxis, ObservableList<EventBundleNodeBase<?, ?, ?>> selectedNodes) {
EventDetailsChart(TimeLineController controller, DateAxis dateAxis, final Axis<EventCluster> verticalAxis, ObservableList<EventBundleNodeBase<?, ?, ?>> selectedNodes) {
super(dateAxis, verticalAxis);
this.controller = controller;
this.filteredEvents = this.controller.getEventsModel();
filteredEvents.zoomParametersProperty().addListener(o -> {
clearGuideLine();
clearIntervalSelector();
selectedNodes.clear();
projectionMap.clear();
controller.selectEventIDs(Collections.emptyList());
});
Tooltip.install(this, AbstractVisualizationPane.getDragTooltip());
dateAxis.setAutoRanging(false);
verticalAxis.setVisible(false);//TODO: why doesn't this hide the vertical axis, instead we have to turn off all parts individually? -jm
@ -204,6 +224,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
truncateAll.addListener(layoutInvalidationListener);
truncateWidth.addListener(layoutInvalidationListener);
descrVisibility.addListener(layoutInvalidationListener);
getController().getQuickHideFilters().addListener(layoutInvalidationListener);
//this is needed to allow non circular binding of the guideline and timerangeRect heights to the height of the chart
//TODO: seems like a hack, can we remove? -jm
@ -211,22 +232,12 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
setPrefHeight(boundsInLocalProperty().get().getHeight());
});
///////set up mouse listeners
setOnMouseClicked((MouseEvent clickEvent) -> {
if (chartContextMenu != null) {
chartContextMenu.hide();
}
if (clickEvent.getButton() == MouseButton.SECONDARY && clickEvent.isStillSincePress()) {
getChartContextMenu(clickEvent);
chartContextMenu.show(EventDetailChart.this, clickEvent.getScreenX(), clickEvent.getScreenY());
clickEvent.consume();
}
});
//use one handler with an if chain because it maintains state
final ChartDragHandler<DateTime, EventDetailChart> dragHandler = new ChartDragHandler<>(this, getXAxis());
setOnMousePressed(dragHandler);
setOnMouseReleased(dragHandler);
setOnMouseDragged(dragHandler);
ChartDragHandler<DateTime, EventDetailsChart> chartDragHandler = new ChartDragHandler<>(this);
setOnMousePressed(chartDragHandler);
setOnMouseReleased(chartDragHandler);
setOnMouseDragged(chartDragHandler);
setOnMouseClicked(new MouseClickedHandler<>(this));
this.selectedNodes = selectedNodes;
this.selectedNodes.addListener(new SelectionChangeHandler());
@ -236,21 +247,20 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
return bundles;
}
TimeLineController getController() {
@Override
public TimeLineController getController() {
return controller;
}
@NbBundle.Messages({"EventDetailChart.chartContextMenu.placeMarker.name=Place Marker",
"EventDetailChart.contextMenu.zoomHistory.name=Zoom History"})
ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException {
@Override
public ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException {
if (chartContextMenu != null) {
chartContextMenu.hide();
}
chartContextMenu = ActionUtils.createContextMenu(Arrays.asList(new PlaceMarkerAction(clickEvent),
new ActionGroup(Bundle.EventDetailChart_contextMenu_zoomHistory_name(),
new Back(controller),
new Forward(controller))));
// new StartIntervalSelectionAction(clickEvent, dragHandler),
TimeLineChart.newZoomHistoyActionGroup(controller)));
chartContextMenu.setAutoHide(true);
return chartContextMenu;
}
@ -266,32 +276,8 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
}
@Override
public synchronized void setController(TimeLineController controller) {
this.controller = controller;
setModel(this.controller.getEventsModel());
getController().getQuickHideFilters().addListener(layoutInvalidationListener);
}
@Override
public void setModel(FilteredEventsModel filteredEvents) {
if (this.filteredEvents != filteredEvents) {
filteredEvents.zoomParametersProperty().addListener(o -> {
clearGuideLine();
clearIntervalSelector();
selectedNodes.clear();
projectionMap.clear();
controller.selectEventIDs(Collections.emptyList());
});
}
this.filteredEvents = filteredEvents;
}
@Override
public IntervalSelector<DateTime> newIntervalSelector(double x, Axis<DateTime> axis) {
return new DetailIntervalSelector(x, getHeight() - axis.getHeight() - axis.getTickLength(), axis, controller);
public IntervalSelector<DateTime> newIntervalSelector() {
return new DetailIntervalSelector(this);
}
synchronized void setBandByType(Boolean t1) {
@ -300,12 +286,12 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
/**
* get the DateTime along the x-axis that corresponds to the given
* x-coordinate in the coordinate system of this {@link EventDetailChart}
* x-coordinate in the coordinate system of this {@link EventDetailsChart}
*
* @param x a x-coordinate in the space of this {@link EventDetailChart}
* @param x a x-coordinate in the space of this {@link EventDetailsChart}
*
* @return the DateTime along the x-axis corresponding to the given x value
* (in the space of this {@link EventDetailChart}
* (in the space of this {@link EventDetailsChart}
*/
public DateTime getDateTimeForPosition(double x) {
return getXAxis().getValueForDisplay(getXAxis().parentToLocal(x, 0).getX());
@ -352,7 +338,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
return EventStripe.merge(u, v);
}
);
EventStripeNode stripeNode = new EventStripeNode(EventDetailChart.this, eventStripe, null);
EventStripeNode stripeNode = new EventStripeNode(EventDetailsChart.this, eventStripe, null);
stripeNodeMap.put(eventStripe, stripeNode);
nodeGroup.getChildren().add(stripeNode);
data.setNode(stripeNode);
@ -498,7 +484,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
bundleNode.setVisible(true);
bundleNode.setManaged(true);
//apply advanced layout description visibility options
bundleNode.setDescriptionVisibilityLevel(descrVisibility.get());
bundleNode.setDescriptionVisibility(descrVisibility.get());
bundleNode.setDescriptionWidth(truncateAll.get() ? truncateWidth.get() : USE_PREF_SIZE);
//do recursive layout
@ -586,8 +572,8 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
static private class DetailIntervalSelector extends IntervalSelector<DateTime> {
DetailIntervalSelector(double x, double height, Axis<DateTime> axis, TimeLineController controller) {
super(x, height, axis, controller);
DetailIntervalSelector(EventDetailsChart chart) {
super(chart);
}
@Override
@ -608,6 +594,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
private class PlaceMarkerAction extends Action {
@NbBundle.Messages({"EventDetailChart.chartContextMenu.placeMarker.name=Place Marker"})
PlaceMarkerAction(MouseEvent clickEvent) {
super(Bundle.EventDetailChart_chartContextMenu_placeMarker_name());
@ -664,7 +651,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
}
});
}
EventDetailChart.this.controller.selectEventIDs(selectedNodes.stream()
EventDetailsChart.this.controller.selectEventIDs(selectedNodes.stream()
.flatMap(detailNode -> detailNode.getEventIDs().stream())
.collect(Collectors.toList()));
}
@ -708,4 +695,5 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
);
}
}
}

View File

@ -37,7 +37,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
import static org.sleuthkit.autopsy.timeline.ui.detailview.EventBundleNodeBase.configureLoDButton;
/**
* Node used in {@link EventDetailChart} to represent an EventStripe.
* Node used in {@link EventDetailsChart} to represent an EventStripe.
*/
final public class EventStripeNode extends EventBundleNodeBase<EventStripe, EventCluster, EventClusterNode> {
@ -53,12 +53,12 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
// private final HBox clustersHBox = new HBox();
private final ImageView eventTypeImageView = new ImageView();
public EventStripeNode(EventDetailChart chart, EventStripe eventStripe, EventClusterNode parentNode) {
public EventStripeNode(EventDetailsChart chart, EventStripe eventStripe, EventClusterNode parentNode) {
super(chart, eventStripe, parentNode);
setMinHeight(48);
EventDetailChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD());
EventDetailsChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD());
hideButton = ActionUtils.createButton(hideClusterAction, ActionUtils.ActionTextBehavior.HIDE);
configureLoDButton(hideButton);
@ -115,7 +115,7 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
}
@Override
public void setDescriptionVisibility(DescriptionVisibility descrVis) {
void setDescriptionVisibiltiyImpl(DescriptionVisibility descrVis) {
final int size = getEventStripe().getEventIDs().size();
switch (descrVis) {
@ -150,7 +150,7 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
public void handle(MouseEvent t) {
if (t.getButton() == MouseButton.PRIMARY) {
if (t.isShiftDown()) {
if (chart.selectedNodes.contains(EventStripeNode.this) == false) {
chart.selectedNodes.add(EventStripeNode.this);
@ -167,7 +167,7 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
contextMenu = new ContextMenu();
contextMenu.setAutoHide(true);
EventDetailChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD());
EventDetailsChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD());
MenuItem hideDescriptionMenuItem = ActionUtils.createMenuItem(hideClusterAction);
contextMenu.getItems().addAll(hideDescriptionMenuItem);
contextMenu.getItems().addAll(chartContextMenu.getItems());

View File

@ -1 +0,0 @@
NavPanel.eventsTreeLabel.text=Sort By\:

View File

@ -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

View File

@ -46,9 +46,7 @@ import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
@ -59,11 +57,9 @@ import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
* out. Right clicking on a item in the tree shows a context menu to show/hide
* it.
*/
public class NavPanel extends BorderPane implements TimeLineView {
final public class EventsTree extends BorderPane {
private TimeLineController controller;
private FilteredEventsModel filteredEvents;
private final TimeLineController controller;
private DetailViewPane detailViewPane;
@ -76,8 +72,10 @@ public class NavPanel extends BorderPane implements TimeLineView {
@FXML
private ComboBox<Comparator<TreeItem<EventBundle<?>>>> sortByBox;
public NavPanel() {
FXMLConstructor.construct(this, "NavPanel.fxml"); // NON-NLS
public EventsTree(TimeLineController controller) {
this.controller = controller;
FXMLConstructor.construct(this, "EventsTree.fxml"); // NON-NLS
}
public void setDetailViewPane(DetailViewPane detailViewPane) {
@ -112,19 +110,8 @@ public class NavPanel extends BorderPane implements TimeLineView {
}
@Override
public void setController(TimeLineController controller) {
this.controller = controller;
setModel(controller.getEventsModel());
}
@Override
public void setModel(FilteredEventsModel filteredEvents) {
this.filteredEvents = filteredEvents;
}
@FXML
@NbBundle.Messages("EventsTree.Label.text=Sort By:")
void initialize() {
assert sortByBox != null : "fx:id=\"sortByBox\" was not injected: check your FXML file 'NavPanel.fxml'."; // NON-NLS
@ -137,7 +124,7 @@ public class NavPanel extends BorderPane implements TimeLineView {
eventsTree.setCellFactory((TreeView<EventBundle<?>> p) -> new EventBundleTreeCell());
eventsTree.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
eventsTreeLabel.setText(NbBundle.getMessage(this.getClass(), "NavPanel.eventsTreeLabel.text"));
eventsTreeLabel.setText(Bundle.EventsTree_Label_text());
}
/**
@ -232,6 +219,5 @@ public class NavPanel extends BorderPane implements TimeLineView {
setContextMenu(null);
}
}
}
}

View File

@ -44,7 +44,6 @@ import org.controlsfx.control.action.ActionUtils;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.VisualizationMode;
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
@ -64,7 +63,7 @@ import static org.sleuthkit.autopsy.timeline.ui.filtering.Bundle.Timeline_ui_fil
* This also implements {@link TimeLineView} since it dynamically updates its
* filters based on the contents of a {@link FilteredEventsModel}
*/
final public class FilterSetPanel extends BorderPane implements TimeLineView {
final public class FilterSetPanel extends BorderPane {
private static final Image TICK = new Image("org/sleuthkit/autopsy/timeline/images/tick.png");
@ -171,20 +170,25 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView {
legendColumn.setCellValueFactory(param -> param.getValue().valueProperty());
legendColumn.setCellFactory(col -> new LegendCell(this.controller));
}
public FilterSetPanel() {
FXMLConstructor.construct(this, "FilterSetPanel.fxml"); // NON-NLS
expansionMap.put(new TypeFilter(RootEventType.getInstance()).getDisplayName(), true);
}
@Override
public void setController(TimeLineController timeLineController) {
this.controller = timeLineController;
Action defaultFiltersAction = new ResetFilters(controller);
defaultButton.setOnAction(defaultFiltersAction);
defaultButton.disableProperty().bind(defaultFiltersAction.disabledProperty());
this.setModel(timeLineController.getEventsModel());
this.filteredEvents.eventTypeZoomProperty().addListener((Observable observable) -> {
applyFilters();
});
this.filteredEvents.descriptionLODProperty().addListener((Observable observable1) -> {
applyFilters();
});
this.filteredEvents.timeRangeProperty().addListener((Observable observable2) -> {
applyFilters();
});
this.filteredEvents.filterProperty().addListener((Observable o) -> {
refresh();
});
refresh();
hiddenDescriptionsListView.setItems(controller.getQuickHideFilters());
hiddenDescriptionsListView.setCellFactory((ListView<DescriptionFilter> param) -> {
@ -237,25 +241,13 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView {
}
});
}
@Override
public void setModel(FilteredEventsModel filteredEvents) {
this.filteredEvents = filteredEvents;
this.filteredEvents.eventTypeZoomProperty().addListener((Observable observable) -> {
applyFilters();
});
this.filteredEvents.descriptionLODProperty().addListener((Observable observable) -> {
applyFilters();
});
this.filteredEvents.timeRangeProperty().addListener((Observable observable) -> {
applyFilters();
});
this.filteredEvents.filterProperty().addListener((Observable o) -> {
refresh();
});
refresh();
public FilterSetPanel(TimeLineController controller) {
this.controller = controller;
this.filteredEvents = controller.getEventsModel();
FXMLConstructor.construct(this, "FilterSetPanel.fxml"); // NON-NLS
}

View File

@ -29,7 +29,6 @@ import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
@ -40,18 +39,19 @@ import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
* A TreeTableCell that shows an icon and color corresponding to the represented
* filter
*/
class LegendCell extends TreeTableCell<AbstractFilter, AbstractFilter> implements TimeLineView {
final class LegendCell extends TreeTableCell<AbstractFilter, AbstractFilter> {
private static final Color CLEAR = Color.rgb(0, 0, 0, 0);
private TimeLineController controller;
private final TimeLineController controller;
private FilteredEventsModel filteredEvents;
private final FilteredEventsModel filteredEvents;
//We need a controller so we can listen to changes in EventTypeZoom to show/hide legends
public LegendCell(TimeLineController controller) {
LegendCell(TimeLineController controller) {
setEditable(false);
setController(controller);
this.controller = controller;
this.filteredEvents = this.controller.getEventsModel();
}
@Override
@ -119,15 +119,4 @@ class LegendCell extends TreeTableCell<AbstractFilter, AbstractFilter> implement
});
}
}
@Override
synchronized public final void setController(TimeLineController controller) {
this.controller = controller;
setModel(this.controller.getEventsModel());
}
@Override
public void setModel(FilteredEventsModel filteredEvents) {
this.filteredEvents = filteredEvents;
}
}

View File

@ -18,19 +18,20 @@
*/
package org.sleuthkit.autopsy.timeline.zooming;
import java.net.URL;
import java.time.temporal.ChronoUnit;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.control.TitledPane;
import javafx.scene.control.Tooltip;
import javafx.util.StringConverter;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.VisualizationMode;
import org.sleuthkit.autopsy.timeline.actions.Back;
import org.sleuthkit.autopsy.timeline.actions.Forward;
@ -44,13 +45,7 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
* has sliders to provide context/control over three axes of zooming (timescale,
* event hierarchy, and description detail).
*/
public class ZoomSettingsPane extends TitledPane implements TimeLineView {
@FXML
private ResourceBundle resources;
@FXML
private URL location;
public class ZoomSettingsPane extends TitledPane {
@FXML
private Button backButton;
@ -105,35 +100,6 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
timeUnitLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.timeUnitLabel.text"));
zoomLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.zoomLabel.text"));
historyLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.historyLabel.text"));
}
public ZoomSettingsPane() {
FXMLConstructor.construct(this, "ZoomSettingsPane.fxml"); // NON-NLS
}
@Override
synchronized public void setController(TimeLineController controller) {
this.controller = controller;
setModel(controller.getEventsModel());
descrLODSlider.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS));
Back back = new Back(controller);
backButton.disableProperty().bind(back.disabledProperty());
backButton.setOnAction(back);
backButton.setTooltip(new Tooltip(
NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.backButton.toolTip.text",
back.getAccelerator().getName())));
Forward forward = new Forward(controller);
forwardButton.disableProperty().bind(forward.disabledProperty());
forwardButton.setOnAction(forward);
forwardButton.setTooltip(new Tooltip(
NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.forwardButton.toolTip.text",
forward.getAccelerator().getName())));
}
@Override
public void setModel(FilteredEventsModel filteredEvents) {
this.filteredEvents = filteredEvents;
initializeSlider(timeUnitSlider,
() -> {
@ -151,18 +117,14 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
timeUnitSlider.setValue(TimeUnits.fromChronoUnit(chronoUnit).ordinal() - 1);
});
initializeSlider(descrLODSlider,
() -> {
DescriptionLoD newLOD = DescriptionLoD.values()[Math.round(descrLODSlider.valueProperty().floatValue())];
if (controller.pushDescrLOD(newLOD) == false) {
descrLODSlider.setValue(new DescrLODConverter().fromString(filteredEvents.getDescriptionLOD().toString()));
}
}, this.filteredEvents.descriptionLODProperty(),
() -> {
descrLODSlider.setValue(this.filteredEvents.descriptionLODProperty().get().ordinal());
});
initializeSlider(descrLODSlider, () -> {
DescriptionLoD newLOD = DescriptionLoD.values()[Math.round(descrLODSlider.valueProperty().floatValue())];
if (controller.pushDescrLOD(newLOD) == false) {
descrLODSlider.setValue(new DescrLODConverter().fromString(controller.getEventsModel().getDescriptionLOD().toString()));
}
}, this.filteredEvents.descriptionLODProperty(), () -> {
descrLODSlider.setValue(this.filteredEvents.descriptionLODProperty().get().ordinal());
});
initializeSlider(typeZoomSlider,
() -> {
EventTypeZoomLevel newZoomLevel = EventTypeZoomLevel.values()[Math.round(typeZoomSlider.valueProperty().floatValue())];
@ -172,6 +134,26 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
() -> {
typeZoomSlider.setValue(this.filteredEvents.eventTypeZoomProperty().get().ordinal());
});
descrLODSlider.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS));
Back back = new Back(controller);
backButton.disableProperty().bind(back.disabledProperty());
backButton.setOnAction(back);
backButton.setTooltip(new Tooltip(
NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.backButton.toolTip.text",
back.getAccelerator().getName())));
Forward forward = new Forward(controller);
forwardButton.disableProperty().bind(forward.disabledProperty());
forwardButton.setOnAction(forward);
forwardButton.setTooltip(new Tooltip(
NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.forwardButton.toolTip.text",
forward.getAccelerator().getName())));
}
public ZoomSettingsPane(TimeLineController controller) {
this.controller = controller;
this.filteredEvents = controller.getEventsModel();
FXMLConstructor.construct(this, "ZoomSettingsPane.fxml"); // NON-NLS
}
/**

View 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)
}

View File

@ -49,10 +49,12 @@ import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javax.annotation.Nullable;
import javax.swing.SwingUtilities;
import org.apache.commons.lang3.StringUtils;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Cancellable;
import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
@ -395,6 +397,9 @@ public final class ImageGalleryController {
tagsManager.clearFollowUpTagName();
tagsManager.unregisterListener(groupManager);
tagsManager.unregisterListener(categoryManager);
dbWorkerThread.cancelAllTasks();
dbWorkerThread = null;
restartWorker();
Toolbar.getDefault(this).reset();
groupManager.clear();
@ -418,7 +423,12 @@ public final class ImageGalleryController {
dbWorkerThread.addTask(innerTask);
}
@Nullable
synchronized public DrawableFile<?> getFileFromId(Long fileID) throws TskCoreException {
if (Objects.isNull(db)) {
LOGGER.log(Level.WARNING, "Could not get file from id, no DB set. The case is probably closed.");
return null;
}
return db.getFileFromID(fileID);
}
@ -587,7 +597,7 @@ public final class ImageGalleryController {
try {
InnerTask it = workQueue.take();
if (it.cancelled == false) {
if (it.isCancelled() == false) {
it.run();
}
@ -609,7 +619,7 @@ public final class ImageGalleryController {
/**
* Abstract base class for task to be done on {@link DBWorkerThread}
*/
static public abstract class InnerTask implements Runnable {
static public abstract class InnerTask implements Runnable, Cancellable {
public double getProgress() {
return progress.get();
@ -653,13 +663,13 @@ public final class ImageGalleryController {
protected InnerTask() {
}
protected volatile boolean cancelled = false;
public void cancel() {
@Override
synchronized public boolean cancel() {
updateState(Worker.State.CANCELLED);
return true;
}
protected boolean isCancelled() {
synchronized protected boolean isCancelled() {
return getState() == Worker.State.CANCELLED;
}
}
@ -693,7 +703,7 @@ public final class ImageGalleryController {
*/
static private class UpdateFileTask extends FileTask {
public UpdateFileTask(AbstractFile f, DrawableDB taskDB) {
UpdateFileTask(AbstractFile f, DrawableDB taskDB) {
super(f, taskDB);
}
@ -720,7 +730,7 @@ public final class ImageGalleryController {
*/
static private class RemoveFileTask extends FileTask {
public RemoveFileTask(AbstractFile f, DrawableDB taskDB) {
RemoveFileTask(AbstractFile f, DrawableDB taskDB) {
super(f, taskDB);
}
@ -756,7 +766,7 @@ public final class ImageGalleryController {
private final DrawableDB taskDB;
private final SleuthkitCase tskCase;
public CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
this.controller = controller;
this.taskDB = taskDB;
this.tskCase = tskCase;
@ -766,8 +776,8 @@ public final class ImageGalleryController {
+ StringUtils.join(FileTypeUtils.getAllSupportedExtensions(),
"' or name LIKE '%.")
+ "')";
static private final String MIMETYPE_CLAUSE
= "blackboard_attributes.value_text LIKE '"
static private final String MIMETYPE_CLAUSE =
"blackboard_attributes.value_text LIKE '"
+ StringUtils.join(FileTypeUtils.getAllSupportedMimeTypes(),
"' OR blackboard_attributes.value_text LIKE '") + "' ";
@ -801,7 +811,7 @@ public final class ImageGalleryController {
DrawableDB.DrawableTransaction tr = taskDB.beginTransaction();
int units = 0;
for (final AbstractFile f : files) {
if (cancelled) {
if (isCancelled()) {
LOGGER.log(Level.WARNING, "task cancelled: not all contents may be transfered to database");
progressHandle.finish();
break;
@ -848,12 +858,12 @@ public final class ImageGalleryController {
} catch (TskCoreException ex) {
Logger.getLogger(CopyAnalyzedFiles.class.getName()).log(Level.WARNING, "failed to transfer all database contents", ex);
}
}
progressHandle.finish();
updateMessage("");
updateProgress(-1.0);
controller.setStale(false);
}
}
@ -877,13 +887,13 @@ public final class ImageGalleryController {
// (name like '.jpg' or name like '.png' ...)
private final String DRAWABLE_QUERY = "(name LIKE '%." + StringUtils.join(FileTypeUtils.getAllSupportedExtensions(), "' OR name LIKE '%.") + "') ";
private ProgressHandle progressHandle = ProgressHandleFactory.createHandle("prepopulating image/video database");
private ProgressHandle progressHandle = ProgressHandleFactory.createHandle("prepopulating image/video database", this);
/**
*
* @param dataSourceId Data source object ID
*/
public PrePopulateDataSourceFiles(Content dataSource) {
PrePopulateDataSourceFiles(Content dataSource) {
super();
this.dataSource = dataSource;
}
@ -933,7 +943,7 @@ public final class ImageGalleryController {
DrawableDB.DrawableTransaction tr = db.beginTransaction();
int units = 0;
for (final AbstractFile f : files) {
if (cancelled) {
if (isCancelled()) {
LOGGER.log(Level.WARNING, "task cancelled: not all contents may be transfered to database");
progressHandle.finish();
break;
@ -951,7 +961,7 @@ public final class ImageGalleryController {
} catch (TskCoreException ex) {
Logger.getLogger(PrePopulateDataSourceFiles.class.getName()).log(Level.WARNING, "failed to transfer all database contents", ex);
}
}
progressHandle.finish();
}

View File

@ -31,7 +31,7 @@ import org.openide.util.Lookup;
*/
@OptionsPanelController.TopLevelRegistration(
categoryName = "#OptionsCategory_Name_Options",
iconBase = "org/sleuthkit/autopsy/imagegallery/images/polaroid_48_silhouette.png",
iconBase = "org/sleuthkit/autopsy/imagegallery/images/polaroid_32_silhouette.png",
keywords = "#OptionsCategory_Keywords_Options",
keywordsCategory = "Options",
position = 10

View File

@ -89,7 +89,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
return fileOpt;
} else {
try {
fileOpt = Optional.of(getController().getFileFromId(fileIDOpt.get()));
fileOpt = Optional.ofNullable(getController().getFileFromId(fileIDOpt.get()));
} catch (TskCoreException ex) {
Logger.getAnonymousLogger().log(Level.WARNING, "failed to get DrawableFile for obj_id" + fileIDOpt.get(), ex);
fileOpt = Optional.empty();
@ -206,7 +206,6 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
super.failed();
LOGGER.log(Level.SEVERE, "Failed to cache content for" + file.getName(), getException());
}
abstract void saveToCache(X result);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -30,9 +30,11 @@
<property name="win64.TskLib.path" value="${env.TSK_HOME}/win32/x64/Release_PostgreSQL"/>
<property name="win32.TskLib.path" value="${env.TSK_HOME}/win32/Release_PostgreSQL" />
<property name="win64.TskLib.postgres_path" value="${env.TSK_HOME}/win32/x64/Release_PostgreSQL"/>
<property name="win32.TskLib.postgres_path" value="${env.TSK_HOME}/win32/Release_PostgreSQL"/>
<available property="win64.TskLib.exists" type="dir" file="${win64.TskLib.path}" />
<available property="win32.TskLib.exists" type="dir" file="${win32.TskLib.path}" />
<available property="win64.TskLib_postgres.exists" type="dir" file="{win64.TskLib.postgres_path}" />
<available property="win32.TskLib_postgres.exists" type="dir" file="{win32.TskLib.postgres_path}" />
</target>
<!-- The following copy the libtsk_jni dependencies to the Autopsy
@ -68,23 +70,35 @@
<target name="copyWinTskLibs32ToBaseDir" if="win32.TskLib.exists">
<fileset dir="${win32.TskLib.path}" id="win32dlls">
<include name="zlib.dll" />
<include name="libewf.dll"/>
<include name="libewf.dll"/>
</fileset>
<fileset dir="${win32.TskLib.postgres_path}" id="postgres32dlls">
<include name="libeay32.dll"/>
<include name="ssleay32.dll"/>
<include name="intl.dll"/>
<include name="libpq.dll"/>
<include name="msvcr120.dll"/>
</fileset>
<copy todir="${i386}" overwrite="true">
<fileset refid="win32dlls" />
<fileset refid="postgres32dlls" />
</copy>
<copy todir="${x86}" overwrite="true">
<fileset refid="win32dlls" />
<fileset refid="postgres32dlls" />
</copy>
<copy todir="${i586}" overwrite="true">
<fileset refid="win32dlls" />
<fileset refid="postgres32dlls" />
</copy>
<copy todir="${i686}" overwrite="true">
<fileset refid="win32dlls" />
<fileset refid="postgres32dlls" />
</copy>
</target>

View File

@ -26,17 +26,25 @@ There are also a set of tutorials that Basis Technology published on their blog:
-You don't really need anything to develop a python Autopsy module except for the standard Autopsy and your favorite text editor. We recommend IntelliJ IDEA or the Jython plug-in to NetBeans.
To install NetBeans' plug-in:
-# Download and install the Jython 2.7 installer (http://www.jython.org/downloads.html).
-# Download and install the Jython 2.7 installer to desired location (http://www.jython.org/downloads.html).
-# Download NetBeans Python plug-in zip file (http://plugins.netbeans.org/plugin/56795/python4netbeans802).
-# Unpack the content (.nbm files) of the zip file to the desired location.
-# In NetBeans go to Tools->Plugins. In Download tab, click on Add Plugins, then choose extracted .nbm files.
-# Setup Jython path from Tools->Python Platform, click on new, then choose Jython.exe (usually in C:/Program files/Jython2.7/bin/)
-# In NetBeans go to Tools->Plugins. In Downloaded tab, click on Add Plugins, then choose extracted .nbm files.
-# Setup Jython path from Tools->Python Platforms, click on new, then choose Jython.exe (usually in C:/Program files/Jython2.7/bin/)
To install IntelliJ IDEA + Python plug-in:
-# Download and install IDEA https://www.jetbrains.com/idea/download/
-# In File->Settings->Plugins-> install Python Community Edition
-# In File->Project Structure->Project-> Project SDK-> choose IntelliJ IDEA Community Edition
-# In Libraries->add new libraries->choose desired autopsy modules (usually in C:\Program Files\Autopsy-3.1.3\autopsy\modules)
-# Download java JDK depending on platform. Install to desired location (http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html).
-# Download and install IDEA Community Edition to desired location (https://www.jetbrains.com/idea/download/).
-# Open IDEA and choose desired UI theme. Continue with default settings.
-# Choose to either create a new empty project or open an existing one.
-# It will ask you to modify Project Structure. Leave that for now and click OK.
-# In File->Settings. Go to Plugins tab and click on Install JetBrains Plugin.
-# Look for and install Python Community Edition. After the installation, it will ask you restart. Restart IDEA.
-# In File->Project Structure. In Project tab, Project SDK, click on New and choose IntelliJ Platform Plugin SKD.
-# It will ask you to configure the JKD first, click OK and navigate to the JDK folder location and click OK.
-# After that it will ask you to choose the IntelliJ Platform Plugin SKD. It will most likely take you to it's locaation automatically. (Usually in C:\Program Files (x86)\JetBrains\IntelliJ IDEA Community Edition 14.1.5)
-# In the drop down menu next to New button, choose IntelliJ IDEA Community Edition.
-# Still in Project STructure, In Libraries tab, click on '+' to add new libraries. Choose desired autopsy modules (usually in C:\Program Files\Autopsy-3.1.3\autopsy\modules if you have executable version).
\section mod_dev_py_create Creating a Basic Python Module