Merge pull request #3726 from rcordovano/3726-result-view-infrastructure

3726 result view infrastructure
This commit is contained in:
Richard Cordovano 2018-04-27 20:12:59 -04:00 committed by GitHub
commit ef58e61759
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1110 additions and 925 deletions

View File

@ -128,7 +128,7 @@ public final class MessageBrowser extends JPanel implements ExplorerManager.Prov
public void propertyChange(PropertyChangeEvent pce) { public void propertyChange(PropertyChangeEvent pce) {
if (pce.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { if (pce.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
final Node[] selectedNodes = MessageBrowser.this.tableEM.getSelectedNodes(); final Node[] selectedNodes = MessageBrowser.this.tableEM.getSelectedNodes();
messagesResultPanel.setNumMatches(0); messagesResultPanel.setNumberOfChildNodes(0);
messagesResultPanel.setNode(null); messagesResultPanel.setNode(null);
messagesResultPanel.setPath(""); messagesResultPanel.setPath("");
if (selectedNodes.length > 0) { if (selectedNodes.length > 0) {

View File

@ -98,7 +98,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
@NbBundle.Messages("MessageContentViewer.AtrachmentsPanel.title=Attachments") @NbBundle.Messages("MessageContentViewer.AtrachmentsPanel.title=Attachments")
public MessageContentViewer() { public MessageContentViewer() {
initComponents(); 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); attachmentsScrollPane.setViewportView(drp);
msgbodyTabbedPane.setEnabledAt(ATTM_TAB_INDEX, true); msgbodyTabbedPane.setEnabledAt(ATTM_TAB_INDEX, true);

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011 Basis Technology Corp. * Copyright 2012-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -22,41 +22,58 @@ import java.util.List;
import org.openide.nodes.Node; 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 { 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(); 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); 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); 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(); public boolean isMain();

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-17 Basis Technology Corp. * Copyright 2012-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -22,76 +22,120 @@ import java.awt.Component;
import org.openide.nodes.Node; import org.openide.nodes.Node;
/** /**
* Interface for the different viewers that show a set of nodes in the * An interface for result viewers. A result viewer uses a Swing Component to
* DataResult area. AbstractDataResultViewer has default implementations for the * provide a view of the application data represented by a NetBeans Node passed
* action handlers. * 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 { public interface DataResultViewer {
/** /**
* Set the root node to display in this viewer. When called with null, must * Creates a new instance of this result viewer, which allows the
* clear all references to previous nodes. * 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
public void setNode(Node selectedNode); * result viewer as a "factory" for creating other instances.
/**
* Gets the title of this viewer
*/
public String getTitle();
/**
* Get a new instance of DataResultViewer
*/ */
public DataResultViewer createInstance(); public DataResultViewer createInstance();
/** /**
* Get the Swing component (i.e. JPanel) for 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 boolean isSupported(Node node);
/**
* 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 void setNode(Node node);
/**
* 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.
*/
default public void setSelectedNodes(Node[] selectedNodes) {
}
/**
* Gets the title of this result viewer.
*
* @return The title.
*/
public String getTitle();
/**
* Gets the Swing component for this viewer.
*
* @return The component.
*/ */
public Component getComponent(); public Component getComponent();
/** /**
* Resets the viewer. * Resets the state of the Swing component for this viewer to its default
* state.
*/ */
public void resetComponent(); default public void resetComponent() {
}
/** /**
* Frees the objects that have been allocated by this viewer, in preparation * Frees any resources tha have been allocated by this result viewer, in
* for permanently disposing of it. * preparation for permanently disposing of it.
*/ */
public void clearComponent(); default public void clearComponent() {
}
/** /**
* Expand node, if supported by the viewed * Sets the node for which this result viewer should provide a view of the
* underlying application data model object, and expands the node.
* *
* @param n Node to expand * @param node The node.
*/
public void expandNode(Node n);
/**
* Select the given node array
*/
public void setSelectedNodes(Node[] selected);
/**
* Checks whether the currently selected root node is supported by this
* viewer
* *
* @param selectedNode the selected node * @deprecated This API is not used by the application.
*
* @return True if supported, else false
*/
public boolean isSupported(Node selectedNode);
/**
* Set a custom content viewer to respond to selection events from this
* result viewer. If not set, the default content viewer is used
*
* @param contentViewer content viewer to respond to selection events from
* this viewer
*
* @deprecated All implementations of this in the standard DataResultViewers are now no-ops.
*/ */
@Deprecated @Deprecated
public void setContentViewer(DataContent contentViewer); default public void expandNode(Node node) {
}
/**
* Sets a custom content viewer to which nodes selected in this result
* viewer should be pushed via DataContent.setNode.
*
* @param contentViewer The content viewer.
*
* @deprecated This API is not used by the application.
*/
@Deprecated
default public void setContentViewer(DataContent contentViewer) {
}
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-17 Basis Technology Corp. * Copyright 2012-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -23,65 +23,64 @@ import java.beans.PropertyVetoException;
import java.util.logging.Level; import java.util.logging.Level;
import javax.swing.JPanel; import javax.swing.JPanel;
import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerManager.Provider;
import org.openide.nodes.Node; import org.openide.nodes.Node;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
/** /**
* This class provides a default implementation of selected methods of the * An abstract base class for an implementation of the result viewer interface
* DataResultViewer interface. Derived classes will be Swing JPanel objects. * that is a JPanel that displays the child nodes of a given node using a
* Additionally, the ExplorerManager.Provider interface is implemented to supply * NetBeans explorer view as a child component. Such a result viewer should use
* an ExplorerManager to derived classes and their child components. * 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()); 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 * Constructs an abstract base class for an implementation of the result
* an ExplorerManager provided by a TopComponent, allowing Node selections * viewer interface that is a JPanel that displays the child nodes of the
* to be available to Actions via the action global context lookup when the * given node using a NetBeans explorer view as a child component.
* 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.
* *
* @param explorerManager * @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.
*/ */
AbstractDataResultViewer(ExplorerManager explorerManager) { public AbstractDataResultViewer(ExplorerManager explorerManager) {
this.em = explorerManager; this.explorerManager = 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.
*/
public AbstractDataResultViewer() {
this(new ExplorerManager());
} }
@Override @Override
public void clearComponent() { public ExplorerManager getExplorerManager() {
if (this.explorerManager == null) {
this.explorerManager = ExplorerManager.find(this);
} }
return this.explorerManager;
public Node getSelectedNode() {
Node result = null;
Node[] selectedNodes = this.getExplorerManager().getSelectedNodes();
if (selectedNodes.length > 0) {
result = selectedNodes[0];
}
return result;
} }
@Override @Override
public void expandNode(Node n) { public void setSelectedNodes(Node[] selected) {
try {
this.getExplorerManager().setSelectedNodes(selected);
} catch (PropertyVetoException ex) {
logger.log(Level.SEVERE, "Couldn't set selected nodes", ex); //NON-NLS
} }
@Override
public void resetComponent() {
} }
@Override @Override
@ -89,22 +88,4 @@ abstract class AbstractDataResultViewer extends JPanel implements DataResultView
return this; return this;
} }
@Override
public ExplorerManager getExplorerManager() {
return this.em;
}
@Override
public void setSelectedNodes(Node[] selected) {
try {
this.em.setSelectedNodes(selected);
} catch (PropertyVetoException ex) {
logger.log(Level.WARNING, "Couldn't set selected nodes.", ex); //NON-NLS
}
}
@Deprecated
@Override
public void setContentViewer(DataContent contentViewer) {
}
} }

View File

@ -62,9 +62,6 @@ DataResultViewerThumbnail.filePathLabel.text=\ \ \
DataResultViewerThumbnail.goToPageLabel.text=Go to Page: DataResultViewerThumbnail.goToPageLabel.text=Go to Page:
DataResultViewerThumbnail.goToPageField.text= DataResultViewerThumbnail.goToPageField.text=
AdvancedConfigurationDialog.cancelButton.text=Cancel 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.waitText=Retrieving and preparing data, please wait...
DataContentViewerArtifact.errorText=Error retrieving result DataContentViewerArtifact.errorText=Error retrieving result
DataContentViewerArtifact.title=Results DataContentViewerArtifact.title=Results
@ -181,3 +178,6 @@ AutopsyOptionsPanel.agencyLogoPreview.text=<html><div style='text-align: center;
AutopsyOptionsPanel.logoPanel.border.title=Logo AutopsyOptionsPanel.logoPanel.border.title=Logo
AutopsyOptionsPanel.runtimePanel.border.title=Runtime AutopsyOptionsPanel.runtimePanel.border.title=Runtime
AutopsyOptionsPanel.viewPanel.border.title=View AutopsyOptionsPanel.viewPanel.border.title=View
DataResultPanel.matchLabel.text=Results
DataResultPanel.numberOfChildNodesLabel.text=0
DataResultPanel.descriptionLabel.text=directoryPath

View File

@ -37,9 +37,6 @@ DataResultViewerThumbnail.imagesRangeLabel.text=-
DataResultViewerThumbnail.pageNumLabel.text=- DataResultViewerThumbnail.pageNumLabel.text=-
DataResultViewerThumbnail.goToPageLabel.text=\u6b21\u306e\u30da\u30fc\u30b8\u306b\u79fb\u52d5\uff1a DataResultViewerThumbnail.goToPageLabel.text=\u6b21\u306e\u30da\u30fc\u30b8\u306b\u79fb\u52d5\uff1a
AdvancedConfigurationDialog.cancelButton.text=\u30ad\u30e3\u30f3\u30bb\u30eb 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.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.errorText=\u7d50\u679c\u3092\u53d6\u8fbc\u307f\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
DataContentViewerArtifact.title=\u7d50\u679c 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.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.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 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

View File

@ -25,13 +25,13 @@
<DimensionLayout dim="0"> <DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
<Group type="102" 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"/> <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"/> <EmptySpace max="-2" attributes="0"/>
<Component id="matchLabel" min="-2" max="-2" attributes="0"/> <Component id="matchLabel" min="-2" max="-2" attributes="0"/>
</Group> </Group>
<Component id="dataResultTabbedPanel" max="32767" attributes="0"/> <Component id="resultViewerTabs" max="32767" attributes="0"/>
</Group> </Group>
</DimensionLayout> </DimensionLayout>
<DimensionLayout dim="1"> <DimensionLayout dim="1">
@ -39,32 +39,32 @@
<Group type="102" attributes="0"> <Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
<Group type="103" alignment="0" groupAlignment="3" 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"/> <Component id="matchLabel" alignment="3" min="-2" max="-2" attributes="0"/>
</Group> </Group>
<Component id="directoryTablePath" alignment="0" min="-2" max="-2" attributes="0"/> <Component id="descriptionLabel" alignment="0" min="-2" max="-2" attributes="0"/>
</Group> </Group>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/> <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>
</Group> </Group>
</DimensionLayout> </DimensionLayout>
</Layout> </Layout>
<SubComponents> <SubComponents>
<Component class="javax.swing.JLabel" name="directoryTablePath"> <Component class="javax.swing.JLabel" name="descriptionLabel">
<Properties> <Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <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, &quot;{key}&quot;)"/> <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.descriptionLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property> </Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[5, 14]"/> <Dimension value="[5, 14]"/>
</Property> </Property>
</Properties> </Properties>
</Component> </Component>
<Component class="javax.swing.JLabel" name="numberMatchLabel"> <Component class="javax.swing.JLabel" name="numberOfChildNodesLabel">
<Properties> <Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <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, &quot;{key}&quot;)"/> <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultPanel.numberOfChildNodesLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property> </Property>
</Properties> </Properties>
</Component> </Component>
@ -75,7 +75,7 @@
</Property> </Property>
</Properties> </Properties>
</Component> </Component>
<Container class="javax.swing.JTabbedPane" name="dataResultTabbedPanel"> <Container class="javax.swing.JTabbedPane" name="resultViewerTabs">
<Properties> <Properties>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 5]"/> <Dimension value="[0, 5]"/>

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-2018 Basis Technology Corp. * Copyright 2013-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -22,6 +22,7 @@ import java.awt.Cursor;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import javax.swing.JTabbedPane; import javax.swing.JTabbedPane;
@ -44,153 +45,204 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo;
/** /**
* A Swing JPanel with a JTabbedPane child component. The tabbed pane contains * A result view panel is a JPanel with a JTabbedPane child component that
* result viewers. * 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 * A result view panel provides an implementation of the setNode API of the the
* (DataResultViewerTable) and a thumbnail viewer (DataResultViewerThumbnail), * DataResult interface that pushes a given NetBeans Node into its child result
* plus zero to many additional DataResultViewers, since the DataResultViewer * viewers via the DataResultViewer.setNode API. The result viewers are
* interface is an extension point. * 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 * A result panel should be child components of top components that are explorer
* (DataResultTopComponent) that is normally docked into the upper right hand * manager providers. The parent top component is expected to expose a lookup
* side of the main window of the desktop application. * 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 * All result view panels push single node selections in the child result
* node selected in the tree view (DirectoryTreeTopComponent) that is normally * viewers to either the "main" content view (DataContentTopComponent) that is
* docked into the left hand side of the main window of the desktop application. * normally docked into the lower right hand side of the main application
* * window, or to a supplied custom content view (implements DataContent).
* 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.
*/ */
public class DataResultPanel extends javax.swing.JPanel implements DataResult, ChangeListener, ExplorerManager.Provider { public class DataResultPanel extends javax.swing.JPanel implements DataResult, ChangeListener, ExplorerManager.Provider {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final int NO_TAB_SELECTED = -1; private static final int NO_TAB_SELECTED = -1;
private static final String PLEASE_WAIT_NODE_DISPLAY_NAME = NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.pleasewaitNodeDisplayName"); private static final String PLEASE_WAIT_NODE_DISPLAY_NAME = NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.pleasewaitNodeDisplayName");
private final List<DataResultViewer> resultViewers = new ArrayList<>(); private final boolean isMain;
private boolean isMain; private final List<DataResultViewer> resultViewers;
private ExplorerManager explorerManager; private final ExplorerManagerListener explorerManagerListener;
private ExplorerManagerNodeSelectionListener emNodeSelectionListener; private final RootNodeListener rootNodeListener;
private Node rootNode;
private final RootNodeListener rootNodeListener = new RootNodeListener();
private boolean listeningToTabbedPane;
private DataContent contentView; private DataContent contentView;
private ExplorerManager explorerManager;
private Node currentRootNode;
private boolean listeningToTabbedPane;
/** /**
* Constructs and opens a DataResultPanel with the given initial data, and * Creates and opens a Swing JPanel with a JTabbedPane child component that
* the default DataContent. * 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 title The title for the result view panel.
* @param pathText Descriptive text about the source of the nodes * @param description Descriptive text about the source of the nodes
* displayed. * displayed.
* @param rootNode The new root node. * @param currentRootNode The current root (parent) node for the nodes
* @param totalMatches Cardinality of root node's children * displayed. May be changed by calling setNode.
* @param childNodeCount The cardinality of the root node's children.
* *
* @return A DataResultPanel instance. * @return A result view panel.
*/ */
public static DataResultPanel createInstance(String title, String pathText, Node rootNode, int totalMatches) { public static DataResultPanel createInstance(String title, String description, Node currentRootNode, int childNodeCount) {
DataResultPanel resultPanel = new DataResultPanel(title, false); DataResultPanel resultPanel = new DataResultPanel(title, false, Collections.emptyList(), null);
createInstanceCommon(title, pathText, rootNode, totalMatches, resultPanel); createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel);
resultPanel.open(); resultPanel.open();
return resultPanel; return resultPanel;
} }
/** /**
* Constructs and opens a DataResultPanel with the given initial data, and a * Creates and opens a Swing JPanel with a JTabbedPane child component that
* custom DataContent. * 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 panel. * @param title The title for the result view panel.
* @param pathText Descriptive text about the source of the nodes * @param description Descriptive text about the source of the nodes
* displayed. * displayed.
* @param rootNode The new root node. * @param currentRootNode The current root (parent) node for the nodes
* @param totalMatches Cardinality of root node's children * displayed. May be changed by calling setNode.
* @param customContentView A content view to use in place of the default * @param childNodeCount The cardinality of the root node's children.
* content view. * @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, DataContent customContentView) { public static DataResultPanel createInstance(String title, String description, Node currentRootNode, int childNodeCount, Collection<DataResultViewer> viewers) {
DataResultPanel resultPanel = new DataResultPanel(title, customContentView); DataResultPanel resultPanel = new DataResultPanel(title, false, viewers, null);
createInstanceCommon(title, pathText, rootNode, totalMatches, resultPanel); createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel);
resultPanel.open(); resultPanel.open();
return resultPanel; return resultPanel;
} }
/** /**
* Constructs a DataResultPanel with the given initial data, and a custom * Creates and opens a Swing JPanel with a JTabbedPane child component that
* DataContent. The panel is NOT opened; the client of this method must call * contains instances of the result viewers (DataResultViewer) provided by
* open on the panel that is returned. * 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 title The title for the result view panel.
* @param pathText Descriptive text about the source of the nodes * @param description Descriptive text about the source of the nodes
* displayed. * displayed.
* @param rootNode The new root node. * @param currentRootNode The current root (parent) node for the nodes
* @param totalMatches Cardinality of root node's children * displayed. May be changed by calling setNode.
* @param customContentView A content view to use in place of the default * @param childNodeCount The cardinality of the root node's children.
* content view. * @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 createInstanceUninitialized(String title, String pathText, Node rootNode, int totalMatches, DataContent customContentView) { public static DataResultPanel createInstance(String title, String description, Node currentRootNode, int childNodeCount, DataContent customContentView) {
DataResultPanel resultPanel = new DataResultPanel(title, customContentView); DataResultPanel resultPanel = new DataResultPanel(title, false, Collections.emptyList(), customContentView);
createInstanceCommon(title, pathText, rootNode, totalMatches, resultPanel); createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel);
resultPanel.open();
return resultPanel; return resultPanel;
} }
/** /**
* Executes code common to all of the DataSreultPanel factory methods. * 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 title The title for the result view panel.
* @param pathText Descriptive text about the source of the nodes * @param description Descriptive text about the source of the nodes
* displayed. * displayed.
* @param rootNode The new root node. * @param currentRootNode The current root (parent) node for the nodes
* @param totalMatches Cardinality of root node's children * displayed. May be changed by calling setNode.
* @param resultViewPanel A content view to use in place of the default * @param childNodeCount The cardinality of the root node's children.
* @param customContentView A content view to use in place of the default
* content view. * content view.
*
* @return A result view panel.
*/ */
private static void createInstanceCommon(String title, String pathText, Node rootNode, int totalMatches, DataResultPanel resultViewPanel) { 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 result view panel factory methods.
*
* @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 resultViewPanel A new results view panel.
*/
private static void createInstanceCommon(String title, String description, Node currentRootNode, int childNodeCount, DataResultPanel resultViewPanel) {
resultViewPanel.setTitle(title); resultViewPanel.setTitle(title);
resultViewPanel.setName(title); resultViewPanel.setName(title);
resultViewPanel.setNumMatches(totalMatches); resultViewPanel.setNumberOfChildNodes(childNodeCount);
resultViewPanel.setNode(rootNode); resultViewPanel.setNode(currentRootNode);
resultViewPanel.setPath(pathText); 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 title The title of the result view panel.
* @param isMain True if the DataResultPanel being constructed is the "main" * @param isMain Whether or not the result view panel is the
* DataResultPanel. * "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) { DataResultPanel(String title, boolean isMain, Collection<DataResultViewer> viewers, DataContent customContentView) {
this(isMain, Lookup.getDefault().lookup(DataContent.class)); this.setTitle(title);
setTitle(title);
}
private DataResultPanel(boolean isMain, DataContent contentView) {
this.isMain = isMain; 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(); initComponents();
} }
/** /**
* Constructs a DataResultPanel with the a custom DataContent. * Gets the preferred identifier for this result view panel in the window
* * system.
* @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.
* *
* @return The preferred identifier. * @return The preferred identifier.
*/ */
@ -200,19 +252,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
} }
/** /**
* Gets whether or not this panel is the "main" panel used to view the child * Sets the title of this result view panel.
* 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.
* *
* @param title The title. * @param title The title.
*/ */
@ -223,27 +263,27 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
/** /**
* Sets the descriptive text about the source of the nodes displayed in this * 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 @Override
public void setPath(String pathText) { public void setPath(String description) {
this.directoryTablePath.setText(pathText); 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) { public void addResultViewer(DataResultViewer resultViewer) {
resultViewers.add(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. * @return A list of result viewers.
*/ */
@ -253,8 +293,8 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
} }
/** /**
* Sets the content view for this panel. Needs to be called before the first * Sets the content view for this result view panel. Needs to be called
* call to open. * before the first call to open.
* *
* @param customContentView A content view to use in place of the default * @param customContentView A content view to use in place of the default
* content view. * content view.
@ -264,67 +304,60 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
} }
/** /**
* 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. * when the top component is opened.
*/ */
public void open() { public void open() {
if (null == explorerManager) {
/* /*
* Get an explorer manager to pass to the child result viewers. If * The parent top component is expected to be an explorer manager
* the application components are put together as expected, this * provider that exposes a lookup maintained by its explorer manager to
* will be an explorer manager owned by a parent top component, and * the actions global context. The child result view panel will then
* placed by the top component in the look up that is proxied as the * find the parent top component's explorer manager at runtime, so that
* action global context when the top component has focus. The * it can act as an explorer manager provider for its child result
* sharing of this explorer manager enables the same child node * viewers. This connects the nodes displayed in the result viewers to
* selections to be made in all of the result viewers. * the actions global context.
*/ */
explorerManager = ExplorerManager.find(this); if (this.explorerManager == null) {
emNodeSelectionListener = new ExplorerManagerNodeSelectionListener(); this.explorerManager = ExplorerManager.find(this);
explorerManager.addPropertyChangeListener(emNodeSelectionListener); 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()) { if (this.resultViewerTabs.getTabCount() == 0) {
/* if (this.resultViewers.isEmpty()) {
* TODO (JIRA-2658): Fix the DataResultViewer extension point. When for (DataResultViewer resultViewer : Lookup.getDefault().lookupAll(DataResultViewer.class)) {
* this is done, restore the implementation of DataResultViewerTable if (this.isMain) {
* and DataREsultViewerThumbnail as DataResultViewer service this.resultViewers.add(resultViewer);
* 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 { } else {
resultViewer = factory.createInstance(); this.resultViewers.add(resultViewer.createInstance());
}
addResultViewer(resultViewer);
} }
} }
}
if (isMain && null == rootNode) { this.resultViewers.forEach((resultViewer) -> resultViewerTabs.addTab(resultViewer.getTitle(), resultViewer.getComponent()));
setNode(rootNode);
} }
this.setVisible(true); this.setVisible(true);
} }
/** /**
* Sets the root node for this panel. The child nodes of the root node will * Sets the current root node for this result view panel. The child nodes of
* be displayed in the result viewers. For the "main" panel, the root node * the current root node will be displayed in the child result viewers. For
* is the currently selected node in the tree view docked into the left side * the "main" panel, the root node is the currently selected node in the
* of the main application window. * 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 @Override
public void setNode(Node rootNode) { public void setNode(Node rootNode) {
if (this.rootNode != null) { if (this.currentRootNode != null) {
this.rootNode.removeNodeListener(rootNodeListener); this.currentRootNode.removeNodeListener(rootNodeListener);
} }
/* /*
@ -333,53 +366,54 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
* construction. * construction.
*/ */
if (listeningToTabbedPane == false) { if (listeningToTabbedPane == false) {
dataResultTabbedPanel.addChangeListener(this); resultViewerTabs.addChangeListener(this);
listeningToTabbedPane = true; listeningToTabbedPane = true;
} }
this.rootNode = rootNode; this.currentRootNode = rootNode;
if (this.rootNode != null) { if (this.currentRootNode != null) {
rootNodeListener.reset(); rootNodeListener.reset();
this.rootNode.addNodeListener(rootNodeListener); this.currentRootNode.addNodeListener(rootNodeListener);
} }
resetTabs(this.rootNode); this.resultViewers.forEach((viewer) -> {
setupTabs(this.rootNode); viewer.resetComponent();
});
setupTabs(this.currentRootNode);
if (null != this.rootNode) { if (this.currentRootNode != null) {
int childrenCount = this.rootNode.getChildren().getNodesCount(); int childrenCount = this.currentRootNode.getChildren().getNodesCount();
this.numberMatchLabel.setText(Integer.toString(childrenCount)); 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 * Gets the root node of this result view panel. For the "main" panel, the
* the currently selected node in the tree view docked into the left side of * root node is the currently selected node in the application tree view
* the main application window. * docked into the left side of the main application window.
* *
* @return The root node. * @return The root node.
*/ */
public Node getRootNode() { 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) { public void setNumberOfChildNodes(Integer numberOfChildNodes) {
if (this.numberMatchLabel != null) { this.numberOfChildNodesLabel.setText(Integer.toString(numberOfChildNodes));
this.numberMatchLabel.setText(Integer.toString(numberOfChildNodes));
}
} }
/** /**
* Sets the children of the root node that should be currently selected in * Selects the given child nodes of the root node in this panel's result
* this panel's result viewers. * viewers.
* *
* @param selectedNodes The nodes to be selected. * @param selectedNodes The child nodes to be selected.
*/ */
public void setSelectedNodes(Node[] selectedNodes) { public void setSelectedNodes(Node[] selectedNodes) {
this.resultViewers.forEach((viewer) -> viewer.setSelectedNodes(selectedNodes)); this.resultViewers.forEach((viewer) -> viewer.setSelectedNodes(selectedNodes));
@ -396,11 +430,11 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
* Enable or disable the result viewer tabs based on whether or not the * Enable or disable the result viewer tabs based on whether or not the
* corresponding results viewer supports display of the selected node. * 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)) { if (resultViewers.get(i).isSupported(selectedNode)) {
dataResultTabbedPanel.setEnabledAt(i, true); resultViewerTabs.setEnabledAt(i, true);
} else { } else {
dataResultTabbedPanel.setEnabledAt(i, false); resultViewerTabs.setEnabledAt(i, false);
} }
} }
@ -415,17 +449,17 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
NodeSelectionInfo selectedChildInfo = ((TableFilterNode) selectedNode).getChildNodeSelectionInfo(); NodeSelectionInfo selectedChildInfo = ((TableFilterNode) selectedNode).getChildNodeSelectionInfo();
if (null != selectedChildInfo) { if (null != selectedChildInfo) {
for (int i = 0; i < resultViewers.size(); ++i) { 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; tabToSelect = i;
} }
} }
} }
}; };
if (NO_TAB_SELECTED == tabToSelect) { if (tabToSelect == NO_TAB_SELECTED) {
tabToSelect = dataResultTabbedPanel.getSelectedIndex(); tabToSelect = resultViewerTabs.getSelectedIndex();
if ((NO_TAB_SELECTED == tabToSelect) || (!dataResultTabbedPanel.isEnabledAt(tabToSelect))) { if ((tabToSelect == NO_TAB_SELECTED) || (!resultViewerTabs.isEnabledAt(tabToSelect))) {
for (int i = 0; i < dataResultTabbedPanel.getTabCount(); ++i) { for (int i = 0; i < resultViewerTabs.getTabCount(); ++i) {
if (dataResultTabbedPanel.isEnabledAt(i)) { if (resultViewerTabs.isEnabledAt(i)) {
tabToSelect = i; tabToSelect = i;
break; break;
} }
@ -434,27 +468,15 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
} }
/* /*
* If there is a tab to sele3ct, do so, and push the selected node to * If there is a tab to select, do so, and push the selected node to the
* the corresponding result viewer. * corresponding result viewer.
*/ */
if (NO_TAB_SELECTED != tabToSelect) { if (tabToSelect != NO_TAB_SELECTED) {
dataResultTabbedPanel.setSelectedIndex(tabToSelect); resultViewerTabs.setSelectedIndex(tabToSelect);
resultViewers.get(tabToSelect).setNode(selectedNode); 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 * Responds to a tab selection changed event by setting the root node of the
* corresponding result viewer. * corresponding result viewer.
@ -465,57 +487,34 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
public void stateChanged(ChangeEvent event) { public void stateChanged(ChangeEvent event) {
JTabbedPane pane = (JTabbedPane) event.getSource(); JTabbedPane pane = (JTabbedPane) event.getSource();
int currentTab = pane.getSelectedIndex(); int currentTab = pane.getSelectedIndex();
if (-1 != currentTab) { if (currentTab != DataResultPanel.NO_TAB_SELECTED) {
DataResultViewer currentViewer = this.resultViewers.get(currentTab); DataResultViewer currentViewer = this.resultViewers.get(currentTab);
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try { try {
currentViewer.setNode(rootNode); currentViewer.setNode(currentRootNode);
} finally { } 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 * Closes this reult view panel. Intended to be called by the parent top
* 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
* component when it is closed. * component when it is closed.
*/ */
void close() { void close() {
if (null != explorerManager && null != emNodeSelectionListener) { if (explorerManager != null && explorerManagerListener != null) {
explorerManager.removePropertyChangeListener(emNodeSelectionListener); explorerManager.removePropertyChangeListener(explorerManagerListener);
explorerManager = null; explorerManager = null;
} }
this.resultViewers.forEach((viewer) -> viewer.setNode(null)); this.resultViewers.forEach((viewer) -> viewer.setNode(null));
if (!this.isMain) { if (!this.isMain) { // RJCTODO: What?
this.resultViewers.forEach(DataResultViewer::clearComponent); this.resultViewers.forEach(DataResultViewer::clearComponent);
this.directoryTablePath.removeAll(); this.descriptionLabel.removeAll();
this.directoryTablePath = null; this.numberOfChildNodesLabel.removeAll();
this.numberMatchLabel.removeAll();
this.numberMatchLabel = null;
this.matchLabel.removeAll(); this.matchLabel.removeAll();
this.matchLabel = null;
this.setLayout(null); this.setLayout(null);
this.removeAll(); this.removeAll();
this.setVisible(false); this.setVisible(false);
@ -525,52 +524,39 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
@Override @Override
public ExplorerManager getExplorerManager() { public ExplorerManager getExplorerManager() {
return explorerManager; 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 @Override
public void propertyChange(PropertyChangeEvent evt) { public void propertyChange(PropertyChangeEvent evt) {
try { if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES) && contentView != null) {
Case.getOpenCase();
} catch (NoCurrentCaseException ex) {
return;
}
/* /*
* Only interested in node selection events. * 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.
*/ */
if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
if (contentView != null) {
Node[] selectedNodes = explorerManager.getSelectedNodes(); 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) { if (1 == selectedNodes.length) {
contentView.setNode(selectedNodes[0]); contentView.setNode(selectedNodes[0]);
} else { } else {
contentView.setNode(null); contentView.setNode(null);
} }
} }
} finally {
setCursor(null);
}
}
} }
} }
@ -578,6 +564,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
* Responds to changes in the root node due to asynchronous child node * Responds to changes in the root node due to asynchronous child node
* creation. * creation.
*/ */
// RJCTODO: Why do we need this?
private class RootNodeListener implements NodeListener { private class RootNodeListener implements NodeListener {
private volatile boolean waitingForData = true; private volatile boolean waitingForData = true;
@ -623,8 +610,8 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
* *
*/ */
private void updateMatches() { private void updateMatches() {
if (rootNode != null && rootNode.getChildren() != null) { if (currentRootNode != null && currentRootNode.getChildren() != null) {
setNumMatches(rootNode.getChildren().getNodesCount()); setNumMatches(currentRootNode.getChildren().getNodesCount());
} }
} }
@ -655,52 +642,93 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() { private void initComponents() {
directoryTablePath = new javax.swing.JLabel(); descriptionLabel = new javax.swing.JLabel();
numberMatchLabel = new javax.swing.JLabel(); numberOfChildNodesLabel = new javax.swing.JLabel();
matchLabel = 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)); setMinimumSize(new java.awt.Dimension(0, 5));
setPreferredSize(new java.awt.Dimension(5, 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 org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.descriptionLabel.text")); // NOI18N
directoryTablePath.setMinimumSize(new java.awt.Dimension(5, 14)); 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 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); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout); this.setLayout(layout);
layout.setHorizontalGroup( layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup() .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) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(numberMatchLabel) .addComponent(numberOfChildNodesLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(matchLabel)) .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.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup() .addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(numberMatchLabel) .addComponent(numberOfChildNodesLabel)
.addComponent(matchLabel)) .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) .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 }// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JTabbedPane dataResultTabbedPanel; private javax.swing.JLabel descriptionLabel;
private javax.swing.JLabel directoryTablePath;
private javax.swing.JLabel matchLabel; 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 // 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);
}
} }

View File

@ -28,7 +28,7 @@
<SubComponents> <SubComponents>
<Component class="org.sleuthkit.autopsy.corecomponents.DataResultPanel" name="dataResultPanelLocal"> <Component class="org.sleuthkit.autopsy.corecomponents.DataResultPanel" name="dataResultPanelLocal">
<AuxValues> <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_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/> <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues> </AuxValues>

View File

@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.corecomponents; package org.sleuthkit.autopsy.corecomponents;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
@ -39,201 +40,237 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.coreutils.Logger; 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. * Result view top components are typically docked into the upper right hand
* Others can also create an additional result viewer tc using one of the * side of the main application window (editor mode), and are linked to the
* factory methods, that can be: * 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 * The "main" result view top component receives its current node as a selection
* different, custom mode, - linked to a custom content viewer that responds to * from the case tree view in the top component (DirectoryTreeYopComponent)
* selections from this top component. * docked into the left hand side of the main application window.
* *
* For embedding custom data result in other top components window, use * Result view top components are explorer manager providers to connect the
* DataResultPanel component instead, since we cannot nest top components. * lookups of the nodes displayed in the NetBeans explorer views of the result
* * viewers to the actions global context.
* Encapsulates the internal DataResultPanel and delegates to it.
*
* Implements DataResult interface by delegating to the encapsulated
* DataResultPanel.
*/ */
@RetainLocation("editor") @RetainLocation("editor")
public class DataResultTopComponent extends TopComponent implements DataResult, ExplorerManager.Provider { public class DataResultTopComponent extends TopComponent implements DataResult, ExplorerManager.Provider {
private static final Logger logger = Logger.getLogger(DataResultTopComponent.class.getName()); 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 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, * @param title The title for the top component, appears on the top
* there can be only 1 main result viewer * component's tab.
* @param title title of the data result window * @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) { public static DataResultTopComponent createInstance(String title, String description, Node node, int childNodeCount) {
associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap())); DataResultTopComponent resultViewTopComponent = new DataResultTopComponent(false, title, null, Collections.emptyList(), null);
this.dataResultPanel = new DataResultPanel(title, isMain); initInstance(description, node, childNodeCount, resultViewTopComponent);
initComponents(); return resultViewTopComponent;
customizeComponent(isMain, title);
} }
/** /**
* Create a new, custom data result top component, in addition to the * Creates a result view top component that provides multiple views of the
* application main one * 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 * @param title The title for the top component, appears on the top
* used as title * component's tab.
* @param mode custom mode to dock into * @param description Descriptive text about the node displayed, appears
* @param customContentViewer custom content viewer to send selection events * on the top component's tab
* to * @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) { 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;
}
/**
* 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;
}
/**
* Initializes a partially initialized result view top component.
*
* @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 description, Node node, int childNodeCount, DataResultTopComponent resultViewTopComponent) {
resultViewTopComponent.setNumberOfChildNodes(childNodeCount);
resultViewTopComponent.open();
resultViewTopComponent.setNode(node);
resultViewTopComponent.setPath(description);
resultViewTopComponent.requestActive();
}
/**
* 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 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 The result view top component.
*/
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 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 The title for the top component, appears on the top
* component's tab.
*/
public DataResultTopComponent(String title) {
this(true, title, null, Collections.emptyList(), null);
}
/**
* Constructs a result view top component that provides multiple views of
* the application data represented by a NetBeans Node.
*
* @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,
*/
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())); associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap()));
this.customModeName = mode; this.customModeName = mode;
dataResultPanel = new DataResultPanel(name, customContentViewer); this.dataResultPanel = new DataResultPanel(title, isMain, viewers, contentViewTopComponent);
initComponents(); initComponents();
customizeComponent(isMain, name); customizeComponent(title);
} }
private void customizeComponent(boolean isMain, String title) { private void customizeComponent(String title) {
this.isMain = isMain; setToolTipText(NbBundle.getMessage(DataResultTopComponent.class, "HINT_NodeTableTopComponent")); //NON-NLS
this.customModeName = null; setTitle(title);
setToolTipText(NbBundle.getMessage(DataResultTopComponent.class, "HINT_NodeTableTopComponent"));
setTitle(title); // set the title
setName(title); setName(title);
getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(AddBookmarkTagAction.BOOKMARK_SHORTCUT, "addBookmarkTag"); //NON-NLS getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(AddBookmarkTagAction.BOOKMARK_SHORTCUT, "addBookmarkTag"); //NON-NLS
getActionMap().put("addBookmarkTag", new AddBookmarkTagAction()); //NON-NLS getActionMap().put("addBookmarkTag", new AddBookmarkTagAction()); //NON-NLS
putClientProperty(TopComponent.PROP_CLOSING_DISABLED, isMain);
putClientProperty(TopComponent.PROP_CLOSING_DISABLED, isMain); // set option to close compoment in GUI
putClientProperty(TopComponent.PROP_MAXIMIZATION_DISABLED, true); putClientProperty(TopComponent.PROP_MAXIMIZATION_DISABLED, true);
putClientProperty(TopComponent.PROP_DRAGGING_DISABLED, true); putClientProperty(TopComponent.PROP_DRAGGING_DISABLED, true);
activeComponentIds.add(title); activeComponentIds.add(title);
} }
/**
* Initialize previously created tc instance with additional data
*
* @param pathText
* @param givenNode
* @param totalMatches
* @param newDataResult previously created with createInstance()
* uninitialized instance
*/
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();
}
/**
* Creates a new non-default DataResult component and initializes it
*
* @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
*
* @return a new, not default, initialized DataResultTopComponent instance
*/
public static DataResultTopComponent createInstance(String title, String pathText, Node givenNode, int totalMatches) {
DataResultTopComponent newDataResult = new DataResultTopComponent(false, title);
initInstance(pathText, givenNode, totalMatches, newDataResult);
return newDataResult;
}
/**
* Creates a new non-default DataResult component linked with a custom data
* content, and initializes it.
*
*
* @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
*/
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;
}
/**
* Creates a new non-default DataResult component. You probably want to use
* initInstance after it
*
* @param title
*
* @return a new, not default, not fully initialized DataResultTopComponent
* instance
*/
public static DataResultTopComponent createInstance(String title) {
final DataResultTopComponent newDataResult = new DataResultTopComponent(false, title);
return newDataResult;
}
@Override @Override
public ExplorerManager getExplorerManager() { public ExplorerManager getExplorerManager() {
return explorerManager; return explorerManager;
} }
/** /**
* 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() { public static List<String> getActiveComponentIds() {
return new ArrayList<>(activeComponentIds); 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 @Override
public int getPersistenceType() { public int getPersistenceType() {
if (customModeName == null) { if (customModeName == null) {
@ -245,16 +282,6 @@ public class DataResultTopComponent extends TopComponent implements DataResult,
@Override @Override
public void open() { 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) { if (customModeName != null) {
Mode mode = WindowManager.getDefault().findMode(customModeName); Mode mode = WindowManager.getDefault().findMode(customModeName);
if (mode != null) { if (mode != null) {
@ -264,6 +291,12 @@ public class DataResultTopComponent extends TopComponent implements DataResult,
logger.log(Level.WARNING, "Could not find mode: {0}, will dock into the default one", customModeName);//NON-NLS 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 @Override
@ -343,31 +376,15 @@ public class DataResultTopComponent extends TopComponent implements DataResult,
@Override @Override
public boolean canClose() { 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; Case openCase;
try { try {
openCase = Case.getOpenCase(); openCase = Case.getOpenCase();
} catch (NoCurrentCaseException ex) { } catch (NoCurrentCaseException unused) {
return true; return true;
} }
return (!this.isMain) || openCase.hasData() == false; 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) { public void setSelectedNodes(Node[] selected) {
dataResultPanel.setSelectedNodes(selected); dataResultPanel.setSelectedNodes(selected);
} }
@ -376,7 +393,74 @@ public class DataResultTopComponent extends TopComponent implements DataResult,
return dataResultPanel.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);
}
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-2017 Basis Technology Corp. * Copyright 2012-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -60,151 +60,150 @@ import org.openide.nodes.Node;
import org.openide.nodes.Node.Property; import org.openide.nodes.Node.Property;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.NbPreferences; import org.openide.util.NbPreferences;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; 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, * Instances of this class should use the explorer manager of an ancestor top
* restore implementation of DataResultViewerTable as a DataResultViewer service * component to connect the lookups of the nodes displayed in the OutlineView to
* provider. * 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) @ServiceProvider(service = DataResultViewer.class)
public class DataResultViewerTable extends AbstractDataResultViewer { public final class DataResultViewerTable extends AbstractDataResultViewer {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(DataResultViewerTable.class.getName());
@NbBundle.Messages("DataResultViewerTable.firstColLbl=Name") @NbBundle.Messages("DataResultViewerTable.firstColLbl=Name")
static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl(); 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 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: * 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.
*/
public DataResultViewerTable() {
this(null, Bundle.DataResultViewerTable_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.
* *
* stored value of column index -> property at that index * @param explorerManager The explorer manager of the ancestor top
* * component.
* 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().
*/
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;
/**
* Creates a DataResultViewerTable object that is compatible with node
* multiple selection actions, and the default title.
*
* @param explorerManager allow for explorer manager sharing
*/ */
public DataResultViewerTable(ExplorerManager explorerManager) { public DataResultViewerTable(ExplorerManager explorerManager) {
this(explorerManager, Bundle.DataResultViewerTable_title()); this(explorerManager, Bundle.DataResultViewerTable_title());
} }
/** /**
* Creates a DataResultViewerTable object that is compatible with node * Constructs a tabular result viewer that displays the children of a given
* multiple selection actions, and a custom title. * 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 explorerManager The explorer manager of the ancestor top
* @param title The custom title. * component.
* @param title The title.
*/ */
public DataResultViewerTable(ExplorerManager explorerManager, String title) { public DataResultViewerTable(ExplorerManager explorerManager, String title) {
super(explorerManager); super(explorerManager);
this.title = title; this.title = title;
this.columnMap = new HashMap<>();
this.propertiesMap = new TreeMap<>();
/*
* Execute the code generated by the GUI builder.
*/
initComponents(); initComponents();
/*
* Configure the child OutlineView (explorer view) component.
*/
outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE); outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE);
outline = outlineView.getOutline(); outline = outlineView.getOutline();
outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
outline.setRootVisible(false); // don't show the root node outline.setRootVisible(false);
outline.setDragEnabled(false); outline.setDragEnabled(false);
outline.setDefaultRenderer(Object.class, new ColorTagCustomRenderer()); 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);
}
/** /*
* Creates a DataResultViewerTable object that is NOT compatible with node * Add a table listener to the child OutlineView (explorer view) to
* multiple selection actions. * persist the order of the table columns when a column is moved.
*/ */
public DataResultViewerTable() { outlineViewListener = new TableListener();
this(new ExplorerManager(),Bundle.DataResultViewerTable_title()); 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);
} }
/** /**
* Expand node * 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.
* *
* @param n Node to expand * @return A new instance of a tabular result viewer,
*/ */
@Override @Override
public void expandNode(Node n) { public DataResultViewer createInstance() {
super.expandNode(n); return new DataResultViewerTable();
outlineView.expandNode(n);
} }
/** /**
* This method is called from within the constructor to initialize the form. * Gets the title of this tabular result viewer.
* 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
@Override @Override
public boolean isSupported(Node selectedNode) { @NbBundle.Messages("DataResultViewerTable.title=Table")
public String getTitle() {
return title;
}
/**
* Indicates whether a given node is supported as a root node for this
* tabular viewer.
*
* @param candidateRootNode The candidate root node.
*
* @return
*/
@Override
public boolean isSupported(Node candidateRootNode) {
return true; 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 @Override
@ThreadConfined(type = ThreadConfined.ThreadType.AWT) @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, * The quick filter must be reset because when determining column width,
* ETable.getRowCount is called, and the documentation states that quick * ETable.getRowCount is called, and the documentation states that quick
@ -213,30 +212,30 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
* model." * model."
*/ */
outline.unsetQuickFilter(); outline.unsetQuickFilter();
// change the cursor to "waiting cursor" for this operation
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try { try {
boolean hasChildren = false; /*
if (selectedNode != null) { * If the given node is not null and has children, set it as the
// @@@ This just did a DB round trip to get the count and the results were not saved... * root context of the child OutlineView, otherwise make an
hasChildren = selectedNode.getChildren().getNodesCount() > 0; * "empty"node the root context.
} *
* IMPORTANT NOTE: This is the first of many times where a
if (hasChildren) { * getChildren call on the current root node causes all of the
currentRoot = selectedNode; * children of the root node to be created and defeats lazy child
em.setRootContext(currentRoot); * 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(); setupTable();
} else { } else {
Node emptyNode = new AbstractNode(Children.LEAF); Node emptyNode = new AbstractNode(Children.LEAF);
em.setRootContext(emptyNode); // make empty node this.getExplorerManager().setRootContext(emptyNode);
outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
outlineViewListener.listenToVisibilityChanges(false);
/* outlineView.setPropertyColumns();
* 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
} }
} finally { } finally {
this.setCursor(null); this.setCursor(null);
@ -244,17 +243,18 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
} }
/** /**
* Create Column Headers based on the Content represented by the Nodes in * Sets up the Outline view of this tabular result viewer by creating
* the table. Load persisted column order, sorting and visibility. * column headers based on the children of the current root node. The
* persisted column order, sorting and visibility is used.
*/ */
private void setupTable() { private void setupTable() {
/* /*
* Since we are modifying the columns, we don't want to listen to * 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. * 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 * OutlineView makes the first column be the result of
* node.getDisplayName with the icon. This duplicates our first column, * node.getDisplayName with the icon. This duplicates our first column,
* which is the file name, etc. So, pop that property off the list, but * which is the file name, etc. So, pop that property off the list, but
@ -286,7 +286,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
setColumnWidths(); 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(); loadColumnSorting();
/* /*
@ -298,7 +301,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
*/ */
populateColumnMap(); 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(); loadColumnVisibility();
/* /*
@ -306,32 +312,36 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
* it. * it.
*/ */
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
if (currentRoot instanceof TableFilterNode) { if (rootNode instanceof TableFilterNode) {
NodeSelectionInfo selectedChildInfo = ((TableFilterNode) currentRoot).getChildNodeSelectionInfo(); NodeSelectionInfo selectedChildInfo = ((TableFilterNode) rootNode).getChildNodeSelectionInfo();
if (null != selectedChildInfo) { if (null != selectedChildInfo) {
Node[] childNodes = currentRoot.getChildren().getNodes(true); Node[] childNodes = rootNode.getChildren().getNodes(true);
for (int i = 0; i < childNodes.length; ++i) { for (int i = 0; i < childNodes.length; ++i) {
Node childNode = childNodes[i]; Node childNode = childNodes[i];
if (selectedChildInfo.matches(childNode)) { if (selectedChildInfo.matches(childNode)) {
try { try {
em.setSelectedNodes(new Node[]{childNode}); this.getExplorerManager().setSelectedNodes(new Node[]{childNode});
} catch (PropertyVetoException ex) { } catch (PropertyVetoException ex) {
logger.log(Level.SEVERE, "Failed to select node specified by selected child info", ex); logger.log(Level.SEVERE, "Failed to select node specified by selected child info", ex);
} }
break; 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. * loading/storing the visibility info.
*/ */
private void populateColumnMap() { private void populateColumnMap() {
@ -348,8 +358,12 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
} }
} }
/*
* Sets the column widths for the child OutlineView of this tabular results
* viewer.
*/
private void setColumnWidths() { private void setColumnWidths() {
if (currentRoot.getChildren().getNodesCount() != 0) { if (rootNode.getChildren().getNodesCount() != 0) {
final Graphics graphics = outlineView.getGraphics(); final Graphics graphics = outlineView.getGraphics();
if (graphics != null) { if (graphics != null) {
final FontMetrics metrics = graphics.getFontMetrics(); final FontMetrics metrics = graphics.getFontMetrics();
@ -385,8 +399,11 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
} }
} }
/*
* 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) { synchronized private void assignColumns(List<Property<?>> props) {
// Get the columns setup with respect to names and sortability
String[] propStrings = new String[props.size() * 2]; String[] propStrings = new String[props.size() * 2];
for (int i = 0; i < props.size(); i++) { for (int i = 0; i < props.size(); i++) {
final Property<?> prop = props.get(i); final Property<?> prop = props.get(i);
@ -399,29 +416,25 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
propStrings[2 * i] = prop.getName(); propStrings[2 * i] = prop.getName();
propStrings[2 * i + 1] = prop.getDisplayName(); propStrings[2 * i + 1] = prop.getDisplayName();
} }
outlineView.setPropertyColumns(propStrings); 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() { private synchronized void storeColumnVisibility() {
if (currentRoot == null || propertiesMap.isEmpty()) { if (rootNode == null || propertiesMap.isEmpty()) {
return; return;
} }
if (currentRoot instanceof TableFilterNode) { if (rootNode instanceof TableFilterNode) {
TableFilterNode tfn = (TableFilterNode) currentRoot; TableFilterNode tfn = (TableFilterNode) rootNode;
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
//store hidden state
for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) { for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
String columnName = entry.getKey(); String columnName = entry.getKey();
final String columnHiddenKey = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName); final String columnHiddenKey = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName);
final TableColumn column = entry.getValue(); final TableColumn column = entry.getValue();
boolean columnHidden = columnModel.isColumnHidden(column); boolean columnHidden = columnModel.isColumnHidden(column);
if (columnHidden) { if (columnHidden) {
preferences.putBoolean(columnHiddenKey, true); preferences.putBoolean(columnHiddenKey, true);
@ -433,16 +446,16 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
} }
/** /**
* 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() { private synchronized void storeColumnOrder() {
if (currentRoot == null || propertiesMap.isEmpty()) { if (rootNode == null || propertiesMap.isEmpty()) {
return; return;
} }
if (currentRoot instanceof TableFilterNode) { if (rootNode instanceof TableFilterNode) {
TableFilterNode tfn = (TableFilterNode) currentRoot; TableFilterNode tfn = (TableFilterNode) rootNode;
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
// Store the current order of the columns into settings // Store the current order of the columns into settings
for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) { for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey()); preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey());
@ -451,20 +464,19 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
} }
/** /**
* Store the current column sorting information into a preference file. * Persists the current column sorting information using a preferences file.
*/ */
private synchronized void storeColumnSorting() { private synchronized void storeColumnSorting() {
if (currentRoot == null || propertiesMap.isEmpty()) { if (rootNode == null || propertiesMap.isEmpty()) {
return; return;
} }
if (currentRoot instanceof TableFilterNode) { if (rootNode instanceof TableFilterNode) {
final TableFilterNode tfn = ((TableFilterNode) currentRoot); final TableFilterNode tfn = ((TableFilterNode) rootNode);
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) { for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
ETableColumn etc = entry.getValue(); ETableColumn etc = entry.getValue();
String columnName = entry.getKey(); String columnName = entry.getKey();
//store sort rank and order //store sort rank and order
final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName); final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName);
final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, columnName); final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, columnName);
@ -482,47 +494,43 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
/** /**
* Reads and applies the column sorting information persisted to the * Reads and applies the column sorting information persisted to the
* preferences file. Must be called after loadColumnOrder() since it depends * preferences file. Must be called after loadColumnOrder, since it depends
* on propertiesMap being initialized, and after assignColumns since it * on the properties map being initialized, and after assignColumns, since
* cannot set the sort on columns that have not been added to the table. * it cannot set the sort on columns that have not been added to the table.
*/ */
private synchronized void loadColumnSorting() { private synchronized void loadColumnSorting() {
if (currentRoot == null || propertiesMap.isEmpty()) { if (rootNode == null || propertiesMap.isEmpty()) {
return; return;
} }
if (rootNode instanceof TableFilterNode) {
if (currentRoot instanceof TableFilterNode) { final TableFilterNode tfn = (TableFilterNode) rootNode;
final TableFilterNode tfn = (TableFilterNode) currentRoot;
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
//organize property sorting information, sorted by rank //organize property sorting information, sorted by rank
TreeSet<ColumnSortInfo> sortInfos = new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank)); TreeSet<ColumnSortInfo> sortInfos = new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank));
propertiesMap.entrySet().stream().forEach(entry -> { propertiesMap.entrySet().stream().forEach(entry -> {
final String propName = entry.getValue().getName(); final String propName = entry.getValue().getName();
//if the sort rank is undefined, it will be defaulted to 0 => unsorted. //if the sort rank is undefined, it will be defaulted to 0 => unsorted.
Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0); Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0);
//default to true => ascending //default to true => ascending
Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName), true); Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName), true);
sortInfos.add(new ColumnSortInfo(entry.getKey(), sortRank, sortOrder)); sortInfos.add(new ColumnSortInfo(entry.getKey(), sortRank, sortOrder));
}); });
//apply sort information in rank order. //apply sort information in rank order.
sortInfos.forEach(sortInfo -> outline.setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank)); 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() { private synchronized void loadColumnVisibility() {
if (currentRoot == null || propertiesMap.isEmpty()) { if (rootNode == null || propertiesMap.isEmpty()) {
return; return;
} }
if (rootNode instanceof TableFilterNode) {
if (currentRoot instanceof TableFilterNode) {
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
final TableFilterNode tfn = ((TableFilterNode) rootNode);
final TableFilterNode tfn = ((TableFilterNode) currentRoot);
ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) { for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
final String propName = entry.getValue().getName(); final String propName = entry.getValue().getName();
@ -535,7 +543,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
/** /**
* Gets a list of child properties (columns) in the order persisted in the * 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. * order.
* *
* @return a List<Node.Property<?>> of the properties in the persisted * @return a List<Node.Property<?>> of the properties in the persisted
@ -543,14 +551,14 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
*/ */
private synchronized List<Node.Property<?>> loadColumnOrder() { 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 node is not table filter node, use default order for columns
if (!(currentRoot instanceof TableFilterNode)) { if (!(rootNode instanceof TableFilterNode)) {
return props; return props;
} }
final TableFilterNode tfn = ((TableFilterNode) currentRoot); final TableFilterNode tfn = ((TableFilterNode) rootNode);
propertiesMap.clear(); propertiesMap.clear();
/* /*
@ -587,24 +595,15 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
return new ArrayList<>(propertiesMap.values()); return new ArrayList<>(propertiesMap.values());
} }
@Override /**
@NbBundle.Messages("DataResultViewerTable.title=Table") * Frees the resources that have been allocated by this tabular results
public String getTitle() { * viewer, in preparation for permanently disposing of it.
return title; */
}
@Override
public DataResultViewer createInstance() {
return new DataResultViewerTable();
}
@Override @Override
public void clearComponent() { public void clearComponent() {
this.outlineView.removeAll(); this.outlineView.removeAll();
this.outlineView = null; this.outlineView = null;
super.clearComponent(); super.clearComponent();
} }
/** /**
@ -775,8 +774,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col); Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
// only override the color if a node is not selected // only override the color if a node is not selected
if (currentRoot != null && !isSelected) { if (rootNode != null && !isSelected) {
Node node = currentRoot.getChildren().getNodeAt(table.convertRowIndexToModel(row)); Node node = rootNode.getChildren().getNodeAt(table.convertRowIndexToModel(row));
boolean tagFound = false; boolean tagFound = false;
if (node != null) { if (node != null) {
Node.PropertySet[] propSets = node.getPropertySets(); Node.PropertySet[] propSets = node.getPropertySets();
@ -796,10 +795,37 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
} }
//if the node does have associated tags, set its background color //if the node does have associated tags, set its background color
if (tagFound) { if (tagFound) {
component.setBackground(TAGGED_COLOR); component.setBackground(TAGGED_ROW_COLOR);
} }
} }
return component; 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
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-2017 Basis Technology Corp. * Copyright 2012-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -50,6 +50,7 @@ import org.openide.nodes.NodeMemberEvent;
import org.openide.nodes.NodeReorderEvent; import org.openide.nodes.NodeReorderEvent;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.NbPreferences; import org.openide.util.NbPreferences;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import static org.sleuthkit.autopsy.corecomponents.Bundle.*; import static org.sleuthkit.autopsy.corecomponents.Bundle.*;
import org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.SortCriterion; import org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.SortCriterion;
@ -59,64 +60,67 @@ import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException; 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 * Instances of this class should use the explorer manager of an ancestor top
* (currently) 200 images at a time. This works whether or not the underlying * component to connect the lookups of the nodes displayed in the IconView to
* content nodes are being lazy loaded or not. * 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
* TODO (JIRA-2658): Fix DataResultViewer extension point. When this is done, * ancestor top component's explorer manager at runtime.
* restore implementation of DataResultViewerTable as a DataResultViewer service
* provider.
*/ */
//@ServiceProvider(service = DataResultViewer.class) @ServiceProvider(service = DataResultViewer.class)
final class DataResultViewerThumbnail extends AbstractDataResultViewer { public final class DataResultViewerThumbnail extends AbstractDataResultViewer {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName()); private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName());
private int curPage;
private int totalPages;
private int curPageImages;
private int thumbSize = ImageUtils.ICON_SIZE_MEDIUM;
private final PageUpdater pageUpdater = new PageUpdater(); private final PageUpdater pageUpdater = new PageUpdater();
private TableFilterNode tfn; private TableFilterNode rootNode;
private ThumbnailViewChildren tvc; private ThumbnailViewChildren rootNodeChildren;
private NodeSelectionListener selectionListener;
private int currentPage;
private int totalPages;
private int currentPageImages;
private int thumbSize = ImageUtils.ICON_SIZE_MEDIUM;
/** /**
* Constructs a thumbnail viewer for the results view, with paging support, * Constructs a thumbnail result viewer, with paging support, that displays
* that is compatible with node multiple selection actions. * 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.
*/
public DataResultViewerThumbnail() {
this(null);
}
/**
* 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 shared ExplorerManager for the result viewers. * @param explorerManager The explorer manager of the ancestor top
* component.
*/ */
DataResultViewerThumbnail(ExplorerManager explorerManager) { @NbBundle.Messages({
super(explorerManager); "DataResultViewerThumbnail.thumbnailSizeComboBox.small=Small Thumbnails",
initialize();
}
/**
* Constructs a thumbnail viewer for the results view, with paging support,
* that is NOT compatible with node multiple selection actions.
*/
DataResultViewerThumbnail() {
initialize();
}
@NbBundle.Messages({"DataResultViewerThumbnail.thumbnailSizeComboBox.small=Small Thumbnails",
"DataResultViewerThumbnail.thumbnailSizeComboBox.medium=Medium Thumbnails", "DataResultViewerThumbnail.thumbnailSizeComboBox.medium=Medium Thumbnails",
"DataResultViewerThumbnail.thumbnailSizeComboBox.large=Large Thumbnails" "DataResultViewerThumbnail.thumbnailSizeComboBox.large=Large Thumbnails"
}) })
private void initialize() { public DataResultViewerThumbnail(ExplorerManager explorerManager) {
super(explorerManager);
initComponents(); initComponents();
iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
em.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener()); thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[]{
thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>( Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_small(),
new String[]{Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_small(),
Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_medium(), Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_medium(),
Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_large()})); Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_large()}));
thumbnailSizeComboBox.setSelectedIndex(1); thumbnailSizeComboBox.setSelectedIndex(1);
curPage = -1; currentPage = -1;
totalPages = 0; totalPages = 0;
curPageImages = 0; currentPageImages = 0;
} }
/** /**
@ -297,24 +301,22 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
if (thumbSize != newIconSize) { if (thumbSize != newIconSize) {
thumbSize = newIconSize; thumbSize = newIconSize;
Node root = em.getRootContext(); Node root = this.getExplorerManager().getRootContext();
((ThumbnailViewChildren) root.getChildren()).setThumbsSize(thumbSize); ((ThumbnailViewChildren) root.getChildren()).setThumbsSize(thumbSize);
// Temporarily set the explored context to the root, instead of a child node. // 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 // 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 // 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 // will cause the entire view to update completely. After this we
// immediately set the node back to the current child by calling switchPage(). // immediately set the node back to the current child by calling switchPage().
em.setExploredContext(root); this.getExplorerManager().setExploredContext(root);
switchPage(); switchPage();
} }
}//GEN-LAST:event_thumbnailSizeComboBoxActionPerformed }//GEN-LAST:event_thumbnailSizeComboBoxActionPerformed
private void sortButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sortButtonActionPerformed private void sortButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sortButtonActionPerformed
List<Node.Property<?>> childProperties = ResultViewerPersistence.getAllChildProperties(em.getRootContext(), 100); List<Node.Property<?>> childProperties = ResultViewerPersistence.getAllChildProperties(this.getExplorerManager().getRootContext(), 100);
SortChooser sortChooser = new SortChooser(childProperties, ResultViewerPersistence.loadSortCriteria(tfn)); SortChooser sortChooser = new SortChooser(childProperties, ResultViewerPersistence.loadSortCriteria(rootNode));
DialogDescriptor dialogDescriptor = new DialogDescriptor(sortChooser, sortChooser.getDialogTitle()); DialogDescriptor dialogDescriptor = new DialogDescriptor(sortChooser, sortChooser.getDialogTitle());
Dialog createDialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor); Dialog createDialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor);
createDialog.setVisible(true); createDialog.setVisible(true);
@ -335,8 +337,8 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
Node.Property<?> prop = childProperties.get(i); Node.Property<?> prop = childProperties.get(i);
String propName = prop.getName(); String propName = prop.getName();
SortCriterion criterion = criteriaMap.get(prop); SortCriterion criterion = criteriaMap.get(prop);
final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, propName); final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(rootNode, propName);
final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, propName); final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(rootNode, propName);
if (criterion != null) { if (criterion != null) {
preferences.putBoolean(columnSortOrderKey, criterion.getSortOrder() == SortOrder.ASCENDING); preferences.putBoolean(columnSortOrderKey, criterion.getSortOrder() == SortOrder.ASCENDING);
@ -346,7 +348,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
preferences.remove(columnSortRankKey); 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 }//GEN-LAST:event_sortButtonActionPerformed
@ -379,28 +381,31 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
@Override @Override
public void setNode(Node givenNode) { public void setNode(Node givenNode) {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
if (tvc != null) { if (selectionListener == null) {
tvc.cancelLoadingThumbnails(); this.getExplorerManager().addPropertyChangeListener(new NodeSelectionListener()); // RJCTODO: remove listener on cleanup
}
if (rootNodeChildren != null) {
rootNodeChildren.cancelLoadingThumbnails();
} }
try { try {
if (givenNode != null) { if (givenNode != null) {
tfn = (TableFilterNode) givenNode; rootNode = (TableFilterNode) givenNode;
/* /*
* Wrap the given node in a ThumbnailViewChildren that will * Wrap the given node in a ThumbnailViewChildren that will
* produce ThumbnailPageNodes with ThumbnailViewNode children * produce ThumbnailPageNodes with ThumbnailViewNode children
* from the child nodes of the given node. * from the child nodes of the given node.
*/ */
tvc = new ThumbnailViewChildren(givenNode,thumbSize); rootNodeChildren = new ThumbnailViewChildren(givenNode, thumbSize);
final Node root = new AbstractNode(tvc); final Node root = new AbstractNode(rootNodeChildren);
pageUpdater.setRoot(root); pageUpdater.setRoot(root);
root.addNodeListener(pageUpdater); root.addNodeListener(pageUpdater);
em.setRootContext(root); this.getExplorerManager().setRootContext(root);
} else { } else {
tfn = null; rootNode = null;
tvc = null; rootNodeChildren = null;
Node emptyNode = new AbstractNode(Children.LEAF); Node emptyNode = new AbstractNode(Children.LEAF);
em.setRootContext(emptyNode); this.getExplorerManager().setRootContext(emptyNode);
iconView.setBackground(Color.BLACK); iconView.setBackground(Color.BLACK);
} }
} finally { } finally {
@ -422,8 +427,8 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
public void resetComponent() { public void resetComponent() {
super.resetComponent(); super.resetComponent();
this.totalPages = 0; this.totalPages = 0;
this.curPage = -1; this.currentPage = -1;
curPageImages = 0; currentPageImages = 0;
updateControls(); updateControls();
} }
@ -435,15 +440,15 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
} }
private void nextPage() { private void nextPage() {
if (curPage < totalPages) { if (currentPage < totalPages) {
curPage++; currentPage++;
switchPage(); switchPage();
} }
} }
private void previousPage() { private void previousPage() {
if (curPage > 1) { if (currentPage > 1) {
curPage--; currentPage--;
switchPage(); switchPage();
} }
} }
@ -465,7 +470,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
return; return;
} }
curPage = newPage; currentPage = newPage;
switchPage(); switchPage();
} }
@ -488,10 +493,11 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.genThumbs")); NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.genThumbs"));
progress.start(); progress.start();
progress.switchToIndeterminate(); progress.switchToIndeterminate();
Node root = em.getRootContext(); ExplorerManager explorerManager = DataResultViewerThumbnail.this.getExplorerManager();
Node pageNode = root.getChildren().getNodeAt(curPage - 1); Node root = explorerManager.getRootContext();
em.setExploredContext(pageNode); Node pageNode = root.getChildren().getNodeAt(currentPage - 1);
curPageImages = pageNode.getChildren().getNodesCount(); explorerManager.setExploredContext(pageNode);
currentPageImages = pageNode.getChildren().getNodesCount();
return null; return null;
} }
@ -504,8 +510,8 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
try { try {
get(); get();
} catch (InterruptedException | ExecutionException ex) { } catch (InterruptedException | ExecutionException ex) {
NotifyDescriptor d = NotifyDescriptor d
new NotifyDescriptor.Message( = new NotifyDescriptor.Message(
NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.switchPage.done.errMsg", NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.switchPage.done.errMsg",
ex.getMessage()), ex.getMessage()),
NotifyDescriptor.ERROR_MESSAGE); NotifyDescriptor.ERROR_MESSAGE);
@ -534,20 +540,19 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
sortLabel.setText(DataResultViewerThumbnail_sortLabel_text()); sortLabel.setText(DataResultViewerThumbnail_sortLabel_text());
} else { } else {
pageNumLabel.setText( pageNumLabel.setText(NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.pageNumbers.curOfTotal",
NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.pageNumbers.curOfTotal", Integer.toString(currentPage), Integer.toString(totalPages)));
Integer.toString(curPage), Integer.toString(totalPages))); final int imagesFrom = (currentPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE + 1;
final int imagesFrom = (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE + 1; final int imagesTo = currentPageImages + (currentPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE;
final int imagesTo = curPageImages + (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE;
imagesRangeLabel.setText(imagesFrom + "-" + imagesTo); imagesRangeLabel.setText(imagesFrom + "-" + imagesTo);
pageNextButton.setEnabled(!(curPage == totalPages)); pageNextButton.setEnabled(!(currentPage == totalPages));
pagePrevButton.setEnabled(!(curPage == 1)); pagePrevButton.setEnabled(!(currentPage == 1));
goToPageField.setEnabled(totalPages > 1); goToPageField.setEnabled(totalPages > 1);
sortButton.setEnabled(true); sortButton.setEnabled(true);
thumbnailSizeComboBox.setEnabled(true); thumbnailSizeComboBox.setEnabled(true);
if (tfn != null) { if (rootNode != null) {
String sortString = ResultViewerPersistence.loadSortCriteria(tfn).stream() String sortString = ResultViewerPersistence.loadSortCriteria(rootNode).stream()
.map(SortCriterion::toString) .map(SortCriterion::toString)
.collect(Collectors.joining(" ")); .collect(Collectors.joining(" "));
sortString = StringUtils.defaultIfBlank(sortString, "---"); sortString = StringUtils.defaultIfBlank(sortString, "---");
@ -578,30 +583,30 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
totalPages = root.getChildren().getNodesCount(); totalPages = root.getChildren().getNodesCount();
if (totalPages == 0) { if (totalPages == 0) {
curPage = -1; currentPage = -1;
updateControls(); updateControls();
return; return;
} }
if (curPage == -1 || curPage > totalPages) { if (currentPage == -1 || currentPage > totalPages) {
curPage = 1; currentPage = 1;
} }
//force load the curPage node //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}); //em.setSelectedNodes(new Node[]{pageNode});
if (pageNode != null) { if (pageNode != null) {
pageNode.addNodeListener(new NodeListener() { pageNode.addNodeListener(new NodeListener() {
@Override @Override
public void childrenAdded(NodeMemberEvent nme) { public void childrenAdded(NodeMemberEvent nme) {
curPageImages = pageNode.getChildren().getNodesCount(); currentPageImages = pageNode.getChildren().getNodesCount();
updateControls(); updateControls();
} }
@Override @Override
public void childrenRemoved(NodeMemberEvent nme) { public void childrenRemoved(NodeMemberEvent nme) {
curPageImages = 0; currentPageImages = 0;
updateControls(); updateControls();
} }
@ -618,7 +623,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
} }
}); });
em.setExploredContext(pageNode); DataResultViewerThumbnail.this.getExplorerManager().setExploredContext(pageNode);
} }
updateControls(); updateControls();
@ -627,7 +632,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
@Override @Override
public void childrenRemoved(NodeMemberEvent nme) { public void childrenRemoved(NodeMemberEvent nme) {
totalPages = 0; totalPages = 0;
curPage = -1; currentPage = -1;
updateControls(); updateControls();
} }
@ -640,14 +645,14 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
} }
} }
private class ExplorerManagerNodeSelectionListener implements PropertyChangeListener { private class NodeSelectionListener implements PropertyChangeListener {
@Override @Override
public void propertyChange(PropertyChangeEvent evt) { public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try { try {
Node[] selectedNodes = em.getSelectedNodes(); Node[] selectedNodes = DataResultViewerThumbnail.this.getExplorerManager().getSelectedNodes();
if (selectedNodes.length == 1) { if (selectedNodes.length == 1) {
AbstractFile af = selectedNodes[0].getLookup().lookup(AbstractFile.class); AbstractFile af = selectedNodes[0].getLookup().lookup(AbstractFile.class);
if (af == null) { if (af == null) {

View File

@ -101,7 +101,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
private final transient ExplorerManager em = new ExplorerManager(); private final transient ExplorerManager em = new ExplorerManager();
private static DirectoryTreeTopComponent instance; 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[]> backList;
private final LinkedList<String[]> forwardList; private final LinkedList<String[]> forwardList;
private static final String PREFERRED_ID = "DirectoryTreeTopComponent"; //NON-NLS private static final String PREFERRED_ID = "DirectoryTreeTopComponent"; //NON-NLS