mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-15 01:07:42 +00:00
merge
This commit is contained in:
commit
add51c6ecd
@ -42,10 +42,11 @@ to the root 64-bit JRE directory.
|
|||||||
use a released version or download the latest from github:
|
use a released version or download the latest from github:
|
||||||
- git://github.com/sleuthkit/sleuthkit.git
|
- git://github.com/sleuthkit/sleuthkit.git
|
||||||
|
|
||||||
2b) Build the TSK JAR file by typing 'ant PostgreSQL' in bindings/java in the
|
2b) Build the TSK JAR file by typing 'ant dist-PostgreSQL' in
|
||||||
TSK source code folder from a command line. Note it is case sensitive. You
|
bindings/java in the
|
||||||
can also add the code to a NetBeans project and build it from there,
|
TSK source code folder from a command line. Note it is case
|
||||||
selecting the PostgreSQL target.
|
sensitive. You can also add the code to a NetBeans project and build
|
||||||
|
it from there, selecting the dist-PostgreSQL target.
|
||||||
|
|
||||||
2c) Set TSK_HOME environment variable to the root directory of TSK
|
2c) Set TSK_HOME environment variable to the root directory of TSK
|
||||||
|
|
||||||
|
@ -245,7 +245,6 @@ NewCaseVisualPanel1.singleUserCaseRadioButton.text=Single-user
|
|||||||
NewCaseVisualPanel1.multiUserSettingsWarningLabel.text=Multi-user settings warning label
|
NewCaseVisualPanel1.multiUserSettingsWarningLabel.text=Multi-user settings warning label
|
||||||
Case.deleteReports.deleteFromDiskException.log.msg=Unable to delete the report from the disk.
|
Case.deleteReports.deleteFromDiskException.log.msg=Unable to delete the report from the disk.
|
||||||
Case.deleteReports.deleteFromDiskException.msg=Unable to delete the report {0} from the disk.\nYou may manually delete it from {1}
|
Case.deleteReports.deleteFromDiskException.msg=Unable to delete the report {0} from the disk.\nYou may manually delete it from {1}
|
||||||
CaseOpenException.DatabaseSettingsIssue=Database settings:
|
|
||||||
CaseExceptionWarning.CheckMultiUserOptions=Check Multi-user options.
|
CaseExceptionWarning.CheckMultiUserOptions=Check Multi-user options.
|
||||||
SingleUserCaseConverter.BadDatabaseFileName=Database file does not exist!
|
SingleUserCaseConverter.BadDatabaseFileName=Database file does not exist!
|
||||||
SingleUserCaseConverter.AlreadyMultiUser=Case is already multi-user!
|
SingleUserCaseConverter.AlreadyMultiUser=Case is already multi-user!
|
||||||
|
@ -349,7 +349,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
|
|||||||
}
|
}
|
||||||
// start listening for TSK errors for the new case
|
// start listening for TSK errors for the new case
|
||||||
currentCase.tskErrorReporter = new IntervalErrorReportData(currentCase, MIN_SECONDS_BETWEEN_ERROR_REPORTS,
|
currentCase.tskErrorReporter = new IntervalErrorReportData(currentCase, MIN_SECONDS_BETWEEN_ERROR_REPORTS,
|
||||||
NbBundle.getMessage(Case.class, "IntervalErrorReport.ErrorText"));
|
NbBundle.getMessage(Case.class, "IntervalErrorReport.ErrorText"));
|
||||||
doCaseChange(currentCase);
|
doCaseChange(currentCase);
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
RecentCases.getInstance().addRecentCase(currentCase.name, currentCase.configFilePath); // update the recent cases
|
RecentCases.getInstance().addRecentCase(currentCase.name, currentCase.configFilePath); // update the recent cases
|
||||||
@ -464,7 +464,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
|
|||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||||
});
|
});
|
||||||
throw new CaseActionException(NbBundle.getMessage(Case.class, "CaseOpenException.DatabaseSettingsIssue") + " " + ex.getMessage()); //NON-NLS
|
throw new CaseActionException(ex.getMessage(), ex); //NON-NLS
|
||||||
} catch (UserPreferencesException ex) {
|
} catch (UserPreferencesException ex) {
|
||||||
logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS
|
logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS
|
||||||
throw new CaseActionException(
|
throw new CaseActionException(
|
||||||
@ -581,7 +581,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
|
|||||||
throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.multiUserCaseNotEnabled"));
|
throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.multiUserCaseNotEnabled"));
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
db = SleuthkitCase.openCase(metadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseDir);
|
db = SleuthkitCase.openCase(metadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseDir);
|
||||||
} catch (UserPreferencesException ex) {
|
} catch (UserPreferencesException ex) {
|
||||||
logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS
|
logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS
|
||||||
throw new CaseActionException(
|
throw new CaseActionException(
|
||||||
@ -635,7 +635,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
|
|||||||
} catch (IllegalStateException unused) {
|
} catch (IllegalStateException unused) {
|
||||||
// Already logged.
|
// Already logged.
|
||||||
}
|
}
|
||||||
throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.gen.msg") + ". " + ex.getMessage(), ex); //NON-NLS
|
throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.gen.msg") + ": " + ex.getMessage(), ex); //NON-NLS
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
try {
|
try {
|
||||||
Case badCase = Case.getCurrentCase();
|
Case badCase = Case.getCurrentCase();
|
||||||
@ -646,7 +646,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
|
|||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||||
});
|
});
|
||||||
throw new CaseActionException(NbBundle.getMessage(Case.class, "CaseOpenException.DatabaseSettingsIssue") + " " + ex.getMessage(), ex); //NON-NLS
|
throw new CaseActionException(ex.getMessage(), ex); //NON-NLS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +111,6 @@ final class NewCaseWizardAction extends CallableSystemAction {
|
|||||||
final String caseName = (String) wizardDescriptor.getProperty("caseName"); //NON-NLS
|
final String caseName = (String) wizardDescriptor.getProperty("caseName"); //NON-NLS
|
||||||
String createdDirectory = (String) wizardDescriptor.getProperty("createdDirectory"); //NON-NLS
|
String createdDirectory = (String) wizardDescriptor.getProperty("createdDirectory"); //NON-NLS
|
||||||
CaseType caseType = CaseType.values()[(int) wizardDescriptor.getProperty("caseType")]; //NON-NLS
|
CaseType caseType = CaseType.values()[(int) wizardDescriptor.getProperty("caseType")]; //NON-NLS
|
||||||
|
|
||||||
Case.create(createdDirectory, caseName, caseNumber, examiner, caseType);
|
Case.create(createdDirectory, caseName, caseNumber, examiner, caseType);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -121,26 +120,10 @@ final class NewCaseWizardAction extends CallableSystemAction {
|
|||||||
final String caseName = (String) wizardDescriptor.getProperty("caseName"); //NON-NLS
|
final String caseName = (String) wizardDescriptor.getProperty("caseName"); //NON-NLS
|
||||||
try {
|
try {
|
||||||
get();
|
get();
|
||||||
CaseType currentCaseType = CaseType.values()[(int) wizardDescriptor.getProperty("caseType")]; //NON-NLS
|
AddImageAction addImageAction = SystemAction.get(AddImageAction.class);
|
||||||
CaseDbConnectionInfo info = UserPreferences.getDatabaseConnectionInfo();
|
addImageAction.actionPerformed(null);
|
||||||
|
} catch (Exception ex) {
|
||||||
if (currentCaseType == CaseType.SINGLE_USER_CASE) {
|
logger.log(Level.SEVERE, "Error creating case", ex); //NON-NLS
|
||||||
AddImageAction addImageAction = SystemAction.get(AddImageAction.class);
|
|
||||||
addImageAction.actionPerformed(null);
|
|
||||||
} else {
|
|
||||||
if (info.getDbType() != DbType.SQLITE) {
|
|
||||||
SleuthkitCase.tryConnect(info);
|
|
||||||
AddImageAction addImageAction = SystemAction.get(AddImageAction.class);
|
|
||||||
addImageAction.actionPerformed(null);
|
|
||||||
} else {
|
|
||||||
JOptionPane.showMessageDialog(null,
|
|
||||||
NbBundle.getMessage(this.getClass(), "NewCaseWizardAction.databaseProblem1.text"),
|
|
||||||
NbBundle.getMessage(this.getClass(), "NewCaseWizardAction.databaseProblem2.text"),
|
|
||||||
JOptionPane.ERROR_MESSAGE);
|
|
||||||
doFailedCaseCleanup(wizardDescriptor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (InterruptedException | ExecutionException | MissingResourceException | TskCoreException | HeadlessException | UserPreferencesException ex) {
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), ex.getCause().getMessage() + " "
|
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), ex.getCause().getMessage() + " "
|
||||||
+ NbBundle.getMessage(this.getClass(), "CaseExceptionWarning.CheckMultiUserOptions"),
|
+ NbBundle.getMessage(this.getClass(), "CaseExceptionWarning.CheckMultiUserOptions"),
|
||||||
|
@ -66,7 +66,7 @@ public class Installer extends ModuleInstall {
|
|||||||
//We should update this if we officially switch to a new version of CRT/compiler
|
//We should update this if we officially switch to a new version of CRT/compiler
|
||||||
System.loadLibrary("msvcr100"); //NON-NLS
|
System.loadLibrary("msvcr100"); //NON-NLS
|
||||||
System.loadLibrary("msvcp100"); //NON-NLS
|
System.loadLibrary("msvcp100"); //NON-NLS
|
||||||
System.loadLibrary("msvcr120"); //NON-NLS
|
|
||||||
logger.log(Level.INFO, "MSVCR100 and MSVCP100 libraries loaded"); //NON-NLS
|
logger.log(Level.INFO, "MSVCR100 and MSVCP100 libraries loaded"); //NON-NLS
|
||||||
} catch (UnsatisfiedLinkError e) {
|
} catch (UnsatisfiedLinkError e) {
|
||||||
logger.log(Level.SEVERE, "Error loading MSVCR100 and MSVCP100 libraries, ", e); //NON-NLS
|
logger.log(Level.SEVERE, "Error loading MSVCR100 and MSVCP100 libraries, ", e); //NON-NLS
|
||||||
@ -86,6 +86,14 @@ public class Installer extends ModuleInstall {
|
|||||||
logger.log(Level.SEVERE, "Error loading EWF library, ", e); //NON-NLS
|
logger.log(Level.SEVERE, "Error loading EWF library, ", e); //NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* PostgreSQL */
|
||||||
|
try {
|
||||||
|
System.loadLibrary("msvcr120"); //NON-NLS
|
||||||
|
logger.log(Level.INFO, "MSVCR 120 library loaded"); //NON-NLS
|
||||||
|
} catch (UnsatisfiedLinkError e) {
|
||||||
|
logger.log(Level.SEVERE, "Error loading MSVCR120 library, ", e); //NON-NLS
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
System.loadLibrary("libeay32"); //NON-NLS
|
System.loadLibrary("libeay32"); //NON-NLS
|
||||||
logger.log(Level.INFO, "LIBEAY32 library loaded"); //NON-NLS
|
logger.log(Level.INFO, "LIBEAY32 library loaded"); //NON-NLS
|
||||||
|
@ -197,6 +197,7 @@
|
|||||||
<file name="org-netbeans-modules-autoupdate-ui-actions-PluginManagerAction.shadow"/>
|
<file name="org-netbeans-modules-autoupdate-ui-actions-PluginManagerAction.shadow"/>
|
||||||
<file name="org-netbeans-modules-favorites-templates-TemplatesAction.shadow_hidden"/>
|
<file name="org-netbeans-modules-favorites-templates-TemplatesAction.shadow_hidden"/>
|
||||||
<file name="org-netbeans-modules-options-OptionsWindowAction.shadow"/>
|
<file name="org-netbeans-modules-options-OptionsWindowAction.shadow"/>
|
||||||
|
<file name="org-netbeans-modules-templates-actions-TemplatesAction.shadow_hidden"/>
|
||||||
<file name="org-openide-actions-ToolsAction.shadow_hidden"/>
|
<file name="org-openide-actions-ToolsAction.shadow_hidden"/>
|
||||||
<file name="org-sleuthkit-autopsy-filesearch-FileSearchAction.shadow">
|
<file name="org-sleuthkit-autopsy-filesearch-FileSearchAction.shadow">
|
||||||
<attr name="originalFile" stringvalue="Actions/Tools/org-sleuthkit-autopsy-filesearch-FileSearchAction.instance"/>
|
<attr name="originalFile" stringvalue="Actions/Tools/org-sleuthkit-autopsy-filesearch-FileSearchAction.instance"/>
|
||||||
|
@ -22,6 +22,7 @@ import java.awt.Dimension;
|
|||||||
import java.awt.EventQueue;
|
import java.awt.EventQueue;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -174,6 +175,9 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi
|
|||||||
borderpane.setCenter(fxImageView);
|
borderpane.setCenter(fxImageView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (EOFException ex) {
|
||||||
|
LOGGER.log(Level.WARNING, "Could not load image file into media view (EOF): {0}", file.getName()); //NON-NLS
|
||||||
|
borderpane.setCenter(errorLabel);
|
||||||
} catch (IllegalArgumentException | IOException ex) {
|
} catch (IllegalArgumentException | IOException ex) {
|
||||||
LOGGER.log(Level.WARNING, "Could not load image file into media view: " + file.getName(), ex); //NON-NLS
|
LOGGER.log(Level.WARNING, "Could not load image file into media view: " + file.getName(), ex); //NON-NLS
|
||||||
borderpane.setCenter(errorLabel);
|
borderpane.setCenter(errorLabel);
|
||||||
|
@ -26,6 +26,7 @@ import com.google.common.io.Files;
|
|||||||
import java.awt.Image;
|
import java.awt.Image;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.EOFException;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -135,8 +136,8 @@ public class ImageUtils {
|
|||||||
/**
|
/**
|
||||||
* thread that saves generated thumbnails to disk in the background
|
* thread that saves generated thumbnails to disk in the background
|
||||||
*/
|
*/
|
||||||
private static final Executor imageSaver
|
private static final Executor imageSaver =
|
||||||
= Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
|
Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
|
||||||
.namingPattern("icon saver-%d").build());
|
.namingPattern("icon saver-%d").build());
|
||||||
|
|
||||||
public static List<String> getSupportedImageExtensions() {
|
public static List<String> getSupportedImageExtensions() {
|
||||||
@ -524,14 +525,13 @@ public class ImageUtils {
|
|||||||
return ScalrWrapper.cropImage(bi, Math.min(iconSize, bi.getWidth()), Math.min(iconSize, bi.getHeight()));
|
return ScalrWrapper.cropImage(bi, Math.min(iconSize, bi.getWidth()), Math.min(iconSize, bi.getHeight()));
|
||||||
}
|
}
|
||||||
} catch (OutOfMemoryError e) {
|
} catch (OutOfMemoryError e) {
|
||||||
LOGGER.log(Level.WARNING, "Could not scale image (too large): " + content.getName(), e); //NON-NLS
|
LOGGER.log(Level.WARNING, "Could not scale image (too large) " + content.getName(), e); //NON-NLS
|
||||||
|
} catch (EOFException e) {
|
||||||
return null;
|
LOGGER.log(Level.WARNING, "Could not load image (EOF) {0}", content.getName()); //NON-NLS
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.log(Level.WARNING, "Could not load image: " + content.getName(), e); //NON-NLS
|
LOGGER.log(Level.WARNING, "Could not load image " + content.getName(), e); //NON-NLS
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.modules.filetypeid;
|
package org.sleuthkit.autopsy.modules.filetypeid;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
@ -117,7 +118,6 @@ class FileType {
|
|||||||
* The way the signature byte sequence should be interpreted.
|
* The way the signature byte sequence should be interpreted.
|
||||||
*/
|
*/
|
||||||
enum Type {
|
enum Type {
|
||||||
|
|
||||||
RAW, ASCII
|
RAW, ASCII
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -131,8 +131,8 @@ class FileType {
|
|||||||
*
|
*
|
||||||
* @param signatureBytes The signature bytes.
|
* @param signatureBytes The signature bytes.
|
||||||
* @param offset The offset of the signature bytes.
|
* @param offset The offset of the signature bytes.
|
||||||
* @param type The interpretation of the signature bytes
|
* @param type The type of data in the byte array. Impacts
|
||||||
* (e.g., raw bytes, an ASCII string).
|
* how it is displayed to the user in the UI.
|
||||||
*/
|
*/
|
||||||
Signature(final byte[] signatureBytes, long offset, Type type) {
|
Signature(final byte[] signatureBytes, long offset, Type type) {
|
||||||
this.signatureBytes = Arrays.copyOf(signatureBytes, signatureBytes.length);
|
this.signatureBytes = Arrays.copyOf(signatureBytes, signatureBytes.length);
|
||||||
@ -140,6 +140,34 @@ class FileType {
|
|||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a file signature consisting of an ASCII string at a
|
||||||
|
* specific offset within a file.
|
||||||
|
*
|
||||||
|
* @param signatureString The ASCII string
|
||||||
|
* @param offset The offset of the signature bytes.
|
||||||
|
*/
|
||||||
|
Signature(String signatureString, long offset) {
|
||||||
|
this.signatureBytes = signatureString.getBytes(StandardCharsets.US_ASCII);
|
||||||
|
this.offset = offset;
|
||||||
|
this.type = Type.ASCII;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a file signature consisting of a sequence of bytes at a
|
||||||
|
* specific offset within a file. If bytes correspond to an ASCII
|
||||||
|
* string, use one of the other constructors so that the string is
|
||||||
|
* displayed to the user instead of the raw bytes.
|
||||||
|
*
|
||||||
|
* @param signatureBytes The signature bytes.
|
||||||
|
* @param offset The offset of the signature bytes.
|
||||||
|
*/
|
||||||
|
Signature(final byte[] signatureBytes, long offset) {
|
||||||
|
this.signatureBytes = Arrays.copyOf(signatureBytes, signatureBytes.length);
|
||||||
|
this.offset = offset;
|
||||||
|
this.type = Type.RAW;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the byte sequence of the signature.
|
* Gets the byte sequence of the signature.
|
||||||
*
|
*
|
||||||
|
@ -180,20 +180,54 @@ final class UserDefinedFileTypesManager {
|
|||||||
* org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException
|
* org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException
|
||||||
*/
|
*/
|
||||||
private void loadPredefinedFileTypes() throws UserDefinedFileTypesException {
|
private void loadPredefinedFileTypes() throws UserDefinedFileTypesException {
|
||||||
|
byte[] byteArray;
|
||||||
|
FileType fileType;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
FileType fileTypeXml = new FileType("text/xml", new Signature("<?xml".getBytes(ASCII_ENCODING), 0L, FileType.Signature.Type.ASCII), "", false); //NON-NLS
|
// Add rule for xml
|
||||||
fileTypes.put(fileTypeXml.getMimeType(), fileTypeXml);
|
fileType = new FileType("text/xml", new Signature("<?xml", 0L), "", false); //NON-NLS
|
||||||
|
fileTypes.put(fileType.getMimeType(), fileType);
|
||||||
|
|
||||||
byte[] gzip = DatatypeConverter.parseHexBinary("1F8B08");
|
// Add rule for gzip
|
||||||
FileType fileTypeGzip = new FileType("application/x-gzip", new Signature(gzip, 0L, FileType.Signature.Type.ASCII), "", false); //NON-NLS
|
byteArray = DatatypeConverter.parseHexBinary("1F8B"); //NON-NLS
|
||||||
fileTypes.put(fileTypeGzip.getMimeType(), fileTypeGzip);
|
fileType = new FileType("application/x-gzip", new Signature(byteArray, 0L), "", false); //NON-NLS
|
||||||
|
fileTypes.put(fileType.getMimeType(), fileType);
|
||||||
|
|
||||||
} catch (UnsupportedEncodingException ex) {
|
// Add rule for .wk1
|
||||||
/**
|
byteArray = DatatypeConverter.parseHexBinary("0000020006040600080000000000"); //NON-NLS
|
||||||
* Using an all-or-none policy.
|
fileType = new FileType("application/x-123", new Signature(byteArray, 0L), "", false); //NON-NLS
|
||||||
*/
|
fileTypes.put(fileType.getMimeType(), fileType);
|
||||||
fileTypes.clear();
|
|
||||||
throwUserDefinedFileTypesException(ex, "UserDefinedFileTypesManager.loadFileTypes.errorMessage");
|
// Add rule for Radiance image
|
||||||
|
byteArray = DatatypeConverter.parseHexBinary("233F52414449414E43450A");//NON-NLS
|
||||||
|
fileType = new FileType("image/vnd.radiance", new Signature(byteArray, 0L), "", false); //NON-NLS
|
||||||
|
fileTypes.put(fileType.getMimeType(), fileType);
|
||||||
|
|
||||||
|
// Add rule for .dcx image
|
||||||
|
byteArray = DatatypeConverter.parseHexBinary("B168DE3A"); //NON-NLS
|
||||||
|
fileType = new FileType("image/x-dcx", new Signature(byteArray, 0L), "", false); //NON-NLS
|
||||||
|
fileTypes.put(fileType.getMimeType(), fileType);
|
||||||
|
|
||||||
|
// Add rule for .ics image
|
||||||
|
fileType = new FileType("image/x-icns", new Signature("icns", 0L), "", false); //NON-NLS
|
||||||
|
fileTypes.put(fileType.getMimeType(), fileType);
|
||||||
|
|
||||||
|
// Add rule for .pict image
|
||||||
|
byteArray = DatatypeConverter.parseHexBinary("001102FF"); //NON-NLS
|
||||||
|
fileType = new FileType("image/x-pict", new Signature(byteArray, 522L), "", false); //NON-NLS
|
||||||
|
fileTypes.put(fileType.getMimeType(), fileType);
|
||||||
|
|
||||||
|
// Add rule for .pam
|
||||||
|
fileType = new FileType("image/x-portable-arbitrarymap", new Signature("P7", 0L), "", false); //NON-NLS
|
||||||
|
fileTypes.put(fileType.getMimeType(), fileType);
|
||||||
|
|
||||||
|
// Add rule for .pfm
|
||||||
|
fileType = new FileType("image/x-portable-floatmap", new Signature("PF", 0L), "", false); //NON-NLS
|
||||||
|
fileTypes.put(fileType.getMimeType(), fileType);
|
||||||
|
}
|
||||||
|
// parseHexBinary() throws this if the argument passed in is not Hex
|
||||||
|
catch (IllegalArgumentException e) {
|
||||||
|
throw new UserDefinedFileTypesException("Error creating predefined file types", e); //
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,6 +249,9 @@ public class ReportProgressPanel extends javax.swing.JPanel {
|
|||||||
EventQueue.invokeLater(new Runnable() {
|
EventQueue.invokeLater(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
// make sure we disable an indeterminate
|
||||||
|
reportProgressBar.setIndeterminate(false);
|
||||||
|
|
||||||
if (STATUS != ReportStatus.CANCELED) {
|
if (STATUS != ReportStatus.CANCELED) {
|
||||||
switch (reportStatus) {
|
switch (reportStatus) {
|
||||||
case COMPLETE: {
|
case COMPLETE: {
|
||||||
@ -261,6 +264,7 @@ public class ReportProgressPanel extends javax.swing.JPanel {
|
|||||||
// set reportProgressBar color as green.
|
// set reportProgressBar color as green.
|
||||||
reportProgressBar.setForeground(new Color(50, 205, 50));
|
reportProgressBar.setForeground(new Color(50, 205, 50));
|
||||||
reportProgressBar.setString("Complete"); //NON-NLS
|
reportProgressBar.setString("Complete"); //NON-NLS
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ERROR: {
|
case ERROR: {
|
||||||
|
@ -24,10 +24,7 @@ TimelinePanel.jButton7.text=3d
|
|||||||
TimelinePanel.jButton2.text=1m
|
TimelinePanel.jButton2.text=1m
|
||||||
TimelinePanel.jButton3.text=3m
|
TimelinePanel.jButton3.text=3m
|
||||||
TimelinePanel.jButton4.text=2w
|
TimelinePanel.jButton4.text=2w
|
||||||
TimeLineTopComponent.eventsTab.name=Events
|
|
||||||
TimeLineTopComponent.filterTab.name=Filters
|
|
||||||
OpenTimelineAction.title=Timeline
|
OpenTimelineAction.title=Timeline
|
||||||
OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources.
|
OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources.
|
||||||
TimeLineTopComponent.timeZonePanel.text=Display Times In\:
|
|
||||||
ProgressWindow.progressHeader.text=\
|
ProgressWindow.progressHeader.text=\
|
||||||
|
|
||||||
|
@ -42,8 +42,7 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
|||||||
* intended only to remove the boilerplate initialization code when defining a
|
* intended only to remove the boilerplate initialization code when defining a
|
||||||
* relatively static layout
|
* relatively static layout
|
||||||
*
|
*
|
||||||
* TODO: find a way to move this to CoreUtils and remove duplicate verison in
|
* TODO: move this to CoreUtils and remove duplicate verison in image analyzer
|
||||||
* image analyzer
|
|
||||||
*/
|
*/
|
||||||
public class FXMLConstructor {
|
public class FXMLConstructor {
|
||||||
|
|
||||||
@ -55,12 +54,28 @@ public class FXMLConstructor {
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @param node a node to initialize from a loaded FXML
|
* @param node a node to initialize from a loaded FXML
|
||||||
* @param fxmlFileName the the file name of the FXML to load, relative to
|
* @param fxmlFileName the file name of the FXML to load, relative to the
|
||||||
* the package that the class of node is defined in.
|
* package that the class of node is defined in.
|
||||||
*/
|
*/
|
||||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||||
static public void construct(Node node, String fxmlFileName) {
|
static public void construct(Node node, String fxmlFileName) {
|
||||||
final String name = "nbres:/" + StringUtils.replace(node.getClass().getPackage().getName(), ".", "/") + "/" + fxmlFileName; // NON-NLS
|
construct(node, node.getClass(), fxmlFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load an fxml file and initialize a node with it. Since this manipulates
|
||||||
|
* the node, it must be called on the JFX thread.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param node a node to initialize from a loaded FXML
|
||||||
|
* @param clazz a class to use for relative location of the fxml
|
||||||
|
* @param fxmlFileName the file name of the FXML to load, relative to the
|
||||||
|
* package of clazz.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||||
|
static public void construct(Node node, Class<? extends Node> clazz, String fxmlFileName) {
|
||||||
|
final String name = "nbres:/" + StringUtils.replace(clazz.getPackage().getName(), ".", "/") + "/" + fxmlFileName; // NON-NLS
|
||||||
|
|
||||||
try {
|
try {
|
||||||
FXMLLoader fxmlLoader = new FXMLLoader(new URL(name));
|
FXMLLoader fxmlLoader = new FXMLLoader(new URL(name));
|
||||||
|
@ -64,13 +64,13 @@ import org.openide.windows.WindowManager;
|
|||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE;
|
import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE;
|
||||||
import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_ADDED;
|
import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_ADDED;
|
||||||
import org.sleuthkit.autopsy.coreutils.History;
|
|
||||||
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
|
||||||
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
|
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
|
||||||
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent;
|
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent;
|
||||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.History;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
@ -135,6 +135,21 @@ public class TimeLineController {
|
|||||||
|
|
||||||
private final ReadOnlyStringWrapper taskTitle = new ReadOnlyStringWrapper();
|
private final ReadOnlyStringWrapper taskTitle = new ReadOnlyStringWrapper();
|
||||||
|
|
||||||
|
private final ReadOnlyStringWrapper status = new ReadOnlyStringWrapper();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* status is a string that will be displayed in the status bar as a kind of
|
||||||
|
* user hint/information when it is not empty
|
||||||
|
*
|
||||||
|
* @return the status property
|
||||||
|
*/
|
||||||
|
public ReadOnlyStringProperty getStatusProperty() {
|
||||||
|
return status.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String string) {
|
||||||
|
status.set(string);
|
||||||
|
}
|
||||||
private final Case autoCase;
|
private final Case autoCase;
|
||||||
|
|
||||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||||
@ -301,8 +316,10 @@ public class TimeLineController {
|
|||||||
LOGGER.log(Level.INFO, "Beginning generation of timeline"); // NON-NLS
|
LOGGER.log(Level.INFO, "Beginning generation of timeline"); // NON-NLS
|
||||||
try {
|
try {
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
if (isWindowOpen()) {
|
synchronized (TimeLineController.this) {
|
||||||
mainFrame.close();
|
if (isWindowOpen()) {
|
||||||
|
mainFrame.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase();
|
final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase();
|
||||||
@ -347,8 +364,10 @@ public class TimeLineController {
|
|||||||
void rebuildTagsTable() {
|
void rebuildTagsTable() {
|
||||||
LOGGER.log(Level.INFO, "starting to rebuild tags table"); // NON-NLS
|
LOGGER.log(Level.INFO, "starting to rebuild tags table"); // NON-NLS
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
if (isWindowOpen()) {
|
synchronized (TimeLineController.this) {
|
||||||
mainFrame.close();
|
if (isWindowOpen()) {
|
||||||
|
mainFrame.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
synchronized (eventsRepository) {
|
synchronized (eventsRepository) {
|
||||||
@ -373,16 +392,19 @@ public class TimeLineController {
|
|||||||
IngestManager.getInstance().removeIngestModuleEventListener(ingestModuleListener);
|
IngestManager.getInstance().removeIngestModuleEventListener(ingestModuleListener);
|
||||||
IngestManager.getInstance().removeIngestJobEventListener(ingestJobListener);
|
IngestManager.getInstance().removeIngestJobEventListener(ingestJobListener);
|
||||||
Case.removePropertyChangeListener(caseListener);
|
Case.removePropertyChangeListener(caseListener);
|
||||||
mainFrame.close();
|
SwingUtilities.invokeLater(() -> {
|
||||||
mainFrame.setVisible(false);
|
synchronized (TimeLineController.this) {
|
||||||
mainFrame = null;
|
mainFrame.close();
|
||||||
|
mainFrame = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* show the timeline window and prompt for rebuilding database if necessary.
|
* show the timeline window and prompt for rebuilding database if necessary.
|
||||||
*/
|
*/
|
||||||
synchronized void openTimeLine() {
|
void openTimeLine() {
|
||||||
// listen for case changes (specifically images being added, and case changes).
|
// listen for case changes (specifically images being added, and case changes).
|
||||||
if (Case.isCaseOpen() && !listeningToAutopsy) {
|
if (Case.isCaseOpen() && !listeningToAutopsy) {
|
||||||
IngestManager.getInstance().addIngestModuleEventListener(ingestModuleListener);
|
IngestManager.getInstance().addIngestModuleEventListener(ingestModuleListener);
|
||||||
@ -524,20 +546,20 @@ public class TimeLineController {
|
|||||||
/**
|
/**
|
||||||
* private method to build gui if necessary and make it visible.
|
* private method to build gui if necessary and make it visible.
|
||||||
*/
|
*/
|
||||||
synchronized private void showWindow() {
|
private void showWindow() {
|
||||||
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);
|
|
||||||
}
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
mainFrame.open();
|
synchronized (TimeLineController.this) {
|
||||||
mainFrame.setVisible(true);
|
if (mainFrame == null) {
|
||||||
mainFrame.toFront();
|
LOGGER.log(Level.WARNING, "Tried to show timeline with invalid window. Rebuilding GUI."); // NON-NLS
|
||||||
|
mainFrame = (TimeLineTopComponent) WindowManager.getDefault().findTopComponent(
|
||||||
|
NbBundle.getMessage(TimeLineController.class, "CTL_TimeLineTopComponentAction"));
|
||||||
|
if (mainFrame == null) {
|
||||||
|
mainFrame = new TimeLineTopComponent(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mainFrame.open();
|
||||||
|
mainFrame.toFront();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.embed.swing.JFXPanel;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.control.SplitPane;
|
import javafx.scene.control.SplitPane;
|
||||||
import javafx.scene.control.Tab;
|
import javafx.scene.control.Tab;
|
||||||
@ -34,7 +34,6 @@ import javafx.scene.input.KeyCodeCombination;
|
|||||||
import javafx.scene.input.KeyEvent;
|
import javafx.scene.input.KeyEvent;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import org.netbeans.api.settings.ConvertAsProperties;
|
|
||||||
import org.openide.explorer.ExplorerManager;
|
import org.openide.explorer.ExplorerManager;
|
||||||
import org.openide.explorer.ExplorerUtils;
|
import org.openide.explorer.ExplorerUtils;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
@ -51,135 +50,103 @@ import org.sleuthkit.autopsy.timeline.ui.StatusBar;
|
|||||||
import org.sleuthkit.autopsy.timeline.ui.TimeLineResultView;
|
import org.sleuthkit.autopsy.timeline.ui.TimeLineResultView;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.TimeZonePanel;
|
import org.sleuthkit.autopsy.timeline.ui.TimeZonePanel;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.VisualizationPanel;
|
import org.sleuthkit.autopsy.timeline.ui.VisualizationPanel;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.NavPanel;
|
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.filtering.FilterSetPanel;
|
import org.sleuthkit.autopsy.timeline.ui.filtering.FilterSetPanel;
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.ZoomSettingsPane;
|
import org.sleuthkit.autopsy.timeline.zooming.ZoomSettingsPane;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TopComponent for the timeline feature.
|
* TopComponent for the timeline feature.
|
||||||
*/
|
*/
|
||||||
@ConvertAsProperties(
|
|
||||||
dtd = "-//org.sleuthkit.autopsy.timeline//TimeLine//EN",
|
|
||||||
autostore = false)
|
|
||||||
@TopComponent.Description(
|
@TopComponent.Description(
|
||||||
preferredID = "TimeLineTopComponent",
|
preferredID = "TimeLineTopComponent",
|
||||||
//iconBase="SET/PATH/TO/ICON/HERE",
|
//iconBase="SET/PATH/TO/ICON/HERE",
|
||||||
persistenceType = TopComponent.PERSISTENCE_NEVER)
|
persistenceType = TopComponent.PERSISTENCE_NEVER)
|
||||||
@TopComponent.Registration(mode = "timeline", openAtStartup = false)
|
@TopComponent.Registration(mode = "timeline", openAtStartup = false)
|
||||||
public final class TimeLineTopComponent extends TopComponent implements ExplorerManager.Provider, TimeLineUI {
|
public final class TimeLineTopComponent extends TopComponent implements ExplorerManager.Provider {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(TimeLineTopComponent.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(TimeLineTopComponent.class.getName());
|
||||||
|
|
||||||
private DataContentPanel dataContentPanel;
|
private final DataContentPanel dataContentPanel;
|
||||||
|
|
||||||
private TimeLineResultView tlrv;
|
private final TimeLineResultView tlrv;
|
||||||
|
|
||||||
private final ExplorerManager em = new ExplorerManager();
|
private final ExplorerManager em = new ExplorerManager();
|
||||||
|
|
||||||
private TimeLineController controller;
|
private final TimeLineController controller;
|
||||||
|
|
||||||
////jfx componenets that make up the interface
|
public TimeLineTopComponent(TimeLineController controller) {
|
||||||
private final FilterSetPanel filtersPanel = new FilterSetPanel();
|
|
||||||
|
|
||||||
private final Tab eventsTab = new Tab(
|
|
||||||
NbBundle.getMessage(TimeLineTopComponent.class, "TimeLineTopComponent.eventsTab.name"));
|
|
||||||
|
|
||||||
private final Tab filterTab = new Tab(
|
|
||||||
NbBundle.getMessage(TimeLineTopComponent.class, "TimeLineTopComponent.filterTab.name"));
|
|
||||||
|
|
||||||
private final VBox leftVBox = new VBox(5);
|
|
||||||
|
|
||||||
private final NavPanel navPanel = new NavPanel();
|
|
||||||
|
|
||||||
private final StatusBar statusBar = new StatusBar();
|
|
||||||
|
|
||||||
private final TabPane tabPane = new TabPane();
|
|
||||||
|
|
||||||
private final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane();
|
|
||||||
|
|
||||||
private final VisualizationPanel visualizationPanel = new VisualizationPanel(navPanel);
|
|
||||||
|
|
||||||
private final SplitPane splitPane = new SplitPane();
|
|
||||||
|
|
||||||
private final TimeZonePanel timeZonePanel = new TimeZonePanel();
|
|
||||||
|
|
||||||
public TimeLineTopComponent() {
|
|
||||||
initComponents();
|
initComponents();
|
||||||
|
this.controller = controller;
|
||||||
associateLookup(ExplorerUtils.createLookup(em, getActionMap()));
|
associateLookup(ExplorerUtils.createLookup(em, getActionMap()));
|
||||||
|
|
||||||
setName(NbBundle.getMessage(TimeLineTopComponent.class, "CTL_TimeLineTopComponent"));
|
setName(NbBundle.getMessage(TimeLineTopComponent.class, "CTL_TimeLineTopComponent"));
|
||||||
setToolTipText(NbBundle.getMessage(TimeLineTopComponent.class, "HINT_TimeLineTopComponent"));
|
setToolTipText(NbBundle.getMessage(TimeLineTopComponent.class, "HINT_TimeLineTopComponent"));
|
||||||
setIcon(WindowManager.getDefault().getMainWindow().getIconImage()); //use the same icon as main application
|
setIcon(WindowManager.getDefault().getMainWindow().getIconImage()); //use the same icon as main application
|
||||||
|
|
||||||
timeZonePanel.setText(NbBundle.getMessage(this.getClass(), "TimeLineTopComponent.timeZonePanel.text"));
|
|
||||||
customizeComponents();
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized private void customizeComponents() {
|
|
||||||
|
|
||||||
dataContentPanel = DataContentPanel.createInstance();
|
dataContentPanel = DataContentPanel.createInstance();
|
||||||
this.contentViewerContainerPanel.add(dataContentPanel, BorderLayout.CENTER);
|
this.contentViewerContainerPanel.add(dataContentPanel, BorderLayout.CENTER);
|
||||||
tlrv = new TimeLineResultView(dataContentPanel);
|
tlrv = new TimeLineResultView(controller, dataContentPanel);
|
||||||
DataResultPanel dataResultPanel = tlrv.getDataResultPanel();
|
DataResultPanel dataResultPanel = tlrv.getDataResultPanel();
|
||||||
this.resultContainerPanel.add(dataResultPanel, BorderLayout.CENTER);
|
this.resultContainerPanel.add(dataResultPanel, BorderLayout.CENTER);
|
||||||
dataResultPanel.open();
|
dataResultPanel.open();
|
||||||
|
customizeFXComponents();
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@NbBundle.Messages({"TimeLineTopComponent.eventsTab.name=Events",
|
||||||
public synchronized void setController(TimeLineController controller) {
|
"TimeLineTopComponent.filterTab.name=Filters"})
|
||||||
this.controller = controller;
|
void customizeFXComponents() {
|
||||||
|
|
||||||
tlrv.setController(controller);
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
jFXVizPanel.getScene().addEventFilter(KeyEvent.KEY_PRESSED,
|
|
||||||
(KeyEvent event) -> {
|
//create and wire up jfx componenets that make up the interface
|
||||||
if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(event)) {
|
final Tab filterTab = new Tab(Bundle.TimeLineTopComponent_filterTab_name(), new FilterSetPanel(controller));
|
||||||
new Back(controller).handle(new ActionEvent());
|
filterTab.setClosable(false);
|
||||||
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE).match(event)) {
|
filterTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/funnel.png")); // NON-NLS
|
||||||
new Back(controller).handle(new ActionEvent());
|
|
||||||
} else if (new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN).match(event)) {
|
final EventsTree eventsTree = new EventsTree(controller);
|
||||||
new Forward(controller).handle(new ActionEvent());
|
final VisualizationPanel visualizationPanel = new VisualizationPanel(controller, eventsTree);
|
||||||
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE, KeyCodeCombination.SHIFT_DOWN).match(event)) {
|
final Tab eventsTreeTab = new Tab(Bundle.TimeLineTopComponent_eventsTab_name(), eventsTree);
|
||||||
new Forward(controller).handle(new ActionEvent());
|
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) -> {
|
controller.viewModeProperty().addListener((Observable observable) -> {
|
||||||
if (controller.viewModeProperty().get().equals(VisualizationMode.COUNTS)) {
|
if (controller.viewModeProperty().get().equals(VisualizationMode.COUNTS)) {
|
||||||
tabPane.getSelectionModel().select(filterTab);
|
//if view mode is counts, make sure events tabd is not active
|
||||||
|
leftTabPane.getSelectionModel().select(filterTab);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
eventsTab.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS));
|
|
||||||
visualizationPanel.setController(controller);
|
final TimeZonePanel timeZonePanel = new TimeZonePanel();
|
||||||
navPanel.setController(controller);
|
VBox.setVgrow(timeZonePanel, Priority.SOMETIMES);
|
||||||
filtersPanel.setController(controller);
|
|
||||||
zoomSettingsPane.setController(controller);
|
final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane(controller);
|
||||||
statusBar.setController(controller);
|
|
||||||
|
final VBox leftVBox = new VBox(5, timeZonePanel, zoomSettingsPane, leftTabPane);
|
||||||
|
SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE);
|
||||||
|
|
||||||
|
final SplitPane mainSplitPane = new SplitPane(leftVBox, visualizationPanel);
|
||||||
|
mainSplitPane.setDividerPositions(0);
|
||||||
|
|
||||||
|
final Scene scene = new Scene(mainSplitPane);
|
||||||
|
scene.addEventFilter(KeyEvent.KEY_PRESSED,
|
||||||
|
(KeyEvent event) -> {
|
||||||
|
if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(event)) {
|
||||||
|
new Back(controller).handle(null);
|
||||||
|
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE).match(event)) {
|
||||||
|
new Back(controller).handle(null);
|
||||||
|
} else if (new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN).match(event)) {
|
||||||
|
new Forward(controller).handle(null);
|
||||||
|
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE, KeyCodeCombination.SHIFT_DOWN).match(event)) {
|
||||||
|
new Forward(controller).handle(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//add ui componenets to JFXPanels
|
||||||
|
jFXVizPanel.setScene(scene);
|
||||||
|
jFXstatusPanel.setScene(new Scene(new StatusBar(controller)));
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,9 +163,9 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
|||||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||||
private void initComponents() {
|
private void initComponents() {
|
||||||
|
|
||||||
jFXstatusPanel = new javafx.embed.swing.JFXPanel();
|
jFXstatusPanel = new JFXPanel();
|
||||||
splitYPane = new javax.swing.JSplitPane();
|
splitYPane = new javax.swing.JSplitPane();
|
||||||
jFXVizPanel = new javafx.embed.swing.JFXPanel();
|
jFXVizPanel = new JFXPanel();
|
||||||
lowerSplitXPane = new javax.swing.JSplitPane();
|
lowerSplitXPane = new javax.swing.JSplitPane();
|
||||||
resultContainerPanel = new javax.swing.JPanel();
|
resultContainerPanel = new javax.swing.JPanel();
|
||||||
contentViewerContainerPanel = new javax.swing.JPanel();
|
contentViewerContainerPanel = new javax.swing.JPanel();
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
* Autopsy Forensic Browser
|
|
||||||
*
|
|
||||||
* Copyright 2014 Basis Technology Corp.
|
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.sleuthkit.autopsy.timeline;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface TimeLineUI {
|
|
||||||
|
|
||||||
void setController(TimeLineController controller);
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
/*
|
|
||||||
* Autopsy Forensic Browser
|
|
||||||
*
|
|
||||||
* Copyright 2013 Basis Technology Corp.
|
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.sleuthkit.autopsy.timeline;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface to be implemented by views of the data.
|
|
||||||
*
|
|
||||||
* Most implementations should install the relevant listeners in their
|
|
||||||
* {@link #setController} and {@link #setModel} methods
|
|
||||||
*/
|
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
|
||||||
|
|
||||||
public interface TimeLineView extends TimeLineUI {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void setController(TimeLineController controller);
|
|
||||||
|
|
||||||
void setModel(final FilteredEventsModel filteredEvents);
|
|
||||||
|
|
||||||
}
|
|
@ -1,6 +1,4 @@
|
|||||||
Back.actions.name.text=Back
|
Back.actions.name.text=Back
|
||||||
DefaultFilters.action.name.text=apply default filters
|
DefaultFilters.action.name.text=apply default filters
|
||||||
Forward.action.name.text=Forward
|
Forward.action.name.text=Forward
|
||||||
SaveSnapshot.action.name.text=save snapshot
|
|
||||||
SaveSnapshot.fileChoose.title.text=Save snapshot to
|
|
||||||
ZoomOut.action.name.text=apply default filters
|
|
||||||
|
@ -46,51 +46,48 @@ import org.sleuthkit.datamodel.TskCoreException;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
public class SaveSnapshot extends Action {
|
public class SaveSnapshotAsReport extends Action {
|
||||||
|
|
||||||
private static final String HTML_EXT = ".html";
|
private static final String HTML_EXT = ".html";
|
||||||
|
|
||||||
private static final String REPORT_IMAGE_EXTENSION = ".png";
|
private static final String REPORT_IMAGE_EXTENSION = ".png";
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(SaveSnapshot.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(SaveSnapshotAsReport.class.getName());
|
||||||
|
|
||||||
private final TimeLineController controller;
|
@NbBundle.Messages({"SaveSnapshot.action.name.text=save snapshot",
|
||||||
|
"SaveSnapshot.fileChoose.title.text=Save snapshot to"})
|
||||||
private final WritableImage snapshot;
|
public SaveSnapshotAsReport(TimeLineController controller, WritableImage snapshot) {
|
||||||
|
super(Bundle.SaveSnapshot_action_name_text());
|
||||||
public SaveSnapshot(TimeLineController controller, WritableImage snapshot) {
|
|
||||||
super(NbBundle.getMessage(SaveSnapshot.class, "SaveSnapshot.action.name.text"));
|
|
||||||
this.controller = controller;
|
|
||||||
this.snapshot = snapshot;
|
|
||||||
setEventHandler(new Consumer<ActionEvent>() {
|
setEventHandler(new Consumer<ActionEvent>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void accept(ActionEvent t) {
|
public void accept(ActionEvent t) {
|
||||||
//choose location/name
|
//choose location/name
|
||||||
DirectoryChooser fileChooser = new DirectoryChooser();
|
DirectoryChooser fileChooser = new DirectoryChooser();
|
||||||
fileChooser.setTitle(NbBundle.getMessage(this.getClass(), "SaveSnapshot.fileChoose.title.text"));
|
fileChooser.setTitle(Bundle.SaveSnapshot_fileChoose_title_text());
|
||||||
fileChooser.setInitialDirectory(new File(Case.getCurrentCase().getReportDirectory()));
|
fileChooser.setInitialDirectory(new File(Case.getCurrentCase().getReportDirectory()));
|
||||||
File outFolder = fileChooser.showDialog(null);
|
File reportDirectory = fileChooser.showDialog(null);
|
||||||
if (outFolder == null) {
|
if (reportDirectory == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
outFolder.mkdir();
|
reportDirectory.mkdir();
|
||||||
String name = outFolder.getName();
|
String reportName = reportDirectory.getName();
|
||||||
|
String reportPath = reportDirectory.getPath();
|
||||||
|
|
||||||
//gather metadata
|
//gather metadata
|
||||||
List<Pair<String, String>> reportMetaData = new ArrayList<>();
|
List<Pair<String, String>> reportMetaData = new ArrayList<>();
|
||||||
|
|
||||||
reportMetaData.add(new Pair<>("Case", Case.getCurrentCase().getName())); // NON-NLS
|
reportMetaData.add(new Pair<>("Case", Case.getCurrentCase().getName())); // NON-NLS
|
||||||
|
|
||||||
ZoomParams get = controller.getEventsModel().zoomParametersProperty().get();
|
ZoomParams zoomParams = controller.getEventsModel().zoomParametersProperty().get();
|
||||||
reportMetaData.add(new Pair<>("Time Range", get.getTimeRange().toString())); // NON-NLS
|
reportMetaData.add(new Pair<>("Time Range", zoomParams.getTimeRange().toString())); // NON-NLS
|
||||||
reportMetaData.add(new Pair<>("Description Level of Detail", get.getDescriptionLOD().getDisplayName())); // NON-NLS
|
reportMetaData.add(new Pair<>("Description Level of Detail", zoomParams.getDescriptionLOD().getDisplayName())); // NON-NLS
|
||||||
reportMetaData.add(new Pair<>("Event Type Zoom Level", get.getTypeZoomLevel().getDisplayName())); // NON-NLS
|
reportMetaData.add(new Pair<>("Event Type Zoom Level", zoomParams.getTypeZoomLevel().getDisplayName())); // NON-NLS
|
||||||
reportMetaData.add(new Pair<>("Filters", get.getFilter().getHTMLReportString())); // NON-NLS
|
reportMetaData.add(new Pair<>("Filters", zoomParams.getFilter().getHTMLReportString())); // NON-NLS
|
||||||
|
|
||||||
//save snapshot as png
|
//save snapshot as png
|
||||||
try {
|
try {
|
||||||
ImageIO.write(SwingFXUtils.fromFXImage(snapshot, null), "png", new File(outFolder.getPath() + File.separator + outFolder.getName() + REPORT_IMAGE_EXTENSION)); // NON-NLS
|
ImageIO.write(SwingFXUtils.fromFXImage(snapshot, null), "png",
|
||||||
|
new File(reportPath, reportName + REPORT_IMAGE_EXTENSION)); // NON-NLS
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
LOGGER.log(Level.WARNING, "failed to write snapshot to disk", ex); // NON-NLS
|
LOGGER.log(Level.WARNING, "failed to write snapshot to disk", ex); // NON-NLS
|
||||||
return;
|
return;
|
||||||
@ -99,17 +96,18 @@ public class SaveSnapshot extends Action {
|
|||||||
//build html string
|
//build html string
|
||||||
StringBuilder wrapper = new StringBuilder();
|
StringBuilder wrapper = new StringBuilder();
|
||||||
wrapper.append("<html>\n<head>\n\t<title>").append("timeline snapshot").append("</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n</head>\n<body>\n"); // NON-NLS
|
wrapper.append("<html>\n<head>\n\t<title>").append("timeline snapshot").append("</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n</head>\n<body>\n"); // NON-NLS
|
||||||
wrapper.append("<div id=\"content\">\n<h1>").append(outFolder.getName()).append("</h1>\n"); // NON-NLS
|
wrapper.append("<div id=\"content\">\n<h1>").append(reportDirectory.getName()).append("</h1>\n"); // NON-NLS
|
||||||
wrapper.append("<img src = \"").append(outFolder.getName()).append(REPORT_IMAGE_EXTENSION + "\" alt = \"snaphot\">"); // NON-NLS
|
wrapper.append("<img src = \"").append(reportDirectory.getName()).append(REPORT_IMAGE_EXTENSION + "\" alt = \"snaphot\">"); // NON-NLS
|
||||||
wrapper.append("<table>\n"); // NON-NLS
|
wrapper.append("<table>\n"); // NON-NLS
|
||||||
for (Pair<String, String> pair : reportMetaData) {
|
for (Pair<String, String> pair : reportMetaData) {
|
||||||
wrapper.append("<tr><td>").append(pair.getKey()).append(": </td><td>").append(pair.getValue()).append("</td></tr>\n"); // NON-NLS
|
wrapper.append("<tr><td>").append(pair.getKey()).append(": </td><td>").append(pair.getValue()).append("</td></tr>\n"); // NON-NLS
|
||||||
}
|
}
|
||||||
wrapper.append("</table>\n"); // NON-NLS
|
wrapper.append("</table>\n"); // NON-NLS
|
||||||
wrapper.append("</div>\n</body>\n</html>"); // NON-NLS
|
wrapper.append("</div>\n</body>\n</html>"); // NON-NLS
|
||||||
|
File reportHTMLFIle = new File(reportDirectory, reportName + HTML_EXT);
|
||||||
|
|
||||||
//write html wrapper
|
//write html wrapper
|
||||||
try (Writer htmlWriter = new FileWriter(new File(outFolder, name + HTML_EXT))) {
|
try (Writer htmlWriter = new FileWriter(reportHTMLFIle)) {
|
||||||
htmlWriter.write(wrapper.toString());
|
htmlWriter.write(wrapper.toString());
|
||||||
} catch (FileNotFoundException ex) {
|
} catch (FileNotFoundException ex) {
|
||||||
LOGGER.log(Level.WARNING, "failed to open html wrapper file for writing ", ex); // NON-NLS
|
LOGGER.log(Level.WARNING, "failed to open html wrapper file for writing ", ex); // NON-NLS
|
||||||
@ -121,14 +119,14 @@ public class SaveSnapshot extends Action {
|
|||||||
|
|
||||||
//copy css
|
//copy css
|
||||||
try (InputStream resource = this.getClass().getResourceAsStream("/org/sleuthkit/autopsy/timeline/index.css")) { // NON-NLS
|
try (InputStream resource = this.getClass().getResourceAsStream("/org/sleuthkit/autopsy/timeline/index.css")) { // NON-NLS
|
||||||
Files.copy(resource, Paths.get(outFolder.getPath(), "index.css")); // NON-NLS
|
Files.copy(resource, Paths.get(reportPath, "index.css")); // NON-NLS
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
LOGGER.log(Level.WARNING, "failed to copy css file", ex); // NON-NLS
|
LOGGER.log(Level.WARNING, "failed to copy css file", ex); // NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
//add html file as report to case
|
//add html file as report to case
|
||||||
try {
|
try {
|
||||||
Case.getCurrentCase().addReport(outFolder.getPath() + File.separator + outFolder.getName() + HTML_EXT, "Timeline", outFolder.getName() + HTML_EXT); // NON-NLS
|
Case.getCurrentCase().addReport(reportHTMLFIle.getPath(), "Timeline", reportName + HTML_EXT); // NON-NLS
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
LOGGER.log(Level.WARNING, "failed add html wrapper as a report", ex); // NON-NLS
|
LOGGER.log(Level.WARNING, "failed add html wrapper as a report", ex); // NON-NLS
|
||||||
}
|
}
|
44
Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomIn.java
Normal file
44
Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomIn.java
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2015 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.timeline.actions;
|
||||||
|
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import org.controlsfx.control.action.Action;
|
||||||
|
import org.openide.util.NbBundle;
|
||||||
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class ZoomIn extends Action {
|
||||||
|
|
||||||
|
private static final Image MAGNIFIER_IN = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-in-green.png"); //NOI18N
|
||||||
|
|
||||||
|
@NbBundle.Messages({"ZoomIn.longText=Zoom in to view half as much time.",
|
||||||
|
"ZoomIn.action.text=Zoom in"})
|
||||||
|
public ZoomIn(TimeLineController controller) {
|
||||||
|
super(Bundle.ZoomIn_action_text());
|
||||||
|
setLongText(Bundle.ZoomIn_longText());
|
||||||
|
setGraphic(new ImageView(MAGNIFIER_IN));
|
||||||
|
setEventHandler(actionEvent -> {
|
||||||
|
controller.pushZoomInTime();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2014 Basis Technology Corp.
|
* Copyright 2015 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -19,7 +19,8 @@
|
|||||||
package org.sleuthkit.autopsy.timeline.actions;
|
package org.sleuthkit.autopsy.timeline.actions;
|
||||||
|
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
import org.controlsfx.control.action.Action;
|
import org.controlsfx.control.action.Action;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
@ -30,15 +31,22 @@ import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
|||||||
*/
|
*/
|
||||||
public class ZoomOut extends Action {
|
public class ZoomOut extends Action {
|
||||||
|
|
||||||
private final TimeLineController controller;
|
private static final Image MAGNIFIER_OUT = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-out-red.png"); //NOI18N
|
||||||
|
|
||||||
private final FilteredEventsModel eventsModel;
|
@NbBundle.Messages({"ZoomOut.longText=Zoom out to view 50% more time.",
|
||||||
|
"ZoomOut.action.text=Zoom out"})
|
||||||
|
public ZoomOut(TimeLineController controller) {
|
||||||
|
super(Bundle.ZoomOut_action_text());
|
||||||
|
setLongText(Bundle.ZoomOut_longText());
|
||||||
|
setGraphic(new ImageView(MAGNIFIER_OUT));
|
||||||
|
setEventHandler(actionEvent -> {
|
||||||
|
controller.pushZoomOutTime();
|
||||||
|
});
|
||||||
|
|
||||||
public ZoomOut(final TimeLineController controller) {
|
//disable action when the current time range already encompases the entire case.
|
||||||
super(NbBundle.getMessage(ZoomOut.class, "ZoomOut.action.name.text"));
|
|
||||||
this.controller = controller;
|
|
||||||
eventsModel = controller.getEventsModel();
|
|
||||||
disabledProperty().bind(new BooleanBinding() {
|
disabledProperty().bind(new BooleanBinding() {
|
||||||
|
private final FilteredEventsModel eventsModel = controller.getEventsModel();
|
||||||
|
|
||||||
{
|
{
|
||||||
bind(eventsModel.zoomParametersProperty());
|
bind(eventsModel.zoomParametersProperty());
|
||||||
}
|
}
|
||||||
@ -48,8 +56,5 @@ public class ZoomOut extends Action {
|
|||||||
return eventsModel.zoomParametersProperty().getValue().getTimeRange().contains(eventsModel.getSpanningInterval());
|
return eventsModel.zoomParametersProperty().getValue().getTimeRange().contains(eventsModel.getSpanningInterval());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setEventHandler((ActionEvent t) -> {
|
|
||||||
controller.zoomOutToActivity();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2014-15 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.timeline.actions;
|
||||||
|
|
||||||
|
import javafx.beans.binding.BooleanBinding;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import org.controlsfx.control.action.Action;
|
||||||
|
import org.openide.util.NbBundle;
|
||||||
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class ZoomToEvents extends Action {
|
||||||
|
|
||||||
|
private static final Image MAGNIFIER_OUT = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-out-red.png", 16, 16, true, true); //NOI18N
|
||||||
|
|
||||||
|
@NbBundle.Messages({"ZoomToEvents.action.text=Zoom to events",
|
||||||
|
"ZoomToEvents.longText=Zoom out to show the nearest events."})
|
||||||
|
public ZoomToEvents(final TimeLineController controller) {
|
||||||
|
super(Bundle.ZoomToEvents_action_text());
|
||||||
|
setLongText(Bundle.ZoomToEvents_longText());
|
||||||
|
setGraphic(new ImageView(MAGNIFIER_OUT));
|
||||||
|
setEventHandler(actionEvent -> {
|
||||||
|
controller.zoomOutToActivity();
|
||||||
|
});
|
||||||
|
|
||||||
|
//disable action when the current time range already encompases the entire case.
|
||||||
|
disabledProperty().bind(new BooleanBinding() {
|
||||||
|
private final FilteredEventsModel eventsModel = controller.getEventsModel();
|
||||||
|
|
||||||
|
{
|
||||||
|
bind(eventsModel.zoomParametersProperty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean computeValue() {
|
||||||
|
//TODO: do a db query to see if using this action will actually result in viewable events
|
||||||
|
return eventsModel.zoomParametersProperty().getValue().getTimeRange().contains(eventsModel.getSpanningInterval());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -41,7 +41,6 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
|||||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent.DeletedContentTagInfo;
|
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent.DeletedContentTagInfo;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
||||||
import org.sleuthkit.autopsy.timeline.db.EventsRepository;
|
import org.sleuthkit.autopsy.timeline.db.EventsRepository;
|
||||||
|
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/cross-script.png
Normal file
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/cross-script.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 623 B |
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/select.png
Normal file
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/select.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 800 B |
@ -40,6 +40,7 @@ import javafx.scene.chart.Chart;
|
|||||||
import javafx.scene.chart.XYChart;
|
import javafx.scene.chart.XYChart;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.OverrunStyle;
|
import javafx.scene.control.OverrunStyle;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.scene.effect.Effect;
|
import javafx.scene.effect.Effect;
|
||||||
import javafx.scene.input.MouseButton;
|
import javafx.scene.input.MouseButton;
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
@ -50,12 +51,11 @@ import javafx.scene.text.Font;
|
|||||||
import javafx.scene.text.FontWeight;
|
import javafx.scene.text.FontWeight;
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
import javafx.scene.text.TextAlignment;
|
import javafx.scene.text.TextAlignment;
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
|
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
|
||||||
|
|
||||||
@ -73,8 +73,15 @@ import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
|
|||||||
* {@link XYChart} doing the rendering. Is this a good idea? -jm TODO: pull up
|
* {@link XYChart} doing the rendering. Is this a good idea? -jm TODO: pull up
|
||||||
* common history context menu items out of derived classes? -jm
|
* common history context menu items out of derived classes? -jm
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & TimeLineChart<X>> extends BorderPane implements TimeLineView {
|
public abstract class AbstractVisualizationPane<X, Y, N, C extends XYChart<X, Y> & TimeLineChart<X>> extends BorderPane {
|
||||||
|
|
||||||
|
@NbBundle.Messages("AbstractVisualization.Drag_Tooltip.text=Drag the mouse to select a time interval to zoom into.")
|
||||||
|
private static final Tooltip DRAG_TOOLTIP = new Tooltip(Bundle.AbstractVisualization_Drag_Tooltip_text());
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(AbstractVisualizationPane.class.getName());
|
||||||
|
|
||||||
|
public static Tooltip getDragTooltip() {
|
||||||
|
return DRAG_TOOLTIP;
|
||||||
|
}
|
||||||
protected final SimpleBooleanProperty hasEvents = new SimpleBooleanProperty(true);
|
protected final SimpleBooleanProperty hasEvents = new SimpleBooleanProperty(true);
|
||||||
|
|
||||||
protected final ObservableList<BarChart.Series<X, Y>> dataSets = FXCollections.<BarChart.Series<X, Y>>observableArrayList();
|
protected final ObservableList<BarChart.Series<X, Y>> dataSets = FXCollections.<BarChart.Series<X, Y>>observableArrayList();
|
||||||
@ -93,11 +100,15 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
|||||||
*/
|
*/
|
||||||
private Task<Boolean> updateTask;
|
private Task<Boolean> updateTask;
|
||||||
|
|
||||||
protected TimeLineController controller;
|
final protected TimeLineController controller;
|
||||||
|
|
||||||
protected FilteredEventsModel filteredEvents;
|
final protected FilteredEventsModel filteredEvents;
|
||||||
|
|
||||||
protected ReadOnlyListWrapper<N> selectedNodes = new ReadOnlyListWrapper<>(FXCollections.observableArrayList());
|
final protected ReadOnlyListWrapper<N> selectedNodes = new ReadOnlyListWrapper<>(FXCollections.observableArrayList());
|
||||||
|
|
||||||
|
private InvalidationListener invalidationListener = (Observable observable) -> {
|
||||||
|
update();
|
||||||
|
};
|
||||||
|
|
||||||
public ReadOnlyListProperty<N> getSelectedNodes() {
|
public ReadOnlyListProperty<N> getSelectedNodes() {
|
||||||
return selectedNodes.getReadOnlyProperty();
|
return selectedNodes.getReadOnlyProperty();
|
||||||
@ -177,7 +188,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
|||||||
* Primarily this invokes the background {@link Task} returned by
|
* Primarily this invokes the background {@link Task} returned by
|
||||||
* {@link #getUpdateTask()} which derived classes must implement.
|
* {@link #getUpdateTask()} which derived classes must implement.
|
||||||
*/
|
*/
|
||||||
synchronized public void update() {
|
final synchronized public void update() {
|
||||||
if (updateTask != null) {
|
if (updateTask != null) {
|
||||||
updateTask.cancel(true);
|
updateTask.cancel(true);
|
||||||
updateTask = null;
|
updateTask = null;
|
||||||
@ -195,7 +206,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
|||||||
try {
|
try {
|
||||||
this.hasEvents.set(updateTask.get());
|
this.hasEvents.set(updateTask.get());
|
||||||
} catch (InterruptedException | ExecutionException ex) {
|
} catch (InterruptedException | ExecutionException ex) {
|
||||||
Logger.getLogger(AbstractVisualization.class.getName()).log(Level.SEVERE, "Unexpected exception updating visualization", ex);
|
LOGGER.log(Level.SEVERE, "Unexpected exception updating visualization", ex); //NOI18N
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -203,7 +214,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
|||||||
controller.monitorTask(updateTask);
|
controller.monitorTask(updateTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized public void dispose() {
|
final synchronized public void dispose() {
|
||||||
if (updateTask != null) {
|
if (updateTask != null) {
|
||||||
updateTask.cancel(true);
|
updateTask.cancel(true);
|
||||||
}
|
}
|
||||||
@ -211,7 +222,12 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
|||||||
invalidationListener = null;
|
invalidationListener = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AbstractVisualization(Pane partPane, Pane contextPane, Region spacer) {
|
protected AbstractVisualizationPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) {
|
||||||
|
this.controller = controller;
|
||||||
|
|
||||||
|
this.filteredEvents = controller.getEventsModel();
|
||||||
|
this.filteredEvents.registerForEvents(this);
|
||||||
|
this.filteredEvents.zoomParametersProperty().addListener(invalidationListener);
|
||||||
this.leafPane = partPane;
|
this.leafPane = partPane;
|
||||||
this.branchPane = contextPane;
|
this.branchPane = contextPane;
|
||||||
this.spacer = spacer;
|
this.spacer = spacer;
|
||||||
@ -226,31 +242,17 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
TimeLineController.getTimeZone().addListener(invalidationListener);
|
||||||
synchronized public void setController(TimeLineController controller) {
|
|
||||||
this.controller = controller;
|
|
||||||
chart.setController(controller);
|
|
||||||
|
|
||||||
setModel(controller.getEventsModel());
|
//show tooltip text in status bar
|
||||||
TimeLineController.getTimeZone().addListener((Observable observable) -> {
|
hoverProperty().addListener((observable, oldActivated, newActivated) -> {
|
||||||
update();
|
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();
|
update();
|
||||||
}
|
}
|
||||||
@ -260,10 +262,6 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
|||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected InvalidationListener invalidationListener = (Observable observable) -> {
|
|
||||||
update();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* iterate through the list of tick-marks building a two level structure of
|
* iterate through the list of tick-marks building a two level structure of
|
||||||
* replacement tick marl labels. (Visually) upper level has most
|
* replacement tick marl labels. (Visually) upper level has most
|
||||||
@ -370,7 +368,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
|||||||
*/
|
*/
|
||||||
private synchronized void assignLeafLabel(String labelText, double labelWidth, double labelX, boolean bold) {
|
private synchronized void assignLeafLabel(String labelText, double labelWidth, double labelX, boolean bold) {
|
||||||
|
|
||||||
Text label = new Text(" " + labelText + " ");
|
Text label = new Text(" " + labelText + " "); //NOI18N
|
||||||
label.setTextAlignment(TextAlignment.CENTER);
|
label.setTextAlignment(TextAlignment.CENTER);
|
||||||
label.setFont(Font.font(null, bold ? FontWeight.BOLD : FontWeight.NORMAL, 10));
|
label.setFont(Font.font(null, bold ? FontWeight.BOLD : FontWeight.NORMAL, 10));
|
||||||
//position label accounting for width
|
//position label accounting for width
|
||||||
@ -414,9 +412,9 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
|||||||
label.relocate(labelX, 0);
|
label.relocate(labelX, 0);
|
||||||
|
|
||||||
if (labelX == 0) { // first label has no border
|
if (labelX == 0) { // first label has no border
|
||||||
label.setStyle("-fx-border-width: 0 0 0 0 ; -fx-border-color:black;"); // NON-NLS
|
label.setStyle("-fx-border-width: 0 0 0 0 ; -fx-border-color:black;"); // NON-NLS //NOI18N
|
||||||
} else { // subsequent labels have border on left to create dividers
|
} else { // subsequent labels have border on left to create dividers
|
||||||
label.setStyle("-fx-border-width: 0 0 0 1; -fx-border-color:black;"); // NON-NLS
|
label.setStyle("-fx-border-width: 0 0 0 1; -fx-border-color:black;"); // NON-NLS //NOI18N
|
||||||
}
|
}
|
||||||
|
|
||||||
branchPane.getChildren().add(label);
|
branchPane.getChildren().add(label);
|
||||||
@ -446,10 +444,10 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
|||||||
|
|
||||||
TwoPartDateTime(String dateString) {
|
TwoPartDateTime(String dateString) {
|
||||||
//find index of separator to spit on
|
//find index of separator to spit on
|
||||||
int splitIndex = StringUtils.lastIndexOfAny(dateString, " ", "-", ":");
|
int splitIndex = StringUtils.lastIndexOfAny(dateString, " ", "-", ":"); //NOI18N
|
||||||
if (splitIndex < 0) { // there is only one part
|
if (splitIndex < 0) { // there is only one part
|
||||||
leaf = dateString;
|
leaf = dateString;
|
||||||
branch = "";
|
branch = ""; //NOI18N
|
||||||
} else { //split at index
|
} else { //split at index
|
||||||
leaf = StringUtils.substring(dateString, splitIndex + 1);
|
leaf = StringUtils.substring(dateString, splitIndex + 1);
|
||||||
branch = StringUtils.substring(dateString, 0, splitIndex);
|
branch = StringUtils.substring(dateString, 0, splitIndex);
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013 Basis Technology Corp.
|
* Copyright 2013-15 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -35,7 +35,6 @@ Timeline.ui.ZoomRanges.threeyears.text=Three Years
|
|||||||
Timeline.ui.ZoomRanges.fiveyears.text=Five Years
|
Timeline.ui.ZoomRanges.fiveyears.text=Five Years
|
||||||
Timeline.ui.ZoomRanges.tenyears.text=Ten Years
|
Timeline.ui.ZoomRanges.tenyears.text=Ten Years
|
||||||
Timeline.ui.ZoomRanges.all.text=All
|
Timeline.ui.ZoomRanges.all.text=All
|
||||||
Timeline.ui.TimeLineChart.tooltip.text=Double-click to zoom into range\:\n{0} to {1}\nRight-click to clear.
|
|
||||||
TimeLineResultView.startDateToEndDate.text={0} to {1}
|
TimeLineResultView.startDateToEndDate.text={0} to {1}
|
||||||
VisualizationPanel.histogramTask.title=Rebuild Histogram
|
VisualizationPanel.histogramTask.title=Rebuild Histogram
|
||||||
VisualizationPanel.histogramTask.preparing=preparing
|
VisualizationPanel.histogramTask.preparing=preparing
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
.intervalSelector{
|
||||||
|
-fx-background-color: rgba(0,0,255,.25);
|
||||||
|
-fx-border-color: rgba(0,0,255,.25);
|
||||||
|
-fx-border-width: 0 3 0 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton{
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton:hover{
|
||||||
|
-fx-opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoomButton:hover{
|
||||||
|
-fx-opacity: 1;
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.*?>
|
||||||
|
<?import javafx.scene.image.*?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import java.lang.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
|
||||||
|
<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" styleClass="intervalSelector" stylesheets="@IntervalSelector.css" type="BorderPane" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
|
<top>
|
||||||
|
<Button fx:id="closeButton" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" opacity="0.5" prefHeight="16.0" prefWidth="16.0" styleClass="closeButton" BorderPane.alignment="CENTER_RIGHT">
|
||||||
|
<graphic>
|
||||||
|
<ImageView opacity="0.5" pickOnBounds="true" preserveRatio="true">
|
||||||
|
<image>
|
||||||
|
<Image url="@../images/cross-script.png" />
|
||||||
|
</image>
|
||||||
|
</ImageView>
|
||||||
|
</graphic>
|
||||||
|
</Button>
|
||||||
|
</top>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
|
||||||
|
</padding>
|
||||||
|
<bottom>
|
||||||
|
<BorderPane fx:id="bottomBorder" minHeight="32.0" BorderPane.alignment="BOTTOM_LEFT">
|
||||||
|
<left>
|
||||||
|
<HBox alignment="BOTTOM_LEFT" BorderPane.alignment="BOTTOM_LEFT">
|
||||||
|
<children>
|
||||||
|
<Label fx:id="startLabel" alignment="BOTTOM_LEFT" minWidth="0.0" text="Label" />
|
||||||
|
<Region HBox.hgrow="ALWAYS" />
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
</left>
|
||||||
|
<right>
|
||||||
|
<HBox alignment="BOTTOM_RIGHT" BorderPane.alignment="CENTER">
|
||||||
|
<children>
|
||||||
|
<Region HBox.hgrow="ALWAYS" />
|
||||||
|
<Label fx:id="endLabel" minWidth="0.0" text="Label" />
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
</right>
|
||||||
|
<center>
|
||||||
|
<Button fx:id="zoomButton" alignment="CENTER" mnemonicParsing="false" opacity="0.66" styleClass="zoomButton" text="Zoom" BorderPane.alignment="CENTER">
|
||||||
|
<graphic>
|
||||||
|
<ImageView pickOnBounds="true" preserveRatio="true">
|
||||||
|
<image>
|
||||||
|
<Image url="@../images/magnifier-zoom-fit.png" />
|
||||||
|
</image>
|
||||||
|
</ImageView>
|
||||||
|
</graphic>
|
||||||
|
</Button>
|
||||||
|
</center>
|
||||||
|
</BorderPane>
|
||||||
|
</bottom>
|
||||||
|
</fx:root>
|
316
Core/src/org/sleuthkit/autopsy/timeline/ui/IntervalSelector.java
Normal file
316
Core/src/org/sleuthkit/autopsy/timeline/ui/IntervalSelector.java
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2014-15 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.timeline.ui;
|
||||||
|
|
||||||
|
import javafx.beans.binding.BooleanBinding;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.Cursor;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.input.MouseButton;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import org.controlsfx.control.action.Action;
|
||||||
|
import org.controlsfx.control.action.ActionUtils;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.joda.time.Interval;
|
||||||
|
import org.openide.util.NbBundle;
|
||||||
|
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||||
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visually represents a 'selected' time range, and allows mouse interactions
|
||||||
|
* with it.
|
||||||
|
*
|
||||||
|
* @param <X> the type of values along the x axis this is a selector for
|
||||||
|
*
|
||||||
|
* This abstract class requires concrete implementations to implement template
|
||||||
|
* methods to handle formating and date 'lookup' of the generic x-axis type
|
||||||
|
*/
|
||||||
|
public abstract class IntervalSelector<X> extends BorderPane {
|
||||||
|
|
||||||
|
private static final Image ClEAR_INTERVAL_ICON = new Image("/org/sleuthkit/autopsy/timeline/images/cross-script.png", 16, 16, true, true, true);
|
||||||
|
private static final Image ZOOM_TO_INTERVAL_ICON = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-fit.png", 16, 16, true, true, true);
|
||||||
|
private static final double STROKE_WIDTH = 3;
|
||||||
|
private static final double HALF_STROKE = STROKE_WIDTH / 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the Axis this is a selector over
|
||||||
|
*/
|
||||||
|
public final TimeLineChart<X> chart;
|
||||||
|
|
||||||
|
private Tooltip tooltip;
|
||||||
|
/////////drag state
|
||||||
|
private DragPosition dragPosition;
|
||||||
|
private double startLeft;
|
||||||
|
private double startDragX;
|
||||||
|
private double startWidth;
|
||||||
|
|
||||||
|
private final BooleanProperty isDragging = new SimpleBooleanProperty(false);
|
||||||
|
/////////end drag state
|
||||||
|
private final TimeLineController controller;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label startLabel;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label endLabel;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button closeButton;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button zoomButton;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private BorderPane bottomBorder;
|
||||||
|
|
||||||
|
public IntervalSelector(TimeLineChart<X> chart) {
|
||||||
|
this.chart = chart;
|
||||||
|
this.controller = chart.getController();
|
||||||
|
FXMLConstructor.construct(this, IntervalSelector.class, "IntervalSelector.fxml"); // NON-NLS
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void initialize() {
|
||||||
|
assert startLabel != null : "fx:id=\"startLabel\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
|
||||||
|
assert endLabel != null : "fx:id=\"endLabel\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
|
||||||
|
assert closeButton != null : "fx:id=\"closeButton\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
|
||||||
|
assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
|
||||||
|
|
||||||
|
setMaxHeight(USE_PREF_SIZE);
|
||||||
|
setMinHeight(USE_PREF_SIZE);
|
||||||
|
setMaxWidth(USE_PREF_SIZE);
|
||||||
|
setMinWidth(USE_PREF_SIZE);
|
||||||
|
|
||||||
|
BooleanBinding showingControls = hoverProperty().and(isDragging.not());
|
||||||
|
closeButton.visibleProperty().bind(showingControls);
|
||||||
|
closeButton.managedProperty().bind(showingControls);
|
||||||
|
zoomButton.visibleProperty().bind(showingControls);
|
||||||
|
zoomButton.managedProperty().bind(showingControls);
|
||||||
|
|
||||||
|
widthProperty().addListener(o -> {
|
||||||
|
IntervalSelector.this.updateStartAndEnd();
|
||||||
|
if (startLabel.getWidth() + zoomButton.getWidth() + endLabel.getWidth() > getWidth()) {
|
||||||
|
this.setCenter(zoomButton);
|
||||||
|
} else {
|
||||||
|
bottomBorder.setCenter(zoomButton);
|
||||||
|
}
|
||||||
|
BorderPane.setAlignment(zoomButton, Pos.BOTTOM_CENTER);
|
||||||
|
});
|
||||||
|
layoutXProperty().addListener(o -> this.updateStartAndEnd());
|
||||||
|
updateStartAndEnd();
|
||||||
|
|
||||||
|
setOnMouseMoved(mouseMove -> {
|
||||||
|
Point2D parentMouse = getLocalMouseCoords(mouseMove);
|
||||||
|
final double diffX = getLayoutX() - parentMouse.getX();
|
||||||
|
if (Math.abs(diffX) <= HALF_STROKE) {
|
||||||
|
setCursor(Cursor.W_RESIZE);
|
||||||
|
} else if (Math.abs(diffX + getWidth()) <= HALF_STROKE) {
|
||||||
|
setCursor(Cursor.E_RESIZE);
|
||||||
|
} else {
|
||||||
|
setCursor(Cursor.HAND);
|
||||||
|
}
|
||||||
|
mouseMove.consume();
|
||||||
|
});
|
||||||
|
|
||||||
|
setOnMousePressed(mousePress -> {
|
||||||
|
Point2D parentMouse = getLocalMouseCoords(mousePress);
|
||||||
|
final double diffX = getLayoutX() - parentMouse.getX();
|
||||||
|
startDragX = mousePress.getScreenX();
|
||||||
|
startWidth = getWidth();
|
||||||
|
startLeft = getLayoutX();
|
||||||
|
if (Math.abs(diffX) <= HALF_STROKE) {
|
||||||
|
dragPosition = IntervalSelector.DragPosition.LEFT;
|
||||||
|
} else if (Math.abs(diffX + getWidth()) <= HALF_STROKE) {
|
||||||
|
dragPosition = IntervalSelector.DragPosition.RIGHT;
|
||||||
|
} else {
|
||||||
|
dragPosition = IntervalSelector.DragPosition.CENTER;
|
||||||
|
}
|
||||||
|
mousePress.consume();
|
||||||
|
});
|
||||||
|
|
||||||
|
setOnMouseReleased(mouseRelease -> isDragging.set(false));
|
||||||
|
setOnMouseDragged(mouseDrag -> {
|
||||||
|
isDragging.set(true);
|
||||||
|
double dX = mouseDrag.getScreenX() - startDragX;
|
||||||
|
switch (dragPosition) {
|
||||||
|
case CENTER:
|
||||||
|
setLayoutX(startLeft + dX);
|
||||||
|
break;
|
||||||
|
case LEFT:
|
||||||
|
if (dX > startWidth) {
|
||||||
|
startDragX = mouseDrag.getScreenX();
|
||||||
|
startWidth = 0;
|
||||||
|
dragPosition = DragPosition.RIGHT;
|
||||||
|
} else {
|
||||||
|
setLayoutX(startLeft + dX);
|
||||||
|
setPrefWidth(startWidth - dX);
|
||||||
|
autosize();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RIGHT:
|
||||||
|
Point2D parentMouse = getLocalMouseCoords(mouseDrag);
|
||||||
|
if (parentMouse.getX() < startLeft) {
|
||||||
|
dragPosition = DragPosition.LEFT;
|
||||||
|
startDragX = mouseDrag.getScreenX();
|
||||||
|
startWidth = 0;
|
||||||
|
} else {
|
||||||
|
setPrefWidth(startWidth + dX);
|
||||||
|
autosize();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mouseDrag.consume();
|
||||||
|
});
|
||||||
|
|
||||||
|
ActionUtils.configureButton(new ZoomToSelectedIntervalAction(), zoomButton);
|
||||||
|
ActionUtils.configureButton(new ClearSelectedIntervalAction(), closeButton);
|
||||||
|
|
||||||
|
//have to add handler rather than use convenience methods so that charts can listen for dismisal click
|
||||||
|
setOnMouseClicked(mosueClick -> {
|
||||||
|
if (mosueClick.getButton() == MouseButton.SECONDARY) {
|
||||||
|
chart.clearIntervalSelector();
|
||||||
|
mosueClick.consume();
|
||||||
|
}
|
||||||
|
if (mosueClick.getClickCount() >= 2) {
|
||||||
|
zoomToSelectedInterval();
|
||||||
|
mosueClick.consume();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Point2D getLocalMouseCoords(MouseEvent mouseEvent) {
|
||||||
|
return getParent().sceneToLocal(new Point2D(mouseEvent.getSceneX(), mouseEvent.getSceneY()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void zoomToSelectedInterval() {
|
||||||
|
//convert to DateTimes, using max/min if null(off axis)
|
||||||
|
DateTime start = parseDateTime(getSpanStart());
|
||||||
|
DateTime end = parseDateTime(getSpanEnd());
|
||||||
|
Interval i = adjustInterval(start.isBefore(end) ? new Interval(start, end) : new Interval(end, start));
|
||||||
|
controller.pushTimeRange(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param i the interval represented by this selector
|
||||||
|
*
|
||||||
|
* @return a modified version of {@code i} adjusted to suite the needs of
|
||||||
|
* the concrete implementation
|
||||||
|
*/
|
||||||
|
protected abstract Interval adjustInterval(Interval i);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* format a string representation of the given x-axis value to use in the
|
||||||
|
* tooltip
|
||||||
|
*
|
||||||
|
* @param date a x-axis value of type X
|
||||||
|
*
|
||||||
|
* @return a string representation of the given x-axis value
|
||||||
|
*/
|
||||||
|
protected abstract String formatSpan(final X date);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parse an x-axis value to a {@link DateTime}
|
||||||
|
*
|
||||||
|
* @param date a x-axis value of type X
|
||||||
|
*
|
||||||
|
* @return a {@link DateTime} corresponding to the given x-axis value
|
||||||
|
*/
|
||||||
|
protected abstract DateTime parseDateTime(X date);
|
||||||
|
|
||||||
|
@NbBundle.Messages(value = {"# {0} - start timestamp",
|
||||||
|
"# {1} - end timestamp",
|
||||||
|
"Timeline.ui.TimeLineChart.tooltip.text=Double-click to zoom into range:\n{0} to {1}\nRight-click to clear."})
|
||||||
|
private void updateStartAndEnd() {
|
||||||
|
String startString = formatSpan(getSpanStart());
|
||||||
|
String endString = formatSpan(getSpanEnd());
|
||||||
|
startLabel.setText(startString);
|
||||||
|
endLabel.setText(endString);
|
||||||
|
|
||||||
|
Tooltip.uninstall(this, tooltip);
|
||||||
|
tooltip = new Tooltip(Bundle.Timeline_ui_TimeLineChart_tooltip_text(startString, endString));
|
||||||
|
Tooltip.install(this, tooltip);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value along the x-axis corresponding to the left edge of the
|
||||||
|
* selector
|
||||||
|
*/
|
||||||
|
public X getSpanEnd() {
|
||||||
|
return getValueForDisplay(getBoundsInParent().getMaxX());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value along the x-axis corresponding to the right edge of the
|
||||||
|
* selector
|
||||||
|
*/
|
||||||
|
public X getSpanStart() {
|
||||||
|
return getValueForDisplay(getBoundsInParent().getMinX());
|
||||||
|
}
|
||||||
|
|
||||||
|
private X getValueForDisplay(final double display) {
|
||||||
|
return chart.getXAxis().getValueForDisplay(chart.getXAxis().parentToLocal(display, 0).getX());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* enum to represent whether the drag is a left/right-edge modification or a
|
||||||
|
* horizontal slide triggered by dragging the center
|
||||||
|
*/
|
||||||
|
private enum DragPosition {
|
||||||
|
|
||||||
|
LEFT,
|
||||||
|
CENTER,
|
||||||
|
RIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ZoomToSelectedIntervalAction extends Action {
|
||||||
|
|
||||||
|
@NbBundle.Messages("IntervalSelector.ZoomAction.name=Zoom")
|
||||||
|
ZoomToSelectedIntervalAction() {
|
||||||
|
super(Bundle.IntervalSelector_ZoomAction_name());
|
||||||
|
setGraphic(new ImageView(ZOOM_TO_INTERVAL_ICON));
|
||||||
|
setEventHandler((ActionEvent t) -> {
|
||||||
|
zoomToSelectedInterval();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ClearSelectedIntervalAction extends Action {
|
||||||
|
|
||||||
|
@NbBundle.Messages("IntervalSelector.ClearSelectedIntervalAction.tooltTipText=Clear Selected Interval")
|
||||||
|
ClearSelectedIntervalAction() {
|
||||||
|
super("");
|
||||||
|
setLongText(Bundle.IntervalSelector_ClearSelectedIntervalAction_tooltTipText());
|
||||||
|
setGraphic(new ImageView(ClEAR_INTERVAL_ICON));
|
||||||
|
setEventHandler((ActionEvent t) -> {
|
||||||
|
chart.clearIntervalSelector();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,7 @@
|
|||||||
<?import javafx.scene.image.*?>
|
<?import javafx.scene.image.*?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
|
|
||||||
<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" type="ToolBar" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" type="ToolBar" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<items>
|
<items>
|
||||||
<Label fx:id="refreshLabel">
|
<Label fx:id="refreshLabel">
|
||||||
<graphic>
|
<graphic>
|
||||||
@ -17,6 +17,15 @@
|
|||||||
</graphic>
|
</graphic>
|
||||||
</Label>
|
</Label>
|
||||||
<Separator orientation="VERTICAL" />
|
<Separator orientation="VERTICAL" />
|
||||||
|
<Label fx:id="statusLabel" layoutX="10.0" layoutY="11.0">
|
||||||
|
<graphic>
|
||||||
|
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||||
|
<image>
|
||||||
|
<Image url="@../images/information-gray.png" />
|
||||||
|
</image>
|
||||||
|
</ImageView>
|
||||||
|
</graphic>
|
||||||
|
</Label>
|
||||||
<Region fx:id="spacer" maxWidth="1.7976931348623157E308" />
|
<Region fx:id="spacer" maxWidth="1.7976931348623157E308" />
|
||||||
<Separator orientation="VERTICAL" />
|
<Separator orientation="VERTICAL" />
|
||||||
<Label fx:id="taskLabel" contentDisplay="RIGHT">
|
<Label fx:id="taskLabel" contentDisplay="RIGHT">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2014 Basis Technology Corp.
|
* Copyright 2014-15 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -28,19 +28,21 @@ import javafx.scene.layout.Region;
|
|||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineUI;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* simple status bar that only shows one possible message determined by
|
* simple status bar that only shows one possible message determined by
|
||||||
* {@link TimeLineController#newEventsFlag}
|
* {@link TimeLineController#newEventsFlag}
|
||||||
*/
|
*/
|
||||||
public class StatusBar extends ToolBar implements TimeLineUI {
|
public class StatusBar extends ToolBar {
|
||||||
|
|
||||||
private TimeLineController controller;
|
private TimeLineController controller;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label refreshLabel;
|
private Label refreshLabel;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label statusLabel;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ProgressBar progressBar;
|
private ProgressBar progressBar;
|
||||||
|
|
||||||
@ -53,7 +55,8 @@ public class StatusBar extends ToolBar implements TimeLineUI {
|
|||||||
@FXML
|
@FXML
|
||||||
private Label messageLabel;
|
private Label messageLabel;
|
||||||
|
|
||||||
public StatusBar() {
|
public StatusBar(TimeLineController controller) {
|
||||||
|
this.controller = controller;
|
||||||
FXMLConstructor.construct(this, "StatusBar.fxml"); // NON-NLS
|
FXMLConstructor.construct(this, "StatusBar.fxml"); // NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,15 +73,16 @@ public class StatusBar extends ToolBar implements TimeLineUI {
|
|||||||
taskLabel.setText(NbBundle.getMessage(this.getClass(), "StatusBar.taskLabel.text"));
|
taskLabel.setText(NbBundle.getMessage(this.getClass(), "StatusBar.taskLabel.text"));
|
||||||
taskLabel.setVisible(false);
|
taskLabel.setVisible(false);
|
||||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setController(TimeLineController controller) {
|
|
||||||
this.controller = controller;
|
|
||||||
refreshLabel.visibleProperty().bind(this.controller.getNewEventsFlag());
|
refreshLabel.visibleProperty().bind(this.controller.getNewEventsFlag());
|
||||||
|
refreshLabel.managedProperty().bind(this.controller.getNewEventsFlag());
|
||||||
taskLabel.textProperty().bind(this.controller.getTaskTitle());
|
taskLabel.textProperty().bind(this.controller.getTaskTitle());
|
||||||
messageLabel.textProperty().bind(this.controller.getMessage());
|
messageLabel.textProperty().bind(this.controller.getMessage());
|
||||||
progressBar.progressProperty().bind(this.controller.getProgress());
|
progressBar.progressProperty().bind(this.controller.getProgress());
|
||||||
taskLabel.visibleProperty().bind(this.controller.getTasks().emptyProperty().not());
|
taskLabel.visibleProperty().bind(this.controller.getTasks().emptyProperty().not());
|
||||||
|
|
||||||
|
statusLabel.textProperty().bind(this.controller.getStatusProperty());
|
||||||
|
statusLabel.visibleProperty().bind(statusLabel.textProperty().isNotEmpty());
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2014 Basis Technology Corp.
|
* Copyright 2014-15 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -19,28 +19,27 @@
|
|||||||
package org.sleuthkit.autopsy.timeline.ui;
|
package org.sleuthkit.autopsy.timeline.ui;
|
||||||
|
|
||||||
import javafx.event.EventHandler;
|
import javafx.event.EventHandler;
|
||||||
import javafx.geometry.Point2D;
|
import javafx.event.EventType;
|
||||||
import javafx.scene.Cursor;
|
import javafx.scene.Cursor;
|
||||||
import javafx.scene.chart.Axis;
|
import javafx.scene.chart.Axis;
|
||||||
import javafx.scene.chart.Chart;
|
import javafx.scene.chart.Chart;
|
||||||
import javafx.scene.control.Tooltip;
|
import javafx.scene.control.ContextMenu;
|
||||||
import javafx.scene.input.MouseButton;
|
import javafx.scene.input.MouseButton;
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.scene.paint.Color;
|
import org.controlsfx.control.action.ActionGroup;
|
||||||
import javafx.scene.shape.Rectangle;
|
|
||||||
import org.joda.time.DateTime;
|
|
||||||
import org.joda.time.Interval;
|
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||||
|
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for TimeLineViews that are 'charts'.
|
* Interface for TimeLineViews that are 'charts'.
|
||||||
*
|
*
|
||||||
* @param <X> the type of values along the horizontal axis
|
* @param <X> the type of values along the horizontal axis
|
||||||
*/
|
*/
|
||||||
public interface TimeLineChart<X> extends TimeLineView {
|
public interface TimeLineChart<X> {
|
||||||
|
|
||||||
|
// void setController(TimeLineController controller);
|
||||||
IntervalSelector<? extends X> getIntervalSelector();
|
IntervalSelector<? extends X> getIntervalSelector();
|
||||||
|
|
||||||
void setIntervalSelector(IntervalSelector<? extends X> newIntervalSelector);
|
void setIntervalSelector(IntervalSelector<? extends X> newIntervalSelector);
|
||||||
@ -49,12 +48,9 @@ public interface TimeLineChart<X> extends TimeLineView {
|
|||||||
* derived classes should implement this so as to supply an appropriate
|
* derived classes should implement this so as to supply an appropriate
|
||||||
* subclass of {@link IntervalSelector}
|
* subclass of {@link IntervalSelector}
|
||||||
*
|
*
|
||||||
* @param x the initial x position of the new interval selector
|
|
||||||
* @param axis the axis the new interval selector will be over
|
|
||||||
*
|
|
||||||
* @return a new interval selector
|
* @return a new interval selector
|
||||||
*/
|
*/
|
||||||
IntervalSelector<X> newIntervalSelector(double x, Axis<X> axis);
|
IntervalSelector<X> newIntervalSelector();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* clear any references to previous interval selectors , including removing
|
* clear any references to previous interval selectors , including removing
|
||||||
@ -62,6 +58,14 @@ public interface TimeLineChart<X> extends TimeLineView {
|
|||||||
*/
|
*/
|
||||||
void clearIntervalSelector();
|
void clearIntervalSelector();
|
||||||
|
|
||||||
|
public Axis<X> getXAxis();
|
||||||
|
|
||||||
|
public TimeLineController getController();
|
||||||
|
|
||||||
|
ContextMenu getChartContextMenu();
|
||||||
|
|
||||||
|
ContextMenu getChartContextMenu(MouseEvent m);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* drag handler class used by {@link TimeLineChart}s to create
|
* drag handler class used by {@link TimeLineChart}s to create
|
||||||
* {@link IntervalSelector}s
|
* {@link IntervalSelector}s
|
||||||
@ -69,228 +73,87 @@ public interface TimeLineChart<X> extends TimeLineView {
|
|||||||
* @param <X> the type of values along the horizontal axis
|
* @param <X> the type of values along the horizontal axis
|
||||||
* @param <Y> the type of chart this is a drag handler for
|
* @param <Y> the type of chart this is a drag handler for
|
||||||
*/
|
*/
|
||||||
class ChartDragHandler<X, Y extends Chart & TimeLineChart<X>> implements EventHandler<MouseEvent> {
|
static class ChartDragHandler<X, Y extends Chart & TimeLineChart<X>> implements EventHandler<MouseEvent> {
|
||||||
|
|
||||||
private final Y chart;
|
private final Y chart;
|
||||||
|
|
||||||
private final Axis<X> dateAxis;
|
|
||||||
|
|
||||||
private double startX; //hanlder mainstains position of drag start
|
private double startX; //hanlder mainstains position of drag start
|
||||||
|
|
||||||
public ChartDragHandler(Y chart, Axis<X> dateAxis) {
|
public ChartDragHandler(Y chart) {
|
||||||
this.chart = chart;
|
this.chart = chart;
|
||||||
this.dateAxis = dateAxis;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(MouseEvent t) {
|
public void handle(MouseEvent mouseEvent) {
|
||||||
if (t.getButton() == MouseButton.SECONDARY) {
|
EventType<? extends MouseEvent> mouseEventType = mouseEvent.getEventType();
|
||||||
|
if (mouseEventType == MouseEvent.MOUSE_PRESSED) {
|
||||||
if (t.getEventType() == MouseEvent.MOUSE_PRESSED) {
|
//caputure x-position, incase we are repositioning existing selector
|
||||||
//caputure x-position, incase we are repositioning existing selector
|
startX = mouseEvent.getX();
|
||||||
startX = t.getX();
|
chart.setCursor(Cursor.H_RESIZE);
|
||||||
chart.setCursor(Cursor.E_RESIZE);
|
} else if (mouseEventType == MouseEvent.MOUSE_DRAGGED) {
|
||||||
} else if (t.getEventType() == MouseEvent.MOUSE_DRAGGED) {
|
if (chart.getIntervalSelector() == null) {
|
||||||
if (chart.getIntervalSelector() == null) {
|
//make new interval selector
|
||||||
//make new interval selector
|
chart.setIntervalSelector(chart.newIntervalSelector());
|
||||||
chart.setIntervalSelector(chart.newIntervalSelector(t.getX(), dateAxis));
|
chart.getIntervalSelector().prefHeightProperty().bind(chart.heightProperty());
|
||||||
chart.getIntervalSelector().heightProperty().bind(chart.heightProperty().subtract(dateAxis.heightProperty().subtract(dateAxis.tickLengthProperty())));
|
startX = mouseEvent.getX();
|
||||||
chart.getIntervalSelector().addEventHandler(MouseEvent.MOUSE_CLICKED, (MouseEvent event) -> {
|
chart.getIntervalSelector().relocate(startX, 0);
|
||||||
if (event.getButton() == MouseButton.SECONDARY) {
|
} else {
|
||||||
chart.clearIntervalSelector();
|
//resize/position existing selector
|
||||||
event.consume();
|
if (mouseEvent.getX() > startX) {
|
||||||
}
|
chart.getIntervalSelector().relocate(startX, 0);
|
||||||
});
|
chart.getIntervalSelector().setPrefWidth(mouseEvent.getX() - startX);
|
||||||
startX = t.getX();
|
|
||||||
} else {
|
} else {
|
||||||
//resize/position existing selector
|
chart.getIntervalSelector().relocate(mouseEvent.getX(), 0);
|
||||||
if (t.getX() > startX) {
|
chart.getIntervalSelector().setPrefWidth(startX - mouseEvent.getX());
|
||||||
chart.getIntervalSelector().setX(startX);
|
|
||||||
chart.getIntervalSelector().setWidth(t.getX() - startX);
|
|
||||||
} else {
|
|
||||||
chart.getIntervalSelector().setX(t.getX());
|
|
||||||
chart.getIntervalSelector().setWidth(startX - t.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
|
* enum to represent whether the drag is a left/right-edge modification or a
|
||||||
* interactions with it.
|
* horizontal slide triggered by dragging the center
|
||||||
*
|
|
||||||
* @param <X> the type of values along the x axis this is a selector for
|
|
||||||
*
|
|
||||||
* This abstract class requires concrete implementations to implement hook
|
|
||||||
* methods to handle formating and date 'lookup' of the generic x-axis type
|
|
||||||
*/
|
*/
|
||||||
static abstract class IntervalSelector<X> extends Rectangle {
|
enum DragPosition {
|
||||||
|
|
||||||
private static final double STROKE_WIDTH = 3;
|
LEFT,
|
||||||
|
CENTER,
|
||||||
|
RIGHT
|
||||||
|
}
|
||||||
|
|
||||||
private static final double HALF_STROKE = STROKE_WIDTH / 2;
|
@NbBundle.Messages({"TimeLineChart.zoomHistoryActionGroup.name=Zoom History"})
|
||||||
|
static ActionGroup newZoomHistoyActionGroup(TimeLineController controller) {
|
||||||
/**
|
return new ActionGroup(Bundle.TimeLineChart_zoomHistoryActionGroup_name(),
|
||||||
* the Axis this is a selector over
|
new Back(controller),
|
||||||
*/
|
new Forward(controller));
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,12 +25,11 @@ import javax.swing.SwingUtilities;
|
|||||||
import org.joda.time.format.DateTimeFormatter;
|
import org.joda.time.format.DateTimeFormatter;
|
||||||
import org.openide.nodes.Node;
|
import org.openide.nodes.Node;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
|
||||||
import org.sleuthkit.autopsy.timeline.explorernodes.EventRootNode;
|
|
||||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
|
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
|
||||||
import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
|
import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
|
||||||
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
|
import org.sleuthkit.autopsy.timeline.explorernodes.EventRootNode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Since it was too hard to derive from {@link DataResultPanel}, this class
|
* Since it was too hard to derive from {@link DataResultPanel}, this class
|
||||||
@ -39,16 +38,16 @@ import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
|
|||||||
* {@link DataResultPanel}. That is, this class acts as a sort of bridge/adapter
|
* {@link DataResultPanel}. That is, this class acts as a sort of bridge/adapter
|
||||||
* between a FilteredEventsModel instance and a DataResultPanel instance.
|
* between a FilteredEventsModel instance and a DataResultPanel instance.
|
||||||
*/
|
*/
|
||||||
public class TimeLineResultView implements TimeLineView {
|
public class TimeLineResultView {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the {@link DataResultPanel} that is the real view proxied by this class
|
* the {@link DataResultPanel} that is the real view proxied by this class
|
||||||
*/
|
*/
|
||||||
private final DataResultPanel dataResultPanel;
|
private final DataResultPanel dataResultPanel;
|
||||||
|
|
||||||
private TimeLineController controller;
|
private final TimeLineController controller;
|
||||||
|
|
||||||
private FilteredEventsModel filteredEvents;
|
private final FilteredEventsModel filteredEvents;
|
||||||
|
|
||||||
private Set<Long> selectedEventIDs = new HashSet<>();
|
private Set<Long> selectedEventIDs = new HashSet<>();
|
||||||
|
|
||||||
@ -56,19 +55,11 @@ public class TimeLineResultView implements TimeLineView {
|
|||||||
return dataResultPanel;
|
return dataResultPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeLineResultView(DataContent dataContent) {
|
public TimeLineResultView(TimeLineController controller, DataContent dataContent) {
|
||||||
dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, dataContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the Controller for this class. Also sets the model provided by the
|
|
||||||
* controller as the model for this view.
|
|
||||||
*
|
|
||||||
* @param controller
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void setController(TimeLineController controller) {
|
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
|
this.filteredEvents = controller.getEventsModel();
|
||||||
|
dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, dataContent);
|
||||||
|
|
||||||
//set up listeners on relevant properties
|
//set up listeners on relevant properties
|
||||||
TimeLineController.getTimeZone().addListener((Observable observable) -> {
|
TimeLineController.getTimeZone().addListener((Observable observable) -> {
|
||||||
@ -78,18 +69,7 @@ public class TimeLineResultView implements TimeLineView {
|
|||||||
controller.getSelectedEventIDs().addListener((Observable o) -> {
|
controller.getSelectedEventIDs().addListener((Observable o) -> {
|
||||||
refresh();
|
refresh();
|
||||||
});
|
});
|
||||||
|
refresh();
|
||||||
setModel(controller.getEventsModel());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the Model for this View
|
|
||||||
*
|
|
||||||
* @param filteredEvents
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
synchronized public void setModel(final FilteredEventsModel filteredEvents) {
|
|
||||||
this.filteredEvents = filteredEvents;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,7 +23,10 @@ import java.util.Date;
|
|||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.RadioButton;
|
||||||
|
import javafx.scene.control.TitledPane;
|
||||||
|
import javafx.scene.control.Toggle;
|
||||||
|
import javafx.scene.control.ToggleGroup;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
@ -50,8 +53,9 @@ public class TimeZonePanel extends TitledPane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
|
@NbBundle.Messages({"TimeZonePanel.title=Display Times In:"})
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
|
setText(Bundle.TimeZonePanel_title());
|
||||||
// localRadio.setText("Local Time Zone: " + getTimeZoneString(TimeZone.getDefault()));
|
// localRadio.setText("Local Time Zone: " + getTimeZoneString(TimeZone.getDefault()));
|
||||||
localRadio.setText(NbBundle.getMessage(this.getClass(), "TimeZonePanel.localRadio.text"));
|
localRadio.setText(NbBundle.getMessage(this.getClass(), "TimeZonePanel.localRadio.text"));
|
||||||
otherRadio.setText(NbBundle.getMessage(this.getClass(), "TimeZonePanel.otherRadio.text"));
|
otherRadio.setText(NbBundle.getMessage(this.getClass(), "TimeZonePanel.otherRadio.text"));
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<?import jfxtras.scene.control.*?>
|
<?import jfxtras.scene.control.*?>
|
||||||
<?import org.controlsfx.control.*?>
|
<?import org.controlsfx.control.*?>
|
||||||
|
|
||||||
<fx:root prefHeight="-1.0" prefWidth="-1.0" type="javafx.scene.layout.BorderPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
<fx:root prefHeight="-1.0" prefWidth="-1.0" type="javafx.scene.layout.BorderPane" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<top>
|
<top>
|
||||||
<ToolBar fx:id="toolBar" prefWidth="200.0" BorderPane.alignment="CENTER">
|
<ToolBar fx:id="toolBar" prefWidth="200.0" BorderPane.alignment="CENTER">
|
||||||
<items>
|
<items>
|
||||||
@ -115,7 +115,7 @@
|
|||||||
</Separator>
|
</Separator>
|
||||||
<HBox>
|
<HBox>
|
||||||
<children>
|
<children>
|
||||||
<Button fx:id="zoomOutButton" mnemonicParsing="false">
|
<Button fx:id="zoomOutButton" contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false">
|
||||||
<graphic>
|
<graphic>
|
||||||
<ImageView pickOnBounds="true" preserveRatio="true">
|
<ImageView pickOnBounds="true" preserveRatio="true">
|
||||||
<image>
|
<image>
|
||||||
@ -127,7 +127,7 @@
|
|||||||
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
|
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
|
||||||
</HBox.margin>
|
</HBox.margin>
|
||||||
</Button>
|
</Button>
|
||||||
<Button fx:id="zoomInButton" mnemonicParsing="false">
|
<Button fx:id="zoomInButton" contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false">
|
||||||
<graphic>
|
<graphic>
|
||||||
<ImageView pickOnBounds="true" preserveRatio="true">
|
<ImageView pickOnBounds="true" preserveRatio="true">
|
||||||
<image>
|
<image>
|
||||||
@ -143,7 +143,7 @@
|
|||||||
</HBox>
|
</HBox>
|
||||||
<MenuButton fx:id="zoomMenuButton" mnemonicParsing="false">
|
<MenuButton fx:id="zoomMenuButton" mnemonicParsing="false">
|
||||||
<graphic>
|
<graphic>
|
||||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
<ImageView pickOnBounds="true" preserveRatio="true">
|
||||||
<image>
|
<image>
|
||||||
<Image url="@../images/magnifier-left.png" />
|
<Image url="@../images/magnifier-left.png" />
|
||||||
</image>
|
</image>
|
||||||
|
@ -33,8 +33,6 @@ import javafx.beans.value.ObservableValue;
|
|||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Rectangle2D;
|
|
||||||
import javafx.scene.SnapshotParameters;
|
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.MenuButton;
|
import javafx.scene.control.MenuButton;
|
||||||
@ -48,7 +46,6 @@ import javafx.scene.control.Tooltip;
|
|||||||
import javafx.scene.effect.Lighting;
|
import javafx.scene.effect.Lighting;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.image.WritableImage;
|
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.scene.layout.Background;
|
import javafx.scene.layout.Background;
|
||||||
import javafx.scene.layout.BackgroundFill;
|
import javafx.scene.layout.BackgroundFill;
|
||||||
@ -66,6 +63,7 @@ import jfxtras.scene.control.LocalDateTimeTextField;
|
|||||||
import org.controlsfx.control.NotificationPane;
|
import org.controlsfx.control.NotificationPane;
|
||||||
import org.controlsfx.control.RangeSlider;
|
import org.controlsfx.control.RangeSlider;
|
||||||
import org.controlsfx.control.action.Action;
|
import org.controlsfx.control.action.Action;
|
||||||
|
import org.controlsfx.control.action.ActionUtils;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.joda.time.DateTimeZone;
|
import org.joda.time.DateTimeZone;
|
||||||
import org.joda.time.Interval;
|
import org.joda.time.Interval;
|
||||||
@ -74,11 +72,12 @@ import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
|||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
|
||||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
|
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.SaveSnapshot;
|
import org.sleuthkit.autopsy.timeline.actions.SaveSnapshotAsReport;
|
||||||
|
import org.sleuthkit.autopsy.timeline.actions.ZoomIn;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.ZoomOut;
|
import org.sleuthkit.autopsy.timeline.actions.ZoomOut;
|
||||||
|
import org.sleuthkit.autopsy.timeline.actions.ZoomToEvents;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.events.TagsUpdatedEvent;
|
import org.sleuthkit.autopsy.timeline.events.TagsUpdatedEvent;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.TagsFilter;
|
import org.sleuthkit.autopsy.timeline.filters.TagsFilter;
|
||||||
@ -86,18 +85,18 @@ import static org.sleuthkit.autopsy.timeline.ui.Bundle.VisualizationPanel_refres
|
|||||||
import static org.sleuthkit.autopsy.timeline.ui.Bundle.VisualizationPanel_tagsAddedOrDeleted;
|
import static org.sleuthkit.autopsy.timeline.ui.Bundle.VisualizationPanel_tagsAddedOrDeleted;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane;
|
import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
|
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.NavPanel;
|
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
|
||||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Container for an {@link AbstractVisualization}, has a toolbar on top to
|
* A container for an {@link AbstractVisualizationPane}, has a toolbar on top to
|
||||||
* hold settings widgets supplied by contained {@link AbstractVisualization},
|
* hold settings widgets supplied by contained {@link AbstAbstractVisualization}
|
||||||
* and the histogram / timeselection on bottom. Also supplies containers for
|
* and the histogram / timeselection on bottom. Also supplies containers for
|
||||||
* replacement axis to contained {@link AbstractVisualization}
|
* replacement axis to contained {@link AbstractAbstractVisualization}
|
||||||
*
|
*
|
||||||
* TODO: refactor common code out of histogram and CountsView? -jm
|
* TODO: refactor common code out of histogram and CountsView? -jm
|
||||||
*/
|
*/
|
||||||
public class VisualizationPanel extends BorderPane implements TimeLineView {
|
final public class VisualizationPanel extends BorderPane {
|
||||||
|
|
||||||
private static final Image INFORMATION = new Image("org/sleuthkit/autopsy/timeline/images/information.png", 16, 16, true, true); // NON-NLS
|
private static final Image INFORMATION = new Image("org/sleuthkit/autopsy/timeline/images/information.png", 16, 16, true, true); // NON-NLS
|
||||||
private static final Image REFRESH = new Image("org/sleuthkit/autopsy/timeline/images/arrow-circle-double-135.png"); // NON-NLS
|
private static final Image REFRESH = new Image("org/sleuthkit/autopsy/timeline/images/arrow-circle-double-135.png"); // NON-NLS
|
||||||
@ -107,9 +106,9 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
private LoggedTask<Void> histogramTask;
|
private LoggedTask<Void> histogramTask;
|
||||||
|
|
||||||
private final NavPanel navPanel;
|
private final EventsTree eventsTree;
|
||||||
|
|
||||||
private AbstractVisualization<?, ?, ?, ?> visualization;
|
private AbstractVisualizationPane<?, ?, ?, ?> visualization;
|
||||||
|
|
||||||
//// range slider and histogram componenets
|
//// range slider and histogram componenets
|
||||||
@FXML
|
@FXML
|
||||||
@ -178,8 +177,8 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
|
|
||||||
private FilteredEventsModel filteredEvents;
|
private FilteredEventsModel filteredEvents;
|
||||||
|
|
||||||
private final ChangeListener<Object> rangeSliderListener
|
private final ChangeListener<Object> rangeSliderListener =
|
||||||
= (observable1, oldValue, newValue) -> {
|
(observable1, oldValue, newValue) -> {
|
||||||
if (rangeSlider.isHighValueChanging() == false && rangeSlider.isLowValueChanging() == false) {
|
if (rangeSlider.isHighValueChanging() == false && rangeSlider.isLowValueChanging() == false) {
|
||||||
Long minTime = filteredEvents.getMinTime() * 1000;
|
Long minTime = filteredEvents.getMinTime() * 1000;
|
||||||
controller.pushTimeRange(new Interval(
|
controller.pushTimeRange(new Interval(
|
||||||
@ -207,14 +206,15 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
|
|
||||||
static private final Lighting lighting = new Lighting();
|
static private final Lighting lighting = new Lighting();
|
||||||
|
|
||||||
public VisualizationPanel(NavPanel navPanel) {
|
public VisualizationPanel(TimeLineController controller, EventsTree eventsTree) {
|
||||||
this.navPanel = navPanel;
|
this.controller = controller;
|
||||||
|
this.eventsTree = eventsTree;
|
||||||
FXMLConstructor.construct(this, "VisualizationPanel.fxml"); // NON-NLS
|
FXMLConstructor.construct(this, "VisualizationPanel.fxml"); // NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML // This method is called by the FXMLLoader when initialization is complete
|
@FXML // This method is called by the FXMLLoader when initialization is complete
|
||||||
@NbBundle.Messages("VisualizationPanel.refresh=refresh")
|
@NbBundle.Messages("VisualizationPanel.refresh=refresh")
|
||||||
protected void initialize() {
|
void initialize() {
|
||||||
assert endPicker != null : "fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
|
assert endPicker != null : "fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
|
||||||
assert histogramBox != null : "fx:id=\"histogramBox\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
|
assert histogramBox != null : "fx:id=\"histogramBox\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
|
||||||
assert startPicker != null : "fx:id=\"startPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
|
assert startPicker != null : "fx:id=\"startPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
|
||||||
@ -289,32 +289,30 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
}
|
}
|
||||||
zoomMenuButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomMenuButton.text")); // NON-NLS
|
zoomMenuButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomMenuButton.text")); // NON-NLS
|
||||||
|
|
||||||
zoomOutButton.setOnAction(e -> {
|
snapShotButton.setOnAction(event ->
|
||||||
controller.pushZoomOutTime();
|
this.snapshot(snapShotResult -> {
|
||||||
});
|
new SaveSnapshotAsReport(controller, snapShotResult.getImage()).handle(event);
|
||||||
zoomInButton.setOnAction(e -> {
|
return null;
|
||||||
controller.pushZoomInTime();
|
}, null, null)
|
||||||
});
|
);
|
||||||
|
|
||||||
snapShotButton.setOnAction((ActionEvent event) -> {
|
|
||||||
//take snapshot
|
|
||||||
final SnapshotParameters snapshotParameters = new SnapshotParameters();
|
|
||||||
snapshotParameters.setViewport(new Rectangle2D(visualization.getBoundsInParent().getMinX(), visualization.getBoundsInParent().getMinY(),
|
|
||||||
visualization.getBoundsInParent().getWidth(),
|
|
||||||
contextPane.getLayoutBounds().getHeight() + visualization.getLayoutBounds().getHeight() + partPane.getLayoutBounds().getHeight()
|
|
||||||
));
|
|
||||||
WritableImage snapshot = this.snapshot(snapshotParameters, null);
|
|
||||||
//pass snapshot to save action
|
|
||||||
new SaveSnapshot(controller, snapshot).handle(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
snapShotButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapShotButton.text")); // NON-NLS
|
snapShotButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapShotButton.text")); // NON-NLS
|
||||||
}
|
if (this.filteredEvents != null && this.filteredEvents != controller.getEventsModel()) {
|
||||||
|
this.filteredEvents.unRegisterForEvents(this);
|
||||||
|
this.filteredEvents.timeRangeProperty().removeListener(timeRangeInvalidationListener);
|
||||||
|
this.filteredEvents.zoomParametersProperty().removeListener(zoomListener);
|
||||||
|
}
|
||||||
|
if (this.filteredEvents != controller.getEventsModel()) {
|
||||||
|
controller.getEventsModel().registerForEvents(this);
|
||||||
|
controller.getEventsModel().timeRangeProperty().addListener(timeRangeInvalidationListener);
|
||||||
|
controller.getEventsModel().zoomParametersProperty().addListener(zoomListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
setViewMode(controller.viewModeProperty().get());
|
||||||
controller.getNeedsHistogramRebuild().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
|
controller.getNeedsHistogramRebuild().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
@ -329,39 +327,20 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
refreshHistorgram();
|
refreshHistorgram();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setModel(FilteredEventsModel filteredEvents) {
|
|
||||||
if (this.filteredEvents != null && this.filteredEvents != filteredEvents) {
|
|
||||||
this.filteredEvents.unRegisterForEvents(this);
|
|
||||||
this.filteredEvents.timeRangeProperty().removeListener(timeRangeInvalidationListener);
|
|
||||||
this.filteredEvents.zoomParametersProperty().removeListener(zoomListener);
|
|
||||||
}
|
|
||||||
if (this.filteredEvents != filteredEvents) {
|
|
||||||
filteredEvents.registerForEvents(this);
|
|
||||||
filteredEvents.timeRangeProperty().addListener(timeRangeInvalidationListener);
|
|
||||||
filteredEvents.zoomParametersProperty().addListener(zoomListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.filteredEvents = filteredEvents;
|
|
||||||
|
|
||||||
refreshTimeUI(filteredEvents.timeRangeProperty().get());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setViewMode(VisualizationMode visualizationMode) {
|
private void setViewMode(VisualizationMode visualizationMode) {
|
||||||
switch (visualizationMode) {
|
switch (visualizationMode) {
|
||||||
case COUNTS:
|
case COUNTS:
|
||||||
setVisualization(new CountsViewPane(partPane, contextPane, spacer));
|
setVisualization(new CountsViewPane(controller, partPane, contextPane, spacer));
|
||||||
countsToggle.setSelected(true);
|
countsToggle.setSelected(true);
|
||||||
break;
|
break;
|
||||||
case DETAIL:
|
case DETAIL:
|
||||||
setVisualization(new DetailViewPane(partPane, contextPane, spacer));
|
setVisualization(new DetailViewPane(controller, partPane, contextPane, spacer));
|
||||||
detailsToggle.setSelected(true);
|
detailsToggle.setSelected(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void setVisualization(final AbstractVisualization<?, ?, ?, ?> newViz) {
|
private synchronized void setVisualization(final AbstractVisualizationPane<?, ?, ?, ?> newViz) {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
synchronized (VisualizationPanel.this) {
|
synchronized (VisualizationPanel.this) {
|
||||||
if (visualization != null) {
|
if (visualization != null) {
|
||||||
@ -370,24 +349,25 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
visualization = newViz;
|
visualization = newViz;
|
||||||
|
visualization.update();
|
||||||
toolBar.getItems().addAll(newViz.getSettingsNodes());
|
toolBar.getItems().addAll(newViz.getSettingsNodes());
|
||||||
|
|
||||||
visualization.setController(controller);
|
|
||||||
notificationPane.setContent(visualization);
|
notificationPane.setContent(visualization);
|
||||||
if (visualization instanceof DetailViewPane) {
|
if (visualization instanceof DetailViewPane) {
|
||||||
navPanel.setDetailViewPane((DetailViewPane) visualization);
|
eventsTree.setDetailViewPane((DetailViewPane) visualization);
|
||||||
}
|
}
|
||||||
visualization.hasEvents.addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
|
visualization.hasEvents.addListener((observable, oldValue, newValue) -> {
|
||||||
if (newValue == false) {
|
if (newValue == false) {
|
||||||
|
|
||||||
notificationPane.setContent(new StackPane(visualization, new Region() {
|
notificationPane.setContent(
|
||||||
{
|
new StackPane(visualization,
|
||||||
setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)));
|
new Region() {
|
||||||
setOpacity(.3);
|
{
|
||||||
}
|
setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||||
}, new NoEventsDialog(() -> {
|
setOpacity(.3);
|
||||||
notificationPane.setContent(visualization);
|
}
|
||||||
})));
|
},
|
||||||
|
new NoEventsDialog(() -> notificationPane.setContent(visualization))));
|
||||||
} else {
|
} else {
|
||||||
notificationPane.setContent(visualization);
|
notificationPane.setContent(visualization);
|
||||||
}
|
}
|
||||||
@ -552,7 +532,6 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
private NoEventsDialog(Runnable closeCallback) {
|
private NoEventsDialog(Runnable closeCallback) {
|
||||||
this.closeCallback = closeCallback;
|
this.closeCallback = closeCallback;
|
||||||
FXMLConstructor.construct(this, "NoEventsDialog.fxml"); // NON-NLS
|
FXMLConstructor.construct(this, "NoEventsDialog.fxml"); // NON-NLS
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@ -562,15 +541,9 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
|
assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
|
||||||
|
|
||||||
noEventsDialogLabel.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.noEventsDialogLabel.text")); // NON-NLS
|
noEventsDialogLabel.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.noEventsDialogLabel.text")); // NON-NLS
|
||||||
zoomButton.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.zoomButton.text")); // NON-NLS
|
ActionUtils.configureButton(new ZoomToEvents(controller), zoomButton);
|
||||||
|
|
||||||
Action zoomOutAction = new ZoomOut(controller);
|
dismissButton.setOnAction(actionEvent -> closeCallback.run());
|
||||||
zoomButton.setOnAction(zoomOutAction);
|
|
||||||
zoomButton.disableProperty().bind(zoomOutAction.disabledProperty());
|
|
||||||
|
|
||||||
dismissButton.setOnAction(e -> {
|
|
||||||
closeCallback.run();
|
|
||||||
});
|
|
||||||
Action defaultFiltersAction = new ResetFilters(controller);
|
Action defaultFiltersAction = new ResetFilters(controller);
|
||||||
resetFiltersButton.setOnAction(defaultFiltersAction);
|
resetFiltersButton.setOnAction(defaultFiltersAction);
|
||||||
resetFiltersButton.disableProperty().bind(defaultFiltersAction.disabledProperty());
|
resetFiltersButton.disableProperty().bind(defaultFiltersAction.disabledProperty());
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013 Basis Technology Corp.
|
* Copyright 2013-15 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -26,14 +26,12 @@ Timeline.ui.countsview.menuItem.selectTimeRange=Select Time Range
|
|||||||
Timeline.ui.countsview.menuItem.selectEventType=Select Event Type
|
Timeline.ui.countsview.menuItem.selectEventType=Select Event Type
|
||||||
Timeline.ui.countsview.menuItem.selectTimeandType=Select Time and Type
|
Timeline.ui.countsview.menuItem.selectTimeandType=Select Time and Type
|
||||||
Timeline.ui.countsview.menuItem.zoomIntoTimeRange=Zoom into Time Range
|
Timeline.ui.countsview.menuItem.zoomIntoTimeRange=Zoom into Time Range
|
||||||
Timeline.ui.countsview.contextMenu.ActionGroup.zoomHistory.title=Zoom History
|
|
||||||
CountsViewPane.loggedTask.name=Updating Counts Graph
|
CountsViewPane.loggedTask.name=Updating Counts Graph
|
||||||
CountsViewPane.loggedTask.prepUpdate=preparing update
|
CountsViewPane.loggedTask.prepUpdate=preparing update
|
||||||
CountsViewPane.loggedTask.resetUI=resetting ui
|
CountsViewPane.loggedTask.resetUI=resetting ui
|
||||||
CountsViewPane.tooltip.text={0} {1} events\nbetween {2}\nand {3}
|
CountsViewPane.tooltip.text={0} {1} events\nbetween {2}\nand {3}
|
||||||
CountsViewPane.loggedTask.updatingCounts=updating counts
|
CountsViewPane.loggedTask.updatingCounts=updating counts
|
||||||
CountsViewPane.loggedTask.wrappingUp=wrapping up
|
CountsViewPane.loggedTask.wrappingUp=wrapping up
|
||||||
EventCountsChart.contextMenu.zoomHistory.name=Zoom History
|
|
||||||
CountsViewPane.scaleLabel.text=Scale\:
|
CountsViewPane.scaleLabel.text=Scale\:
|
||||||
CountsViewPane.logRadio.text=Logarithmic
|
CountsViewPane.logRadio.text=Logarithmic
|
||||||
CountsViewPane.linearRadio.text=Linear
|
CountsViewPane.linearRadio.text=Linear
|
||||||
|
@ -66,14 +66,13 @@ import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
|||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
|
||||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization;
|
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,7 +96,7 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
|||||||
* TODO: refactor common code out of this class and ClusterChartPane into
|
* TODO: refactor common code out of this class and ClusterChartPane into
|
||||||
* AbstractChartView
|
* AbstractChartView
|
||||||
*/
|
*/
|
||||||
public class CountsViewPane extends AbstractVisualization<String, Number, Node, EventCountsChart> {
|
public class CountsViewPane extends AbstractVisualizationPane<String, Number, Node, EventCountsChart> {
|
||||||
|
|
||||||
private static final Effect SELECTED_NODE_EFFECT = new Lighting();
|
private static final Effect SELECTED_NODE_EFFECT = new Lighting();
|
||||||
|
|
||||||
@ -213,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.setStyle("-fx-border-width: 2; -fx-border-color: " + ColorUtilities.getRGBCode(et.getSuperType().getColor()) + "; -fx-bar-fill: " + ColorUtilities.getRGBCode(et.getColor())); // NON-NLS
|
||||||
node.setCursor(Cursor.HAND);
|
node.setCursor(Cursor.HAND);
|
||||||
|
|
||||||
|
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) -> {
|
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.setEffect(new DropShadow(10, et.getColor()));
|
||||||
});
|
});
|
||||||
node.setOnMouseExited((MouseEvent event) -> {
|
node.setOnMouseExited((MouseEvent event) -> {
|
||||||
@ -279,13 +278,15 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public CountsViewPane(Pane partPane, Pane contextPane, Region spacer) {
|
public CountsViewPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) {
|
||||||
super(partPane, contextPane, spacer);
|
super(controller, partPane, contextPane, spacer);
|
||||||
chart = new EventCountsChart(dateAxis, countAxis);
|
chart = new EventCountsChart(controller, dateAxis, countAxis);
|
||||||
setChartClickHandler();
|
setChartClickHandler();
|
||||||
chart.setData(dataSets);
|
chart.setData(dataSets);
|
||||||
setCenter(chart);
|
setCenter(chart);
|
||||||
|
|
||||||
|
Tooltip.install(chart, getDragTooltip());
|
||||||
|
|
||||||
settingsNodes = new ArrayList<>(new CountsViewSettingsPane().getChildrenUnmodifiable());
|
settingsNodes = new ArrayList<>(new CountsViewSettingsPane().getChildrenUnmodifiable());
|
||||||
|
|
||||||
dateAxis.getTickMarks().addListener((Observable observable) -> {
|
dateAxis.getTickMarks().addListener((Observable observable) -> {
|
||||||
@ -310,9 +311,6 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected NumberAxis getYAxis() {
|
protected NumberAxis getYAxis() {
|
||||||
return countAxis;
|
return countAxis;
|
||||||
@ -534,7 +532,7 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
|
|||||||
scaleLabel.setText(NbBundle.getMessage(this.getClass(), "CountsViewPane.scaleLabel.text"));
|
scaleLabel.setText(NbBundle.getMessage(this.getClass(), "CountsViewPane.scaleLabel.text"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public CountsViewSettingsPane() {
|
CountsViewSettingsPane() {
|
||||||
FXMLConstructor.construct(this, "CountsViewSettingsPane.fxml"); // NON-NLS
|
FXMLConstructor.construct(this, "CountsViewSettingsPane.fxml"); // NON-NLS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2014 Basis Technology Corp.
|
* Copyright 2014-15 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -19,24 +19,19 @@
|
|||||||
package org.sleuthkit.autopsy.timeline.ui.countsview;
|
package org.sleuthkit.autopsy.timeline.ui.countsview;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.MissingResourceException;
|
||||||
import javafx.scene.chart.Axis;
|
|
||||||
import javafx.scene.chart.CategoryAxis;
|
import javafx.scene.chart.CategoryAxis;
|
||||||
import javafx.scene.chart.NumberAxis;
|
import javafx.scene.chart.NumberAxis;
|
||||||
import javafx.scene.chart.StackedBarChart;
|
import javafx.scene.chart.StackedBarChart;
|
||||||
import javafx.scene.control.ContextMenu;
|
import javafx.scene.control.ContextMenu;
|
||||||
import javafx.scene.input.MouseButton;
|
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
import org.controlsfx.control.action.ActionGroup;
|
|
||||||
import org.controlsfx.control.action.ActionUtils;
|
import org.controlsfx.control.action.ActionUtils;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.joda.time.Interval;
|
import org.joda.time.Interval;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
import org.sleuthkit.autopsy.timeline.ui.IntervalSelector;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
|
||||||
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
|
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
|
||||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||||
|
|
||||||
@ -44,11 +39,16 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
|||||||
* Customized {@link StackedBarChart<String, Number>} used to display the event
|
* Customized {@link StackedBarChart<String, Number>} used to display the event
|
||||||
* counts in {@link CountsViewPane}
|
* counts in {@link CountsViewPane}
|
||||||
*/
|
*/
|
||||||
class EventCountsChart extends StackedBarChart<String, Number> implements TimeLineChart<String> {
|
final class EventCountsChart extends StackedBarChart<String, Number> implements TimeLineChart<String> {
|
||||||
|
|
||||||
private ContextMenu contextMenu;
|
private ContextMenu chartContextMenu;
|
||||||
|
|
||||||
private TimeLineController controller;
|
@Override
|
||||||
|
public ContextMenu getChartContextMenu() {
|
||||||
|
return chartContextMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final TimeLineController controller;
|
||||||
|
|
||||||
private IntervalSelector<? extends String> intervalSelector;
|
private IntervalSelector<? extends String> intervalSelector;
|
||||||
|
|
||||||
@ -59,8 +59,9 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
|||||||
*/
|
*/
|
||||||
private RangeDivisionInfo rangeInfo;
|
private RangeDivisionInfo rangeInfo;
|
||||||
|
|
||||||
EventCountsChart(CategoryAxis dateAxis, NumberAxis countAxis) {
|
EventCountsChart(TimeLineController controller, CategoryAxis dateAxis, NumberAxis countAxis) {
|
||||||
super(dateAxis, countAxis);
|
super(dateAxis, countAxis);
|
||||||
|
this.controller = controller;
|
||||||
//configure constant properties on axes and chart
|
//configure constant properties on axes and chart
|
||||||
dateAxis.setAnimated(true);
|
dateAxis.setAnimated(true);
|
||||||
dateAxis.setLabel(null);
|
dateAxis.setLabel(null);
|
||||||
@ -80,19 +81,13 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
|||||||
setAnimated(true);
|
setAnimated(true);
|
||||||
setTitle(null);
|
setTitle(null);
|
||||||
|
|
||||||
//use one handler with an if chain because it maintains state
|
ChartDragHandler<String, EventCountsChart> chartDragHandler = new ChartDragHandler<>(this);
|
||||||
ChartDragHandler<String, EventCountsChart> dragHandler = new ChartDragHandler<>(this, getXAxis());
|
setOnMousePressed(chartDragHandler);
|
||||||
setOnMousePressed(dragHandler);
|
setOnMouseReleased(chartDragHandler);
|
||||||
setOnMouseReleased(dragHandler);
|
setOnMouseDragged(chartDragHandler);
|
||||||
setOnMouseDragged(dragHandler);
|
|
||||||
|
setOnMouseClicked(new MouseClickedHandler<>(this));
|
||||||
|
|
||||||
setOnMouseClicked((MouseEvent clickEvent) -> {
|
|
||||||
contextMenu.hide();
|
|
||||||
if (clickEvent.getButton() == MouseButton.SECONDARY && clickEvent.isStillSincePress()) {
|
|
||||||
contextMenu.show(EventCountsChart.this, clickEvent.getScreenX(), clickEvent.getScreenY());
|
|
||||||
clickEvent.consume();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -102,16 +97,21 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final synchronized void setController(TimeLineController controller) {
|
public ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException {
|
||||||
this.controller = controller;
|
if (chartContextMenu != null) {
|
||||||
setModel(this.controller.getEventsModel());
|
chartContextMenu.hide();
|
||||||
//we have defered creating context menu until control is available
|
}
|
||||||
contextMenu = ActionUtils.createContextMenu(
|
|
||||||
Arrays.asList(new ActionGroup(
|
chartContextMenu = ActionUtils.createContextMenu(
|
||||||
NbBundle.getMessage(this.getClass(), "EventCountsChart.contextMenu.zoomHistory.name"),
|
Arrays.asList(
|
||||||
new Back(controller),
|
TimeLineChart.newZoomHistoyActionGroup(controller)));
|
||||||
new Forward(controller))));
|
chartContextMenu.setAutoHide(true);
|
||||||
contextMenu.setAutoHide(true);
|
return chartContextMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TimeLineController getController() {
|
||||||
|
return controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -126,16 +126,8 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setModel(FilteredEventsModel filteredEvents) {
|
public CountsIntervalSelector newIntervalSelector() {
|
||||||
filteredEvents.zoomParametersProperty().addListener(o -> {
|
return new CountsIntervalSelector(this);
|
||||||
clearIntervalSelector();
|
|
||||||
controller.selectEventIDs(Collections.emptyList());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CountsIntervalSelector newIntervalSelector(double x, Axis<String> dateAxis) {
|
|
||||||
return new CountsIntervalSelector(x, getHeight() - dateAxis.getHeight() - dateAxis.getTickLength(), dateAxis, controller);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -145,7 +137,7 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
|||||||
* @return the context menu for this chart
|
* @return the context menu for this chart
|
||||||
*/
|
*/
|
||||||
ContextMenu getContextMenu() {
|
ContextMenu getContextMenu() {
|
||||||
return contextMenu;
|
return chartContextMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setRangeInfo(RangeDivisionInfo rangeInfo) {
|
void setRangeInfo(RangeDivisionInfo rangeInfo) {
|
||||||
@ -175,10 +167,13 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
|||||||
* Interval Selector for the counts chart, adjusts interval based on
|
* Interval Selector for the counts chart, adjusts interval based on
|
||||||
* rangeInfo to include final period
|
* rangeInfo to include final period
|
||||||
*/
|
*/
|
||||||
private class CountsIntervalSelector extends IntervalSelector<String> {
|
final static private class CountsIntervalSelector extends IntervalSelector<String> {
|
||||||
|
|
||||||
public CountsIntervalSelector(double x, double height, Axis<String> axis, TimeLineController controller) {
|
private final EventCountsChart countsChart;
|
||||||
super(x, height, axis, controller);
|
|
||||||
|
CountsIntervalSelector(EventCountsChart chart) {
|
||||||
|
super(chart);
|
||||||
|
this.countsChart = chart;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -195,12 +190,13 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
|||||||
final DateTime lowerDate = new DateTime(lowerBound, TimeLineController.getJodaTimeZone());
|
final DateTime lowerDate = new DateTime(lowerBound, TimeLineController.getJodaTimeZone());
|
||||||
final DateTime upperDate = new DateTime(upperBound, TimeLineController.getJodaTimeZone());
|
final DateTime upperDate = new DateTime(upperBound, TimeLineController.getJodaTimeZone());
|
||||||
//add extra block to end that gets cut of by conversion from string/category.
|
//add extra block to end that gets cut of by conversion from string/category.
|
||||||
return new Interval(lowerDate, upperDate.plus(rangeInfo.getPeriodSize().getPeriod()));
|
return new Interval(lowerDate, upperDate.plus(countsChart.rangeInfo.getPeriodSize().getPeriod()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DateTime parseDateTime(String date) {
|
protected DateTime parseDateTime(String date) {
|
||||||
return date == null ? new DateTime(rangeInfo.getLowerBound()) : rangeInfo.getTickFormatter().parseDateTime(date);
|
return date == null ? new DateTime(countsChart.rangeInfo.getLowerBound()) : countsChart.rangeInfo.getTickFormatter().parseDateTime(date);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -73,24 +73,23 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
|
|||||||
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
|
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization;
|
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller class for a {@link EventDetailChart} based implementation of a
|
* Controller class for a {@link EventDetailsChart} based implementation of a
|
||||||
* TimeLineView.
|
* TimeLineView.
|
||||||
*
|
*
|
||||||
* This class listens to changes in the assigned {@link FilteredEventsModel} and
|
* This class listens to changes in the assigned {@link FilteredEventsModel} and
|
||||||
* updates the internal {@link EventDetailChart} to reflect the currently
|
* updates the internal {@link EventDetailsChart} to reflect the currently
|
||||||
* requested events.
|
* requested events.
|
||||||
*
|
*
|
||||||
* Concurrency Policy: Access to the private members clusterChart, dateAxis,
|
* Concurrency Policy: Access to the private members clusterChart, dateAxis,
|
||||||
* EventTypeMap, and dataSets is all linked directly to the ClusterChart which
|
* EventTypeMap, and dataSets is all linked directly to the ClusterChart which
|
||||||
* must only be manipulated on the JavaFx thread.
|
* must only be manipulated on the JavaFx thread.
|
||||||
*/
|
*/
|
||||||
public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster, EventBundleNodeBase<?, ?, ?>, EventDetailChart> {
|
public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventCluster, EventBundleNodeBase<?, ?, ?>, EventDetailsChart> {
|
||||||
|
|
||||||
|
|
||||||
private final static Logger LOGGER = Logger.getLogger(DetailViewPane.class.getName());
|
private final static Logger LOGGER = Logger.getLogger(DetailViewPane.class.getName());
|
||||||
|
|
||||||
@ -114,13 +113,15 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
|
|||||||
return chart.getEventBundles();
|
return chart.getEventBundles();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DetailViewPane(Pane partPane, Pane contextPane, Region spacer) {
|
|
||||||
super(partPane, contextPane, spacer);
|
public DetailViewPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) {
|
||||||
chart = new EventDetailChart(dateAxis, verticalAxis, selectedNodes);
|
super(controller, partPane, contextPane, spacer);
|
||||||
|
chart = new EventDetailsChart(controller, dateAxis, verticalAxis, selectedNodes);
|
||||||
setChartClickHandler();
|
setChartClickHandler();
|
||||||
chart.setData(dataSets);
|
chart.setData(dataSets);
|
||||||
setCenter(chart);
|
setCenter(chart);
|
||||||
|
|
||||||
|
|
||||||
chart.setPrefHeight(USE_COMPUTED_SIZE);
|
chart.setPrefHeight(USE_COMPUTED_SIZE);
|
||||||
|
|
||||||
settingsNodes = new ArrayList<>(new DetailViewSettingsPane().getChildrenUnmodifiable());
|
settingsNodes = new ArrayList<>(new DetailViewSettingsPane().getChildrenUnmodifiable());
|
||||||
@ -211,11 +212,6 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void setModel(FilteredEventsModel filteredEvents) {
|
|
||||||
super.setModel(filteredEvents);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void incrementScrollValue(int factor) {
|
private void incrementScrollValue(int factor) {
|
||||||
vertScrollBar.valueProperty().set(Math.max(0, Math.min(100, vertScrollBar.getValue() + factor * (chart.getHeight() / chart.maxVScrollProperty().get()))));
|
vertScrollBar.valueProperty().set(Math.max(0, Math.min(100, vertScrollBar.getValue() + factor * (chart.getHeight() / chart.maxVScrollProperty().get()))));
|
||||||
}
|
}
|
||||||
@ -477,5 +473,4 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
|
|||||||
return chart.new HideDescriptionAction(description, descriptionLoD);
|
return chart.new HideDescriptionAction(description, descriptionLoD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
|
|||||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
|
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||||
|
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||||
import static org.sleuthkit.autopsy.timeline.ui.detailview.EventBundleNodeBase.show;
|
import static org.sleuthkit.autopsy.timeline.ui.detailview.EventBundleNodeBase.show;
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
||||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||||
@ -96,7 +97,7 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
|||||||
b.setManaged(show);
|
b.setManaged(show);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final EventDetailChart chart;
|
protected final EventDetailsChart chart;
|
||||||
final SimpleObjectProperty<DescriptionLoD> descLOD = new SimpleObjectProperty<>();
|
final SimpleObjectProperty<DescriptionLoD> descLOD = new SimpleObjectProperty<>();
|
||||||
final SimpleObjectProperty<DescriptionVisibility> descVisibility = new SimpleObjectProperty<>(DescriptionVisibility.SHOWN);
|
final SimpleObjectProperty<DescriptionVisibility> descVisibility = new SimpleObjectProperty<>(DescriptionVisibility.SHOWN);
|
||||||
protected final BundleType eventBundle;
|
protected final BundleType eventBundle;
|
||||||
@ -119,9 +120,9 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
|||||||
final ImageView tagIV = new ImageView(TAG);
|
final ImageView tagIV = new ImageView(TAG);
|
||||||
final HBox infoHBox = new HBox(5, descrLabel, countLabel, hashIV, tagIV);
|
final HBox infoHBox = new HBox(5, descrLabel, countLabel, hashIV, tagIV);
|
||||||
|
|
||||||
private Tooltip tooltip;
|
private final Tooltip tooltip = new Tooltip("loading...");
|
||||||
|
|
||||||
public EventBundleNodeBase(EventDetailChart chart, BundleType eventBundle, ParentNodeType parentNode) {
|
public EventBundleNodeBase(EventDetailsChart chart, BundleType eventBundle, ParentNodeType parentNode) {
|
||||||
this.eventBundle = eventBundle;
|
this.eventBundle = eventBundle;
|
||||||
this.parentNode = parentNode;
|
this.parentNode = parentNode;
|
||||||
this.chart = chart;
|
this.chart = chart;
|
||||||
@ -156,7 +157,6 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
|||||||
infoHBox.setMaxWidth(USE_PREF_SIZE);
|
infoHBox.setMaxWidth(USE_PREF_SIZE);
|
||||||
infoHBox.setPadding(new Insets(2, 5, 2, 5));
|
infoHBox.setPadding(new Insets(2, 5, 2, 5));
|
||||||
infoHBox.setAlignment(Pos.TOP_LEFT);
|
infoHBox.setAlignment(Pos.TOP_LEFT);
|
||||||
infoHBox.setPickOnBounds(true);
|
|
||||||
|
|
||||||
//set up subnode pane sizing contraints
|
//set up subnode pane sizing contraints
|
||||||
subNodePane.setPrefHeight(USE_COMPUTED_SIZE);
|
subNodePane.setPrefHeight(USE_COMPUTED_SIZE);
|
||||||
@ -165,27 +165,34 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
|||||||
subNodePane.setMinWidth(USE_PREF_SIZE);
|
subNodePane.setMinWidth(USE_PREF_SIZE);
|
||||||
subNodePane.setMaxWidth(USE_PREF_SIZE);
|
subNodePane.setMaxWidth(USE_PREF_SIZE);
|
||||||
|
|
||||||
|
Tooltip.install(this, this.tooltip);
|
||||||
|
|
||||||
//set up mouse hover effect and tooltip
|
//set up mouse hover effect and tooltip
|
||||||
setOnMouseEntered((MouseEvent e) -> {
|
setOnMouseEntered((MouseEvent e) -> {
|
||||||
/*
|
/*
|
||||||
* defer tooltip creation till needed, this had a surprisingly large
|
* defer tooltip content creation till needed, this had a
|
||||||
* impact on speed of loading the chart
|
* surprisingly large impact on speed of loading the chart
|
||||||
*/
|
*/
|
||||||
installTooltip();
|
installTooltip();
|
||||||
|
Tooltip.uninstall(chart, AbstractVisualizationPane.getDragTooltip());
|
||||||
showHoverControls(true);
|
showHoverControls(true);
|
||||||
toFront();
|
toFront();
|
||||||
|
|
||||||
});
|
});
|
||||||
setOnMouseExited((MouseEvent event) -> {
|
setOnMouseExited((MouseEvent event) -> {
|
||||||
showHoverControls(false);
|
showHoverControls(false);
|
||||||
if (parentNode != null) {
|
if (parentNode != null) {
|
||||||
parentNode.showHoverControls(true);
|
parentNode.showHoverControls(true);
|
||||||
|
} else {
|
||||||
|
Tooltip.install(chart, AbstractVisualizationPane.getDragTooltip());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setDescriptionVisibility(DescriptionVisibility.SHOWN);
|
|
||||||
descVisibility.addListener((ObservableValue<? extends DescriptionVisibility> observable, DescriptionVisibility oldValue, DescriptionVisibility newValue) -> {
|
descVisibility.addListener((ObservableValue<? extends DescriptionVisibility> observable, DescriptionVisibility oldValue, DescriptionVisibility newValue) -> {
|
||||||
setDescriptionVisibility(newValue);
|
setDescriptionVisibiltiyImpl(newValue);
|
||||||
});
|
});
|
||||||
|
setDescriptionVisibiltiyImpl(DescriptionVisibility.SHOWN);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final DescriptionLoD getDescriptionLoD() {
|
final DescriptionLoD getDescriptionLoD() {
|
||||||
@ -210,8 +217,11 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
|||||||
"EventBundleNodeBase.tooltip.text={0} {1} events\n{2}\nbetween\t{3}\nand \t{4}"})
|
"EventBundleNodeBase.tooltip.text={0} {1} events\n{2}\nbetween\t{3}\nand \t{4}"})
|
||||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||||
private void installTooltip() {
|
private void installTooltip() {
|
||||||
if (tooltip == null) {
|
if (tooltip.getText().equalsIgnoreCase("loading...")) {
|
||||||
final Task<String> tooltTipTask = new Task<String>() {
|
final Task<String> tooltTipTask = new Task<String>() {
|
||||||
|
{
|
||||||
|
updateTitle("loading tooltip");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String call() throws Exception {
|
protected String call() throws Exception {
|
||||||
@ -252,13 +262,10 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
|||||||
protected void succeeded() {
|
protected void succeeded() {
|
||||||
super.succeeded();
|
super.succeeded();
|
||||||
try {
|
try {
|
||||||
tooltip = new Tooltip(get());
|
tooltip.setText(get());
|
||||||
tooltip.setAutoHide(true);
|
tooltip.setGraphic(null);
|
||||||
Tooltip.install(EventBundleNodeBase.this, tooltip);
|
|
||||||
} catch (InterruptedException | ExecutionException ex) {
|
} catch (InterruptedException | ExecutionException ex) {
|
||||||
LOGGER.log(Level.SEVERE, "Tooltip generation failed.", ex);
|
LOGGER.log(Level.SEVERE, "Tooltip generation failed.", ex);
|
||||||
Tooltip.uninstall(EventBundleNodeBase.this, tooltip);
|
|
||||||
tooltip = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -288,15 +295,18 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
|||||||
return subNodes;
|
return subNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract void setDescriptionVisibility(DescriptionVisibility get);
|
abstract void setDescriptionVisibiltiyImpl(DescriptionVisibility get);
|
||||||
|
|
||||||
void showHoverControls(final boolean showControls) {
|
void showHoverControls(final boolean showControls) {
|
||||||
Effect dropShadow = dropShadowMap.computeIfAbsent(getEventType(),
|
Effect dropShadow = dropShadowMap.computeIfAbsent(getEventType(),
|
||||||
eventType -> new DropShadow(-10, eventType.getColor()));
|
eventType -> new DropShadow(-10, eventType.getColor()));
|
||||||
setEffect(showControls ? dropShadow : null);
|
setEffect(showControls ? dropShadow : null);
|
||||||
|
enableTooltip(showControls);
|
||||||
if (parentNode != null) {
|
if (parentNode != null) {
|
||||||
|
parentNode.enableTooltip(false);
|
||||||
parentNode.showHoverControls(false);
|
parentNode.showHoverControls(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final EventType getEventType() {
|
final EventType getEventType() {
|
||||||
@ -330,8 +340,15 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
|||||||
*/
|
*/
|
||||||
abstract void setDescriptionWidth(double w);
|
abstract void setDescriptionWidth(double w);
|
||||||
|
|
||||||
void setDescriptionVisibilityLevel(DescriptionVisibility get) {
|
void setDescriptionVisibility(DescriptionVisibility get) {
|
||||||
descVisibility.set(get);
|
descVisibility.set(get);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void enableTooltip(boolean toolTipEnabled) {
|
||||||
|
if (toolTipEnabled) {
|
||||||
|
Tooltip.install(this, tooltip);
|
||||||
|
} else {
|
||||||
|
Tooltip.uninstall(this, tooltip);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
|
|||||||
final Button plusButton = ActionUtils.createButton(new ExpandClusterAction(), ActionUtils.ActionTextBehavior.HIDE);
|
final Button plusButton = ActionUtils.createButton(new ExpandClusterAction(), ActionUtils.ActionTextBehavior.HIDE);
|
||||||
final Button minusButton = ActionUtils.createButton(new CollapseClusterAction(), ActionUtils.ActionTextBehavior.HIDE);
|
final Button minusButton = ActionUtils.createButton(new CollapseClusterAction(), ActionUtils.ActionTextBehavior.HIDE);
|
||||||
|
|
||||||
public EventClusterNode(EventDetailChart chart, EventCluster eventCluster, EventStripeNode parentNode) {
|
public EventClusterNode(EventDetailsChart chart, EventCluster eventCluster, EventStripeNode parentNode) {
|
||||||
super(chart, eventCluster, parentNode);
|
super(chart, eventCluster, parentNode);
|
||||||
setMinHeight(24);
|
setMinHeight(24);
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setDescriptionVisibility(DescriptionVisibility descrVis) {
|
void setDescriptionVisibiltiyImpl(DescriptionVisibility descrVis) {
|
||||||
final int size = getEventBundle().getEventIDs().size();
|
final int size = getEventBundle().getEventIDs().size();
|
||||||
switch (descrVis) {
|
switch (descrVis) {
|
||||||
case HIDDEN:
|
case HIDDEN:
|
||||||
|
@ -53,6 +53,7 @@ import javafx.scene.chart.Axis;
|
|||||||
import javafx.scene.chart.NumberAxis;
|
import javafx.scene.chart.NumberAxis;
|
||||||
import javafx.scene.chart.XYChart;
|
import javafx.scene.chart.XYChart;
|
||||||
import javafx.scene.control.ContextMenu;
|
import javafx.scene.control.ContextMenu;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.input.MouseButton;
|
import javafx.scene.input.MouseButton;
|
||||||
@ -62,14 +63,11 @@ import javafx.scene.shape.StrokeLineCap;
|
|||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||||
import org.controlsfx.control.action.Action;
|
import org.controlsfx.control.action.Action;
|
||||||
import org.controlsfx.control.action.ActionGroup;
|
|
||||||
import org.controlsfx.control.action.ActionUtils;
|
import org.controlsfx.control.action.ActionUtils;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.joda.time.Interval;
|
import org.joda.time.Interval;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
|
import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
|
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
|
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
|
||||||
@ -77,6 +75,8 @@ import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
|||||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
|
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||||
|
import org.sleuthkit.autopsy.timeline.ui.IntervalSelector;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
|
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
|||||||
*
|
*
|
||||||
* //TODO: refactor the projected lines to a separate class. -jm
|
* //TODO: refactor the projected lines to a separate class. -jm
|
||||||
*/
|
*/
|
||||||
public final class EventDetailChart extends XYChart<DateTime, EventCluster> implements TimeLineChart<DateTime> {
|
public final class EventDetailsChart extends XYChart<DateTime, EventCluster> implements TimeLineChart<DateTime> {
|
||||||
|
|
||||||
private static final Image HIDE = new Image("/org/sleuthkit/autopsy/timeline/images/eye--minus.png"); // NON-NLS
|
private static final Image HIDE = new Image("/org/sleuthkit/autopsy/timeline/images/eye--minus.png"); // NON-NLS
|
||||||
private static final Image SHOW = new Image("/org/sleuthkit/autopsy/timeline/images/eye--plus.png"); // NON-NLS
|
private static final Image SHOW = new Image("/org/sleuthkit/autopsy/timeline/images/eye--plus.png"); // NON-NLS
|
||||||
@ -102,11 +102,18 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
private static final int PROJECTED_LINE_Y_OFFSET = 5;
|
private static final int PROJECTED_LINE_Y_OFFSET = 5;
|
||||||
private static final int PROJECTED_LINE_STROKE_WIDTH = 5;
|
private static final int PROJECTED_LINE_STROKE_WIDTH = 5;
|
||||||
private static final int MINIMUM_EVENT_NODE_GAP = 4;
|
private static final int MINIMUM_EVENT_NODE_GAP = 4;
|
||||||
|
|
||||||
|
|
||||||
|
private final TimeLineController controller;
|
||||||
|
private final FilteredEventsModel filteredEvents;
|
||||||
|
|
||||||
private ContextMenu chartContextMenu;
|
private ContextMenu chartContextMenu;
|
||||||
|
|
||||||
private TimeLineController controller;
|
|
||||||
|
|
||||||
private FilteredEventsModel filteredEvents;
|
|
||||||
|
public ContextMenu getChartContextMenu() {
|
||||||
|
return chartContextMenu;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* a user positionable vertical line to help compare events
|
* a user positionable vertical line to help compare events
|
||||||
@ -183,8 +190,21 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
*/
|
*/
|
||||||
final SimpleDoubleProperty truncateWidth = new SimpleDoubleProperty(200.0);
|
final SimpleDoubleProperty truncateWidth = new SimpleDoubleProperty(200.0);
|
||||||
|
|
||||||
EventDetailChart(DateAxis dateAxis, final Axis<EventCluster> verticalAxis, ObservableList<EventBundleNodeBase<?, ?, ?>> selectedNodes) {
|
EventDetailsChart(TimeLineController controller, DateAxis dateAxis, final Axis<EventCluster> verticalAxis, ObservableList<EventBundleNodeBase<?, ?, ?>> selectedNodes) {
|
||||||
super(dateAxis, verticalAxis);
|
super(dateAxis, verticalAxis);
|
||||||
|
this.controller = controller;
|
||||||
|
this.filteredEvents = this.controller.getEventsModel();
|
||||||
|
|
||||||
|
filteredEvents.zoomParametersProperty().addListener(o -> {
|
||||||
|
clearGuideLine();
|
||||||
|
clearIntervalSelector();
|
||||||
|
selectedNodes.clear();
|
||||||
|
projectionMap.clear();
|
||||||
|
controller.selectEventIDs(Collections.emptyList());
|
||||||
|
});
|
||||||
|
Tooltip.install(this, AbstractVisualizationPane.getDragTooltip());
|
||||||
|
|
||||||
|
|
||||||
dateAxis.setAutoRanging(false);
|
dateAxis.setAutoRanging(false);
|
||||||
|
|
||||||
verticalAxis.setVisible(false);//TODO: why doesn't this hide the vertical axis, instead we have to turn off all parts individually? -jm
|
verticalAxis.setVisible(false);//TODO: why doesn't this hide the vertical axis, instead we have to turn off all parts individually? -jm
|
||||||
@ -204,6 +224,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
truncateAll.addListener(layoutInvalidationListener);
|
truncateAll.addListener(layoutInvalidationListener);
|
||||||
truncateWidth.addListener(layoutInvalidationListener);
|
truncateWidth.addListener(layoutInvalidationListener);
|
||||||
descrVisibility.addListener(layoutInvalidationListener);
|
descrVisibility.addListener(layoutInvalidationListener);
|
||||||
|
getController().getQuickHideFilters().addListener(layoutInvalidationListener);
|
||||||
|
|
||||||
//this is needed to allow non circular binding of the guideline and timerangeRect heights to the height of the chart
|
//this is needed to allow non circular binding of the guideline and timerangeRect heights to the height of the chart
|
||||||
//TODO: seems like a hack, can we remove? -jm
|
//TODO: seems like a hack, can we remove? -jm
|
||||||
@ -211,22 +232,12 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
setPrefHeight(boundsInLocalProperty().get().getHeight());
|
setPrefHeight(boundsInLocalProperty().get().getHeight());
|
||||||
});
|
});
|
||||||
|
|
||||||
///////set up mouse listeners
|
ChartDragHandler<DateTime, EventDetailsChart> chartDragHandler = new ChartDragHandler<>(this);
|
||||||
setOnMouseClicked((MouseEvent clickEvent) -> {
|
setOnMousePressed(chartDragHandler);
|
||||||
if (chartContextMenu != null) {
|
setOnMouseReleased(chartDragHandler);
|
||||||
chartContextMenu.hide();
|
setOnMouseDragged(chartDragHandler);
|
||||||
}
|
|
||||||
if (clickEvent.getButton() == MouseButton.SECONDARY && clickEvent.isStillSincePress()) {
|
setOnMouseClicked(new MouseClickedHandler<>(this));
|
||||||
getChartContextMenu(clickEvent);
|
|
||||||
chartContextMenu.show(EventDetailChart.this, clickEvent.getScreenX(), clickEvent.getScreenY());
|
|
||||||
clickEvent.consume();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//use one handler with an if chain because it maintains state
|
|
||||||
final ChartDragHandler<DateTime, EventDetailChart> dragHandler = new ChartDragHandler<>(this, getXAxis());
|
|
||||||
setOnMousePressed(dragHandler);
|
|
||||||
setOnMouseReleased(dragHandler);
|
|
||||||
setOnMouseDragged(dragHandler);
|
|
||||||
|
|
||||||
this.selectedNodes = selectedNodes;
|
this.selectedNodes = selectedNodes;
|
||||||
this.selectedNodes.addListener(new SelectionChangeHandler());
|
this.selectedNodes.addListener(new SelectionChangeHandler());
|
||||||
@ -236,21 +247,20 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
return bundles;
|
return bundles;
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeLineController getController() {
|
@Override
|
||||||
|
public TimeLineController getController() {
|
||||||
return controller;
|
return controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NbBundle.Messages({"EventDetailChart.chartContextMenu.placeMarker.name=Place Marker",
|
@Override
|
||||||
"EventDetailChart.contextMenu.zoomHistory.name=Zoom History"})
|
public ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException {
|
||||||
ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException {
|
|
||||||
if (chartContextMenu != null) {
|
if (chartContextMenu != null) {
|
||||||
chartContextMenu.hide();
|
chartContextMenu.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
chartContextMenu = ActionUtils.createContextMenu(Arrays.asList(new PlaceMarkerAction(clickEvent),
|
chartContextMenu = ActionUtils.createContextMenu(Arrays.asList(new PlaceMarkerAction(clickEvent),
|
||||||
new ActionGroup(Bundle.EventDetailChart_contextMenu_zoomHistory_name(),
|
// new StartIntervalSelectionAction(clickEvent, dragHandler),
|
||||||
new Back(controller),
|
TimeLineChart.newZoomHistoyActionGroup(controller)));
|
||||||
new Forward(controller))));
|
|
||||||
chartContextMenu.setAutoHide(true);
|
chartContextMenu.setAutoHide(true);
|
||||||
return chartContextMenu;
|
return chartContextMenu;
|
||||||
}
|
}
|
||||||
@ -266,32 +276,8 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void setController(TimeLineController controller) {
|
public IntervalSelector<DateTime> newIntervalSelector() {
|
||||||
this.controller = controller;
|
return new DetailIntervalSelector(this);
|
||||||
setModel(this.controller.getEventsModel());
|
|
||||||
getController().getQuickHideFilters().addListener(layoutInvalidationListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setModel(FilteredEventsModel filteredEvents) {
|
|
||||||
|
|
||||||
if (this.filteredEvents != filteredEvents) {
|
|
||||||
filteredEvents.zoomParametersProperty().addListener(o -> {
|
|
||||||
clearGuideLine();
|
|
||||||
clearIntervalSelector();
|
|
||||||
|
|
||||||
selectedNodes.clear();
|
|
||||||
projectionMap.clear();
|
|
||||||
controller.selectEventIDs(Collections.emptyList());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.filteredEvents = filteredEvents;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IntervalSelector<DateTime> newIntervalSelector(double x, Axis<DateTime> axis) {
|
|
||||||
return new DetailIntervalSelector(x, getHeight() - axis.getHeight() - axis.getTickLength(), axis, controller);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void setBandByType(Boolean t1) {
|
synchronized void setBandByType(Boolean t1) {
|
||||||
@ -300,12 +286,12 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* get the DateTime along the x-axis that corresponds to the given
|
* get the DateTime along the x-axis that corresponds to the given
|
||||||
* x-coordinate in the coordinate system of this {@link EventDetailChart}
|
* x-coordinate in the coordinate system of this {@link EventDetailsChart}
|
||||||
*
|
*
|
||||||
* @param x a x-coordinate in the space of this {@link EventDetailChart}
|
* @param x a x-coordinate in the space of this {@link EventDetailsChart}
|
||||||
*
|
*
|
||||||
* @return the DateTime along the x-axis corresponding to the given x value
|
* @return the DateTime along the x-axis corresponding to the given x value
|
||||||
* (in the space of this {@link EventDetailChart}
|
* (in the space of this {@link EventDetailsChart}
|
||||||
*/
|
*/
|
||||||
public DateTime getDateTimeForPosition(double x) {
|
public DateTime getDateTimeForPosition(double x) {
|
||||||
return getXAxis().getValueForDisplay(getXAxis().parentToLocal(x, 0).getX());
|
return getXAxis().getValueForDisplay(getXAxis().parentToLocal(x, 0).getX());
|
||||||
@ -352,7 +338,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
return EventStripe.merge(u, v);
|
return EventStripe.merge(u, v);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
EventStripeNode stripeNode = new EventStripeNode(EventDetailChart.this, eventStripe, null);
|
EventStripeNode stripeNode = new EventStripeNode(EventDetailsChart.this, eventStripe, null);
|
||||||
stripeNodeMap.put(eventStripe, stripeNode);
|
stripeNodeMap.put(eventStripe, stripeNode);
|
||||||
nodeGroup.getChildren().add(stripeNode);
|
nodeGroup.getChildren().add(stripeNode);
|
||||||
data.setNode(stripeNode);
|
data.setNode(stripeNode);
|
||||||
@ -498,7 +484,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
bundleNode.setVisible(true);
|
bundleNode.setVisible(true);
|
||||||
bundleNode.setManaged(true);
|
bundleNode.setManaged(true);
|
||||||
//apply advanced layout description visibility options
|
//apply advanced layout description visibility options
|
||||||
bundleNode.setDescriptionVisibilityLevel(descrVisibility.get());
|
bundleNode.setDescriptionVisibility(descrVisibility.get());
|
||||||
bundleNode.setDescriptionWidth(truncateAll.get() ? truncateWidth.get() : USE_PREF_SIZE);
|
bundleNode.setDescriptionWidth(truncateAll.get() ? truncateWidth.get() : USE_PREF_SIZE);
|
||||||
|
|
||||||
//do recursive layout
|
//do recursive layout
|
||||||
@ -586,8 +572,8 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
|
|
||||||
static private class DetailIntervalSelector extends IntervalSelector<DateTime> {
|
static private class DetailIntervalSelector extends IntervalSelector<DateTime> {
|
||||||
|
|
||||||
DetailIntervalSelector(double x, double height, Axis<DateTime> axis, TimeLineController controller) {
|
DetailIntervalSelector(EventDetailsChart chart) {
|
||||||
super(x, height, axis, controller);
|
super(chart);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -608,6 +594,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
|
|
||||||
private class PlaceMarkerAction extends Action {
|
private class PlaceMarkerAction extends Action {
|
||||||
|
|
||||||
|
@NbBundle.Messages({"EventDetailChart.chartContextMenu.placeMarker.name=Place Marker"})
|
||||||
PlaceMarkerAction(MouseEvent clickEvent) {
|
PlaceMarkerAction(MouseEvent clickEvent) {
|
||||||
super(Bundle.EventDetailChart_chartContextMenu_placeMarker_name());
|
super(Bundle.EventDetailChart_chartContextMenu_placeMarker_name());
|
||||||
|
|
||||||
@ -664,7 +651,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
EventDetailChart.this.controller.selectEventIDs(selectedNodes.stream()
|
EventDetailsChart.this.controller.selectEventIDs(selectedNodes.stream()
|
||||||
.flatMap(detailNode -> detailNode.getEventIDs().stream())
|
.flatMap(detailNode -> detailNode.getEventIDs().stream())
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
@ -708,4 +695,5 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -37,7 +37,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
|
|||||||
import static org.sleuthkit.autopsy.timeline.ui.detailview.EventBundleNodeBase.configureLoDButton;
|
import static org.sleuthkit.autopsy.timeline.ui.detailview.EventBundleNodeBase.configureLoDButton;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Node used in {@link EventDetailChart} to represent an EventStripe.
|
* Node used in {@link EventDetailsChart} to represent an EventStripe.
|
||||||
*/
|
*/
|
||||||
final public class EventStripeNode extends EventBundleNodeBase<EventStripe, EventCluster, EventClusterNode> {
|
final public class EventStripeNode extends EventBundleNodeBase<EventStripe, EventCluster, EventClusterNode> {
|
||||||
|
|
||||||
@ -53,12 +53,12 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
|
|||||||
// private final HBox clustersHBox = new HBox();
|
// private final HBox clustersHBox = new HBox();
|
||||||
private final ImageView eventTypeImageView = new ImageView();
|
private final ImageView eventTypeImageView = new ImageView();
|
||||||
|
|
||||||
public EventStripeNode(EventDetailChart chart, EventStripe eventStripe, EventClusterNode parentNode) {
|
public EventStripeNode(EventDetailsChart chart, EventStripe eventStripe, EventClusterNode parentNode) {
|
||||||
super(chart, eventStripe, parentNode);
|
super(chart, eventStripe, parentNode);
|
||||||
|
|
||||||
setMinHeight(48);
|
setMinHeight(48);
|
||||||
|
|
||||||
EventDetailChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD());
|
EventDetailsChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD());
|
||||||
hideButton = ActionUtils.createButton(hideClusterAction, ActionUtils.ActionTextBehavior.HIDE);
|
hideButton = ActionUtils.createButton(hideClusterAction, ActionUtils.ActionTextBehavior.HIDE);
|
||||||
configureLoDButton(hideButton);
|
configureLoDButton(hideButton);
|
||||||
|
|
||||||
@ -115,7 +115,7 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setDescriptionVisibility(DescriptionVisibility descrVis) {
|
void setDescriptionVisibiltiyImpl(DescriptionVisibility descrVis) {
|
||||||
final int size = getEventStripe().getEventIDs().size();
|
final int size = getEventStripe().getEventIDs().size();
|
||||||
|
|
||||||
switch (descrVis) {
|
switch (descrVis) {
|
||||||
@ -167,7 +167,7 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
|
|||||||
contextMenu = new ContextMenu();
|
contextMenu = new ContextMenu();
|
||||||
contextMenu.setAutoHide(true);
|
contextMenu.setAutoHide(true);
|
||||||
|
|
||||||
EventDetailChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD());
|
EventDetailsChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD());
|
||||||
MenuItem hideDescriptionMenuItem = ActionUtils.createMenuItem(hideClusterAction);
|
MenuItem hideDescriptionMenuItem = ActionUtils.createMenuItem(hideClusterAction);
|
||||||
contextMenu.getItems().addAll(hideDescriptionMenuItem);
|
contextMenu.getItems().addAll(hideDescriptionMenuItem);
|
||||||
contextMenu.getItems().addAll(chartContextMenu.getItems());
|
contextMenu.getItems().addAll(chartContextMenu.getItems());
|
||||||
|
@ -1 +0,0 @@
|
|||||||
NavPanel.eventsTreeLabel.text=Sort By\:
|
|
@ -1 +1 @@
|
|||||||
NavPanel.eventsTreeLabel.text=\u4E0B\u8A18\u306B\u5F93\u3044\u4E26\u3079\u66FF\u3048\uFF1A
|
EventsTree.Label.text=\u4e0b\u8a18\u306b\u5f93\u3044\u4e26\u3079\u66ff\u3048\uff1a
|
@ -46,9 +46,7 @@ import org.openide.util.NbBundle;
|
|||||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
|
import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
|
||||||
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
|
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
|
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
|
||||||
@ -59,11 +57,9 @@ import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
|
|||||||
* out. Right clicking on a item in the tree shows a context menu to show/hide
|
* out. Right clicking on a item in the tree shows a context menu to show/hide
|
||||||
* it.
|
* it.
|
||||||
*/
|
*/
|
||||||
public class NavPanel extends BorderPane implements TimeLineView {
|
final public class EventsTree extends BorderPane {
|
||||||
|
|
||||||
private TimeLineController controller;
|
private final TimeLineController controller;
|
||||||
|
|
||||||
private FilteredEventsModel filteredEvents;
|
|
||||||
|
|
||||||
private DetailViewPane detailViewPane;
|
private DetailViewPane detailViewPane;
|
||||||
|
|
||||||
@ -76,8 +72,10 @@ public class NavPanel extends BorderPane implements TimeLineView {
|
|||||||
@FXML
|
@FXML
|
||||||
private ComboBox<Comparator<TreeItem<EventBundle<?>>>> sortByBox;
|
private ComboBox<Comparator<TreeItem<EventBundle<?>>>> sortByBox;
|
||||||
|
|
||||||
public NavPanel() {
|
public EventsTree(TimeLineController controller) {
|
||||||
FXMLConstructor.construct(this, "NavPanel.fxml"); // NON-NLS
|
this.controller = controller;
|
||||||
|
|
||||||
|
FXMLConstructor.construct(this, "EventsTree.fxml"); // NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDetailViewPane(DetailViewPane detailViewPane) {
|
public void setDetailViewPane(DetailViewPane detailViewPane) {
|
||||||
@ -112,19 +110,8 @@ public class NavPanel extends BorderPane implements TimeLineView {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setController(TimeLineController controller) {
|
|
||||||
this.controller = controller;
|
|
||||||
setModel(controller.getEventsModel());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setModel(FilteredEventsModel filteredEvents) {
|
|
||||||
this.filteredEvents = filteredEvents;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
|
@NbBundle.Messages("EventsTree.Label.text=Sort By:")
|
||||||
void initialize() {
|
void initialize() {
|
||||||
assert sortByBox != null : "fx:id=\"sortByBox\" was not injected: check your FXML file 'NavPanel.fxml'."; // NON-NLS
|
assert sortByBox != null : "fx:id=\"sortByBox\" was not injected: check your FXML file 'NavPanel.fxml'."; // NON-NLS
|
||||||
|
|
||||||
@ -137,7 +124,7 @@ public class NavPanel extends BorderPane implements TimeLineView {
|
|||||||
eventsTree.setCellFactory((TreeView<EventBundle<?>> p) -> new EventBundleTreeCell());
|
eventsTree.setCellFactory((TreeView<EventBundle<?>> p) -> new EventBundleTreeCell());
|
||||||
eventsTree.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
eventsTree.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||||
|
|
||||||
eventsTreeLabel.setText(NbBundle.getMessage(this.getClass(), "NavPanel.eventsTreeLabel.text"));
|
eventsTreeLabel.setText(Bundle.EventsTree_Label_text());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -232,6 +219,5 @@ public class NavPanel extends BorderPane implements TimeLineView {
|
|||||||
setContextMenu(null);
|
setContextMenu(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -44,7 +44,6 @@ import org.controlsfx.control.action.ActionUtils;
|
|||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
|
||||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
|
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
@ -64,7 +63,7 @@ import static org.sleuthkit.autopsy.timeline.ui.filtering.Bundle.Timeline_ui_fil
|
|||||||
* This also implements {@link TimeLineView} since it dynamically updates its
|
* This also implements {@link TimeLineView} since it dynamically updates its
|
||||||
* filters based on the contents of a {@link FilteredEventsModel}
|
* filters based on the contents of a {@link FilteredEventsModel}
|
||||||
*/
|
*/
|
||||||
final public class FilterSetPanel extends BorderPane implements TimeLineView {
|
final public class FilterSetPanel extends BorderPane {
|
||||||
|
|
||||||
private static final Image TICK = new Image("org/sleuthkit/autopsy/timeline/images/tick.png");
|
private static final Image TICK = new Image("org/sleuthkit/autopsy/timeline/images/tick.png");
|
||||||
|
|
||||||
@ -171,20 +170,25 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView {
|
|||||||
legendColumn.setCellValueFactory(param -> param.getValue().valueProperty());
|
legendColumn.setCellValueFactory(param -> param.getValue().valueProperty());
|
||||||
legendColumn.setCellFactory(col -> new LegendCell(this.controller));
|
legendColumn.setCellFactory(col -> new LegendCell(this.controller));
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public FilterSetPanel() {
|
|
||||||
FXMLConstructor.construct(this, "FilterSetPanel.fxml"); // NON-NLS
|
|
||||||
expansionMap.put(new TypeFilter(RootEventType.getInstance()).getDisplayName(), true);
|
expansionMap.put(new TypeFilter(RootEventType.getInstance()).getDisplayName(), true);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setController(TimeLineController timeLineController) {
|
|
||||||
this.controller = timeLineController;
|
|
||||||
Action defaultFiltersAction = new ResetFilters(controller);
|
Action defaultFiltersAction = new ResetFilters(controller);
|
||||||
defaultButton.setOnAction(defaultFiltersAction);
|
defaultButton.setOnAction(defaultFiltersAction);
|
||||||
defaultButton.disableProperty().bind(defaultFiltersAction.disabledProperty());
|
defaultButton.disableProperty().bind(defaultFiltersAction.disabledProperty());
|
||||||
this.setModel(timeLineController.getEventsModel());
|
|
||||||
|
this.filteredEvents.eventTypeZoomProperty().addListener((Observable observable) -> {
|
||||||
|
applyFilters();
|
||||||
|
});
|
||||||
|
this.filteredEvents.descriptionLODProperty().addListener((Observable observable1) -> {
|
||||||
|
applyFilters();
|
||||||
|
});
|
||||||
|
this.filteredEvents.timeRangeProperty().addListener((Observable observable2) -> {
|
||||||
|
applyFilters();
|
||||||
|
});
|
||||||
|
this.filteredEvents.filterProperty().addListener((Observable o) -> {
|
||||||
|
refresh();
|
||||||
|
});
|
||||||
|
refresh();
|
||||||
|
|
||||||
hiddenDescriptionsListView.setItems(controller.getQuickHideFilters());
|
hiddenDescriptionsListView.setItems(controller.getQuickHideFilters());
|
||||||
hiddenDescriptionsListView.setCellFactory((ListView<DescriptionFilter> param) -> {
|
hiddenDescriptionsListView.setCellFactory((ListView<DescriptionFilter> param) -> {
|
||||||
@ -237,25 +241,13 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView {
|
|||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public FilterSetPanel(TimeLineController controller) {
|
||||||
|
this.controller = controller;
|
||||||
public void setModel(FilteredEventsModel filteredEvents) {
|
this.filteredEvents = controller.getEventsModel();
|
||||||
this.filteredEvents = filteredEvents;
|
FXMLConstructor.construct(this, "FilterSetPanel.fxml"); // NON-NLS
|
||||||
this.filteredEvents.eventTypeZoomProperty().addListener((Observable observable) -> {
|
|
||||||
applyFilters();
|
|
||||||
});
|
|
||||||
this.filteredEvents.descriptionLODProperty().addListener((Observable observable) -> {
|
|
||||||
applyFilters();
|
|
||||||
});
|
|
||||||
this.filteredEvents.timeRangeProperty().addListener((Observable observable) -> {
|
|
||||||
applyFilters();
|
|
||||||
});
|
|
||||||
this.filteredEvents.filterProperty().addListener((Observable o) -> {
|
|
||||||
refresh();
|
|
||||||
});
|
|
||||||
refresh();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,6 @@ import javafx.scene.paint.Color;
|
|||||||
import javafx.scene.shape.Rectangle;
|
import javafx.scene.shape.Rectangle;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
|
||||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
|
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
|
||||||
@ -40,18 +39,19 @@ import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
|
|||||||
* A TreeTableCell that shows an icon and color corresponding to the represented
|
* A TreeTableCell that shows an icon and color corresponding to the represented
|
||||||
* filter
|
* filter
|
||||||
*/
|
*/
|
||||||
class LegendCell extends TreeTableCell<AbstractFilter, AbstractFilter> implements TimeLineView {
|
final class LegendCell extends TreeTableCell<AbstractFilter, AbstractFilter> {
|
||||||
|
|
||||||
private static final Color CLEAR = Color.rgb(0, 0, 0, 0);
|
private static final Color CLEAR = Color.rgb(0, 0, 0, 0);
|
||||||
|
|
||||||
private TimeLineController controller;
|
private final TimeLineController controller;
|
||||||
|
|
||||||
private FilteredEventsModel filteredEvents;
|
private final FilteredEventsModel filteredEvents;
|
||||||
|
|
||||||
//We need a controller so we can listen to changes in EventTypeZoom to show/hide legends
|
//We need a controller so we can listen to changes in EventTypeZoom to show/hide legends
|
||||||
public LegendCell(TimeLineController controller) {
|
LegendCell(TimeLineController controller) {
|
||||||
setEditable(false);
|
setEditable(false);
|
||||||
setController(controller);
|
this.controller = controller;
|
||||||
|
this.filteredEvents = this.controller.getEventsModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -119,15 +119,4 @@ class LegendCell extends TreeTableCell<AbstractFilter, AbstractFilter> implement
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
synchronized public final void setController(TimeLineController controller) {
|
|
||||||
this.controller = controller;
|
|
||||||
setModel(this.controller.getEventsModel());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setModel(FilteredEventsModel filteredEvents) {
|
|
||||||
this.filteredEvents = filteredEvents;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -18,19 +18,20 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.zooming;
|
package org.sleuthkit.autopsy.timeline.zooming;
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.ResourceBundle;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.InvalidationListener;
|
import javafx.beans.InvalidationListener;
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.Slider;
|
||||||
|
import javafx.scene.control.TitledPane;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
|
||||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||||
@ -44,13 +45,7 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
|||||||
* has sliders to provide context/control over three axes of zooming (timescale,
|
* has sliders to provide context/control over three axes of zooming (timescale,
|
||||||
* event hierarchy, and description detail).
|
* event hierarchy, and description detail).
|
||||||
*/
|
*/
|
||||||
public class ZoomSettingsPane extends TitledPane implements TimeLineView {
|
public class ZoomSettingsPane extends TitledPane {
|
||||||
|
|
||||||
@FXML
|
|
||||||
private ResourceBundle resources;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private URL location;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button backButton;
|
private Button backButton;
|
||||||
@ -105,35 +100,6 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
|
|||||||
timeUnitLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.timeUnitLabel.text"));
|
timeUnitLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.timeUnitLabel.text"));
|
||||||
zoomLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.zoomLabel.text"));
|
zoomLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.zoomLabel.text"));
|
||||||
historyLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.historyLabel.text"));
|
historyLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.historyLabel.text"));
|
||||||
}
|
|
||||||
|
|
||||||
public ZoomSettingsPane() {
|
|
||||||
FXMLConstructor.construct(this, "ZoomSettingsPane.fxml"); // NON-NLS
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
synchronized public void setController(TimeLineController controller) {
|
|
||||||
this.controller = controller;
|
|
||||||
setModel(controller.getEventsModel());
|
|
||||||
descrLODSlider.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS));
|
|
||||||
Back back = new Back(controller);
|
|
||||||
backButton.disableProperty().bind(back.disabledProperty());
|
|
||||||
backButton.setOnAction(back);
|
|
||||||
backButton.setTooltip(new Tooltip(
|
|
||||||
NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.backButton.toolTip.text",
|
|
||||||
back.getAccelerator().getName())));
|
|
||||||
Forward forward = new Forward(controller);
|
|
||||||
forwardButton.disableProperty().bind(forward.disabledProperty());
|
|
||||||
forwardButton.setOnAction(forward);
|
|
||||||
forwardButton.setTooltip(new Tooltip(
|
|
||||||
NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.forwardButton.toolTip.text",
|
|
||||||
forward.getAccelerator().getName())));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setModel(FilteredEventsModel filteredEvents) {
|
|
||||||
this.filteredEvents = filteredEvents;
|
|
||||||
|
|
||||||
initializeSlider(timeUnitSlider,
|
initializeSlider(timeUnitSlider,
|
||||||
() -> {
|
() -> {
|
||||||
@ -151,18 +117,14 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
|
|||||||
|
|
||||||
timeUnitSlider.setValue(TimeUnits.fromChronoUnit(chronoUnit).ordinal() - 1);
|
timeUnitSlider.setValue(TimeUnits.fromChronoUnit(chronoUnit).ordinal() - 1);
|
||||||
});
|
});
|
||||||
|
initializeSlider(descrLODSlider, () -> {
|
||||||
initializeSlider(descrLODSlider,
|
DescriptionLoD newLOD = DescriptionLoD.values()[Math.round(descrLODSlider.valueProperty().floatValue())];
|
||||||
() -> {
|
if (controller.pushDescrLOD(newLOD) == false) {
|
||||||
DescriptionLoD newLOD = DescriptionLoD.values()[Math.round(descrLODSlider.valueProperty().floatValue())];
|
descrLODSlider.setValue(new DescrLODConverter().fromString(controller.getEventsModel().getDescriptionLOD().toString()));
|
||||||
if (controller.pushDescrLOD(newLOD) == false) {
|
}
|
||||||
descrLODSlider.setValue(new DescrLODConverter().fromString(filteredEvents.getDescriptionLOD().toString()));
|
}, this.filteredEvents.descriptionLODProperty(), () -> {
|
||||||
}
|
descrLODSlider.setValue(this.filteredEvents.descriptionLODProperty().get().ordinal());
|
||||||
}, this.filteredEvents.descriptionLODProperty(),
|
});
|
||||||
() -> {
|
|
||||||
descrLODSlider.setValue(this.filteredEvents.descriptionLODProperty().get().ordinal());
|
|
||||||
});
|
|
||||||
|
|
||||||
initializeSlider(typeZoomSlider,
|
initializeSlider(typeZoomSlider,
|
||||||
() -> {
|
() -> {
|
||||||
EventTypeZoomLevel newZoomLevel = EventTypeZoomLevel.values()[Math.round(typeZoomSlider.valueProperty().floatValue())];
|
EventTypeZoomLevel newZoomLevel = EventTypeZoomLevel.values()[Math.round(typeZoomSlider.valueProperty().floatValue())];
|
||||||
@ -172,6 +134,26 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
|
|||||||
() -> {
|
() -> {
|
||||||
typeZoomSlider.setValue(this.filteredEvents.eventTypeZoomProperty().get().ordinal());
|
typeZoomSlider.setValue(this.filteredEvents.eventTypeZoomProperty().get().ordinal());
|
||||||
});
|
});
|
||||||
|
descrLODSlider.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS));
|
||||||
|
Back back = new Back(controller);
|
||||||
|
backButton.disableProperty().bind(back.disabledProperty());
|
||||||
|
backButton.setOnAction(back);
|
||||||
|
backButton.setTooltip(new Tooltip(
|
||||||
|
NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.backButton.toolTip.text",
|
||||||
|
back.getAccelerator().getName())));
|
||||||
|
Forward forward = new Forward(controller);
|
||||||
|
forwardButton.disableProperty().bind(forward.disabledProperty());
|
||||||
|
forwardButton.setOnAction(forward);
|
||||||
|
forwardButton.setTooltip(new Tooltip(
|
||||||
|
NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.forwardButton.toolTip.text",
|
||||||
|
forward.getAccelerator().getName())));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZoomSettingsPane(TimeLineController controller) {
|
||||||
|
this.controller = controller;
|
||||||
|
this.filteredEvents = controller.getEventsModel();
|
||||||
|
FXMLConstructor.construct(this, "ZoomSettingsPane.fxml"); // NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
34
CoreLibs/src/com/sun/javafx/Utils.java
Normal file
34
CoreLibs/src/com/sun/javafx/Utils.java
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2015 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.sun.javafx;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class takes the place of the one in Java8 versions prior to u60. As of
|
||||||
|
* u60 com.sun.javafx.Utils was moved to the com.sun.javafx.util package and
|
||||||
|
* code, specifically ControlsFX, that depended on it broke. ControlsFX has
|
||||||
|
* removed their dependency on this class, but their fix will not be released
|
||||||
|
* until version 8.60.10 of ControlsFX. Until then, this shim class allows
|
||||||
|
* version 8.40.9 to run on Java 8u60. This class (and package) should and will
|
||||||
|
* be removed once we upgrade to ControlsFX 8.60.x.
|
||||||
|
*/
|
||||||
|
@Deprecated // DO NOT USE
|
||||||
|
public class Utils extends com.sun.javafx.util.Utils {
|
||||||
|
|
||||||
|
//Does nothing but expose com.sun.javafx.utila.Utils in the old package (com.sun.javafx.Utils)
|
||||||
|
}
|
@ -49,10 +49,12 @@ import javafx.scene.layout.CornerRadii;
|
|||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.netbeans.api.progress.ProgressHandle;
|
import org.netbeans.api.progress.ProgressHandle;
|
||||||
import org.netbeans.api.progress.ProgressHandleFactory;
|
import org.netbeans.api.progress.ProgressHandleFactory;
|
||||||
|
import org.openide.util.Cancellable;
|
||||||
import org.openide.util.Exceptions;
|
import org.openide.util.Exceptions;
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||||
@ -395,6 +397,9 @@ public final class ImageGalleryController {
|
|||||||
tagsManager.clearFollowUpTagName();
|
tagsManager.clearFollowUpTagName();
|
||||||
tagsManager.unregisterListener(groupManager);
|
tagsManager.unregisterListener(groupManager);
|
||||||
tagsManager.unregisterListener(categoryManager);
|
tagsManager.unregisterListener(categoryManager);
|
||||||
|
dbWorkerThread.cancelAllTasks();
|
||||||
|
dbWorkerThread = null;
|
||||||
|
restartWorker();
|
||||||
|
|
||||||
Toolbar.getDefault(this).reset();
|
Toolbar.getDefault(this).reset();
|
||||||
groupManager.clear();
|
groupManager.clear();
|
||||||
@ -418,7 +423,12 @@ public final class ImageGalleryController {
|
|||||||
dbWorkerThread.addTask(innerTask);
|
dbWorkerThread.addTask(innerTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
synchronized public DrawableFile<?> getFileFromId(Long fileID) throws TskCoreException {
|
synchronized public DrawableFile<?> getFileFromId(Long fileID) throws TskCoreException {
|
||||||
|
if (Objects.isNull(db)) {
|
||||||
|
LOGGER.log(Level.WARNING, "Could not get file from id, no DB set. The case is probably closed.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return db.getFileFromID(fileID);
|
return db.getFileFromID(fileID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -587,7 +597,7 @@ public final class ImageGalleryController {
|
|||||||
try {
|
try {
|
||||||
InnerTask it = workQueue.take();
|
InnerTask it = workQueue.take();
|
||||||
|
|
||||||
if (it.cancelled == false) {
|
if (it.isCancelled() == false) {
|
||||||
it.run();
|
it.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -609,7 +619,7 @@ public final class ImageGalleryController {
|
|||||||
/**
|
/**
|
||||||
* Abstract base class for task to be done on {@link DBWorkerThread}
|
* Abstract base class for task to be done on {@link DBWorkerThread}
|
||||||
*/
|
*/
|
||||||
static public abstract class InnerTask implements Runnable {
|
static public abstract class InnerTask implements Runnable, Cancellable {
|
||||||
|
|
||||||
public double getProgress() {
|
public double getProgress() {
|
||||||
return progress.get();
|
return progress.get();
|
||||||
@ -653,13 +663,13 @@ public final class ImageGalleryController {
|
|||||||
protected InnerTask() {
|
protected InnerTask() {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected volatile boolean cancelled = false;
|
@Override
|
||||||
|
synchronized public boolean cancel() {
|
||||||
public void cancel() {
|
|
||||||
updateState(Worker.State.CANCELLED);
|
updateState(Worker.State.CANCELLED);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isCancelled() {
|
synchronized protected boolean isCancelled() {
|
||||||
return getState() == Worker.State.CANCELLED;
|
return getState() == Worker.State.CANCELLED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -693,7 +703,7 @@ public final class ImageGalleryController {
|
|||||||
*/
|
*/
|
||||||
static private class UpdateFileTask extends FileTask {
|
static private class UpdateFileTask extends FileTask {
|
||||||
|
|
||||||
public UpdateFileTask(AbstractFile f, DrawableDB taskDB) {
|
UpdateFileTask(AbstractFile f, DrawableDB taskDB) {
|
||||||
super(f, taskDB);
|
super(f, taskDB);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -720,7 +730,7 @@ public final class ImageGalleryController {
|
|||||||
*/
|
*/
|
||||||
static private class RemoveFileTask extends FileTask {
|
static private class RemoveFileTask extends FileTask {
|
||||||
|
|
||||||
public RemoveFileTask(AbstractFile f, DrawableDB taskDB) {
|
RemoveFileTask(AbstractFile f, DrawableDB taskDB) {
|
||||||
super(f, taskDB);
|
super(f, taskDB);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -756,7 +766,7 @@ public final class ImageGalleryController {
|
|||||||
private final DrawableDB taskDB;
|
private final DrawableDB taskDB;
|
||||||
private final SleuthkitCase tskCase;
|
private final SleuthkitCase tskCase;
|
||||||
|
|
||||||
public CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
|
CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
this.taskDB = taskDB;
|
this.taskDB = taskDB;
|
||||||
this.tskCase = tskCase;
|
this.tskCase = tskCase;
|
||||||
@ -766,8 +776,8 @@ public final class ImageGalleryController {
|
|||||||
+ StringUtils.join(FileTypeUtils.getAllSupportedExtensions(),
|
+ StringUtils.join(FileTypeUtils.getAllSupportedExtensions(),
|
||||||
"' or name LIKE '%.")
|
"' or name LIKE '%.")
|
||||||
+ "')";
|
+ "')";
|
||||||
static private final String MIMETYPE_CLAUSE
|
static private final String MIMETYPE_CLAUSE =
|
||||||
= "blackboard_attributes.value_text LIKE '"
|
"blackboard_attributes.value_text LIKE '"
|
||||||
+ StringUtils.join(FileTypeUtils.getAllSupportedMimeTypes(),
|
+ StringUtils.join(FileTypeUtils.getAllSupportedMimeTypes(),
|
||||||
"' OR blackboard_attributes.value_text LIKE '") + "' ";
|
"' OR blackboard_attributes.value_text LIKE '") + "' ";
|
||||||
|
|
||||||
@ -801,7 +811,7 @@ public final class ImageGalleryController {
|
|||||||
DrawableDB.DrawableTransaction tr = taskDB.beginTransaction();
|
DrawableDB.DrawableTransaction tr = taskDB.beginTransaction();
|
||||||
int units = 0;
|
int units = 0;
|
||||||
for (final AbstractFile f : files) {
|
for (final AbstractFile f : files) {
|
||||||
if (cancelled) {
|
if (isCancelled()) {
|
||||||
LOGGER.log(Level.WARNING, "task cancelled: not all contents may be transfered to database");
|
LOGGER.log(Level.WARNING, "task cancelled: not all contents may be transfered to database");
|
||||||
progressHandle.finish();
|
progressHandle.finish();
|
||||||
break;
|
break;
|
||||||
@ -849,11 +859,11 @@ public final class ImageGalleryController {
|
|||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
Logger.getLogger(CopyAnalyzedFiles.class.getName()).log(Level.WARNING, "failed to transfer all database contents", ex);
|
Logger.getLogger(CopyAnalyzedFiles.class.getName()).log(Level.WARNING, "failed to transfer all database contents", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
progressHandle.finish();
|
progressHandle.finish();
|
||||||
|
|
||||||
updateMessage("");
|
updateMessage("");
|
||||||
updateProgress(-1.0);
|
updateProgress(-1.0);
|
||||||
|
|
||||||
controller.setStale(false);
|
controller.setStale(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -877,13 +887,13 @@ public final class ImageGalleryController {
|
|||||||
// (name like '.jpg' or name like '.png' ...)
|
// (name like '.jpg' or name like '.png' ...)
|
||||||
private final String DRAWABLE_QUERY = "(name LIKE '%." + StringUtils.join(FileTypeUtils.getAllSupportedExtensions(), "' OR name LIKE '%.") + "') ";
|
private final String DRAWABLE_QUERY = "(name LIKE '%." + StringUtils.join(FileTypeUtils.getAllSupportedExtensions(), "' OR name LIKE '%.") + "') ";
|
||||||
|
|
||||||
private ProgressHandle progressHandle = ProgressHandleFactory.createHandle("prepopulating image/video database");
|
private ProgressHandle progressHandle = ProgressHandleFactory.createHandle("prepopulating image/video database", this);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param dataSourceId Data source object ID
|
* @param dataSourceId Data source object ID
|
||||||
*/
|
*/
|
||||||
public PrePopulateDataSourceFiles(Content dataSource) {
|
PrePopulateDataSourceFiles(Content dataSource) {
|
||||||
super();
|
super();
|
||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
}
|
}
|
||||||
@ -933,7 +943,7 @@ public final class ImageGalleryController {
|
|||||||
DrawableDB.DrawableTransaction tr = db.beginTransaction();
|
DrawableDB.DrawableTransaction tr = db.beginTransaction();
|
||||||
int units = 0;
|
int units = 0;
|
||||||
for (final AbstractFile f : files) {
|
for (final AbstractFile f : files) {
|
||||||
if (cancelled) {
|
if (isCancelled()) {
|
||||||
LOGGER.log(Level.WARNING, "task cancelled: not all contents may be transfered to database");
|
LOGGER.log(Level.WARNING, "task cancelled: not all contents may be transfered to database");
|
||||||
progressHandle.finish();
|
progressHandle.finish();
|
||||||
break;
|
break;
|
||||||
|
@ -31,7 +31,7 @@ import org.openide.util.Lookup;
|
|||||||
*/
|
*/
|
||||||
@OptionsPanelController.TopLevelRegistration(
|
@OptionsPanelController.TopLevelRegistration(
|
||||||
categoryName = "#OptionsCategory_Name_Options",
|
categoryName = "#OptionsCategory_Name_Options",
|
||||||
iconBase = "org/sleuthkit/autopsy/imagegallery/images/polaroid_48_silhouette.png",
|
iconBase = "org/sleuthkit/autopsy/imagegallery/images/polaroid_32_silhouette.png",
|
||||||
keywords = "#OptionsCategory_Keywords_Options",
|
keywords = "#OptionsCategory_Keywords_Options",
|
||||||
keywordsCategory = "Options",
|
keywordsCategory = "Options",
|
||||||
position = 10
|
position = 10
|
||||||
|
@ -89,7 +89,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
|||||||
return fileOpt;
|
return fileOpt;
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
fileOpt = Optional.of(getController().getFileFromId(fileIDOpt.get()));
|
fileOpt = Optional.ofNullable(getController().getFileFromId(fileIDOpt.get()));
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
Logger.getAnonymousLogger().log(Level.WARNING, "failed to get DrawableFile for obj_id" + fileIDOpt.get(), ex);
|
Logger.getAnonymousLogger().log(Level.WARNING, "failed to get DrawableFile for obj_id" + fileIDOpt.get(), ex);
|
||||||
fileOpt = Optional.empty();
|
fileOpt = Optional.empty();
|
||||||
@ -207,7 +207,6 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
|||||||
LOGGER.log(Level.SEVERE, "Failed to cache content for" + file.getName(), getException());
|
LOGGER.log(Level.SEVERE, "Failed to cache content for" + file.getName(), getException());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
abstract void saveToCache(X result);
|
abstract void saveToCache(X result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
@ -158,7 +158,6 @@ public class Server {
|
|||||||
public static final Charset DEFAULT_INDEXED_TEXT_CHARSET = Charset.forName("UTF-8"); ///< default Charset to index text as
|
public static final Charset DEFAULT_INDEXED_TEXT_CHARSET = Charset.forName("UTF-8"); ///< default Charset to index text as
|
||||||
private static final int MAX_SOLR_MEM_MB = 512; //TODO set dynamically based on avail. system resources
|
private static final int MAX_SOLR_MEM_MB = 512; //TODO set dynamically based on avail. system resources
|
||||||
private Process curSolrProcess = null;
|
private Process curSolrProcess = null;
|
||||||
private static Ingester ingester = null;
|
|
||||||
static final String PROPERTIES_FILE = KeywordSearchSettings.MODULE_NAME;
|
static final String PROPERTIES_FILE = KeywordSearchSettings.MODULE_NAME;
|
||||||
static final String PROPERTIES_CURRENT_SERVER_PORT = "IndexingServerPort"; //NON-NLS
|
static final String PROPERTIES_CURRENT_SERVER_PORT = "IndexingServerPort"; //NON-NLS
|
||||||
static final String PROPERTIES_CURRENT_STOP_PORT = "IndexingServerStopPort"; //NON-NLS
|
static final String PROPERTIES_CURRENT_STOP_PORT = "IndexingServerStopPort"; //NON-NLS
|
||||||
@ -291,13 +290,12 @@ public class Server {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
InputStreamReader isr = new InputStreamReader(stream);
|
|
||||||
BufferedReader br = new BufferedReader(isr);
|
try (InputStreamReader isr = new InputStreamReader(stream);
|
||||||
OutputStreamWriter osw = null;
|
BufferedReader br = new BufferedReader(isr);
|
||||||
BufferedWriter bw = null;
|
OutputStreamWriter osw = new OutputStreamWriter(out, PlatformUtil.getDefaultPlatformCharset());
|
||||||
try {
|
BufferedWriter bw = new BufferedWriter(osw);) {
|
||||||
osw = new OutputStreamWriter(out, PlatformUtil.getDefaultPlatformCharset());
|
|
||||||
bw = new BufferedWriter(osw);
|
|
||||||
String line = null;
|
String line = null;
|
||||||
while (doRun && (line = br.readLine()) != null) {
|
while (doRun && (line = br.readLine()) != null) {
|
||||||
bw.write(line);
|
bw.write(line);
|
||||||
@ -309,22 +307,7 @@ public class Server {
|
|||||||
}
|
}
|
||||||
bw.flush();
|
bw.flush();
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
logger.log(Level.WARNING, "Error redirecting Solr output stream"); //NON-NLS
|
logger.log(Level.SEVERE, "Error redirecting Solr output stream", ex); //NON-NLS
|
||||||
} finally {
|
|
||||||
if (bw != null) {
|
|
||||||
try {
|
|
||||||
bw.close();
|
|
||||||
} catch (IOException ex) {
|
|
||||||
logger.log(Level.WARNING, "Error closing Solr output stream writer"); //NON-NLS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (br != null) {
|
|
||||||
try {
|
|
||||||
br.close();
|
|
||||||
} catch (IOException ex) {
|
|
||||||
logger.log(Level.WARNING, "Error closing Solr output stream reader"); //NON-NLS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -357,7 +340,7 @@ public class Server {
|
|||||||
void killSolr() {
|
void killSolr() {
|
||||||
List<Long> solrPids = getSolrPIDs();
|
List<Long> solrPids = getSolrPIDs();
|
||||||
for (long pid : solrPids) {
|
for (long pid : solrPids) {
|
||||||
logger.log(Level.INFO, "Trying to kill old Solr process, PID: " + pid); //NON-NLS
|
logger.log(Level.INFO, "Trying to kill old Solr process, PID: {0}", pid); //NON-NLS
|
||||||
PlatformUtil.killProcess(pid);
|
PlatformUtil.killProcess(pid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -399,22 +382,12 @@ public class Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log(Level.INFO, "Starting Solr server from: " + solrFolder.getAbsolutePath()); //NON-NLS
|
logger.log(Level.INFO, "Starting Solr server from: {0}", solrFolder.getAbsolutePath()); //NON-NLS
|
||||||
|
|
||||||
if (isPortAvailable(currentSolrServerPort)) {
|
if (isPortAvailable(currentSolrServerPort)) {
|
||||||
logger.log(Level.INFO, "Port [" + currentSolrServerPort + "] available, starting Solr"); //NON-NLS
|
logger.log(Level.INFO, "Port [{0}] available, starting Solr", currentSolrServerPort); //NON-NLS
|
||||||
try {
|
try {
|
||||||
final String MAX_SOLR_MEM_MB_PAR = "-Xmx" + Integer.toString(MAX_SOLR_MEM_MB) + "m"; //NON-NLS
|
final String MAX_SOLR_MEM_MB_PAR = "-Xmx" + Integer.toString(MAX_SOLR_MEM_MB) + "m"; //NON-NLS
|
||||||
|
|
||||||
// String loggingPropertiesOpt = "-Djava.util.logging.config.file="; //NON-NLS
|
|
||||||
// String loggingPropertiesFilePath = instanceDir + File.separator + "conf" + File.separator; //NON-NLS
|
|
||||||
//
|
|
||||||
// if (DEBUG) {
|
|
||||||
// loggingPropertiesFilePath += "logging-development.properties"; //NON-NLS
|
|
||||||
// } else {
|
|
||||||
// loggingPropertiesFilePath += "logging-release.properties"; //NON-NLS
|
|
||||||
// }
|
|
||||||
// final String loggingProperties = loggingPropertiesOpt + loggingPropertiesFilePath;
|
|
||||||
List<String> commandLine = new ArrayList<>();
|
List<String> commandLine = new ArrayList<>();
|
||||||
commandLine.add(javaPath);
|
commandLine.add(javaPath);
|
||||||
commandLine.add(MAX_SOLR_MEM_MB_PAR);
|
commandLine.add(MAX_SOLR_MEM_MB_PAR);
|
||||||
@ -434,7 +407,7 @@ public class Server {
|
|||||||
Path solrStderrPath = Paths.get(Places.getUserDirectory().getAbsolutePath(), "var", "log", "solr.log.stderr");
|
Path solrStderrPath = Paths.get(Places.getUserDirectory().getAbsolutePath(), "var", "log", "solr.log.stderr");
|
||||||
solrProcessBuilder.redirectError(solrStderrPath.toFile());
|
solrProcessBuilder.redirectError(solrStderrPath.toFile());
|
||||||
|
|
||||||
logger.log(Level.INFO, "Starting Solr using: " + solrProcessBuilder.command()); //NON-NLS
|
logger.log(Level.INFO, "Starting Solr using: {0}", solrProcessBuilder.command()); //NON-NLS
|
||||||
curSolrProcess = solrProcessBuilder.start();
|
curSolrProcess = solrProcessBuilder.start();
|
||||||
logger.log(Level.INFO, "Finished starting Solr"); //NON-NLS
|
logger.log(Level.INFO, "Finished starting Solr"); //NON-NLS
|
||||||
|
|
||||||
@ -447,7 +420,7 @@ public class Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final List<Long> pids = this.getSolrPIDs();
|
final List<Long> pids = this.getSolrPIDs();
|
||||||
logger.log(Level.INFO, "New Solr process PID: " + pids); //NON-NLS
|
logger.log(Level.INFO, "New Solr process PID: {0}", pids); //NON-NLS
|
||||||
} catch (SecurityException ex) {
|
} catch (SecurityException ex) {
|
||||||
logger.log(Level.SEVERE, "Could not start Solr process!", ex); //NON-NLS
|
logger.log(Level.SEVERE, "Could not start Solr process!", ex); //NON-NLS
|
||||||
throw new KeywordSearchModuleException(
|
throw new KeywordSearchModuleException(
|
||||||
@ -526,7 +499,7 @@ public class Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.log(Level.INFO, "Stopping Solr server from: " + solrFolder.getAbsolutePath()); //NON-NLS
|
logger.log(Level.INFO, "Stopping Solr server from: {0}", solrFolder.getAbsolutePath()); //NON-NLS
|
||||||
|
|
||||||
//try graceful shutdown
|
//try graceful shutdown
|
||||||
final String[] SOLR_STOP_CMD = {
|
final String[] SOLR_STOP_CMD = {
|
||||||
@ -1278,6 +1251,7 @@ public class Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ServerAction extends AbstractAction {
|
class ServerAction extends AbstractAction {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
@ -1289,11 +1263,12 @@ public class Server {
|
|||||||
* Exception thrown if solr port not available
|
* Exception thrown if solr port not available
|
||||||
*/
|
*/
|
||||||
class SolrServerNoPortException extends SocketException {
|
class SolrServerNoPortException extends SocketException {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the port number that is not available
|
* the port number that is not available
|
||||||
*/
|
*/
|
||||||
private int port;
|
private final int port;
|
||||||
|
|
||||||
SolrServerNoPortException(int port) {
|
SolrServerNoPortException(int port) {
|
||||||
super(NbBundle.getMessage(Server.class, "Server.solrServerNoPortException.msg", port,
|
super(NbBundle.getMessage(Server.class, "Server.solrServerNoPortException.msg", port,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#Updated by build script
|
#Updated by build script
|
||||||
#Tue, 13 Oct 2015 17:00:33 -0400
|
#Fri, 23 Oct 2015 09:28:23 -0400
|
||||||
LBL_splash_window_title=Starting Autopsy
|
LBL_splash_window_title=Starting Autopsy
|
||||||
SPLASH_HEIGHT=314
|
SPLASH_HEIGHT=314
|
||||||
SPLASH_WIDTH=538
|
SPLASH_WIDTH=538
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#Updated by build script
|
#Updated by build script
|
||||||
#Tue, 13 Oct 2015 17:00:33 -0400
|
#Fri, 23 Oct 2015 09:28:23 -0400
|
||||||
CTL_MainWindow_Title=Autopsy 3.1.3
|
CTL_MainWindow_Title=Autopsy 3.1.3
|
||||||
CTL_MainWindow_Title_No_Project=Autopsy 3.1.3
|
CTL_MainWindow_Title_No_Project=Autopsy 3.1.3
|
||||||
|
@ -30,9 +30,11 @@
|
|||||||
<property name="win64.TskLib.path" value="${env.TSK_HOME}/win32/x64/Release_PostgreSQL"/>
|
<property name="win64.TskLib.path" value="${env.TSK_HOME}/win32/x64/Release_PostgreSQL"/>
|
||||||
<property name="win32.TskLib.path" value="${env.TSK_HOME}/win32/Release_PostgreSQL" />
|
<property name="win32.TskLib.path" value="${env.TSK_HOME}/win32/Release_PostgreSQL" />
|
||||||
<property name="win64.TskLib.postgres_path" value="${env.TSK_HOME}/win32/x64/Release_PostgreSQL"/>
|
<property name="win64.TskLib.postgres_path" value="${env.TSK_HOME}/win32/x64/Release_PostgreSQL"/>
|
||||||
|
<property name="win32.TskLib.postgres_path" value="${env.TSK_HOME}/win32/Release_PostgreSQL"/>
|
||||||
<available property="win64.TskLib.exists" type="dir" file="${win64.TskLib.path}" />
|
<available property="win64.TskLib.exists" type="dir" file="${win64.TskLib.path}" />
|
||||||
<available property="win32.TskLib.exists" type="dir" file="${win32.TskLib.path}" />
|
<available property="win32.TskLib.exists" type="dir" file="${win32.TskLib.path}" />
|
||||||
<available property="win64.TskLib_postgres.exists" type="dir" file="{win64.TskLib.postgres_path}" />
|
<available property="win64.TskLib_postgres.exists" type="dir" file="{win64.TskLib.postgres_path}" />
|
||||||
|
<available property="win32.TskLib_postgres.exists" type="dir" file="{win32.TskLib.postgres_path}" />
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<!-- The following copy the libtsk_jni dependencies to the Autopsy
|
<!-- The following copy the libtsk_jni dependencies to the Autopsy
|
||||||
@ -50,6 +52,7 @@
|
|||||||
<include name="ssleay32.dll"/>
|
<include name="ssleay32.dll"/>
|
||||||
<include name="libintl-8.dll"/>
|
<include name="libintl-8.dll"/>
|
||||||
<include name="libpq.dll"/>
|
<include name="libpq.dll"/>
|
||||||
|
<include name="msvcr120.dll"/>
|
||||||
</fileset>
|
</fileset>
|
||||||
|
|
||||||
<copy todir="${amd64}" overwrite="true">
|
<copy todir="${amd64}" overwrite="true">
|
||||||
@ -70,20 +73,32 @@
|
|||||||
<include name="libewf.dll"/>
|
<include name="libewf.dll"/>
|
||||||
</fileset>
|
</fileset>
|
||||||
|
|
||||||
|
<fileset dir="${win32.TskLib.postgres_path}" id="postgres32dlls">
|
||||||
|
<include name="libeay32.dll"/>
|
||||||
|
<include name="ssleay32.dll"/>
|
||||||
|
<include name="intl.dll"/>
|
||||||
|
<include name="libpq.dll"/>
|
||||||
|
<include name="msvcr120.dll"/>
|
||||||
|
</fileset>
|
||||||
|
|
||||||
<copy todir="${i386}" overwrite="true">
|
<copy todir="${i386}" overwrite="true">
|
||||||
<fileset refid="win32dlls" />
|
<fileset refid="win32dlls" />
|
||||||
|
<fileset refid="postgres32dlls" />
|
||||||
</copy>
|
</copy>
|
||||||
|
|
||||||
<copy todir="${x86}" overwrite="true">
|
<copy todir="${x86}" overwrite="true">
|
||||||
<fileset refid="win32dlls" />
|
<fileset refid="win32dlls" />
|
||||||
|
<fileset refid="postgres32dlls" />
|
||||||
</copy>
|
</copy>
|
||||||
|
|
||||||
<copy todir="${i586}" overwrite="true">
|
<copy todir="${i586}" overwrite="true">
|
||||||
<fileset refid="win32dlls" />
|
<fileset refid="win32dlls" />
|
||||||
|
<fileset refid="postgres32dlls" />
|
||||||
</copy>
|
</copy>
|
||||||
|
|
||||||
<copy todir="${i686}" overwrite="true">
|
<copy todir="${i686}" overwrite="true">
|
||||||
<fileset refid="win32dlls" />
|
<fileset refid="win32dlls" />
|
||||||
|
<fileset refid="postgres32dlls" />
|
||||||
</copy>
|
</copy>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
@ -147,6 +162,4 @@
|
|||||||
<fileset refid="crt64dlls"/>
|
<fileset refid="crt64dlls"/>
|
||||||
</copy>
|
</copy>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
@ -38,7 +38,7 @@ PROJECT_NAME = "Autopsy User Documentation"
|
|||||||
# could be handy for archiving the generated documentation or if some version
|
# could be handy for archiving the generated documentation or if some version
|
||||||
# control system is used.
|
# control system is used.
|
||||||
|
|
||||||
PROJECT_NUMBER = 3.1
|
PROJECT_NUMBER = 4.0
|
||||||
|
|
||||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||||
# for a project that appears at the top of each page and should give viewer a
|
# for a project that appears at the top of each page and should give viewer a
|
||||||
@ -1025,7 +1025,7 @@ GENERATE_HTML = YES
|
|||||||
# The default directory is: html.
|
# The default directory is: html.
|
||||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||||
|
|
||||||
HTML_OUTPUT = 3.1
|
HTML_OUTPUT = 4.0
|
||||||
|
|
||||||
# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
|
# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
|
||||||
# generated HTML page (for example: .htm, .php, .asp).
|
# generated HTML page (for example: .htm, .php, .asp).
|
||||||
|
@ -16,6 +16,8 @@ The New Case wizard dialog will open and you will need to enter the case name an
|
|||||||
|
|
||||||
\image html case-newcase.png
|
\image html case-newcase.png
|
||||||
|
|
||||||
|
NOTE: You will only have the option of making a multi-user case if you have configured Autopsy with multi-user settings. See \ref install_multiuser_page for installation instructions and \ref creating_multi_user_cases for details on creating multi-user cases.
|
||||||
|
|
||||||
You will also be prompted for optional information, such as investigator name and case number.
|
You will also be prompted for optional information, such as investigator name and case number.
|
||||||
|
|
||||||
After you create the case, you will be prompted to add a data source, as described in \ref ds_add.
|
After you create the case, you will be prompted to add a data source, as described in \ref ds_add.
|
||||||
|
@ -25,6 +25,8 @@ Regardless of the type of data source, there are some common steps in the proces
|
|||||||
|
|
||||||
\image html select-data-source-type.PNG
|
\image html select-data-source-type.PNG
|
||||||
|
|
||||||
|
NOTE: If you are adding a data source to a multi-user case, ensure that all Autopsy clients will have access to the data source at the same path. We recommend using UNC paths to ensure this consistent mapping.
|
||||||
|
|
||||||
2) Autopsy will perform a basic examination of the data source and populate an embedded database with an entry for each file in the data source. No content is analyzed in the process, only the files are enumerated.
|
2) Autopsy will perform a basic examination of the data source and populate an embedded database with an entry for each file in the data source. No content is analyzed in the process, only the files are enumerated.
|
||||||
|
|
||||||
3) While it is examining the data source, you will be prompted with a list of ingest modules to enable.
|
3) While it is examining the data source, you will be prompted with a list of ingest modules to enable.
|
||||||
|
@ -1,60 +1,80 @@
|
|||||||
/*! \page install_activemq Install and Configure ActiveMQ
|
/*! \page install_activemq Install and Configure ActiveMQ
|
||||||
To install ActiveMQ, perform the following steps:
|
To install ActiveMQ, perform the following steps:
|
||||||
1. You need a 32-bit or 64-bit version of the Java Runtime Environment (JRE) installed, depending upon the version of Autopsy you have installed. You can test this by running _where java_ from the command line. If you see output like the yellow results below, you have a JRE.
|
|
||||||
|
\section install_activemq_prereq Prerequisites
|
||||||
|
|
||||||
|
You will need:
|
||||||
|
- 64-bit version of the Java Runtime Environment (JRE) from http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html.
|
||||||
|
- Download ActiveMQ-5.11.1 from: http://activemq.apache.org/activemq-5111-release.html
|
||||||
|
|
||||||
|
|
||||||
|
\section install_activemq_install Installation
|
||||||
|
|
||||||
|
\subsection install_activemq_install_java JRE Installation
|
||||||
|
Install the Java JRE if needed. You can test this by running _where java_ from the command line. If you see output like the yellow results below, you have a JRE.
|
||||||
<br><br>
|
<br><br>
|
||||||
\image html wherejava.PNG
|
\image html wherejava.PNG
|
||||||
<br><br>
|
<br><br>
|
||||||
You can test if you have a 32-bit or 64-bit JRE installed via the following:
|
If you need the JRE, install it with the default settings.
|
||||||
- _java -d32 -version_ for a 32-bit JRE
|
|
||||||
- _java -d64 -version_ for a 64-bit JRE
|
|
||||||
<br><br>
|
\subsection install_activemq_install_mq ActiveMQ Installation
|
||||||
The screenshot below shows that there is a no 32-bit JRE on this machine, and there is a 64-bit JRE.
|
|
||||||
<br><br>
|
1. Extract the contents of the ActiveMQ archive folder to a location of your choice, bearing in mind that the files should be in a location that the running process will have write permissions to the folder. A typical folder choice is <i>C:\\Program Files\\apache-activemq-5.11.1</i>. Typically, it will ask for administrator permission to move the folder. Allow it if required.
|
||||||
\image html JRE_bitness.PNG
|
|
||||||
<br><br>
|
2. Edit the <i>conf\\activemq.xml</i> in the extracted folder to add <i>"&wireFormat.maxInactivityDuration=0"</i> to the URI for the _transportConnector_ named _openwire_. Add the text highlighted in yellow below:
|
||||||
If you do not have a JRE installed, proceed to step 2. If you have a JRE installed, proceed to step 3.
|
|
||||||
<br><br>
|
|
||||||
2. Install the appropriate 32 or 64-bit version of the JRE, depending upon the version of Autopsy you have installed. Download one from: http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html. If you installed 32-bit Autopsy, be sure to select a package that has "x86" in the name. If you installed 64-bit Autopsy, be sure to select a package that has "x64" in the name. Follow the installation prompts to install the JRE.
|
|
||||||
3. Download ActiveMQ-5.11.1 from: http://activemq.apache.org/activemq-5111-release.html
|
|
||||||
4. Extract the files in the archive
|
|
||||||
5. Edit <i>apache-activemq-5.11.1\\conf\\activemq.xml</i> to add <i>"&wireFormat.maxInactivityDuration=0"</i> to the URI for the _transportConnector_ named _openwire_. Add the text highlighted in yellow below:
|
|
||||||
<br><br>
|
<br><br>
|
||||||
\image html maxinactivityduration.PNG
|
\image html maxinactivityduration.PNG
|
||||||
<br><br>
|
<br><br>
|
||||||
6. Move the <i>apache-activemq-5.11.1</i> folder to a location of your choice, bearing in mind that the files should be in a location that the running process will have write permissions to the folder. A typical folder choice is <i>C:\\Program Files\\apache-activemq-5.11.1</i>. Typically, it will ask for administrator permission to move the folder. Allow it if required.
|
|
||||||
7. Install ActiveMQ as a service by navigating to the folder <i>apache-activemq-5.11.1-bin\\apache-activemq-5.11.1\\bin\\win64</i>, right-clicking _InstallService.bat_, clicking _Run as administrator_, then click _Yes_.
|
|
||||||
8. If you desire authentication for your ActiveMQ server (a good idea), the following directions allow you to set up credentials:
|
|
||||||
+ Copy and paste the following text to the file <i>"C:\Program Files\apache-activemq-5.11.1-bin\apache-activemq-5.11.1\conf\groups.properties"</i>, overwriting the text highlighted in yellow in the screenshot below:
|
|
||||||
|
|
||||||
admins=system,sslclient,client,broker1,broker2
|
|
||||||
tempDestinationAdmins=system,user,sslclient,client,broker1,broker2
|
|
||||||
users=system,user,sslclient,client,broker1,broker2
|
|
||||||
guests=guest
|
|
||||||
|
|
||||||
<br><br>
|
3. Install ActiveMQ as a service by navigating to the folder <i>bin\\win64</i>, right-clicking _InstallService.bat_, clicking _Run as administrator_, then click _Yes_.
|
||||||
\image html groups.properties.before.PNG
|
|
||||||
<br><br>
|
|
||||||
When complete, the file should look like this:
|
|
||||||
<br><br>
|
|
||||||
\image html groups.properties.after.PNG
|
|
||||||
<br><br>
|
|
||||||
|
|
||||||
+ Copy and paste the following text to the file <i>"C:\Program Files\apache-activemq-5.11.1-bin\apache-activemq-5.11.1\conf\users.properties"</i>, overwriting the text highlighted in yellow in the screenshot below:
|
4. Start the ActiveMQ service by pressing _Start_, type _services.msc_, and press _Enter_. Find _ActiveMQ_ in the list and press the _Start the service_ link.
|
||||||
|
|
||||||
system=manager
|
5. ActiveMQ should now be installed and configured using the default credentials. You should go to the next section to change the default passwords. To test your installation, you can access the admin pages in your web browser via a URL like this (set your host): http://localhost:8161/admin. The default administrator username is _admin_ with a password of _admin_ and the default regular username is _user_ with a default password of _password_. You can change these passwords by following the instructions below. If you can see a page that looks like the following, it is ready to function.
|
||||||
user=password
|
<br><br>
|
||||||
guest=password
|
\image html activemq.PNG
|
||||||
sslclient=CN=localhost, OU=activemq.org, O=activemq.org, L=LA, ST=CA, C=US
|
<br><br>
|
||||||
|
|
||||||
<br><br>
|
If you do not see a screen like the above screenshot and you have double checked that the ActiveMQ service is running, contact your network administrator. For the ActiveMQ service to be accessible by network clients you may need to configure your Windows firewall (and any other 3rd party firewall in use) to allow communication.
|
||||||
\image html users.properties.before.PNG
|
|
||||||
<br><br>
|
|
||||||
When complete, the file should look like this:
|
|
||||||
<br><br>
|
|
||||||
\image html users.properties.after.PNG
|
|
||||||
<br><br>
|
|
||||||
|
|
||||||
+ Copy and paste the following text to the file <i>"C:\Program Files\apache-activemq-5.11.1-bin\apache-activemq-5.11.1\conf\activemq.xml"</i>, inserting the text at the line shown in yellow in the screenshot below.
|
|
||||||
|
\subsection install_activemq_install_pw Configuring Authentication
|
||||||
|
|
||||||
|
You can optionally add authentication to your ActiveMQ server. The ActiveMQ communications are not encrypted and contain basic messages between the systems about when new data has been found.
|
||||||
|
|
||||||
|
The following directions allow you to set up credentials:
|
||||||
|
|
||||||
|
1. Copy and paste the following text to the file <i>"conf\groups.properties"</i>, overwriting the text highlighted in yellow in the screenshot below:
|
||||||
|
<BLOCKQUOTE>
|
||||||
|
admins=system,sslclient,client,broker1,broker2<br />
|
||||||
|
tempDestinationAdmins=system,user,sslclient,client,broker1,broker2<br />
|
||||||
|
users=system,user,sslclient,client,broker1,broker2<br />
|
||||||
|
guests=guest<br />
|
||||||
|
</BLOCKQUOTE>
|
||||||
|
<br><br>
|
||||||
|
\image html groups.properties.before.PNG
|
||||||
|
<br><br>
|
||||||
|
When complete, the file should look like this:
|
||||||
|
<br><br>
|
||||||
|
\image html groups.properties.after.PNG
|
||||||
|
<br><br>
|
||||||
|
2. Copy and paste the following text to the file <i>"conf\users.properties"</i>, overwriting the text highlighted in yellow in the screenshot below:
|
||||||
|
<BLOCKQUOTE>
|
||||||
|
system=manager<br />
|
||||||
|
user=password<br />
|
||||||
|
guest=password<br />
|
||||||
|
sslclient=CN=localhost, OU=activemq.org, O=activemq.org, L=LA, ST=CA, C=US<br />
|
||||||
|
</BLOCKQUOTE>
|
||||||
|
<br><br>
|
||||||
|
\image html users.properties.before.PNG
|
||||||
|
<br><br>
|
||||||
|
When complete, the file should look like this:
|
||||||
|
<br><br>
|
||||||
|
\image html users.properties.after.PNG
|
||||||
|
<br><br>
|
||||||
|
3. Copy and paste the following text to the file <i>"conf\activemq.xml"</i>, inserting the text at the line shown in yellow in the screenshot below.
|
||||||
|
|
||||||
<plugins>
|
<plugins>
|
||||||
<jaasAuthenticationPlugin configuration="activemq-domain" />
|
<jaasAuthenticationPlugin configuration="activemq-domain" />
|
||||||
@ -67,41 +87,28 @@ If you do not have a JRE installed, proceed to step 2. If you have a JRE install
|
|||||||
</simpleAuthenticationPlugin>
|
</simpleAuthenticationPlugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
|
|
||||||
<br><br>
|
|
||||||
\image html insertTextHere.PNG
|
|
||||||
<br><br>
|
|
||||||
After insertion, the file should look like the screenshot below, with the inserted portion highlighted in yellow. This is where you can change the username and password for your ActiveMQ setup.
|
|
||||||
<br><br>
|
|
||||||
\image html insertedText.PNG
|
|
||||||
<br><br>
|
|
||||||
To add a new user or change the password:
|
|
||||||
+ Stop the ActiveMQ service by pressing _Start_, type _services.msc_, and press _Enter_. Find _ActiveMQ_ in the list and press the _Stop the service_ link.
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
\image html StopActiveMQService.PNG
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
+ Edit <i>"C:\Program Files\apache-activemq-5.11.1-bin\apache-activemq-5.11.1\conf\activemq.xml"</i> adding the desired line. Both _username_ and _password_ are case sensitive. You will very likely want to keep your new users in the _users_ group.
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
\image html newUserAndPassword.PNG
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
+ Start the ActiveMQ service by pressing _Start_, type _services.msc_, and press _Enter_. Find _ActiveMQ_ in the list and press the _Start the service_ link.
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
\image html StartActiveMQService.PNG
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
|
|
||||||
9. If not already started, start the ActiveMQ service by pressing _Start_, type _services.msc_, and press _Enter_. Find _ActiveMQ_ in the list and press the _Start the service_ link.
|
|
||||||
10. ActiveMQ should now be fully installed and configured. You can access the admin pages in your web browser via a URL like this (set your host): http://localhost:8161/admin. The default administrator username is _admin_ with a password of _admin_ and the default regular username is _user_ with a default password of _password_. You can change these passwords by editing the file <i>"C:\Program Files\apache-activemq-5.11.1-bin\apache-activemq-5.11.1\conf\activemq.xml"</i> on the ActiveMQ server as discussed above. If you can see a page that looks like the following, it is ready to function.
|
|
||||||
<br><br>
|
<br><br>
|
||||||
\image html activemq.PNG
|
\image html insertTextHere.PNG
|
||||||
<br>
|
<br><br>
|
||||||
<br>
|
After insertion, the file should look like the screenshot below, with the inserted portion highlighted in yellow. This is where you can change the username and password for your ActiveMQ setup.
|
||||||
If you do not see a screen like the above screenshot and you have double checked that the ActiveMQ service is running, contact your network administrator. For the ActiveMQ service to be accessible by network clients you may need to configure your Windows firewall (and any other 3rd party firewall in use) to allow communication.
|
<br><br>
|
||||||
|
\image html insertedText.PNG
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
|
|
||||||
|
To add a new user or change the password:
|
||||||
|
|
||||||
|
1. Stop the ActiveMQ service by pressing _Start_, type _services.msc_, and press _Enter_. Find _ActiveMQ_ in the list and press the _Stop the service_ link.
|
||||||
|
<br><br>
|
||||||
|
\image html StopActiveMQService.PNG
|
||||||
|
<br><br>
|
||||||
|
2. Edit <i>"conf\activemq.xml"</i> adding the desired line. Both _username_ and _password_ are case sensitive. You will very likely want to keep your new users in the _users_ group.
|
||||||
|
<br><br>
|
||||||
|
\image html newUserAndPassword.PNG
|
||||||
|
<br><br>
|
||||||
|
3. Start the ActiveMQ service by pressing _Start_, type _services.msc_, and press _Enter_. Find _ActiveMQ_ in the list and press the _Start the service_ link.
|
||||||
|
<br><br>
|
||||||
|
\image html StartActiveMQService.PNG
|
||||||
|
<br><br>
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
31
docs/doxygen-user/installMultiUser.dox
Normal file
31
docs/doxygen-user/installMultiUser.dox
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/*! \page install_multiuser_page Setting Up Multi-user Environment
|
||||||
|
|
||||||
|
\section multiuser_install Multi-user Installation
|
||||||
|
|
||||||
|
Autopsy can be setup to work in an environment where multiple users on different computers can have the same case open at the same time. To set up this type of environment, you will need to configure additional (free and open source) network-based services.
|
||||||
|
|
||||||
|
\subsection multiuser_install_services Network-based Services
|
||||||
|
|
||||||
|
You will need the following that all Autopsy clients can access:
|
||||||
|
- Centralized storage that all clients running Autopsy have access to. The central storage should be either mounted at the same Windows drive letter or UNC paths should be used everywhere. All clients need to be able to access data using the same path.
|
||||||
|
- A central PostgreSQL database. A database will be created for each case and will be stored on the local drive of the database server. Installation and configuration is explained in \ref install_postgresql.
|
||||||
|
- A central Solr text index. A Solr core will be created for each case and will be stored in the case folder (not on the local drive of the Solr server). We recommend using Bitnami Solr. This is explained in \ref install_solr.
|
||||||
|
- An ActiveMQ messaging server to allow the various clients to communicate with each other. This service has minimal storage requirements. This is explained in \ref install_activemq.
|
||||||
|
|
||||||
|
When you setup the above services, write down the addresses, user names, and passwords or each so that you can configure each of the client systems afterwards.
|
||||||
|
|
||||||
|
We recommend using at least 2 dedicated computers for this additional infrastructure. Spreading the services out across several machines can improve throughput.
|
||||||
|
If possible, place Solr on a machine by itself, as it is the largest RAM and CPU utilizer among the servers.
|
||||||
|
|
||||||
|
Ensure that the central storage and PostgreSQL servers are regularly backed up.
|
||||||
|
|
||||||
|
\subsection multiuser_install_clients Autopsy Clients
|
||||||
|
|
||||||
|
Once the infrastructure is in place, you will need to configure Autopsy to use them.
|
||||||
|
- Install Autopsy on each client system as normal using the steps from \ref installation_page.
|
||||||
|
- Start Autopsy and open the multi-user options panel from “Tools”, “Options”, “Multi-user”. As shown in the screenshot below, you can then enter all of the address and authentication information for the network-based services. Note that in order to create or open Multi-user cases, "Enable Multi-user cases" must be checked and the settings below must be correct.
|
||||||
|
|
||||||
|
\image html multiuser_settings.PNG
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
@ -1,32 +1,42 @@
|
|||||||
/*! \page install_solr Install and Configure Solr
|
/*! \page install_solr Install and Configure Solr
|
||||||
A central Solr server is needed to store keyword indexes. To install Solr, perform the following steps:
|
A central Solr server is needed to store keyword indexes. To install Solr, perform the following steps:
|
||||||
|
|
||||||
1. You need a 32-bit or 64-bit version of the Java Runtime Environment (JRE) installed, depending upon the version of Autopsy you have installed. You can test this by running _where java_ from the command line. If you see output like the yellow results below, you have a JRE.
|
|
||||||
|
|
||||||
|
\section install_solr_prereq Prerequisites
|
||||||
|
|
||||||
|
You will need:
|
||||||
|
- 64-bit version of the Java Runtime Environment (JRE) from http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html.
|
||||||
|
- Download the Apache Solr 4.10.3-0 installation package from https://bitnami.com/stack/solr/installer.
|
||||||
|
- Access to an installed version of Autopsy so that you can copy files from it.
|
||||||
|
|
||||||
|
|
||||||
|
\section install_solr_install Installation
|
||||||
|
|
||||||
|
\subsection install_solr_install_java JRE Installation
|
||||||
|
Install the Java JRE if needed. You can test this by running _where java_ from the command line. If you see output like the yellow results below, you have a JRE.
|
||||||
<br><br>
|
<br><br>
|
||||||
\image html wherejava.PNG
|
\image html wherejava.PNG
|
||||||
<br><br>
|
<br><br>
|
||||||
You can test if you have a 32-bit or 64-bit JRE installed via the following:
|
If you need the JRE, install it with the default settings.
|
||||||
- _java -d32 -version_ for a 32-bit JRE
|
|
||||||
- _java -d64 -version_ for a 64-bit JRE
|
|
||||||
<br><br>
|
\subsection install_solr_install_solr Solr Installation
|
||||||
The screenshot below shows that there is a no 32-bit JRE on this machine, and there is a 64-bit JRE.
|
|
||||||
<br><br>
|
The following steps will configure Solr to run using an account that will have access to the network storage.
|
||||||
\image html JRE_bitness.PNG
|
|
||||||
<br><br>
|
1. Run the Bitnami installer, <i>bitnami-solr-4.10.3-0-windows-installer.exe</i>
|
||||||
If you do not have a JRE installed, proceed to step 2. If you have a JRE installed, proceed to step 3.
|
2. If Windows prompts with User Account Control, click _Yes_
|
||||||
<br><br>
|
3. Follow the prompts through to completion. You do not need to "Learn more about Bitnami cloud hosting" so you can clear the check box.
|
||||||
2. Install the appropriate 32 or 64-bit version of the JRE, depending upon the version of Autopsy you have installed. Download one from: http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html. If you installed 32-bit Autopsy, be sure to select a package that has "x86" in the name. If you installed 64-bit Autopsy, be sure to select a package that has "x64" in the name. Follow the installation prompts to install the JRE.
|
4. If you see an error dialog like the following, you may safely ignore it.
|
||||||
3. Download the Apache Solr 4.10.3-0 installation package from https://bitnami.com/stack/solr/installer. The following steps will configure Solr to run using an account that will have access to the network storage.
|
|
||||||
4. Run the Bitnami installer, <i>bitnami-solr-4.10.3-0-windows-installer.exe</i>
|
|
||||||
5. If Windows prompts with User Account Control, click _Yes_
|
|
||||||
6. Follow the prompts through to completion. You do not need to "Learn more about Bitnami cloud hosting" so you can clear the check box.
|
|
||||||
7. If you see an error dialog like the following, you may safely ignore it.
|
|
||||||
<br><br>
|
<br><br>
|
||||||
\image html apachebadmessage.PNG
|
\image html apachebadmessage.PNG
|
||||||
<br>
|
<br>
|
||||||
8. When the installation completes, clear the "Launch Bitnami Apache Solr Stack Now?" checkbox and click _Finish_.
|
5. When the installation completes, clear the "Launch Bitnami Apache Solr Stack Now?" checkbox and click _Finish_.
|
||||||
9. Stop _solrApache_ and _solrJetty_ services by pressing _Start_, typing _services.msc_, pressing _Enter_, and locating the _solrApache_ and _solrJetty_ Windows services. Select the services one at a time, and press _Stop the service_ once for each of them. If the service is already stopped and there is no _Stop the service_ available, this is okay.
|
|
||||||
10. Edit the <i>C:\\Bitnami\\solr-4.10.3-0\\apache-solr\\scripts\\serviceinstall.bat</i> script. You need administrator permission to change this file. The easiest way around this is to save a copy on the Desktop, edit the Desktop version, and copy the new one back over the top of the old. Windows will ask for permission to overwrite the old file; allow it. You should make the following changes to this file:
|
\subsection install_solr_config Solr Configuration
|
||||||
|
1. Stop _solrApache_ and _solrJetty_ services by pressing _Start_, typing _services.msc_, pressing _Enter_, and locating the _solrApache_ and _solrJetty_ Windows services. Select the services one at a time, and press _Stop the service_ once for each of them. If the service is already stopped and there is no _Stop the service_ available, this is okay.
|
||||||
|
2. Edit the <i>C:\\Bitnami\\solr-4.10.3-0\\apache-solr\\scripts\\serviceinstall.bat</i> script. You need administrator permission to change this file. The easiest way around this is to save a copy on the Desktop, edit the Desktop version, and copy the new one back over the top of the old. Windows will ask for permission to overwrite the old file; allow it. You should make the following changes to this file:
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
- Add the following options in the _JvmOptions_ section of the line that begins with <i>"C:\Bitnami\solr-4.10.3-0/apache-solr\scripts\prunsrv.exe"</i> :
|
- Add the following options in the _JvmOptions_ section of the line that begins with <i>"C:\Bitnami\solr-4.10.3-0/apache-solr\scripts\prunsrv.exe"</i> :
|
||||||
@ -49,7 +59,7 @@ If you do not have a JRE installed, proceed to step 2. If you have a JRE install
|
|||||||
<br><br>
|
<br><br>
|
||||||
\image html updatedServiceInstall.PNG
|
\image html updatedServiceInstall.PNG
|
||||||
<br><br>
|
<br><br>
|
||||||
11. Edit <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr\solr.xml"</i> to set the _transientCacheSize_ to the maximum number of cases expected to be open concurrently. If you expect ten concurrent cases, the text to add is
|
3. Edit <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr\solr.xml"</i> to set the _transientCacheSize_ to the maximum number of cases expected to be open concurrently. If you expect ten concurrent cases, the text to add is
|
||||||
<i>\<int name="transientCacheSize">10\</int></i>
|
<i>\<int name="transientCacheSize">10\</int></i>
|
||||||
<br><br>
|
<br><br>
|
||||||
The added part is highlighted in yellow below. Ensure that it is inside the <i>\<solr></i> tag as follows:
|
The added part is highlighted in yellow below. Ensure that it is inside the <i>\<solr></i> tag as follows:
|
||||||
@ -58,7 +68,7 @@ The added part is highlighted in yellow below. Ensure that it is inside the <i>\
|
|||||||
<br>
|
<br>
|
||||||
Again you may have trouble saving to the file in the current location. If so, just save it out to the desktop and copy the edited file back over the top of the original.
|
Again you may have trouble saving to the file in the current location. If so, just save it out to the desktop and copy the edited file back over the top of the original.
|
||||||
<br><br>
|
<br><br>
|
||||||
12. Edit <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\resources/log4j.properties"</i> to configure Solr log settings:
|
4. Edit <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\resources/log4j.properties"</i> to configure Solr log settings:
|
||||||
- Increase the log rotation size threshold (_log4j\.appender\.file\.MaxFileSize_) from 4MB to 100MB.
|
- Increase the log rotation size threshold (_log4j\.appender\.file\.MaxFileSize_) from 4MB to 100MB.
|
||||||
- Remove the _CONSOLE_ appender from the _log4j\.rootLogger_ line.
|
- Remove the _CONSOLE_ appender from the _log4j\.rootLogger_ line.
|
||||||
<br><br>
|
<br><br>
|
||||||
@ -66,17 +76,17 @@ The log file should end up looking like this (modified lines are highlighted in
|
|||||||
<br><br>
|
<br><br>
|
||||||
\image html log4j.PNG
|
\image html log4j.PNG
|
||||||
<br><br>
|
<br><br>
|
||||||
13. Edit the file <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr\zoo.cfg"</i> to increase the _tickTime_ value to 15000 as shown in the screenshot below.
|
5. Edit the file <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr\zoo.cfg"</i> to increase the _tickTime_ value to 15000 as shown in the screenshot below.
|
||||||
<br><br>
|
<br><br>
|
||||||
\image html tickTime.PNG
|
\image html tickTime.PNG
|
||||||
<br><br>
|
<br><br>
|
||||||
14. Edit the file <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr\zoo.cfg"</i> to set the value <i>dataDir=C:/Bitnami/zookeeper</i> as shown in the screenshot below.
|
6. Edit the file <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr\zoo.cfg"</i> to set the value <i>dataDir=C:/Bitnami/zookeeper</i> as shown in the screenshot below.
|
||||||
<br><br>
|
<br><br>
|
||||||
\image html dataDir.PNG
|
\image html dataDir.PNG
|
||||||
<br><br>
|
<br><br>
|
||||||
15. Copy the folder _configsets_ from your Autopsy installation (<i>"C:\Program Files (x86)\Autopsy-4.0\autopsy\solr\solr"</i> for 32-bit or <i>"C:\Program Files\Autopsy-4.0\autopsy\solr\solr"</i> for 64-bit) to <i>"C:\\Bitnami\\solr-4.10.3-0\\apache-solr\\solr"</i>
|
7. From an Autopsy installation, copy the folder <i>"C:\Program Files\Autopsy-4.0\autopsy\solr\solr\configsets"</i> to <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr"</i>.
|
||||||
16. Copy the folder _lib_ from your Autopsy installation (<i>"C:\Program Files (x86)\Autopsy-4.0\autopsy\solr\solr"</i> for 32-bit or <i>"C:\Program Files\Autopsy-4.0\autopsy\solr\solr"</i> for 64-bit) to <i>"C:\\Bitnami\\solr-4.10.3-0\\apache-solr\\solr"</i>
|
8. From an Autopsy installation, copy the folder <i>"C:\Program Files\Autopsy-4.0\autopsy\solr\solr\lib"</i> to <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr"</i>.
|
||||||
17. Right-click on the file <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\scripts\serviceinstall.bat"</i> and click "Run As Administrator", selecting _Yes_ if prompted by User Account Control.
|
9. Right-click on the file <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\scripts\serviceinstall.bat"</i> and click "Run As Administrator", selecting _Yes_ if prompted by User Account Control.
|
||||||
|
|
||||||
If there is no "Run as administrator" option when you right-click the _serviceinstall.bat_ file, start a Windows command prompt as administrator by pressing _Start_, typing _command_, right clicking on _Command Prompt_, and clicking on _Run as administrator_. Then run the following command to install the _solrJetty_ service:
|
If there is no "Run as administrator" option when you right-click the _serviceinstall.bat_ file, start a Windows command prompt as administrator by pressing _Start_, typing _command_, right clicking on _Command Prompt_, and clicking on _Run as administrator_. Then run the following command to install the _solrJetty_ service:
|
||||||
<br><br>
|
<br><br>
|
||||||
@ -86,13 +96,13 @@ The log file should end up looking like this (modified lines are highlighted in
|
|||||||
<br><br>
|
<br><br>
|
||||||
\image html solrinstall1.PNG
|
\image html solrinstall1.PNG
|
||||||
<br><br>
|
<br><br>
|
||||||
18. Press _Start_, type _services.msc_, and press _Enter_. Find _solrJetty_. If the service is running, press _Stop the service_, then double click it, and switch to the _Log On_ tab to change the logon credentials to a user who will have access to read and write the primary shared drive. If the machine is on a domain, the Account Name will be in the form of _DOMAINNAME\\username_ as shown in the example below. Note that in the screenshot below, the domain name is _DOMAIN_ and the user name is _username_. These are just examples, not real values.
|
10. Press _Start_, type _services.msc_, and press _Enter_. Find _solrJetty_. If the service is running, press _Stop the service_, then double click it, and switch to the _Log On_ tab to change the logon credentials to a user who will have access to read and write the primary shared drive. If the machine is on a domain, the Account Name will be in the form of _DOMAINNAME\\username_ as shown in the example below. Note that in the screenshot below, the domain name is _DOMAIN_ and the user name is _username_. These are just examples, not real values.
|
||||||
<br><br>
|
<br><br>
|
||||||
\image html solrinstall2.PNG
|
\image html solrinstall2.PNG
|
||||||
<br>
|
<br>
|
||||||
If the machine is on a domain, **make sure** to select the domain with the mouse by going to the _Log On_ tab, clicking _Browse_, then clicking _Locations_ and selecting the domain of interest. Then enter the user name desired and press _Check Names_. When that completes, press _OK_, type in the password once for each box and press _OK_. You may see "The user has been granted the log on as a service right."
|
If the machine is on a domain, **make sure** to select the domain with the mouse by going to the _Log On_ tab, clicking _Browse_, then clicking _Locations_ and selecting the domain of interest. Then enter the user name desired and press _Check Names_. When that completes, press _OK_, type in the password once for each box and press _OK_. You may see "The user has been granted the log on as a service right."
|
||||||
|
|
||||||
19. You should be able to see the Solr service in a web browser via the URL <i>http://localhost:8983/solr/#/</i> as shown in the screenshot below.
|
11. You should be able to see the Solr service in a web browser via the URL <i>http://localhost:8983/solr/#/</i> as shown in the screenshot below.
|
||||||
<br><br>
|
<br><br>
|
||||||
\image html solrinstall3.PNG
|
\image html solrinstall3.PNG
|
||||||
<br><br>
|
<br><br>
|
||||||
|
@ -5,68 +5,28 @@
|
|||||||
\section prereqs Prerequisites
|
\section prereqs Prerequisites
|
||||||
It is _highly_ recommended to remove or disable any antivirus software from computers that will be processing or reviewing cases. Antivirus software will often conflict with forensic software, and may quarantine or even delete some of your results before you get a chance to look at them.
|
It is _highly_ recommended to remove or disable any antivirus software from computers that will be processing or reviewing cases. Antivirus software will often conflict with forensic software, and may quarantine or even delete some of your results before you get a chance to look at them.
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
|
|
||||||
|
\section install Deployment Types
|
||||||
|
Starting with Autopsy 4.0, there are two ways to deploy Autopsy:
|
||||||
|
- **Single-User**: Cases can be open by only a single instance of Autopsy at a time. Autopsy installations do not communicate with each other. This is the easiest to install and deploy. This page outlines that installation process.
|
||||||
|
- **Multi-User**: Cases can be open by multiple users at the same time and users can see what each other is doing. This collaborative deployment requires installation and configuration of other network-based services. The installation of this deployment is covered in \ref install_multiuser_page.
|
||||||
|
|
||||||
|
|
||||||
\section download Download
|
\section download Download
|
||||||
Download Autopsy from the website:
|
Download Autopsy from the website:
|
||||||
|
|
||||||
http://sleuthkit.org/autopsy/download.php
|
http://sleuthkit.org/autopsy/download.php
|
||||||
|
|
||||||
The current version of Autopsy 3 runs only on Microsoft Windows.
|
The current version of Autopsy 4 is distributed on sleuthkit.org only as a Windows installer. It can run on Linux and OS X, but requires some manual setup.
|
||||||
We have gotten older versions to run on other platforms, such as Linux and OS X, but we do not have it in a state that makes it easy to distribute and find the needed libraries.
|
|
||||||
<br><br>
|
|
||||||
|
|
||||||
\section installation_section Installation
|
|
||||||
|
\section install_standalone Installation
|
||||||
To install Autopsy, perform the following steps:
|
To install Autopsy, perform the following steps:
|
||||||
1. Run the Autopsy _msi_ file
|
1. Run the Autopsy _msi_ file
|
||||||
2. If Windows prompts with User Account Control, click _Yes_
|
2. If Windows prompts with User Account Control, click _Yes_
|
||||||
3. Click through the dialog boxes until you click a button that says _Finish_
|
3. Click through the dialog boxes until you click a button that says _Finish_
|
||||||
4. Core Autopsy should now be fully installed
|
4. Autopsy should now be fully installed
|
||||||
|
|
||||||
<br>
|
|
||||||
\section deployment_types Deployment Types
|
|
||||||
There are two types of cases that Autopsy can create and use:
|
|
||||||
- **Standalone**: A single user with a single computer. Not intended to have multiple examiners working on the same case.
|
|
||||||
- **Collaborative**: A multi-user environment with multiple computers. Multiple examiners can work on the same case at the same time.
|
|
||||||
|
|
||||||
Both deployment types use the same analysis modules and the same base installer.
|
|
||||||
<br><br>
|
|
||||||
\subsection standalone_install Standalone (Single User) Installation
|
|
||||||
1. Install Autopsy as explained in \ref installation_section above. The Windows installer is self-contained and will place everything in the needed places. Simply follow the standard prompts for installation.
|
|
||||||
|
|
||||||
<br>
|
|
||||||
\subsection collab_install Collaborative (Multi-user) Installation
|
|
||||||
To use the Multi-user collaboration feature, three additional software packages are required. These packages install servers that need to be accessible to machines running Autopsy collaborative cases via the network. These servers do not have to be installed on the same machine as Autopsy, nor on the same machine as each other.
|
|
||||||
|
|
||||||
1. Install Autopsy just as in \ref standalone_install above
|
|
||||||
2. Install and configure Apache ActiveMQ on a machine accessible to Autopsy nodes. This is explained in \ref install_activemq.
|
|
||||||
3. Install and configure Bitnami Solr on a machine accessible to Autopsy nodes. This is explained in \ref install_solr.
|
|
||||||
4. Install and configure PostgreSQL on a machine accessible to Autopsy nodes. This is explained in \ref install_postgresql.
|
|
||||||
5. Configure Multi-user settings. This is explained in \ref multi_user_options.
|
|
||||||
|
|
||||||
<br>
|
|
||||||
While you may run all of the external services (ActiveMQ, Solr, and PostgreSQL) on the same machine that is running Autopsy, this is not ideal. Spreading the services out across several machines can improve throughput. Keep in mind that all the machines need to be able to communicate with each other and see the shared drive over the network.
|
|
||||||
If possible, place Solr on a machine by itself, as it is the largest RAM and CPU utilizer among the servers.
|
|
||||||
<br><br>
|
|
||||||
To use Multi-user cases, there needs to be a shared network drive accessible to all participating computers.
|
|
||||||
<br><br>
|
|
||||||
|
|
||||||
\section optimizing_performance Optimizing Performance
|
|
||||||
After installing Autopsy, there are several hardware-based things that we suggest you do to optimize performance:
|
|
||||||
1. Change the number of parallel pipelines used at run time. The default is two pipelines, but this can be increased if you are running on a system with several cores. To do this:
|
|
||||||
- Run Autopsy from the Start Menu or desktop
|
|
||||||
- When presented with the case creation splash screen, cancel/close the window
|
|
||||||
- Select "Tools", "Options"
|
|
||||||
- On the "Autopsy" tab, there is a drop down for _Number of threads to use for file ingest_. We recommend you set this value 4. If you set this number too high, performance can degrade because the pipelines are fighting for the same physical resources. Individual testing should be done to find an optimal setting.
|
|
||||||
- After each change, restart Autopsy to let this setting take effect.
|
|
||||||
<br><br>
|
|
||||||
\image html threadcount.PNG
|
|
||||||
<br><br>
|
|
||||||
2. In the screenshot above, there is an option to <i>Enable timeout to allow modules to automatically terminate after a set amount of time</i>. Enabling this feature by applying a checkmark and setting a number of hours puts a maximum amount of time an individual module may attempt to process before being stopped. If enabled and a module attempts to run for longer than this value, Autopsy stops the module and moves on to process the next module. This allows processing to continue even if a rogue module does not end appropriately in a reasonable amount of time.
|
|
||||||
<br><br>
|
|
||||||
3. When making a case, use different drives to store the case and the images. The case directory is where the SQLite database and keyword search index are stored in Single-user cases. This allows the maximum amount of data to be read and written at the same time. If using collaborative Multi-user mode, it is important that UNC paths are used to specifiy drive names. Fully-specified UNC paths should be in the form of <i>\\\\hostname\\sharename\\folder</i>.
|
|
||||||
<br><br>
|
|
||||||
4. We have had best performance using either solid state drives or fibre channel-attached SAN storage.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
@ -6,7 +6,7 @@ Overview
|
|||||||
|
|
||||||
This is the User's Guide for the <a href="http://www.sleuthkit.org/autopsy/">open source Autopsy platform</a>. Autopsy allows you to examine a hard drive or mobile device and recover evidence from it. This guide should help you with using Autopsy. The <a href="http://www.sleuthkit.org/autopsy/docs/api-docs/3.1/"> developer's guide</a> will help you develop your own Autopsy modules.
|
This is the User's Guide for the <a href="http://www.sleuthkit.org/autopsy/">open source Autopsy platform</a>. Autopsy allows you to examine a hard drive or mobile device and recover evidence from it. This guide should help you with using Autopsy. The <a href="http://www.sleuthkit.org/autopsy/docs/api-docs/3.1/"> developer's guide</a> will help you develop your own Autopsy modules.
|
||||||
|
|
||||||
Autopsy 3 is a complete rewrite from Autopsy 2, and none of this document is relevant to Autopsy 2.
|
Autopsy 4 (and 3) are a complete rewrite from Autopsy 2, and none of this document is relevant to Autopsy 2.
|
||||||
|
|
||||||
Help Topics
|
Help Topics
|
||||||
-------
|
-------
|
||||||
@ -15,11 +15,6 @@ The following topics are available here:
|
|||||||
- \subpage installation_page
|
- \subpage installation_page
|
||||||
- \subpage quick_start_guide "Quick Start Guide"
|
- \subpage quick_start_guide "Quick Start Guide"
|
||||||
- \subpage workflow_page
|
- \subpage workflow_page
|
||||||
- Multi-user Collaboration Setup
|
|
||||||
- \subpage install_activemq
|
|
||||||
- \subpage install_postgresql
|
|
||||||
- \subpage install_solr
|
|
||||||
- \subpage multiuser_page
|
|
||||||
- Cases and Adding Data Sources
|
- Cases and Adding Data Sources
|
||||||
- \subpage cases_page
|
- \subpage cases_page
|
||||||
- \subpage ds_page
|
- \subpage ds_page
|
||||||
@ -42,14 +37,21 @@ The following topics are available here:
|
|||||||
- \subpage tree_viewer_page
|
- \subpage tree_viewer_page
|
||||||
- \subpage result_viewer_page
|
- \subpage result_viewer_page
|
||||||
- \subpage content_viewer_page
|
- \subpage content_viewer_page
|
||||||
<!-- - \subpage image_gallery_page Not released yet, coming soon-->
|
- \subpage image_gallery_page
|
||||||
- \subpage file_search_page
|
- \subpage file_search_page
|
||||||
- \subpage timeline_page
|
- \subpage timeline_page
|
||||||
- \subpage stix_page
|
- \subpage stix_page
|
||||||
- Reporting
|
- Reporting
|
||||||
- \subpage tagging_page
|
- \subpage tagging_page
|
||||||
- \subpage reporting_page
|
- \subpage reporting_page
|
||||||
- \subpage module_install_page
|
- \subpage module_install_page
|
||||||
|
- \subpage performance_page
|
||||||
|
- Multi-user Collaborative Deployments
|
||||||
|
- \subpage install_multiuser_page
|
||||||
|
- \subpage install_activemq
|
||||||
|
- \subpage install_postgresql
|
||||||
|
- \subpage install_solr
|
||||||
|
- \subpage multiuser_page
|
||||||
|
|
||||||
If the topic you need is not listed, refer to the <a href="http://wiki.sleuthkit.org/index.php?title=Autopsy_User%27s_Guide">Autopsy Wiki</a> or join the <a href="https://lists.sourceforge.net/lists/listinfo/sleuthkit-users">SleuthKit User List</a> at SourceForge.
|
If the topic you need is not listed, refer to the <a href="http://wiki.sleuthkit.org/index.php?title=Autopsy_User%27s_Guide">Autopsy Wiki</a> or join the <a href="https://lists.sourceforge.net/lists/listinfo/sleuthkit-users">SleuthKit User List</a> at SourceForge.
|
||||||
|
|
||||||
|
@ -1,38 +1,42 @@
|
|||||||
/*! \page multiuser_page Using Multi-user Collaboration
|
/*! \page multiuser_page Using Multi-user Cases
|
||||||
|
|
||||||
\section creating_multi_user_cases Creating Multi-user cases
|
\section creating_multi_user_cases Creating Multi-user cases
|
||||||
The collaborative Multi-user capabilities enable Autopsy cases to be opened by multiple reviewers at the same time allowing simultaneous case review with multiple reviewers.
|
|
||||||
|
|
||||||
|
Multi-user cases allow multiple instances of Autopsy to have the same case open at the same time. When creating a case, users are now presented with a choice of Single-user or Multi-user as shown in the screenshot below.
|
||||||
|
|
||||||
When creating a case, users are now presented with a choice of Single-user or Multi-user as shown in the screenshot below.
|
|
||||||
<br><br>
|
<br><br>
|
||||||
\image html case-newcase.PNG
|
\image html case-newcase.PNG
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
Single-user functions the same as always, with a back end SQLite database and a machine-local version of Solr.
|
Single-user functions the same as always, with a back end SQLite database and a machine-local version of Solr.
|
||||||
|
|
||||||
Multi-user allows multiple computers to open the same case at the same time. In order to accomplish this, some setup needs to take place. To use a Multi-user setup, one must install PostgreSQL, Solr, and ActiveMQ (see \subpage installation_page) on machines connected to the network, and properly configure Autopsy to interact with these services. This configuration is done in the Multi-user options panel, discussed below.
|
To create a multi-user case, the following must occur:
|
||||||
<br><br>
|
- The network services must be installed, configured, and running. See \ref multiuser_install_services.
|
||||||
|
- The Case folder needs to be in a shared folder that all other clients can also access at the same path (UNC or drive letter).
|
||||||
|
- The data sources that are added with the Add Data Source wizard must be in a shared folder that all clients can access at the same path.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
\section multi_user_other Other Multi-user Information
|
||||||
|
|
||||||
|
- When using a multi-user case, other nodes could be running data ingest on the same case. While this is happening, you will see a progress bar labelled with the hostname of the machine performing the ingest on the bottom right of Autopsy. The progress bar will continue to move back and forth until ingest has been completed or cancelled. You can still run ingest on your local machine while this is ongoing. This is shown in the screenshot below.
|
||||||
|
|
||||||
\section multi_user_options Multi-user options panel
|
|
||||||
As shown in the screenshot below, all three services need configuration of IP addresses and ports, and two of them need user names and passwords. Enter the correct information into the dialog and press okay. Note that in order to create or open Multi-user cases, "Enable Multi-user cases" must be checked and the settings below must be correct.
|
|
||||||
<br><br>
|
|
||||||
\image html multiuser_settings.PNG
|
|
||||||
<br><br>
|
|
||||||
Other Multi-user Information
|
|
||||||
=======
|
|
||||||
- When using a case in collaborative Multi-user mode, other nodes could be running data ingest. While this is happening, you will see a progress bar labelled with the hostname of the machine performing the ingest on the bottom right of Autopsy. The progress bar will continue to move back and forth until ingest has been completed or cancelled. You can still run ingest on your local machine while this is ongoing. This is shown in the screenshot below.
|
|
||||||
<br><br>
|
<br><br>
|
||||||
\image html othernodeingesting.PNG
|
\image html othernodeingesting.PNG
|
||||||
<br><br>
|
<br><br>
|
||||||
- When issues occur, there is an information "bubble" on the bottom right of the screen. It has an "i" inside a circle, with the color of the circle changed based upon the message. It uses red for bad and blue for good. See the screenshot below.
|
|
||||||
|
- When issues occur, there is an information "bubble" on the bottom right of the screen. It has an "i" inside a circle, with the color of the circle changed based upon the message. It uses red for bad and blue for good. See the screenshot below.
|
||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
\image html messagebubbles.PNG
|
\image html messagebubbles.PNG
|
||||||
<br><br>
|
<br><br>
|
||||||
- Clicking on the information "bubble" brings up the list of prior notifications that have not been dismissed by clicking on the "x". As you can see in the screenshot below, the network cable was unplugged from the machine and it lost all connection to the three services. When the cable was reconnected, it found the services again.
|
|
||||||
|
- Clicking on the information "bubble" brings up the list of prior notifications that have not been dismissed by clicking on the "x". As you can see in the screenshot below, the network cable was unplugged from the machine and it lost all connection to the three services. When the cable was reconnected, it found the services again.
|
||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
\image html messagebubblesbigger.PNG
|
\image html messagebubblesbigger.PNG
|
||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
- While using collaborative Multi-user mode, it is important that UNC paths are used to specifiy drive names. Drive mapping will work, but it is sometimes difficult to get all the machines participating in a case to map to the same drive letters for the same resources. It is much simpler to use fully-specified UNC paths in the form of <i>\\\\hostname\\sharename\\folder</i>.
|
|
||||||
|
- When creating multi-user cases, we recommend using UNC paths to specify drive names. Drive mapping will work, but it is sometimes difficult to get all the machines participating in a case to map to the same drive letters for the same resources. It is much simpler to use fully-specified UNC paths in the form of <i>\\\\hostname\\sharename\\folder</i>.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
19
docs/doxygen-user/performance.dox
Normal file
19
docs/doxygen-user/performance.dox
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*! \page performance_page Optimizing Performance
|
||||||
|
|
||||||
|
After installing Autopsy, there are several hardware-based things that we suggest you do to optimize performance:
|
||||||
|
|
||||||
|
1. Number of Threads: Change the number of parallel pipelines used at run time. The default is two pipelines, but this can be increased if you are running on a system with several cores. To do this:
|
||||||
|
- Run Autopsy from the Start Menu or desktop
|
||||||
|
- When presented with the case creation splash screen, cancel/close the window
|
||||||
|
- Select "Tools", "Options"
|
||||||
|
- On the "Autopsy" tab, there is a drop down for _Number of threads to use for file ingest_. We recommend you set this value 4. If you set this number too high, performance can degrade because the pipelines are fighting for the same physical resources. Individual testing should be done to find an optimal setting.
|
||||||
|
- After each change, restart Autopsy to let this setting take effect.
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
\image html threadcount.PNG
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
2. When making a case, use different drives to store the case and the images. This allows the maximum amount of data to be read and written at the same time.
|
||||||
|
|
||||||
|
3. We have had best performance using either solid state drives or fibre channel-attached SAN storage.
|
||||||
|
*/
|
@ -38,7 +38,7 @@ PROJECT_NAME = "Autopsy"
|
|||||||
# could be handy for archiving the generated documentation or if some version
|
# could be handy for archiving the generated documentation or if some version
|
||||||
# control system is used.
|
# control system is used.
|
||||||
|
|
||||||
PROJECT_NUMBER = 3.1
|
PROJECT_NUMBER = 4.0
|
||||||
|
|
||||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||||
# for a project that appears at the top of each page and should give viewer a
|
# for a project that appears at the top of each page and should give viewer a
|
||||||
@ -1063,7 +1063,7 @@ GENERATE_HTML = YES
|
|||||||
# The default directory is: html.
|
# The default directory is: html.
|
||||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||||
|
|
||||||
HTML_OUTPUT = api-docs/3.1/
|
HTML_OUTPUT = api-docs/4.0/
|
||||||
|
|
||||||
# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
|
# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
|
||||||
# generated HTML page (for example: .htm, .php, .asp).
|
# generated HTML page (for example: .htm, .php, .asp).
|
||||||
@ -2058,7 +2058,7 @@ SKIP_FUNCTION_MACROS = YES
|
|||||||
# the path). If a tag file is not located in the directory in which doxygen is
|
# the path). If a tag file is not located in the directory in which doxygen is
|
||||||
# run, you must also specify the path to the tagfile here.
|
# run, you must also specify the path to the tagfile here.
|
||||||
|
|
||||||
TAGFILES = $(TSK_HOME)/tskjni_doxygen.tag=http://www.sleuthkit.org/sleuthkit/docs/jni-docs/
|
TAGFILES = $(TSK_HOME)/bindings/java/doxygen/tskjni_doxygen.tag=http://www.sleuthkit.org/sleuthkit/docs/jni-docs/4.3/
|
||||||
|
|
||||||
# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
|
# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
|
||||||
# tag file that is based on the input files it reads. See section "Linking to
|
# tag file that is based on the input files it reads. See section "Linking to
|
||||||
|
@ -26,17 +26,25 @@ There are also a set of tutorials that Basis Technology published on their blog:
|
|||||||
-You don't really need anything to develop a python Autopsy module except for the standard Autopsy and your favorite text editor. We recommend IntelliJ IDEA or the Jython plug-in to NetBeans.
|
-You don't really need anything to develop a python Autopsy module except for the standard Autopsy and your favorite text editor. We recommend IntelliJ IDEA or the Jython plug-in to NetBeans.
|
||||||
|
|
||||||
To install NetBeans' plug-in:
|
To install NetBeans' plug-in:
|
||||||
-# Download and install the Jython 2.7 installer (http://www.jython.org/downloads.html).
|
-# Download and install the Jython 2.7 installer to desired location (http://www.jython.org/downloads.html).
|
||||||
-# Download NetBeans Python plug-in zip file (http://plugins.netbeans.org/plugin/56795/python4netbeans802).
|
-# Download NetBeans Python plug-in zip file (http://plugins.netbeans.org/plugin/56795/python4netbeans802).
|
||||||
-# Unpack the content (.nbm files) of the zip file to the desired location.
|
-# Unpack the content (.nbm files) of the zip file to the desired location.
|
||||||
-# In NetBeans go to Tools->Plugins. In Download tab, click on Add Plugins, then choose extracted .nbm files.
|
-# In NetBeans go to Tools->Plugins. In Downloaded tab, click on Add Plugins, then choose extracted .nbm files.
|
||||||
-# Setup Jython path from Tools->Python Platform, click on new, then choose Jython.exe (usually in C:/Program files/Jython2.7/bin/)
|
-# Setup Jython path from Tools->Python Platforms, click on new, then choose Jython.exe (usually in C:/Program files/Jython2.7/bin/)
|
||||||
|
|
||||||
To install IntelliJ IDEA + Python plug-in:
|
To install IntelliJ IDEA + Python plug-in:
|
||||||
-# Download and install IDEA https://www.jetbrains.com/idea/download/
|
-# Download java JDK depending on platform. Install to desired location (http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html).
|
||||||
-# In File->Settings->Plugins-> install Python Community Edition
|
-# Download and install IDEA Community Edition to desired location (https://www.jetbrains.com/idea/download/).
|
||||||
-# In File->Project Structure->Project-> Project SDK-> choose IntelliJ IDEA Community Edition
|
-# Open IDEA and choose desired UI theme. Continue with default settings.
|
||||||
-# In Libraries->add new libraries->choose desired autopsy modules (usually in C:\Program Files\Autopsy-3.1.3\autopsy\modules)
|
-# Choose to either create a new empty project or open an existing one.
|
||||||
|
-# It will ask you to modify Project Structure. Leave that for now and click OK.
|
||||||
|
-# In File->Settings. Go to Plugins tab and click on Install JetBrains Plugin.
|
||||||
|
-# Look for and install Python Community Edition. After the installation, it will ask you restart. Restart IDEA.
|
||||||
|
-# In File->Project Structure. In Project tab, Project SDK, click on New and choose IntelliJ Platform Plugin SKD.
|
||||||
|
-# It will ask you to configure the JKD first, click OK and navigate to the JDK folder location and click OK.
|
||||||
|
-# After that it will ask you to choose the IntelliJ Platform Plugin SKD. It will most likely take you to it's locaation automatically. (Usually in C:\Program Files (x86)\JetBrains\IntelliJ IDEA Community Edition 14.1.5)
|
||||||
|
-# In the drop down menu next to New button, choose IntelliJ IDEA Community Edition.
|
||||||
|
-# Still in Project STructure, In Libraries tab, click on '+' to add new libraries. Choose desired autopsy modules (usually in C:\Program Files\Autopsy-3.1.3\autopsy\modules if you have executable version).
|
||||||
|
|
||||||
\section mod_dev_py_create Creating a Basic Python Module
|
\section mod_dev_py_create Creating a Basic Python Module
|
||||||
|
|
||||||
|
@ -8,14 +8,15 @@ The easiest guidance (from http://bits.netbeans.org/dev/javadoc/org-openide-modu
|
|||||||
|
|
||||||
\section native_autopsy Autopsy Native Libraries
|
\section native_autopsy Autopsy Native Libraries
|
||||||
|
|
||||||
Autopsy has two types of native libraries:
|
Autopsy has three types of native libraries that it depends on (and ships with):
|
||||||
- The libtsk_jni native library
|
- The Sleuth Kit JNI Library(libtsk_jni)
|
||||||
- The libraries that libtsk_jni depends on
|
- This is embedded inside of the <tt>Tsk_DataModel.jar</tt> file. It gets copied into there by the datamodel ant target.
|
||||||
|
- At runtime, java code extracts the file from the jar into a local temp folder and launches it in the LibraryUtils.loadSleuthkitJNI() method.
|
||||||
<tt>libtsk_jni</tt> is embedded inside of the <tt>Tsk_DataModel.jar</tt> file from The Sleuth Kit. The java code extracts the file from the jar into a local temp folder and launches it in the LibraryUtils.loadSleuthkitJNI() method.
|
- The libraries that libtsk_jni depends on that are not the Visual Studio runtime dlls that were used to build libtsk_jni
|
||||||
|
- These are copied by Autopsy from TSK_HOME to a folder inside of Autopsy at compile time.
|
||||||
The libraries that <tt>libtsk_jni</tt> depends on are launched by Autopsy in the Installer.loadDynLibraries() method. This is because if we wait until <tt>libtsk_jni</tt> needs them, then they will be located based on Windows search paths and the NetBeans paths are not in that set. So, we launch them before <tt>libtsk_jni</tt> needs them and from within Autopsy so that it uses the Autopsy search pathes.
|
- They are loaded by Autopsy in the Installer.loadDynLibraries() method. This is because if we wait until <tt>libtsk_jni</tt> needs them, then they will be located based on Windows search paths and the NetBeans paths are not in that set. So, we launch them before <tt>libtsk_jni</tt> needs them and from within Autopsy so that it uses the Autopsy search paths.
|
||||||
|
- The Visual Studio runtime dlls that were used to build libtsk_jni
|
||||||
There is code in build-windows.xml and build-unix.xml to copy the external libraries into their respective locations when a ZIP package is made of the program. These libraries must be accessible via normal launching methods when developing Autopsy (i.e. we only copy them into the Autopsy structure when building the ZIP).
|
- These are only copied by Autopsy when a ZIP folder is created of the project (on the theory that the local system doesn't need copies of them because it is already building TSK). There is code in build-windows.xml and build-unix.xml to copy the external libraries (from thidrparty/crt) into their respective locations in the ZIP file.
|
||||||
|
- NOTE: If other versions of the runtimes need to be included (because of the other 3rd party libraries), then they should be treated like other 3rd party libraries and copied at compile time.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
branding.token=autopsy
|
branding.token=autopsy
|
||||||
|
nbjdk.active=JDK_1.8_66
|
||||||
# Version of platform that is automatically downloaded
|
# Version of platform that is automatically downloaded
|
||||||
# Note build.xml has similar definitions that should be kept in sync (manually)
|
# Note build.xml has similar definitions that should be kept in sync (manually)
|
||||||
netbeans-plat-version=8.0.2
|
netbeans-plat-version=8.0.2
|
||||||
@ -13,15 +14,12 @@ cluster.path=\
|
|||||||
${nbplatform.active.dir}/java:\
|
${nbplatform.active.dir}/java:\
|
||||||
${nbplatform.active.dir}/platform
|
${nbplatform.active.dir}/platform
|
||||||
disabled.modules=\
|
disabled.modules=\
|
||||||
org.apache.tools.ant.module,\
|
|
||||||
org.netbeans.api.debugger.jpda,\
|
org.netbeans.api.debugger.jpda,\
|
||||||
org.netbeans.api.java,\
|
org.netbeans.api.java,\
|
||||||
org.netbeans.api.maven,\
|
|
||||||
org.netbeans.lib.nbjavac,\
|
org.netbeans.lib.nbjavac,\
|
||||||
org.netbeans.libs.cglib,\
|
org.netbeans.libs.cglib,\
|
||||||
org.netbeans.libs.javacapi,\
|
org.netbeans.libs.javacapi,\
|
||||||
org.netbeans.libs.javacimpl,\
|
org.netbeans.libs.javacimpl,\
|
||||||
org.netbeans.libs.javafx,\
|
|
||||||
org.netbeans.libs.springframework,\
|
org.netbeans.libs.springframework,\
|
||||||
org.netbeans.modules.ant.browsetask,\
|
org.netbeans.modules.ant.browsetask,\
|
||||||
org.netbeans.modules.ant.debugger,\
|
org.netbeans.modules.ant.debugger,\
|
||||||
@ -33,7 +31,6 @@ disabled.modules=\
|
|||||||
org.netbeans.modules.dbschema,\
|
org.netbeans.modules.dbschema,\
|
||||||
org.netbeans.modules.debugger.jpda,\
|
org.netbeans.modules.debugger.jpda,\
|
||||||
org.netbeans.modules.debugger.jpda.ant,\
|
org.netbeans.modules.debugger.jpda.ant,\
|
||||||
org.netbeans.modules.debugger.jpda.js,\
|
|
||||||
org.netbeans.modules.debugger.jpda.kit,\
|
org.netbeans.modules.debugger.jpda.kit,\
|
||||||
org.netbeans.modules.debugger.jpda.projects,\
|
org.netbeans.modules.debugger.jpda.projects,\
|
||||||
org.netbeans.modules.debugger.jpda.ui,\
|
org.netbeans.modules.debugger.jpda.ui,\
|
||||||
@ -46,8 +43,6 @@ disabled.modules=\
|
|||||||
org.netbeans.modules.form.nb,\
|
org.netbeans.modules.form.nb,\
|
||||||
org.netbeans.modules.form.refactoring,\
|
org.netbeans.modules.form.refactoring,\
|
||||||
org.netbeans.modules.hibernate,\
|
org.netbeans.modules.hibernate,\
|
||||||
org.netbeans.modules.hibernate4lib,\
|
|
||||||
org.netbeans.modules.hibernatelib,\
|
|
||||||
org.netbeans.modules.hudson.ant,\
|
org.netbeans.modules.hudson.ant,\
|
||||||
org.netbeans.modules.hudson.maven,\
|
org.netbeans.modules.hudson.maven,\
|
||||||
org.netbeans.modules.i18n,\
|
org.netbeans.modules.i18n,\
|
||||||
@ -69,21 +64,16 @@ disabled.modules=\
|
|||||||
org.netbeans.modules.java.examples,\
|
org.netbeans.modules.java.examples,\
|
||||||
org.netbeans.modules.java.freeform,\
|
org.netbeans.modules.java.freeform,\
|
||||||
org.netbeans.modules.java.guards,\
|
org.netbeans.modules.java.guards,\
|
||||||
org.netbeans.modules.java.helpset,\
|
|
||||||
org.netbeans.modules.java.hints,\
|
org.netbeans.modules.java.hints,\
|
||||||
org.netbeans.modules.java.hints.declarative,\
|
org.netbeans.modules.java.hints.declarative,\
|
||||||
org.netbeans.modules.java.hints.declarative.test,\
|
org.netbeans.modules.java.hints.declarative.test,\
|
||||||
org.netbeans.modules.java.hints.legacy.spi,\
|
org.netbeans.modules.java.hints.legacy.spi,\
|
||||||
org.netbeans.modules.java.hints.test,\
|
org.netbeans.modules.java.hints.test,\
|
||||||
org.netbeans.modules.java.hints.ui,\
|
org.netbeans.modules.java.hints.ui,\
|
||||||
org.netbeans.modules.java.j2sedeploy,\
|
|
||||||
org.netbeans.modules.java.j2seembedded,\
|
|
||||||
org.netbeans.modules.java.j2seplatform,\
|
org.netbeans.modules.java.j2seplatform,\
|
||||||
org.netbeans.modules.java.j2seprofiles,\
|
|
||||||
org.netbeans.modules.java.j2seproject,\
|
org.netbeans.modules.java.j2seproject,\
|
||||||
org.netbeans.modules.java.kit,\
|
org.netbeans.modules.java.kit,\
|
||||||
org.netbeans.modules.java.lexer,\
|
org.netbeans.modules.java.lexer,\
|
||||||
org.netbeans.modules.java.metrics,\
|
|
||||||
org.netbeans.modules.java.navigation,\
|
org.netbeans.modules.java.navigation,\
|
||||||
org.netbeans.modules.java.platform,\
|
org.netbeans.modules.java.platform,\
|
||||||
org.netbeans.modules.java.preprocessorbridge,\
|
org.netbeans.modules.java.preprocessorbridge,\
|
||||||
@ -95,7 +85,6 @@ disabled.modules=\
|
|||||||
org.netbeans.modules.java.sourceui,\
|
org.netbeans.modules.java.sourceui,\
|
||||||
org.netbeans.modules.java.testrunner,\
|
org.netbeans.modules.java.testrunner,\
|
||||||
org.netbeans.modules.javadoc,\
|
org.netbeans.modules.javadoc,\
|
||||||
org.netbeans.modules.javaee.injection,\
|
|
||||||
org.netbeans.modules.javawebstart,\
|
org.netbeans.modules.javawebstart,\
|
||||||
org.netbeans.modules.jellytools.java,\
|
org.netbeans.modules.jellytools.java,\
|
||||||
org.netbeans.modules.junit,\
|
org.netbeans.modules.junit,\
|
||||||
@ -116,8 +105,6 @@ disabled.modules=\
|
|||||||
org.netbeans.modules.maven.repository,\
|
org.netbeans.modules.maven.repository,\
|
||||||
org.netbeans.modules.maven.search,\
|
org.netbeans.modules.maven.search,\
|
||||||
org.netbeans.modules.maven.spring,\
|
org.netbeans.modules.maven.spring,\
|
||||||
org.netbeans.modules.nashorn.execution,\
|
|
||||||
org.netbeans.modules.performance,\
|
|
||||||
org.netbeans.modules.performance.java,\
|
org.netbeans.modules.performance.java,\
|
||||||
org.netbeans.modules.projectimport.eclipse.core,\
|
org.netbeans.modules.projectimport.eclipse.core,\
|
||||||
org.netbeans.modules.projectimport.eclipse.j2se,\
|
org.netbeans.modules.projectimport.eclipse.j2se,\
|
||||||
@ -130,8 +117,6 @@ disabled.modules=\
|
|||||||
org.netbeans.modules.websvc.jaxws21,\
|
org.netbeans.modules.websvc.jaxws21,\
|
||||||
org.netbeans.modules.websvc.jaxws21api,\
|
org.netbeans.modules.websvc.jaxws21api,\
|
||||||
org.netbeans.modules.websvc.saas.codegen.java,\
|
org.netbeans.modules.websvc.saas.codegen.java,\
|
||||||
org.netbeans.modules.whitelist,\
|
|
||||||
org.netbeans.modules.xml.jaxb,\
|
org.netbeans.modules.xml.jaxb,\
|
||||||
org.netbeans.modules.xml.tools.java,\
|
org.netbeans.modules.xml.tools.java,\
|
||||||
org.netbeans.spi.java.hints
|
org.netbeans.spi.java.hints
|
||||||
|
|
||||||
|
BIN
thirdparty/crt/win64/msvcr120.dll
vendored
BIN
thirdparty/crt/win64/msvcr120.dll
vendored
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user