diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties
index baffc3ee43..3c7409d6b8 100644
--- a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties
@@ -54,3 +54,6 @@ ObjectDetectedFilterPanel.text=Object Detected:
DetailsPanel.instancesList.border.title=Instances
DateFilterPanel.mostRecentRadioButton.text=Only last:
DateFilterPanel.dateFilterCheckBox.text=Date Filter:
+DomainSummaryPanel.activityLabel.text=
+DomainSummaryPanel.pagesLabel.text=
+DomainSummaryPanel.filesDownloadedLabel.text=
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.form
new file mode 100644
index 0000000000..db3cda4dfe
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.form
@@ -0,0 +1,115 @@
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.java
new file mode 100644
index 0000000000..bcbbd93e43
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.java
@@ -0,0 +1,172 @@
+/*
+ * Autopsy
+ *
+ * Copyright 2020 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.discovery.ui;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Point;
+import java.awt.event.MouseEvent;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import javax.swing.GroupLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JComponent;
+import javax.swing.JList;
+import javax.swing.ListCellRenderer;
+import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.coreutils.ImageUtils;
+
+/**
+ * Class which displays a preview and details about a document.
+ */
+class DomainSummaryPanel extends javax.swing.JPanel implements ListCellRenderer {
+
+ private static final long serialVersionUID = 1L;
+ private static final Color SELECTION_COLOR = new Color(0, 120, 215);
+ private static final int MAX_NAME_STRING = 90;
+ private static final SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd yyyy");
+
+ /**
+ * Creates new form DocumentPanel.
+ */
+ DomainSummaryPanel() {
+ initComponents();
+ domainNameLabel.setFont(domainNameLabel.getFont().deriveFont(domainNameLabel.getFont().getSize() + 6));
+ }
+
+ /**
+ * 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
+ * regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ domainNameLabel = new javax.swing.JLabel();
+ sampleImageLabel = new javax.swing.JLabel();
+ numberOfImagesLabel = new javax.swing.JLabel();
+ activityLabel = new javax.swing.JLabel();
+ pagesLabel = new javax.swing.JLabel();
+ filesDownloadedLabel = new javax.swing.JLabel();
+
+ setBorder(javax.swing.BorderFactory.createEtchedBorder());
+
+ sampleImageLabel.setBorder(javax.swing.BorderFactory.createEtchedBorder());
+ sampleImageLabel.setIconTextGap(0);
+ sampleImageLabel.setMaximumSize(new java.awt.Dimension(100, 100));
+ sampleImageLabel.setMinimumSize(new java.awt.Dimension(100, 100));
+ sampleImageLabel.setPreferredSize(new java.awt.Dimension(100, 100));
+
+ org.openide.awt.Mnemonics.setLocalizedText(activityLabel, org.openide.util.NbBundle.getMessage(DomainSummaryPanel.class, "DomainSummaryPanel.activityLabel.text")); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(pagesLabel, org.openide.util.NbBundle.getMessage(DomainSummaryPanel.class, "DomainSummaryPanel.pagesLabel.text")); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(filesDownloadedLabel, org.openide.util.NbBundle.getMessage(DomainSummaryPanel.class, "DomainSummaryPanel.filesDownloadedLabel.text")); // NOI18N
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(domainNameLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 586, Short.MAX_VALUE)
+ .addComponent(activityLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(pagesLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(filesDownloadedLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
+ .addComponent(numberOfImagesLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(sampleImageLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ .addContainerGap())
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addComponent(numberOfImagesLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 17, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(sampleImageLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addContainerGap())
+ .addGroup(layout.createSequentialGroup()
+ .addComponent(domainNameLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 32, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(activityLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(pagesLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(filesDownloadedLabel)
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
+ );
+ }// //GEN-END:initComponents
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JLabel activityLabel;
+ private javax.swing.JLabel domainNameLabel;
+ private javax.swing.JLabel filesDownloadedLabel;
+ private javax.swing.JLabel numberOfImagesLabel;
+ private javax.swing.JLabel pagesLabel;
+ private javax.swing.JLabel sampleImageLabel;
+ // End of variables declaration//GEN-END:variables
+
+ @NbBundle.Messages({"# {0} - startDate",
+ "# {1} - endDate",
+ "DomainSummaryPanel.activity.text=Activity: {0} to {1}",
+ "DomainSummaryPanel.pages.text=Pages in past 60 days: ",
+ "DomainSummaryPanel.downloads.text=Files downloaded: "})
+ @Override
+ public Component getListCellRendererComponent(JList extends DomainWrapper> list, DomainWrapper value, int index, boolean isSelected, boolean cellHasFocus) {
+ domainNameLabel.setText(value.getResultDomain().getDomain());
+ String startDate = dateFormat.format(new Date(value.getResultDomain().getActivityStart()*1000));
+ String endDate =dateFormat.format(new Date(value.getResultDomain().getActivityEnd()*1000));
+ activityLabel.setText(Bundle.DomainSummaryPanel_activity_text(startDate, endDate));
+ pagesLabel.setText(Bundle.DomainSummaryPanel_pages_text() + value.getResultDomain().getVisitsInLast60());
+ filesDownloadedLabel.setText(Bundle.DomainSummaryPanel_downloads_text() + value.getResultDomain().getFilesDownloaded());
+ if (value.getThumbnail() == null) {
+ numberOfImagesLabel.setText(Bundle.DocumentPanel_numberOfImages_noImages());
+ sampleImageLabel.setIcon(new ImageIcon(ImageUtils.getDefaultThumbnail()));
+ }
+ setBackground(isSelected ? SELECTION_COLOR : list.getBackground());
+ return this;
+ }
+
+ @Override
+ public String getToolTipText(MouseEvent event) {
+ if (event != null) {
+ //gets tooltip of internal panel item mouse is over
+ Point point = event.getPoint();
+ for (Component comp : getComponents()) {
+ if (DiscoveryUiUtils.isPointOnIcon(comp, point)) {
+ String toolTip = ((JComponent) comp).getToolTipText();
+ if (toolTip == null || toolTip.isEmpty()) {
+ return null;
+ } else {
+ return toolTip;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.form
new file mode 100644
index 0000000000..715f837f05
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.form
@@ -0,0 +1,43 @@
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.java
new file mode 100644
index 0000000000..068a47de31
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryViewer.java
@@ -0,0 +1,86 @@
+/*
+ * Autopsy
+ *
+ * Copyright 2020 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.discovery.ui;
+
+import javax.swing.DefaultListModel;
+
+/**
+ *
+ * @author wschaefer
+ */
+public class DomainSummaryViewer extends javax.swing.JPanel {
+
+ private static final long serialVersionUID = 1L;
+
+ private final DefaultListModel domainListModel = new DefaultListModel<>();
+
+ /**
+ * Clear the list of documents being displayed.
+ */
+ void clearViewer() {
+ synchronized (this) {
+ domainListModel.removeAllElements();
+ domainScrollPane.getVerticalScrollBar().setValue(0);
+ }
+ }
+
+ /**
+ * Creates new form DomainSummaryPanel
+ */
+ public DomainSummaryViewer() {
+ initComponents();
+ }
+
+ /**
+ * 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
+ * regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ domainScrollPane = new javax.swing.JScrollPane();
+ domainList = new javax.swing.JList<>();
+
+ setLayout(new java.awt.BorderLayout());
+
+ domainScrollPane.setViewportView(domainList);
+
+ add(domainScrollPane, java.awt.BorderLayout.CENTER);
+ }// //GEN-END:initComponents
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JList domainList;
+ private javax.swing.JScrollPane domainScrollPane;
+ // End of variables declaration//GEN-END:variables
+
+ /**
+ * Add the summary for a domain to the panel.
+ *
+ * @param domainWrapper The object which contains the domain summary which
+ * will be displayed.
+ */
+ void addDomain(DomainWrapper domainWrapper) {
+ synchronized (this) {
+ domainListModel.addElement(domainWrapper);
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainWrapper.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainWrapper.java
new file mode 100644
index 0000000000..47ae242360
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainWrapper.java
@@ -0,0 +1,72 @@
+/*
+ * Autopsy
+ *
+ * Copyright 2020 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.discovery.ui;
+
+import java.awt.Image;
+import org.sleuthkit.autopsy.discovery.search.ResultDomain;
+
+/**
+ *
+ * @author wschaefer
+ */
+public class DomainWrapper {
+
+ private ResultDomain domain;
+ private Image thumbnail = null;
+
+ /**
+ * Construct a new DocumentWrapper.
+ *
+ * @param file The ResultFile which represents the document which the
+ * summary is created for.
+ */
+ DomainWrapper(ResultDomain domain) {
+ this.domain = domain;
+ }
+
+ /**
+ * Set the thumbnail which exists.
+ *
+ * @param thumbnail The Image object which will be used to represent this
+ * domain object.
+ */
+ void setThumnail(Image thumbnail) {
+ this.thumbnail = thumbnail;
+ }
+
+ /**
+ * Get the ResultDomain which represents the Domain the summary was created
+ * for.
+ *
+ * @return The ResultDomain which represents the domain attribute which the
+ * summary was created for.
+ */
+ ResultDomain getResultDomain() {
+ return domain;
+ }
+
+ /**
+ * Get the image to be used for the domain.
+ *
+ * @return The Image which represents the domain.
+ */
+ Image getThumbnail() {
+ return thumbnail;
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ResultsPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ResultsPanel.java
index 64f68d5515..702f4cedf8 100644
--- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ResultsPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ResultsPanel.java
@@ -46,6 +46,7 @@ import org.sleuthkit.autopsy.discovery.search.FileSearch;
import org.sleuthkit.autopsy.discovery.search.SearchData;
import org.sleuthkit.autopsy.discovery.search.ResultsSorter;
import org.sleuthkit.autopsy.discovery.search.Result;
+import org.sleuthkit.autopsy.discovery.search.ResultDomain;
import org.sleuthkit.autopsy.discovery.search.ResultFile;
import org.sleuthkit.autopsy.textsummarizer.TextSummary;
@@ -60,6 +61,7 @@ final class ResultsPanel extends javax.swing.JPanel {
private final VideoThumbnailViewer videoThumbnailViewer;
private final ImageThumbnailViewer imageThumbnailViewer;
private final DocumentPreviewViewer documentPreviewViewer;
+ private final DomainSummaryViewer domainSummaryViewer;
private List searchFilters;
private DiscoveryAttributes.AttributeType groupingAttribute;
private Group.GroupSortingAlgorithm groupSort;
@@ -82,6 +84,7 @@ final class ResultsPanel extends javax.swing.JPanel {
imageThumbnailViewer = new ImageThumbnailViewer();
videoThumbnailViewer = new VideoThumbnailViewer();
documentPreviewViewer = new DocumentPreviewViewer();
+ domainSummaryViewer = new DomainSummaryViewer();
videoThumbnailViewer.addListSelectionListener((e) -> {
if (resultType == SearchData.Type.VIDEO) {
if (!e.getValueIsAdjusting()) {
@@ -116,6 +119,7 @@ final class ResultsPanel extends javax.swing.JPanel {
}
}
});
+ //JIRA-TODO
}
/**
@@ -182,6 +186,8 @@ final class ResultsPanel extends javax.swing.JPanel {
resultsViewerPanel.add(documentPreviewViewer);
break;
case DOMAIN:
+ populateDomainViewer(pageRetrievedEvent.getSearchResults());
+ resultsViewerPanel.add(domainSummaryViewer);
break;
default:
break;
@@ -201,6 +207,7 @@ final class ResultsPanel extends javax.swing.JPanel {
resultsViewerPanel.remove(imageThumbnailViewer);
resultsViewerPanel.remove(videoThumbnailViewer);
resultsViewerPanel.remove(documentPreviewViewer);
+ resultsViewerPanel.remove(domainSummaryViewer);
//cancel any unfished thumb workers
for (SwingWorker thumbWorker : resultContentWorkers) {
if (!thumbWorker.isDone()) {
@@ -212,13 +219,14 @@ final class ResultsPanel extends javax.swing.JPanel {
videoThumbnailViewer.clearViewer();
imageThumbnailViewer.clearViewer();
documentPreviewViewer.clearViewer();
+ domainSummaryViewer.clearViewer();
}
/**
* Populate the video thumbnail viewer, cancelling any thumbnails which are
* currently being created first.
*
- * @param files The list of ResultFiles to populate the video viewer with.
+ * @param results The list of ResultFiles to populate the video viewer with.
*/
synchronized void populateVideoViewer(List results) {
for (Result result : results) {
@@ -233,7 +241,7 @@ final class ResultsPanel extends javax.swing.JPanel {
* Populate the image thumbnail viewer, cancelling any thumbnails which are
* currently being created first.
*
- * @param files The list of ResultFiles to populate the image viewer with.
+ * @param results The list of ResultFiles to populate the image viewer with.
*/
synchronized void populateImageViewer(List results) {
for (Result result : results) {
@@ -248,7 +256,7 @@ final class ResultsPanel extends javax.swing.JPanel {
* Populate the document preview viewer, cancelling any content which is
* currently being created first.
*
- * @param files The list of ResultFiles to populate the image viewer with.
+ * @param results The list of ResultFiles to populate the document viewer with.
*/
synchronized void populateDocumentViewer(List results) {
for (Result result : results) {
@@ -259,6 +267,21 @@ final class ResultsPanel extends javax.swing.JPanel {
}
}
+ /**
+ * Populate the domain summary viewer, cancelling any content which is
+ * currently being created first.
+ *
+ * @param results The list of ResultDomains to populate the domain summary viewer with.
+ */
+ synchronized void populateDomainViewer(List results) {
+ for (Result result : results) {
+ DomainThumbnailWorker domainWorker = new DomainThumbnailWorker((ResultDomain) result);
+ domainWorker.execute();
+ //keep track of thumb worker for possible cancelation
+ resultContentWorkers.add(domainWorker);
+ }
+ }
+
/**
* Subscribe and respond to GroupSelectedEvents.
*
@@ -293,6 +316,7 @@ final class ResultsPanel extends javax.swing.JPanel {
videoThumbnailViewer.clearViewer();
imageThumbnailViewer.clearViewer();
documentPreviewViewer.clearViewer();
+ domainSummaryViewer.clearViewer();
resultsViewerPanel.revalidate();
resultsViewerPanel.repaint();
});
@@ -763,4 +787,44 @@ final class ResultsPanel extends javax.swing.JPanel {
}
+ /**
+ * Swing worker to handle the retrieval of domain thumbnails and population
+ * of the Domain Summary Viewer.
+ */
+ private class DomainThumbnailWorker extends SwingWorker {
+
+ private final DomainWrapper domainWrapper;
+
+ /**
+ * Construct a new DomainThumbnailWorker.
+ *
+ * @param file The ResultFile which represents the domain attribute the
+ * preview is being retrieved for.
+ */
+ DomainThumbnailWorker(ResultDomain domain) {
+ domainWrapper = new DomainWrapper(domain);
+ domainSummaryViewer.addDomain(domainWrapper);
+ }
+
+ @Override
+ protected Void doInBackground() throws Exception {
+ domainWrapper.setThumnail(null);
+ return null;
+ }
+
+ @Override
+ protected void done() {
+ try {
+ get();
+ } catch (InterruptedException | ExecutionException ex) {
+ domainWrapper.setThumnail(null);
+ logger.log(Level.WARNING, "Document Worker Exception", ex);
+ } catch (CancellationException ignored) {
+ domainWrapper.setThumnail(null);
+ //we want to do nothing in response to this since we allow it to be cancelled
+ }
+ documentPreviewViewer.repaint();
+ }
+
+ }
}