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/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index d33fcee97d..25443636ba 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -50,11 +50,14 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; */ @ServiceProvider(service = DataResultViewer.class) public class DataResultViewerTable extends AbstractDataResultViewer { + private String firstColumnLabel = "Name"; private Set propertiesAcc = new LinkedHashSet(); - private static final Logger logger = Logger.getLogger(DataResultViewerTable.class.getName()); + private static final Logger logger = Logger.getLogger(DataResultViewerTable.class.getName()); - /** Creates new form DataResultViewerTable */ + /** + * Creates new form DataResultViewerTable + */ public DataResultViewerTable() { initComponents(); @@ -66,23 +69,26 @@ public class DataResultViewerTable extends AbstractDataResultViewer { // don't show the root node ov.getOutline().setRootVisible(false); } - + /** * Expand node + * * @param n Node to expand */ @Override public void expandNode(Node n) { - if ( this.tableScrollPanel != null) { + super.expandNode(n); + + if (this.tableScrollPanel != null) { OutlineView ov = ((OutlineView) this.tableScrollPanel); ov.expandNode(n); } } - /** 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 @@ -115,23 +121,9 @@ public class DataResultViewerTable extends AbstractDataResultViewer { private javax.swing.JScrollPane tableScrollPanel; // 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; - } - /** * Gets regular Bean property set properties from first child of Node. + * * @param parent Node with at least one child to get properties from * @return Properties, */ @@ -152,8 +144,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } /** - * Gets regular Bean property set properties from all first children and, recursively, subchildren of Node. - * Note: won't work out the box for lazy load - you need to set all children props for the parent by hand + * Gets regular Bean property set properties from all first children and, + * recursively, subchildren of Node. Note: won't work out the box for lazy + * load - you need to set all children props for the parent by hand + * * @param parent Node with at least one child to get properties from * @return Properties, */ @@ -161,7 +155,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { Node firstChild = parent.getChildren().getNodeAt(0); Property[] properties = null; - + if (firstChild == null) { throw new IllegalArgumentException("Couldn't get a child Node from the given parent."); } else { @@ -169,44 +163,48 @@ public class DataResultViewerTable extends AbstractDataResultViewer { while (firstChild != null) { for (PropertySet ps : firstChild.getPropertySets()) { //if (ps.getName().equals(Sheet.PROPERTIES)) { - //return ps.getProperties(); - final Property [] props = ps.getProperties(); - final int propsNum = props.length; - for (int i = 0; i< propsNum; ++i) - allProperties.add(props[i]); + //return ps.getProperties(); + final Property[] props = ps.getProperties(); + final int propsNum = props.length; + for (int i = 0; i < propsNum; ++i) { + allProperties.add(props[i]); + } //} } firstChild = firstChild.getChildren().getNodeAt(0); } - properties = allProperties.toArray(new Property[0]); + properties = allProperties.toArray(new Property[0]); //throw new IllegalArgumentException("Child Node doesn't have the regular PropertySet."); } return properties; - + } - - + /** - * Gets regular Bean property set properties from all children and, recursively, subchildren of Node. - * Note: won't work out the box for lazy load - you need to set all children props for the parent by hand + * Gets regular Bean property set properties from all children and, + * recursively, subchildren of Node. Note: won't work out the box for lazy + * load - you need to set all children props for the parent by hand + * * @param parent Node with at least one child to get properties from - * @param rows max number of rows to retrieve properties for (can be used for memory optimization) + * @param rows max number of rows to retrieve properties for (can be used + * for memory optimization) */ private void getAllChildPropertyHeadersRec(Node parent, int rows) { Children children = parent.getChildren(); int total = Math.min(rows, children.getNodesCount()); - for(int i = 0; i < total; i++){ + for (int i = 0; i < total; i++) { Node child = children.getNodeAt(i); for (PropertySet ps : child.getPropertySets()) { - //if (ps.getName().equals(Sheet.PROPERTIES)) { - //return ps.getProperties(); - final Property [] props = ps.getProperties(); - final int propsNum = props.length; - for (int j = 0; j< propsNum; ++j) - propertiesAcc.add(props[j]); - //} + //if (ps.getName().equals(Sheet.PROPERTIES)) { + //return ps.getProperties(); + final Property[] props = ps.getProperties(); + final int propsNum = props.length; + for (int j = 0; j < propsNum; ++j) { + propertiesAcc.add(props[j]); } + //} + } getAllChildPropertyHeadersRec(child, rows); } } @@ -215,8 +213,6 @@ public class DataResultViewerTable extends AbstractDataResultViewer { public boolean isSupported(Node selectedNode) { return true; } - - @Override public void setNode(Node selectedNode) { @@ -243,17 +239,17 @@ public class DataResultViewerTable extends AbstractDataResultViewer { //} em.setRootContext(root); - + OutlineView ov = ((OutlineView) this.tableScrollPanel); propertiesAcc.clear(); - + this.getAllChildPropertyHeadersRec(selectedNode, 100); List props = new ArrayList(propertiesAcc); - if(props.size() > 0) { + if (props.size() > 0) { Node.Property prop = props.remove(0); - ((DefaultOutlineModel)ov.getOutline().getOutlineModel()).setNodesColumnLabel(prop.getDisplayName()); + ((DefaultOutlineModel) ov.getOutline().getOutlineModel()).setNodesColumnLabel(prop.getDisplayName()); } @@ -261,18 +257,18 @@ public class DataResultViewerTable extends AbstractDataResultViewer { //First property column is sortable, but also sorted initially, so //initially this one will have the arrow icon: - if(props.size() > 0){ + if (props.size() > 0) { props.get(0).setValue("TreeColumnTTV", Boolean.TRUE); // Identifies special property representing first (tree) column. props.get(0).setValue("SortingColumnTTV", Boolean.TRUE); // TreeTableView should be initially sorted by this property column. } - + // The rest of the columns are sortable, but not initially sorted, // so initially will have no arrow icon: - String[] propStrings = new String[props.size()*2]; + String[] propStrings = new String[props.size() * 2]; for (int i = 0; i < props.size(); i++) { props.get(i).setValue("ComparableColumnTTV", Boolean.TRUE); - propStrings[2*i] = props.get(i).getName(); - propStrings[2*i+1] = props.get(i).getDisplayName(); + propStrings[2 * i] = props.get(i).getName(); + propStrings[2 * i + 1] = props.get(i).getDisplayName(); } ov.setPropertyColumns(propStrings); @@ -342,7 +338,9 @@ public class DataResultViewerTable extends AbstractDataResultViewer { for (int i = 0; i < maxRows; i++) { PropertySet[] props = node.getChildren().getNodeAt(i).getPropertySets(); if (props.length == 0) //rare special case + { continue; + } Property[] property = props[0].getProperties(); objs[i] = new Object[property.length]; @@ -370,20 +368,16 @@ public class DataResultViewerTable extends AbstractDataResultViewer { return new DataResultViewerTable(); } - @Override - public void resetComponent() { - } - /** * Gets the max width of the column from the given index, header, and table. * - * @param index the index of the column on the table / header - * @param metrics the font metrics that this component use - * @param margin the left/right margin of the column - * @param padding the left/right padding of the column - * @param header the property headers of the table - * @param table the object table - * @return max the maximum width of the column + * @param index the index of the column on the table / header + * @param metrics the font metrics that this component use + * @param margin the left/right margin of the column + * @param padding the left/right padding of the column + * @param header the property headers of the table + * @param table the object table + * @return max the maximum width of the column */ private int getMaxColumnWidth(int index, FontMetrics metrics, int margin, int padding, List header, Object[][] table) { // set the tree (the node / names column) width @@ -395,13 +389,13 @@ public class DataResultViewerTable extends AbstractDataResultViewer { /** * Gets the max width of the column from the given index, header, and table. * - * @param index the index of the column on the table / header - * @param metrics the font metrics that this component use - * @param margin the left/right margin of the column - * @param padding the left/right padding of the column - * @param header the column header for the comparison - * @param table the object table - * @return max the maximum width of the column + * @param index the index of the column on the table / header + * @param metrics the font metrics that this component use + * @param margin the left/right margin of the column + * @param padding the left/right padding of the column + * @param header the column header for the comparison + * @param table the object table + * @return max the maximum width of the column */ private int getMaxColumnWidth(int index, FontMetrics metrics, int margin, int padding, String header, Object[][] table) { // set the tree (the node / names column) width @@ -411,8 +405,9 @@ public class DataResultViewerTable extends AbstractDataResultViewer { // Get maximum width of column data for (int i = 0; i < table.length; i++) { - if(index >= table[i].length) + if (index >= table[i].length) { continue; + } String test = table[i][index].toString(); colWidth = Math.max(colWidth, metrics.stringWidth(test)); } @@ -429,30 +424,13 @@ public class DataResultViewerTable extends AbstractDataResultViewer { @Override public void clearComponent() { - em.removePropertyChangeListener(this); this.tableScrollPanel.removeAll(); this.tableScrollPanel = null; - try { - this.em.getRootContext().destroy(); - em = null; - } catch (IOException ex) { - // TODO: Proper thing to do? Log? Don't throw RuntimeException? - throw new RuntimeException("Error: can't clear the component of the Table Result Viewer.", ex); - } + + //this destroys em + super.clearComponent(); + } - @Override - public Component getComponent() { - return this; - } - - @Override - public void setSelectedNodes(Node[] selected) { - try{ - this.em.setSelectedNodes(selected); - } catch (PropertyVetoException ex) { - Logger.getLogger(DataResultViewerTable.class.getName()) - .log(Level.WARNING, "Couldn't set selected nodes.", ex); - } - } + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.form index 0eea18060b..5645ded3d5 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.form @@ -16,22 +16,155 @@ - + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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() { + private ProgressHandle progress; + + @Override + protected Object doInBackground() throws Exception { + pagePrevButton.setEnabled(false); + pageNextButton.setEnabled(false); + progress = ProgressHandleFactory.createHandle("Generating Thumbnails..."); + progress.start(); + progress.switchToIndeterminate(); + Node root = em.getRootContext(); + Node pageNode = root.getChildren().getNodeAt(curPage - 1); + em.setExploredContext(pageNode); + return null; + } + + @Override + protected void done() { + progress.finish(); + setCursor(null); + updateControls(); + + } + }.execute(); + + } + + private void updateControls() { + if (totalPages == 0) { + pagePrevButton.setEnabled(false); + pageNextButton.setEnabled(false); + curPageLabel.setText(""); + totalPagesLabel.setText(""); + } else { + curPageLabel.setText(Integer.toString(curPage)); + totalPagesLabel.setText(Integer.toString(totalPages)); + + + pageNextButton.setEnabled(!(curPage == totalPages)); + pagePrevButton.setEnabled(!(curPage == 1)); + + } + + } + + /** + * Listens for root change updates and updates the paging controls + */ + private class PageUpdater implements NodeListener { + + private Node root; + + void setRoot(Node root) { + this.root = root; + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + } + + @Override + public void childrenAdded(NodeMemberEvent nme) { + totalPages = root.getChildren().getNodesCount(); + + if (curPage == -1 || curPage > totalPages) { + curPage = 1; + } + + updateControls(); + + + //force load the curPage node + final Node pageNode = root.getChildren().getNodeAt(curPage - 1); + + //em.setSelectedNodes(new Node[]{pageNode}); + if (pageNode != null) { + em.setExploredContext(pageNode); + } + + } + + @Override + public void childrenRemoved(NodeMemberEvent nme) { + totalPages = 0; + curPage = -1; + updateControls(); + } + + @Override + public void childrenReordered(NodeReorderEvent nre) { + } + + @Override + public void nodeDestroyed(NodeEvent ne) { } } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java index 9787b9f689..41af95b854 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java @@ -54,6 +54,7 @@ class ThumbnailViewChildren extends Children.Keys { private Node parent; private final HashMap> pages = new HashMap>(); private int totalImages = 0; + private int totalPages = 0; private static final Logger logger = Logger.getLogger(ThumbnailViewChildren.class.getName()); /** @@ -76,6 +77,15 @@ class ThumbnailViewChildren extends Children.Keys { setupKeys(); } + + int getTotalPages() { + return totalPages; + } + + int getTotalImages() { + return totalImages; + } + private void setupKeys() { //divide the supported content into buckets @@ -97,9 +107,14 @@ class ThumbnailViewChildren extends Children.Keys { return; } - int totalPages = totalImages / IMAGES_PER_PAGE; - if (totalPages % totalImages != 0) { - ++totalPages; + totalPages = 0; + if (totalImages < IMAGES_PER_PAGE) { + totalPages = 1; + } else { + totalPages = totalImages / IMAGES_PER_PAGE; + if (totalPages % totalImages != 0) { + ++totalPages; + } } int prevImages = 0; @@ -109,10 +124,10 @@ class ThumbnailViewChildren extends Children.Keys { pages.put(page, pageContent); prevImages += toAdd; } - - Integer [] pageNums = new Integer[totalPages]; - for (int i = 0; i< totalPages; ++i) { - pageNums[i] = i+1; + + Integer[] pageNums = new Integer[totalPages]; + for (int i = 0; i < totalPages; ++i) { + pageNums[i] = i + 1; } setKeys(pageNums); @@ -169,28 +184,26 @@ class ThumbnailViewChildren extends Children.Keys { } /** - * Node representing page node, a parent of image nodes, with a name showing children range + * Node representing page node, a parent of image nodes, with a name showing + * children range */ private class ThumbnailPageNode extends AbstractNode { ThumbnailPageNode(Integer pageNum) { super(new ThumbnailPageNodeChildren(pages.get(pageNum)), Lookups.singleton(pageNum)); setName(Integer.toString(pageNum)); - int from = 1 + ((pageNum-1) * IMAGES_PER_PAGE); - int showImages = Math.min(IMAGES_PER_PAGE, totalImages - (from-1)); + int from = 1 + ((pageNum - 1) * IMAGES_PER_PAGE); + int showImages = Math.min(IMAGES_PER_PAGE, totalImages - (from - 1)); int to = from + showImages - 1; setDisplayName(from + "-" + to); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/Folder-icon.png"); } - - - } - + //TODO insert node at beginning pressing which goes back to page view - private class ThumbnailPageNodeChildren extends Children.Keys { + private class ThumbnailPageNodeChildren extends Children.Keys { //wrapped original nodes private List contentImages = null; @@ -201,23 +214,19 @@ class ThumbnailViewChildren extends Children.Keys { this.contentImages = contentImages; } - @Override protected void addNotify() { super.addNotify(); - + setKeys(contentImages); } @Override protected void removeNotify() { super.removeNotify(); - + setKeys(new ArrayList()); } - - - @Override protected Node[] createNodes(Node wrapped) { @@ -228,8 +237,5 @@ class ThumbnailViewChildren extends Children.Keys { return new Node[]{}; } } - - } - } diff --git a/NEWS.txt b/NEWS.txt index e846c3214b..236ab9a2ac 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -1,14 +1,18 @@ ---------------- VERSION Current (development) -------------- New features: -- Build scripts enhancements to include module version tracking. + Improvements: +- Removed limit on number of results displayed. +- Thumbnail viewer - added paging and removed limit of images. +- Slight improvements in UI responsiveness for large number of results. +- Build scripts enhancements to include module version tracking. - Netbeans RCP upgrade from 7.2 to 7.2.1 - Enable user to select any file when opening image. Bugfixes: -- UI fix for keyword search box. +- UI fix for keyword search box when case is changed. ---------------- VERSION 3.0.0 --------------