diff --git a/Core/ivy.xml b/Core/ivy.xml
index 9dfd0fcf85..54ed532feb 100644
--- a/Core/ivy.xml
+++ b/Core/ivy.xml
@@ -45,6 +45,9 @@
+
+
+
diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties
index 955079d04a..8209da0221 100644
--- a/Core/nbproject/project.properties
+++ b/Core/nbproject/project.properties
@@ -111,6 +111,10 @@ file.reference.grpc-context-1.19.0.jar=release/modules/ext/grpc-context-1.19.0.j
file.reference.opencensus-api-0.19.2.jar=release/modules/ext/opencensus-api-0.19.2.jar
file.reference.opencensus-contrib-http-util-0.19.2.jar=release/modules/ext/opencensus-contrib-http-util-0.19.2.jar
file.reference.threetenbp-1.3.3.jar=release/modules/ext/threetenbp-1.3.3.jar
+file.reference.okhttp-2.7.5-javadoc.jar=release/modules/ext/okhttp-2.7.5-javadoc.jar
+file.reference.okhttp-2.7.5-sources.jar=release/modules/ext/okhttp-2.7.5-sources.jar
+file.reference.okhttp-2.7.5.jar=release/modules/ext/okhttp-2.7.5.jar
+file.reference.okio-1.6.0.jar=release/modules/ext/okio-1.6.0.jar
javac.source=1.8
javac.compilerargs=-Xlint -Xlint:-serial
license.file=../LICENSE-2.0.txt
diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml
index 1ac3c9d2fd..3dbc3069e3 100644
--- a/Core/nbproject/project.xml
+++ b/Core/nbproject/project.xml
@@ -773,6 +773,14 @@
ext/google-api-services-translate-v2-rev20170525-1.27.0.jarrelease/modules/ext/google-api-services-translate-v2-rev20170525-1.27.0.jar
+
+ ext/okhttp-2.7.5.jar
+ release/modules/ext/okhttp-2.7.5.jar
+
+
+ ext/okio-1.6.0.jar
+ release/modules/ext/okio-1.6.0.jar
+
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties
index 2683f71d6d..85bd858a97 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties
@@ -238,13 +238,7 @@ OpenMultiUserCasePanel.cancelButton.text=Cancel
OpenMultiUserCasePanel.openSingleUserCaseButton.text=Open Single-User Case...
OpenMultiUserCasePanel.openSelectedCaseButton.text=Open Selected Case
OpenMultiUserCasePanel.searchLabel.text=Select any case and start typing to search by case name
-LogicalImagerPanel.jLabel1.text=Insert external drive
-LogicalImagerPanel.scanButton.text=Scan
-LogicalImagerPanel.jLabel6.text=Or, pick a Logical Imager folder
LogicalImagerPanel.browseButton.text=Browse
-LogicalImagerPanel.topLabel.text=Import Autopsy Imager Results
-LogicalImagerPanel.selectDriveLabel.text=Select Drive
-LogicalImagerPanel.messageLabel.text=Error/Status message
UnpackagePortableCaseDialog.desc2Label.text=Portable Case Report Module.
UnpackagePortableCaseDialog.desc1Label.text=Unpackage a portable case so it can be opened in Autopsy. Portable cases are created through the
UnpackagePortableCaseDialog.exitButton.text=Exit
@@ -259,4 +253,13 @@ UnpackagePortableCaseProgressDialog.cancelButton.text=Cancel
UnpackagePortableCaseProgressDialog.okButton.text=OK
UnpackagePortableCaseProgressDialog.resultLabel.text=resultLabel
UnpackagePortableCaseDialog.extractLabel.text=Folder to extract to:
-UnpackagePortableCaseDialog.caseLabel.text=Portable Case:
\ No newline at end of file
+UnpackagePortableCaseDialog.caseLabel.text=Portable Case:
+LogicalImagerPanel.importRadioButton.text=Import From External Drive
+LogicalImagerPanel.manualRadioButton.text=Manually Choose Folder
+LogicalImagerPanel.importRadioButton.toolTipText=
+LogicalImagerPanel.pathTextField.text=
+LogicalImagerPanel.selectFolderLabel.text=Selected Folder:
+LogicalImagerPanel.refreshButton.text=Refresh
+LogicalImagerPanel.selectFromDriveLabel.text=Select Acquisition From Drive
+LogicalImagerPanel.selectDriveLabel.text=Select Drive
+LogicalImagerPanel.messageTextArea.text=
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED
index 5ad9042e9f..1a55b465df 100755
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED
@@ -191,7 +191,6 @@ LogicalImagerDSProcessor.failToCreateDirectory=Failed to create directory {0}
LogicalImagerDSProcessor.imageDirPathNotFound={0} not found.\nUSB drive has been ejected.
LogicalImagerPanel.imageTable.columnModel.title0=Hostname
LogicalImagerPanel.imageTable.columnModel.title1=Extracted Date
-LogicalImagerPanel.messageLabel.clickScanOrBrowse=Click SCAN or BROWSE button to find images
# {0} - sparseImageDirectory
# {1} - image
LogicalImagerPanel.messageLabel.directoryDoesNotContainSparseImage=Directory {0} does not contain {1}
@@ -201,7 +200,6 @@ LogicalImagerPanel.messageLabel.driveHasNoImages=Drive has no images
LogicalImagerPanel.messageLabel.noExternalDriveFound=No drive found
LogicalImagerPanel.messageLabel.noImageSelected=No image selected
LogicalImagerPanel.messageLabel.scanningExternalDrives=Scanning external drives for sparse_image.vhd ...
-LogicalImagerPanel.messageLabel.selectedImage=Selected folder
LogicalImagerPanel.selectAcquisitionFromDriveLabel.text=Select acquisition from Drive
Menu/Case/OpenRecentCase=Open Recent Case
CTL_CaseDeleteAction=Delete Case
@@ -475,13 +473,7 @@ OpenMultiUserCasePanel.cancelButton.text=Cancel
OpenMultiUserCasePanel.openSingleUserCaseButton.text=Open Single-User Case...
OpenMultiUserCasePanel.openSelectedCaseButton.text=Open Selected Case
OpenMultiUserCasePanel.searchLabel.text=Select any case and start typing to search by case name
-LogicalImagerPanel.jLabel1.text=Insert external drive
-LogicalImagerPanel.scanButton.text=Scan
-LogicalImagerPanel.jLabel6.text=Or, pick a Logical Imager folder
LogicalImagerPanel.browseButton.text=Browse
-LogicalImagerPanel.topLabel.text=Import Autopsy Imager Results
-LogicalImagerPanel.selectDriveLabel.text=Select Drive
-LogicalImagerPanel.messageLabel.text=Error/Status message
UnpackagePortableCaseDialog.desc2Label.text=Portable Case Report Module.
UnpackagePortableCaseDialog.desc1Label.text=Unpackage a portable case so it can be opened in Autopsy. Portable cases are created through the
UnpackagePortableCaseDialog.exitButton.text=Exit
@@ -497,3 +489,12 @@ UnpackagePortableCaseProgressDialog.okButton.text=OK
UnpackagePortableCaseProgressDialog.resultLabel.text=resultLabel
UnpackagePortableCaseDialog.extractLabel.text=Folder to extract to:
UnpackagePortableCaseDialog.caseLabel.text=Portable Case:
+LogicalImagerPanel.importRadioButton.text=Import From External Drive
+LogicalImagerPanel.manualRadioButton.text=Manually Choose Folder
+LogicalImagerPanel.importRadioButton.toolTipText=
+LogicalImagerPanel.pathTextField.text=
+LogicalImagerPanel.selectFolderLabel.text=Selected Folder:
+LogicalImagerPanel.refreshButton.text=Refresh
+LogicalImagerPanel.selectFromDriveLabel.text=Select Acquisition From Drive
+LogicalImagerPanel.selectDriveLabel.text=Select Drive
+LogicalImagerPanel.messageTextArea.text=
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LogicalImagerPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalImagerPanel.form
index 79e6241551..f5a370ae4c 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/LogicalImagerPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalImagerPanel.form
@@ -1,6 +1,10 @@
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LogicalImagerPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalImagerPanel.java
index cfc0f87eb1..b54d468d34 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/LogicalImagerPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalImagerPanel.java
@@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.casemodule;
import java.awt.Color;
+import java.awt.Component;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileStore;
@@ -46,7 +47,6 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
* select a file.
*/
@Messages({
- "LogicalImagerPanel.messageLabel.selectedImage=Selected folder",
"LogicalImagerPanel.messageLabel.noImageSelected=No image selected",
"LogicalImagerPanel.messageLabel.driveHasNoImages=Drive has no images",
"LogicalImagerPanel.selectAcquisitionFromDriveLabel.text=Select acquisition from Drive",})
@@ -55,7 +55,6 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
private static final long serialVersionUID = 1L;
private static final String SPARSE_IMAGE_VHD = "sparse_image.vhd"; //NON-NLS
- private static final String SELECTED_IMAGE = Bundle.LogicalImagerPanel_messageLabel_selectedImage();
private static final String NO_IMAGE_SELECTED = Bundle.LogicalImagerPanel_messageLabel_noImageSelected();
private static final String DRIVE_HAS_NO_IMAGES = Bundle.LogicalImagerPanel_messageLabel_driveHasNoImages();
private static final String[] EMPTY_LIST_DATA = {};
@@ -75,6 +74,7 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
private LogicalImagerPanel(String context) {
this.contextName = context;
initComponents();
+ jScrollPane1.setBorder(null);
clearImageTable();
}
@@ -86,13 +86,9 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
*
* @return instance of the LogicalImagerPanel
*/
- @Messages({
- "LogicalImagerPanel.messageLabel.clickScanOrBrowse=Click SCAN or BROWSE button to find images"
- })
public static synchronized LogicalImagerPanel createInstance(String context) {
LogicalImagerPanel instance = new LogicalImagerPanel(context);
// post-constructor initialization of listener support without leaking references of uninitialized objects
- instance.messageLabel.setText(Bundle.LogicalImagerPanel_messageLabel_clickScanOrBrowse());
instance.imageTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
return instance;
}
@@ -104,43 +100,68 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
*/
// //GEN-BEGIN:initComponents
private void initComponents() {
+ bindingGroup = new org.jdesktop.beansbinding.BindingGroup();
- topLabel = new javax.swing.JLabel();
- jLabel1 = new javax.swing.JLabel();
- scanButton = new javax.swing.JButton();
- messageLabel = new javax.swing.JLabel();
+ buttonGroup1 = new javax.swing.ButtonGroup();
+ browseButton = new javax.swing.JButton();
+ importRadioButton = new javax.swing.JRadioButton();
+ manualRadioButton = new javax.swing.JRadioButton();
+ pathTextField = new javax.swing.JTextField();
+ selectFolderLabel = new javax.swing.JLabel();
selectDriveLabel = new javax.swing.JLabel();
+ selectFromDriveLabel = new javax.swing.JLabel();
driveListScrollPane = new javax.swing.JScrollPane();
driveList = new javax.swing.JList<>();
- selectAcquisitionFromDriveLabel = new javax.swing.JLabel();
- jLabel6 = new javax.swing.JLabel();
- browseButton = new javax.swing.JButton();
+ refreshButton = new javax.swing.JButton();
imageScrollPane = new javax.swing.JScrollPane();
imageTable = new javax.swing.JTable();
- jSeparator1 = new javax.swing.JSeparator();
+ jSeparator2 = new javax.swing.JSeparator();
+ jScrollPane1 = new javax.swing.JScrollPane();
+ messageTextArea = new javax.swing.JTextArea();
setMinimumSize(new java.awt.Dimension(0, 65));
setPreferredSize(new java.awt.Dimension(403, 65));
- org.openide.awt.Mnemonics.setLocalizedText(topLabel, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.topLabel.text")); // NOI18N
-
- org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.jLabel1.text")); // NOI18N
-
- org.openide.awt.Mnemonics.setLocalizedText(scanButton, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.scanButton.text")); // NOI18N
- scanButton.addActionListener(new java.awt.event.ActionListener() {
+ org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.browseButton.text")); // NOI18N
+ browseButton.setEnabled(false);
+ browseButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
- scanButtonActionPerformed(evt);
+ browseButtonActionPerformed(evt);
}
});
- org.openide.awt.Mnemonics.setLocalizedText(messageLabel, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.messageLabel.text")); // NOI18N
+ buttonGroup1.add(importRadioButton);
+ importRadioButton.setSelected(true);
+ org.openide.awt.Mnemonics.setLocalizedText(importRadioButton, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.importRadioButton.text")); // NOI18N
+ importRadioButton.setToolTipText(org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.importRadioButton.toolTipText")); // NOI18N
+ importRadioButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ importRadioButtonActionPerformed(evt);
+ }
+ });
+
+ buttonGroup1.add(manualRadioButton);
+ org.openide.awt.Mnemonics.setLocalizedText(manualRadioButton, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.manualRadioButton.text")); // NOI18N
+ manualRadioButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ manualRadioButtonActionPerformed(evt);
+ }
+ });
+
+ pathTextField.setText(org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.pathTextField.text")); // NOI18N
+ pathTextField.setDisabledTextColor(java.awt.Color.black);
+ pathTextField.setEnabled(false);
+
+ org.openide.awt.Mnemonics.setLocalizedText(selectFolderLabel, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.selectFolderLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(selectDriveLabel, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.selectDriveLabel.text")); // NOI18N
+ org.openide.awt.Mnemonics.setLocalizedText(selectFromDriveLabel, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.selectFromDriveLabel.text")); // NOI18N
+
driveList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
driveList.addMouseListener(new java.awt.event.MouseAdapter() {
- public void mouseClicked(java.awt.event.MouseEvent evt) {
- driveListMouseClicked(evt);
+ public void mouseReleased(java.awt.event.MouseEvent evt) {
+ driveListMouseReleased(evt);
}
});
driveList.addKeyListener(new java.awt.event.KeyAdapter() {
@@ -150,14 +171,10 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
});
driveListScrollPane.setViewportView(driveList);
- org.openide.awt.Mnemonics.setLocalizedText(selectAcquisitionFromDriveLabel, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.selectAcquisitionFromDriveLabel.text")); // NOI18N
-
- org.openide.awt.Mnemonics.setLocalizedText(jLabel6, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.jLabel6.text")); // NOI18N
-
- org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.browseButton.text")); // NOI18N
- browseButton.addActionListener(new java.awt.event.ActionListener() {
+ org.openide.awt.Mnemonics.setLocalizedText(refreshButton, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.refreshButton.text")); // NOI18N
+ refreshButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
- browseButtonActionPerformed(evt);
+ refreshButtonActionPerformed(evt);
}
});
@@ -178,8 +195,8 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
imageTable.getTableHeader().setReorderingAllowed(false);
imageTable.setUpdateSelectionOnSort(false);
imageTable.addMouseListener(new java.awt.event.MouseAdapter() {
- public void mouseClicked(java.awt.event.MouseEvent evt) {
- imageTableMouseClicked(evt);
+ public void mouseReleased(java.awt.event.MouseEvent evt) {
+ imageTableMouseReleased(evt);
}
});
imageTable.addKeyListener(new java.awt.event.KeyAdapter() {
@@ -190,72 +207,94 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
imageScrollPane.setViewportView(imageTable);
imageTable.getColumnModel().getSelectionModel().setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
+ jScrollPane1.setBorder(null);
+
+ messageTextArea.setBackground(new java.awt.Color(240, 240, 240));
+ messageTextArea.setColumns(20);
+ messageTextArea.setForeground(java.awt.Color.red);
+ messageTextArea.setLineWrap(true);
+ messageTextArea.setRows(3);
+ messageTextArea.setText(org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.messageTextArea.text")); // NOI18N
+ messageTextArea.setBorder(null);
+ messageTextArea.setDisabledTextColor(java.awt.Color.red);
+ messageTextArea.setEnabled(false);
+ messageTextArea.setMargin(new java.awt.Insets(0, 0, 0, 0));
+
+ org.jdesktop.beansbinding.Binding binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, messageTextArea, org.jdesktop.beansbinding.ELProperty.create("false"), messageTextArea, org.jdesktop.beansbinding.BeanProperty.create("editable"));
+ bindingGroup.addBinding(binding);
+
+ jScrollPane1.setViewportView(messageTextArea);
+
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
- .addGap(238, 238, 238)
- .addComponent(topLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 163, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addGap(0, 0, Short.MAX_VALUE))
- .addGroup(layout.createSequentialGroup()
- .addGap(28, 28, 28)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(messageLabel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup()
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
- .addComponent(jSeparator1, javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(layout.createSequentialGroup()
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(layout.createSequentialGroup()
- .addComponent(selectDriveLabel)
- .addGap(289, 289, 289))
- .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
- .addComponent(scanButton)
- .addGap(126, 126, 126)))
- .addGap(36, 36, 36)
- .addComponent(browseButton))
- .addGroup(layout.createSequentialGroup()
- .addComponent(driveListScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 211, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addGap(28, 28, 28)
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(selectAcquisitionFromDriveLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 305, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addComponent(imageScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 346, javax.swing.GroupLayout.PREFERRED_SIZE)))
- .addGroup(layout.createSequentialGroup()
- .addGap(346, 346, 346)
- .addComponent(jLabel6, javax.swing.GroupLayout.PREFERRED_SIZE, 154, javax.swing.GroupLayout.PREFERRED_SIZE))
- .addGroup(layout.createSequentialGroup()
- .addGap(144, 144, 144)
- .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 116, javax.swing.GroupLayout.PREFERRED_SIZE))))
- .addContainerGap(48, Short.MAX_VALUE))))
+ .addGap(10, 10, 10)
+ .addComponent(selectFolderLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 81, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGap(13, 13, 13)
+ .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 474, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
+ .addComponent(jSeparator2, javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGap(41, 41, 41)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(driveListScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 160, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(refreshButton))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(imageScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 377, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addGroup(layout.createSequentialGroup()
+ .addGap(20, 20, 20)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addComponent(manualRadioButton)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(browseButton))
+ .addComponent(importRadioButton)
+ .addGroup(layout.createSequentialGroup()
+ .addGap(21, 21, 21)
+ .addComponent(selectDriveLabel)
+ .addGap(113, 113, 113)
+ .addComponent(selectFromDriveLabel))))))
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 568, javax.swing.GroupLayout.PREFERRED_SIZE)))
+ .addContainerGap(14, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
- .addComponent(topLabel)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
- .addComponent(jLabel1)
- .addComponent(jLabel6))
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
- .addComponent(scanButton)
- .addComponent(browseButton))
+ .addGap(16, 16, 16)
+ .addComponent(importRadioButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
- .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 4, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
.addComponent(selectDriveLabel)
- .addComponent(selectAcquisitionFromDriveLabel))
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
+ .addComponent(selectFromDriveLabel))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(imageScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)
- .addComponent(driveListScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 194, Short.MAX_VALUE))
- .addGap(26, 26, 26)
- .addComponent(messageLabel)
- .addGap(154, 154, 154))
+ .addComponent(driveListScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 186, Short.MAX_VALUE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(refreshButton)
+ .addGap(18, 18, 18)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(browseButton)
+ .addComponent(manualRadioButton))
+ .addGap(18, 18, 18)
+ .addComponent(jSeparator2, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(selectFolderLabel)
+ .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 61, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGap(6, 6, 6))
);
+
+ bindingGroup.bind();
}// //GEN-END:initComponents
public static String humanReadableByteCount(long bytes, boolean si) {
@@ -268,47 +307,6 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); //NON-NLS
}
- @Messages({
- "LogicalImagerPanel.messageLabel.scanningExternalDrives=Scanning external drives for sparse_image.vhd ...",
- "LogicalImagerPanel.messageLabel.noExternalDriveFound=No drive found"
- })
- private void scanButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_scanButtonActionPerformed
- // Scan external drives for sparse_image.vhd
- clearImageTable();
- setNormalMessage(Bundle.LogicalImagerPanel_messageLabel_scanningExternalDrives());
- List listData = new ArrayList<>();
- File[] roots = File.listRoots();
- int firstRemovableDrive = -1;
- int i = 0;
- for (File root : roots) {
- String description = FileSystemView.getFileSystemView().getSystemTypeDescription(root);
- long spaceInBytes = root.getTotalSpace();
- String sizeWithUnit = humanReadableByteCount(spaceInBytes, false);
- listData.add(root + " (" + description + ") (" + sizeWithUnit + ")");
- if (firstRemovableDrive == -1) {
- try {
- FileStore fileStore = Files.getFileStore(root.toPath());
- if ((boolean) fileStore.getAttribute("volume:isRemovable")) { //NON-NLS
- firstRemovableDrive = i;
- }
- } catch (IOException ex) {
- ; // skip
- }
- }
- i++;
- }
- driveList.setListData(listData.toArray(new String[0]));
- if (!listData.isEmpty()) {
- // auto-select the first external drive, if any
- driveList.setSelectedIndex(firstRemovableDrive == -1 ? 0 : firstRemovableDrive);
- driveListMouseClicked(null);
- driveList.requestFocusInWindow();
- } else {
- setErrorMessage(Bundle.LogicalImagerPanel_messageLabel_noExternalDriveFound());
- }
- firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false);
- }//GEN-LAST:event_scanButtonActionPerformed
-
@Messages({
"# {0} - sparseImageDirectory",
"# {1} - image",
@@ -333,7 +331,7 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
return;
}
choosenImageDirPath = Paths.get(path);
- setNormalMessage(SELECTED_IMAGE + " " + path);
+ setNormalMessage(path);
firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true);
} else {
setErrorMessage(Bundle.LogicalImagerPanel_messageLabel_directoryFormatInvalid(path));
@@ -348,7 +346,7 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
int index = imageTable.getSelectedRow();
if (index != -1) {
choosenImageDirPath = Paths.get((String) imageTableModel.getValueAt(index, 2));
- setNormalMessage(SELECTED_IMAGE + " " + choosenImageDirPath.toString());
+ setNormalMessage(choosenImageDirPath.toString());
firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true);
} else {
choosenImageDirPath = null;
@@ -357,10 +355,6 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
}
}
- private void imageTableMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_imageTableMouseClicked
- imageTableSelect();
- }//GEN-LAST:event_imageTableMouseClicked
-
private void driveListSelect() {
String selectedStr = driveList.getSelectedValue();
if (selectedStr == null) {
@@ -398,7 +392,7 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
}
}
}
- selectAcquisitionFromDriveLabel.setText(Bundle.LogicalImagerPanel_selectAcquisitionFromDriveLabel_text()
+ selectFromDriveLabel.setText(Bundle.LogicalImagerPanel_selectAcquisitionFromDriveLabel_text()
+ " " + driveLetter);
imageTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
imageTable.setModel(imageTableModel);
@@ -411,6 +405,10 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
choosenImageDirPath = null;
setErrorMessage(DRIVE_HAS_NO_IMAGES);
}
+ } else {
+ clearImageTable();
+ choosenImageDirPath = null;
+ setErrorMessage(DRIVE_HAS_NO_IMAGES);
}
}
@@ -421,13 +419,14 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
}
private void setErrorMessage(String msg) {
- messageLabel.setForeground(Color.red);
- messageLabel.setText(msg);
+ messageTextArea.setForeground(Color.red);
+ messageTextArea.setText(msg);
+ pathTextField.setText("");
}
private void setNormalMessage(String msg) {
- messageLabel.setForeground(Color.black);
- messageLabel.setText(msg);
+ pathTextField.setText(msg);
+ messageTextArea.setText("");
}
private void clearImageTable() {
@@ -436,43 +435,138 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
fixImageTableColumnWidth();
}
- private void driveListMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_driveListMouseClicked
- driveListSelect();
+ private void toggleMouseAndKeyListeners(Component component, boolean isEnable) {
+ component.setEnabled(isEnable);
+ }
+
+ private void manualRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_manualRadioButtonActionPerformed
+ browseButton.setEnabled(true);
+
+ // disable import panel
+ toggleMouseAndKeyListeners(driveList, false);
+ toggleMouseAndKeyListeners(driveListScrollPane, false);
+ toggleMouseAndKeyListeners(imageScrollPane, false);
+ toggleMouseAndKeyListeners(imageTable, false);
+
+ refreshButton.setEnabled(false);
+
+ choosenImageDirPath = null;
+ setNormalMessage("");
firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false);
- }//GEN-LAST:event_driveListMouseClicked
+ }//GEN-LAST:event_manualRadioButtonActionPerformed
+
+ private void importRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_importRadioButtonActionPerformed
+ browseButton.setEnabled(false);
+
+ toggleMouseAndKeyListeners(driveList, true);
+ toggleMouseAndKeyListeners(driveListScrollPane, true);
+ toggleMouseAndKeyListeners(imageScrollPane, true);
+ toggleMouseAndKeyListeners(imageTable, true);
+
+ refreshButton.setEnabled(true);
+
+ choosenImageDirPath = null;
+ setNormalMessage("");
+ refreshButton.doClick();
+ }//GEN-LAST:event_importRadioButtonActionPerformed
+
+ @Messages({
+ "LogicalImagerPanel.messageLabel.scanningExternalDrives=Scanning external drives for sparse_image.vhd ...",
+ "LogicalImagerPanel.messageLabel.noExternalDriveFound=No drive found"
+ })
+ private void refreshButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_refreshButtonActionPerformed
+ // Scan external drives for sparse_image.vhd
+ clearImageTable();
+ setNormalMessage(Bundle.LogicalImagerPanel_messageLabel_scanningExternalDrives());
+ List listData = new ArrayList<>();
+ File[] roots = File.listRoots();
+ int firstRemovableDrive = -1;
+ int i = 0;
+ for (File root : roots) {
+ String description = FileSystemView.getFileSystemView().getSystemTypeDescription(root);
+ long spaceInBytes = root.getTotalSpace();
+ String sizeWithUnit = humanReadableByteCount(spaceInBytes, false);
+ listData.add(root + " (" + description + ") (" + sizeWithUnit + ")");
+ if (firstRemovableDrive == -1) {
+ try {
+ FileStore fileStore = Files.getFileStore(root.toPath());
+ if ((boolean) fileStore.getAttribute("volume:isRemovable")) { //NON-NLS
+ firstRemovableDrive = i;
+ }
+ } catch (IOException ex) {
+ ; // skip
+ }
+ }
+ i++;
+ }
+ driveList.setListData(listData.toArray(new String[0]));
+ if (!listData.isEmpty()) {
+ // auto-select the first external drive, if any
+ driveList.setSelectedIndex(firstRemovableDrive == -1 ? 0 : firstRemovableDrive);
+ driveListMouseReleased(null);
+ driveList.requestFocusInWindow();
+ } else {
+ setErrorMessage(Bundle.LogicalImagerPanel_messageLabel_noExternalDriveFound());
+ }
+ firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false);
+ }//GEN-LAST:event_refreshButtonActionPerformed
private void driveListKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_driveListKeyReleased
- driveListSelect();
- firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false);
+ if (importRadioButton.isSelected()) {
+ driveListSelect();
+ firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false);
+ }
}//GEN-LAST:event_driveListKeyReleased
private void imageTableKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_imageTableKeyReleased
- imageTableSelect();
- firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false);
+ if (importRadioButton.isSelected()) {
+ imageTableSelect();
+ }
}//GEN-LAST:event_imageTableKeyReleased
+ private void imageTableMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_imageTableMouseReleased
+ if (importRadioButton.isSelected()) {
+ imageTableSelect();
+ }
+ }//GEN-LAST:event_imageTableMouseReleased
+
+ private void driveListMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_driveListMouseReleased
+ if (importRadioButton.isSelected()) {
+ driveListSelect();
+ firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false);
+ }
+ }//GEN-LAST:event_driveListMouseReleased
+
+
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton browseButton;
+ private javax.swing.ButtonGroup buttonGroup1;
private javax.swing.JList driveList;
private javax.swing.JScrollPane driveListScrollPane;
private javax.swing.JScrollPane imageScrollPane;
private javax.swing.JTable imageTable;
- private javax.swing.JLabel jLabel1;
- private javax.swing.JLabel jLabel6;
- private javax.swing.JSeparator jSeparator1;
- private javax.swing.JLabel messageLabel;
- private javax.swing.JButton scanButton;
- private javax.swing.JLabel selectAcquisitionFromDriveLabel;
+ private javax.swing.JRadioButton importRadioButton;
+ private javax.swing.JScrollPane jScrollPane1;
+ private javax.swing.JSeparator jSeparator2;
+ private javax.swing.JRadioButton manualRadioButton;
+ private javax.swing.JTextArea messageTextArea;
+ private javax.swing.JTextField pathTextField;
+ private javax.swing.JButton refreshButton;
private javax.swing.JLabel selectDriveLabel;
- private javax.swing.JLabel topLabel;
+ private javax.swing.JLabel selectFolderLabel;
+ private javax.swing.JLabel selectFromDriveLabel;
+ private org.jdesktop.beansbinding.BindingGroup bindingGroup;
// End of variables declaration//GEN-END:variables
public void reset() {
//reset the UI elements to default
choosenImageDirPath = null;
+ setNormalMessage("");
driveList.setListData(EMPTY_LIST_DATA);
clearImageTable();
- setNormalMessage(Bundle.LogicalImagerPanel_messageLabel_clickScanOrBrowse());
+ if (importRadioButton.isSelected()) {
+ refreshButton.doClick();
+ }
}
/**
@@ -488,10 +582,6 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
return choosenImageDirPath;
}
- public void setMessageLabel(String message) {
- messageLabel.setText(message);
- }
-
@Override
public void insertUpdate(DocumentEvent e) {
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java
index 8b551ce4ec..aedbb3671f 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java
@@ -88,7 +88,7 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener {
* @return caseName the case name from the case name text field
*/
String getCaseName() {
- return this.caseNameTextField.getText();
+ return this.caseNameTextField.getText().trim();
}
/**
@@ -109,7 +109,7 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener {
* @return baseDirectory the base directory from the case dir text field
*/
String getCaseParentDir() {
- String parentDir = this.caseParentDirTextField.getText();
+ String parentDir = this.caseParentDirTextField.getText().trim();
if (parentDir.endsWith(File.separator) == false) {
parentDir = parentDir + File.separator;
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties-MERGED
index 508e04b76a..fe998b0e57 100755
--- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties-MERGED
@@ -66,6 +66,7 @@ DataSourceSummaryDialog.window.title=Data Sources Summary
DataSourceSummaryNode.column.dataSourceName.header=Data Source Name
DataSourceSummaryNode.column.files.header=Files
DataSourceSummaryNode.column.results.header=Results
+DataSourceSummaryNode.column.status.header=Ingest Status
DataSourceSummaryNode.column.tags.header=Tags
DataSourceSummaryNode.column.type.header=Type
DataSourceSummaryNode.viewDataSourceAction.text=Go to Data Source
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java
index e3b9fa0e93..ac09010de1 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java
@@ -37,8 +37,10 @@ import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.datasourcesummary.DataSourceSummaryNode.DataSourceSummaryEntryNode;
import static javax.swing.SwingConstants.RIGHT;
+import javax.swing.SwingUtilities;
import javax.swing.table.TableColumn;
import org.sleuthkit.datamodel.DataSource;
+import org.sleuthkit.datamodel.IngestJobInfo;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
@@ -50,9 +52,10 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(DataSourceBrowser.class.getName());
- private static final int COUNT_COLUMN_WIDTH = 25;
- private static final int USAGE_COLUMN_WIDTH = 120;
- private static final int DATA_SOURCE_COLUMN_WIDTH = 325;
+ private static final int COUNT_COLUMN_WIDTH = 20;
+ private static final int INGEST_STATUS_WIDTH = 50;
+ private static final int USAGE_COLUMN_WIDTH = 110;
+ private static final int DATA_SOURCE_COLUMN_WIDTH = 280;
private final Outline outline;
private final org.openide.explorer.view.OutlineView outlineView;
private final ExplorerManager explorerManager;
@@ -69,6 +72,7 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana
outlineView = new org.openide.explorer.view.OutlineView();
this.setVisible(true);
outlineView.setPropertyColumns(
+ Bundle.DataSourceSummaryNode_column_status_header(), Bundle.DataSourceSummaryNode_column_status_header(),
Bundle.DataSourceSummaryNode_column_type_header(), Bundle.DataSourceSummaryNode_column_type_header(),
Bundle.DataSourceSummaryNode_column_files_header(), Bundle.DataSourceSummaryNode_column_files_header(),
Bundle.DataSourceSummaryNode_column_results_header(), Bundle.DataSourceSummaryNode_column_results_header(),
@@ -90,6 +94,8 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana
column.setPreferredWidth(COUNT_COLUMN_WIDTH);
} else if (column.getHeaderValue().toString().equals(Bundle.DataSourceSummaryNode_column_type_header())) {
column.setPreferredWidth(USAGE_COLUMN_WIDTH);
+ } else if (column.getHeaderValue().toString().equals(Bundle.DataSourceSummaryNode_column_status_header())) {
+ column.setPreferredWidth(INGEST_STATUS_WIDTH);
} else {
column.setPreferredWidth(DATA_SOURCE_COLUMN_WIDTH);
}
@@ -182,6 +188,47 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana
return null;
}
+ /**
+ * Update the DataSourceBrowser to display up to date status information for
+ * the data sources.
+ *
+ * @param dataSourceId the ID of the data source which should be updated
+ * @param newStatus the new status which the data source should have
+ */
+ void refresh(long dataSourceId, IngestJobInfo.IngestJobStatusType newStatus) {
+
+ //attempt to update the status of any datasources that had status which was STARTED
+ for (DataSourceSummary summary : dataSourceSummaryList) {
+ if (summary.getDataSource().getId() == dataSourceId) {
+ summary.setIngestStatus(newStatus);
+ }
+ }
+ //figure out which nodes were previously selected
+ Node[] selectedNodes = explorerManager.getSelectedNodes();
+ SwingUtilities.invokeLater(() -> {
+ explorerManager.setRootContext(new DataSourceSummaryNode(dataSourceSummaryList));
+ List nodesToSelect = new ArrayList<>();
+ for (Node node : explorerManager.getRootContext().getChildren().getNodes()) {
+ if (node instanceof DataSourceSummaryEntryNode) {
+ //there should only be one selected node as multi-select is disabled
+ for (Node selectedNode : selectedNodes) {
+ if (((DataSourceSummaryEntryNode) node).getDataSource().equals(((DataSourceSummaryEntryNode) selectedNode).getDataSource())) {
+ nodesToSelect.add(node);
+ }
+ }
+ }
+ }
+ //reselect the previously selected Nodes
+ try {
+ explorerManager.setSelectedNodes(nodesToSelect.toArray(new Node[nodesToSelect.size()]));
+ } catch (PropertyVetoException ex) {
+ logger.log(Level.WARNING, "Error selecting previously selected nodes", ex);
+ }
+
+ });
+
+ }
+
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java
index 1aa55d95b5..390dce1afe 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java
@@ -18,15 +18,27 @@
*/
package org.sleuthkit.autopsy.casemodule.datasourcesummary;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.datamodel.CaseDbAccessManager;
import org.sleuthkit.datamodel.DataSource;
+import org.sleuthkit.datamodel.IngestJobInfo.IngestJobStatusType;
+import org.sleuthkit.datamodel.TskCoreException;
/**
* Wrapper object for a DataSource and the information associated with it.
*
*/
class DataSourceSummary {
-
+
+ private static final Logger logger = Logger.getLogger(DataSourceSummary.class.getName());
+ private static final String INGEST_JOB_STATUS_QUERY = "status_id FROM ingest_jobs WHERE obj_id=";
private final DataSource dataSource;
+ private IngestJobStatusType status = null;
private final String type;
private final long filesCount;
private final long resultsCount;
@@ -45,12 +57,26 @@ class DataSourceSummary {
*/
DataSourceSummary(DataSource dSource, String typeValue, Long numberOfFiles, Long numberOfResults, Long numberOfTags) {
dataSource = dSource;
+ getStatusFromDatabase();
type = typeValue == null ? "" : typeValue;
filesCount = numberOfFiles == null ? 0 : numberOfFiles;
resultsCount = numberOfResults == null ? 0 : numberOfResults;
tagsCount = numberOfTags == null ? 0 : numberOfTags;
}
+ /**
+ * Get the status of the ingest job from the case database
+ */
+ private void getStatusFromDatabase() {
+ try {
+ IngestJobQueryCallback callback = new IngestJobQueryCallback();
+ Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager().select(INGEST_JOB_STATUS_QUERY + dataSource.getId(), callback);
+ status = callback.getStatus();
+ } catch (NoCurrentCaseException | TskCoreException ex) {
+ logger.log(Level.WARNING, "Error getting status for data source from case database", ex);
+ }
+ }
+
/**
* Get the DataSource
*
@@ -60,6 +86,16 @@ class DataSourceSummary {
return dataSource;
}
+ /**
+ * Manually set the ingest job status
+ *
+ * @param ingestStatus the status which the ingest job should have
+ * currently, null to display empty string
+ */
+ void setIngestStatus(IngestJobStatusType ingestStatus) {
+ status = ingestStatus;
+ }
+
/**
* Get the type of this DataSource
*
@@ -87,6 +123,16 @@ class DataSourceSummary {
return resultsCount;
}
+ /**
+ * Get the IngestJobStatusType associated with this data source.
+ *
+ * @return the IngestJobStatusType associated with this data source. Can be
+ * null if the IngestJobStatusType is not STARTED or COMPLETED.
+ */
+ IngestJobStatusType getIngestStatus() {
+ return status;
+ }
+
/**
* Get the number of tagged content objects in this DataSource
*
@@ -95,4 +141,40 @@ class DataSourceSummary {
long getTagsCount() {
return tagsCount;
}
+
+ /**
+ * Callback to parse result set, getting the status to be associated with
+ * this data source
+ */
+ class IngestJobQueryCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
+
+ private IngestJobStatusType jobStatus = null;
+
+ @Override
+ public void process(ResultSet rs) {
+ try {
+ while (rs.next()) {
+ IngestJobStatusType currentStatus = IngestJobStatusType.fromID(rs.getInt("status_id"));
+ if (currentStatus == IngestJobStatusType.COMPLETED) {
+ jobStatus = currentStatus;
+ } else if (currentStatus == IngestJobStatusType.STARTED) {
+ jobStatus = currentStatus;
+ return;
+ }
+ }
+ } catch (SQLException ex) {
+ logger.log(Level.WARNING, "Error getting status for ingest job", ex);
+ }
+ }
+
+ /**
+ * Get the status which was determined for this callback
+ *
+ * @return the status of the data source which was queried for
+ */
+ IngestJobStatusType getStatus() {
+ return jobStatus;
+ }
+
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java
index 3fd86c8de8..1535cbefe0 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java
@@ -19,14 +19,18 @@
package org.sleuthkit.autopsy.casemodule.datasourcesummary;
import java.awt.Frame;
+import java.beans.PropertyChangeEvent;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
-import java.util.logging.Logger;
import javax.swing.event.ListSelectionEvent;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.IngestJobInfoPanel;
+import org.sleuthkit.autopsy.ingest.IngestManager;
+import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent;
+import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent.Reason;
import org.sleuthkit.datamodel.DataSource;
+import org.sleuthkit.datamodel.IngestJobInfo;
/**
* Dialog for displaying the Data Sources Summary information
@@ -38,7 +42,6 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser
private final DataSourceSummaryDetailsPanel detailsPanel;
private final DataSourceBrowser dataSourcesPanel;
private final IngestJobInfoPanel ingestHistoryPanel;
- private static final Logger logger = Logger.getLogger(DataSourceSummaryDialog.class.getName());
/**
* Creates new form DataSourceSummaryDialog for displaying a summary of the
@@ -73,6 +76,17 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser
this.repaint();
}
});
+ //add listener to refresh jobs with Started status when they complete
+ IngestManager.getInstance().addIngestJobEventListener((PropertyChangeEvent evt) -> {
+ if (evt instanceof DataSourceAnalysisCompletedEvent) {
+ DataSourceAnalysisCompletedEvent dsEvent = (DataSourceAnalysisCompletedEvent) evt;
+ if (dsEvent.getResult() == Reason.ANALYSIS_COMPLETED) {
+ dataSourcesPanel.refresh(dsEvent.getDataSource().getId(), IngestJobInfo.IngestJobStatusType.COMPLETED);
+ } else if (dsEvent.getResult() == Reason.ANALYSIS_CANCELLED) {
+ dataSourcesPanel.refresh(dsEvent.getDataSource().getId(), null);
+ }
+ }
+ });
this.pack();
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java
index 8323061a66..0411d94bb2 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java
@@ -109,6 +109,7 @@ final class DataSourceSummaryNode extends AbstractNode {
static final class DataSourceSummaryEntryNode extends AbstractNode {
private final DataSource dataSource;
+ private final String status;
private final String type;
private final long filesCount;
private final long resultsCount;
@@ -124,6 +125,7 @@ final class DataSourceSummaryNode extends AbstractNode {
DataSourceSummaryEntryNode(DataSourceSummary dataSourceSummary) {
super(Children.LEAF);
dataSource = dataSourceSummary.getDataSource();
+ status = dataSourceSummary.getIngestStatus() == null ? "" : dataSourceSummary.getIngestStatus().getDisplayName();
type = dataSourceSummary.getType();
filesCount = dataSourceSummary.getFilesCount();
resultsCount = dataSourceSummary.getResultsCount();
@@ -143,6 +145,7 @@ final class DataSourceSummaryNode extends AbstractNode {
}
@Messages({"DataSourceSummaryNode.column.dataSourceName.header=Data Source Name",
+ "DataSourceSummaryNode.column.status.header=Ingest Status",
"DataSourceSummaryNode.column.type.header=Type",
"DataSourceSummaryNode.column.files.header=Files",
"DataSourceSummaryNode.column.results.header=Results",
@@ -157,6 +160,7 @@ final class DataSourceSummaryNode extends AbstractNode {
}
sheetSet.put(new NodeProperty<>(Bundle.DataSourceSummaryNode_column_dataSourceName_header(), Bundle.DataSourceSummaryNode_column_dataSourceName_header(), Bundle.DataSourceSummaryNode_column_dataSourceName_header(),
dataSource));
+ sheetSet.put(new NodeProperty<>(Bundle.DataSourceSummaryNode_column_status_header(), Bundle.DataSourceSummaryNode_column_status_header(), Bundle.DataSourceSummaryNode_column_status_header(), status));
sheetSet.put(new NodeProperty<>(Bundle.DataSourceSummaryNode_column_type_header(), Bundle.DataSourceSummaryNode_column_type_header(), Bundle.DataSourceSummaryNode_column_type_header(),
type));
sheetSet.put(new NodeProperty<>(Bundle.DataSourceSummaryNode_column_files_header(), Bundle.DataSourceSummaryNode_column_files_header(), Bundle.DataSourceSummaryNode_column_files_header(),
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java
index 896298c2bd..40597e9af3 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java
@@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.casemodule.services;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -30,15 +31,18 @@ import java.util.logging.Level;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
+import org.sleuthkit.datamodel.CaseDbAccessManager;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
+import org.sleuthkit.datamodel.TskData.DbType;
/**
* A per case Autopsy service that manages the addition of content and artifact
@@ -48,6 +52,32 @@ public class TagsManager implements Closeable {
private static final Logger LOGGER = Logger.getLogger(TagsManager.class.getName());
private final SleuthkitCase caseDb;
+
+ static {
+ //Create the contentviewer tags table (beta) if the current case does not
+ //have the table present
+ Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), evt -> {
+ if (evt.getNewValue() != null) {
+ Case currentCase = (Case) evt.getNewValue();
+ try {
+ CaseDbAccessManager caseDb = currentCase.getSleuthkitCase().getCaseDbAccessManager();
+ if (caseDb.tableExists(ContentViewerTagManager.TABLE_NAME)) {
+ return;
+ }
+
+ if (currentCase.getSleuthkitCase().getDatabaseType().equals(DbType.SQLITE)) {
+ caseDb.createTable(ContentViewerTagManager.TABLE_NAME, ContentViewerTagManager.TABLE_SCHEMA_SQLITE);
+ } else if (currentCase.getSleuthkitCase().getDatabaseType().equals(DbType.POSTGRESQL)) {
+ caseDb.createTable(ContentViewerTagManager.TABLE_NAME, ContentViewerTagManager.TABLE_SCHEMA_POSTGRESQL);
+ }
+ } catch (TskCoreException ex) {
+ LOGGER.log(Level.SEVERE,
+ String.format("Unable to create the %s table for image tag storage.",
+ ContentViewerTagManager.TABLE_NAME), ex);
+ }
+ }
+ });
+ }
/**
* Tests whether or not a given tag display name contains an illegal
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/contentviewertags/ContentViewerTagManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/contentviewertags/ContentViewerTagManager.java
new file mode 100755
index 0000000000..8d117e908b
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/contentviewertags/ContentViewerTagManager.java
@@ -0,0 +1,276 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2019 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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.casemodule.services.contentviewertags;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.datamodel.ContentTag;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ * A per case Autopsy service that manages the addition of content viewer tags
+ * to the case database. This manager is also responsible for serializing and
+ * deserializing instances of your tag data objects for persistence and
+ * retrieval.
+ */
+public class ContentViewerTagManager {
+
+ //Used to convert Java beans into the physical representation that will be stored
+ //in the database.
+ private static final ObjectMapper SERIALIZER = new ObjectMapper();
+
+ public static final String TABLE_NAME = "beta_tag_app_data";
+ public static final String TABLE_SCHEMA_SQLITE = "(app_data_id INTEGER PRIMARY KEY, "
+ + "content_tag_id INTEGER NOT NULL, app_data TEXT NOT NULL, "
+ + "FOREIGN KEY(content_tag_id) REFERENCES content_tags(tag_id))";
+ public static final String TABLE_SCHEMA_POSTGRESQL = "(app_data_id BIGSERIAL PRIMARY KEY, "
+ + "content_tag_id INTEGER NOT NULL, app_data TEXT NOT NULL, "
+ + "FOREIGN KEY(content_tag_id) REFERENCES content_tags(tag_id))";
+
+ private static final String INSERT_TAG_DATA = "(content_tag_id, app_data) VALUES (%d, '%s')";
+ private static final String UPDATE_TAG_DATA = "SET content_tag_id = %d, app_data = '%s' WHERE app_data_id = %d";
+ private static final String SELECT_TAG_DATA = "* FROM " + TABLE_NAME + " WHERE content_tag_id = %d";
+ private static final String DELETE_TAG_DATA = "WHERE app_data_id = %d";
+
+ /**
+ * Creates and saves a new ContentViewerTag in the case database. The
+ * generic tag data instance T will be automatically serialized into a
+ * storable format.
+ *
+ * @param Generic class type that will be serialized into a storable
+ * format for persistence.
+ * @param contentTag ContentTag that this ContentViewerTag is associated
+ * with (1:1).
+ * @param tagDataBean Data instance that contains the tag information to be
+ * persisted.
+ * @return An instance of a ContentViewerTag of type T, which contains all
+ * the stored information.
+ *
+ * @throws SerializationException Thrown if the tag data instance T could
+ * not be serialized into a storable format.
+ * @throws TskCoreException Thrown if this operation did not successfully
+ * persist in the case database.
+ * @throws NoCurrentCaseException Thrown if invocation of this method occurs
+ * when no case is open.
+ */
+ public static ContentViewerTag saveTag(ContentTag contentTag, T tagDataBean)
+ throws SerializationException, TskCoreException, NoCurrentCaseException {
+ try {
+ long contentTagId = contentTag.getId();
+ String serialAppData = SERIALIZER.writeValueAsString(tagDataBean);
+ String insertTemplateInstance = String.format(INSERT_TAG_DATA,
+ contentTagId, serialAppData);
+ long insertId = Case.getCurrentCaseThrows()
+ .getSleuthkitCase()
+ .getCaseDbAccessManager()
+ .insert(TABLE_NAME, insertTemplateInstance);
+ return new ContentViewerTag<>(insertId, contentTag, tagDataBean);
+ } catch (JsonProcessingException ex) {
+ throw new SerializationException("Unable to convert object instance into a storable format", ex);
+ }
+ }
+
+ /**
+ * Updates the ContentViewerTag instance with the new tag data T and
+ * persists the changes to the case database.
+ *
+ * @param Generic class type that will be serialized into a storable
+ * format.
+ * @param oldTag ContentViewerTag instance to be updated
+ * @param tagDataBean Data instance that contains the updated information to
+ * be persisted.
+ *
+ * @throws SerializationException Thrown if the tag data instance T could
+ * not be serialized into a storable format.
+ * @throws TskCoreException Thrown if this operation did not successfully
+ * persist in the case database.
+ * @throws NoCurrentCaseException Thrown if invocation of this method occurs
+ * when no case is open.
+ */
+ public static ContentViewerTag updateTag(ContentViewerTag oldTag, T tagDataBean)
+ throws SerializationException, TskCoreException, NoCurrentCaseException {
+ try {
+ String serialAppData = SERIALIZER.writeValueAsString(tagDataBean);
+ String updateTemplateInstance = String.format(UPDATE_TAG_DATA,
+ oldTag.getContentTag().getId(), serialAppData, oldTag.getId());
+ Case.getCurrentCaseThrows()
+ .getSleuthkitCase()
+ .getCaseDbAccessManager()
+ .update(TABLE_NAME, updateTemplateInstance);
+ return new ContentViewerTag<>(oldTag.getId(), oldTag.getContentTag(), tagDataBean);
+ } catch (JsonProcessingException ex) {
+ throw new SerializationException("Unable to convert object instance into a storable format", ex);
+ }
+ }
+
+ /**
+ * Retrieves a ContentViewerTag instance that is associated with the
+ * specified ContentTag. The Java class T that represents the technical
+ * details of the tag should be passed so that automatic binding can take
+ * place.
+ *
+ * @param Generic class type that will be instantiated and filled in
+ * with data.
+ * @param contentTag ContentTag that this ContentViewerTag is associated
+ * with (1:1)
+ * @param clazz Generic class that will be instantiated and filled in with
+ * data.
+ * @return ContentViewerTag with an instance of T as a member variable or
+ * null if the content tag does not have an associated ContentViewerTag of
+ * type T.
+ *
+ * @throws TskCoreException Thrown if this operation did not successfully
+ * persist in the case database.
+ * @throws NoCurrentCaseException Thrown if invocation of this method occurs
+ * when no case is open.
+ */
+ public static ContentViewerTag getTag(ContentTag contentTag, Class clazz) throws TskCoreException, NoCurrentCaseException {
+ String selectTemplateInstance = String.format(SELECT_TAG_DATA, contentTag.getId());
+ final ResultWrapper> result = new ResultWrapper<>();
+ Case.getCurrentCaseThrows()
+ .getSleuthkitCase()
+ .getCaseDbAccessManager()
+ .select(selectTemplateInstance, (ResultSet rs) -> {
+ try {
+ if (rs.next()) {
+ long tagId = rs.getLong(1);
+ String appDetails = rs.getString(3);
+ try {
+ T instance = SERIALIZER.readValue(appDetails, clazz);
+ result.setResult(new ContentViewerTag<>(tagId, contentTag, instance));
+ } catch (IOException ex) {
+ //Databind for type T failed. Not a system error
+ //but rather a logic error on the part of the caller.
+ result.setResult(null);
+ }
+ }
+ } catch (SQLException ex) {
+ result.setException(ex);
+ }
+ });
+
+ if (result.hasException()) {
+ throw new TskCoreException("Unable to select tag from case db", result.getException());
+ }
+
+ return result.getResult();
+ }
+
+ /**
+ * Wrapper for holding state in the CaseDbAccessQueryCallback.
+ * CaseDbAccessQueryCallback has no support for exception handling.
+ *
+ * @param
+ */
+ private static class ResultWrapper {
+
+ private T result;
+ private SQLException ex = null;
+
+ public void setResult(T result) {
+ this.result = result;
+ }
+
+ public void setException(SQLException ex) {
+ this.ex = ex;
+ }
+
+ public boolean hasException() {
+ return this.ex != null;
+ }
+
+ public SQLException getException() {
+ return ex;
+ }
+
+ public T getResult() {
+ return result;
+ }
+ }
+
+ /**
+ * Deletes the content viewer tag with the specified id.
+ *
+ * @param contentViewerTag ContentViewerTag to delete
+ * @throws TskCoreException Thrown if this operation did not successfully
+ * persist in the case database.
+ * @throws NoCurrentCaseException Thrown if invocation of this method occurs
+ * when no case is open.
+ */
+ public static void deleteTag(ContentViewerTag contentViewerTag) throws TskCoreException, NoCurrentCaseException {
+ String deleteTemplateInstance = String.format(DELETE_TAG_DATA, contentViewerTag.getId());
+ Case.getCurrentCaseThrows()
+ .getSleuthkitCase()
+ .getCaseDbAccessManager()
+ .delete(TABLE_NAME, deleteTemplateInstance);
+ }
+
+ /**
+ * This class represents a stored tag in the case database. It is a wrapper
+ * for the tag id, the attached Content tag object, and the Java bean
+ * instance that describes the technical details for reconstructing the tag.
+ *
+ * @param Generic class type that will be instantiated and filled in
+ * with data.
+ */
+ public static class ContentViewerTag {
+
+ private final long id;
+ private final ContentTag contentTag;
+ private final T details;
+
+ private ContentViewerTag(long id, ContentTag contentTag, T details) {
+ this.id = id;
+ this.contentTag = contentTag;
+ this.details = details;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public ContentTag getContentTag() {
+ return contentTag;
+ }
+
+ public T getDetails() {
+ return details;
+ }
+ }
+
+ /**
+ * System exception thrown in the event that class instance T could not be
+ * properly serialized.
+ */
+ public static class SerializationException extends Exception {
+
+ public SerializationException(String message, Exception source) {
+ super(message, source);
+ }
+ }
+
+ //Prevent this class from being instantiated.
+ private ContentViewerTagManager() {
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java
index fcf64182a3..fe66b2fa6e 100644
--- a/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java
+++ b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java
@@ -75,6 +75,10 @@ public final class AccountsBrowser extends JPanel implements ExplorerManager.Pro
public AccountsBrowser() {
initComponents();
+
+ jSplitPane1.setResizeWeight(0.5);
+ jSplitPane1.setDividerLocation(0.75);
+
outline = outlineView.getOutline();
outlineView.setPropertyColumns(
"device", Bundle.AccountNode_device(),
@@ -118,7 +122,7 @@ public final class AccountsBrowser extends JPanel implements ExplorerManager.Pro
final int rows = Math.min(100, outline.getRowCount());
- for (int column = 0; column < outline.getModel().getColumnCount(); column++) {
+ for (int column = 0; column < outline.getColumnCount(); column++) {
int columnWidthLimit = 500;
int columnWidth = 0;
diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.form b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.form
index 055719c488..bc16bb6da2 100644
--- a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.form
+++ b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.form
@@ -11,34 +11,10 @@
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -51,6 +27,11 @@
+
+
+
+
+
@@ -85,11 +66,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java
index 16c3208ff8..0032793584 100644
--- a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java
+++ b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java
@@ -22,12 +22,13 @@ import com.google.common.eventbus.Subscribe;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
import java.util.List;
import java.util.stream.Collectors;
-import javax.swing.GroupLayout;
import javax.swing.ImageIcon;
import javax.swing.JTabbedPane;
-import javax.swing.LayoutStyle;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.windows.Mode;
@@ -93,39 +94,39 @@ public final class CVTTopComponent extends TopComponent {
*/
// //GEN-BEGIN:initComponents
private void initComponents() {
+ GridBagConstraints gridBagConstraints;
browseVisualizeTabPane = new JTabbedPane();
accountsBrowser = new AccountsBrowser();
vizPanel = new VisualizationPanel();
filtersPane = new FiltersPanel();
+ setLayout(new GridBagLayout());
+
browseVisualizeTabPane.setFont(new Font("Tahoma", 0, 18)); // NOI18N
browseVisualizeTabPane.addTab(NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.accountsBrowser.TabConstraints.tabTitle_1"), new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/table.png")), accountsBrowser); // NOI18N
browseVisualizeTabPane.addTab(NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.vizPanel.TabConstraints.tabTitle_1"), new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/emblem-web.png")), vizPanel); // NOI18N
- filtersPane.setMinimumSize(new Dimension(256, 495));
-
- GroupLayout layout = new GroupLayout(this);
- this.setLayout(layout);
- layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
- .addGroup(layout.createSequentialGroup()
- .addGap(6, 6, 6)
- .addComponent(filtersPane, GroupLayout.PREFERRED_SIZE, 265, GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(browseVisualizeTabPane, GroupLayout.PREFERRED_SIZE, 786, Short.MAX_VALUE)
- .addContainerGap())
- );
- layout.setVerticalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
- .addGroup(layout.createSequentialGroup()
- .addGap(6, 6, 6)
- .addComponent(filtersPane, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addGap(5, 5, 5))
- .addGroup(GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
- .addComponent(browseVisualizeTabPane)
- .addContainerGap())
- );
-
+ gridBagConstraints = new GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.fill = GridBagConstraints.BOTH;
+ gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 0.75;
+ gridBagConstraints.weighty = 1.0;
+ gridBagConstraints.insets = new Insets(15, 0, 15, 15);
+ add(browseVisualizeTabPane, gridBagConstraints);
browseVisualizeTabPane.getAccessibleContext().setAccessibleName(NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.browseVisualizeTabPane.AccessibleContext.accessibleName")); // NOI18N
+
+ gridBagConstraints = new GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.fill = GridBagConstraints.BOTH;
+ gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 0.25;
+ gridBagConstraints.weighty = 1.0;
+ gridBagConstraints.insets = new Insets(15, 15, 15, 5);
+ add(filtersPane, gridBagConstraints);
}// //GEN-END:initComponents
diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form
index 8bd260ea4b..2a656008c2 100644
--- a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form
@@ -11,493 +11,487 @@
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java
index a6cdf63b63..5ba6b9b89a 100644
--- a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java
@@ -394,135 +394,123 @@ final public class FiltersPanel extends JPanel {
@SuppressWarnings("unchecked")
// //GEN-BEGIN:initComponents
private void initComponents() {
+ java.awt.GridBagConstraints gridBagConstraints;
- applyFiltersButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/tick.png"))); // NOI18N
- applyFiltersButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.applyFiltersButton.text")); // NOI18N
- applyFiltersButton.setPreferredSize(null);
+ setLayout(new java.awt.GridBagLayout());
+
+ topPane.setLayout(new java.awt.GridBagLayout());
filtersTitleLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/funnel.png"))); // NOI18N
filtersTitleLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.filtersTitleLabel.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ topPane.add(filtersTitleLabel, gridBagConstraints);
- unCheckAllAccountTypesButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.unCheckAllAccountTypesButton.text")); // NOI18N
- unCheckAllAccountTypesButton.addActionListener(new java.awt.event.ActionListener() {
+ refreshButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/arrow-circle-double-135.png"))); // NOI18N
+ refreshButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.refreshButton.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 2;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
+ topPane.add(refreshButton, gridBagConstraints);
+
+ applyFiltersButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/tick.png"))); // NOI18N
+ applyFiltersButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.applyFiltersButton.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
+ topPane.add(applyFiltersButton, gridBagConstraints);
+
+ needsRefreshLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.needsRefreshLabel.text")); // NOI18N
+ needsRefreshLabel.setForeground(new java.awt.Color(255, 0, 0));
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.gridwidth = 3;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ topPane.add(needsRefreshLabel, gridBagConstraints);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_END;
+ gridBagConstraints.weightx = 1.0;
+ add(topPane, gridBagConstraints);
+
+ scrollPane.setBorder(null);
+
+ mainPanel.setLayout(new java.awt.GridBagLayout());
+
+ limitPane.setLayout(new java.awt.GridBagLayout());
+
+ mostRecentLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.mostRecentLabel.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(0, 9, 0, 9);
+ limitPane.add(mostRecentLabel, gridBagConstraints);
+
+ limitComboBox.setEditable(true);
+ limitComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "All", "10000", "5000", "1000", "500", "100" }));
+ limitComboBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
- unCheckAllAccountTypesButtonActionPerformed(evt);
+ limitComboBoxActionPerformed(evt);
}
});
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ limitPane.add(limitComboBox, gridBagConstraints);
- accountTypesLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/accounts.png"))); // NOI18N
- accountTypesLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.accountTypesLabel.text")); // NOI18N
+ limitTitlePanel.setLayout(new java.awt.GridBagLayout());
- checkAllAccountTypesButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.checkAllAccountTypesButton.text")); // NOI18N
- checkAllAccountTypesButton.addActionListener(new java.awt.event.ActionListener() {
- public void actionPerformed(java.awt.event.ActionEvent evt) {
- checkAllAccountTypesButtonActionPerformed(evt);
- }
- });
+ limitHeaderLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.limitHeaderLabel.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ limitTitlePanel.add(limitHeaderLabel, gridBagConstraints);
- accountTypesScrollPane.setPreferredSize(new java.awt.Dimension(2, 200));
+ limitErrorMsgLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/error-icon-16.png"))); // NOI18N
+ limitErrorMsgLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.limitErrorMsgLabel.text")); // NOI18N
+ limitErrorMsgLabel.setForeground(new java.awt.Color(255, 0, 0));
+ limitErrorMsgLabel.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING);
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
+ limitTitlePanel.add(limitErrorMsgLabel, gridBagConstraints);
- accountTypeListPane.setLayout(new javax.swing.BoxLayout(accountTypeListPane, javax.swing.BoxLayout.Y_AXIS));
- accountTypesScrollPane.setViewportView(accountTypeListPane);
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.gridwidth = 2;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(0, 0, 9, 0);
+ limitPane.add(limitTitlePanel, gridBagConstraints);
- accountTypeRequiredLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/error-icon-16.png"))); // NOI18N
- accountTypeRequiredLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.accountTypeRequiredLabel.text")); // NOI18N
- accountTypeRequiredLabel.setForeground(new java.awt.Color(255, 0, 0));
- accountTypeRequiredLabel.setHorizontalTextPosition(javax.swing.SwingConstants.LEFT);
-
- javax.swing.GroupLayout accountTypesPaneLayout = new javax.swing.GroupLayout(accountTypesPane);
- accountTypesPane.setLayout(accountTypesPaneLayout);
- accountTypesPaneLayout.setHorizontalGroup(
- accountTypesPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, accountTypesPaneLayout.createSequentialGroup()
- .addGroup(accountTypesPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
- .addGroup(accountTypesPaneLayout.createSequentialGroup()
- .addComponent(accountTypesLabel)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addComponent(accountTypeRequiredLabel))
- .addGroup(accountTypesPaneLayout.createSequentialGroup()
- .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addComponent(unCheckAllAccountTypesButton)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(checkAllAccountTypesButton))
- .addGroup(accountTypesPaneLayout.createSequentialGroup()
- .addGap(10, 10, 10)
- .addComponent(accountTypesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
- .addGap(0, 0, 0))
- );
- accountTypesPaneLayout.setVerticalGroup(
- accountTypesPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(accountTypesPaneLayout.createSequentialGroup()
- .addGroup(accountTypesPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
- .addComponent(accountTypesLabel)
- .addComponent(accountTypeRequiredLabel))
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(accountTypesScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addGroup(accountTypesPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
- .addComponent(checkAllAccountTypesButton)
- .addComponent(unCheckAllAccountTypesButton)))
- );
-
- unCheckAllDevicesButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.unCheckAllDevicesButton.text")); // NOI18N
- unCheckAllDevicesButton.addActionListener(new java.awt.event.ActionListener() {
- public void actionPerformed(java.awt.event.ActionEvent evt) {
- unCheckAllDevicesButtonActionPerformed(evt);
- }
- });
-
- devicesLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/image.png"))); // NOI18N
- devicesLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.devicesLabel.text")); // NOI18N
-
- checkAllDevicesButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.checkAllDevicesButton.text")); // NOI18N
- checkAllDevicesButton.addActionListener(new java.awt.event.ActionListener() {
- public void actionPerformed(java.awt.event.ActionEvent evt) {
- checkAllDevicesButtonActionPerformed(evt);
- }
- });
-
- devicesScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
- devicesScrollPane.setMinimumSize(new java.awt.Dimension(27, 75));
-
- devicesListPane.setMinimumSize(new java.awt.Dimension(4, 100));
- devicesListPane.setLayout(new javax.swing.BoxLayout(devicesListPane, javax.swing.BoxLayout.Y_AXIS));
- devicesScrollPane.setViewportView(devicesListPane);
-
- deviceRequiredLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/error-icon-16.png"))); // NOI18N
- deviceRequiredLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.deviceRequiredLabel.text")); // NOI18N
- deviceRequiredLabel.setForeground(new java.awt.Color(255, 0, 0));
- deviceRequiredLabel.setHorizontalTextPosition(javax.swing.SwingConstants.LEFT);
-
- javax.swing.GroupLayout devicesPaneLayout = new javax.swing.GroupLayout(devicesPane);
- devicesPane.setLayout(devicesPaneLayout);
- devicesPaneLayout.setHorizontalGroup(
- devicesPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(devicesPaneLayout.createSequentialGroup()
- .addComponent(devicesLabel)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addComponent(deviceRequiredLabel))
- .addGroup(devicesPaneLayout.createSequentialGroup()
- .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addComponent(unCheckAllDevicesButton)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(checkAllDevicesButton))
- .addGroup(devicesPaneLayout.createSequentialGroup()
- .addGap(10, 10, 10)
- .addComponent(devicesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
- );
- devicesPaneLayout.setVerticalGroup(
- devicesPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(devicesPaneLayout.createSequentialGroup()
- .addGroup(devicesPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
- .addComponent(devicesLabel)
- .addComponent(deviceRequiredLabel))
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(devicesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 94, Short.MAX_VALUE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addGroup(devicesPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
- .addComponent(checkAllDevicesButton)
- .addComponent(unCheckAllDevicesButton))
- .addGap(5, 5, 5))
- );
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 3;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.weighty = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(15, 0, 15, 0);
+ mainPanel.add(limitPane, gridBagConstraints);
startDatePicker.setEnabled(false);
@@ -582,97 +570,177 @@ final public class FiltersPanel extends JPanel {
.addComponent(endCheckBox)))
);
- refreshButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/arrow-circle-double-135.png"))); // NOI18N
- refreshButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.refreshButton.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 2;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(15, 0, 0, 0);
+ mainPanel.add(dateRangePane, gridBagConstraints);
- needsRefreshLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.needsRefreshLabel.text")); // NOI18N
- needsRefreshLabel.setForeground(new java.awt.Color(255, 0, 0));
+ devicesPane.setLayout(new java.awt.GridBagLayout());
- limitHeaderLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.limitHeaderLabel.text")); // NOI18N
-
- mostRecentLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.mostRecentLabel.text")); // NOI18N
-
- limitComboBox.setEditable(true);
- limitComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "All", "10000", "5000", "1000", "500", "100" }));
- limitComboBox.addActionListener(new java.awt.event.ActionListener() {
+ unCheckAllDevicesButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.unCheckAllDevicesButton.text")); // NOI18N
+ unCheckAllDevicesButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
- limitComboBoxActionPerformed(evt);
+ unCheckAllDevicesButtonActionPerformed(evt);
}
});
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 2;
+ gridBagConstraints.gridwidth = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 9);
+ devicesPane.add(unCheckAllDevicesButton, gridBagConstraints);
- limitErrorMsgLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/error-icon-16.png"))); // NOI18N
- limitErrorMsgLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.limitErrorMsgLabel.text")); // NOI18N
- limitErrorMsgLabel.setForeground(new java.awt.Color(255, 0, 0));
- limitErrorMsgLabel.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING);
+ devicesLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/image.png"))); // NOI18N
+ devicesLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.devicesLabel.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(0, 0, 9, 0);
+ devicesPane.add(devicesLabel, gridBagConstraints);
- javax.swing.GroupLayout limitPaneLayout = new javax.swing.GroupLayout(limitPane);
- limitPane.setLayout(limitPaneLayout);
- limitPaneLayout.setHorizontalGroup(
- limitPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(limitPaneLayout.createSequentialGroup()
- .addComponent(limitHeaderLabel)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addComponent(limitErrorMsgLabel)
- .addContainerGap())
- .addGroup(limitPaneLayout.createSequentialGroup()
- .addContainerGap()
- .addComponent(mostRecentLabel)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(limitComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
- );
- limitPaneLayout.setVerticalGroup(
- limitPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(limitPaneLayout.createSequentialGroup()
- .addContainerGap()
- .addGroup(limitPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
- .addComponent(limitHeaderLabel)
- .addComponent(limitErrorMsgLabel))
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addGroup(limitPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
- .addComponent(mostRecentLabel)
- .addComponent(limitComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
- .addGap(0, 32, Short.MAX_VALUE))
- );
+ checkAllDevicesButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.checkAllDevicesButton.text")); // NOI18N
+ checkAllDevicesButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ checkAllDevicesButtonActionPerformed(evt);
+ }
+ });
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 2;
+ gridBagConstraints.gridy = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
+ gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0);
+ devicesPane.add(checkAllDevicesButton, gridBagConstraints);
- javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
- this.setLayout(layout);
- layout.setHorizontalGroup(
- layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(devicesPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addComponent(accountTypesPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addGroup(layout.createSequentialGroup()
- .addComponent(filtersTitleLabel)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addComponent(applyFiltersButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(refreshButton))
- .addComponent(dateRangePane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addGroup(layout.createSequentialGroup()
- .addContainerGap()
- .addComponent(needsRefreshLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
- .addComponent(limitPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- );
- layout.setVerticalGroup(
- layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(layout.createSequentialGroup()
- .addGap(0, 0, 0)
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
- .addComponent(filtersTitleLabel)
- .addComponent(applyFiltersButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addComponent(refreshButton))
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(needsRefreshLabel)
- .addGap(4, 4, 4)
- .addComponent(devicesPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(accountTypesPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(dateRangePane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(limitPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
- );
+ devicesScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
+ devicesScrollPane.setMinimumSize(new java.awt.Dimension(27, 75));
+
+ devicesListPane.setMinimumSize(new java.awt.Dimension(4, 100));
+ devicesListPane.setLayout(new javax.swing.BoxLayout(devicesListPane, javax.swing.BoxLayout.Y_AXIS));
+ devicesScrollPane.setViewportView(devicesListPane);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.gridwidth = 3;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.weighty = 1.0;
+ devicesPane.add(devicesScrollPane, gridBagConstraints);
+
+ deviceRequiredLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/error-icon-16.png"))); // NOI18N
+ deviceRequiredLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.deviceRequiredLabel.text")); // NOI18N
+ deviceRequiredLabel.setForeground(new java.awt.Color(255, 0, 0));
+ deviceRequiredLabel.setHorizontalTextPosition(javax.swing.SwingConstants.LEFT);
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.gridwidth = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
+ gridBagConstraints.insets = new java.awt.Insets(0, 0, 9, 0);
+ devicesPane.add(deviceRequiredLabel, gridBagConstraints);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.ipady = 100;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(15, 0, 0, 0);
+ mainPanel.add(devicesPane, gridBagConstraints);
+
+ accountTypesPane.setLayout(new java.awt.GridBagLayout());
+
+ unCheckAllAccountTypesButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.unCheckAllAccountTypesButton.text")); // NOI18N
+ unCheckAllAccountTypesButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ unCheckAllAccountTypesButtonActionPerformed(evt);
+ }
+ });
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 2;
+ gridBagConstraints.gridwidth = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 9);
+ accountTypesPane.add(unCheckAllAccountTypesButton, gridBagConstraints);
+
+ accountTypesLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/accounts.png"))); // NOI18N
+ accountTypesLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.accountTypesLabel.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ accountTypesPane.add(accountTypesLabel, gridBagConstraints);
+
+ checkAllAccountTypesButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.checkAllAccountTypesButton.text")); // NOI18N
+ checkAllAccountTypesButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ checkAllAccountTypesButtonActionPerformed(evt);
+ }
+ });
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 2;
+ gridBagConstraints.gridy = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
+ gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0);
+ accountTypesPane.add(checkAllAccountTypesButton, gridBagConstraints);
+
+ accountTypesScrollPane.setPreferredSize(new java.awt.Dimension(2, 200));
+
+ accountTypeListPane.setLayout(new javax.swing.BoxLayout(accountTypeListPane, javax.swing.BoxLayout.Y_AXIS));
+ accountTypesScrollPane.setViewportView(accountTypeListPane);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.gridwidth = 3;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.weighty = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0);
+ accountTypesPane.add(accountTypesScrollPane, gridBagConstraints);
+
+ accountTypeRequiredLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/error-icon-16.png"))); // NOI18N
+ accountTypeRequiredLabel.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.accountTypeRequiredLabel.text")); // NOI18N
+ accountTypeRequiredLabel.setForeground(new java.awt.Color(255, 0, 0));
+ accountTypeRequiredLabel.setHorizontalTextPosition(javax.swing.SwingConstants.LEFT);
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.gridwidth = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
+ accountTypesPane.add(accountTypeRequiredLabel, gridBagConstraints);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(15, 0, 0, 0);
+ mainPanel.add(accountTypesPane, gridBagConstraints);
+
+ scrollPane.setViewportView(mainPanel);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.weighty = 1.0;
+ add(scrollPane, gridBagConstraints);
}// //GEN-END:initComponents
/**
@@ -933,11 +1001,15 @@ final public class FiltersPanel extends JPanel {
private final javax.swing.JLabel limitErrorMsgLabel = new javax.swing.JLabel();
private final javax.swing.JLabel limitHeaderLabel = new javax.swing.JLabel();
private final javax.swing.JPanel limitPane = new javax.swing.JPanel();
+ private final javax.swing.JPanel limitTitlePanel = new javax.swing.JPanel();
+ private final javax.swing.JPanel mainPanel = new javax.swing.JPanel();
private final javax.swing.JLabel mostRecentLabel = new javax.swing.JLabel();
private final javax.swing.JLabel needsRefreshLabel = new javax.swing.JLabel();
private final javax.swing.JButton refreshButton = new javax.swing.JButton();
+ private final javax.swing.JScrollPane scrollPane = new javax.swing.JScrollPane();
private final javax.swing.JCheckBox startCheckBox = new javax.swing.JCheckBox();
private final com.github.lgooddatepicker.components.DatePicker startDatePicker = new com.github.lgooddatepicker.components.DatePicker();
+ private final javax.swing.JPanel topPane = new javax.swing.JPanel();
private final javax.swing.JButton unCheckAllAccountTypesButton = new javax.swing.JButton();
private final javax.swing.JButton unCheckAllDevicesButton = new javax.swing.JButton();
// End of variables declaration//GEN-END:variables
diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED
index b31598dbc6..162cf1982f 100755
--- a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED
@@ -44,3 +44,4 @@ SummaryViewer_CentralRepository_Message=
diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.form
index 83cb8057d0..308eec1a42 100755
--- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.form
+++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.form
@@ -11,29 +11,38 @@
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java
index 2fec1406c0..4fdb400c87 100755
--- a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java
@@ -23,7 +23,6 @@ import java.awt.KeyboardFocusManager;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JPanel;
-import javax.swing.SwingUtilities;
import static javax.swing.SwingUtilities.isDescendingFrom;
import org.netbeans.swing.outline.DefaultOutlineModel;
import org.netbeans.swing.outline.Outline;
@@ -126,6 +125,9 @@ public final class ContactsViewer extends JPanel implements RelationshipsViewer{
updateOutlineViewPanel();
}
});
+
+ splitPane.setResizeWeight(0.5);
+ splitPane.setDividerLocation(0.5);
}
@Override
@@ -183,29 +185,32 @@ public final class ContactsViewer extends JPanel implements RelationshipsViewer{
@SuppressWarnings("unchecked")
// //GEN-BEGIN:initComponents
private void initComponents() {
+ java.awt.GridBagConstraints gridBagConstraints;
+ splitPane = new javax.swing.JSplitPane();
contactPane = new org.sleuthkit.autopsy.communications.relationships.ContactDetailsPane();
outlineViewPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel();
- javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
- this.setLayout(layout);
- layout.setHorizontalGroup(
- layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(contactPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addComponent(outlineViewPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- );
- layout.setVerticalGroup(
- layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(layout.createSequentialGroup()
- .addComponent(outlineViewPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(contactPane, javax.swing.GroupLayout.DEFAULT_SIZE, 332, Short.MAX_VALUE))
- );
+ setLayout(new java.awt.GridBagLayout());
+
+ splitPane.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
+ splitPane.setRightComponent(contactPane);
+ splitPane.setLeftComponent(outlineViewPanel);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.weighty = 1.0;
+ add(splitPane, gridBagConstraints);
}// //GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private org.sleuthkit.autopsy.communications.relationships.ContactDetailsPane contactPane;
private org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel outlineViewPanel;
+ private javax.swing.JSplitPane splitPane;
// End of variables declaration//GEN-END:variables
}
diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.form
index 8f311d3dd4..cd109eaea8 100755
--- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.form
+++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.form
@@ -11,58 +11,57 @@
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java
index 95405b0887..cbb71263e2 100755
--- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java
@@ -65,6 +65,11 @@ final class MediaViewer extends JPanel implements RelationshipsViewer, ExplorerM
* Creates new form ThumbnailViewer
*/
public MediaViewer() {
+ initComponents();
+
+ splitPane.setResizeWeight(0.5);
+ splitPane.setDividerLocation(0.5);
+
proxyLookup = new ModifiableProxyLookup(createLookup(tableEM, getActionMap()));
// See org.sleuthkit.autopsy.timeline.TimeLineTopComponent for a detailed
@@ -87,8 +92,6 @@ final class MediaViewer extends JPanel implements RelationshipsViewer, ExplorerM
}
};
- initComponents();
-
tableEM.addPropertyChangeListener((PropertyChangeEvent evt) -> {
if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
handleNodeSelectionChange();
@@ -190,43 +193,37 @@ final class MediaViewer extends JPanel implements RelationshipsViewer, ExplorerM
@SuppressWarnings("unchecked")
// //GEN-BEGIN:initComponents
private void initComponents() {
+ java.awt.GridBagConstraints gridBagConstraints;
+ splitPane = new javax.swing.JSplitPane();
thumbnailViewer = new org.sleuthkit.autopsy.corecomponents.DataResultViewerThumbnail(tableEM);
contentViewer = new MessageDataContent();
- separator = new javax.swing.JSeparator();
+
+ setLayout(new java.awt.GridBagLayout());
+
+ splitPane.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
thumbnailViewer.setMinimumSize(new java.awt.Dimension(350, 102));
thumbnailViewer.setPreferredSize(new java.awt.Dimension(450, 400));
+ splitPane.setLeftComponent(thumbnailViewer);
contentViewer.setPreferredSize(new java.awt.Dimension(450, 400));
+ splitPane.setRightComponent(contentViewer);
- javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
- this.setLayout(layout);
- layout.setHorizontalGroup(
- layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(thumbnailViewer, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addComponent(contentViewer, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addGroup(layout.createSequentialGroup()
- .addContainerGap()
- .addComponent(separator)
- .addContainerGap())
- );
- layout.setVerticalGroup(
- layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(layout.createSequentialGroup()
- .addComponent(thumbnailViewer, javax.swing.GroupLayout.DEFAULT_SIZE, 350, Short.MAX_VALUE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(separator, javax.swing.GroupLayout.PREFERRED_SIZE, 2, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(contentViewer, javax.swing.GroupLayout.PREFERRED_SIZE, 450, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addGap(3, 3, 3))
- );
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.weighty = 1.0;
+ add(splitPane, gridBagConstraints);
}// //GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private org.sleuthkit.autopsy.contentviewers.MessageContentViewer contentViewer;
- private javax.swing.JSeparator separator;
+ private javax.swing.JSplitPane splitPane;
private org.sleuthkit.autopsy.corecomponents.DataResultViewerThumbnail thumbnailViewer;
// End of variables declaration//GEN-END:variables
diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java
index e6ac3f507f..d1cdcbabf2 100755
--- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java
+++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java
@@ -18,13 +18,10 @@
*/
package org.sleuthkit.autopsy.communications.relationships;
-import java.util.List;
import java.util.TimeZone;
import java.util.logging.Level;
import org.apache.commons.lang3.StringUtils;
import org.openide.nodes.Sheet;
-import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
-import org.sleuthkit.autopsy.core.UserPreferences;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
@@ -40,7 +37,6 @@ import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHO
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO;
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SUBJECT;
import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME;
-import org.sleuthkit.datamodel.Tag;
import org.sleuthkit.datamodel.TimeUtilities;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.autopsy.communications.Utils;
@@ -72,7 +68,6 @@ final class MessageNode extends BlackboardArtifactNode {
@Override
protected Sheet createSheet() {
Sheet sheet = super.createSheet();
- List tags = getAllTagsFromDatabase();
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
if (sheetSet == null) {
sheetSet = Sheet.createPropertiesSet();
@@ -81,17 +76,6 @@ final class MessageNode extends BlackboardArtifactNode {
sheetSet.put(new NodeProperty<>("Type", Bundle.MessageNode_Node_Property_Type(), "", getDisplayName())); //NON-NLS
- addScoreProperty(sheetSet, tags);
-
- CorrelationAttributeInstance correlationAttribute = null;
- if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) {
- correlationAttribute = getCorrelationAttributeInstance();
- }
- addCommentProperty(sheetSet, tags, correlationAttribute);
-
- if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) {
- addCountProperty(sheetSet, correlationAttribute);
- }
final BlackboardArtifact artifact = getArtifact();
BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID());
diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesViewer.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesViewer.form
index 34126bbf8c..780c55aa04 100755
--- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesViewer.form
+++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesViewer.form
@@ -11,32 +11,41 @@
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesViewer.java
index 8bda078161..f258e2c7a3 100755
--- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesViewer.java
@@ -23,7 +23,6 @@ import java.awt.KeyboardFocusManager;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JPanel;
-import javax.swing.SwingUtilities;
import static javax.swing.SwingUtilities.isDescendingFrom;
import org.netbeans.swing.outline.DefaultOutlineModel;
import org.netbeans.swing.outline.Outline;
@@ -68,6 +67,8 @@ public final class MessagesViewer extends JPanel implements RelationshipsViewer
public MessagesViewer() {
initComponents();
+ splitPane.setResizeWeight(0.5);
+
outlineViewPanel.hideOutlineView(Bundle.MessageViewer_no_messages());
proxyLookup = new ModifiableProxyLookup(createLookup(outlineViewPanel.getExplorerManager(), getActionMap()));
@@ -138,6 +139,8 @@ public final class MessagesViewer extends JPanel implements RelationshipsViewer
updateOutlineViewPanel();
}
});
+
+ outlineViewPanel.setTableColumnsWidth(5,10,10,15,50,10);
}
@Override
@@ -192,29 +195,32 @@ public final class MessagesViewer extends JPanel implements RelationshipsViewer
@SuppressWarnings("unchecked")
// //GEN-BEGIN:initComponents
private void initComponents() {
+ java.awt.GridBagConstraints gridBagConstraints;
+ splitPane = new javax.swing.JSplitPane();
contentViewer = new MessageDataContent();
outlineViewPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel();
- javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
- this.setLayout(layout);
- layout.setHorizontalGroup(
- layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(contentViewer, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addComponent(outlineViewPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- );
- layout.setVerticalGroup(
- layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(layout.createSequentialGroup()
- .addComponent(outlineViewPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(contentViewer, javax.swing.GroupLayout.DEFAULT_SIZE, 390, Short.MAX_VALUE))
- );
+ setLayout(new java.awt.GridBagLayout());
+
+ splitPane.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
+ splitPane.setBottomComponent(contentViewer);
+ splitPane.setLeftComponent(outlineViewPanel);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.weighty = 1.0;
+ add(splitPane, gridBagConstraints);
}// //GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private org.sleuthkit.autopsy.contentviewers.MessageContentViewer contentViewer;
private org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel outlineViewPanel;
+ private javax.swing.JSplitPane splitPane;
// End of variables declaration//GEN-END:variables
}
diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.form
index 5efb16c2b1..159fd3d554 100755
--- a/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.form
@@ -1,6 +1,9 @@
diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java
index b8c19f518e..29ec349b82 100755
--- a/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java
+++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java
@@ -54,9 +54,7 @@ public final class RelationshipBrowser extends JPanel implements Lookup.Provider
tabPane.add(summaryViewer.getDisplayName(), summaryViewer);
tabPane.add(messagesViewer.getDisplayName(), messagesViewer);
tabPane.add(contactsViewer.getDisplayName(), contactsViewer);
- tabPane.add(mediaViewer.getDisplayName(), mediaViewer);
-
-
+ tabPane.add(mediaViewer.getDisplayName(), mediaViewer);
}
/**
@@ -78,33 +76,26 @@ public final class RelationshipBrowser extends JPanel implements Lookup.Provider
@SuppressWarnings("unchecked")
// //GEN-BEGIN:initComponents
private void initComponents() {
+ java.awt.GridBagConstraints gridBagConstraints;
- scrollPane = new javax.swing.JScrollPane();
tabPane = new javax.swing.JTabbedPane();
- scrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
+ setLayout(new java.awt.GridBagLayout());
tabPane.addChangeListener(new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent evt) {
tabPaneStateChanged(evt);
}
});
- scrollPane.setViewportView(tabPane);
-
- javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
- this.setLayout(layout);
- layout.setHorizontalGroup(
- layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGap(0, 400, Short.MAX_VALUE)
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(scrollPane, javax.swing.GroupLayout.Alignment.TRAILING))
- );
- layout.setVerticalGroup(
- layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGap(0, 300, Short.MAX_VALUE)
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(scrollPane))
- );
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.weighty = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(1, 1, 1, 1);
+ add(tabPane, gridBagConstraints);
}// //GEN-END:initComponents
private void tabPaneStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_tabPaneStateChanged
@@ -121,7 +112,6 @@ public final class RelationshipBrowser extends JPanel implements Lookup.Provider
// Variables declaration - do not modify//GEN-BEGIN:variables
- private javax.swing.JScrollPane scrollPane;
private javax.swing.JTabbedPane tabPane;
// End of variables declaration//GEN-END:variables
diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.form
index af02a7dee7..c789e2c0cd 100755
--- a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.form
+++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.form
@@ -11,36 +11,10 @@
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -52,6 +26,11 @@
+
+
+
+
+
@@ -183,7 +162,7 @@
-
+
@@ -192,12 +171,14 @@
-
-
-
+
+
+
+
+
-
+
@@ -206,10 +187,12 @@
-
-
-
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java
index d31e7c902f..464fb137f8 100755
--- a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java
@@ -46,7 +46,8 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
"SummaryViewer_FileRefNameColumn_Title=Path",
"SummaryViewer_CaseRefNameColumn_Title=Case Name",
"SummaryViewer_CentralRepository_Message=",
- "SummaryViewer_Creation_Date_Title=Creation Date"
+ "SummaryViewer_Creation_Date_Title=Creation Date",
+ "SummeryViewer_FileRef_Message=
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java
index 5c16dbed93..fbca1e3ab8 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java
@@ -20,18 +20,31 @@ package org.sleuthkit.autopsy.contentviewers;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static java.util.Objects.nonNull;
import java.util.SortedSet;
import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
import java.util.stream.Collectors;
import javafx.application.Platform;
+import javafx.collections.ListChangeListener.Change;
import javafx.concurrent.Task;
import javafx.embed.swing.JFXPanel;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.Cursor;
+import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
@@ -45,19 +58,39 @@ import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
import javax.imageio.ImageIO;
+import javax.swing.JFileChooser;
+import javafx.scene.Node;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JSeparator;
+import javax.swing.SwingUtilities;
+import javax.swing.SwingWorker;
+import org.apache.commons.io.FilenameUtils;
import org.controlsfx.control.MaskerPane;
import org.openide.util.NbBundle;
import org.python.google.common.collect.Lists;
-import javafx.scene.Group;
-import javafx.scene.input.MouseEvent;
-import javafx.scene.paint.Color;
-import javafx.scene.shape.Rectangle;
+import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog;
+import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog.TagNameAndComment;
import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager;
+import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager.ContentViewerTag;
+import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager.SerializationException;
+import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagsUtility;
+import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagControls;
+import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagRegion;
+import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagCreator;
+import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTag;
+import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagsGroup;
import org.sleuthkit.autopsy.coreutils.ImageUtils;
+import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.FileNode;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.datamodel.AbstractFile;
+import org.sleuthkit.datamodel.ContentTag;
+import org.sleuthkit.datamodel.TskCoreException;
/**
* Image viewer part of the Media View layered pane. Uses JavaFX to display the
@@ -70,17 +103,30 @@ import org.sleuthkit.datamodel.AbstractFile;
class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPanel {
private static final Image EXTERNAL = new Image(MediaViewImagePanel.class.getResource("/org/sleuthkit/autopsy/images/external.png").toExternalForm());
+ private final static Logger LOGGER = Logger.getLogger(MediaViewImagePanel.class.getName());
private final boolean fxInited;
private JFXPanel fxPanel;
- private Group imageGroup;
- private ImageTaggingTool tagger;
+ private AbstractFile file;
+ private Group masterGroup;
+ private ImageTagsGroup tagsGroup;
+ private ImageTagCreator imageTagCreator;
private ImageView fxImageView;
private ScrollPane scrollPane;
private final ProgressBar progressBar = new ProgressBar();
private final MaskerPane maskerPane = new MaskerPane();
+ private final JPopupMenu popupMenu = new JPopupMenu();
+ private final JMenuItem createTagMenuItem;
+ private final JMenuItem deleteTagMenuItem;
+ private final JMenuItem hideTagsMenuItem;
+ private final JMenuItem exportTagsMenuItem;
+
+ private final JFileChooser exportChooser;
+
+ private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
+
private double zoomRatio;
private double rotation; // Can be 0, 90, 180, and 270.
@@ -116,33 +162,163 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
public MediaViewImagePanel() {
initComponents();
fxInited = org.sleuthkit.autopsy.core.Installer.isJavaFxInited();
+
+ exportChooser = new JFileChooser();
+ exportChooser.setDialogTitle(Bundle.MediaViewImagePanel_fileChooserTitle());
+
+ //Build popupMenu when Tags Menu button is pressed.
+ createTagMenuItem = new JMenuItem("Create");
+ createTagMenuItem.addActionListener((event) -> createTag());
+ popupMenu.add(createTagMenuItem);
+
+ popupMenu.add(new JSeparator());
+
+ deleteTagMenuItem = new JMenuItem("Delete");
+ deleteTagMenuItem.addActionListener((event) -> deleteTag());
+ popupMenu.add(deleteTagMenuItem);
+
+ popupMenu.add(new JSeparator());
+
+ hideTagsMenuItem = new JMenuItem("Hide");
+ hideTagsMenuItem.addActionListener((event) -> showOrHideTags());
+ popupMenu.add(hideTagsMenuItem);
+
+ popupMenu.add(new JSeparator());
+
+ exportTagsMenuItem = new JMenuItem("Export");
+ exportTagsMenuItem.addActionListener((event) -> exportTags());
+ popupMenu.add(exportTagsMenuItem);
+
+ popupMenu.setPopupSize(300, 150);
+
if (fxInited) {
- Platform.runLater(() -> {
+ Platform.runLater(new Runnable() {
+ @Override
+ public void run() {
+ // build jfx ui (we could do this in FXML?)
+ fxImageView = new ImageView(); // will hold image
+ masterGroup = new Group(fxImageView);
+ tagsGroup = new ImageTagsGroup(fxImageView);
+ tagsGroup.getChildren().addListener((Change extends Node> c) -> {
+ if (c.getList().isEmpty()) {
+ pcs.firePropertyChange(new PropertyChangeEvent(this,
+ "state", null, State.EMPTY));
+ }
+ });
- // build jfx ui (we could do this in FXML?)
- fxImageView = new ImageView(); // will hold image
- imageGroup = new Group();
- imageGroup.getChildren().add(fxImageView);
- scrollPane = new ScrollPane(imageGroup); // scrolls and sizes imageview
- scrollPane.getStyleClass().add("bg"); //NOI18N
- scrollPane.setVbarPolicy(ScrollBarPolicy.AS_NEEDED);
- scrollPane.setHbarPolicy(ScrollBarPolicy.AS_NEEDED);
+ subscribeTagMenuItemsToStateChanges();
- fxPanel = new JFXPanel(); // bridge jfx-swing
- Scene scene = new Scene(scrollPane); //root of jfx tree
- scene.getStylesheets().add(MediaViewImagePanel.class.getResource("MediaViewImagePanel.css").toExternalForm()); //NOI18N
- fxPanel.setScene(scene);
+ masterGroup.getChildren().add(tagsGroup);
- fxImageView.setSmooth(true);
- fxImageView.setCache(true);
+ //Update buttons when users select (or unselect) image tags.
+ tagsGroup.addFocusChangeListener((event) -> {
+ if (event.getPropertyName().equals(ImageTagControls.NOT_FOCUSED.getName())) {
+ if (masterGroup.getChildren().contains(imageTagCreator)) {
+ return;
+ }
- EventQueue.invokeLater(() -> {
- add(fxPanel);//add jfx ui to JPanel
- });
+ if (tagsGroup.getChildren().isEmpty()) {
+ pcs.firePropertyChange(new PropertyChangeEvent(this,
+ "state", null, State.EMPTY));
+ } else {
+ pcs.firePropertyChange(new PropertyChangeEvent(this,
+ "state", null, State.CREATE));
+ }
+ } else if (event.getPropertyName().equals(ImageTagControls.FOCUSED.getName())) {
+ pcs.firePropertyChange(new PropertyChangeEvent(this,
+ "state", null, State.SELECTED));
+ }
+ });
+
+ scrollPane = new ScrollPane(masterGroup); // scrolls and sizes imageview
+ scrollPane.getStyleClass().add("bg"); //NOI18N
+ scrollPane.setVbarPolicy(ScrollBarPolicy.AS_NEEDED);
+ scrollPane.setHbarPolicy(ScrollBarPolicy.AS_NEEDED);
+
+ fxPanel = new JFXPanel(); // bridge jfx-swing
+ Scene scene = new Scene(scrollPane); //root of jfx tree
+ scene.getStylesheets().add(MediaViewImagePanel.class.getResource("MediaViewImagePanel.css").toExternalForm()); //NOI18N
+ fxPanel.setScene(scene);
+
+ fxImageView.setSmooth(true);
+ fxImageView.setCache(true);
+
+ EventQueue.invokeLater(() -> {
+ add(fxPanel);//add jfx ui to JPanel
+ });
+ }
});
}
}
+ /**
+ * Handle tags menu item enabling and disabling given the state of the
+ * content viewer. For example, when the tags group is empty (no tags on image),
+ * disable delete menu item, hide menu item, and export menu item.
+ */
+ private void subscribeTagMenuItemsToStateChanges() {
+ pcs.addPropertyChangeListener((event) -> {
+ State currentState = (State) event.getNewValue();
+ switch (currentState) {
+ case CREATE:
+ createTagMenuItem.setEnabled(true);
+ deleteTagMenuItem.setEnabled(false);
+ hideTagsMenuItem.setEnabled(true);
+ exportTagsMenuItem.setEnabled(true);
+ break;
+ case SELECTED:
+ if (masterGroup.getChildren().contains(imageTagCreator)) {
+ imageTagCreator.disconnect();
+ masterGroup.getChildren().remove(imageTagCreator);
+ }
+ createTagMenuItem.setEnabled(false);
+ deleteTagMenuItem.setEnabled(true);
+ hideTagsMenuItem.setEnabled(true);
+ exportTagsMenuItem.setEnabled(true);
+ break;
+ case HIDDEN:
+ createTagMenuItem.setEnabled(false);
+ deleteTagMenuItem.setEnabled(false);
+ hideTagsMenuItem.setEnabled(true);
+ hideTagsMenuItem.setText(DisplayOptions.SHOW_TAGS.getName());
+ exportTagsMenuItem.setEnabled(false);
+ break;
+ case VISIBLE:
+ createTagMenuItem.setEnabled(true);
+ deleteTagMenuItem.setEnabled(false);
+ hideTagsMenuItem.setEnabled(true);
+ hideTagsMenuItem.setText(DisplayOptions.HIDE_TAGS.getName());
+ exportTagsMenuItem.setEnabled(true);
+ break;
+ case DEFAULT:
+ case EMPTY:
+ if (masterGroup.getChildren().contains(imageTagCreator)) {
+ imageTagCreator.disconnect();
+ }
+ createTagMenuItem.setEnabled(true);
+ deleteTagMenuItem.setEnabled(false);
+ hideTagsMenuItem.setEnabled(false);
+ hideTagsMenuItem.setText(DisplayOptions.HIDE_TAGS.getName());
+ exportTagsMenuItem.setEnabled(false);
+ break;
+ case NONEMPTY:
+ createTagMenuItem.setEnabled(true);
+ deleteTagMenuItem.setEnabled(false);
+ hideTagsMenuItem.setEnabled(true);
+ exportTagsMenuItem.setEnabled(true);
+ break;
+ case DISABLE:
+ createTagMenuItem.setEnabled(false);
+ deleteTagMenuItem.setEnabled(false);
+ hideTagsMenuItem.setEnabled(false);
+ exportTagsMenuItem.setEnabled(false);
+ break;
+ default:
+ break;
+ }
+ });
+ }
+
public boolean isInited() {
return fxInited;
}
@@ -154,10 +330,11 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
Platform.runLater(() -> {
fxImageView.setViewport(new Rectangle2D(0, 0, 0, 0));
fxImageView.setImage(null);
- tagger.defaultSettings();
-
+ pcs.firePropertyChange(new PropertyChangeEvent(this,
+ "state", null, State.DEFAULT));
+ masterGroup.getChildren().clear();
scrollPane.setContent(null);
- scrollPane.setContent(imageGroup);
+ scrollPane.setContent(masterGroup);
});
}
@@ -205,14 +382,31 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
try {
Image fxImage = readImageTask.get();
+ masterGroup.getChildren().clear();
+ tagsGroup.getChildren().clear();
+ this.file = file;
if (nonNull(fxImage)) {
// We have a non-null image, so let's show it.
fxImageView.setImage(fxImage);
- imageGroup.getChildren().remove(tagger);
- tagger = new ImageTaggingTool(fxImageView, Color.RED);
- imageGroup.getChildren().add(tagger);
resetView();
- scrollPane.setContent(imageGroup);
+ masterGroup.getChildren().add(fxImageView);
+ masterGroup.getChildren().add(tagsGroup);
+
+ try {
+ List tags = Case.getCurrentCase().getServices()
+ .getTagsManager().getContentTagsByContent(file);
+
+ List> contentViewerTags = getContentViewerTags(tags);
+ //Add all image tags
+ tagsGroup = buildImageTagsGroup(contentViewerTags);
+ if (!tagsGroup.getChildren().isEmpty()) {
+ pcs.firePropertyChange(new PropertyChangeEvent(this,
+ "state", null, State.NONEMPTY));
+ }
+ } catch (TskCoreException | NoCurrentCaseException ex) {
+ LOGGER.log(Level.WARNING, "Could not retrieve image tags for file in case db", ex); //NON-NLS
+ }
+ scrollPane.setContent(masterGroup);
} else {
showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file);
}
@@ -252,6 +446,52 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
});
}
+ /**
+ * Finds all ContentViewerTags that are of type 'ImageTagRegion' for the
+ * current file.
+ *
+ * @param contentTags
+ * @return
+ * @throws TskCoreException
+ * @throws NoCurrentCaseException
+ */
+ private List> getContentViewerTags(List contentTags)
+ throws TskCoreException, NoCurrentCaseException {
+ List> contentViewerTags = new ArrayList<>();
+ for (ContentTag contentTag : contentTags) {
+ ContentViewerTag contentViewerTag = ContentViewerTagManager
+ .getTag(contentTag, ImageTagRegion.class);
+ if (contentViewerTag == null) {
+ continue;
+ }
+
+ contentViewerTags.add(contentViewerTag);
+ }
+ return contentViewerTags;
+ }
+
+ /**
+ * Builds ImageTag instances from stored ContentViewerTags of the
+ * appropriate type.
+ *
+ * @param contentTags
+ * @return
+ * @throws TskCoreException
+ * @throws NoCurrentCaseException
+ */
+ private ImageTagsGroup buildImageTagsGroup(List> contentViewerTags) {
+
+ contentViewerTags.forEach(contentViewerTag -> {
+ /**
+ * Build the image tag, add an edit event call back to persist all
+ * edits made on this image tag instance.
+ */
+ tagsGroup.getChildren().add(buildImageTag(contentViewerTag));
+ });
+
+ return tagsGroup;
+ }
+
/**
* @return supported mime types
*/
@@ -303,6 +543,10 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
zoomInButton = new javax.swing.JButton();
jSeparator2 = new javax.swing.JToolBar.Separator();
zoomResetButton = new javax.swing.JButton();
+ filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0));
+ filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(32767, 0));
+ jPanel1 = new javax.swing.JPanel();
+ tagsMenu = new javax.swing.JButton();
setBackground(new java.awt.Color(0, 0, 0));
addComponentListener(new java.awt.event.ComponentAdapter() {
@@ -406,6 +650,23 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
}
});
toolbar.add(zoomResetButton);
+ toolbar.add(filler1);
+ toolbar.add(filler2);
+ toolbar.add(jPanel1);
+
+ org.openide.awt.Mnemonics.setLocalizedText(tagsMenu, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.tagsMenu.text_1")); // NOI18N
+ tagsMenu.setFocusable(false);
+ tagsMenu.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
+ tagsMenu.setMaximumSize(new java.awt.Dimension(75, 21));
+ tagsMenu.setMinimumSize(new java.awt.Dimension(75, 21));
+ tagsMenu.setPreferredSize(new java.awt.Dimension(75, 21));
+ tagsMenu.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
+ tagsMenu.addMouseListener(new java.awt.event.MouseAdapter() {
+ public void mousePressed(java.awt.event.MouseEvent evt) {
+ tagsMenuMousePressed(evt);
+ }
+ });
+ toolbar.add(tagsMenu);
add(toolbar);
}// //GEN-END:initComponents
@@ -450,12 +711,234 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
updateView();
}//GEN-LAST:event_formComponentResized
+ /**
+ * Deletes the selected tag when the Delete button is pressed in the Tag
+ * Menu.
+ */
+ private void deleteTag() {
+ Platform.runLater(() -> {
+ ImageTag tagInFocus = tagsGroup.getFocus();
+ if (tagInFocus == null) {
+ return;
+ }
+
+ try {
+ ContentViewerTag contentViewerTag = tagInFocus.getContentViewerTag();
+ scrollPane.setCursor(Cursor.WAIT);
+ ContentViewerTagManager.deleteTag(contentViewerTag);
+ Case.getCurrentCase().getServices().getTagsManager().deleteContentTag(contentViewerTag.getContentTag());
+ tagsGroup.getChildren().remove(tagInFocus);
+ } catch (TskCoreException | NoCurrentCaseException ex) {
+ LOGGER.log(Level.WARNING, "Could not delete image tag in case db", ex); //NON-NLS
+ }
+
+ scrollPane.setCursor(Cursor.DEFAULT);
+ });
+
+ pcs.firePropertyChange(new PropertyChangeEvent(this,
+ "state", null, State.CREATE));
+ }
+
+ /**
+ * Enables create tag logic when the Create button is pressed in the Tags
+ * Menu.
+ */
+ private void createTag() {
+ pcs.firePropertyChange(new PropertyChangeEvent(this,
+ "state", null, State.DISABLE));
+ imageTagCreator = new ImageTagCreator(fxImageView);
+
+ PropertyChangeListener newTagListener = (event) -> {
+ SwingUtilities.invokeLater(() -> {
+ ImageTagRegion tag = (ImageTagRegion) event.getNewValue();
+ //Ask the user for tag name and comment
+ TagNameAndComment result = GetTagNameAndCommentDialog.doDialog();
+ if (result != null) {
+ //Persist and build image tag
+ Platform.runLater(() -> {
+ try {
+ scrollPane.setCursor(Cursor.WAIT);
+ ContentViewerTag contentViewerTag = storeImageTag(tag, result);
+ ImageTag imageTag = buildImageTag(contentViewerTag);
+ tagsGroup.getChildren().add(imageTag);
+ } catch (TskCoreException | SerializationException | NoCurrentCaseException ex) {
+ LOGGER.log(Level.WARNING, "Could not save new image tag in case db", ex); //NON-NLS
+ }
+
+ scrollPane.setCursor(Cursor.DEFAULT);
+ });
+ }
+
+ pcs.firePropertyChange(new PropertyChangeEvent(this,
+ "state", null, State.CREATE));
+ });
+
+ //Remove image tag creator from panel
+ Platform.runLater(() -> {
+ imageTagCreator.disconnect();
+ masterGroup.getChildren().remove(imageTagCreator);
+ });
+ };
+
+ imageTagCreator.addNewTagListener(newTagListener);
+ Platform.runLater(() -> masterGroup.getChildren().add(imageTagCreator));
+ }
+
+ /**
+ * Creates an ImageTag instance from the ContentViewerTag.
+ *
+ * @param contentViewerTag
+ * @return
+ */
+ private ImageTag buildImageTag(ContentViewerTag contentViewerTag) {
+ ImageTag imageTag = new ImageTag(contentViewerTag, fxImageView);
+
+ //Automatically persist edits made by user
+ imageTag.subscribeToEditEvents((edit) -> {
+ try {
+ scrollPane.setCursor(Cursor.WAIT);
+ ImageTagRegion newRegion = (ImageTagRegion) edit.getNewValue();
+ ContentViewerTagManager.updateTag(contentViewerTag, newRegion);
+ } catch (SerializationException | TskCoreException | NoCurrentCaseException ex) {
+ LOGGER.log(Level.WARNING, "Could not save edit for image tag in case db", ex); //NON-NLS
+ }
+ scrollPane.setCursor(Cursor.DEFAULT);
+ });
+ return imageTag;
+ }
+
+ /**
+ * Stores the image tag by creating a ContentTag instance and associating
+ * the ImageTagRegion data with it in the case database.
+ *
+ * @param data
+ * @param result
+ */
+ private ContentViewerTag storeImageTag(ImageTagRegion data, TagNameAndComment result)
+ throws TskCoreException, SerializationException, NoCurrentCaseException {
+ scrollPane.setCursor(Cursor.WAIT);
+ try {
+ ContentTag contentTag = Case.getCurrentCaseThrows().getServices().getTagsManager()
+ .addContentTag(file, result.getTagName(), result.getComment());
+ return ContentViewerTagManager.saveTag(contentTag, data);
+ } finally {
+ scrollPane.setCursor(Cursor.DEFAULT);
+ }
+ }
+
+ /**
+ * Hides or show tags when the Hide or Show button is pressed in the Tags
+ * Menu.
+ */
+ private void showOrHideTags() {
+ Platform.runLater(() -> {
+ if (DisplayOptions.HIDE_TAGS.getName().equals(hideTagsMenuItem.getText())) {
+ //Temporarily remove the tags group and update buttons
+ masterGroup.getChildren().remove(tagsGroup);
+ hideTagsMenuItem.setText(DisplayOptions.SHOW_TAGS.getName());
+ tagsGroup.clearFocus();
+ pcs.firePropertyChange(new PropertyChangeEvent(this,
+ "state", null, State.HIDDEN));
+ } else {
+ //Add tags group back in and update buttons
+ masterGroup.getChildren().add(tagsGroup);
+ hideTagsMenuItem.setText(DisplayOptions.HIDE_TAGS.getName());
+ pcs.firePropertyChange(new PropertyChangeEvent(this,
+ "state", null, State.VISIBLE));
+ }
+ });
+ }
+
+ @NbBundle.Messages({
+ "MediaViewImagePanel.exportSaveText=Save",
+ "MediaViewImagePanel.successfulExport=Tagged image was successfully saved.",
+ "MediaViewImagePanel.unsuccessfulExport=Unable to export tagged image to disk.",
+ "MediaViewImagePanel.fileChooserTitle=Choose a save location"
+ })
+ private void exportTags() {
+ tagsGroup.clearFocus();
+ exportChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ //Always base chooser location to export folder
+ exportChooser.setCurrentDirectory(new File(Case.getCurrentCase().getExportDirectory()));
+ int returnVal = exportChooser.showDialog(this, Bundle.MediaViewImagePanel_exportSaveText());
+ if (returnVal == JFileChooser.APPROVE_OPTION) {
+ new SwingWorker() {
+ @Override
+ protected Void doInBackground() {
+ try {
+ //Retrieve content viewer tags
+ List tags = Case.getCurrentCase().getServices()
+ .getTagsManager().getContentTagsByContent(file);
+ List> contentViewerTags = getContentViewerTags(tags);
+
+ //Pull out image tag regions
+ Collection regions = contentViewerTags.stream()
+ .map(cvTag -> cvTag.getDetails()).collect(Collectors.toList());
+
+ //Apply tags to image and write to file
+ BufferedImage pngImage = ImageTagsUtility.writeTags(file, regions, "png");
+ Path output = Paths.get(exportChooser.getSelectedFile().getPath(),
+ FilenameUtils.getBaseName(file.getName()) + "-with_tags.png"); //NON-NLS
+ ImageIO.write(pngImage, "png", output.toFile());
+
+ JOptionPane.showMessageDialog(null, Bundle.MediaViewImagePanel_successfulExport());
+ } catch (TskCoreException | NoCurrentCaseException | IOException ex) {
+ LOGGER.log(Level.WARNING, "Unable to export tagged image to disk", ex); //NON-NLS
+ JOptionPane.showMessageDialog(null, Bundle.MediaViewImagePanel_unsuccessfulExport());
+ }
+ return null;
+ }
+ }.execute();
+ }
+ }
+
+ private void tagsMenuMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_tagsMenuMousePressed
+ popupMenu.show(tagsMenu, -300 + tagsMenu.getWidth(), tagsMenu.getHeight() + 3);
+ }//GEN-LAST:event_tagsMenuMousePressed
+
+ /**
+ * Display states for the show/hide tags button.
+ */
+ enum DisplayOptions {
+ HIDE_TAGS("Hide"),
+ SHOW_TAGS("Show");
+
+ private final String name;
+
+ DisplayOptions(String name) {
+ this.name = name;
+ }
+
+ String getName() {
+ return name;
+ }
+ }
+
+ /**
+ * Different states that the content viewer can be in. These states drive
+ * which buttons are enabled for tagging.
+ */
+ enum State {
+ HIDDEN,
+ VISIBLE,
+ SELECTED,
+ CREATE,
+ EMPTY,
+ NONEMPTY,
+ DEFAULT,
+ DISABLE;
+ }
+
// Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.Box.Filler filler1;
+ private javax.swing.Box.Filler filler2;
+ private javax.swing.JPanel jPanel1;
private javax.swing.JToolBar.Separator jSeparator1;
private javax.swing.JToolBar.Separator jSeparator2;
private javax.swing.JButton rotateLeftButton;
private javax.swing.JButton rotateRightButton;
private javax.swing.JTextField rotationTextField;
+ private javax.swing.JButton tagsMenu;
private javax.swing.JToolBar toolbar;
private javax.swing.JButton zoomInButton;
private javax.swing.JButton zoomOutButton;
@@ -601,8 +1084,8 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
// Add the transforms in reverse order of intended execution.
// Note: They MUST be added in this order to ensure translate is
// executed last.
- imageGroup.getTransforms().clear();
- imageGroup.getTransforms().addAll(translate, rotate, scale);
+ masterGroup.getTransforms().clear();
+ masterGroup.getTransforms().addAll(translate, rotate, scale);
// Adjust scroll bar positions for view changes.
if (viewportWidth > fxPanel.getWidth()) {
@@ -618,107 +1101,4 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
rotationTextField.setText((int) rotation + "°");
zoomTextField.setText((Math.round(zoomRatio * 100.0)) + "%");
}
-
- /**
- * Enables users to 'tag' a region of an image by clicking and dragging a
- * rectangle overtop.
- */
- class ImageTaggingTool extends Rectangle {
-
- private final double imageWidth;
- private final double imageHeight;
- private final double imageOriginX;
- private final double imageOriginY;
-
- //Origin of the drag event.
- private double rectangleOriginX;
- private double rectangleOriginY;
-
- //Rectangle lines should be 1.5% of the image. This level of thickness has
- //a good balance between visual acuity and loss of selection at the borders
- //of the image.
- private double lineThicknessAsPercent = 1.5;
-
- /**
- * Adds tagging support to an image, where the 'tag' rectangle will be
- * the specified color.
- *
- * @param image Image to tag
- * @param color Color of the 'tag' rectangle
- */
- private ImageTaggingTool(ImageView image, Color color) {
- defaultSettings();
-
- imageWidth = image.getImage().getWidth();
- imageHeight = image.getImage().getHeight();
- imageOriginX = image.getX();
- imageOriginY = image.getY();
-
- setStroke(color);
- setFill(color.deriveColor(0, 0, 0, 0));
-
- //Calculate how many pixels the stroke width should be to guarentee
- //a consistent % of image consumed by the rectangle border.
- double min = Math.min(imageWidth, imageHeight);
- double lineThicknessPixels = min * lineThicknessAsPercent / 100.0;
- setStrokeWidth(lineThicknessPixels);
- setVisible(false);
-
- //Create a rectangle by left clicking on the image
- image.setOnMousePressed((MouseEvent event) -> {
- if (event.isSecondaryButtonDown()) {
- return;
- }
-
- //Reset box on new click.
- defaultSettings();
-
- rectangleOriginX = event.getX();
- rectangleOriginY = event.getY();
-
- setX(rectangleOriginX);
- setY(rectangleOriginY);
- });
-
- //Adjust the rectangle by dragging the left mouse button
- image.setOnMouseDragged((MouseEvent event) -> {
- if (event.isSecondaryButtonDown()) {
- return;
- }
-
- /**
- * Ensure the rectangle is contained within image boundaries and
- * that the line thickness is kept within bounds.
- */
- double newX = Math.min(Math.max(event.getX(), imageOriginX)
- + lineThicknessPixels / 2, imageWidth - lineThicknessPixels / 2);
- double newY = Math.min(Math.max(event.getY(), imageOriginY)
- + lineThicknessPixels / 2, imageHeight - lineThicknessPixels / 2);
-
- setVisible(true);
- double offsetX = newX - rectangleOriginX;
- if (offsetX < 0) {
- setX(newX);
- }
- setWidth(Math.abs(offsetX));
-
- double offsetY = newY - rectangleOriginY;
- if (offsetY < 0) {
- setY(newY);
- }
- setHeight(Math.abs(offsetY));
- });
- }
-
- /**
- * Reset the rectangle to default dimensions.
- */
- public final void defaultSettings() {
- setX(0);
- setY(0);
- setWidth(0);
- setHeight(0);
- setVisible(false);
- }
- }
}
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.form
index 1172483699..cf38160cd6 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.form
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.form
@@ -271,22 +271,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java
index 48f7e4e82e..0f36c84b9e 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java
@@ -87,7 +87,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
private static final int ATTM_TAB_INDEX = 4;
private final List textAreas;
-
+ private final org.sleuthkit.autopsy.contentviewers.HtmlPanel htmlPanel = new org.sleuthkit.autopsy.contentviewers.HtmlPanel();
/**
* Artifact currently being displayed
*/
@@ -101,6 +101,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
@NbBundle.Messages("MessageContentViewer.AtrachmentsPanel.title=Attachments")
public MessageContentViewer() {
initComponents();
+ htmlPane.add(htmlPanel);
envelopePanel.setBackground(new Color(0, 0, 0, 38));
drp = DataResultPanel.createInstanceUninitialized(Bundle.MessageContentViewer_AtrachmentsPanel_title(), "", new TableFilterNode(Node.EMPTY, false), 0, null);
attachmentsScrollPane.setViewportView(drp);
@@ -153,7 +154,6 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
textbodyScrollPane = new javax.swing.JScrollPane();
textbodyTextArea = new javax.swing.JTextArea();
htmlPane = new javax.swing.JPanel();
- htmlPanel = new org.sleuthkit.autopsy.contentviewers.HtmlPanel();
rtfbodyScrollPane = new javax.swing.JScrollPane();
rtfbodyTextPane = new javax.swing.JTextPane();
attachmentsPanel = new javax.swing.JPanel();
@@ -266,17 +266,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
msgbodyTabbedPane.addTab(org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.textbodyScrollPane.TabConstraints.tabTitle"), textbodyScrollPane); // NOI18N
- javax.swing.GroupLayout htmlPaneLayout = new javax.swing.GroupLayout(htmlPane);
- htmlPane.setLayout(htmlPaneLayout);
- htmlPaneLayout.setHorizontalGroup(
- htmlPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(htmlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 647, Short.MAX_VALUE)
- );
- htmlPaneLayout.setVerticalGroup(
- htmlPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(htmlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 362, Short.MAX_VALUE)
- );
-
+ htmlPane.setLayout(new java.awt.BorderLayout());
msgbodyTabbedPane.addTab(org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.htmlPane.TabConstraints.tabTitle"), htmlPane); // NOI18N
rtfbodyScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
@@ -357,7 +347,6 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
private javax.swing.JScrollPane headersScrollPane;
private javax.swing.JTextArea headersTextArea;
private javax.swing.JPanel htmlPane;
- private org.sleuthkit.autopsy.contentviewers.HtmlPanel htmlPanel;
private javax.swing.JTabbedPane msgbodyTabbedPane;
private javax.swing.JScrollPane rtfbodyScrollPane;
private javax.swing.JTextPane rtfbodyTextPane;
@@ -534,7 +523,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
textComponent.setCaretPosition(0); //make sure we start at the top
}
}
-
+
final boolean hasText = attributeText.length() > 0;
msgbodyTabbedPane.setEnabledAt(index, hasText);
@@ -680,18 +669,18 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
protected Sheet createSheet() {
Sheet sheet = super.createSheet();
Set keepProps = new HashSet<>(Arrays.asList(
- NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.nameColLbl"),
- NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.createSheet.score.name"),
- NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.createSheet.comment.name"),
- NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.createSheet.count.name"),
- NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.sizeColLbl"),
- NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.mimeType"),
- NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.knownColLbl")));
-
+ NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.nameColLbl"),
+ NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.createSheet.score.name"),
+ NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.createSheet.comment.name"),
+ NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.createSheet.count.name"),
+ NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.sizeColLbl"),
+ NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.mimeType"),
+ NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.knownColLbl")));
+
//Remove all other props except for the ones above
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
- for(Property> p : sheetSet.getProperties()) {
- if(!keepProps.contains(p.getName())){
+ for (Property> p : sheetSet.getProperties()) {
+ if (!keepProps.contains(p.getName())) {
sheetSet.remove(p.getName());
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTag.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTag.java
new file mode 100755
index 0000000000..10708f9d1a
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTag.java
@@ -0,0 +1,343 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2019 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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.contentviewers.imagetagging;
+
+import com.sun.javafx.event.EventDispatchChainImpl;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import javafx.collections.ListChangeListener;
+import javafx.scene.Cursor;
+import javafx.scene.Group;
+import javafx.scene.Node;
+import javafx.scene.control.Tooltip;
+import javafx.scene.image.ImageView;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Circle;
+import javafx.scene.shape.Rectangle;
+import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager.ContentViewerTag;
+
+/**
+ * A tagged region displayed over an image. This class contains a "physical tag"
+ * and 8 edit "handles". The physical tag is a plain old rectangle that defines
+ * the tag boundaries. The edit handles serve two purposes. One is to represent
+ * selection. All 8 edit handles will become visible overtop the physical tag
+ * when the user clicks on the rectangle. The other purpose is to allow the user to edit
+ * and manipulate the physical tag boundaries (hence the name, edit handle).
+ * This class should be treated as a logical image tag.
+ */
+public final class ImageTag extends Group {
+
+ // Used to tell the 8 edit handles to hide if this tag is no longer selected
+ private final EventDispatchChainImpl ALL_CHILDREN;
+
+ //Notifies listeners that the user has editted the tag boundaries
+ private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
+
+ //The underlying presistent tag details that this image tag originates from
+ private final ContentViewerTag appTag;
+
+ public ImageTag(ContentViewerTag contentViewerTag, ImageView image) {
+ ALL_CHILDREN = new EventDispatchChainImpl();
+ this.appTag = contentViewerTag;
+
+ this.getChildren().addListener((ListChangeListener) change -> {
+ change.next();
+ change.getAddedSubList().forEach((node) -> ALL_CHILDREN.append(node.getEventDispatcher()));
+ });
+
+ ImageTagRegion details = contentViewerTag.getDetails();
+ PhysicalTag physicalTag = new PhysicalTag(details);
+
+ //Defines the max allowable boundary that a user may drag any given handle.
+ Boundary dragBoundary = (x, y) -> {
+ double boundingX = image.getX();
+ double boundingY = image.getY();
+ double width = image.getImage().getWidth();
+ double height = image.getImage().getHeight();
+
+ return x > boundingX + details.getStrokeThickness() / 2
+ && x < boundingX + width - details.getStrokeThickness() / 2
+ && y > boundingY + details.getStrokeThickness() / 2
+ && y < boundingY + height - details.getStrokeThickness() / 2;
+ };
+
+ EditHandle bottomLeft = new EditHandle(physicalTag)
+ .setPosition(Position.bottom(), Position.left())
+ .setDrag(dragBoundary, Draggable.bottom(), Draggable.left());
+
+ EditHandle bottomRight = new EditHandle(physicalTag)
+ .setPosition(Position.bottom(), Position.right())
+ .setDrag(dragBoundary, Draggable.bottom(), Draggable.right());
+
+ EditHandle topLeft = new EditHandle(physicalTag)
+ .setPosition(Position.top(), Position.left())
+ .setDrag(dragBoundary, Draggable.top(), Draggable.left());
+
+ EditHandle topRight = new EditHandle(physicalTag)
+ .setPosition(Position.top(), Position.right())
+ .setDrag(dragBoundary, Draggable.top(), Draggable.right());
+
+ EditHandle bottomMiddle = new EditHandle(physicalTag)
+ .setPosition(Position.bottom(), Position.xMiddle())
+ .setDrag(dragBoundary, Draggable.bottom());
+
+ EditHandle topMiddle = new EditHandle(physicalTag)
+ .setPosition(Position.top(), Position.xMiddle())
+ .setDrag(dragBoundary, Draggable.top());
+
+ EditHandle rightMiddle = new EditHandle(physicalTag)
+ .setPosition(Position.right(), Position.yMiddle())
+ .setDrag(dragBoundary, Draggable.right());
+
+ EditHandle leftMiddle = new EditHandle(physicalTag)
+ .setPosition(Position.left(), Position.yMiddle())
+ .setDrag(dragBoundary, Draggable.left());
+
+ //The "logical" tag is the Group
+ this.getChildren().addAll(physicalTag, bottomLeft, bottomRight, topLeft,
+ topRight, bottomMiddle, topMiddle, rightMiddle, leftMiddle);
+
+ Tooltip.install(this, new Tooltip(contentViewerTag.getContentTag()
+ .getName().getDisplayName()));
+
+ this.addEventHandler(ImageTagControls.NOT_FOCUSED, event -> ALL_CHILDREN.dispatchEvent(event));
+ this.addEventHandler(ImageTagControls.FOCUSED, event -> ALL_CHILDREN.dispatchEvent(event));
+ }
+
+ /**
+ * Add a new listener for edit events. These events are generated when a
+ * user drags on one of the edit "knobs" of the tag.
+ *
+ * @param listener
+ */
+ public void subscribeToEditEvents(PropertyChangeListener listener) {
+ pcs.addPropertyChangeListener(listener);
+ }
+
+ /**
+ * Get the content viewer tag that this class represents.
+ *
+ * @return
+ */
+ public ContentViewerTag getContentViewerTag() {
+ return appTag;
+ }
+
+ /**
+ * Plain old rectangle that represents an unselected Image Tag
+ */
+ class PhysicalTag extends Rectangle {
+
+ public PhysicalTag(ImageTagRegion details) {
+ this.setStroke(Color.RED);
+ this.setFill(Color.RED.deriveColor(0, 0, 0, 0));
+ this.setStrokeWidth(details.getStrokeThickness());
+
+ setX(details.getX());
+ setY(details.getY());
+ setWidth(details.getWidth());
+ setHeight(details.getHeight());
+
+ this.addEventHandler(ImageTagControls.NOT_FOCUSED, event -> this.setOpacity(1));
+ this.addEventHandler(ImageTagControls.FOCUSED, event -> this.setOpacity(0.5));
+ }
+
+ /**
+ * Builds a portable description of the tag region.
+ *
+ * @return
+ */
+ public ImageTagRegion getState() {
+ return new ImageTagRegion()
+ .setX(this.getX())
+ .setY(this.getY())
+ .setWidth(this.getWidth())
+ .setHeight(this.getHeight())
+ .setStrokeThickness(this.getStrokeWidth());
+ }
+ }
+
+ /**
+ * Draggable "knob" used to manipulate the physical tag boundaries.
+ */
+ class EditHandle extends Circle {
+
+ private final PhysicalTag parent;
+
+ public EditHandle(PhysicalTag parent) {
+ this.setVisible(false);
+
+ //Hide when the tag is not selected.
+ this.addEventHandler(ImageTagControls.NOT_FOCUSED, event -> this.setVisible(false));
+ this.addEventHandler(ImageTagControls.FOCUSED, event -> this.setVisible(true));
+
+ this.setRadius(parent.getStrokeWidth());
+ this.setFill(parent.getStroke());
+
+ this.setOnDragDetected(event -> {
+ this.getParent().setCursor(Cursor.CLOSED_HAND);
+ });
+
+ this.setOnMouseReleased(event -> {
+ this.getParent().setCursor(Cursor.DEFAULT);
+ pcs.firePropertyChange(new PropertyChangeEvent(this, "Tag Edit", null, parent.getState()));
+ });
+
+ this.parent = parent;
+ }
+
+ /**
+ * Sets the positioning of this edit handle on the physical tag.
+ *
+ * @param vals
+ * @return
+ */
+ public EditHandle setPosition(Position... vals) {
+ for (Position pos : vals) {
+ parent.widthProperty().addListener((obs, oldVal, newVal) -> pos.set(parent, this));
+ parent.heightProperty().addListener((obs, oldVal, newVal) -> pos.set(parent, this));
+ pos.set(parent, this);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the drag capabilities for manipulating the physical tag.
+ *
+ * @param bounds
+ * @param vals
+ * @return
+ */
+ public EditHandle setDrag(Boundary bounds, Draggable... vals) {
+ this.setOnMouseDragged((event) -> {
+ for (Draggable drag : vals) {
+ drag.perform(parent, event, bounds);
+ }
+ });
+ return this;
+ }
+ }
+
+ /**
+ * Position strategies for "sticking" to a location on the physical tag when
+ * it is resized.
+ */
+ static interface Position {
+
+ void set(PhysicalTag parent, Circle knob);
+
+ static Position left() {
+ return (parent, knob) -> knob.centerXProperty().bind(parent.xProperty());
+ }
+
+ static Position right() {
+ return (parent, knob) -> knob.centerXProperty().bind(parent.xProperty().add(parent.getWidth()));
+ }
+
+ static Position top() {
+ return (parent, knob) -> knob.centerYProperty().bind(parent.yProperty());
+ }
+
+ static Position bottom() {
+ return (parent, knob) -> knob.centerYProperty().bind(parent.yProperty().add(parent.getHeight()));
+ }
+
+ static Position xMiddle() {
+ return (parent, knob) -> knob.centerXProperty().bind(parent.xProperty().add(parent.getWidth() / 2));
+ }
+
+ static Position yMiddle() {
+ return (parent, knob) -> knob.centerYProperty().bind(parent.yProperty().add(parent.getHeight() / 2));
+ }
+ }
+
+ /**
+ * Defines the bounding box for which dragging is allowable.
+ */
+ @FunctionalInterface
+ static interface Boundary {
+
+ boolean isPointInBounds(double x, double y);
+ }
+
+ /**
+ * Drag strategies for manipulating the physical tag from a given side of
+ * the rectangle.
+ */
+ static interface Draggable {
+
+ void perform(PhysicalTag parent, MouseEvent event, Boundary b);
+
+ static Draggable bottom() {
+ return (parent, event, bounds) -> {
+ if (!bounds.isPointInBounds(event.getX(), event.getY())) {
+ return;
+ }
+
+ double deltaY = event.getY() - parent.getY();
+ if (deltaY > 0) {
+ parent.setHeight(deltaY);
+ }
+ };
+ }
+
+ static Draggable top() {
+ return (parent, event, bounds) -> {
+ if (!bounds.isPointInBounds(event.getX(), event.getY())) {
+ return;
+ }
+
+ double deltaY = parent.getY() + parent.getHeight() - event.getY();
+ if (deltaY < parent.getY() + parent.getHeight() && deltaY > 0) {
+ parent.setHeight(deltaY);
+ parent.setY(event.getY());
+ }
+ };
+ }
+
+ static Draggable left() {
+ return (parent, event, bounds) -> {
+ if (!bounds.isPointInBounds(event.getX(), event.getY())) {
+ return;
+ }
+
+ double deltaX = parent.getX() + parent.getWidth() - event.getX();
+ if (deltaX < parent.getX() + parent.getWidth() && deltaX > 0) {
+ parent.setWidth(deltaX);
+ parent.setX(event.getX());
+ }
+ };
+ }
+
+ static Draggable right() {
+ return (parent, event, bounds) -> {
+ if (!bounds.isPointInBounds(event.getX(), event.getY())) {
+ return;
+ }
+
+ double deltaX = event.getX() - parent.getX();
+ if (deltaX > 0) {
+ parent.setWidth(deltaX);
+ }
+ };
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagControls.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagControls.java
new file mode 100755
index 0000000000..d4ee799262
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagControls.java
@@ -0,0 +1,31 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2019 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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.contentviewers.imagetagging;
+
+import javafx.event.Event;
+import javafx.event.EventType;
+
+/**
+ * Focus events for ImageTags to consume. These events trigger selection behavior
+ * on ImageTags and are originated from the ImageTagsGroup class.
+ */
+public class ImageTagControls {
+ public static final EventType NOT_FOCUSED = new EventType<>("NOT_FOCUSED");
+ public static final EventType FOCUSED = new EventType<>("FOCUSED");
+}
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagCreator.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagCreator.java
new file mode 100755
index 0000000000..24421d796c
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagCreator.java
@@ -0,0 +1,180 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2019 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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.contentviewers.imagetagging;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import javafx.event.EventHandler;
+import javafx.scene.image.ImageView;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Rectangle;
+
+/**
+ * Creates image tags. This class attaches itself to a source image, waiting
+ * for mouse press, mouse drag, and mouse release events. Upon a mouse release
+ * event, any listeners are updated with the portable description of the new tag
+ * boundaries (ImageTagRegion).
+ */
+public final class ImageTagCreator extends Rectangle {
+
+ //Origin of the drag event.
+ private double rectangleOriginX, rectangleOriginY;
+
+ //Rectangle lines should be 1.5% of the image. This level of thickness has
+ //a good balance between visual acuity and loss of selection at the borders
+ //of the image.
+ private final static double LINE_THICKNESS_PERCENT = 1.5;
+ private final double minArea;
+
+ //Used to update listeners of the new tag boundaries
+ private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
+
+ private final EventHandler mousePressed;
+ private final EventHandler mouseDragged;
+ private final EventHandler mouseReleased;
+
+ //Handles the unregistering this ImageTagCreator from mouse press, mouse drag,
+ //and mouse release events of the source image.
+ private final Runnable disconnectRunnable;
+
+ /**
+ * Adds tagging support to an image, where the 'tag' rectangle will be the
+ * specified color.
+ *
+ * @param image Image to tag
+ */
+ public ImageTagCreator(ImageView image) {
+ setStroke(Color.RED);
+ setFill(Color.RED.deriveColor(0, 0, 0, 0));
+
+ //Calculate how many pixels the stroke width should be to guarentee
+ //a consistent % of image consumed by the rectangle border.
+ double min = Math.min(image.getImage().getWidth(), image.getImage().getHeight());
+ double lineThicknessPixels = min * LINE_THICKNESS_PERCENT / 100.0;
+ setStrokeWidth(lineThicknessPixels);
+ minArea = lineThicknessPixels * lineThicknessPixels;
+ setVisible(false);
+
+ this.mousePressed = (MouseEvent event) -> {
+ if (event.isSecondaryButtonDown()) {
+ return;
+ }
+
+ //Reset box on new click.
+ defaultSettings();
+ rectangleOriginX = event.getX();
+ rectangleOriginY = event.getY();
+
+ setX(rectangleOriginX);
+ setY(rectangleOriginY);
+ };
+
+ image.addEventHandler(MouseEvent.MOUSE_PRESSED, this.mousePressed);
+
+ this.mouseDragged = (MouseEvent event) -> {
+ if (event.isSecondaryButtonDown()) {
+ return;
+ }
+
+ double currentX = event.getX(), currentY = event.getY();
+
+ /**
+ * Ensure the rectangle is contained within image boundaries and
+ * that the line thickness is kept within bounds.
+ */
+ double newX = Math.min(Math.max(currentX, image.getX())
+ + lineThicknessPixels / 2, image.getImage().getWidth() - lineThicknessPixels / 2);
+ double newY = Math.min(Math.max(currentY, image.getY())
+ + lineThicknessPixels / 2, image.getImage().getHeight() - lineThicknessPixels / 2);
+
+ setVisible(true);
+ double offsetX = newX - rectangleOriginX;
+ if (offsetX < 0) {
+ setX(newX);
+ }
+ setWidth(Math.abs(offsetX));
+
+ double offsetY = newY - rectangleOriginY;
+ if (offsetY < 0) {
+ setY(newY);
+ }
+ setHeight(Math.abs(offsetY));
+ };
+
+ image.addEventHandler(MouseEvent.MOUSE_DRAGGED, this.mouseDragged);
+
+ this.mouseReleased = event -> {
+ //Reject any drags that are too small to count as a meaningful tag.
+ //Meaningful is described as having an area that is visible that is
+ //not consumed by the thickness of the stroke.
+ if ((this.getWidth() - this.getStrokeWidth())
+ * (this.getHeight() - this.getStrokeWidth()) <= minArea) {
+ defaultSettings();
+ return;
+ }
+
+ this.pcs.firePropertyChange(new PropertyChangeEvent(this, "New Tag",
+ null, new ImageTagRegion()
+ .setX(this.getX())
+ .setY(this.getY())
+ .setWidth(this.getWidth())
+ .setHeight(this.getHeight())
+ .setStrokeThickness(lineThicknessPixels)));
+ };
+
+ image.addEventHandler(MouseEvent.MOUSE_RELEASED, this.mouseReleased);
+
+ //Used to remove itself from mouse events on the source image
+ disconnectRunnable = () -> {
+ defaultSettings();
+ image.removeEventHandler(MouseEvent.MOUSE_RELEASED, mouseReleased);
+ image.removeEventHandler(MouseEvent.MOUSE_DRAGGED, mouseDragged);
+ image.removeEventHandler(MouseEvent.MOUSE_PRESSED, mousePressed);
+ };
+ }
+
+ /**
+ * Registers a PCL for new tag events. Listeners are updated with a portable
+ * description (ImageTagRegion) of the new tag, which represent the
+ * rectangle boundaries.
+ *
+ * @param listener
+ */
+ public void addNewTagListener(PropertyChangeListener listener) {
+ this.pcs.addPropertyChangeListener(listener);
+ }
+
+ /**
+ * Removes itself from mouse events on the source image.
+ */
+ public void disconnect() {
+ this.disconnectRunnable.run();
+ }
+
+ /**
+ * Reset the rectangle to default dimensions.
+ */
+ private void defaultSettings() {
+ setWidth(0);
+ setHeight(0);
+ setVisible(false);
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagRegion.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagRegion.java
new file mode 100755
index 0000000000..b4ba2035b0
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagRegion.java
@@ -0,0 +1,82 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2019 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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.contentviewers.imagetagging;
+
+/**
+ * Bean representation of an image tag. This class is used for storage and
+ * retrieval of ImageTags from the case database.
+ */
+public class ImageTagRegion {
+
+ /**
+ * These fields will be serialized and stored in the case database by the
+ * ContentViewerTagManager.
+ */
+ private double x;
+ private double y;
+ private double width;
+ private double height;
+
+ private double strokeThickness;
+
+ public ImageTagRegion setStrokeThickness(double thickness) {
+ this.strokeThickness = thickness;
+ return this;
+ }
+
+ public ImageTagRegion setX(double x) {
+ this.x = x;
+ return this;
+ }
+
+ public ImageTagRegion setWidth(double width) {
+ this.width = width;
+ return this;
+ }
+
+ public ImageTagRegion setY(double y) {
+ this.y = y;
+ return this;
+ }
+
+ public ImageTagRegion setHeight(double height) {
+ this.height = height;
+ return this;
+ }
+
+ public double getX() {
+ return x;
+ }
+
+ public double getWidth() {
+ return width;
+ }
+
+ public double getY() {
+ return y;
+ }
+
+ public double getHeight() {
+ return height;
+ }
+
+ public double getStrokeThickness() {
+ return strokeThickness;
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsGroup.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsGroup.java
new file mode 100755
index 0000000000..a9f23304a3
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsGroup.java
@@ -0,0 +1,134 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2019 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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.contentviewers.imagetagging;
+
+import com.sun.javafx.event.EventDispatchChainImpl;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import javafx.event.Event;
+import javafx.scene.Group;
+import javafx.scene.Node;
+import javafx.scene.input.MouseEvent;
+
+/**
+ * Manages the focus and z-ordering of ImageTags. Only one image tag may be
+ * selected at a time. Image tags show their 8 edit "handles" upon selection
+ * (see ImageTag class for more details) and get the highest z-ordering to make
+ * editing easier. This class is responsible for setting and dropping focus as
+ * the user navigates from tag to tag. The ImageTag is treated as a logical
+ * unit, however it's underlying representation consists of the physical
+ * rectangle and the 8 edit handles. JavaFX will report selection on the Node
+ * level (so either the Rectangle, or a singe edit handle), which makes keeping
+ * the entire image tag in focus a non-trivial problem.
+ */
+public final class ImageTagsGroup extends Group {
+
+ private final EventDispatchChainImpl NO_OP_CHAIN = new EventDispatchChainImpl();
+ private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
+
+ private volatile ImageTag currentFocus;
+
+ public ImageTagsGroup(Node backDrop) {
+
+ //Reset focus of current selection if the back drop has focus.
+ backDrop.setOnMousePressed((mouseEvent) -> {
+ if (currentFocus != null) {
+ currentFocus.getEventDispatcher().dispatchEvent(
+ new Event(ImageTagControls.NOT_FOCUSED), NO_OP_CHAIN);
+ }
+
+ this.pcs.firePropertyChange(new PropertyChangeEvent(this,
+ ImageTagControls.NOT_FOCUSED.getName(), currentFocus, null));
+ currentFocus = null;
+ });
+
+ //Set the focus of selected tag
+ this.addEventFilter(MouseEvent.MOUSE_PRESSED, (MouseEvent e) -> {
+ if (!e.isPrimaryButtonDown()) {
+ return;
+ }
+
+ //Pull out the logical image tag that this node is associated with
+ Node topLevelChild = e.getPickResult().getIntersectedNode();
+ while (!this.getChildren().contains(topLevelChild)) {
+ topLevelChild = topLevelChild.getParent();
+ }
+
+ requestFocus((ImageTag) topLevelChild);
+ });
+ }
+
+ /**
+ * Subscribe to focus change events on Image tags.
+ *
+ * @param fcl PCL to be notified which Image tag has been selected.
+ */
+ public void addFocusChangeListener(PropertyChangeListener fcl) {
+ this.pcs.addPropertyChangeListener(fcl);
+ }
+
+ /**
+ * Get the image tag that current has focus.
+ *
+ * @return ImageTag instance or null if no tag is in focus.
+ */
+ public ImageTag getFocus() {
+ return currentFocus;
+ }
+
+ /**
+ * Clears the current focus
+ */
+ public void clearFocus() {
+ if(currentFocus != null) {
+ resetFocus(currentFocus);
+ currentFocus = null;
+ }
+ }
+
+ /**
+ * Notifies the logical image tag that it is no longer in focus.
+ *
+ * @param n
+ */
+ private void resetFocus(ImageTag n) {
+ n.getEventDispatcher().dispatchEvent(new Event(ImageTagControls.NOT_FOCUSED), NO_OP_CHAIN);
+ this.pcs.firePropertyChange(new PropertyChangeEvent(this, ImageTagControls.NOT_FOCUSED.getName(), n, null));
+ }
+
+ /**
+ * Notifies the logical image that it is in focus.
+ *
+ * @param n
+ */
+ private void requestFocus(ImageTag n) {
+ if (n.equals(currentFocus)) {
+ return;
+ } else if (currentFocus != null && !currentFocus.equals(n)) {
+ resetFocus(currentFocus);
+ }
+
+ n.getEventDispatcher().dispatchEvent(new Event(ImageTagControls.FOCUSED), NO_OP_CHAIN);
+ this.pcs.firePropertyChange(new PropertyChangeEvent(this, ImageTagControls.FOCUSED.getName(), currentFocus, n));
+
+ currentFocus = n;
+ n.toFront();
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtility.java b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtility.java
new file mode 100755
index 0000000000..3c5fccc5e6
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/imagetagging/ImageTagsUtility.java
@@ -0,0 +1,141 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2019 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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.contentviewers.imagetagging;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Collection;
+import javax.imageio.ImageIO;
+import org.opencv.core.Core;
+import org.opencv.core.Mat;
+import org.opencv.core.MatOfByte;
+import org.opencv.core.MatOfInt;
+import org.opencv.core.Point;
+import org.opencv.core.Scalar;
+import org.opencv.core.Size;
+import org.opencv.highgui.Highgui;
+import org.opencv.imgproc.Imgproc;
+import org.sleuthkit.datamodel.AbstractFile;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ * Utility class for handling content viewer tags on images.
+ */
+public final class ImageTagsUtility {
+
+ /**
+ * Sizes for thumbnails
+ */
+ public enum IconSize {
+ SMALL(50),
+ MEDIUM(100),
+ LARGE(200);
+
+ private final int SIZE;
+
+ IconSize(int size) {
+ this.SIZE = size;
+ }
+
+ public int getSize() {
+ return SIZE;
+ }
+ }
+
+ /**
+ * Embeds the tag regions into an image.
+ *
+ * @param file Base Image
+ * @param tagRegions Tag regions to be saved into the image
+ * @param outputEncoding Format of image (jpg, png, etc). See OpenCV for
+ * supported formats. Do not include a "."
+ * @return Output image as a BufferedImage
+ *
+ * @throws TskCoreException Cannot read from abstract file
+ * @throws IOException Could not create buffered image from OpenCV result
+ */
+ public static BufferedImage writeTags(AbstractFile file, Collection tagRegions,
+ String outputEncoding) throws TskCoreException, IOException {
+ byte[] imageInMemory = new byte[(int) file.getSize()];
+ file.read(imageInMemory, 0, file.getSize());
+ Mat originalImage = Highgui.imdecode(new MatOfByte(imageInMemory), Highgui.IMREAD_UNCHANGED);
+
+ tagRegions.forEach((region) -> {
+ Core.rectangle(
+ originalImage, //Matrix obj of the image
+ new Point(region.getX(), region.getY()), //p1
+ new Point(region.getX() + region.getWidth(), region.getY() + region.getHeight()), //p2
+ new Scalar(0, 0, 255), //Scalar object for color
+ (int) Math.rint(region.getStrokeThickness())
+ );
+ });
+
+ MatOfByte matOfByte = new MatOfByte();
+ MatOfInt params = new MatOfInt(Highgui.IMWRITE_JPEG_QUALITY, 100);
+ Highgui.imencode("." + outputEncoding, originalImage, matOfByte, params);
+
+ try (ByteArrayInputStream imageStream = new ByteArrayInputStream(matOfByte.toArray())) {
+ BufferedImage result = ImageIO.read(imageStream);
+ originalImage.release();
+ matOfByte.release();
+ return result;
+ }
+ }
+
+ /**
+ * Creates a thumbnail version of the image with tags applied.
+ *
+ * @param file Input file to apply tags & produce thumbnail from
+ * @param tagRegions Tags to apply
+ * @param iconSize Size of the output thumbnail
+ * @param outputEncoding Format of thumbnail (jpg, png, etc). See OpenCV for
+ * supported formats. Do not include a "."
+ * @return BufferedImage representing the thumbnail
+ *
+ * @throws TskCoreException Could not read from file
+ * @throws IOException Could not create buffered image from OpenCV result
+ */
+ public static BufferedImage makeThumbnail(AbstractFile file, Collection tagRegions,
+ IconSize iconSize, String outputEncoding) throws TskCoreException, IOException {
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ BufferedImage result = writeTags(file, tagRegions, outputEncoding);
+ ImageIO.write(result, outputEncoding, baos);
+ Mat markedUpImage = Highgui.imdecode(new MatOfByte(baos.toByteArray()), Highgui.IMREAD_UNCHANGED);
+ Mat thumbnail = new Mat();
+ Size resize = new Size(iconSize.getSize(), iconSize.getSize());
+
+ Imgproc.resize(markedUpImage, thumbnail, resize);
+ MatOfByte matOfByte = new MatOfByte();
+ Highgui.imencode("." + outputEncoding, thumbnail, matOfByte);
+
+ try (ByteArrayInputStream thumbnailStream = new ByteArrayInputStream(matOfByte.toArray())) {
+ BufferedImage thumbnailImage = ImageIO.read(thumbnailStream);
+ thumbnail.release();
+ matOfByte.release();
+ markedUpImage.release();
+ return thumbnailImage;
+ }
+ }
+ }
+
+ private ImageTagsUtility() {
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/textcontentviewer/TextContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/textcontentviewer/TextContentViewer.java
index 81384acbaa..d7f853caab 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/textcontentviewer/TextContentViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/textcontentviewer/TextContentViewer.java
@@ -23,6 +23,7 @@ import org.openide.nodes.Node;
import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
+import org.sleuthkit.datamodel.AbstractFile;
/**
* A DataContentViewer that displays text with the TextViewers available.
@@ -90,6 +91,17 @@ public class TextContentViewer implements DataContentViewer {
if (node == null) {
return false;
}
+ // get the node's File, if it has one
+ AbstractFile file = node.getLookup().lookup(AbstractFile.class);
+ if (file == null) {
+ return false;
+ }
+
+ // disable the text content viewer for directories and empty files
+ if (file.isDir() || file.getSize() == 0) {
+ return false;
+ }
+
return panel.isSupported(node);
}
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/textcontentviewer/TextContentViewerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/textcontentviewer/TextContentViewerPanel.java
index e1af060cfc..7c80d606fd 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/textcontentviewer/TextContentViewerPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/textcontentviewer/TextContentViewerPanel.java
@@ -79,7 +79,7 @@ public class TextContentViewerPanel extends javax.swing.JPanel implements DataCo
}
/**
- * Deterime wether the content viewer which displays this panel isSupported.
+ * Determine whether the content viewer which displays this panel isSupported.
* This panel is supported if any of the TextViewer's displayed in it are
* supported.
*
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
index 7eb261880f..fae74a30ac 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
@@ -217,3 +217,4 @@ DataResultViewerTable.pageNumLabel.text=
DataResultViewerTable.pageLabel.text=Page:
ViewPreferencesPanel.maxResultsLabel.text=Maximum number of Results to show in table:
ViewPreferencesPanel.maxResultsLabel.toolTipText=\nSetting this value to 0 will display all results in the results table.\n Note that setting this value to 0 may result in poor UI responsiveness when there are large numbers of results.\n
+DataResultViewerTable.exportCSVButton.text=Save table as CSV
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED
index cd75ae919e..b0e36da986 100755
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED
@@ -32,6 +32,7 @@ DataResultViewerTable.commentRenderer.noComment.toolTip=No comments found
DataResultViewerTable.commentRenderer.tagComment.toolTip=Comment exists on associated tag(s)
DataResultViewerTable.countRender.name=O
DataResultViewerTable.countRender.toolTip=O(ccurrences) indicates the number of data sources containing the item in the Central Repository
+DataResultViewerTable.exportCSVButtonActionPerformed.empty=No data to export
DataResultViewerTable.firstColLbl=Name
DataResultViewerTable.goToPageTextField.err=Invalid page number
# {0} - totalPages
@@ -270,3 +271,4 @@ DataResultViewerTable.pageNumLabel.text=
DataResultViewerTable.pageLabel.text=Page:
ViewPreferencesPanel.maxResultsLabel.text=Maximum number of Results to show in table:
ViewPreferencesPanel.maxResultsLabel.toolTipText=\nSetting this value to 0 will display all results in the results table.\n Note that setting this value to 0 may result in poor UI responsiveness when there are large numbers of results.\n
+DataResultViewerTable.exportCSVButton.text=Save table as CSV
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form
index 6b17cfd6dd..6aeda07298 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form
@@ -16,13 +16,13 @@
-
-
-
+
+
+
-
+
-
+
@@ -32,14 +32,15 @@
-
+
+
-
+
@@ -48,9 +49,10 @@
+
-
-
+
+
@@ -164,5 +166,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java
index a5bbc461b8..b192942e71 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java
@@ -77,6 +77,7 @@ import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo;
@@ -176,6 +177,13 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
initComponents();
initializePagingSupport();
+
+ /*
+ * Disable the CSV export button for the common properties results
+ */
+ if (this instanceof org.sleuthkit.autopsy.commonpropertiessearch.CommonAttributesSearchResultsViewerTable) {
+ exportCSVButton.setEnabled(false);
+ }
/*
* Configure the child OutlineView (explorer view) component.
@@ -293,20 +301,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
- /*
- * If the given node is not null and has children, set it as the
- * root context of the child OutlineView, otherwise make an
- * "empty"node the root context.
- *
- * IMPORTANT NOTE: This is the first of many times where a
- * getChildren call on the current root node causes all of the
- * children of the root node to be created and defeats lazy child
- * node creation, if it is enabled. It also likely leads to many
- * case database round trips.
- */
- if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) {
- this.rootNode = rootNode;
-
+ if (rootNode != null) {
/**
* Check to see if we have previously created a paging support
* class for this node.
@@ -355,6 +350,21 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
// No-op
}
});
+ }
+
+ /*
+ * If the given node is not null and has children, set it as the
+ * root context of the child OutlineView, otherwise make an
+ * "empty"node the root context.
+ *
+ * IMPORTANT NOTE: This is the first of many times where a
+ * getChildren call on the current root node causes all of the
+ * children of the root node to be created and defeats lazy child
+ * node creation, if it is enabled. It also likely leads to many
+ * case database round trips.
+ */
+ if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) {
+ this.rootNode = rootNode;
this.getExplorerManager().setRootContext(this.rootNode);
setupTable();
@@ -1291,6 +1301,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL);
gotoPageLabel = new javax.swing.JLabel();
gotoPageTextField = new javax.swing.JTextField();
+ exportCSVButton = new javax.swing.JButton();
pageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageLabel.text")); // NOI18N
@@ -1338,17 +1349,24 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
}
});
+ exportCSVButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.exportCSVButton.text")); // NOI18N
+ exportCSVButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ exportCSVButtonActionPerformed(evt);
+ }
+ });
+
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
- .addContainerGap(608, Short.MAX_VALUE)
+ .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 904, Short.MAX_VALUE)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
.addComponent(pageLabel)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(pageNumLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addGap(14, 14, 14)
.addComponent(pagesLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)
@@ -1358,7 +1376,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
.addComponent(gotoPageLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 33, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addContainerGap())
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(exportCSVButton))
);
layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {pageNextButton, pagePrevButton});
@@ -1366,7 +1385,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
- .addContainerGap()
+ .addGap(3, 3, 3)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
.addComponent(pageLabel)
.addComponent(pageNumLabel)
@@ -1374,9 +1393,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
.addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 15, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(gotoPageLabel)
- .addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 324, Short.MAX_VALUE)
+ .addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(exportCSVButton))
+ .addGap(3, 3, 3)
+ .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 321, Short.MAX_VALUE)
.addContainerGap())
);
@@ -1397,7 +1417,19 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
pagingSupport.gotoPage();
}//GEN-LAST:event_gotoPageTextFieldActionPerformed
+ @NbBundle.Messages({"DataResultViewerTable.exportCSVButtonActionPerformed.empty=No data to export"
+ })
+ private void exportCSVButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportCSVButtonActionPerformed
+ Node currentRoot = this.getExplorerManager().getRootContext();
+ if (currentRoot != null && currentRoot.getChildren().getNodesCount() > 0) {
+ org.sleuthkit.autopsy.directorytree.ExportCSVAction.saveNodesToCSV(java.util.Arrays.asList(currentRoot.getChildren().getNodes()), this);
+ } else {
+ MessageNotifyUtil.Message.info(Bundle.DataResultViewerTable_exportCSVButtonActionPerformed_empty());
+ }
+ }//GEN-LAST:event_exportCSVButtonActionPerformed
+
// Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JButton exportCSVButton;
private javax.swing.JLabel gotoPageLabel;
private javax.swing.JTextField gotoPageTextField;
private org.openide.explorer.view.OutlineView outlineView;
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.form
index a042fd7de6..798b0b51bd 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.form
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.form
@@ -11,217 +11,278 @@
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java
index 4f4e41d04e..3199dbd7e8 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java
@@ -122,6 +122,11 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer {
currentPage = -1;
totalPages = 0;
currentPageImages = 0;
+
+ // The GUI builder is using FlowLayout therefore this change so have no
+ // impact on the initally designed layout. This change will just effect
+ // how the components are laid out as size of the window changes.
+ buttonBarPanel.setLayout(new WrapLayout(java.awt.FlowLayout.LEFT));
}
/**
@@ -132,33 +137,84 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer {
@SuppressWarnings("unchecked")
// //GEN-BEGIN:initComponents
private void initComponents() {
+ java.awt.GridBagConstraints gridBagConstraints;
+ buttonBarPanel = new javax.swing.JPanel();
+ pagesPanel = new javax.swing.JPanel();
+ pageNumberPane = new javax.swing.JPanel();
pageLabel = new javax.swing.JLabel();
+ pageNumLabel = new javax.swing.JLabel();
+ pageButtonPanel = new javax.swing.JPanel();
pagesLabel = new javax.swing.JLabel();
pagePrevButton = new javax.swing.JButton();
pageNextButton = new javax.swing.JButton();
- imagesLabel = new javax.swing.JLabel();
- imagesRangeLabel = new javax.swing.JLabel();
- pageNumLabel = new javax.swing.JLabel();
- filePathLabel = new javax.swing.JLabel();
+ pageGotoPane = new javax.swing.JPanel();
goToPageLabel = new javax.swing.JLabel();
goToPageField = new javax.swing.JTextField();
+ imagePane = new javax.swing.JPanel();
+ imagesLabel = new javax.swing.JLabel();
+ imagesRangeLabel = new javax.swing.JLabel();
thumbnailSizeComboBox = new javax.swing.JComboBox<>();
- iconView = new org.openide.explorer.view.IconView();
- sortButton = new javax.swing.JButton();
+ sortPane = new javax.swing.JPanel();
sortLabel = new javax.swing.JLabel();
+ sortButton = new javax.swing.JButton();
+ filePathLabel = new javax.swing.JLabel();
+ iconView = new org.openide.explorer.view.IconView();
+
+ setLayout(new java.awt.BorderLayout());
+
+ buttonBarPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEFT));
+
+ pagesPanel.setLayout(new java.awt.GridBagLayout());
+
+ pageNumberPane.setLayout(new java.awt.GridBagLayout());
pageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.pageLabel.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
+ gridBagConstraints.weighty = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 9);
+ pageNumberPane.add(pageLabel, gridBagConstraints);
+
+ pageNumLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.pageNumLabel.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 15);
+ pageNumberPane.add(pageNumLabel, gridBagConstraints);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
+ pagesPanel.add(pageNumberPane, gridBagConstraints);
+
+ buttonBarPanel.add(pagesPanel);
+
+ pageButtonPanel.setLayout(new java.awt.GridBagLayout());
pagesLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.pagesLabel.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+ gridBagConstraints.weighty = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 9);
+ pageButtonPanel.add(pagesLabel, gridBagConstraints);
pagePrevButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"))); // NOI18N
pagePrevButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.pagePrevButton.text")); // NOI18N
+ pagePrevButton.setBorder(null);
pagePrevButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); // NOI18N
pagePrevButton.setFocusable(false);
pagePrevButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
- pagePrevButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
- pagePrevButton.setPreferredSize(new java.awt.Dimension(55, 23));
+ pagePrevButton.setMargin(new java.awt.Insets(0, 0, 0, 0));
pagePrevButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png"))); // NOI18N
pagePrevButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
pagePrevButton.addActionListener(new java.awt.event.ActionListener() {
@@ -166,13 +222,20 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer {
pagePrevButtonActionPerformed(evt);
}
});
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.gridheight = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
+ pageButtonPanel.add(pagePrevButton, gridBagConstraints);
pageNextButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"))); // NOI18N
pageNextButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.pageNextButton.text")); // NOI18N
+ pageNextButton.setBorder(null);
pageNextButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); // NOI18N
pageNextButton.setFocusable(false);
pageNextButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
- pageNextButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
+ pageNextButton.setMargin(new java.awt.Insets(0, 0, 0, 0));
pageNextButton.setMaximumSize(new java.awt.Dimension(27, 23));
pageNextButton.setMinimumSize(new java.awt.Dimension(27, 23));
pageNextButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_hover.png"))); // NOI18N
@@ -182,16 +245,27 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer {
pageNextButtonActionPerformed(evt);
}
});
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 2;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.gridheight = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
+ gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 15);
+ pageButtonPanel.add(pageNextButton, gridBagConstraints);
- imagesLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.imagesLabel.text")); // NOI18N
+ buttonBarPanel.add(pageButtonPanel);
- imagesRangeLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.imagesRangeLabel.text")); // NOI18N
-
- pageNumLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.pageNumLabel.text")); // NOI18N
-
- filePathLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.filePathLabel.text")); // NOI18N
+ pageGotoPane.setLayout(new java.awt.GridBagLayout());
goToPageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.goToPageLabel.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+ gridBagConstraints.weighty = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 9);
+ pageGotoPane.add(goToPageLabel, gridBagConstraints);
goToPageField.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.goToPageField.text")); // NOI18N
goToPageField.addActionListener(new java.awt.event.ActionListener() {
@@ -199,12 +273,49 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer {
goToPageFieldActionPerformed(evt);
}
});
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.gridheight = 2;
+ gridBagConstraints.ipadx = 75;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 15);
+ pageGotoPane.add(goToPageField, gridBagConstraints);
+
+ buttonBarPanel.add(pageGotoPane);
+
+ imagePane.setLayout(new java.awt.GridBagLayout());
+
+ imagesLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.imagesLabel.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 9);
+ imagePane.add(imagesLabel, gridBagConstraints);
+
+ imagesRangeLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.imagesRangeLabel.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 15);
+ imagePane.add(imagesRangeLabel, gridBagConstraints);
+
+ buttonBarPanel.add(imagePane);
thumbnailSizeComboBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
thumbnailSizeComboBoxActionPerformed(evt);
}
});
+ buttonBarPanel.add(thumbnailSizeComboBox);
+
+ sortPane.setLayout(new java.awt.GridBagLayout());
+
+ sortLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.sortLabel.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
+ gridBagConstraints.weighty = 1.0;
+ sortPane.add(sortLabel, gridBagConstraints);
sortButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.sortButton.text")); // NOI18N
sortButton.addActionListener(new java.awt.event.ActionListener() {
@@ -212,65 +323,20 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer {
sortButtonActionPerformed(evt);
}
});
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
+ gridBagConstraints.insets = new java.awt.Insets(0, 15, 0, 9);
+ sortPane.add(sortButton, gridBagConstraints);
- sortLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.sortLabel.text")); // NOI18N
+ buttonBarPanel.add(sortPane);
- javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
- this.setLayout(layout);
- layout.setHorizontalGroup(
- layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
- .addContainerGap()
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
- .addComponent(iconView, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
- .addComponent(pageLabel)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(pageNumLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 95, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
- .addComponent(pagesLabel)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
- .addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addGap(0, 0, 0)
- .addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
- .addComponent(goToPageLabel)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(goToPageField, javax.swing.GroupLayout.PREFERRED_SIZE, 54, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addGap(12, 12, 12)
- .addComponent(imagesLabel)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(imagesRangeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 91, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(thumbnailSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addGap(30, 30, 30)
- .addComponent(sortButton)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
- .addComponent(sortLabel))
- .addComponent(filePathLabel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
- .addContainerGap())
- );
- layout.setVerticalGroup(
- layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(layout.createSequentialGroup()
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
- .addComponent(pageLabel)
- .addComponent(pageNumLabel)
- .addComponent(pagesLabel)
- .addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addComponent(goToPageLabel)
- .addComponent(goToPageField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addComponent(imagesLabel)
- .addComponent(imagesRangeLabel)
- .addComponent(thumbnailSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addComponent(sortButton)
- .addComponent(sortLabel))
- .addGap(13, 13, 13)
- .addComponent(iconView, javax.swing.GroupLayout.DEFAULT_SIZE, 322, Short.MAX_VALUE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(filePathLabel))
- );
+ add(buttonBarPanel, java.awt.BorderLayout.NORTH);
+
+ filePathLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.filePathLabel.text")); // NOI18N
+ add(filePathLabel, java.awt.BorderLayout.SOUTH);
+ add(iconView, java.awt.BorderLayout.CENTER);
}// //GEN-END:initComponents
private void pagePrevButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pagePrevButtonActionPerformed
@@ -355,19 +421,26 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer {
// Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JPanel buttonBarPanel;
private javax.swing.JLabel filePathLabel;
private javax.swing.JTextField goToPageField;
private javax.swing.JLabel goToPageLabel;
private org.openide.explorer.view.IconView iconView;
+ private javax.swing.JPanel imagePane;
private javax.swing.JLabel imagesLabel;
private javax.swing.JLabel imagesRangeLabel;
+ private javax.swing.JPanel pageButtonPanel;
+ private javax.swing.JPanel pageGotoPane;
private javax.swing.JLabel pageLabel;
private javax.swing.JButton pageNextButton;
private javax.swing.JLabel pageNumLabel;
+ private javax.swing.JPanel pageNumberPane;
private javax.swing.JButton pagePrevButton;
private javax.swing.JLabel pagesLabel;
+ private javax.swing.JPanel pagesPanel;
private javax.swing.JButton sortButton;
private javax.swing.JLabel sortLabel;
+ private javax.swing.JPanel sortPane;
private javax.swing.JComboBox thumbnailSizeComboBox;
// End of variables declaration//GEN-END:variables
@@ -679,5 +752,5 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer {
}
}
}
- }
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.java
index f23caa3c61..3bc034467b 100755
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ViewPreferencesPanel.java
@@ -23,6 +23,7 @@ import java.util.EnumSet;
import java.util.Objects;
import java.util.TimeZone;
import javax.swing.JPanel;
+import javax.swing.JSpinner;
import org.netbeans.spi.options.OptionsPanelController;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.CasePreferences;
@@ -55,6 +56,9 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
groupByDataSourceCheckbox.setEnabled(evt.getNewValue() != null);
});
this.timeZoneList.setListData(TimeZoneUtils.createTimeZoneList().stream().toArray(String[]::new));
+
+ // Disable manual editing of max results spinner
+ ((JSpinner.DefaultEditor)maxResultsSpinner.getEditor()).getTextField().setEditable(false);
}
@Override
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/WrapLayout.java b/Core/src/org/sleuthkit/autopsy/corecomponents/WrapLayout.java
new file mode 100755
index 0000000000..be35ea0581
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/WrapLayout.java
@@ -0,0 +1,203 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2019 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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.corecomponents;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Insets;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+
+/**
+* FlowLayout subclass that fully supports wrapping of components.
+*
+* Originally written by Rob Camick
+* https://tips4java.wordpress.com/2008/11/06/wrap-layout/
+*/
+class WrapLayout extends FlowLayout {
+
+ /**
+ * Constructs a new WrapLayout with a left alignment and a
+ * default 5-unit horizontal and vertical gap.
+ */
+ public WrapLayout() {
+ super();
+ }
+
+ /**
+ * Constructs a new FlowLayout with the specified alignment
+ * and a default 5-unit horizontal and vertical gap. The value of the
+ * alignment argument must be one of WrapLayout,
+ * WrapLayout, or WrapLayout.
+ *
+ * @param align the alignment value
+ */
+ public WrapLayout(int align) {
+ super(align);
+ }
+
+ /**
+ * Creates a new flow layout manager with the indicated alignment and
+ * the indicated horizontal and vertical gaps.
+ *
+ * The value of the alignment argument must be one of
+ * WrapLayout, WrapLayout, or
+ * WrapLayout.
+ *
+ * @param align the alignment value
+ * @param hgap the horizontal gap between components
+ * @param vgap the vertical gap between components
+ */
+ public WrapLayout(int align, int hgap, int vgap) {
+ super(align, hgap, vgap);
+ }
+
+ /**
+ * Returns the preferred dimensions for this layout given the
+ * visible components in the specified target container.
+ *
+ * @param target the component which needs to be laid out
+ *
+ * @return the preferred dimensions to lay out the subcomponents of the
+ * specified container
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container target) {
+ return layoutSize(target, true);
+ }
+
+ /**
+ * Returns the minimum dimensions needed to layout the visible
+ * components contained in the specified target container.
+ *
+ * @param target the component which needs to be laid out
+ *
+ * @return the minimum dimensions to lay out the subcomponents of the
+ * specified container
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container target) {
+ Dimension minimum = layoutSize(target, false);
+ minimum.width -= (getHgap() + 1);
+ return minimum;
+ }
+
+ /**
+ * Returns the minimum or preferred dimension needed to layout the
+ * target container.
+ *
+ * @param target target to get layout size for
+ * @param preferred should preferred size be calculated
+ *
+ * @return the dimension to layout the target container
+ */
+ private Dimension layoutSize(Container target, boolean preferred) {
+ synchronized (target.getTreeLock()) {
+ // Each row must fit with the width allocated to the containter.
+ // When the container width = 0, the preferred width of the container
+ // has not yet been calculated so lets ask for the maximum.
+
+ int targetWidth = target.getSize().width;
+ Container container = target;
+
+ while (container.getSize().width == 0 && container.getParent() != null) {
+ container = container.getParent();
+ }
+
+ targetWidth = container.getSize().width;
+
+ if (targetWidth == 0) {
+ targetWidth = Integer.MAX_VALUE;
+ }
+
+ int hgap = getHgap();
+ int vgap = getVgap();
+ Insets insets = target.getInsets();
+ int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2);
+ int maxWidth = targetWidth - horizontalInsetsAndGap;
+
+ // Fit components into the allowed width
+ Dimension dim = new Dimension(0, 0);
+ int rowWidth = 0;
+ int rowHeight = 0;
+
+ int nmembers = target.getComponentCount();
+
+ for (int i = 0; i < nmembers; i++) {
+ Component m = target.getComponent(i);
+
+ if (m.isVisible()) {
+ Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize();
+
+ // Can't add the component to current row. Start a new row.
+ if (rowWidth + d.width > maxWidth) {
+ addRow(dim, rowWidth, rowHeight);
+ rowWidth = 0;
+ rowHeight = 0;
+ }
+
+ // Add a horizontal gap for all components after the first
+ if (rowWidth != 0) {
+ rowWidth += hgap;
+ }
+
+ rowWidth += d.width;
+ rowHeight = Math.max(rowHeight, d.height);
+ }
+ }
+
+ addRow(dim, rowWidth, rowHeight);
+
+ dim.width += horizontalInsetsAndGap;
+ dim.height += insets.top + insets.bottom + vgap * 2;
+
+ // When using a scroll pane or the DecoratedLookAndFeel we need to
+ // make sure the preferred size is less than the size of the
+ // target containter so shrinking the container size works
+ // correctly. Removing the horizontal gap is an easy way to do this.
+ Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target);
+
+ if (scrollPane != null && target.isValid()) {
+ dim.width -= (hgap + 1);
+ }
+
+ return dim;
+ }
+ }
+
+ /*
+ * A new row has been completed. Use the dimensions of this row to
+ * update the preferred size for the container.
+ *
+ * @param dim update the width and height when appropriate @param
+ * rowWidth the width of the row to add @param rowHeight the height of
+ * the row to add
+ */
+ private void addRow(Dimension dim, int rowWidth, int rowHeight) {
+ dim.width = Math.max(dim.width, rowWidth);
+
+ if (dim.height > 0) {
+ dim.height += getVgap();
+ }
+
+ dim.height += rowHeight;
+ }
+ }
\ No newline at end of file
diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/StringExtract.java b/Core/src/org/sleuthkit/autopsy/coreutils/StringExtract.java
index aded120ac7..0f89eb2291 100644
--- a/Core/src/org/sleuthkit/autopsy/coreutils/StringExtract.java
+++ b/Core/src/org/sleuthkit/autopsy/coreutils/StringExtract.java
@@ -221,7 +221,8 @@ public class StringExtract {
StringExtractResult resWin = null;
if (enableUTF8 && resUTF16 != null) {
resWin = runUTF16 && resUTF16.numChars > resUTF8.numChars ? resUTF16 : resUTF8;
- } else if (enableUTF16) {
+ } else if (runUTF16) {
+ //Only let resUTF16 "win" if it was actually run.
resWin = resUTF16;
} else if (enableUTF8) {
resWin = resUTF8;
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java
index fa4529e44b..e9b395f292 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2018 Basis Technology Corp.
+ * Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,7 +18,6 @@
*/
package org.sleuthkit.autopsy.datamodel;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.WeakReference;
@@ -27,8 +26,6 @@ import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.stream.Collectors;
import org.apache.commons.io.FilenameUtils;
@@ -65,6 +62,7 @@ import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
+import org.sleuthkit.datamodel.Tag;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
@@ -76,15 +74,10 @@ import org.sleuthkit.datamodel.TskData;
public abstract class AbstractAbstractFileNode extends AbstractContentNode {
private static final Logger logger = Logger.getLogger(AbstractAbstractFileNode.class.getName());
- @NbBundle.Messages("AbstractAbstractFileNode.addFileProperty.desc=no description")
- private static final String NO_DESCR = AbstractAbstractFileNode_addFileProperty_desc();
private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE,
Case.Events.CONTENT_TAG_ADDED, Case.Events.CONTENT_TAG_DELETED, Case.Events.CR_COMMENT_CHANGED);
- private static final ExecutorService translationPool;
- private static final Integer MAX_POOL_SIZE = 10;
-
/**
* @param abstractFile file to wrap
*/
@@ -101,7 +94,7 @@ public abstract class AbstractAbstractFileNode extends A
}
if (UserPreferences.displayTranslatedFileNames()) {
- AbstractAbstractFileNode.translationPool.submit(new TranslationTask(
+ backgroundTasksPool.submit(new TranslationTask(
new WeakReference<>(this), weakPcl));
}
@@ -110,13 +103,6 @@ public abstract class AbstractAbstractFileNode extends A
Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl);
}
- static {
- //Initialize this pool only once! This will be used by every instance of AAFN
- //to do their heavy duty SCO column and translation updates.
- translationPool = Executors.newFixedThreadPool(MAX_POOL_SIZE,
- new ThreadFactoryBuilder().setNameFormat("translation-task-thread-%d").build());
- }
-
/**
* The finalizer removes event listeners as the BlackboardArtifactNode is
* being garbage collected. Yes, we know that finalizers are considered to
@@ -137,16 +123,6 @@ public abstract class AbstractAbstractFileNode extends A
Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl);
}
- /**
- * Event signals to indicate the background tasks have completed processing.
- * Currently, we have one property task in the background:
- *
- * 1) Retreiving the translation of the file name
- */
- enum NodeSpecificEvents {
- TRANSLATION_AVAILABLE,
- }
-
private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
String eventType = evt.getPropertyName();
@@ -190,7 +166,7 @@ public abstract class AbstractAbstractFileNode extends A
} else if (eventType.equals(Case.Events.CONTENT_TAG_ADDED.toString())) {
ContentTagAddedEvent event = (ContentTagAddedEvent) evt;
if (event.getAddedTag().getContent().equals(content)) {
- List tags = getContentTagsFromDatabase();
+ List tags = this.getAllTagsFromDatabase();
Pair scorePropAndDescr = getScorePropertyAndDescription(tags);
Score value = scorePropAndDescr.getLeft();
String descr = scorePropAndDescr.getRight();
@@ -202,7 +178,7 @@ public abstract class AbstractAbstractFileNode extends A
} else if (eventType.equals(Case.Events.CONTENT_TAG_DELETED.toString())) {
ContentTagDeletedEvent event = (ContentTagDeletedEvent) evt;
if (event.getDeletedTagInfo().getContentID() == content.getId()) {
- List tags = getContentTagsFromDatabase();
+ List tags = getAllTagsFromDatabase();
Pair scorePropAndDescr = getScorePropertyAndDescription(tags);
Score value = scorePropAndDescr.getLeft();
String descr = scorePropAndDescr.getRight();
@@ -214,7 +190,7 @@ public abstract class AbstractAbstractFileNode extends A
} else if (eventType.equals(Case.Events.CR_COMMENT_CHANGED.toString())) {
CommentChangedEvent event = (CommentChangedEvent) evt;
if (event.getContentID() == content.getId()) {
- List tags = getContentTagsFromDatabase();
+ List tags = getAllTagsFromDatabase();
CorrelationAttributeInstance attribute = getCorrelationAttributeInstance();
updateSheet(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, getCommentProperty(tags, attribute)));
}
@@ -223,6 +199,18 @@ public abstract class AbstractAbstractFileNode extends A
//Set the tooltip
this.setShortDescription(content.getName());
updateSheet(new NodeProperty<>(ORIGINAL_NAME.toString(), ORIGINAL_NAME.toString(), NO_DESCR, content.getName()));
+ } else if (eventType.equals(NodeSpecificEvents.SCO_AVAILABLE.toString())) {
+ SCOData scoData = (SCOData) evt.getNewValue();
+ if (scoData.getScoreAndDescription() != null) {
+ updateSheet(new NodeProperty<>(SCORE.toString(), SCORE.toString(), scoData.getScoreAndDescription().getRight(), scoData.getScoreAndDescription().getLeft()));
+ }
+ if (scoData.getComment() != null) {
+ updateSheet(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, scoData.getComment()));
+ }
+ if (scoData.getCountAndDescription() != null
+ && !UserPreferences.hideCentralRepoCommentsAndOccurrences()) {
+ updateSheet(new NodeProperty<>(OCCURRENCES.toString(), OCCURRENCES.toString(), scoData.getCountAndDescription().getRight(), scoData.getCountAndDescription().getLeft()));
+ }
}
};
/**
@@ -235,38 +223,6 @@ public abstract class AbstractAbstractFileNode extends A
*/
private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
- /**
- * Updates the values of the properties in the current property sheet with
- * the new properties being passed in. Only if that property exists in the
- * current sheet will it be applied. That way, we allow for subclasses to
- * add their own (or omit some!) properties and we will not accidentally
- * disrupt their UI.
- *
- * Race condition if not synchronized. Only one update should be applied at
- * a time.
- *
- * @param newProps New file property instances to be updated in the current
- * sheet.
- */
- private synchronized void updateSheet(NodeProperty>... newProps) {
- //Refresh ONLY those properties in the sheet currently. Subclasses may have
- //only added a subset of our properties or their own props.s
- Sheet visibleSheet = this.getSheet();
- Sheet.Set visibleSheetSet = visibleSheet.get(Sheet.PROPERTIES);
- Property>[] visibleProps = visibleSheetSet.getProperties();
- for (NodeProperty> newProp : newProps) {
- for (int i = 0; i < visibleProps.length; i++) {
- if (visibleProps[i].getName().equals(newProp.getName())) {
- visibleProps[i] = newProp;
- }
- }
- }
- visibleSheetSet.put(visibleProps);
- visibleSheet.put(visibleSheetSet);
- //setSheet() will notify Netbeans to update this node in the UI.
- this.setSheet(visibleSheet);
- }
-
/*
* This is called when the node is first initialized. Any new updates or
* changes happen by directly manipulating the sheet. That means we can fire
@@ -368,18 +324,17 @@ public abstract class AbstractAbstractFileNode extends A
properties.add(new NodeProperty<>(ORIGINAL_NAME.toString(), ORIGINAL_NAME.toString(), NO_DESCR, ""));
}
- //SCO column prereq info..
- List tags = getContentTagsFromDatabase();
- CorrelationAttributeInstance attribute = getCorrelationAttributeInstance();
-
- Pair scoreAndDescription = getScorePropertyAndDescription(tags);
- properties.add(new NodeProperty<>(SCORE.toString(), SCORE.toString(), scoreAndDescription.getRight(), scoreAndDescription.getLeft()));
- DataResultViewerTable.HasCommentStatus comment = getCommentProperty(tags, attribute);
- properties.add(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, comment));
- if (!UserPreferences.hideCentralRepoCommentsAndOccurrences()) {
- Pair countAndDescription = getCountPropertyAndDescription(attribute);
- properties.add(new NodeProperty<>(OCCURRENCES.toString(), OCCURRENCES.toString(), countAndDescription.getRight(), countAndDescription.getLeft()));
+ // Create place holders for S C O
+ properties.add(new NodeProperty<>(SCORE.toString(), SCORE.toString(), VALUE_LOADING, ""));
+ properties.add(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), VALUE_LOADING, ""));
+ if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) {
+ properties.add(new NodeProperty<>(OCCURRENCES.toString(), OCCURRENCES.toString(), VALUE_LOADING, ""));
}
+
+ // Get the SCO columns data in a background task
+ backgroundTasksPool.submit(new GetSCOTask(
+ new WeakReference<>(this), weakPcl));
+
properties.add(new NodeProperty<>(LOCATION.toString(), LOCATION.toString(), NO_DESCR, getContentPath(content)));
properties.add(new NodeProperty<>(MOD_TIME.toString(), MOD_TIME.toString(), NO_DESCR, ContentUtils.getStringTime(content.getMtime(), content)));
properties.add(new NodeProperty<>(CHANGED_TIME.toString(), CHANGED_TIME.toString(), NO_DESCR, ContentUtils.getStringTime(content.getCtime(), content)));
@@ -437,19 +392,19 @@ public abstract class AbstractAbstractFileNode extends A
@NbBundle.Messages({
"AbstractAbstractFileNode.createSheet.count.displayName=O",
- "AbstractAbstractFileNode.createSheet.count.noCentralRepo.description=Central repository was not enabled when this column was populated",
"AbstractAbstractFileNode.createSheet.count.hashLookupNotRun.description=Hash lookup had not been run on this file when the column was populated",
- "# {0} - occuranceCount",
- "AbstractAbstractFileNode.createSheet.count.description=There were {0} datasource(s) found with occurances of the correlation value"})
- Pair getCountPropertyAndDescription(CorrelationAttributeInstance attribute) {
+ "# {0} - occurenceCount",
+ "AbstractAbstractFileNode.createSheet.count.description=There were {0} datasource(s) found with occurences of the MD5 correlation value"})
+ @Override
+ protected Pair getCountPropertyAndDescription(CorrelationAttributeInstance.Type attributeType, String attributeValue, String defaultDescription) {
Long count = -1L; //The column renderer will not display negative values, negative value used when count unavailble to preserve sorting
- String description = Bundle.AbstractAbstractFileNode_createSheet_count_noCentralRepo_description();
+ String description = defaultDescription;
try {
//don't perform the query if there is no correlation value
- if (attribute != null && StringUtils.isNotBlank(attribute.getCorrelationValue())) {
- count = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(attribute.getCorrelationType(), attribute.getCorrelationValue());
+ if (attributeType != null && StringUtils.isNotBlank(attributeValue)) {
+ count = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(attributeType, attributeValue);
description = Bundle.AbstractAbstractFileNode_createSheet_count_description(count);
- } else if (attribute != null) {
+ } else if (attributeType != null) {
description = Bundle.AbstractAbstractFileNode_createSheet_count_hashLookupNotRun_description();
}
} catch (EamDbException ex) {
@@ -457,7 +412,6 @@ public abstract class AbstractAbstractFileNode extends A
} catch (CorrelationAttributeNormalizationException ex) {
logger.log(Level.WARNING, "Unable to normalize data to get count of datasources with correlation attribute", ex);
}
-
return Pair.of(count, description);
}
@@ -468,7 +422,8 @@ public abstract class AbstractAbstractFileNode extends A
"AbstractAbstractFileNode.createSheet.taggedFile.description=File has been tagged.",
"AbstractAbstractFileNode.createSheet.notableTaggedFile.description=File tagged with notable tag.",
"AbstractAbstractFileNode.createSheet.noScore.description=No score"})
- Pair getScorePropertyAndDescription(List tags) {
+ @Override
+ protected Pair getScorePropertyAndDescription(List tags) {
DataResultViewerTable.Score score = DataResultViewerTable.Score.NO_SCORE;
String description = Bundle.AbstractAbstractFileNode_createSheet_noScore_description();
if (content.getKnown() == TskData.FileKnown.BAD) {
@@ -486,7 +441,7 @@ public abstract class AbstractAbstractFileNode extends A
if (!tags.isEmpty() && (score == DataResultViewerTable.Score.NO_SCORE || score == DataResultViewerTable.Score.INTERESTING_SCORE)) {
score = DataResultViewerTable.Score.INTERESTING_SCORE;
description = Bundle.AbstractAbstractFileNode_createSheet_taggedFile_description();
- for (ContentTag tag : tags) {
+ for (Tag tag : tags) {
if (tag.getName().getKnownStatus() == TskData.FileKnown.BAD) {
score = DataResultViewerTable.Score.NOTABLE_SCORE;
description = Bundle.AbstractAbstractFileNode_createSheet_notableTaggedFile_description();
@@ -499,11 +454,12 @@ public abstract class AbstractAbstractFileNode extends A
@NbBundle.Messages({
"AbstractAbstractFileNode.createSheet.comment.displayName=C"})
- HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) {
+ @Override
+ protected HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) {
DataResultViewerTable.HasCommentStatus status = !tags.isEmpty() ? DataResultViewerTable.HasCommentStatus.TAG_NO_COMMENT : DataResultViewerTable.HasCommentStatus.NO_COMMENT;
- for (ContentTag tag : tags) {
+ for (Tag tag : tags) {
if (!StringUtils.isBlank(tag.getComment())) {
//if the tag is null or empty or contains just white space it will indicate there is not a comment
status = DataResultViewerTable.HasCommentStatus.TAG_COMMENT;
@@ -571,7 +527,13 @@ public abstract class AbstractAbstractFileNode extends A
return tags;
}
- CorrelationAttributeInstance getCorrelationAttributeInstance() {
+ @Override
+ protected List getAllTagsFromDatabase() {
+ return new ArrayList<>(getContentTagsFromDatabase());
+ }
+
+ @Override
+ protected CorrelationAttributeInstance getCorrelationAttributeInstance() {
CorrelationAttributeInstance attribute = null;
if (EamDb.isEnabled() && !UserPreferences.hideCentralRepoCommentsAndOccurrences()) {
attribute = EamArtifactUtil.getInstanceFromContent(content);
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java
index 4f6f2e5f47..8b1d22d349 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java
@@ -18,20 +18,30 @@
*/
package org.sleuthkit.autopsy.datamodel;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.logging.Level;
+import org.apache.commons.lang3.tuple.Pair;
import org.openide.nodes.Children;
+import org.openide.nodes.Sheet;
import org.openide.util.lookup.Lookups;
import org.openide.util.Lookup;
+import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance.Type;
+import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.SleuthkitCase;
+import org.sleuthkit.datamodel.Tag;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.TskException;
@@ -51,27 +61,60 @@ public abstract class AbstractContentNode extends ContentNode
private static final Logger logger = Logger.getLogger(AbstractContentNode.class.getName());
/**
- * Handles aspects that depend on the Content object
- *
- * @param content Underlying Content instances
+ * A pool of background tasks to run any long computation needed to populate
+ * this node.
*/
- AbstractContentNode(T content) {
- this(content, Lookups.singleton(content) );
+ static final ExecutorService backgroundTasksPool;
+ private static final Integer MAX_POOL_SIZE = 10;
+
+ /**
+ * Default no description string
+ */
+ @NbBundle.Messages({"AbstractContentNode.nodescription=no description",
+ "AbstractContentNode.valueLoading=value loading"})
+ protected static final String NO_DESCR = Bundle.AbstractContentNode_nodescription();
+ protected static final String VALUE_LOADING = Bundle.AbstractContentNode_valueLoading();
+
+ /**
+ * Event signals to indicate the background tasks have completed processing.
+ * Currently, we have one property task in the background:
+ *
+ * 1) Retrieving the translation of the file name
+ */
+ enum NodeSpecificEvents {
+ TRANSLATION_AVAILABLE,
+ SCO_AVAILABLE
+ }
+
+ static {
+ //Initialize this pool only once! This will be used by every instance of AAFN
+ //to do their heavy duty SCO column and translation updates.
+ backgroundTasksPool = Executors.newFixedThreadPool(MAX_POOL_SIZE,
+ new ThreadFactoryBuilder().setNameFormat("content-node-background-task-%d").build());
}
/**
* Handles aspects that depend on the Content object
*
* @param content Underlying Content instances
- * @param lookup The Lookup object for the node.
+ */
+ AbstractContentNode(T content) {
+ this(content, Lookups.singleton(content));
+ }
+
+ /**
+ * Handles aspects that depend on the Content object
+ *
+ * @param content Underlying Content instances
+ * @param lookup The Lookup object for the node.
*/
AbstractContentNode(T content, Lookup lookup) {
- super(Children.create(new ContentChildren(content), true), lookup);
+ super(Children.create(new ContentChildren(content), false), lookup);
this.content = content;
//super.setName(ContentUtils.getSystemName(content));
super.setName("content_" + Long.toString(content.getId())); //NON-NLS
}
-
+
/**
* Return the content data associated with this node
*
@@ -100,40 +143,40 @@ public abstract class AbstractContentNode extends ContentNode
public boolean hasVisibleContentChildren() {
return contentHasVisibleContentChildren(content);
}
-
+
/**
* Return true if the given content object has children. Useful for lazy
* loading.
- *
+ *
* @param c The content object to look for children on
+ *
* @return true if has children
*/
- public static boolean contentHasVisibleContentChildren(Content c){
+ public static boolean contentHasVisibleContentChildren(Content c) {
if (c != null) {
-
+
try {
- if( ! c.hasChildren()) {
+ if (!c.hasChildren()) {
return false;
}
} catch (TskCoreException ex) {
-
+
logger.log(Level.SEVERE, "Error checking if the node has children, for content: " + c, ex); //NON-NLS
return false;
}
-
+
String query = "SELECT COUNT(obj_id) AS count FROM "
- + " ( SELECT obj_id FROM tsk_objects WHERE par_obj_id = " + c.getId() + " AND type = "
- + TskData.ObjectType.ARTIFACT.getObjectType()
- + " INTERSECT SELECT artifact_obj_id FROM blackboard_artifacts WHERE obj_id = " + c.getId()
- + " AND (artifact_type_id = " + ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()
- + " OR artifact_type_id = " + ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() + ") "
- + " UNION SELECT obj_id FROM tsk_objects WHERE par_obj_id = " + c.getId()
- + " AND type = " + TskData.ObjectType.ABSTRACTFILE.getObjectType() + ") AS OBJECT_IDS"; //NON-NLS;
-
-
+ + " ( SELECT obj_id FROM tsk_objects WHERE par_obj_id = " + c.getId() + " AND type = "
+ + TskData.ObjectType.ARTIFACT.getObjectType()
+ + " INTERSECT SELECT artifact_obj_id FROM blackboard_artifacts WHERE obj_id = " + c.getId()
+ + " AND (artifact_type_id = " + ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()
+ + " OR artifact_type_id = " + ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() + ") "
+ + " UNION SELECT obj_id FROM tsk_objects WHERE par_obj_id = " + c.getId()
+ + " AND type = " + TskData.ObjectType.ABSTRACTFILE.getObjectType() + ") AS OBJECT_IDS"; //NON-NLS;
+
try (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query)) {
ResultSet resultSet = dbQuery.getResultSet();
- if(resultSet.next()){
+ if (resultSet.next()) {
return (0 < resultSet.getInt("count"));
}
} catch (TskCoreException | SQLException | NoCurrentCaseException ex) {
@@ -142,7 +185,7 @@ public abstract class AbstractContentNode extends ContentNode
}
return false;
}
-
+
/**
* Return true if the underlying content object has children Useful for lazy
* loading.
@@ -162,7 +205,7 @@ public abstract class AbstractContentNode extends ContentNode
return hasChildren;
}
-
+
/**
* Return ids of children of the underlying content. The ids can be treated
* as keys - useful for lazy loading.
@@ -240,4 +283,83 @@ public abstract class AbstractContentNode extends ContentNode
public int read(byte[] buf, long offset, long len) throws TskException {
return content.read(buf, offset, len);
}
+
+ /**
+ * Updates the values of the properties in the current property sheet with
+ * the new properties being passed in. Only if that property exists in the
+ * current sheet will it be applied. That way, we allow for subclasses to
+ * add their own (or omit some!) properties and we will not accidentally
+ * disrupt their UI.
+ *
+ * Race condition if not synchronized. Only one update should be applied at
+ * a time.
+ *
+ * @param newProps New file property instances to be updated in the current
+ * sheet.
+ */
+ protected synchronized void updateSheet(NodeProperty>... newProps) {
+ //Refresh ONLY those properties in the sheet currently. Subclasses may have
+ //only added a subset of our properties or their own props.s
+ Sheet visibleSheet = this.getSheet();
+ Sheet.Set visibleSheetSet = visibleSheet.get(Sheet.PROPERTIES);
+ Property>[] visibleProps = visibleSheetSet.getProperties();
+ for (NodeProperty> newProp : newProps) {
+ for (int i = 0; i < visibleProps.length; i++) {
+ if (visibleProps[i].getName().equals(newProp.getName())) {
+ visibleProps[i] = newProp;
+ }
+ }
+ }
+ visibleSheetSet.put(visibleProps);
+ visibleSheet.put(visibleSheetSet);
+ //setSheet() will notify Netbeans to update this node in the UI.
+ this.setSheet(visibleSheet);
+ }
+
+ /**
+ * Reads and returns a list of all tags associated with this content node.
+ *
+ * @return list of tags associated with the node.
+ */
+ abstract protected List getAllTagsFromDatabase();
+
+ /**
+ * Returns correlation attribute instance for the underlying content of the
+ * node.
+ *
+ * @return correlation attribute instance for the underlying content of the
+ * node.
+ */
+ abstract protected CorrelationAttributeInstance getCorrelationAttributeInstance();
+
+ /**
+ * Returns Score property for the node.
+ *
+ * @param tags list of tags.
+ *
+ * @return Score property for the underlying content of the node.
+ */
+ abstract protected Pair getScorePropertyAndDescription(List tags);
+
+ /**
+ * Returns comment property for the node.
+ *
+ * @param tags list of tags
+ * @param attribute correlation attribute instance
+ *
+ * @return Comment property for the underlying content of the node.
+ */
+ abstract protected DataResultViewerTable.HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute);
+
+ /**
+ * Returns occurrences/count property for the node.
+ *
+ * @param attributeType the type of the attribute to count
+ * @param attributeValue the value of the attribute to count
+ * @param defaultDescription a description to use when none is determined by
+ * the getCountPropertyAndDescription method
+ *
+ * @return count property for the underlying content of the node.
+ */
+ abstract protected Pair getCountPropertyAndDescription(Type attributeType, String attributeValue, String defaultDescription);
}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java
index 55e81acd17..ef96097391 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2018 Basis Technology Corp.
+ * Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,6 +22,7 @@ import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
+import java.lang.ref.WeakReference;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -37,6 +38,7 @@ import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.swing.Action;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
import org.openide.nodes.Sheet;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
@@ -50,16 +52,19 @@ import org.sleuthkit.autopsy.casemodule.events.CommentChangedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance.Type;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
import org.sleuthkit.autopsy.core.UserPreferences;
+import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.Score;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import static org.sleuthkit.autopsy.datamodel.DisplayableItemNode.findLinked;
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.HasCommentStatus;
+import static org.sleuthkit.autopsy.datamodel.AbstractContentNode.backgroundTasksPool;
import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager;
import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction;
import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction;
@@ -96,9 +101,6 @@ public class BlackboardArtifactNode extends AbstractContentNode> customProperties;
- private final static String NO_DESCR = NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.noDesc.text");
-
-
/*
* Artifact types which should have the full unique path of the associated
* content as a property.
@@ -150,6 +152,18 @@ public class BlackboardArtifactNode extends AbstractContentNode(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), scoData.getScoreAndDescription().getRight(), scoData.getScoreAndDescription().getLeft()));
+ }
+ if (scoData.getComment() != null) {
+ updateSheet(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), NO_DESCR, scoData.getComment()));
+ }
+ if (scoData.getCountAndDescription() != null
+ && !UserPreferences.hideCentralRepoCommentsAndOccurrences()) {
+ updateSheet(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), scoData.getCountAndDescription().getRight(), scoData.getCountAndDescription().getLeft()));
+ }
}
}
};
@@ -319,7 +333,7 @@ public class BlackboardArtifactNode extends AbstractContentNode tags = getAllTagsFromDatabase();
-
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
if (sheetSet == null) {
sheetSet = Sheet.createPropertiesSet();
@@ -351,17 +363,17 @@ public class BlackboardArtifactNode extends AbstractContentNode(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), VALUE_LOADING, ""));
+ sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), VALUE_LOADING, ""));
if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) {
- correlationAttribute = getCorrelationAttributeInstance();
+ sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), VALUE_LOADING, ""));
}
- addCommentProperty(sheetSet, tags, correlationAttribute);
- if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) {
- addCountProperty(sheetSet, correlationAttribute);
- }
+ // Get the SCO columns data in a background task
+ backgroundTasksPool.submit(new GetSCOTask(
+ new WeakReference<>(this), weakPcl));
+
if (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID()) {
try {
BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
@@ -520,6 +532,7 @@ public class BlackboardArtifactNode extends AbstractContentNode getAllTagsFromDatabase() {
List tags = new ArrayList<>();
try {
@@ -569,6 +582,13 @@ public class BlackboardArtifactNode extends AbstractContentNode t.getName().getDisplayName()).collect(Collectors.joining(", "))));
}
+ /**
+ * Gets the correlation attribute for the associated file
+ *
+ * @return the correlation attribute for the file associated with this
+ * BlackboardArtifactNode
+ */
+ @Override
protected final CorrelationAttributeInstance getCorrelationAttributeInstance() {
CorrelationAttributeInstance correlationAttribute = null;
if (EamDb.isEnabled()) {
@@ -581,16 +601,37 @@ public class BlackboardArtifactNode extends AbstractContentNode tags, CorrelationAttributeInstance attribute) {
+ HasCommentStatus status = getCommentProperty(tags, attribute);
+ sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), NO_DESCR,
+ status));
+ }
+
+ /**
+ * Gets the comment property for the node
+ *
+ * @param tags the list of tags associated with the file
+ * @param attribute the correlation attribute associated with this
+ * artifact's associated file, null if central repo is not
+ * enabled
+ *
+ * @return comment property
+ */
+ @Override
+ protected DataResultViewerTable.HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) {
+
HasCommentStatus status = tags.size() > 0 ? HasCommentStatus.TAG_NO_COMMENT : HasCommentStatus.NO_COMMENT;
for (Tag tag : tags) {
if (!StringUtils.isBlank(tag.getComment())) {
@@ -609,17 +650,18 @@ public class BlackboardArtifactNode extends AbstractContentNode(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), NO_DESCR,
- status));
+ return status;
}
/**
* Used by (subclasses of) BlackboardArtifactNode to add the Score property
* to their sheets.
*
- * @param sheetSet the modifiable Sheet.Set returned by
- * Sheet.get(Sheet.PROPERTIES)
+ * @param sheetSet the modifiable Sheet.Set to add the property to
* @param tags the list of tags associated with the file
+ *
+ * @deprecated Use the GetSCOTask to get this data on a background
+ * thread..., and then update the property sheet asynchronously
*/
@NbBundle.Messages({"BlackboardArtifactNode.createSheet.score.name=S",
"BlackboardArtifactNode.createSheet.score.displayName=S",
@@ -628,7 +670,21 @@ public class BlackboardArtifactNode extends AbstractContentNode tags) {
+ @Deprecated
+ protected final void addScorePropertyAndDescription(Sheet.Set sheetSet, List tags) {
+ Pair scoreAndDescription = getScorePropertyAndDescription(tags);
+ sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), scoreAndDescription.getRight(), scoreAndDescription.getLeft()));
+ }
+
+ /**
+ * Get the score property for the node.
+ *
+ * @param tags the list of tags associated with the file
+ *
+ * @return score property and description
+ */
+ @Override
+ protected Pair getScorePropertyAndDescription(List tags) {
Score score = Score.NO_SCORE;
String description = Bundle.BlackboardArtifactNode_createSheet_noScore_description();
if (associated instanceof AbstractFile) {
@@ -673,34 +729,63 @@ public class BlackboardArtifactNode extends AbstractContentNode(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), description, score));
+
+ return Pair.of(score, description);
}
+ /**
+ * Used by (subclasses of) BlackboardArtifactNode to add the Occurrences
+ * property to their sheets.
+ *
+ * @param sheetSet the modifiable Sheet.Set to add the property to
+ * @param attribute correlation attribute instance
+ *
+ * @deprecated Use the GetSCOTask to get this data on a background
+ * thread..., and then update the property sheet asynchronously
+ */
@NbBundle.Messages({"BlackboardArtifactNode.createSheet.count.name=O",
"BlackboardArtifactNode.createSheet.count.displayName=O",
- "BlackboardArtifactNode.createSheet.count.noCentralRepo.description=Central repository was not enabled when this column was populated",
- "BlackboardArtifactNode.createSheet.count.hashLookupNotRun.description=Hash lookup had not been run on this artifact's associated file when the column was populated",
- "# {0} - occuranceCount",
- "BlackboardArtifactNode.createSheet.count.description=There were {0} datasource(s) found with occurances of the correlation value"})
-
+ "BlackboardArtifactNode.createSheet.count.noCorrelationAttributes.description=No correlation properties found",
+ "BlackboardArtifactNode.createSheet.count.noCorrelationValues.description=Unable to find other occurrences because no value exists for the available correlation property",
+ "# {0} - occurenceCount",
+ "# {1} - attributeType",
+ "BlackboardArtifactNode.createSheet.count.description=There were {0} datasource(s) found with occurrences of the correlation value of type {1}"})
+ @Deprecated
protected final void addCountProperty(Sheet.Set sheetSet, CorrelationAttributeInstance attribute) {
- Long count = -1L; //The column renderer will not display negative values, negative value used when count unavailble to preserve sorting
- String description = Bundle.BlackboardArtifactNode_createSheet_count_noCentralRepo_description();
+ Pair countAndDescription = getCountPropertyAndDescription(attribute.getCorrelationType(), attribute.getCorrelationValue(), Bundle.BlackboardArtifactNode_createSheet_count_noCorrelationAttributes_description());
+ sheetSet.put(
+ new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), countAndDescription.getRight(), countAndDescription.getLeft()));
+ }
+
+ /**
+ * Gets the Occurrences property for the node.
+ *
+ * @param attributeType the type of the attribute to count
+ * @param attributeValue the value of the attribute to count
+ * @param defaultDescription a description to use when none is determined by
+ * the getCountPropertyAndDescription method
+ *
+ * @return count and description
+ *
+ */
+ @Override
+ protected Pair getCountPropertyAndDescription(Type attributeType, String attributeValue, String defaultDescription) {
+ Long count = -1L;
+ String description = defaultDescription;
try {
//don't perform the query if there is no correlation value
- if (attribute != null && StringUtils.isNotBlank(attribute.getCorrelationValue())) {
- count = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(attribute.getCorrelationType(), attribute.getCorrelationValue());
- description = Bundle.BlackboardArtifactNode_createSheet_count_description(count);
- } else if (attribute != null) {
- description = Bundle.BlackboardArtifactNode_createSheet_count_hashLookupNotRun_description();
+ if (attributeType != null && StringUtils.isNotBlank(attributeValue)) {
+ count = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(attributeType, attributeValue);
+ description = Bundle.BlackboardArtifactNode_createSheet_count_description(count, attributeType.getDisplayName());
+ } else if (attributeType != null) {
+ description = Bundle.BlackboardArtifactNode_createSheet_count_noCorrelationValues_description();
}
} catch (EamDbException ex) {
logger.log(Level.WARNING, "Error getting count of datasources with correlation attribute", ex);
} catch (CorrelationAttributeNormalizationException ex) {
logger.log(Level.WARNING, "Unable to normalize data to get count of datasources with correlation attribute", ex);
}
- sheetSet.put(
- new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), description, count));
+ return Pair.of(count, description);
}
private void updateSheet() {
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED
index 1d8fb7eb14..04173f9689 100755
--- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED
@@ -1,16 +1,14 @@
AbstractAbstractFileNode.accessTimeColLbl=Access Time
-AbstractAbstractFileNode.addFileProperty.desc=no description
AbstractAbstractFileNode.attrAddrColLbl=Attr. Addr.
AbstractAbstractFileNode.changeTimeColLbl=Change Time
AbstractAbstractFileNode.createdTimeColLbl=Created Time
AbstractAbstractFileNode.createSheet.comment.displayName=C
AbstractAbstractFileNode.createSheet.comment.name=C
-# {0} - occuranceCount
-AbstractAbstractFileNode.createSheet.count.description=There were {0} datasource(s) found with occurances of the correlation value
+# {0} - occurenceCount
+AbstractAbstractFileNode.createSheet.count.description=There were {0} datasource(s) found with occurences of the MD5 correlation value
AbstractAbstractFileNode.createSheet.count.displayName=O
AbstractAbstractFileNode.createSheet.count.hashLookupNotRun.description=Hash lookup had not been run on this file when the column was populated
AbstractAbstractFileNode.createSheet.count.name=O
-AbstractAbstractFileNode.createSheet.count.noCentralRepo.description=Central repository was not enabled when this column was populated
AbstractAbstractFileNode.createSheet.interestingResult.description=File has interesting result associated with it.
AbstractAbstractFileNode.createSheet.noScore.description=No score
AbstractAbstractFileNode.createSheet.notableFile.description=File recognized as notable.
@@ -37,6 +35,8 @@ AbstractAbstractFileNode.tagsProperty.displayName=Tags
AbstractAbstractFileNode.typeDirColLbl=Type(Dir)
AbstractAbstractFileNode.typeMetaColLbl=Type(Meta)
AbstractAbstractFileNode.useridColLbl=UserID
+AbstractContentNode.nodescription=no description
+AbstractContentNode.valueLoading=value loading
AbstractFsContentNode.noDesc.text=no description
ArtifactStringContent.attrsTableHeader.sources=Source(s)
ArtifactStringContent.attrsTableHeader.type=Type
@@ -53,12 +53,13 @@ BlackboardArtifactNode.createSheet.artifactType.displayName=Result Type
BlackboardArtifactNode.createSheet.artifactType.name=Result Type
BlackboardArtifactNode.createSheet.comment.displayName=C
BlackboardArtifactNode.createSheet.comment.name=C
-# {0} - occuranceCount
-BlackboardArtifactNode.createSheet.count.description=There were {0} datasource(s) found with occurances of the correlation value
+# {0} - occurenceCount
+# {1} - attributeType
+BlackboardArtifactNode.createSheet.count.description=There were {0} datasource(s) found with occurrences of the correlation value of type {1}
BlackboardArtifactNode.createSheet.count.displayName=O
-BlackboardArtifactNode.createSheet.count.hashLookupNotRun.description=Hash lookup had not been run on this artifact's associated file when the column was populated
BlackboardArtifactNode.createSheet.count.name=O
-BlackboardArtifactNode.createSheet.count.noCentralRepo.description=Central repository was not enabled when this column was populated
+BlackboardArtifactNode.createSheet.count.noCorrelationAttributes.description=No correlation properties found
+BlackboardArtifactNode.createSheet.count.noCorrelationValues.description=Unable to find other occurrences because no value exists for the available correlation property
BlackboardArtifactNode.createSheet.fileSize.displayName=Size
BlackboardArtifactNode.createSheet.fileSize.name=Size
BlackboardArtifactNode.createSheet.interestingResult.description=Result has an interesting result associated with it.
@@ -114,6 +115,8 @@ FileTypesByMimeTypeNode.createSheet.mediaSubtype.name=Subtype
FileTypesByMimeTypeNode.createSheet.mediaType.desc=no description
FileTypesByMimeTypeNode.createSheet.mediaType.displayName=Type
FileTypesByMimeTypeNode.createSheet.mediaType.name=Type
+GetSCOTask.occurrences.defaultDescription=No correlation properties found
+GetSCOTask.occurrences.multipleProperties=Multiple different correlation properties exist for this result
ImageNode.action.runIngestMods.text=Run Ingest Modules
ImageNode.createSheet.deviceId.desc=Device ID of the image
ImageNode.createSheet.deviceId.displayName=Device ID
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java
index 13a0753ca1..bdb4a394a8 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java
@@ -36,6 +36,7 @@ import org.sleuthkit.autopsy.actions.ReplaceBlackboardArtifactTagAction;
import org.sleuthkit.autopsy.actions.ReplaceContentTagAction;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
import org.sleuthkit.autopsy.datamodel.Reports.ReportNode;
+import org.sleuthkit.autopsy.directorytree.ExportCSVAction;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
@@ -92,6 +93,7 @@ public class DataModelActionsFactory {
}
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
+ actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
if (isArtifactSource) {
@@ -119,6 +121,7 @@ public class DataModelActionsFactory {
actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, slackFileNode));
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
+ actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
if (isArtifactSource) {
@@ -155,6 +158,7 @@ public class DataModelActionsFactory {
}
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());//
+ actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
if (isArtifactSource) {
@@ -189,6 +193,7 @@ public class DataModelActionsFactory {
}
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
+ actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
if (isArtifactSource) {
@@ -223,6 +228,7 @@ public class DataModelActionsFactory {
}
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
+ actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
if (isArtifactSource) {
@@ -257,6 +263,7 @@ public class DataModelActionsFactory {
}
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
+ actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
if (isArtifactSource) {
@@ -291,6 +298,7 @@ public class DataModelActionsFactory {
}
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
+ actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
if (isArtifactSource) {
@@ -325,6 +333,7 @@ public class DataModelActionsFactory {
}
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
+ actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
if (isArtifactSource) {
@@ -379,6 +388,7 @@ public class DataModelActionsFactory {
}
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
+ actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
if (isArtifactSource) {
@@ -415,6 +425,7 @@ public class DataModelActionsFactory {
}
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
+ actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
if (isArtifactSource) {
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java
index 1265db5658..5ab29a5376 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java
@@ -57,7 +57,7 @@ public class DataSourcesNode extends DisplayableItemNode {
}
public DataSourcesNode(long dsObjId) {
- super(Children.create(new DataSourcesNodeChildren(dsObjId), true), Lookups.singleton(NAME));
+ super(Children.create(new DataSourcesNodeChildren(dsObjId), false), Lookups.singleton(NAME));
displayName = (dsObjId > 0) ? NbBundle.getMessage(DataSourcesNode.class, "DataSourcesNode.group_by_datasource.name") : NAME;
init();
}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java
index 25069d9564..adc5a34fa7 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java
@@ -24,7 +24,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
-import java.util.Objects;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
@@ -37,7 +36,6 @@ import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
-import org.sleuthkit.autopsy.casemodule.CasePreferences;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
@@ -59,7 +57,7 @@ import org.sleuthkit.datamodel.VirtualDirectory;
public class DeletedContent implements AutopsyVisitableItem {
private SleuthkitCase skCase;
- private final long datasourceObjId;
+ private final long filteringDSObjId; // 0 if not filtering/grouping by data source
@NbBundle.Messages({"DeletedContent.fsDelFilter.text=File System",
"DeletedContent.allDelFilter.text=All"})
@@ -105,11 +103,11 @@ public class DeletedContent implements AutopsyVisitableItem {
public DeletedContent(SleuthkitCase skCase, long dsObjId) {
this.skCase = skCase;
- this.datasourceObjId = dsObjId;
+ this.filteringDSObjId = dsObjId;
}
long filteringDataSourceObjId() {
- return this.datasourceObjId;
+ return this.filteringDSObjId;
}
@Override
@@ -439,7 +437,7 @@ public class DeletedContent implements AutopsyVisitableItem {
}
- if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) {
+ if (filteringDSObjId > 0) {
query += " AND data_source_obj_id = " + filteringDSObjId;
}
return query;
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java
index 97b0fd8216..5a3476f896 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java
@@ -28,6 +28,7 @@ import org.openide.util.Utilities;
import org.sleuthkit.autopsy.actions.AddContentTagAction;
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
+import org.sleuthkit.autopsy.directorytree.ExportCSVAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
import org.sleuthkit.autopsy.directorytree.ViewContextAction;
@@ -89,6 +90,7 @@ public class DirectoryNode extends AbstractFsContentNode {
actionsList.add(ViewFileInTimelineAction.createViewFileAction(content));
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
+ actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(new RunIngestModulesAction(content));
actionsList.add(null); // creates a menu separator
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java
index 83fcee1b65..7285b2cb8d 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java
@@ -28,7 +28,6 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
@@ -40,7 +39,6 @@ import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
-import org.sleuthkit.autopsy.casemodule.CasePreferences;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
@@ -88,7 +86,7 @@ public class EmailExtracted implements AutopsyVisitableItem {
}
private SleuthkitCase skCase;
private final EmailResults emailResults;
- private final long datasourceObjId;
+ private final long filteringDSObjId; // 0 if not filtering/grouping by data source
@@ -110,7 +108,7 @@ public class EmailExtracted implements AutopsyVisitableItem {
*/
public EmailExtracted(SleuthkitCase skCase, long objId) {
this.skCase = skCase;
- this.datasourceObjId = objId;
+ this.filteringDSObjId = objId;
emailResults = new EmailResults();
}
@@ -162,8 +160,8 @@ public class EmailExtracted implements AutopsyVisitableItem {
+ "attribute_type_id=" + pathAttrId //NON-NLS
+ " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id" //NON-NLS
+ " AND blackboard_artifacts.artifact_type_id=" + artId; //NON-NLS
- if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) {
- query += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId;
+ if (filteringDSObjId > 0) {
+ query += " AND blackboard_artifacts.data_source_obj_id = " + filteringDSObjId;
}
try (CaseDbQuery dbQuery = skCase.executeQuery(query)) {
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java
index a21afff190..a51e36eb87 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java
@@ -26,7 +26,6 @@ import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
-import java.util.Objects;
import java.util.logging.Level;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
@@ -35,7 +34,6 @@ import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
-import org.sleuthkit.autopsy.casemodule.CasePreferences;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
@@ -64,7 +62,7 @@ public class ExtractedContent implements AutopsyVisitableItem {
private SleuthkitCase skCase; // set to null after case has been closed
private Blackboard blackboard;
public static final String NAME = NbBundle.getMessage(RootNode.class, "ExtractedContentNode.name.text");
- private final long datasourceObjId;
+ private final long filteringDSObjId; // 0 if not filtering/grouping by data source
/**
* Constructs extracted content object
@@ -83,7 +81,7 @@ public class ExtractedContent implements AutopsyVisitableItem {
*/
public ExtractedContent(SleuthkitCase skCase, long objId) {
this.skCase = skCase;
- this.datasourceObjId = objId;
+ this.filteringDSObjId = objId;
this.blackboard = skCase.getBlackboard();
}
@@ -307,8 +305,8 @@ public class ExtractedContent implements AutopsyVisitableItem {
protected boolean createKeys(List list) {
if (skCase != null) {
try {
- List types = Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true) ?
- blackboard.getArtifactTypesInUse(datasourceObjId) :
+ List types = (filteringDSObjId > 0) ?
+ blackboard.getArtifactTypesInUse(filteringDSObjId) :
skCase.getArtifactTypesInUse() ;
types.removeAll(doNotShow);
@@ -372,8 +370,8 @@ public class ExtractedContent implements AutopsyVisitableItem {
// a performance increase might be had by adding a
// "getBlackboardArtifactCount()" method to skCase
try {
- this.childCount = Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true) ?
- blackboard.getArtifactsCount(type.getTypeID(), datasourceObjId) :
+ this.childCount = (filteringDSObjId > 0) ?
+ blackboard.getArtifactsCount(type.getTypeID(), filteringDSObjId) :
skCase.getBlackboardArtifactsTypeCount(type.getTypeID());
} catch (TskException ex) {
Logger.getLogger(TypeNode.class.getName())
@@ -501,8 +499,8 @@ public class ExtractedContent implements AutopsyVisitableItem {
protected List makeKeys() {
if (skCase != null) {
try {
- return Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)
- ? blackboard.getArtifacts(type.getTypeID(), datasourceObjId)
+ return (filteringDSObjId > 0)
+ ? blackboard.getArtifacts(type.getTypeID(), filteringDSObjId)
: skCase.getBlackboardArtifacts(type.getTypeID());
} catch (TskException ex) {
Logger.getLogger(ArtifactFactory.class.getName()).log(Level.SEVERE, "Couldn't get blackboard artifacts from database", ex); //NON-NLS
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java
index 1695b253b0..7b3a55c20b 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java
@@ -32,6 +32,7 @@ import org.sleuthkit.autopsy.actions.AddContentTagAction;
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.directorytree.ExportCSVAction;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
@@ -173,6 +174,7 @@ public class FileNode extends AbstractFsContentNode {
actionsList.add(null); // Creates an item separator
actionsList.add(ExtractAction.getInstance());
+ actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // Creates an item separator
actionsList.add(AddContentTagAction.getInstance());
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java
index e9c49c596e..9cceeb5a29 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java
@@ -24,7 +24,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
-import java.util.Objects;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
@@ -37,9 +36,7 @@ import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
-import org.sleuthkit.autopsy.casemodule.CasePreferences;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
-import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.datamodel.AbstractFile;
@@ -63,7 +60,7 @@ import org.sleuthkit.datamodel.VirtualDirectory;
public class FileSize implements AutopsyVisitableItem {
private SleuthkitCase skCase;
- private final long datasourceObjId;
+ private final long filteringDSObjId; // 0 if not filtering/grouping by data source
public enum FileSizeFilter implements AutopsyVisitableItem {
@@ -105,7 +102,7 @@ public class FileSize implements AutopsyVisitableItem {
public FileSize(SleuthkitCase skCase, long dsObjId) {
this.skCase = skCase;
- this.datasourceObjId = dsObjId;
+ this.filteringDSObjId = dsObjId;
}
@Override
@@ -118,7 +115,7 @@ public class FileSize implements AutopsyVisitableItem {
}
long filteringDataSourceObjId() {
- return this.datasourceObjId;
+ return this.filteringDSObjId;
}
/*
* Root node. Children are nodes for specific sizes.
@@ -437,7 +434,7 @@ public class FileSize implements AutopsyVisitableItem {
query = query + " AND (type != " + TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS.getFileType() + ")"; //NON-NLS
// filter by datasource if indicated in case preferences
- if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) {
+ if (filteringDSObjId > 0) {
query += " AND data_source_obj_id = " + filteringDSObjId;
}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java
index b3005c28b4..34ec74280a 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java
@@ -24,7 +24,6 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
-import java.util.Objects;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
@@ -39,7 +38,6 @@ import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
-import org.sleuthkit.autopsy.casemodule.CasePreferences;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger;
@@ -366,7 +364,7 @@ public final class FileTypesByExtension implements AutopsyVisitableItem {
+ (UserPreferences.hideKnownFilesInViewsTree()
? " AND (known IS NULL OR known != " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")"
: " ")
- + (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)
+ + (filteringDataSourceObjId() > 0
? " AND data_source_obj_id = " + filteringDataSourceObjId()
: " ")
+ " AND (extension IN (" + filter.getFilter().stream()
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java
index 1e4f61fc87..86cc42aa8c 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java
@@ -28,7 +28,6 @@ import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
@@ -42,7 +41,6 @@ import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
-import org.sleuthkit.autopsy.casemodule.CasePreferences;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import static org.sleuthkit.autopsy.core.UserPreferences.hideKnownFilesInViewsTree;
import static org.sleuthkit.autopsy.core.UserPreferences.hideSlackFilesInViewsTree;
@@ -103,7 +101,7 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi
+ TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL.ordinal()
+ (hideSlackFilesInViewsTree() ? "" : ("," + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.ordinal()))
+ "))"
- + ( Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true) ? " AND data_source_obj_id = " + this.filteringDataSourceObjId() : " ")
+ + ( (filteringDataSourceObjId() > 0) ? " AND data_source_obj_id = " + this.filteringDataSourceObjId() : " ")
+ (hideKnownFilesInViewsTree() ? (" AND (known IS NULL OR known != " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")") : "");
}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java b/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java
new file mode 100644
index 0000000000..bcdca8632e
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java
@@ -0,0 +1,129 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2019 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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.datamodel;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.logging.Level;
+import org.openide.util.NbBundle.Messages;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance.Type;
+import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil;
+import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
+import org.sleuthkit.autopsy.core.UserPreferences;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.events.AutopsyEvent;
+import org.sleuthkit.datamodel.AbstractFile;
+import org.sleuthkit.datamodel.BlackboardArtifact;
+import org.sleuthkit.datamodel.Tag;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ * Background task to get Score, Comment and Occurrences values for an Abstract
+ * content node.
+ *
+ */
+class GetSCOTask implements Runnable {
+
+ private final WeakReference> weakNodeRef;
+ private final PropertyChangeListener listener;
+ private static final Logger logger = Logger.getLogger(GetSCOTask.class.getName());
+
+ GetSCOTask(WeakReference> weakContentRef, PropertyChangeListener listener) {
+ this.weakNodeRef = weakContentRef;
+ this.listener = listener;
+ }
+
+ @Messages({"GetSCOTask.occurrences.defaultDescription=No correlation properties found",
+ "GetSCOTask.occurrences.multipleProperties=Multiple different correlation properties exist for this result"})
+ @Override
+ public void run() {
+ AbstractContentNode> contentNode = weakNodeRef.get();
+
+ //Check for stale reference
+ if (contentNode == null) {
+ return;
+ }
+
+ // get the SCO column values
+ List tags = contentNode.getAllTagsFromDatabase();
+ CorrelationAttributeInstance fileAttribute = contentNode.getCorrelationAttributeInstance();
+
+ SCOData scoData = new SCOData();
+ scoData.setScoreAndDescription(contentNode.getScorePropertyAndDescription(tags));
+ scoData.setComment(contentNode.getCommentProperty(tags, fileAttribute));
+
+ if (!UserPreferences.hideCentralRepoCommentsAndOccurrences()) {
+ Type type = null;
+ String value = null;
+ String description = Bundle.GetSCOTask_occurrences_defaultDescription();
+ if (contentNode instanceof BlackboardArtifactNode) {
+ BlackboardArtifact bbArtifact = ((BlackboardArtifactNode) contentNode).getArtifact();
+ //for specific artifact types we still want to display information for the file instance correlation attribute
+ if (bbArtifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID()
+ || bbArtifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED.getTypeID()
+ || bbArtifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID()
+ || bbArtifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID()
+ || bbArtifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()
+ || bbArtifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_OBJECT_DETECTED.getTypeID()
+ || bbArtifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED.getTypeID()
+ || bbArtifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) {
+ try {
+ if (bbArtifact.getParent() instanceof AbstractFile) {
+ type = CorrelationAttributeInstance.getDefaultCorrelationTypes().get(CorrelationAttributeInstance.FILES_TYPE_ID);
+ value = ((AbstractFile) bbArtifact.getParent()).getMd5Hash();
+ }
+ } catch (TskCoreException | EamDbException ex) {
+ logger.log(Level.WARNING, "Unable to get correlation type or value to determine value for O column for artifact", ex);
+ }
+ } else {
+ List listOfPossibleAttributes = EamArtifactUtil.makeInstancesFromBlackboardArtifact(bbArtifact, false);
+ if (listOfPossibleAttributes.size() > 1) {
+ //Don't display anything if there is more than 1 correlation property for an artifact but let the user know
+ description = Bundle.GetSCOTask_occurrences_multipleProperties();
+ } else if (!listOfPossibleAttributes.isEmpty()) {
+ //there should only be one item in the list
+ type = listOfPossibleAttributes.get(0).getCorrelationType();
+ value = listOfPossibleAttributes.get(0).getCorrelationValue();
+ }
+ }
+ } else if (contentNode.getContent() instanceof AbstractFile) {
+ //use the file instance correlation attribute if the node is not a BlackboardArtifactNode
+ try {
+ type = CorrelationAttributeInstance.getDefaultCorrelationTypes().get(CorrelationAttributeInstance.FILES_TYPE_ID);
+ value = ((AbstractFile) contentNode.getContent()).getMd5Hash();
+ } catch (EamDbException ex) {
+ logger.log(Level.WARNING, "Unable to get correlation type to determine value for O column for file", ex);
+ }
+ }
+ scoData.setCountAndDescription(contentNode.getCountPropertyAndDescription(type, value, description));
+ }
+
+ // signal SCO data is available.
+ if (listener
+ != null) {
+ listener.propertyChange(new PropertyChangeEvent(
+ AutopsyEvent.SourceType.LOCAL.toString(),
+ AbstractAbstractFileNode.NodeSpecificEvents.SCO_AVAILABLE.toString(),
+ null, scoData));
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java
index 9d6ed53431..03c72d2d2e 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java
@@ -30,7 +30,6 @@ import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
@@ -42,7 +41,6 @@ import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
-import org.sleuthkit.autopsy.casemodule.CasePreferences;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
@@ -65,7 +63,7 @@ public class HashsetHits implements AutopsyVisitableItem {
private static final Logger logger = Logger.getLogger(HashsetHits.class.getName());
private SleuthkitCase skCase;
private final HashsetResults hashsetResults;
- private final long datasourceObjId;
+ private final long filteringDSObjId; // 0 if not filtering/grouping by data source
/**
@@ -87,7 +85,7 @@ public class HashsetHits implements AutopsyVisitableItem {
*/
public HashsetHits(SleuthkitCase skCase, long objId) {
this.skCase = skCase;
- this.datasourceObjId = objId;
+ this.filteringDSObjId = objId;
hashsetResults = new HashsetResults();
}
@@ -142,8 +140,8 @@ public class HashsetHits implements AutopsyVisitableItem {
+ "attribute_type_id=" + setNameId //NON-NLS
+ " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id" //NON-NLS
+ " AND blackboard_artifacts.artifact_type_id=" + artId; //NON-NLS
- if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) {
- query += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId;
+ if (filteringDSObjId > 0) {
+ query += " AND blackboard_artifacts.data_source_obj_id = " + filteringDSObjId;
}
try (CaseDbQuery dbQuery = skCase.executeQuery(query)) {
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java
index 584a405fcc..da82c315b5 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java
@@ -28,12 +28,15 @@ import java.util.EnumSet;
import java.util.List;
import java.util.logging.Level;
import javax.swing.Action;
+import org.apache.commons.lang3.tuple.Pair;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.datasourcesummary.ViewSummaryInformationAction;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
+import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor;
import org.sleuthkit.autopsy.directorytree.FileSearchAction;
@@ -47,6 +50,7 @@ import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.VirtualDirectory;
import org.sleuthkit.autopsy.datamodel.BaseChildFactory.NoSuchEventBusException;
+import org.sleuthkit.datamodel.Tag;
/**
* This class is used to represent the "Node" for the image. The children of
@@ -126,7 +130,7 @@ public class ImageNode extends AbstractContentNode {
"ImageNode.createSheet.type.text=Image",
"ImageNode.createSheet.sectorSize.name=Sector Size (Bytes)",
"ImageNode.createSheet.sectorSize.displayName=Sector Size (Bytes)",
- "ImageNode.createSheet.sectorSize.desc=Sector size of the image in bytes.",
+ "ImageNode.createSheet.sectorSize.desc=Sector size of the image in bytes.",
"ImageNode.createSheet.timezone.name=Timezone",
"ImageNode.createSheet.timezone.displayName=Timezone",
"ImageNode.createSheet.timezone.desc=Timezone of the image",
@@ -250,4 +254,75 @@ public class ImageNode extends AbstractContentNode {
}
};
+ /**
+ * Reads and returns a list of all tags associated with this content node.
+ *
+ * Null implementation of an abstract method.
+ *
+ * @return list of tags associated with the node.
+ */
+ @Override
+ protected List getAllTagsFromDatabase() {
+ return new ArrayList<>();
+ }
+
+ /**
+ * Returns correlation attribute instance for the underlying content of the
+ * node.
+ *
+ * Null implementation of an abstract method.
+ *
+ * @return correlation attribute instance for the underlying content of the
+ * node.
+ */
+ @Override
+ protected CorrelationAttributeInstance getCorrelationAttributeInstance() {
+ return null;
+ }
+
+ /**
+ * Returns Score property for the node.
+ *
+ * Null implementation of an abstract method.
+ *
+ * @param tags list of tags.
+ *
+ * @return Score property for the underlying content of the node.
+ */
+ @Override
+ protected Pair getScorePropertyAndDescription(List tags) {
+ return Pair.of(DataResultViewerTable.Score.NO_SCORE, NO_DESCR);
+ }
+
+ /**
+ * Returns comment property for the node.
+ *
+ * Null implementation of an abstract method.
+ *
+ * @param tags list of tags
+ * @param attribute correlation attribute instance
+ *
+ * @return Comment property for the underlying content of the node.
+ */
+ @Override
+ protected DataResultViewerTable.HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) {
+ return DataResultViewerTable.HasCommentStatus.NO_COMMENT;
+ }
+
+ /**
+ * Returns occurrences/count property for the node.
+ *
+ * Null implementation of an abstract method.
+ *
+ * @param attributeType the type of the attribute to count
+ * @param attributeValue the value of the attribute to coun
+ * @param defaultDescription a description to use when none is determined by
+ * the getCountPropertyAndDescription method
+ *
+ * @return count property for the underlying content of the node.
+ */
+ @Override
+ protected Pair getCountPropertyAndDescription(CorrelationAttributeInstance.Type attributeType, String attributeValue, String defaultDescription) {
+ return Pair.of(-1L, NO_DESCR);
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java
index c36833992c..482ebf1558 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java
@@ -30,7 +30,6 @@ import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
@@ -42,7 +41,6 @@ import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
-import org.sleuthkit.autopsy.casemodule.CasePreferences;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
@@ -61,7 +59,7 @@ public class InterestingHits implements AutopsyVisitableItem {
private static final Logger logger = Logger.getLogger(InterestingHits.class.getName());
private SleuthkitCase skCase;
private final InterestingResults interestingResults = new InterestingResults();
- private final long datasourceObjId;
+ private final long filteringDSObjId; // 0 if not filtering/grouping by data source
/**
* Constructor
@@ -82,7 +80,7 @@ public class InterestingHits implements AutopsyVisitableItem {
*/
public InterestingHits(SleuthkitCase skCase, long objId) {
this.skCase = skCase;
- this.datasourceObjId = objId;
+ this.filteringDSObjId = objId;
interestingResults.update();
}
@@ -133,8 +131,8 @@ public class InterestingHits implements AutopsyVisitableItem {
+ "attribute_type_id=" + setNameId //NON-NLS
+ " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id" //NON-NLS
+ " AND blackboard_artifacts.artifact_type_id=" + artId; //NON-NLS
- if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) {
- query += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId;
+ if (filteringDSObjId > 0) {
+ query += " AND blackboard_artifacts.data_source_obj_id = " + filteringDSObjId;
}
try (CaseDbQuery dbQuery = skCase.executeQuery(query)) {
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java
index cb24744b0c..655f0c1973 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java
@@ -31,7 +31,6 @@ import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
@@ -45,7 +44,6 @@ import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
-import org.sleuthkit.autopsy.casemodule.CasePreferences;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import static org.sleuthkit.autopsy.datamodel.Bundle.*;
@@ -76,7 +74,7 @@ public class KeywordHits implements AutopsyVisitableItem {
private SleuthkitCase skCase;
private final KeywordResults keywordResults;
- private final long datasourceObjId;
+ private final long filteringDSObjId; // 0 if not filtering/grouping by data source
/**
* String used in the instance MAP so that exact matches and substring can
@@ -123,7 +121,7 @@ public class KeywordHits implements AutopsyVisitableItem {
*/
public KeywordHits(SleuthkitCase skCase, long objId) {
this.skCase = skCase;
- this.datasourceObjId = objId;
+ this.filteringDSObjId = objId;
keywordResults = new KeywordResults();
}
@@ -344,8 +342,8 @@ public class KeywordHits implements AutopsyVisitableItem {
}
String queryStr = KEYWORD_HIT_ATTRIBUTES_QUERY;
- if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) {
- queryStr += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId;
+ if (filteringDSObjId > 0) {
+ queryStr += " AND blackboard_artifacts.data_source_obj_id = " + filteringDSObjId;
}
try (CaseDbQuery dbQuery = skCase.executeQuery(queryStr)) {
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java
index bfd8f7bd91..41ecad0340 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java
@@ -29,6 +29,7 @@ import org.openide.util.Utilities;
import org.sleuthkit.autopsy.actions.AddContentTagAction;
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
+import org.sleuthkit.autopsy.directorytree.ExportCSVAction;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
@@ -105,6 +106,7 @@ public class LayoutFileNode extends AbstractAbstractFileNode {
}
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
+ actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java
index 6d64aeb621..4a62cd9e4f 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java
@@ -31,6 +31,7 @@ import org.sleuthkit.autopsy.actions.AddContentTagAction;
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.directorytree.ExportCSVAction;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
@@ -82,6 +83,7 @@ public class LocalFileNode extends AbstractAbstractFileNode {
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
+ actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java
index 66291955a8..c5d77692c8 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildren.java
@@ -25,6 +25,7 @@ import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.datamodel.accounts.Accounts;
+import org.sleuthkit.datamodel.SleuthkitVisitableItem;
/**
* Children implementation for the root node of a ContentNode tree. Accepts a
@@ -34,6 +35,7 @@ public class RootContentChildren extends Children.Keys