diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java index 7b90357993e4766a615d994cb3126bf3d90a1445..dca177046394898f9409f0b0d30b70c548a9ab44 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java @@ -128,7 +128,7 @@ public void propertyChange(final PropertyChangeEvent focusEvent) { public void propertyChange(PropertyChangeEvent pce) { if (pce.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { final Node[] selectedNodes = MessageBrowser.this.tableEM.getSelectedNodes(); - messagesResultPanel.setNumMatches(0); + messagesResultPanel.setNumberOfChildNodes(0); messagesResultPanel.setNode(null); messagesResultPanel.setPath(""); if (selectedNodes.length > 0) { diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java index 960f90f39ea4bbd35d7912a42528a7020a496e34..8726cfedb64592383546ba89d4a9584f5644292c 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java @@ -98,7 +98,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont @NbBundle.Messages("MessageContentViewer.AtrachmentsPanel.title=Attachments") public MessageContentViewer() { initComponents(); - drp = DataResultPanel.createInstanceUninitialized(Bundle.MessageContentViewer_AtrachmentsPanel_title(), "", Node.EMPTY, 0, null); + drp = DataResultPanel.createInstanceUninitialized(Bundle.MessageContentViewer_AtrachmentsPanel_title(), "", new TableFilterNode(Node.EMPTY, false), 0, null); attachmentsScrollPane.setViewportView(drp); msgbodyTabbedPane.setEnabledAt(ATTM_TAB_INDEX, true); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResult.java b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResult.java index 191a851b9a2556563ba6b231ffa5c001e2314f4b..6af30b8729c1b716dc2f7deb4cd5a003a46ca38d 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResult.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResult.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,41 +22,58 @@ import org.openide.nodes.Node; /** - * The interface for the "top right component" window. + * An interface for result view components. A result view component provides + * multiple views of the application data represented by a given NetBeans Node. + * The differing views of the node are supplied by a collection of result + * viewers (implementations of the DataResultViewer interface). * + * A typical implementation of this interface are the NetBeans TopComponents + * (DataResultTopComponents) that use a child result view component + * (DataResultPanel) for displaying their result viewers, and are docked into + * the upper right hand side (editor mode) of the main application window. */ public interface DataResult { /** - * Sets the "selected" node in this class. + * Sets the node for which this result view component should provide + * multiple views of the underlying application data. + * + * @param node The node, may be null. If null, the call to this method is + * equivalent to a call to resetComponent on this result view + * component's result viewers. */ - public void setNode(Node selectedNode); + public void setNode(Node node); /** - * Gets the unique TopComponent ID of this class. + * Gets the preferred identifier for this result view panel in the window + * system. * - * @return preferredID the unique ID + * @return The preferred identifier. */ public String getPreferredID(); /** - * Sets the title of this TopComponent + * Sets the title of this result view component. * - * @param title the given title (String) + * @param title The title. */ public void setTitle(String title); /** - * Sets the descriptive context text at the top of the pane. + * Sets the descriptive text about the source of the nodes displayed in this + * result view component. * - * @param pathText Descriptive text giving context for the current results + * @param description The text to display. */ public void setPath(String pathText); /** - * Checks if this is the main (uncloseable) instance of DataResult + * Gets whether or not this result view panel is the "main" result view + * panel used to view the child nodes of a node selected in the application + * tree view (DirectoryTreeTopComponent) that is normally docked into the + * left hand side of the main window. * - * @return true if it is the main instance, otherwise false + * @return True or false. */ public boolean isMain(); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java index d88c9798445facea3d5b483db1d09cfa5c349af3..c9d305f53ec92978a435f9ba26246b204472e5bf 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-17 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,76 +22,120 @@ import org.openide.nodes.Node; /** - * Interface for the different viewers that show a set of nodes in the - * DataResult area. AbstractDataResultViewer has default implementations for the - * action handlers. + * An interface for result viewers. A result viewer uses a Swing Component to + * provide a view of the application data represented by a NetBeans Node passed + * to it via its setNode method. * + * Result viewers are most commonly presented as a tab in a result view panel + * (DataResultPanel) inside a result view top component (DataResultTopComponent) + * that is docked into the upper right hand side (editor mode) of the main + * application window. + * + * A result viewer is typically a JPanel that displays the child nodes of the + * given node using a NetBeans explorer view child component. Such a result + * viewer should use the explorer manager of the ancestor top component to + * connect the lookups of the nodes displayed in the NetBeans explorer view to + * the actions global context. It is strongly recommended that this type of + * result viewer is implemented by extending the abstract base class + * AbstractDataResultViewer, which will handle some key aspects of working with + * the ancestor top component's explorer manager. + * + * This interface is an extension point, so classes that implement it should + * provide an appropriate ServiceProvider annotation. */ public interface DataResultViewer { /** - * Set the root node to display in this viewer. When called with null, must - * clear all references to previous nodes. + * Creates a new instance of this result viewer, which allows the + * application to use the capability provided by this result viewer in more + * than one result view. This is done by using the default instance of this + * result viewer as a "factory" for creating other instances. */ - public void setNode(Node selectedNode); + public DataResultViewer createInstance(); /** - * Gets the title of this viewer + * Indicates whether this result viewer is able to provide a meaningful view + * of the application data represented by a given node. Typically, indicates + * whether or not this result viewer can use the given node as the root node + * of its child explorer view component. + * + * @param node The node. + * + * @return True or false. */ - public String getTitle(); + public boolean isSupported(Node node); /** - * Get a new instance of DataResultViewer + * Sets the node for which this result viewer should provide a view of the + * underlying application data. Typically, this means using the given node + * as the root node of this result viewer's child explorer view component. + * + * @param node The node, may be null. If null, the call to this method is + * equivalent to a call to resetComponent. */ - public DataResultViewer createInstance(); + public void setNode(Node node); /** - * Get the Swing component (i.e. JPanel) for this viewer + * Requests selection of the given child nodes of the node passed to + * setNode. This method should be implemented as a no-op for result viewers + * that do not display the child nodes of a given root node using a NetBeans + * explorer view set up to use a given explorer manager. + * + * @param selectedNodes The child nodes to select. */ - public Component getComponent(); + default public void setSelectedNodes(Node[] selectedNodes) { + } /** - * Resets the viewer. + * Gets the title of this result viewer. + * + * @return The title. */ - public void resetComponent(); + public String getTitle(); /** - * Frees the objects that have been allocated by this viewer, in preparation - * for permanently disposing of it. + * Gets the Swing component for this viewer. + * + * @return The component. */ - public void clearComponent(); + public Component getComponent(); /** - * Expand node, if supported by the viewed - * - * @param n Node to expand + * Resets the state of the Swing component for this viewer to its default + * state. */ - public void expandNode(Node n); + default public void resetComponent() { + } /** - * Select the given node array + * Frees any resources tha have been allocated by this result viewer, in + * preparation for permanently disposing of it. */ - public void setSelectedNodes(Node[] selected); + default public void clearComponent() { + } /** - * Checks whether the currently selected root node is supported by this - * viewer + * Sets the node for which this result viewer should provide a view of the + * underlying application data model object, and expands the node. * - * @param selectedNode the selected node + * @param node The node. * - * @return True if supported, else false + * @deprecated This API is not used by the application. */ - public boolean isSupported(Node selectedNode); + @Deprecated + default public void expandNode(Node node) { + } /** - * Set a custom content viewer to respond to selection events from this - * result viewer. If not set, the default content viewer is used + * Sets a custom content viewer to which nodes selected in this result + * viewer should be pushed via DataContent.setNode. * - * @param contentViewer content viewer to respond to selection events from - * this viewer + * @param contentViewer The content viewer. * - * @deprecated All implementations of this in the standard DataResultViewers are now no-ops. + * @deprecated This API is not used by the application. */ @Deprecated - public void setContentViewer(DataContent contentViewer); + default public void setContentViewer(DataContent contentViewer) { + } + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java index 26d58d6efff09707dcbaafc816ec099b0cb0f733..fdabf16ac08db9300c6c6a1737e32cb176f51da6 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-17 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,88 +23,69 @@ import java.util.logging.Level; import javax.swing.JPanel; import org.openide.explorer.ExplorerManager; -import org.openide.explorer.ExplorerManager.Provider; import org.openide.nodes.Node; -import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.coreutils.Logger; /** - * This class provides a default implementation of selected methods of the - * DataResultViewer interface. Derived classes will be Swing JPanel objects. - * Additionally, the ExplorerManager.Provider interface is implemented to supply - * an ExplorerManager to derived classes and their child components. + * An abstract base class for an implementation of the result viewer interface + * that is a JPanel that displays the child nodes of a given node using a + * NetBeans explorer view as a child component. Such a result viewer should use + * the explorer manager of an ancestor top component to connect the lookups of + * the nodes displayed in the NetBeans explorer view to the actions global + * context. This class handles some key aspects of working with the ancestor top + * component's explorer manager. + * + * Instances of this class can be supplied with the top component's explorer + * manager during construction, but the typical use case is for the result + * viewer to find the ancestor top component's explorer manager at runtime. + * + * IMPORTANT: If the result viewer is going to find the ancestor top component's + * explorer manager at runtime, the first call to the getExplorerManager method + * of this class must be made AFTER the component hierarchy is fully + * constructed. + * */ -abstract class AbstractDataResultViewer extends JPanel implements DataResultViewer, Provider { +public abstract class AbstractDataResultViewer extends JPanel implements DataResultViewer, ExplorerManager.Provider { private static final Logger logger = Logger.getLogger(AbstractDataResultViewer.class.getName()); - protected transient ExplorerManager em; + private transient ExplorerManager explorerManager; /** - * This constructor is intended to allow an AbstractDataResultViewer to use - * an ExplorerManager provided by a TopComponent, allowing Node selections - * to be available to Actions via the action global context lookup when the - * TopComponent has focus. The ExplorerManager must be present when the - * object is constructed so that its child components can discover it using - * the ExplorerManager.find() method. + * Constructs an abstract base class for an implementation of the result + * viewer interface that is a JPanel that displays the child nodes of the + * given node using a NetBeans explorer view as a child component. * - * @param explorerManager - */ - AbstractDataResultViewer(ExplorerManager explorerManager) { - this.em = explorerManager; - } - - /** - * This constructor can be used by AbstractDataResultViewers that do not - * need to make Node selections available to Actions via the action global - * context lookup. + * @param explorerManager The explorer manager to use in the NetBeans + * explorer view child component of this result + * viewer, may be null. If null, the explorer manager + * will be discovered the first time + * getExplorerManager is called. */ - public AbstractDataResultViewer() { - this(new ExplorerManager()); - } - - @Override - public void clearComponent() { - } - - 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; + public AbstractDataResultViewer(ExplorerManager explorerManager) { + this.explorerManager = explorerManager; } @Override public ExplorerManager getExplorerManager() { - return this.em; + if (this.explorerManager == null) { + this.explorerManager = ExplorerManager.find(this); + } + return this.explorerManager; } @Override public void setSelectedNodes(Node[] selected) { try { - this.em.setSelectedNodes(selected); + this.getExplorerManager().setSelectedNodes(selected); } catch (PropertyVetoException ex) { - logger.log(Level.WARNING, "Couldn't set selected nodes.", ex); //NON-NLS + logger.log(Level.SEVERE, "Couldn't set selected nodes", ex); //NON-NLS } } - @Deprecated @Override - public void setContentViewer(DataContent contentViewer) { + public Component getComponent() { + return this; } + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index 245fe1744df0085b26961346953347f1470a51bb..c2a9d9845fc59fceec4e527bebbb88210f815df3 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -62,9 +62,6 @@ DataResultViewerThumbnail.filePathLabel.text=\ \ \ DataResultViewerThumbnail.goToPageLabel.text=Go to Page: DataResultViewerThumbnail.goToPageField.text= AdvancedConfigurationDialog.cancelButton.text=Cancel -DataResultPanel.directoryTablePath.text=directoryPath -DataResultPanel.numberMatchLabel.text=0 -DataResultPanel.matchLabel.text=Results DataContentViewerArtifact.waitText=Retrieving and preparing data, please wait... DataContentViewerArtifact.errorText=Error retrieving result DataContentViewerArtifact.title=Results @@ -181,3 +178,6 @@ AutopsyOptionsPanel.agencyLogoPreview.text=<html><div style='text-align: center; AutopsyOptionsPanel.logoPanel.border.title=Logo AutopsyOptionsPanel.runtimePanel.border.title=Runtime AutopsyOptionsPanel.viewPanel.border.title=View +DataResultPanel.matchLabel.text=Results +DataResultPanel.numberOfChildNodesLabel.text=0 +DataResultPanel.descriptionLabel.text=directoryPath diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties index 6505a09862f36f8457bd6f6a8a030421bfc182d7..cbdabb3747031a9a1a20bef1901e0e6d3bd44afe 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties @@ -37,9 +37,6 @@ DataResultViewerThumbnail.imagesRangeLabel.text=- DataResultViewerThumbnail.pageNumLabel.text=- DataResultViewerThumbnail.goToPageLabel.text=\u6b21\u306e\u30da\u30fc\u30b8\u306b\u79fb\u52d5\uff1a AdvancedConfigurationDialog.cancelButton.text=\u30ad\u30e3\u30f3\u30bb\u30eb -DataResultPanel.directoryTablePath.text=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30d1\u30b9 -DataResultPanel.numberMatchLabel.text=0 -DataResultPanel.matchLabel.text=\u7d50\u679c DataContentViewerArtifact.waitText=\u30c7\u30fc\u30bf\u3092\u53d6\u8fbc\u307f\u304a\u3088\u3073\u6e96\u5099\u4e2d\u3002\u3057\u3070\u3089\u304f\u304a\u5f85\u3061\u4e0b\u3055\u3044... DataContentViewerArtifact.errorText=\u7d50\u679c\u3092\u53d6\u8fbc\u307f\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f DataContentViewerArtifact.title=\u7d50\u679c @@ -131,3 +128,6 @@ DataResultViewerThumbnail.thumbnailSizeComboBox.small=\u30b5\u30e0\u30cd\u30a4\u MediaViewImagePanel.errorLabel.OOMText=\u30d5\u30a1\u30a4\u30eb\u3092\u30e1\u30c7\u30a3\u30a2\u30d3\u30e5\u30fc\u306b\u8aad\u307f\u8fbc\u3081\u307e\u305b\u3093\u3067\u3057\u305f\uff1a\u30e1\u30e2\u30ea\u4e0d\u8db3\u3002 MediaViewImagePanel.errorLabel.text=\u30d5\u30a1\u30a4\u30eb\u3092\u30e1\u30c7\u30a3\u30a2\u30d3\u30e5\u30fc\u306b\u8aad\u307f\u8fbc\u3081\u307e\u305b\u3093\u3067\u3057\u305f\u3002 MediaViewImagePanel.externalViewerButton.text=\u5916\u90e8\u30d3\u30e5\u30fc\u30a2\u30fc\u3067\u958b\u304f +DataResultPanel.matchLabel.text=\u7d50\u679c +DataResultPanel.numberOfChildNodesLabel.text=0 +DataResultPanel.descriptionLabel.text=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30d1\u30b9 diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.form index 0a6ea4b4a6622b8ad55bcacc74a09c16c8b1be2f..3c24e28182ac20e79536bbf35fae7de775180dcd 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.form @@ -25,13 +25,13 @@ <DimensionLayout dim="0"> <Group type="103" groupAlignment="0" attributes="0"> <Group type="102" attributes="0"> - <Component id="directoryTablePath" min="-2" max="-2" attributes="0"/> + <Component id="descriptionLabel" min="-2" max="-2" attributes="0"/> <EmptySpace max="32767" attributes="0"/> - <Component id="numberMatchLabel" min="-2" max="-2" attributes="0"/> + <Component id="numberOfChildNodesLabel" min="-2" max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/> <Component id="matchLabel" min="-2" max="-2" attributes="0"/> </Group> - <Component id="dataResultTabbedPanel" max="32767" attributes="0"/> + <Component id="resultViewerTabs" max="32767" attributes="0"/> </Group> </DimensionLayout> <DimensionLayout dim="1"> @@ -39,32 +39,32 @@ <Group type="102" attributes="0"> <Group type="103" groupAlignment="0" attributes="0"> <Group type="103" alignment="0" groupAlignment="3" attributes="0"> - <Component id="numberMatchLabel" alignment="3" min="-2" max="-2" attributes="0"/> + <Component id="numberOfChildNodesLabel" alignment="3" min="-2" max="-2" attributes="0"/> <Component id="matchLabel" alignment="3" min="-2" max="-2" attributes="0"/> </Group> - <Component id="directoryTablePath" alignment="0" min="-2" max="-2" attributes="0"/> + <Component id="descriptionLabel" alignment="0" min="-2" max="-2" attributes="0"/> </Group> <EmptySpace min="0" pref="0" max="-2" attributes="0"/> - <Component id="dataResultTabbedPanel" max="32767" attributes="0"/> + <Component id="resultViewerTabs" max="32767" attributes="0"/> </Group> </Group> </DimensionLayout> </Layout> <SubComponents> - <Component class="javax.swing.JLabel" name="directoryTablePath"> + <Component class="javax.swing.JLabel" name="descriptionLabel"> <Properties> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.directoryTablePath.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.descriptionLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> </Property> <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Dimension value="[5, 14]"/> </Property> </Properties> </Component> - <Component class="javax.swing.JLabel" name="numberMatchLabel"> + <Component class="javax.swing.JLabel" name="numberOfChildNodesLabel"> <Properties> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.numberMatchLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.numberOfChildNodesLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> </Property> </Properties> </Component> @@ -75,7 +75,7 @@ </Property> </Properties> </Component> - <Container class="javax.swing.JTabbedPane" name="dataResultTabbedPanel"> + <Container class="javax.swing.JTabbedPane" name="resultViewerTabs"> <Properties> <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Dimension value="[0, 5]"/> diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java index 9751bb024ae28b2e31c3c1151e4eb40120f4c693..c9bae6afb936f344b7584c3c4530a60f73aad3a8 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2013-2018 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,7 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import javax.swing.JTabbedPane; @@ -44,153 +45,204 @@ import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; /** - * A Swing JPanel with a JTabbedPane child component. The tabbed pane contains - * result viewers. + * A result view panel is a JPanel with a JTabbedPane child component that + * contains a collection of result viewers and implements the DataResult + * interface. The result viewers in a result view panel are either supplied + * during construction of the panel or are obtained from the result viewer + * extension point (DataResultViewer service providers). * - * The "main" DataResultPanel for the desktop application has a table viewer - * (DataResultViewerTable) and a thumbnail viewer (DataResultViewerThumbnail), - * plus zero to many additional DataResultViewers, since the DataResultViewer - * interface is an extension point. + * A result view panel provides an implementation of the setNode API of the the + * DataResult interface that pushes a given NetBeans Node into its child result + * viewers via the DataResultViewer.setNode API. The result viewers are + * responsible for providing a view of the application data represented by the + * node. A typical result viewer is a JPanel that displays the child nodes of + * the given node using a NetBeans explorer view child component. * - * The "main" DataResultPanel resides in the "main" results view - * (DataResultTopComponent) that is normally docked into the upper right hand - * side of the main window of the desktop application. + * A result panel should be child components of top components that are explorer + * manager providers. The parent top component is expected to expose a lookup + * maintained by its explorer manager to the actions global context. The child + * result view panel will then find the parent top component's explorer manager + * at runtime, so that it can act as an explorer manager provider for its child + * result viewers. This connects the nodes displayed in the result viewers to + * the actions global context. * - * The result viewers in the "main panel" are used to view the child nodes of a - * node selected in the tree view (DirectoryTreeTopComponent) that is normally - * docked into the left hand side of the main window of the desktop application. - * - * Nodes selected in the child results viewers of a DataResultPanel are - * displayed in a content view (implementation of the DataContent interface) - * supplied the panel. The default content view is (DataContentTopComponent) is - * normally docked into the lower right hand side of the main window, underneath - * the results view. A custom content view may be specified instead. + * All result view panels push single node selections in the child result + * viewers to either the "main" content view (DataContentTopComponent) that is + * normally docked into the lower right hand side of the main application + * window, or to a supplied custom content view (implements DataContent). */ public class DataResultPanel extends javax.swing.JPanel implements DataResult, ChangeListener, ExplorerManager.Provider { private static final long serialVersionUID = 1L; private static final int NO_TAB_SELECTED = -1; private static final String PLEASE_WAIT_NODE_DISPLAY_NAME = NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.pleasewaitNodeDisplayName"); - private final List<DataResultViewer> resultViewers = new ArrayList<>(); - private boolean isMain; + private final boolean isMain; + private final List<DataResultViewer> resultViewers; + private final ExplorerManagerListener explorerManagerListener; + private final RootNodeListener rootNodeListener; + private DataContent contentView; private ExplorerManager explorerManager; - private ExplorerManagerNodeSelectionListener emNodeSelectionListener; - private Node rootNode; - private final RootNodeListener rootNodeListener = new RootNodeListener(); + private Node currentRootNode; private boolean listeningToTabbedPane; - private DataContent contentView; /** - * Constructs and opens a DataResultPanel with the given initial data, and - * the default DataContent. + * Creates and opens a Swing JPanel with a JTabbedPane child component that + * contains instances of the result viewers (DataResultViewer) provided by + * the result viewer extension point (service providers that implement + * DataResultViewer). The result view panel will push single node selections + * from its child result viewers to the "main" content view that is normally + * docked into the lower right hand side of the main application window. * - * @param title The title for the panel. - * @param pathText Descriptive text about the source of the nodes - * displayed. - * @param rootNode The new root node. - * @param totalMatches Cardinality of root node's children + * @param title The title for the result view panel. + * @param description Descriptive text about the source of the nodes + * displayed. + * @param currentRootNode The current root (parent) node for the nodes + * displayed. May be changed by calling setNode. + * @param childNodeCount The cardinality of the root node's children. + * + * @return A result view panel. + */ + public static DataResultPanel createInstance(String title, String description, Node currentRootNode, int childNodeCount) { + DataResultPanel resultPanel = new DataResultPanel(title, false, Collections.emptyList(), null); + createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel); + resultPanel.open(); + return resultPanel; + } + + /** + * Creates and opens a Swing JPanel with a JTabbedPane child component that + * contains a given collection of result viewers (DataResultViewer) instead + * of the result viewers provided by the results viewer extension point. The + * result view panel will push single node selections from its child result + * viewers to the "main" content view that is normally docked into the lower + * right hand side of the main application window.. + * + * @param title The title for the result view panel. + * @param description Descriptive text about the source of the nodes + * displayed. + * @param currentRootNode The current root (parent) node for the nodes + * displayed. May be changed by calling setNode. + * @param childNodeCount The cardinality of the root node's children. + * @param viewers A collection of result viewers to use instead of + * the result viewers provided by the results viewer + * extension point. * - * @return A DataResultPanel instance. + * @return A result view panel. */ - public static DataResultPanel createInstance(String title, String pathText, Node rootNode, int totalMatches) { - DataResultPanel resultPanel = new DataResultPanel(title, false); - createInstanceCommon(title, pathText, rootNode, totalMatches, resultPanel); + public static DataResultPanel createInstance(String title, String description, Node currentRootNode, int childNodeCount, Collection<DataResultViewer> viewers) { + DataResultPanel resultPanel = new DataResultPanel(title, false, viewers, null); + createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel); resultPanel.open(); return resultPanel; } /** - * Constructs and opens a DataResultPanel with the given initial data, and a - * custom DataContent. + * Creates and opens a Swing JPanel with a JTabbedPane child component that + * contains instances of the result viewers (DataResultViewer) provided by + * the result viewer extension point (service providers that implement + * DataResultViewer). The result view panel will push single node selections + * from its child result viewers to the supplied custom content view. * - * @param title The title for the panel. - * @param pathText Descriptive text about the source of the nodes + * @param title The title for the result view panel. + * @param description Descriptive text about the source of the nodes * displayed. - * @param rootNode The new root node. - * @param totalMatches Cardinality of root node's children - * @param customContentView A content view to use in place of the default - * content view. + * @param currentRootNode The current root (parent) node for the nodes + * displayed. May be changed by calling setNode. + * @param childNodeCount The cardinality of the root node's children. + * @param customContentView A custom content view to use instead of the + * "main" content view that is normally docked into + * the lower right hand side of the main + * application window. * - * @return A DataResultPanel instance. + * @return A result view panel. */ - public static DataResultPanel createInstance(String title, String pathText, Node rootNode, int totalMatches, DataContent customContentView) { - DataResultPanel resultPanel = new DataResultPanel(title, customContentView); - createInstanceCommon(title, pathText, rootNode, totalMatches, resultPanel); + public static DataResultPanel createInstance(String title, String description, Node currentRootNode, int childNodeCount, DataContent customContentView) { + DataResultPanel resultPanel = new DataResultPanel(title, false, Collections.emptyList(), customContentView); + createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel); resultPanel.open(); return resultPanel; } /** - * Constructs a DataResultPanel with the given initial data, and a custom - * DataContent. The panel is NOT opened; the client of this method must call - * open on the panel that is returned. + * Creates, but does not open, a Swing JPanel with a JTabbedPane child + * component that contains instances of the result viewers + * (DataResultViewer) provided by the result viewer extension point (service + * providers that implement DataResultViewer). The result view panel will + * push single node selections from its child result viewers to the supplied + * custom content view. * - * @param title The title for the panel. - * @param pathText Descriptive text about the source of the nodes + * @param title The title for the result view panel. + * @param description Descriptive text about the source of the nodes * displayed. - * @param rootNode The new root node. - * @param totalMatches Cardinality of root node's children + * @param currentRootNode The current root (parent) node for the nodes + * displayed. May be changed by calling setNode. + * @param childNodeCount The cardinality of the root node's children. * @param customContentView A content view to use in place of the default * content view. * - * @return A DataResultPanel instance. + * @return A result view panel. */ - public static DataResultPanel createInstanceUninitialized(String title, String pathText, Node rootNode, int totalMatches, DataContent customContentView) { - DataResultPanel resultPanel = new DataResultPanel(title, customContentView); - createInstanceCommon(title, pathText, rootNode, totalMatches, resultPanel); + public static DataResultPanel createInstanceUninitialized(String title, String description, Node currentRootNode, int childNodeCount, DataContent customContentView) { + DataResultPanel resultPanel = new DataResultPanel(title, false, Collections.emptyList(), customContentView); + createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel); return resultPanel; } /** - * Executes code common to all of the DataSreultPanel factory methods. + * Executes code common to all of the result view panel factory methods. * - * @param title The title for the panel. - * @param pathText Descriptive text about the source of the nodes + * @param title The title for the result view panel. + * @param description Descriptive text about the source of the nodes * displayed. - * @param rootNode The new root node. - * @param totalMatches Cardinality of root node's children - * @param resultViewPanel A content view to use in place of the default - * content view. + * @param currentRootNode The current root (parent) node for the nodes + * displayed. May be changed by calling setNode. + * @param childNodeCount The cardinality of the root node's children. + * @param resultViewPanel A new results view panel. */ - private static void createInstanceCommon(String title, String pathText, Node rootNode, int totalMatches, DataResultPanel resultViewPanel) { + private static void createInstanceCommon(String title, String description, Node currentRootNode, int childNodeCount, DataResultPanel resultViewPanel) { resultViewPanel.setTitle(title); resultViewPanel.setName(title); - resultViewPanel.setNumMatches(totalMatches); - resultViewPanel.setNode(rootNode); - resultViewPanel.setPath(pathText); + resultViewPanel.setNumberOfChildNodes(childNodeCount); + resultViewPanel.setNode(currentRootNode); + resultViewPanel.setPath(description); } /** - * Constructs a DataResultPanel with the default DataContent + * Constructs a Swing JPanel with a JTabbedPane child component that + * contains a collection of result viewers that is either supplied or + * provided by the result viewer extension point. * - * @param title The title for the panel. - * @param isMain True if the DataResultPanel being constructed is the "main" - * DataResultPanel. + * @param title The title of the result view panel. + * @param isMain Whether or not the result view panel is the + * "main" instance of the panel that resides in the + * "main" results view (DataResultTopComponent) + * that is normally docked into the upper right + * hand side of the main application window. + * @param viewers A collection of result viewers to use instead of + * the result viewers provided by the results + * viewer extension point, may be empty. + * @param customContentView A custom content view to use instead of the + * "main" content view that is normally docked into + * the lower right hand side of the main + * application window, may be null. */ - DataResultPanel(String title, boolean isMain) { - this(isMain, Lookup.getDefault().lookup(DataContent.class)); - setTitle(title); - } - - private DataResultPanel(boolean isMain, DataContent contentView) { + DataResultPanel(String title, boolean isMain, Collection<DataResultViewer> viewers, DataContent customContentView) { + this.setTitle(title); this.isMain = isMain; - this.contentView = contentView; + if (customContentView == null) { + this.contentView = Lookup.getDefault().lookup(DataContent.class); + } else { + this.contentView = customContentView; + } + this.resultViewers = new ArrayList<>(viewers); + this.explorerManagerListener = new ExplorerManagerListener(); + this.rootNodeListener = new RootNodeListener(); initComponents(); } /** - * Constructs a DataResultPanel with the a custom DataContent. - * - * @param title The title for the panel. - * @param customContentView A content view to use in place of the default - * content view. - */ - DataResultPanel(String title, DataContent customContentView) { - this(false, customContentView); - } - - /** - * Gets the preferred identifier for this panel in the window system. + * Gets the preferred identifier for this result view panel in the window + * system. * * @return The preferred identifier. */ @@ -200,19 +252,7 @@ public String getPreferredID() { } /** - * Gets whether or not this panel is the "main" panel used to view the child - * nodes of a node selected in the tree view (DirectoryTreeTopComponent) - * that is normally docked into the left hand side of the main window. - * - * @return True or false. - */ - @Override - public boolean isMain() { - return this.isMain; - } - - /** - * Sets the title of this panel. + * Sets the title of this result view panel. * * @param title The title. */ @@ -223,27 +263,27 @@ public void setTitle(String title) { /** * Sets the descriptive text about the source of the nodes displayed in this - * panel. + * result view panel. * - * @param pathText The text to display. + * @param description The text to display. */ @Override - public void setPath(String pathText) { - this.directoryTablePath.setText(pathText); + public void setPath(String description) { + this.descriptionLabel.setText(description); } /** - * Adds a result viewer to this panel. + * Adds a results viewer to this result view panel. * - * @param resultViewer The result viewer. + * @param resultViewer The results viewer. */ public void addResultViewer(DataResultViewer resultViewer) { resultViewers.add(resultViewer); - dataResultTabbedPanel.addTab(resultViewer.getTitle(), resultViewer.getComponent()); + resultViewerTabs.addTab(resultViewer.getTitle(), resultViewer.getComponent()); } /** - * Gets the result viewers for this panel. + * Gets the result viewers for this result view panel. * * @return A list of result viewers. */ @@ -253,8 +293,8 @@ public List<DataResultViewer> getViewers() { } /** - * Sets the content view for this panel. Needs to be called before the first - * call to open. + * Sets the content view for this result view panel. Needs to be called + * before the first call to open. * * @param customContentView A content view to use in place of the default * content view. @@ -264,67 +304,60 @@ public void setContentViewer(DataContent customContentView) { } /** - * Initializes this panel. Intended to be called by a parent top component + * Opens this result view panel. Should be called by a parent top component * when the top component is opened. */ public void open() { - if (null == explorerManager) { - /* - * Get an explorer manager to pass to the child result viewers. If - * the application components are put together as expected, this - * will be an explorer manager owned by a parent top component, and - * placed by the top component in the look up that is proxied as the - * action global context when the top component has focus. The - * sharing of this explorer manager enables the same child node - * selections to be made in all of the result viewers. - */ - explorerManager = ExplorerManager.find(this); - emNodeSelectionListener = new ExplorerManagerNodeSelectionListener(); - explorerManager.addPropertyChangeListener(emNodeSelectionListener); + /* + * The parent top component is expected to be an explorer manager + * provider that exposes a lookup maintained by its explorer manager to + * the actions global context. The child result view panel will then + * find the parent top component's explorer manager at runtime, so that + * it can act as an explorer manager provider for its child result + * viewers. This connects the nodes displayed in the result viewers to + * the actions global context. + */ + if (this.explorerManager == null) { + this.explorerManager = ExplorerManager.find(this); + this.explorerManager.addPropertyChangeListener(this.explorerManagerListener); } /* - * Load the child result viewers into the tabbed pane. + * Load either the supplied result viewers or the result viewers + * provided by the result viewer extension point into the tabbed pane. + * If loading from the extension point and distinct result viewer + * instances MUST be created if this is not the "main" result view. */ - if (0 == dataResultTabbedPanel.getTabCount()) { - /* - * TODO (JIRA-2658): Fix the DataResultViewer extension point. When - * this is done, restore the implementation of DataResultViewerTable - * and DataREsultViewerThumbnail as DataResultViewer service - * providers. - */ - addResultViewer(new DataResultViewerTable(this.explorerManager)); - addResultViewer(new DataResultViewerThumbnail(this.explorerManager)); - for (DataResultViewer factory : Lookup.getDefault().lookupAll(DataResultViewer.class)) { - DataResultViewer resultViewer; - if (isMain) { - resultViewer = factory; - } else { - resultViewer = factory.createInstance(); + if (this.resultViewerTabs.getTabCount() == 0) { + if (this.resultViewers.isEmpty()) { + for (DataResultViewer resultViewer : Lookup.getDefault().lookupAll(DataResultViewer.class)) { + if (this.isMain) { + this.resultViewers.add(resultViewer); + } else { + this.resultViewers.add(resultViewer.createInstance()); + } } - addResultViewer(resultViewer); } - } - - if (isMain && null == rootNode) { - setNode(rootNode); + this.resultViewers.forEach((resultViewer) -> resultViewerTabs.addTab(resultViewer.getTitle(), resultViewer.getComponent())); } this.setVisible(true); } /** - * Sets the root node for this panel. The child nodes of the root node will - * be displayed in the result viewers. For the "main" panel, the root node - * is the currently selected node in the tree view docked into the left side - * of the main application window. + * Sets the current root node for this result view panel. The child nodes of + * the current root node will be displayed in the child result viewers. For + * the "main" panel, the root node is the currently selected node in the + * application tree view docked into the left side of the main application + * window. * - * @param rootNode The root node for this panel. + * @param rootNode The root node for this panel, may be null if the panel is + * to be reset. */ @Override public void setNode(Node rootNode) { - if (this.rootNode != null) { - this.rootNode.removeNodeListener(rootNodeListener); + if (this.currentRootNode != null) { + this.currentRootNode.removeNodeListener(rootNodeListener); } /* @@ -333,53 +366,54 @@ public void setNode(Node rootNode) { * construction. */ if (listeningToTabbedPane == false) { - dataResultTabbedPanel.addChangeListener(this); + resultViewerTabs.addChangeListener(this); listeningToTabbedPane = true; } - this.rootNode = rootNode; - if (this.rootNode != null) { + this.currentRootNode = rootNode; + if (this.currentRootNode != null) { rootNodeListener.reset(); - this.rootNode.addNodeListener(rootNodeListener); + this.currentRootNode.addNodeListener(rootNodeListener); } - resetTabs(this.rootNode); - setupTabs(this.rootNode); + this.resultViewers.forEach((viewer) -> { + viewer.resetComponent(); + }); + setupTabs(this.currentRootNode); - if (null != this.rootNode) { - int childrenCount = this.rootNode.getChildren().getNodesCount(); - this.numberMatchLabel.setText(Integer.toString(childrenCount)); + if (this.currentRootNode != null) { + int childrenCount = this.currentRootNode.getChildren().getNodesCount(); + this.numberOfChildNodesLabel.setText(Integer.toString(childrenCount)); } - this.numberMatchLabel.setVisible(true); + this.numberOfChildNodesLabel.setVisible(true); } /** - * Gets the root node of this panel. For the "main" panel, the root node is - * the currently selected node in the tree view docked into the left side of - * the main application window. + * Gets the root node of this result view panel. For the "main" panel, the + * root node is the currently selected node in the application tree view + * docked into the left side of the main application window. * * @return The root node. */ public Node getRootNode() { - return rootNode; + return currentRootNode; } /** - * Set number of child nodes displayed for the current root node. + * Sets the label text that displays the number of the child nodes displayed + * by this result view panel's result viewers. * - * @param numberOfChildNodes + * @param numberOfChildNodes The number of child nodes. */ - public void setNumMatches(Integer numberOfChildNodes) { - if (this.numberMatchLabel != null) { - this.numberMatchLabel.setText(Integer.toString(numberOfChildNodes)); - } + public void setNumberOfChildNodes(Integer numberOfChildNodes) { + this.numberOfChildNodesLabel.setText(Integer.toString(numberOfChildNodes)); } /** - * Sets the children of the root node that should be currently selected in - * this panel's result viewers. + * Selects the given child nodes of the root node in this panel's result + * viewers. * - * @param selectedNodes The nodes to be selected. + * @param selectedNodes The child nodes to be selected. */ public void setSelectedNodes(Node[] selectedNodes) { this.resultViewers.forEach((viewer) -> viewer.setSelectedNodes(selectedNodes)); @@ -396,11 +430,11 @@ private void setupTabs(Node selectedNode) { * Enable or disable the result viewer tabs based on whether or not the * corresponding results viewer supports display of the selected node. */ - for (int i = 0; i < dataResultTabbedPanel.getTabCount(); i++) { + for (int i = 0; i < resultViewerTabs.getTabCount(); i++) { if (resultViewers.get(i).isSupported(selectedNode)) { - dataResultTabbedPanel.setEnabledAt(i, true); + resultViewerTabs.setEnabledAt(i, true); } else { - dataResultTabbedPanel.setEnabledAt(i, false); + resultViewerTabs.setEnabledAt(i, false); } } @@ -415,17 +449,17 @@ private void setupTabs(Node selectedNode) { NodeSelectionInfo selectedChildInfo = ((TableFilterNode) selectedNode).getChildNodeSelectionInfo(); if (null != selectedChildInfo) { for (int i = 0; i < resultViewers.size(); ++i) { - if (resultViewers.get(i) instanceof DataResultViewerTable && dataResultTabbedPanel.isEnabledAt(i)) { + if (resultViewers.get(i) instanceof DataResultViewerTable && resultViewerTabs.isEnabledAt(i)) { tabToSelect = i; } } } }; - if (NO_TAB_SELECTED == tabToSelect) { - tabToSelect = dataResultTabbedPanel.getSelectedIndex(); - if ((NO_TAB_SELECTED == tabToSelect) || (!dataResultTabbedPanel.isEnabledAt(tabToSelect))) { - for (int i = 0; i < dataResultTabbedPanel.getTabCount(); ++i) { - if (dataResultTabbedPanel.isEnabledAt(i)) { + if (tabToSelect == NO_TAB_SELECTED) { + tabToSelect = resultViewerTabs.getSelectedIndex(); + if ((tabToSelect == NO_TAB_SELECTED) || (!resultViewerTabs.isEnabledAt(tabToSelect))) { + for (int i = 0; i < resultViewerTabs.getTabCount(); ++i) { + if (resultViewerTabs.isEnabledAt(i)) { tabToSelect = i; break; } @@ -434,27 +468,15 @@ private void setupTabs(Node selectedNode) { } /* - * If there is a tab to sele3ct, do so, and push the selected node to - * the corresponding result viewer. + * If there is a tab to select, do so, and push the selected node to the + * corresponding result viewer. */ - if (NO_TAB_SELECTED != tabToSelect) { - dataResultTabbedPanel.setSelectedIndex(tabToSelect); + if (tabToSelect != NO_TAB_SELECTED) { + resultViewerTabs.setSelectedIndex(tabToSelect); resultViewers.get(tabToSelect).setNode(selectedNode); } } - /** - * Resets the state of the child result viewers, based on a selected root - * node. - * - * @param unusedSelectedNode The selected node. - */ - public void resetTabs(Node unusedSelectedNode) { - this.resultViewers.forEach((viewer) -> { - viewer.resetComponent(); - }); - } - /** * Responds to a tab selection changed event by setting the root node of the * corresponding result viewer. @@ -465,57 +487,34 @@ public void resetTabs(Node unusedSelectedNode) { public void stateChanged(ChangeEvent event) { JTabbedPane pane = (JTabbedPane) event.getSource(); int currentTab = pane.getSelectedIndex(); - if (-1 != currentTab) { + if (currentTab != DataResultPanel.NO_TAB_SELECTED) { DataResultViewer currentViewer = this.resultViewers.get(currentTab); this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { - currentViewer.setNode(rootNode); + currentViewer.setNode(currentRootNode); } finally { - this.setCursor(null); + this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } } } /** - * Indicates whether or not this panel can be closed at the time of the - * call. - * - * @return True or false. - */ - public boolean canClose() { - /* - * If this is the "main" panel, only allow it to be closed when no case - * is open or no there are no data sources in the current case. - */ - Case openCase; - try { - openCase = Case.getOpenCase(); - } catch (NoCurrentCaseException ex) { - return true; - } - return (!this.isMain) || openCase.hasData() == false; - } - - /** - * Closes down the component. Intended to be called by the parent top + * Closes this reult view panel. Intended to be called by the parent top * component when it is closed. */ void close() { - if (null != explorerManager && null != emNodeSelectionListener) { - explorerManager.removePropertyChangeListener(emNodeSelectionListener); + if (explorerManager != null && explorerManagerListener != null) { + explorerManager.removePropertyChangeListener(explorerManagerListener); explorerManager = null; } this.resultViewers.forEach((viewer) -> viewer.setNode(null)); - if (!this.isMain) { + if (!this.isMain) { // RJCTODO: What? this.resultViewers.forEach(DataResultViewer::clearComponent); - this.directoryTablePath.removeAll(); - this.directoryTablePath = null; - this.numberMatchLabel.removeAll(); - this.numberMatchLabel = null; + this.descriptionLabel.removeAll(); + this.numberOfChildNodesLabel.removeAll(); this.matchLabel.removeAll(); - this.matchLabel = null; this.setLayout(null); this.removeAll(); this.setVisible(false); @@ -525,50 +524,37 @@ void close() { @Override public ExplorerManager getExplorerManager() { return explorerManager; + } /** - * Responds to node selection change events from the explorer manager. + * Responds to node selection change events from the explorer manager of + * this panel's parent top component. The selected nodes are passed to the + * content view. This is how the results view and the content view are kept + * in sync. It is therefore required that all of the result viewers in this + * panel use the explorer manager of the parent top component. This supports + * this way of passing the selection to the content view, plus the exposure + * of the selection to through the actions global context, which is needed + * for multiple selection. */ - private class ExplorerManagerNodeSelectionListener implements PropertyChangeListener { + private class ExplorerManagerListener implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { - try { - Case.getOpenCase(); - } catch (NoCurrentCaseException ex) { - return; - } - - /* - * Only interested in node selection events. - */ - if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - try { - if (contentView != null) { - Node[] selectedNodes = explorerManager.getSelectedNodes(); - - /* - * Pass the selected nodes to all of the result viewers - * sharing this explorer manager. - */ - resultViewers.forEach((viewer) -> viewer.setSelectedNodes(selectedNodes)); - - /* - * Passing null signals that either multiple nodes are - * selected, or no nodes are selected. This is important - * to the content view, since content views only work - * for a single node.. - */ - if (1 == selectedNodes.length) { - contentView.setNode(selectedNodes[0]); - } else { - contentView.setNode(null); - } - } - } finally { - setCursor(null); + if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES) && contentView != null) { + /* + * Pass a single node selection in a result viewer to the + * content view. Note that passing null to the content view + * signals that either multiple nodes are selected, or a + * previous selection has been cleared. This is important to the + * content view, since its child content viewers only work for a + * single node. + */ + Node[] selectedNodes = explorerManager.getSelectedNodes(); + if (1 == selectedNodes.length) { + contentView.setNode(selectedNodes[0]); + } else { + contentView.setNode(null); } } } @@ -578,6 +564,7 @@ public void propertyChange(PropertyChangeEvent evt) { * Responds to changes in the root node due to asynchronous child node * creation. */ + // RJCTODO: Why do we need this? private class RootNodeListener implements NodeListener { private volatile boolean waitingForData = true; @@ -623,8 +610,8 @@ private boolean containsReal(Node[] delta) { * */ private void updateMatches() { - if (rootNode != null && rootNode.getChildren() != null) { - setNumMatches(rootNode.getChildren().getNodesCount()); + if (currentRootNode != null && currentRootNode.getChildren() != null) { + setNumMatches(currentRootNode.getChildren().getNodesCount()); } } @@ -655,52 +642,93 @@ public void propertyChange(PropertyChangeEvent evt) { // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { - directoryTablePath = new javax.swing.JLabel(); - numberMatchLabel = new javax.swing.JLabel(); + descriptionLabel = new javax.swing.JLabel(); + numberOfChildNodesLabel = new javax.swing.JLabel(); matchLabel = new javax.swing.JLabel(); - dataResultTabbedPanel = new javax.swing.JTabbedPane(); + resultViewerTabs = new javax.swing.JTabbedPane(); setMinimumSize(new java.awt.Dimension(0, 5)); setPreferredSize(new java.awt.Dimension(5, 5)); - org.openide.awt.Mnemonics.setLocalizedText(directoryTablePath, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.directoryTablePath.text")); // NOI18N - directoryTablePath.setMinimumSize(new java.awt.Dimension(5, 14)); + org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.descriptionLabel.text")); // NOI18N + descriptionLabel.setMinimumSize(new java.awt.Dimension(5, 14)); - org.openide.awt.Mnemonics.setLocalizedText(numberMatchLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.numberMatchLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(numberOfChildNodesLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.numberOfChildNodesLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(matchLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.matchLabel.text")); // NOI18N - dataResultTabbedPanel.setMinimumSize(new java.awt.Dimension(0, 5)); + resultViewerTabs.setMinimumSize(new java.awt.Dimension(0, 5)); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(directoryTablePath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(descriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(numberMatchLabel) + .addComponent(numberOfChildNodesLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(matchLabel)) - .addComponent(dataResultTabbedPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(resultViewerTabs, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(numberMatchLabel) + .addComponent(numberOfChildNodesLabel) .addComponent(matchLabel)) - .addComponent(directoryTablePath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(descriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGap(0, 0, 0) - .addComponent(dataResultTabbedPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(resultViewerTabs, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); }// </editor-fold>//GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JTabbedPane dataResultTabbedPanel; - private javax.swing.JLabel directoryTablePath; + private javax.swing.JLabel descriptionLabel; private javax.swing.JLabel matchLabel; - private javax.swing.JLabel numberMatchLabel; + private javax.swing.JLabel numberOfChildNodesLabel; + private javax.swing.JTabbedPane resultViewerTabs; // End of variables declaration//GEN-END:variables + /** + * Gets whether or not this result view panel is the "main" result view + * panel used to view the child nodes of a node selected in the application + * tree view (DirectoryTreeTopComponent) that is normally docked into the + * left hand side of the main window. + * + * @return True or false. + * + * @Deprecated This method has no valid use case. + */ + @Deprecated + @Override + public boolean isMain() { + return this.isMain; + } + + /** + * Sets the label text that displays the number of the child nodes displayed + * by this result view panel's result viewers. + * + * @param numberOfChildNodes The number of child nodes. + * + * @deprecated Use setNumberOfChildNodes instead. + */ + @Deprecated + public void setNumMatches(Integer numberOfChildNodes) { + this.setNumberOfChildNodes(numberOfChildNodes); + } + + /** + * Resets the state of this results panel. + * + * @param unusedSelectedNode Unused. + * + * @deprecated Use setNode(null) instead. + */ + @Deprecated + public void resetTabs(Node unusedSelectedNode) { + this.setNode(null); + } + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.form index cb4669a51e93ba4b574830eb716116a84faf9a64..755df0fcf09565c51455bf4840c35c34773d3771 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.form @@ -28,10 +28,10 @@ <SubComponents> <Component class="org.sleuthkit.autopsy.corecomponents.DataResultPanel" name="dataResultPanelLocal"> <AuxValues> - <AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="dataResultPanel;"/> + <AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="resultViewersPanel;"/> <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/> <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/> </AuxValues> </Component> </SubComponents> -</Form> +</Form> \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java index 4a5d265a2c5a0ffb6661a9dba5af512a399250d0..797a8d526274247d59c65fd0b5a0f3df2704b45d 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.corecomponents; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.logging.Level; @@ -39,161 +40,220 @@ import org.sleuthkit.autopsy.coreutils.Logger; /** - * Top component which displays results (top-right editor mode by default). + * A DataResultTopComponent object is a NetBeans top component that provides + * multiple views of the application data represented by a NetBeans Node. It is + * a result view component (implements DataResult) that contains a result view + * panel (DataResultPanel), which is also a result view component. The result + * view panel is a JPanel with a JTabbedPane child component that contains a + * collection of result viewers. Each result viewer (implements + * DataResultViewer) presents a different view of the current node. Result + * viewers are usually JPanels that display the child nodes of the current node + * using a NetBeans explorer view child component. The result viewers are either + * supplied during construction of the result view top component or provided by + * the result viewer extension point (service providers that implement + * DataResultViewer). * - * There is a main tc instance that responds to directory tree selections. - * Others can also create an additional result viewer tc using one of the - * factory methods, that can be: + * Result view top components are typically docked into the upper right hand + * side of the main application window (editor mode), and are linked to the + * content view in the lower right hand side of the main application window + * (output mode) by the result view panel. The panel passes single node + * selections in the active result viewer to the content view. * - * - added to top-right corner as an additional, closeable viewer - added to a - * different, custom mode, - linked to a custom content viewer that responds to - * selections from this top component. + * The "main" result view top component receives its current node as a selection + * from the case tree view in the top component (DirectoryTreeYopComponent) + * docked into the left hand side of the main application window. * - * For embedding custom data result in other top components window, use - * DataResultPanel component instead, since we cannot nest top components. - * - * Encapsulates the internal DataResultPanel and delegates to it. - * - * Implements DataResult interface by delegating to the encapsulated - * DataResultPanel. + * Result view top components are explorer manager providers to connect the + * lookups of the nodes displayed in the NetBeans explorer views of the result + * viewers to the actions global context. */ @RetainLocation("editor") public class DataResultTopComponent extends TopComponent implements DataResult, ExplorerManager.Provider { private static final Logger logger = Logger.getLogger(DataResultTopComponent.class.getName()); - private final ExplorerManager explorerManager = new ExplorerManager(); - private final DataResultPanel dataResultPanel; //embedded component with all the logic - private boolean isMain; - private String customModeName; - - //keep track of tcs opened for menu presenters private static final List<String> activeComponentIds = Collections.synchronizedList(new ArrayList<String>()); + private final boolean isMain; + private final String customModeName; + private final ExplorerManager explorerManager; + private final DataResultPanel dataResultPanel; /** - * Create a new data result top component + * Creates a result view top component that provides multiple views of the + * application data represented by a NetBeans Node. The result view will be + * docked into the upper right hand side of the main application window + * (editor mode) and will be linked to the content view in the lower right + * hand side of the main application window (output mode). Its result + * viewers are provided by the result viewer extension point (service + * providers that implement DataResultViewer). * - * @param isMain whether it is the main, application default result viewer, - * there can be only 1 main result viewer - * @param title title of the data result window + * @param title The title for the top component, appears on the top + * component's tab. + * @param description Descriptive text about the node displayed, appears + * on the top component's tab + * @param node The node to display. + * @param childNodeCount The cardinality of the node's children. + * + * @return The result view top component. */ - public DataResultTopComponent(boolean isMain, String title) { - associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap())); - this.dataResultPanel = new DataResultPanel(title, isMain); - initComponents(); - customizeComponent(isMain, title); + public static DataResultTopComponent createInstance(String title, String description, Node node, int childNodeCount) { + DataResultTopComponent resultViewTopComponent = new DataResultTopComponent(false, title, null, Collections.emptyList(), null); + initInstance(description, node, childNodeCount, resultViewTopComponent); + return resultViewTopComponent; } /** - * Create a new, custom data result top component, in addition to the - * application main one + * Creates a result view top component that provides multiple views of the + * application data represented by a NetBeans Node. The result view will be + * docked into the upper right hand side of the main application window + * (editor mode) and will be linked to the content view in the lower right + * hand side of the main application window (output mode). * - * @param name unique name of the data result window, also - * used as title - * @param mode custom mode to dock into - * @param customContentViewer custom content viewer to send selection events - * to + * @param title The title for the top component, appears on the top + * component's tab. + * @param description Descriptive text about the node displayed, appears + * on the top component's tab + * @param node The node to display. + * @param childNodeCount The cardinality of the node's children. + * @param viewers A collection of result viewers to use instead of + * the result viewers provided by the results viewer + * extension point. + * + * @return The result view top component. */ - DataResultTopComponent(String name, String mode, DataContentTopComponent customContentViewer) { - associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap())); - this.customModeName = mode; - dataResultPanel = new DataResultPanel(name, customContentViewer); - initComponents(); - customizeComponent(isMain, name); + public static DataResultTopComponent createInstance(String title, String description, Node node, int childNodeCount, Collection<DataResultViewer> viewers) { + DataResultTopComponent resultViewTopComponent = new DataResultTopComponent(false, title, null, viewers, null); + initInstance(description, node, childNodeCount, resultViewTopComponent); + return resultViewTopComponent; } - private void customizeComponent(boolean isMain, String title) { - this.isMain = isMain; - this.customModeName = null; - - setToolTipText(NbBundle.getMessage(DataResultTopComponent.class, "HINT_NodeTableTopComponent")); - - setTitle(title); // set the title - setName(title); - getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(AddBookmarkTagAction.BOOKMARK_SHORTCUT, "addBookmarkTag"); //NON-NLS - getActionMap().put("addBookmarkTag", new AddBookmarkTagAction()); //NON-NLS - - putClientProperty(TopComponent.PROP_CLOSING_DISABLED, isMain); // set option to close compoment in GUI - putClientProperty(TopComponent.PROP_MAXIMIZATION_DISABLED, true); - putClientProperty(TopComponent.PROP_DRAGGING_DISABLED, true); - - activeComponentIds.add(title); + /** + * Creates a partially initialized result view top component that provides + * multiple views of the application data represented by a NetBeans Node. + * The result view will be docked into the upper right hand side of the main + * application window (editor mode) and will be linked to the content view + * in the lower right hand side of the main application window (output + * mode). Its result viewers are provided by the result viewer extension + * point (service providers that implement DataResultViewer). + * + * IMPORTANT: Initialization MUST be completed by calling initInstance. + * + * @param title The title for the result view top component, appears on the + * top component's tab. + * + * @return The partially initialized result view top component. + */ + public static DataResultTopComponent createInstance(String title) { + DataResultTopComponent resultViewTopComponent = new DataResultTopComponent(false, title, null, Collections.emptyList(), null); + return resultViewTopComponent; } /** - * Initialize previously created tc instance with additional data + * Initializes a partially initialized result view top component. * - * @param pathText - * @param givenNode - * @param totalMatches - * @param newDataResult previously created with createInstance() - * uninitialized instance + * @param description Descriptive text about the node displayed, + * appears on the top component's tab + * @param node The node to display. + * @param childNodeCount The cardinality of the node's children. + * @param resultViewTopComponent The partially initialized result view top + * component. */ - public static void initInstance(String pathText, Node givenNode, int totalMatches, DataResultTopComponent newDataResult) { - newDataResult.setNumMatches(totalMatches); - - newDataResult.open(); // open it first so the component can be initialized - - // set the tree table view - newDataResult.setNode(givenNode); - newDataResult.setPath(pathText); - - newDataResult.requestActive(); + public static void initInstance(String description, Node node, int childNodeCount, DataResultTopComponent resultViewTopComponent) { + resultViewTopComponent.setNumberOfChildNodes(childNodeCount); + resultViewTopComponent.open(); + resultViewTopComponent.setNode(node); + resultViewTopComponent.setPath(description); + resultViewTopComponent.requestActive(); } /** - * Creates a new non-default DataResult component and initializes it + * Creates a result view top component that provides multiple views of the + * application data represented by a NetBeans Node. The result view will be + * docked into a custom mode and linked to the supplied content view. Its + * result viewers are provided by the result viewer extension point (service + * providers that implement DataResultViewer). * - * @param title Title of the component window - * @param pathText Descriptive text about the source of the nodes - * displayed - * @param givenNode The new root node - * @param totalMatches Cardinality of root node's children + * @param title The title for the top component, appears + * on the top component's tab. + * @param mode The NetBeans Window system mode into which + * this top component should be docked. + * @param description Descriptive text about the node displayed, + * appears on the top component's tab + * @param node The node to display. + * @param childNodeCount The cardinality of the node's children. + * @param contentViewTopComponent A content view to which this result view + * will be linked. * - * @return a new, not default, initialized DataResultTopComponent instance + * @return The result view top component. */ - public static DataResultTopComponent createInstance(String title, String pathText, Node givenNode, int totalMatches) { - DataResultTopComponent newDataResult = new DataResultTopComponent(false, title); - - initInstance(pathText, givenNode, totalMatches, newDataResult); - + public static DataResultTopComponent createInstance(String title, String mode, String description, Node node, int childNodeCount, DataContentTopComponent contentViewTopComponent) { + DataResultTopComponent newDataResult = new DataResultTopComponent(false, title, mode, Collections.emptyList(), contentViewTopComponent); + initInstance(description, node, childNodeCount, newDataResult); return newDataResult; } /** - * Creates a new non-default DataResult component linked with a custom data - * content, and initializes it. + * Creates a result view top component that provides multiple views of the + * application data represented by a NetBeans Node. The result view will be + * the "main" result view and will docked into the upper right hand side of + * the main application window (editor mode) and will be linked to the + * content view in the lower right hand side of the main application window + * (output mode). Its result viewers are provided by the result viewer + * extension point (service providers that implement DataResultViewer). * + * IMPORTANT: The "main" result view top component receives its current node + * as a selection from the case tree view in the top component + * (DirectoryTreeTopComponent) docked into the left hand side of the main + * application window. This constructor is RESERVED for the use of the + * DirectoryTreeTopComponent singleton only. DO NOT USE OTHERWISE. * - * @param title Title of the component window - * @param mode custom mode to dock this custom TopComponent to - * @param pathText Descriptive text about the source of the nodes - * displayed - * @param givenNode The new root node - * @param totalMatches Cardinality of root node's children - * @param dataContentWindow a handle to data content top component window to - * - * @return a new, not default, initialized DataResultTopComponent instance + * @param title The title for the top component, appears on the top + * component's tab. */ - public static DataResultTopComponent createInstance(String title, final String mode, String pathText, Node givenNode, int totalMatches, DataContentTopComponent dataContentWindow) { - DataResultTopComponent newDataResult = new DataResultTopComponent(title, mode, dataContentWindow); - - initInstance(pathText, givenNode, totalMatches, newDataResult); - return newDataResult; + public DataResultTopComponent(String title) { + this(true, title, null, Collections.emptyList(), null); } /** - * Creates a new non-default DataResult component. You probably want to use - * initInstance after it - * - * @param title + * Constructs a result view top component that provides multiple views of + * the application data represented by a NetBeans Node. * - * @return a new, not default, not fully initialized DataResultTopComponent - * instance + * @param isMain Whether or not this is the "main" result + * view top component. + * @param title The title for the top component, appears + * on the top component's tab. + * @param mode The NetBeans Window system mode into which + * this top component should be docked. If + * null, the editor mode will be used by + * default. + * @param viewers A collection of result viewers. If empty, + * the result viewers provided by the results + * viewer extension point will be used. + * @param contentViewTopComponent A content view to which this result view + * will be linked. If null, this result view + * will be linked to the content view docked + * into the lower right hand side of the main + * application window, */ - public static DataResultTopComponent createInstance(String title) { - final DataResultTopComponent newDataResult = new DataResultTopComponent(false, title); + private DataResultTopComponent(boolean isMain, String title, String mode, Collection<DataResultViewer> viewers, DataContentTopComponent contentViewTopComponent) { + this.isMain = isMain; + this.explorerManager = new ExplorerManager(); + associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap())); + this.customModeName = mode; + this.dataResultPanel = new DataResultPanel(title, isMain, viewers, contentViewTopComponent); + initComponents(); + customizeComponent(title); + } - return newDataResult; + private void customizeComponent(String title) { + setToolTipText(NbBundle.getMessage(DataResultTopComponent.class, "HINT_NodeTableTopComponent")); //NON-NLS + setTitle(title); + setName(title); + getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(AddBookmarkTagAction.BOOKMARK_SHORTCUT, "addBookmarkTag"); //NON-NLS + getActionMap().put("addBookmarkTag", new AddBookmarkTagAction()); //NON-NLS + putClientProperty(TopComponent.PROP_CLOSING_DISABLED, isMain); + putClientProperty(TopComponent.PROP_MAXIMIZATION_DISABLED, true); + putClientProperty(TopComponent.PROP_DRAGGING_DISABLED, true); + activeComponentIds.add(title); } @Override @@ -202,38 +262,15 @@ public ExplorerManager getExplorerManager() { } /** - * Get a list with names of active windows ids, e.g. for the menus + * Get a listing of the preferred identifiers of all the result view top + * components that have been created. * - * @return + * @return The listing. */ public static List<String> getActiveComponentIds() { return new ArrayList<>(activeComponentIds); } - /** - * 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. - */ - // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents - private void initComponents() { - - org.sleuthkit.autopsy.corecomponents.DataResultPanel dataResultPanelLocal = dataResultPanel; - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(dataResultPanelLocal, javax.swing.GroupLayout.DEFAULT_SIZE, 967, Short.MAX_VALUE) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(dataResultPanelLocal, javax.swing.GroupLayout.DEFAULT_SIZE, 579, Short.MAX_VALUE) - ); - }// </editor-fold>//GEN-END:initComponents - // Variables declaration - do not modify//GEN-BEGIN:variables - // End of variables declaration//GEN-END:variables - @Override public int getPersistenceType() { if (customModeName == null) { @@ -245,16 +282,6 @@ public int getPersistenceType() { @Override public void open() { - setCustomMode(); - super.open(); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public List<DataResultViewer> getViewers() { - return dataResultPanel.getViewers(); - } - - private void setCustomMode() { if (customModeName != null) { Mode mode = WindowManager.getDefault().findMode(customModeName); if (mode != null) { @@ -264,6 +291,12 @@ private void setCustomMode() { logger.log(Level.WARNING, "Could not find mode: {0}, will dock into the default one", customModeName);//NON-NLS } } + super.open(); + } + + @Override + public List<DataResultViewer> getViewers() { + return dataResultPanel.getViewers(); } @Override @@ -290,7 +323,7 @@ public void componentActivated() { } else { selectedNode = null; } - + /* * If the selected node of the content viewer is different than that of * the result viewer, the content viewer needs to be updated. Otherwise, @@ -343,31 +376,15 @@ public boolean isMain() { @Override public boolean canClose() { - /* - * If this is the results top component in the upper right of the main - * window, only allow it to be closed when there's no case opened or no - * data sources in the open case. - */ Case openCase; try { openCase = Case.getOpenCase(); - } catch (NoCurrentCaseException ex) { + } catch (NoCurrentCaseException unused) { return true; } return (!this.isMain) || openCase.hasData() == false; } - /** - * Resets the tabs based on the selected Node. If the selected node is null - * or not supported, disable that tab as well. - * - * @param selectedNode the selected content Node - */ - public void resetTabs(Node selectedNode) { - - dataResultPanel.resetTabs(selectedNode); - } - public void setSelectedNodes(Node[] selected) { dataResultPanel.setSelectedNodes(selected); } @@ -376,7 +393,74 @@ public Node getRootNode() { return dataResultPanel.getRootNode(); } - void setNumMatches(int matches) { - this.dataResultPanel.setNumMatches(matches); + /** + * Sets the cardinality of the current node's children + * + * @param childNodeCount The cardinality of the node's children. + */ + private void setNumberOfChildNodes(int childNodeCount) { + this.dataResultPanel.setNumberOfChildNodes(childNodeCount); + } + + /** + * 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. + */ + // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents + private void initComponents() { + + org.sleuthkit.autopsy.corecomponents.DataResultPanel dataResultPanelLocal = dataResultPanel; + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(dataResultPanelLocal, javax.swing.GroupLayout.DEFAULT_SIZE, 967, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(dataResultPanelLocal, javax.swing.GroupLayout.DEFAULT_SIZE, 579, Short.MAX_VALUE) + ); + }// </editor-fold>//GEN-END:initComponents + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables + + /** + * Creates a partially initialized result view top component that provides + * multiple views of the application data represented by a NetBeans Node. + * The result view will be docked into the upper right hand side of the main + * application window (editor mode) and will be linked to the content view + * in the lower right hand side of the main application window (output + * mode). Its result viewers are provided by the result viewer extension + * point (service providers that implement DataResultViewer). + * + * IMPORTANT: Initialization MUST be completed by calling initInstance. + * + * @param isMain Ignored. + * @param title The title for the top component, appears on the top + * component's tab. + * + * @deprecated Use an appropriate overload of createIntance instead. + */ + @Deprecated + public DataResultTopComponent(boolean isMain, String title) { + this(false, title, null, Collections.emptyList(), null); + } + + /** + * Sets the node for which this result view component should provide + * multiple views of the underlying application data. + * + * @param node The node, may be null. If null, the call to this method is + * equivalent to a call to resetComponent on this result view + * component's result viewers. + * + * @deprecated Use setNode instead. + */ + @Deprecated + public void resetTabs(Node node) { + dataResultPanel.setNode(node); } + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index fe21a4ca55999a1c1c0a41874a60ce9e9a631a99..ca0185ce292a9f0109daa0af3326ae6c14878d78 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -60,151 +60,150 @@ import org.openide.nodes.Node.Property; import org.openide.util.NbBundle; import org.openide.util.NbPreferences; +import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; /** - * A tabular viewer for the results view. + * A tabular result viewer that displays the children of the given root node + * using an OutlineView. * - * TODO (JIRA-2658): Fix DataResultViewer extension point. When this is done, - * restore implementation of DataResultViewerTable as a DataResultViewer service - * provider. + * Instances of this class should use the explorer manager of an ancestor top + * component to connect the lookups of the nodes displayed in the OutlineView to + * the actions global context. The explorer manager can be supplied during + * construction, but the typical use case is for the result viewer to find the + * ancestor top component's explorer manager at runtime. */ -//@ServiceProvider(service = DataResultViewer.class) -public class DataResultViewerTable extends AbstractDataResultViewer { +@ServiceProvider(service = DataResultViewer.class) +public final class DataResultViewerTable extends AbstractDataResultViewer { private static final long serialVersionUID = 1L; - private static final Logger logger = Logger.getLogger(DataResultViewerTable.class.getName()); @NbBundle.Messages("DataResultViewerTable.firstColLbl=Name") static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl(); - private static final Color TAGGED_COLOR = new Color(255, 255, 195); - + static private final Color TAGGED_ROW_COLOR = new Color(255, 255, 195); + private static final Logger logger = Logger.getLogger(DataResultViewerTable.class.getName()); private final String title; + private final Map<String, ETableColumn> columnMap; + private final Map<Integer, Property<?>> propertiesMap; + private final Outline outline; + private final TableListener outlineViewListener; + private Node rootNode; /** - * The properties map: - * - * stored value of column index -> property at that index - * - * We move around stored values instead of directly using the column indices - * in order to not override settings for a column that may not appear in the - * current table view due to its collection of its children's properties. - */ - private final Map<Integer, Property<?>> propertiesMap = new TreeMap<>(); - - /** - * Stores references to the actual table column objects, keyed by column - * name, so that we can check there visibility later in - * storeColumnVisibility(). + * Constructs a tabular result viewer that displays the children of the + * given root node using an OutlineView. The viewer should have an ancestor + * top component to connect the lookups of the nodes displayed in the + * OutlineView to the actions global context. The explorer manager will be + * discovered at runtime. */ - private final Map<String, ETableColumn> columnMap = new HashMap<>(); - - private Node currentRoot; - - /* - * Convience reference to internal Outline. - */ - private Outline outline; - - /** - * Listener for table model event and mouse clicks. - */ - private final TableListener tableListener; + public DataResultViewerTable() { + this(null, Bundle.DataResultViewerTable_title()); + } /** - * Creates a DataResultViewerTable object that is compatible with node - * multiple selection actions, and the default title. + * Constructs a tabular result viewer that displays the children of a given + * root node using an OutlineView. The viewer should have an ancestor top + * component to connect the lookups of the nodes displayed in the + * OutlineView to the actions global context. * - * @param explorerManager allow for explorer manager sharing + * @param explorerManager The explorer manager of the ancestor top + * component. */ public DataResultViewerTable(ExplorerManager explorerManager) { this(explorerManager, Bundle.DataResultViewerTable_title()); } /** - * Creates a DataResultViewerTable object that is compatible with node - * multiple selection actions, and a custom title. + * Constructs a tabular result viewer that displays the children of a given + * root node using an OutlineView with a given title. The viewer should have + * an ancestor top component to connect the lookups of the nodes displayed + * in the OutlineView to the actions global context. * - * @param explorerManager allow for explorer manager sharing - * @param title The custom title. + * @param explorerManager The explorer manager of the ancestor top + * component. + * @param title The title. */ public DataResultViewerTable(ExplorerManager explorerManager, String title) { super(explorerManager); this.title = title; - + this.columnMap = new HashMap<>(); + this.propertiesMap = new TreeMap<>(); + + /* + * Execute the code generated by the GUI builder. + */ initComponents(); - + + /* + * Configure the child OutlineView (explorer view) component. + */ outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE); outline = outlineView.getOutline(); outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - outline.setRootVisible(false); // don't show the root node + outline.setRootVisible(false); outline.setDragEnabled(false); outline.setDefaultRenderer(Object.class, new ColorTagCustomRenderer()); - // add a listener so that when columns are moved, the new order is stored - tableListener = new TableListener(); - outline.getColumnModel().addColumnModelListener(tableListener); - // the listener also moves columns back if user tries to move the first column out of place - outline.getTableHeader().addMouseListener(tableListener); + + /* + * Add a table listener to the child OutlineView (explorer view) to + * persist the order of the table columns when a column is moved. + */ + outlineViewListener = new TableListener(); + outline.getColumnModel().addColumnModelListener(outlineViewListener); + + /* + * Add a mouse listener to the child OutlineView (explorer view) to make + * sure the first column of the table is kept in place. + */ + outline.getTableHeader().addMouseListener(outlineViewListener); } /** - * Creates a DataResultViewerTable object that is NOT compatible with node - * multiple selection actions. + * Creates a new instance of a tabular result viewer that displays the + * children of a given root node using an OutlineView. This method exists to + * make it possible to use the default service provider instance of this + * class in the "main" results view of the application, while using distinct + * instances in other places in the UI. + * + * @return A new instance of a tabular result viewer, */ - public DataResultViewerTable() { - this(new ExplorerManager(),Bundle.DataResultViewerTable_title()); + @Override + public DataResultViewer createInstance() { + return new DataResultViewerTable(); } - /** - * Expand node - * - * @param n Node to expand + * Gets the title of this tabular result viewer. */ @Override - public void expandNode(Node n) { - super.expandNode(n); - - outlineView.expandNode(n); + @NbBundle.Messages("DataResultViewerTable.title=Table") + public String getTitle() { + return title; } /** - * 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. + * Indicates whether a given node is supported as a root node for this + * tabular viewer. + * + * @param candidateRootNode The candidate root node. + * + * @return */ - @SuppressWarnings("unchecked") - // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents - private void initComponents() { - - outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL); - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE) - ); - }// </editor-fold>//GEN-END:initComponents - // Variables declaration - do not modify//GEN-BEGIN:variables - private org.openide.explorer.view.OutlineView outlineView; - // End of variables declaration//GEN-END:variables - @Override - public boolean isSupported(Node selectedNode) { + public boolean isSupported(Node candidateRootNode) { return true; } + /** + * Sets the current root node of this tabular result viewer. + * + * @param rootNode The node to set as the current root node, possibly null. + */ @Override @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - public void setNode(Node selectedNode) { - + public void setNode(Node rootNode) { /* * The quick filter must be reset because when determining column width, * ETable.getRowCount is called, and the documentation states that quick @@ -213,30 +212,30 @@ public void setNode(Node selectedNode) { * model." */ outline.unsetQuickFilter(); - // change the cursor to "waiting cursor" for this operation + this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { - boolean hasChildren = false; - if (selectedNode != null) { - // @@@ This just did a DB round trip to get the count and the results were not saved... - hasChildren = selectedNode.getChildren().getNodesCount() > 0; - } - - if (hasChildren) { - currentRoot = selectedNode; - em.setRootContext(currentRoot); + /* + * If the given node is not null and has children, set it as the + * root context of the child OutlineView, otherwise make an + * "empty"node the root context. + * + * IMPORTANT NOTE: This is the first of many times where a + * getChildren call on the current root node causes all of the + * children of the root node to be created and defeats lazy child + * node creation, if it is enabled. It also likely leads to many + * case database round trips. + */ + if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) { + this.rootNode = rootNode; + this.getExplorerManager().setRootContext(this.rootNode); setupTable(); } else { Node emptyNode = new AbstractNode(Children.LEAF); - em.setRootContext(emptyNode); // make empty node + this.getExplorerManager().setRootContext(emptyNode); outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); - - /* - * Since we are modifying the columns, we don't want to listen - * to added/removed events as un-hide/hide. - */ - tableListener.listenToVisibilityChanges(false); - outlineView.setPropertyColumns(); // set the empty property header + outlineViewListener.listenToVisibilityChanges(false); + outlineView.setPropertyColumns(); } } finally { this.setCursor(null); @@ -244,17 +243,18 @@ public void setNode(Node selectedNode) { } /** - * Create Column Headers based on the Content represented by the Nodes in - * the table. Load persisted column order, sorting and visibility. + * Sets up the Outline view of this tabular result viewer by creating + * column headers based on the children of the current root node. The + * persisted column order, sorting and visibility is used. */ private void setupTable() { /* * Since we are modifying the columns, we don't want to listen to * added/removed events as un-hide/hide, until the table setup is done. */ - tableListener.listenToVisibilityChanges(false); + outlineViewListener.listenToVisibilityChanges(false); - /** + /* * OutlineView makes the first column be the result of * node.getDisplayName with the icon. This duplicates our first column, * which is the file name, etc. So, pop that property off the list, but @@ -286,7 +286,10 @@ private void setupTable() { setColumnWidths(); - //Load column sorting information from preferences file and apply it to columns. + /* + * Load column sorting information from preferences file and apply it to + * columns. + */ loadColumnSorting(); /* @@ -298,7 +301,10 @@ private void setupTable() { */ populateColumnMap(); - //Load column visibility information from preferences file and apply it to columns. + /* + * Load column visibility information from preferences file and apply it + * to columns. + */ loadColumnVisibility(); /* @@ -306,32 +312,36 @@ private void setupTable() { * it. */ SwingUtilities.invokeLater(() -> { - if (currentRoot instanceof TableFilterNode) { - NodeSelectionInfo selectedChildInfo = ((TableFilterNode) currentRoot).getChildNodeSelectionInfo(); + if (rootNode instanceof TableFilterNode) { + NodeSelectionInfo selectedChildInfo = ((TableFilterNode) rootNode).getChildNodeSelectionInfo(); if (null != selectedChildInfo) { - Node[] childNodes = currentRoot.getChildren().getNodes(true); + Node[] childNodes = rootNode.getChildren().getNodes(true); for (int i = 0; i < childNodes.length; ++i) { Node childNode = childNodes[i]; if (selectedChildInfo.matches(childNode)) { try { - em.setSelectedNodes(new Node[]{childNode}); + this.getExplorerManager().setSelectedNodes(new Node[]{childNode}); } catch (PropertyVetoException ex) { logger.log(Level.SEVERE, "Failed to select node specified by selected child info", ex); } break; } } - ((TableFilterNode) currentRoot).setChildNodeSelectionInfo(null); + ((TableFilterNode) rootNode).setChildNodeSelectionInfo(null); } } }); - //the table setup is done, so any added/removed events can now be treated as un-hide/hide. - tableListener.listenToVisibilityChanges(true); + /* + * The table setup is done, so any added/removed events can now be + * treated as un-hide/hide. + */ + outlineViewListener.listenToVisibilityChanges(true); } /* - * Populate the map with references to the column objects for use when + * Populates the column map for the child OutlineView of this tabular + * result viewer with references to the column objects for use when * loading/storing the visibility info. */ private void populateColumnMap() { @@ -348,8 +358,12 @@ private void populateColumnMap() { } } + /* + * Sets the column widths for the child OutlineView of this tabular results + * viewer. + */ private void setColumnWidths() { - if (currentRoot.getChildren().getNodesCount() != 0) { + if (rootNode.getChildren().getNodesCount() != 0) { final Graphics graphics = outlineView.getGraphics(); if (graphics != null) { final FontMetrics metrics = graphics.getFontMetrics(); @@ -385,8 +399,11 @@ private void setColumnWidths() { } } + /* + * Sets up the columns for the child OutlineView of this tabular results + * viewer with respect to column names and visisbility. + */ synchronized private void assignColumns(List<Property<?>> props) { - // Get the columns setup with respect to names and sortability String[] propStrings = new String[props.size() * 2]; for (int i = 0; i < props.size(); i++) { final Property<?> prop = props.get(i); @@ -399,29 +416,25 @@ synchronized private void assignColumns(List<Property<?>> props) { propStrings[2 * i] = prop.getName(); propStrings[2 * i + 1] = prop.getDisplayName(); } - outlineView.setPropertyColumns(propStrings); } /** - * Store the current column visibility information into a preference file. + * Persists the current column visibility information for the child + * OutlineView of this tabular result viewer using a preferences file. */ private synchronized void storeColumnVisibility() { - if (currentRoot == null || propertiesMap.isEmpty()) { + if (rootNode == null || propertiesMap.isEmpty()) { return; } - if (currentRoot instanceof TableFilterNode) { - TableFilterNode tfn = (TableFilterNode) currentRoot; + if (rootNode instanceof TableFilterNode) { + TableFilterNode tfn = (TableFilterNode) rootNode; final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); - - //store hidden state for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) { - String columnName = entry.getKey(); final String columnHiddenKey = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName); final TableColumn column = entry.getValue(); - boolean columnHidden = columnModel.isColumnHidden(column); if (columnHidden) { preferences.putBoolean(columnHiddenKey, true); @@ -433,16 +446,16 @@ private synchronized void storeColumnVisibility() { } /** - * Store the current column order information into a preference file. + * Persists the current column ordering for the child OutlineView of this + * tabular result viewer using a preferences file. */ private synchronized void storeColumnOrder() { - if (currentRoot == null || propertiesMap.isEmpty()) { + if (rootNode == null || propertiesMap.isEmpty()) { return; } - if (currentRoot instanceof TableFilterNode) { - TableFilterNode tfn = (TableFilterNode) currentRoot; + if (rootNode instanceof TableFilterNode) { + TableFilterNode tfn = (TableFilterNode) rootNode; final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); - // Store the current order of the columns into settings for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) { preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey()); @@ -451,20 +464,19 @@ private synchronized void storeColumnOrder() { } /** - * Store the current column sorting information into a preference file. + * Persists the current column sorting information using a preferences file. */ private synchronized void storeColumnSorting() { - if (currentRoot == null || propertiesMap.isEmpty()) { + if (rootNode == null || propertiesMap.isEmpty()) { return; } - if (currentRoot instanceof TableFilterNode) { - final TableFilterNode tfn = ((TableFilterNode) currentRoot); + if (rootNode instanceof TableFilterNode) { + final TableFilterNode tfn = ((TableFilterNode) rootNode); final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) { ETableColumn etc = entry.getValue(); String columnName = entry.getKey(); - //store sort rank and order final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName); final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, columnName); @@ -482,47 +494,43 @@ private synchronized void storeColumnSorting() { /** * Reads and applies the column sorting information persisted to the - * preferences file. Must be called after loadColumnOrder() since it depends - * on propertiesMap being initialized, and after assignColumns since it - * cannot set the sort on columns that have not been added to the table. + * preferences file. Must be called after loadColumnOrder, since it depends + * on the properties map being initialized, and after assignColumns, since + * it cannot set the sort on columns that have not been added to the table. */ private synchronized void loadColumnSorting() { - if (currentRoot == null || propertiesMap.isEmpty()) { + if (rootNode == null || propertiesMap.isEmpty()) { return; } - - if (currentRoot instanceof TableFilterNode) { - final TableFilterNode tfn = (TableFilterNode) currentRoot; - + if (rootNode instanceof TableFilterNode) { + final TableFilterNode tfn = (TableFilterNode) rootNode; final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); //organize property sorting information, sorted by rank TreeSet<ColumnSortInfo> sortInfos = new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank)); propertiesMap.entrySet().stream().forEach(entry -> { final String propName = entry.getValue().getName(); //if the sort rank is undefined, it will be defaulted to 0 => unsorted. - Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0); //default to true => ascending Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName), true); - sortInfos.add(new ColumnSortInfo(entry.getKey(), sortRank, sortOrder)); }); - //apply sort information in rank order. sortInfos.forEach(sortInfo -> outline.setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank)); } } + /** + * Reads and applies the column visibility information persisted to the + * preferences file. + */ private synchronized void loadColumnVisibility() { - if (currentRoot == null || propertiesMap.isEmpty()) { + if (rootNode == null || propertiesMap.isEmpty()) { return; } - - if (currentRoot instanceof TableFilterNode) { - + if (rootNode instanceof TableFilterNode) { final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); - - final TableFilterNode tfn = ((TableFilterNode) currentRoot); + final TableFilterNode tfn = ((TableFilterNode) rootNode); ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) { final String propName = entry.getValue().getName(); @@ -535,7 +543,7 @@ private synchronized void loadColumnVisibility() { /** * Gets a list of child properties (columns) in the order persisted in the - * preference file. Also initialized the propertiesMap with the column + * preference file. Also initialized the properties map with the column * order. * * @return a List<Node.Property<?>> of the properties in the persisted @@ -543,14 +551,14 @@ private synchronized void loadColumnVisibility() { */ private synchronized List<Node.Property<?>> loadColumnOrder() { - List<Property<?>> props = ResultViewerPersistence.getAllChildProperties(currentRoot, 100); + List<Property<?>> props = ResultViewerPersistence.getAllChildProperties(rootNode, 100); // If node is not table filter node, use default order for columns - if (!(currentRoot instanceof TableFilterNode)) { + if (!(rootNode instanceof TableFilterNode)) { return props; } - final TableFilterNode tfn = ((TableFilterNode) currentRoot); + final TableFilterNode tfn = ((TableFilterNode) rootNode); propertiesMap.clear(); /* @@ -587,24 +595,15 @@ private synchronized List<Node.Property<?>> loadColumnOrder() { return new ArrayList<>(propertiesMap.values()); } - @Override - @NbBundle.Messages("DataResultViewerTable.title=Table") - public String getTitle() { - return title; - } - - @Override - public DataResultViewer createInstance() { - return new DataResultViewerTable(); - } - + /** + * Frees the resources that have been allocated by this tabular results + * viewer, in preparation for permanently disposing of it. + */ @Override public void clearComponent() { this.outlineView.removeAll(); this.outlineView = null; - super.clearComponent(); - } /** @@ -775,8 +774,8 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col); // only override the color if a node is not selected - if (currentRoot != null && !isSelected) { - Node node = currentRoot.getChildren().getNodeAt(table.convertRowIndexToModel(row)); + if (rootNode != null && !isSelected) { + Node node = rootNode.getChildren().getNodeAt(table.convertRowIndexToModel(row)); boolean tagFound = false; if (node != null) { Node.PropertySet[] propSets = node.getPropertySets(); @@ -796,10 +795,37 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole } //if the node does have associated tags, set its background color if (tagFound) { - component.setBackground(TAGGED_COLOR); + component.setBackground(TAGGED_ROW_COLOR); } } return component; } } + + /** + * 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") + // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents + private void initComponents() { + + outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE) + ); + }// </editor-fold>//GEN-END:initComponents + // Variables declaration - do not modify//GEN-BEGIN:variables + private org.openide.explorer.view.OutlineView outlineView; + // End of variables declaration//GEN-END:variables + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index 6d5cd54f5b9f6a400de8ce54a7398ccb05a1305f..b1d268c5a41720d4d5c766431bdb42cb9f527a99 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-2017 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -50,6 +50,7 @@ import org.openide.nodes.NodeReorderEvent; import org.openide.util.NbBundle; import org.openide.util.NbPreferences; +import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import static org.sleuthkit.autopsy.corecomponents.Bundle.*; import org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.SortCriterion; @@ -59,64 +60,67 @@ import org.sleuthkit.datamodel.TskCoreException; /** - * A thumbnail viewer for the results view, with paging support. + * A thumbnail result viewer, with paging support, that displays the children of + * the given root node using an IconView. The paging is intended to reduce + * memory footprint by loading no more than two humdred images at a time. * - * The paging is intended to reduce memory footprint by load only up to - * (currently) 200 images at a time. This works whether or not the underlying - * content nodes are being lazy loaded or not. - * - * TODO (JIRA-2658): Fix DataResultViewer extension point. When this is done, - * restore implementation of DataResultViewerTable as a DataResultViewer service - * provider. + * Instances of this class should use the explorer manager of an ancestor top + * component to connect the lookups of the nodes displayed in the IconView to + * the actions global context. The explorer manager can be supplied during + * construction, but the typical use case is for the result viewer to find the + * ancestor top component's explorer manager at runtime. */ -//@ServiceProvider(service = DataResultViewer.class) -final class DataResultViewerThumbnail extends AbstractDataResultViewer { +@ServiceProvider(service = DataResultViewer.class) +public final class DataResultViewerThumbnail extends AbstractDataResultViewer { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName()); - private int curPage; + private final PageUpdater pageUpdater = new PageUpdater(); + private TableFilterNode rootNode; + private ThumbnailViewChildren rootNodeChildren; + private NodeSelectionListener selectionListener; + private int currentPage; private int totalPages; - private int curPageImages; + private int currentPageImages; private int thumbSize = ImageUtils.ICON_SIZE_MEDIUM; - private final PageUpdater pageUpdater = new PageUpdater(); - private TableFilterNode tfn; - private ThumbnailViewChildren tvc; /** - * Constructs a thumbnail viewer for the results view, with paging support, - * that is compatible with node multiple selection actions. - * - * @param explorerManager The shared ExplorerManager for the result viewers. + * Constructs a thumbnail result viewer, with paging support, that displays + * the children of the given root node using an IconView. The viewer should + * have an ancestor top component to connect the lookups of the nodes + * displayed in the IconView to the actions global context. The explorer + * manager will be discovered at runtime. */ - DataResultViewerThumbnail(ExplorerManager explorerManager) { - super(explorerManager); - initialize(); + public DataResultViewerThumbnail() { + this(null); } /** - * Constructs a thumbnail viewer for the results view, with paging support, - * that is NOT compatible with node multiple selection actions. + * Constructs a thumbnail result viewer, with paging support, that displays + * the children of the given root node using an IconView. The viewer should + * have an ancestor top component to connect the lookups of the nodes + * displayed in the IconView to the actions global context. + * + * @param explorerManager The explorer manager of the ancestor top + * component. */ - DataResultViewerThumbnail() { - initialize(); - } - - @NbBundle.Messages({"DataResultViewerThumbnail.thumbnailSizeComboBox.small=Small Thumbnails", + @NbBundle.Messages({ + "DataResultViewerThumbnail.thumbnailSizeComboBox.small=Small Thumbnails", "DataResultViewerThumbnail.thumbnailSizeComboBox.medium=Medium Thumbnails", "DataResultViewerThumbnail.thumbnailSizeComboBox.large=Large Thumbnails" }) - private void initialize() { + public DataResultViewerThumbnail(ExplorerManager explorerManager) { + super(explorerManager); initComponents(); iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - em.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener()); - thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>( - new String[]{Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_small(), - Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_medium(), - Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_large()})); + thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[]{ + Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_small(), + Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_medium(), + Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_large()})); thumbnailSizeComboBox.setSelectedIndex(1); - curPage = -1; + currentPage = -1; totalPages = 0; - curPageImages = 0; + currentPageImages = 0; } /** @@ -297,24 +301,22 @@ private void thumbnailSizeComboBoxActionPerformed(java.awt.event.ActionEvent evt if (thumbSize != newIconSize) { thumbSize = newIconSize; - Node root = em.getRootContext(); + Node root = this.getExplorerManager().getRootContext(); ((ThumbnailViewChildren) root.getChildren()).setThumbsSize(thumbSize); - - // Temporarily set the explored context to the root, instead of a child node. // This is a workaround hack to convince org.openide.explorer.ExplorerManager to // update even though the new and old Node values are identical. This in turn // will cause the entire view to update completely. After this we // immediately set the node back to the current child by calling switchPage(). - em.setExploredContext(root); + this.getExplorerManager().setExploredContext(root); switchPage(); } }//GEN-LAST:event_thumbnailSizeComboBoxActionPerformed private void sortButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sortButtonActionPerformed - List<Node.Property<?>> childProperties = ResultViewerPersistence.getAllChildProperties(em.getRootContext(), 100); - SortChooser sortChooser = new SortChooser(childProperties, ResultViewerPersistence.loadSortCriteria(tfn)); + List<Node.Property<?>> childProperties = ResultViewerPersistence.getAllChildProperties(this.getExplorerManager().getRootContext(), 100); + SortChooser sortChooser = new SortChooser(childProperties, ResultViewerPersistence.loadSortCriteria(rootNode)); DialogDescriptor dialogDescriptor = new DialogDescriptor(sortChooser, sortChooser.getDialogTitle()); Dialog createDialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor); createDialog.setVisible(true); @@ -335,8 +337,8 @@ private void sortButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FI Node.Property<?> prop = childProperties.get(i); String propName = prop.getName(); SortCriterion criterion = criteriaMap.get(prop); - final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, propName); - final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, propName); + final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(rootNode, propName); + final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(rootNode, propName); if (criterion != null) { preferences.putBoolean(columnSortOrderKey, criterion.getSortOrder() == SortOrder.ASCENDING); @@ -346,7 +348,7 @@ private void sortButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FI preferences.remove(columnSortRankKey); } } - setNode(tfn); //this is just to force a refresh + setNode(rootNode); //this is just to force a refresh } }//GEN-LAST:event_sortButtonActionPerformed @@ -379,28 +381,31 @@ public boolean isSupported(Node selectedNode) { @Override public void setNode(Node givenNode) { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - if (tvc != null) { - tvc.cancelLoadingThumbnails(); + if (selectionListener == null) { + this.getExplorerManager().addPropertyChangeListener(new NodeSelectionListener()); // RJCTODO: remove listener on cleanup + } + if (rootNodeChildren != null) { + rootNodeChildren.cancelLoadingThumbnails(); } try { if (givenNode != null) { - tfn = (TableFilterNode) givenNode; + rootNode = (TableFilterNode) givenNode; /* * Wrap the given node in a ThumbnailViewChildren that will * produce ThumbnailPageNodes with ThumbnailViewNode children * from the child nodes of the given node. */ - tvc = new ThumbnailViewChildren(givenNode,thumbSize); - final Node root = new AbstractNode(tvc); + rootNodeChildren = new ThumbnailViewChildren(givenNode, thumbSize); + final Node root = new AbstractNode(rootNodeChildren); pageUpdater.setRoot(root); root.addNodeListener(pageUpdater); - em.setRootContext(root); + this.getExplorerManager().setRootContext(root); } else { - tfn = null; - tvc = null; + rootNode = null; + rootNodeChildren = null; Node emptyNode = new AbstractNode(Children.LEAF); - em.setRootContext(emptyNode); + this.getExplorerManager().setRootContext(emptyNode); iconView.setBackground(Color.BLACK); } } finally { @@ -422,8 +427,8 @@ public DataResultViewer createInstance() { public void resetComponent() { super.resetComponent(); this.totalPages = 0; - this.curPage = -1; - curPageImages = 0; + this.currentPage = -1; + currentPageImages = 0; updateControls(); } @@ -435,15 +440,15 @@ public void clearComponent() { } private void nextPage() { - if (curPage < totalPages) { - curPage++; + if (currentPage < totalPages) { + currentPage++; switchPage(); } } private void previousPage() { - if (curPage > 1) { - curPage--; + if (currentPage > 1) { + currentPage--; switchPage(); } } @@ -465,7 +470,7 @@ private void goToPage(String pageNumText) { return; } - curPage = newPage; + currentPage = newPage; switchPage(); } @@ -488,10 +493,11 @@ protected Object doInBackground() throws Exception { NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.genThumbs")); progress.start(); progress.switchToIndeterminate(); - Node root = em.getRootContext(); - Node pageNode = root.getChildren().getNodeAt(curPage - 1); - em.setExploredContext(pageNode); - curPageImages = pageNode.getChildren().getNodesCount(); + ExplorerManager explorerManager = DataResultViewerThumbnail.this.getExplorerManager(); + Node root = explorerManager.getRootContext(); + Node pageNode = root.getChildren().getNodeAt(currentPage - 1); + explorerManager.setExploredContext(pageNode); + currentPageImages = pageNode.getChildren().getNodesCount(); return null; } @@ -504,8 +510,8 @@ protected void done() { try { get(); } catch (InterruptedException | ExecutionException ex) { - NotifyDescriptor d = - new NotifyDescriptor.Message( + NotifyDescriptor d + = new NotifyDescriptor.Message( NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.switchPage.done.errMsg", ex.getMessage()), NotifyDescriptor.ERROR_MESSAGE); @@ -534,20 +540,19 @@ private void updateControls() { sortLabel.setText(DataResultViewerThumbnail_sortLabel_text()); } else { - pageNumLabel.setText( - NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.pageNumbers.curOfTotal", - Integer.toString(curPage), Integer.toString(totalPages))); - final int imagesFrom = (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE + 1; - final int imagesTo = curPageImages + (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE; + pageNumLabel.setText(NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.pageNumbers.curOfTotal", + Integer.toString(currentPage), Integer.toString(totalPages))); + final int imagesFrom = (currentPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE + 1; + final int imagesTo = currentPageImages + (currentPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE; imagesRangeLabel.setText(imagesFrom + "-" + imagesTo); - pageNextButton.setEnabled(!(curPage == totalPages)); - pagePrevButton.setEnabled(!(curPage == 1)); + pageNextButton.setEnabled(!(currentPage == totalPages)); + pagePrevButton.setEnabled(!(currentPage == 1)); goToPageField.setEnabled(totalPages > 1); sortButton.setEnabled(true); thumbnailSizeComboBox.setEnabled(true); - if (tfn != null) { - String sortString = ResultViewerPersistence.loadSortCriteria(tfn).stream() + if (rootNode != null) { + String sortString = ResultViewerPersistence.loadSortCriteria(rootNode).stream() .map(SortCriterion::toString) .collect(Collectors.joining(" ")); sortString = StringUtils.defaultIfBlank(sortString, "---"); @@ -578,30 +583,30 @@ public void childrenAdded(NodeMemberEvent nme) { totalPages = root.getChildren().getNodesCount(); if (totalPages == 0) { - curPage = -1; + currentPage = -1; updateControls(); return; } - if (curPage == -1 || curPage > totalPages) { - curPage = 1; + if (currentPage == -1 || currentPage > totalPages) { + currentPage = 1; } //force load the curPage node - final Node pageNode = root.getChildren().getNodeAt(curPage - 1); + final Node pageNode = root.getChildren().getNodeAt(currentPage - 1); //em.setSelectedNodes(new Node[]{pageNode}); if (pageNode != null) { pageNode.addNodeListener(new NodeListener() { @Override public void childrenAdded(NodeMemberEvent nme) { - curPageImages = pageNode.getChildren().getNodesCount(); + currentPageImages = pageNode.getChildren().getNodesCount(); updateControls(); } @Override public void childrenRemoved(NodeMemberEvent nme) { - curPageImages = 0; + currentPageImages = 0; updateControls(); } @@ -618,7 +623,7 @@ public void propertyChange(PropertyChangeEvent evt) { } }); - em.setExploredContext(pageNode); + DataResultViewerThumbnail.this.getExplorerManager().setExploredContext(pageNode); } updateControls(); @@ -627,7 +632,7 @@ public void propertyChange(PropertyChangeEvent evt) { @Override public void childrenRemoved(NodeMemberEvent nme) { totalPages = 0; - curPage = -1; + currentPage = -1; updateControls(); } @@ -640,14 +645,14 @@ public void nodeDestroyed(NodeEvent ne) { } } - private class ExplorerManagerNodeSelectionListener implements PropertyChangeListener { + private class NodeSelectionListener implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { - Node[] selectedNodes = em.getSelectedNodes(); + Node[] selectedNodes = DataResultViewerThumbnail.this.getExplorerManager().getSelectedNodes(); if (selectedNodes.length == 1) { AbstractFile af = selectedNodes[0].getLookup().lookup(AbstractFile.class); if (af == null) { diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 13219f863d13cd981354321f96f2cdea8fa96f49..3069b7fb5d8792a34a27d33e59eca3f532e65c6e 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -101,7 +101,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat private final transient ExplorerManager em = new ExplorerManager(); private static DirectoryTreeTopComponent instance; - private final DataResultTopComponent dataResult = new DataResultTopComponent(true, Bundle.DirectoryTreeTopComponent_resultsView_title()); + private final DataResultTopComponent dataResult = new DataResultTopComponent(Bundle.DirectoryTreeTopComponent_resultsView_title()); private final LinkedList<String[]> backList; private final LinkedList<String[]> forwardList; private static final String PREFERRED_ID = "DirectoryTreeTopComponent"; //NON-NLS