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