diff --git a/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties-MERGED index 1ebe3776ba..af49cf14a4 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties-MERGED @@ -117,6 +117,10 @@ FileSorter.SortingMethod.filetype.displayName=By file type FileSorter.SortingMethod.frequency.displayName=By central repo frequency FileSorter.SortingMethod.keywordlist.displayName=By keyword list names FileSorter.SortingMethod.parent.displayName=By parent path +# {0} - numberOfInstances +ImageThumbnailPanel.countLabel.text=Number of Instances: {0} +# {0} - fileSize +ImageThumbnailPanel.sizeLabel.text=Size: {0} bytes ResultsDialog.dialogTitle.text=File search results ResultsDialog.exitButton.text=Exit ResultsDialog.searchButton.text=Run another search diff --git a/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java b/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java index 5d269629f8..fcabca4238 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java @@ -116,7 +116,7 @@ class FileSearch { // Sort and group the results searchResults.sortGroupsAndFiles(); - LinkedHashMap> resultHashMap = searchResults.toLinkedHashMap(); + Map> resultHashMap = searchResults.toLinkedHashMap(); for (String groupName : resultHashMap.keySet()) { groupCache.put(groupName, resultHashMap.get(groupName)); } @@ -251,7 +251,7 @@ class FileSearch { // Collect everything in the search results SearchResults searchResults = new SearchResults(groupSortingType, groupAttributeType, fileSortingMethod); searchResults.add(resultFiles); - LinkedHashMap> resultHashMap = searchResults.toLinkedHashMap(); + Map> resultHashMap = searchResults.toLinkedHashMap(); for (String groupName : resultHashMap.keySet()) { groupCache.put(groupName, resultHashMap.get(groupName)); } diff --git a/Core/src/org/sleuthkit/autopsy/filequery/ImageThumbnailPanel.form b/Core/src/org/sleuthkit/autopsy/filequery/ImageThumbnailPanel.form new file mode 100644 index 0000000000..bc2f036c23 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/filequery/ImageThumbnailPanel.form @@ -0,0 +1,67 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/filequery/ImageThumbnailPanel.java b/Core/src/org/sleuthkit/autopsy/filequery/ImageThumbnailPanel.java new file mode 100644 index 0000000000..d73a6e4d0c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/filequery/ImageThumbnailPanel.java @@ -0,0 +1,105 @@ +/* + * Autopsy + * + * 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.filequery; + +import java.awt.Color; +import java.awt.Component; +import javax.swing.ImageIcon; +import javax.swing.JList; +import javax.swing.ListCellRenderer; +import org.openide.util.NbBundle; + +/** + * Class which displays a thumbnail and information for an image file. + */ +public class ImageThumbnailPanel extends javax.swing.JPanel implements ListCellRenderer { + + private static final long serialVersionUID = 1L; + private static final Color SELECTION_COLOR = new Color(0, 120, 215); + + /** + * Creates new form ImageThumbnailPanel + */ + public ImageThumbnailPanel() { + 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() { + + javax.swing.JPanel thumbnailPanel = new javax.swing.JPanel(); + thumbnailLabel = new javax.swing.JLabel(); + fileSizeLabel = new javax.swing.JLabel(); + countLabel = new javax.swing.JLabel(); + + thumbnailPanel.setLayout(new java.awt.GridBagLayout()); + thumbnailPanel.add(thumbnailLabel, new java.awt.GridBagConstraints()); + + 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, false) + .addComponent(thumbnailPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(countLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 201, Short.MAX_VALUE) + .addComponent(fileSizeLabel, 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() + .addContainerGap() + .addComponent(thumbnailPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 178, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(fileSizeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(countLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 12, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel countLabel; + private javax.swing.JLabel fileSizeLabel; + private javax.swing.JLabel thumbnailLabel; + // End of variables declaration//GEN-END:variables + + @NbBundle.Messages({"# {0} - fileSize", + "ImageThumbnailPanel.sizeLabel.text=Size: {0} bytes", + "# {0} - numberOfInstances", + "ImageThumbnailPanel.countLabel.text=Number of Instances: {0}"}) + @Override + public Component getListCellRendererComponent(JList list, ImageThumbnailWrapper value, int index, boolean isSelected, boolean cellHasFocus) { + fileSizeLabel.setText(Bundle.ImageThumbnailPanel_sizeLabel_text(value.getResultFile().getFirstInstance().getSize())); + countLabel.setText(Bundle.ImageThumbnailPanel_countLabel_text(value.getResultFile().getAllInstances().size())); + thumbnailLabel.setIcon(new ImageIcon(value.getThumbnail())); + setBackground(isSelected ? SELECTION_COLOR : list.getBackground()); + return this; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/filequery/ImageThumbnailViewer.form b/Core/src/org/sleuthkit/autopsy/filequery/ImageThumbnailViewer.form new file mode 100644 index 0000000000..4b96bac8ae --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/filequery/ImageThumbnailViewer.form @@ -0,0 +1,52 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/filequery/ImageThumbnailViewer.java b/Core/src/org/sleuthkit/autopsy/filequery/ImageThumbnailViewer.java new file mode 100644 index 0000000000..1524f60820 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/filequery/ImageThumbnailViewer.java @@ -0,0 +1,119 @@ +/* + * Autopsy + * + * 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.filequery; + +import java.util.ArrayList; +import java.util.List; +import javax.swing.DefaultListModel; +import javax.swing.event.ListSelectionListener; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * A JPanel to display image thumbnails. + * + */ +public class ImageThumbnailViewer extends javax.swing.JPanel { + + private static final long serialVersionUID = 1L; + + private final DefaultListModel thumbnailListModel = new DefaultListModel<>(); + + /** + * Creates new form ImageThumbnailViewer + */ + public ImageThumbnailViewer() { + 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() { + + javax.swing.JScrollPane thumbnailListScrollPane = new javax.swing.JScrollPane(); + thumbnailList = new javax.swing.JList<>(); + + setLayout(new java.awt.BorderLayout()); + + thumbnailList.setModel(thumbnailListModel); + thumbnailList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + thumbnailList.setCellRenderer(new ImageThumbnailPanel()); + thumbnailList.setLayoutOrientation(javax.swing.JList.HORIZONTAL_WRAP); + thumbnailList.setVisibleRowCount(0); + thumbnailListScrollPane.setViewportView(thumbnailList); + + add(thumbnailListScrollPane, java.awt.BorderLayout.CENTER); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JList thumbnailList; + // End of variables declaration//GEN-END:variables + + /** + * Add a selection listener to the list of thumbnails being displayed. + * + * @param listener The ListSelectionListener to add to the selection model. + */ + void addListSelectionListener(ListSelectionListener listener) { + thumbnailList.getSelectionModel().addListSelectionListener(listener); + } + + /** + * Get the list of AbstractFiles which are represented by the selected image + * thumbnail. + * + * @return The list of AbstractFiles which are represented by the selected image + * thumbnail. + */ + List getInstancesForSelected() { + synchronized (this) { + if (thumbnailList.getSelectedIndex() == -1) { + return new ArrayList<>(); + } else { + return thumbnailListModel.getElementAt(thumbnailList.getSelectedIndex()).getResultFile().getAllInstances(); + } + } + } + + /** + * Clear the list of thumbnails being displayed. + */ + void clearViewer() { + synchronized (this) { + thumbnailListModel.removeAllElements(); + } + } + + /** + * Add the thumbnail for an image to the panel. + * + * @param thumbnailWrapper The object which contains the thumbnail which + * will be displayed. + */ + void addImage(ImageThumbnailWrapper thumbnailWrapper) { + synchronized (this) { + thumbnailListModel.addElement(thumbnailWrapper); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/filequery/ImageThumbnailWrapper.java b/Core/src/org/sleuthkit/autopsy/filequery/ImageThumbnailWrapper.java new file mode 100644 index 0000000000..dd1923007d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/filequery/ImageThumbnailWrapper.java @@ -0,0 +1,72 @@ +/* + * Autopsy + * + * 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.filequery; + +import java.awt.Image; +import org.sleuthkit.autopsy.coreutils.ImageUtils; + +/** + * Class to wrap all the information necessary for an image thumbnail to be + * displayed. + */ +public class ImageThumbnailWrapper { + + private Image thumbnail; + private final ResultFile resultFile; + + /** + * Construct a new ImageThumbnailsWrapper. + * + * @param file The ResultFile which represents the image file which the + * thumbnails were created for. + */ + ImageThumbnailWrapper(ResultFile file) { + this.thumbnail = ImageUtils.getDefaultThumbnail(); + this.resultFile = file; + } + + /** + * Set the image thumbnail which exists. + * + * @param thumbnail The thumbnail which exists for this file. + */ + void setImageThumbnail(Image thumbnail) { + this.thumbnail = thumbnail; + } + + /** + * Get the ResultFile which represents the image file which the thumbnail + * was created for. + * + * @return The ResultFile which represents the image file which the + * thumbnail was created for. + */ + ResultFile getResultFile() { + return resultFile; + } + + /** + * Get the thumbnail for the image. + * + * @return The Image which is the thumbnail for the image. + */ + Image getThumbnail() { + return thumbnail; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.form b/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.form index 8e26e73944..97080a329a 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.form @@ -238,10 +238,18 @@ + + + + + + + + @@ -269,6 +277,8 @@ + + @@ -286,6 +296,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.java b/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.java index b9f04bc63c..5809eb6051 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.java @@ -33,13 +33,12 @@ import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import javax.swing.event.ListSelectionListener; import org.openide.explorer.ExplorerManager; -import org.openide.nodes.AbstractNode; import org.openide.nodes.Node; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; -import org.sleuthkit.autopsy.corecomponents.DataResultViewerThumbnail; import org.sleuthkit.autopsy.corecomponents.TableFilterNode; +import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskCoreException; @@ -51,9 +50,9 @@ import org.sleuthkit.datamodel.TskCoreException; public class ResultsPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; - private final DataResultViewerThumbnail thumbnailViewer; private final DataResultViewerTable tableViewer; private final VideoThumbnailViewer videoThumbnailViewer; + private final ImageThumbnailViewer imageThumbnailViewer; private List searchFilters; private FileSearch.AttributeType groupingAttribute; private FileGroup.GroupSortingAlgorithm groupSort; @@ -74,22 +73,35 @@ public class ResultsPanel extends javax.swing.JPanel { public ResultsPanel(ExplorerManager explorerManager, EamDb centralRepo) { initComponents(); this.centralRepo = centralRepo; - thumbnailViewer = new DataResultViewerThumbnail(explorerManager); tableViewer = new DataResultViewerTable(explorerManager); + imageThumbnailViewer = new ImageThumbnailViewer(); videoThumbnailViewer = new VideoThumbnailViewer(); - // Disable manual editing of page size spinner videoThumbnailViewer.addListSelectionListener((e) -> { if (!e.getValueIsAdjusting()) { populateInstancesList(); } }); + imageThumbnailViewer.addListSelectionListener((e) -> { + if (!e.getValueIsAdjusting()) { + populateInstancesList(); + } + }); + // Disable manual editing of page size spinner ((JSpinner.DefaultEditor) pageSizeSpinner.getEditor()).getTextField().setEditable(false); } + /** + * Add a list selection listener to the instances list. + * + * @param listener The ListSelectionListener to add to the instances list. + */ void addListSelectionListener(ListSelectionListener listener) { instancesList.addListSelectionListener(listener); } + /** + * Populate the instances list. + */ synchronized void populateInstancesList() { SwingUtilities.invokeLater(() -> { instancesListModel.removeAllElements(); @@ -102,6 +114,12 @@ public class ResultsPanel extends javax.swing.JPanel { }); } + /** + * Get the AbstractFile for the item currently selected in the instances + * list. + * + * @return The AbstractFile which is currently selected. + */ synchronized AbstractFile getSelectedFile() { if (instancesList.getSelectedIndex() == -1) { return null; @@ -110,9 +128,18 @@ public class ResultsPanel extends javax.swing.JPanel { } } + /** + * Get the list of all instances for the the currently selected item in the + * results viewer area. + * + * @return The list of AbstractFiles which are represented by the item + * selected in the results viewer area. + */ private List getInstancesForSelected() { if (resultType == FileSearchData.FileType.VIDEO) { return videoThumbnailViewer.getInstancesForSelected(); + } else if (resultType == FileSearchData.FileType.IMAGE) { + return imageThumbnailViewer.getInstancesForSelected(); } return new ArrayList<>(); } @@ -128,23 +155,13 @@ public class ResultsPanel extends javax.swing.JPanel { populateInstancesList(); currentPage = pageRetrievedEvent.getPageNumber(); updateControls(); - thumbnailViewer.resetComponent(); - tableViewer.resetComponent(); - resultsViewerPanel.remove(thumbnailViewer); - resultsViewerPanel.remove(tableViewer); - resultsViewerPanel.remove(videoThumbnailViewer); + resetResultViewer(); if (pageRetrievedEvent.getType() == FileSearchData.FileType.IMAGE) { - resultsViewerPanel.add(thumbnailViewer); - if (pageRetrievedEvent.getSearchResults().size() > 0) { - List filesList = pageRetrievedEvent.getSearchResults().stream().map(file -> file.getFirstInstance()).collect(Collectors.toList()); - thumbnailViewer.setNode(new TableFilterNode(new DataResultFilterNode(new AbstractNode(new DiscoveryThumbnailChildren(filesList))), true)); - } else { - thumbnailViewer.setNode(new TableFilterNode(new DataResultFilterNode(Node.EMPTY), true)); - } + populateImageViewer(pageRetrievedEvent.getSearchResults()); + resultsViewerPanel.add(imageThumbnailViewer); } else if (pageRetrievedEvent.getType() == FileSearchData.FileType.VIDEO) { populateVideoViewer(pageRetrievedEvent.getSearchResults()); resultsViewerPanel.add(videoThumbnailViewer); - } else { resultsViewerPanel.add(tableViewer); if (pageRetrievedEvent.getSearchResults().size() > 0) { @@ -154,10 +171,22 @@ public class ResultsPanel extends javax.swing.JPanel { tableViewer.setNode(new TableFilterNode(new DataResultFilterNode(Node.EMPTY), true)); } } - }); + resultsViewerPanel.revalidate(); + resultsViewerPanel.repaint(); + } + ); } - synchronized void populateVideoViewer(List files) { + /** + * Reset the result viewer and any associate workers to a default empty + * state. + */ + private synchronized void resetResultViewer() { + resultsViewerPanel.remove(imageThumbnailViewer); + tableViewer.resetComponent(); + resultsViewerPanel.remove(tableViewer); + resultsViewerPanel.remove(videoThumbnailViewer); + //cancel any unfished thumb workers for (SwingWorker thumbWorker : thumbnailWorkers) { if (!thumbWorker.isDone()) { @@ -167,6 +196,16 @@ public class ResultsPanel extends javax.swing.JPanel { //clear old thumbnails thumbnailWorkers.clear(); videoThumbnailViewer.clearViewer(); + imageThumbnailViewer.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. + */ + synchronized void populateVideoViewer(List files) { for (ResultFile file : files) { VideoThumbnailWorker thumbWorker = new VideoThumbnailWorker(file); thumbWorker.execute(); @@ -175,6 +214,21 @@ public 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. + */ + synchronized void populateImageViewer(List files) { + for (ResultFile file : files) { + ImageThumbnailWorker thumbWorker = new ImageThumbnailWorker(file); + thumbWorker.execute(); + //keep track of thumb worker for possible cancelation + thumbnailWorkers.add(thumbWorker); + } + } + /** * Subscribe and respond to GroupSelectedEvents. * @@ -201,8 +255,7 @@ public class ResultsPanel extends javax.swing.JPanel { currentPage = 0; updateControls(); videoThumbnailViewer.clearViewer(); - thumbnailViewer.resetComponent(); - thumbnailViewer.setNode(new TableFilterNode(new DataResultFilterNode(Node.EMPTY), true)); + imageThumbnailViewer.clearViewer(); tableViewer.setNode(new TableFilterNode(new DataResultFilterNode(Node.EMPTY), true)); resultsViewerPanel.revalidate(); resultsViewerPanel.repaint(); @@ -262,9 +315,9 @@ public class ResultsPanel extends javax.swing.JPanel { javax.swing.JLabel gotoPageLabel = new javax.swing.JLabel(); gotoPageField = new javax.swing.JTextField(); javax.swing.JLabel pageSizeLabel = new javax.swing.JLabel(); - resultsSplitPane = new javax.swing.JSplitPane(); - instancesPanel = new javax.swing.JPanel(); - instancesScrollPane = new javax.swing.JScrollPane(); + javax.swing.JSplitPane resultsSplitPane = new javax.swing.JSplitPane(); + javax.swing.JPanel instancesPanel = new javax.swing.JPanel(); + javax.swing.JScrollPane instancesScrollPane = new javax.swing.JScrollPane(); instancesList = new javax.swing.JList<>(); resultsViewerPanel = new javax.swing.JPanel(); @@ -374,6 +427,7 @@ public class ResultsPanel extends javax.swing.JPanel { instancesList.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(ResultsPanel.class, "ResultsPanel.instancesList.border.title"))); // NOI18N instancesList.setModel(instancesListModel); + instancesList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); instancesList.setCellRenderer(new InstancesCellRenderer()); instancesScrollPane.setViewportView(instancesList); @@ -489,33 +543,82 @@ public class ResultsPanel extends javax.swing.JPanel { private javax.swing.JLabel currentPageLabel; private javax.swing.JTextField gotoPageField; private javax.swing.JList instancesList; - private javax.swing.JPanel instancesPanel; - private javax.swing.JScrollPane instancesScrollPane; private javax.swing.JButton nextPageButton; private javax.swing.JSpinner pageSizeSpinner; private javax.swing.JPanel pagingPanel; private javax.swing.JButton previousPageButton; - private javax.swing.JSplitPane resultsSplitPane; private javax.swing.JPanel resultsViewerPanel; // End of variables declaration//GEN-END:variables + /** + * Swing worker to handle the retrieval of video thumbnails and population + * of the Video Thumbnail Viewer. + */ private class VideoThumbnailWorker extends SwingWorker { private final VideoThumbnailsWrapper thumbnailWrapper; + /** + * Construct a new VideoThumbnailWorker. + * + * @param file The ResultFile which represents the video file thumbnails + * are being retrieved for. + */ VideoThumbnailWorker(ResultFile file) { - thumbnailWrapper = new VideoThumbnailsWrapper(new ArrayList(), new int[4], file); - videoThumbnailViewer.addRow(thumbnailWrapper); + thumbnailWrapper = new VideoThumbnailsWrapper(file); + videoThumbnailViewer.addVideo(thumbnailWrapper); } @Override protected Void doInBackground() throws Exception { FileSearch.getVideoThumbnails(thumbnailWrapper); - videoThumbnailViewer.repaint(); return null; } + + @Override + protected void done() { + videoThumbnailViewer.repaint(); + } } + /** + * Swing worker to handle the retrieval of image thumbnails and population + * of the Image Thumbnail Viewer. + */ + private class ImageThumbnailWorker extends SwingWorker { + + private final ImageThumbnailWrapper thumbnailWrapper; + + /** + * Construct a new ImageThumbnailWorker. + * + * @param file The ResultFile which represents the image file thumbnails + * are being retrieved for. + */ + ImageThumbnailWorker(ResultFile file) { + thumbnailWrapper = new ImageThumbnailWrapper(file); + imageThumbnailViewer.addImage(thumbnailWrapper); + } + + @Override + protected Void doInBackground() throws Exception { + Image thumbnail = ImageUtils.getThumbnail(thumbnailWrapper.getResultFile().getFirstInstance(), ImageUtils.ICON_SIZE_LARGE); + if (thumbnail != null) { + thumbnailWrapper.setImageThumbnail(thumbnail); + } + return null; + } + + @Override + protected void done() { + imageThumbnailViewer.repaint(); + } + + } + + /** + * Cell renderer for the instances list. + */ private class InstancesCellRenderer extends DefaultListCellRenderer { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/filequery/SearchResults.java b/Core/src/org/sleuthkit/autopsy/filequery/SearchResults.java index 91bb93e865..f48cd4d1f8 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/SearchResults.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/SearchResults.java @@ -150,8 +150,8 @@ class SearchResults { * * @return the grouped and sorted results */ - LinkedHashMap> toLinkedHashMap() throws FileSearchException { - LinkedHashMap> map = new LinkedHashMap<>(); + Map> toLinkedHashMap() throws FileSearchException { + Map> map = new LinkedHashMap<>(); // Sort the groups and files sortGroupsAndFiles(); diff --git a/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailPanel.form b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailPanel.form index a159e274d6..5f41c66123 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailPanel.form +++ b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailPanel.form @@ -47,7 +47,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailPanel.java b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailPanel.java index e6dd569b97..5dbe46cd3d 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailPanel.java @@ -35,7 +35,7 @@ import org.openide.util.NbBundle.Messages; final class VideoThumbnailPanel extends javax.swing.JPanel implements ListCellRenderer { private static final int GAP_SIZE = 4; - private static final Color SELECTION_COLOR = new Color(100, 200, 255); + private static final Color SELECTION_COLOR = new Color(0,120,215); private static final long serialVersionUID = 1L; /** @@ -124,7 +124,7 @@ final class VideoThumbnailPanel extends javax.swing.JPanel implements ListCellRe .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addComponent(fileSizeLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 19, Short.MAX_VALUE) .addComponent(countLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) ); }// //GEN-END:initComponents diff --git a/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailViewer.java b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailViewer.java index da1ef749a9..9994506a0e 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailViewer.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailViewer.java @@ -50,21 +50,12 @@ public class VideoThumbnailViewer extends javax.swing.JPanel { } /** - * Get the AbstractFile associated with the selected thumbnails. + * Get the list of AbstractFiles which are represented by the selected Video + * thumbnails. * - * @return The AbstractFile associated with the selected thumbnails, or null - * if no thumbnails are selected. + * @return The list of AbstractFiles which are represented by the selected Video + * thumbnails. */ - AbstractFile getSelectedFile() { - synchronized (this) { - if (thumbnailList.getSelectedIndex() == -1) { - return null; - } else { - return thumbnailListModel.getElementAt(thumbnailList.getSelectedIndex()).getResultFile().getFirstInstance(); - } - } - } - List getInstancesForSelected() { synchronized (this) { if (thumbnailList.getSelectedIndex() == -1) { @@ -90,7 +81,7 @@ public class VideoThumbnailViewer extends javax.swing.JPanel { * @param thumbnailWrapper The object which contains the thumbnails which * will be displayed. */ - void addRow(VideoThumbnailsWrapper thumbnailWrapper) { + void addVideo(VideoThumbnailsWrapper thumbnailWrapper) { synchronized (this) { thumbnailListModel.addElement(thumbnailWrapper); } diff --git a/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailsWrapper.java b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailsWrapper.java index 106910e87e..08c68611c0 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailsWrapper.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/VideoThumbnailsWrapper.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.filequery; import java.awt.Image; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -28,23 +29,19 @@ import java.util.List; */ final class VideoThumbnailsWrapper { - private List thumbnails; + private final List thumbnails; private final ResultFile resultFile; private int[] timeStamps; /** * Construct a new VideoThumbnailsWrapper. * - * @param thumbnails The list of Images which are the thumbnails for the - * video. - * @param timeStamps An array containing the time in milliseconds into the - * video that each thumbnail created for. - * @param file The ResultFile which represents the video file which - * the thumbnails were created for. + * @param file The ResultFile which represents the video file which the + * thumbnails were created for. */ - VideoThumbnailsWrapper(List thumbnails, int[] timeStamps, ResultFile file) { - this.thumbnails = thumbnails; - this.timeStamps = timeStamps; + VideoThumbnailsWrapper(ResultFile file) { + this.thumbnails = new ArrayList<>(); + this.timeStamps = new int[4]; this.resultFile = file; } @@ -79,9 +76,17 @@ final class VideoThumbnailsWrapper { return Collections.unmodifiableList(thumbnails); } + /** + * Set the thumbnails and their associated time stamps. + * + * @param videoThumbnails The list of Images which are the thumbnails for + * the video. + * @param framePositions An array containing the time in milliseconds into + * the video that each thumbnail created for. + */ void setThumbnails(List videoThumbnails, int[] framePositions) { - this.thumbnails = videoThumbnails; - this.timeStamps = framePositions; + this.thumbnails.addAll(videoThumbnails); + this.timeStamps = framePositions.clone(); } }