diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java
index 4bd9aa5d96..9479867d9e 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java
@@ -18,9 +18,12 @@
*/
package org.sleuthkit.autopsy.corecomponents;
+import java.awt.Component;
import java.awt.Cursor;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
+import java.beans.PropertyVetoException;
+import java.io.IOException;
import java.util.logging.Level;
import javax.swing.JPanel;
import org.openide.explorer.ExplorerManager;
@@ -35,67 +38,114 @@ import org.sleuthkit.autopsy.coreutils.Logger;
* Holds commonalities between all DataResultViewers
*/
public abstract class AbstractDataResultViewer extends JPanel implements
- DataResultViewer, Provider, PropertyChangeListener {
-
+ DataResultViewer, Provider {
+
private static final Logger logger = Logger.getLogger(AbstractDataResultViewer.class.getName());
-
protected transient ExplorerManager em = new ExplorerManager();
-
+ private PropertyChangeListener nodeSelListener;
+
public AbstractDataResultViewer() {
- this.em.addPropertyChangeListener(this);
- }
-
- /**
- * Propagates changes in the current select node from the DataResultViewer
- * to the DataContentTopComponent
- *
- * @param evt
- */
- @Override
- public void propertyChange(PropertyChangeEvent evt) {
- String changed = evt.getPropertyName();
- // change that should affect view
- if (changed.equals(ExplorerManager.PROP_SELECTED_NODES)) {
- //|| changed.equals(ExplorerManager.PROP_NODE_CHANGE)
- //|| changed.equals(ExplorerManager.PROP_EXPLORED_CONTEXT)
- //|| changed.equals(ExplorerManager.PROP_ROOT_CONTEXT)) {
+ //property listener to send nodes to content viewer
+ nodeSelListener = new PropertyChangeListener() {
+ /**
+ * Propagates changes in the current select node from the
+ * DataResultViewer to the DataContentTopComponent
+ *
+ * @param evt
+ */
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ String changed = evt.getPropertyName();
- // change the cursor to "waiting cursor" for this operation
- this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
- try {
- Node selectedNode = this.getSelectedNode();
+ // change that should affect view
+ if (changed.equals(ExplorerManager.PROP_SELECTED_NODES)) {
+ //|| changed.equals(ExplorerManager.PROP_NODE_CHANGE)
+ //|| changed.equals(ExplorerManager.PROP_EXPLORED_CONTEXT)
+ //|| changed.equals(ExplorerManager.PROP_ROOT_CONTEXT)) {
- // DataContent is designed to return only the default viewer
- DataContent dataContent = Lookup.getDefault().lookup(DataContent.class);
+ // change the cursor to "waiting cursor" for this operation
+ setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ try {
+ Node selectedNode = getSelectedNode();
- if (selectedNode != null) {
- // there's a new/changed node to display
- Node newSelectedNode = selectedNode; // get the selected Node on the table
- // push the node to default "DataContent"
- dataContent.setNode(newSelectedNode);
- } else {
- // clear the node viewer
- dataContent.setNode(null);
+ // DataContent is designed to return only the default viewer
+ DataContent dataContent = Lookup.getDefault().lookup(DataContent.class);
+
+ if (selectedNode != null) {
+ // there's a new/changed node to display
+ Node newSelectedNode = selectedNode; // get the selected Node on the table
+ // push the node to default "DataContent"
+ dataContent.setNode(newSelectedNode);
+ } else {
+ // clear the node viewer
+ dataContent.setNode(null);
+ }
+ } finally {
+ setCursor(null);
+ }
}
- } finally {
- this.setCursor(null);
+
+ /*
+ else if (changed.equals(ExplorerManager.PROP_NODE_CHANGE) ) {
+ }
+ else if (changed.equals(ExplorerManager.PROP_EXPLORED_CONTEXT)) {
+ }
+ else if (changed.equals(ExplorerManager.PROP_ROOT_CONTEXT)) {
+ }
+ */
}
- }
-
- /*
- else if (changed.equals(ExplorerManager.PROP_NODE_CHANGE) ) {
- }
- else if (changed.equals(ExplorerManager.PROP_EXPLORED_CONTEXT)) {
- }
- else if (changed.equals(ExplorerManager.PROP_ROOT_CONTEXT)) {
- }
- */
+ };
+
+ em.addPropertyChangeListener(nodeSelListener);
}
- /**
- * Gets the current node selected node
- * @return
- */
- public abstract Node getSelectedNode();
+ @Override
+ public void clearComponent() {
+ em.removePropertyChangeListener(nodeSelListener);
+
+ try {
+ this.em.getRootContext().destroy();
+ em = null;
+ } catch (IOException ex) {
+ // TODO: What's the proper thing to do here? Should it log? Not throw runtime exception?
+ throw new RuntimeException("Error: can't clear the component of the Thumbnail Result Viewer.", ex);
+ }
+ }
+
+ public Node getSelectedNode() {
+ Node result = null;
+ Node[] selectedNodes = this.getExplorerManager().getSelectedNodes();
+ if (selectedNodes.length > 0) {
+ result = selectedNodes[0];
+ }
+ return result;
+ }
+
+ @Override
+ public void expandNode(Node n) {
+ }
+
+ @Override
+ public void resetComponent() {
+ }
+
+ @Override
+ public Component getComponent() {
+ return this;
+ }
+
+ @Override
+ public ExplorerManager getExplorerManager() {
+ return this.em;
+ }
+
+ @Override
+ public void setSelectedNodes(Node[] selected) {
+ try {
+ this.em.setSelectedNodes(selected);
+ } catch (PropertyVetoException ex) {
+ logger.log(Level.WARNING, "Couldn't set selected nodes.", ex);
+ }
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
index 79f949dd2d..116d0319bc 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
@@ -88,3 +88,10 @@ DataContentViewerHex.goToPageLabel.text=Go to Page:
DataContentViewerString.languageLabel.toolTipText=
DataContentViewerString.languageLabel.text=Script:
DataContentViewerString.languageCombo.toolTipText=Language to attempt when interpreting (extracting and decoding) strings from binary data
+DataResultViewerThumbnail.pageLabel.text=Page:
+DataResultViewerThumbnail.curPageLabel.text=-
+DataResultViewerThumbnail.ofLabel.text=of
+DataResultViewerThumbnail.totalPagesLabel.text=-
+DataResultViewerThumbnail.pagesLabel.text=Pages:
+DataResultViewerThumbnail.pagePrevButton.text=
+DataResultViewerThumbnail.pageNextButton.text=
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.form
index ecfaa9de79..9ea7093879 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.form
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.form
@@ -1,4 +1,4 @@
-
+
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java
index 6325b67b61..0a495e4da1 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011 Basis Technology Corp.
+ * Copyright 2012 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,96 +19,177 @@
package org.sleuthkit.autopsy.corecomponents;
import java.awt.Color;
-import java.awt.Component;
import java.awt.Cursor;
+import java.awt.EventQueue;
import java.awt.Graphics;
-import java.beans.PropertyVetoException;
-import java.io.IOException;
-import java.util.logging.Level;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.ListSelectionModel;
+import javax.swing.SwingWorker;
+import org.netbeans.api.progress.ProgressHandle;
+import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.view.IconView;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
+import org.openide.nodes.NodeEvent;
+import org.openide.nodes.NodeListener;
+import org.openide.nodes.NodeMemberEvent;
+import org.openide.nodes.NodeReorderEvent;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
/**
- * Thumbnail view of images in data result
+ * Thumbnail view of images in data result with paging support.
+ *
+ * Paging is added to reduce memory footprint and load only up to (currently)
+ * 1000 images at a time. This works whether or not the underlying content nodes
+ * are being lazy loaded or not.
+ *
*/
@ServiceProvider(service = DataResultViewer.class)
-public class DataResultViewerThumbnail extends AbstractDataResultViewer {
+public final class DataResultViewerThumbnail extends AbstractDataResultViewer {
- private transient ExplorerManager em = new ExplorerManager();
private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName());
+ //flag to keep track if images are being loaded
+ private int curPage;
+ private int totalPages;
+ private final PageUpdater pageUpdater = new PageUpdater();
- /** Creates new form DataResultViewerThumbnail */
+ /**
+ * Creates new form DataResultViewerThumbnail
+ */
public DataResultViewerThumbnail() {
+ super();
+
initComponents();
// only allow one item to be selected at a time
((IconView) thumbnailScrollPanel).setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
-
-
+ curPage = -1;
+ totalPages = 0;
+
}
-
- @Override
- public void paint(Graphics g) {
- super.paint(g);
- //logger.log(Level.INFO, "PAINT()");
- }
-
-
-
-
-
-
- /** 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.
+ /**
+ * 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() {
thumbnailScrollPanel = new IconView();
+ pageLabel = new javax.swing.JLabel();
+ curPageLabel = new javax.swing.JLabel();
+ ofLabel = new javax.swing.JLabel();
+ totalPagesLabel = new javax.swing.JLabel();
+ pagesLabel = new javax.swing.JLabel();
+ pagePrevButton = new javax.swing.JButton();
+ pageNextButton = new javax.swing.JButton();
+
+ thumbnailScrollPanel.setPreferredSize(null);
+
+ pageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.pageLabel.text")); // NOI18N
+
+ curPageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.curPageLabel.text")); // NOI18N
+
+ ofLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.ofLabel.text")); // NOI18N
+
+ totalPagesLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.totalPagesLabel.text")); // NOI18N
+
+ pagesLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerThumbnail.class, "DataResultViewerThumbnail.pagesLabel.text")); // NOI18N
+
+ 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.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); // NOI18N
+ pagePrevButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
+ pagePrevButton.setMaximumSize(new java.awt.Dimension(27, 31));
+ pagePrevButton.setMinimumSize(new java.awt.Dimension(27, 31));
+ pagePrevButton.setPreferredSize(new java.awt.Dimension(55, 23));
+ pagePrevButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png"))); // NOI18N
+ pagePrevButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ pagePrevButtonActionPerformed(evt);
+ }
+ });
+
+ 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.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); // NOI18N
+ pageNextButton.setMargin(new java.awt.Insets(2, 0, 2, 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
+ pageNextButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ pageNextButtonActionPerformed(evt);
+ }
+ });
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(thumbnailScrollPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 675, Short.MAX_VALUE)
+ .addComponent(thumbnailScrollPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 675, Short.MAX_VALUE)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(pageLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(curPageLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(ofLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(totalPagesLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGap(61, 61, 61)
+ .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)
+ .addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(thumbnailScrollPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 336, Short.MAX_VALUE)
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(pageLabel)
+ .addComponent(curPageLabel)
+ .addComponent(ofLabel)
+ .addComponent(totalPagesLabel)
+ .addComponent(pagesLabel)
+ .addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addGroup(layout.createSequentialGroup()
+ .addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGap(0, 0, 0)))
+ .addComponent(thumbnailScrollPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 325, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGap(0, 0, Short.MAX_VALUE))
);
}// //GEN-END:initComponents
+
+ private void pagePrevButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pagePrevButtonActionPerformed
+ previousPage();
+ }//GEN-LAST:event_pagePrevButtonActionPerformed
+
+ private void pageNextButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pageNextButtonActionPerformed
+ nextPage();
+ }//GEN-LAST:event_pageNextButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JLabel curPageLabel;
+ private javax.swing.JLabel ofLabel;
+ private javax.swing.JLabel pageLabel;
+ private javax.swing.JButton pageNextButton;
+ private javax.swing.JButton pagePrevButton;
+ private javax.swing.JLabel pagesLabel;
private javax.swing.JScrollPane thumbnailScrollPanel;
+ private javax.swing.JLabel totalPagesLabel;
// End of variables declaration//GEN-END:variables
- @Override
- public ExplorerManager getExplorerManager() {
- return this.em;
- }
-
- @Override
- public Node getSelectedNode() {
- Node result = null;
-
- Node[] selectedNodes = this.getExplorerManager().getSelectedNodes();
- if (selectedNodes.length > 0) {
- result = selectedNodes[0];
- }
- return result;
- }
-
@Override
public boolean isSupported(Node selectedNode) {
if (selectedNode == null) {
@@ -118,35 +199,30 @@ public class DataResultViewerThumbnail extends AbstractDataResultViewer {
//we will need to query children of the datamodel object instead,
//or force children creation, breaking the lazy loading.
Children ch = selectedNode.getChildren();
- for (Node n : ch.getNodes()) {
+ for (Node n : ch.getNodes()) {
if (ThumbnailViewChildren.isSupported(n)) {
return true;
}
}
return false;
}
-
- /**
- * Expand node
- * @param n Node to expand
- */
- @Override
- public void expandNode(Node n) {
-
- }
-
+
@Override
public void setNode(Node givenNode) {
// change the cursor to "waiting cursor" for this operation
- this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
if (givenNode != null) {
- Node root = new AbstractNode(new ThumbnailViewChildren(givenNode));
+ ThumbnailViewChildren childNode = new ThumbnailViewChildren(givenNode);
+
+ final Node root = new AbstractNode(childNode);
+ pageUpdater.setRoot(root);
+ root.addNodeListener(pageUpdater);
em.setRootContext(root);
} else {
Node emptyNode = new AbstractNode(Children.LEAF);
em.setRootContext(emptyNode); // make empty node
-
+
IconView iv = ((IconView) this.thumbnailScrollPanel);
iv.setBackground(Color.BLACK);
}
@@ -165,36 +241,135 @@ public class DataResultViewerThumbnail extends AbstractDataResultViewer {
return new DataResultViewerThumbnail();
}
- @Override
- public void resetComponent() {
- }
-
@Override
public void clearComponent() {
- em.removePropertyChangeListener(this);
this.thumbnailScrollPanel.removeAll();
this.thumbnailScrollPanel = null;
- try {
- this.em.getRootContext().destroy();
- em = null;
- } catch (IOException ex) {
- // TODO: What's the proper thing to do here? Should it log? Not throw runtime exception?
- throw new RuntimeException("Error: can't clear the component of the Thumbnail Result Viewer.", ex);
+
+ //this destroyes em
+ super.clearComponent();
+ }
+
+ private void nextPage() {
+ if (curPage < totalPages) {
+ curPage++;
+
+ switchPage();
}
}
- @Override
- public Component getComponent() {
- return this;
+ private void previousPage() {
+ if (curPage > 1) {
+ curPage--;
+
+ switchPage();
+ }
}
-
- @Override
- public void setSelectedNodes(Node[] selected) {
- try{
- this.em.setSelectedNodes(selected);
- } catch (PropertyVetoException ex) {
- Logger.getLogger(DataResultViewerThumbnail.class.getName())
- .log(Level.WARNING, "Couldn't set selected nodes.", ex);
+
+ private void switchPage() {
+
+ EventQueue.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ }
+ });
+
+ //Note the nodes factories are likely creating nodes in EDT anyway, but worker still helps
+ new SwingWorker