diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java index d52d2341ab..671f5ed8a2 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.communications; import com.google.common.eventbus.Subscribe; import java.awt.Component; -import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.logging.Level; @@ -36,11 +35,11 @@ import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.Lookup; -import org.openide.util.lookup.ProxyLookup; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AccountDeviceInstance; +import org.sleuthkit.datamodel.CommunicationsFilter; import org.sleuthkit.datamodel.CommunicationsManager; import org.sleuthkit.datamodel.TskCoreException; @@ -70,7 +69,7 @@ public final class AccountsBrowser extends JPanel implements ExplorerManager.Pro * This lookup proxies the selection lookup of both he accounts table and * the messages table. */ - private final ProxyLookup proxyLookup; + private final Lookup lookup; public AccountsBrowser() { initComponents(); @@ -95,16 +94,19 @@ public final class AccountsBrowser extends JPanel implements ExplorerManager.Pro } else if (ExplorerManager.PROP_EXPLORED_CONTEXT.equals(evt.getPropertyName())) { SwingUtilities.invokeLater(this::setColumnWidths); } else if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { - relationshipBrowser.setSelectionInfo(new SelectionInfo()); + final Node[] selectedNodes = accountsTableEM.getSelectedNodes(); + final Set accountDeviceInstances = new HashSet<>(); + + CommunicationsFilter filter = null; + for (final Node node : selectedNodes) { + accountDeviceInstances.add(((AccountDeviceInstanceNode) node).getAccountDeviceInstance()); + filter = ((AccountDeviceInstanceNode)node).getFilter(); + } + relationshipBrowser.setSelectionInfo(new SelectionInfo(accountDeviceInstances, filter)); } }); - final MessageBrowser messageBrowser = new MessageBrowser(accountsTableEM, messageBrowserEM); - -// jSplitPane1.setRightComponent(messageBrowser); - - proxyLookup = new ProxyLookup( - messageBrowser.getLookup(), - ExplorerUtils.createLookup(accountsTableEM, getActionMap())); + + lookup = ExplorerUtils.createLookup(accountsTableEM, getActionMap()); } private void setColumnWidths() { @@ -176,6 +178,6 @@ public final class AccountsBrowser extends JPanel implements ExplorerManager.Pro @Override public Lookup getLookup() { - return proxyLookup; + return lookup; } } diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties index de986e82e2..5f1dd31ce7 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties @@ -39,5 +39,4 @@ VisualizationPanel.fastOrganicLayoutButton.text=Fast Organic VisualizationPanel.hierarchyLayoutButton.text=Hierarchical VisualizationPanel.clearVizButton.text_1=Clear Viz. VisualizationPanel.snapshotButton.text_1=Snapshot Report -ContactsViewer.testLabel.text=No Value Set -MessagesViewer.testLabel.text=No Value Set +ContactDetailsPane.nameLabel.text=jLabel1 diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED index 88ddd57253..0a92efdbf8 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED @@ -3,6 +3,17 @@ AccountNode.accountType=Type AccountNode.device=Device AccountNode.messageCount=Msgs applyText=Apply +ContactNode_Email=Email Address +ContactNode_Home_Number=Home Number +ContactNode_Mobile_Number=Mobile Number +ContactNode_Name=Name +ContactNode_Office_Number=Office Number +ContactNode_Phone=Phone Number +ContactNode_URL=URL +ContactsViewer_columnHeader_Email=Email +ContactsViewer_columnHeader_Name=Name +ContactsViewer_columnHeader_Phone=Phone +ContactsViewer_tabTitle=Contacts CTL_OpenCVTAction=Communications CVTTopComponent.name=\ Communications Visualization CVTTopComponent.TabConstraints.tabTitle=Visualize @@ -22,7 +33,18 @@ FiltersPanel.refreshButton.text=Refresh FiltersPanel.deviceRequiredLabel.text=Select at least one. FiltersPanel.accountTypeRequiredLabel.text=Select at least one. FiltersPanel.needsRefreshLabel.text=Displayed data is out of date. Press Refresh. -MessageBrowser.DataResultViewerTable.title=Messages +MessageNode_Node_Property_Attms=Attachments +MessageNode_Node_Property_Date=Date +MessageNode_Node_Property_From=From +MessageNode_Node_Property_Subject=Subject +MessageNode_Node_Property_To=To +MessageNode_Node_Property_Type=Type +MessageViewer_columnHeader_Attms=Attachments +MessageViewer_columnHeader_Date=Date +MessageViewer_columnHeader_From=From +MessageViewer_columnHeader_Subject=Subject +MessageViewer_columnHeader_To=To +MessageViewer_tabTitle=Messages OpenCVTAction.displayName=Communications PinAccountsAction.pluralText=Add Selected Accounts to Visualization PinAccountsAction.singularText=Add Selected Account to Visualization @@ -83,8 +105,7 @@ VisualizationPanel.fastOrganicLayoutButton.text=Fast Organic VisualizationPanel.hierarchyLayoutButton.text=Hierarchical VisualizationPanel.clearVizButton.text_1=Clear Viz. VisualizationPanel.snapshotButton.text_1=Snapshot Report -ContactsViewer.testLabel.text=No Value Set -MessagesViewer.testLabel.text=No Value Set +ContactDetailsPane.nameLabel.text=jLabel1 VisualizationPanel_action_dialogs_title=Communications VisualizationPanel_action_name_text=Snapshot Report VisualizationPanel_module_name=Communications diff --git a/Core/src/org/sleuthkit/autopsy/communications/ContactDetailsPane.form b/Core/src/org/sleuthkit/autopsy/communications/ContactDetailsPane.form new file mode 100755 index 0000000000..2cf54c02ea --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/ContactDetailsPane.form @@ -0,0 +1,61 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/ContactDetailsPane.java b/Core/src/org/sleuthkit/autopsy/communications/ContactDetailsPane.java new file mode 100755 index 0000000000..c22f60fb7e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/ContactDetailsPane.java @@ -0,0 +1,112 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.communications; + +import org.openide.explorer.ExplorerManager; +import org.openide.nodes.Node; + +/** + * Displays the propertied of a ContactNode in a PropertySheet. + */ +public final class ContactDetailsPane extends javax.swing.JPanel implements ExplorerManager.Provider { + + final private ExplorerManager explorerManager = new ExplorerManager(); + + /** + * Displays the propertied of a ContactNode in a PropertySheet. + */ + public ContactDetailsPane() { + initComponents(); + this.setEnabled(false); + } + + /** + * Sets the list of nodes for the property sheet. + * + * @param nodes List of nodes to set + */ + public void setNode(Node[] nodes) { + if (nodes != null) { + nameLabel.setText(nodes[0].getDisplayName()); + } else { + nameLabel.setText(""); + } + + propertySheet.setNodes(nodes); + } + + @Override + public ExplorerManager getExplorerManager() { + return explorerManager; + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + nameLabel.setEnabled(enabled); + propertySheet.setEnabled(enabled); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + nameLabel = new javax.swing.JLabel(); + propertySheet = new org.openide.explorer.propertysheet.PropertySheet(); + + nameLabel.setFont(new java.awt.Font("Tahoma", 0, 24)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(nameLabel, org.openide.util.NbBundle.getMessage(ContactDetailsPane.class, "ContactDetailsPane.nameLabel.text")); // NOI18N + + propertySheet.setDescriptionAreaVisible(false); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(propertySheet, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(nameLabel) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(nameLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(propertySheet, javax.swing.GroupLayout.DEFAULT_SIZE, 283, Short.MAX_VALUE) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel nameLabel; + private org.openide.explorer.propertysheet.PropertySheet propertySheet; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/ContactNode.java b/Core/src/org/sleuthkit/autopsy/communications/ContactNode.java new file mode 100755 index 0000000000..c73cfff430 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/ContactNode.java @@ -0,0 +1,123 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.communications; + +import java.util.TimeZone; +import java.util.logging.Level; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; +import org.sleuthkit.autopsy.datamodel.NodeProperty; +import org.sleuthkit.datamodel.BlackboardArtifact; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT; +import org.sleuthkit.datamodel.BlackboardAttribute; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_HOME; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_MOBILE; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_OFFICE; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL; +import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME; +import org.sleuthkit.datamodel.TimeUtilities; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Extends BlackboardArtifactNode to override createSheet to create a contact + * artifact specific sheet. + */ +final class ContactNode extends BlackboardArtifactNode { + + private static final Logger logger = Logger.getLogger(RelationshipNode.class.getName()); + + @Messages({ + "ContactNode_Name=Name", + "ContactNode_Phone=Phone Number", + "ContactNode_Email=Email Address", + "ContactNode_Mobile_Number=Mobile Number", + "ContactNode_Office_Number=Office Number", + "ContactNode_URL=URL", + "ContactNode_Home_Number=Home Number",}) + + ContactNode(BlackboardArtifact artifact) { + super(artifact); + + setDisplayName(getAttributeDisplayString(artifact, TSK_NAME)); + } + + @Override + protected Sheet createSheet() { + final BlackboardArtifact artifact = getArtifact(); + BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); + if (fromID != TSK_CONTACT) { + return super.createSheet(); + } + + Sheet sheet = new Sheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>("email", Bundle.ContactNode_Email(), "", + getAttributeDisplayString(artifact, TSK_EMAIL))); //NON-NLS + sheetSet.put(new NodeProperty<>("phone", Bundle.ContactNode_Phone(), "", + getAttributeDisplayString(artifact, TSK_PHONE_NUMBER))); //NON-NLS + sheetSet.put(new NodeProperty<>("mobile", Bundle.ContactNode_Mobile_Number(), "", + getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_MOBILE))); //NON-NLS + sheetSet.put(new NodeProperty<>("home", Bundle.ContactNode_Home_Number(), "", + getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_HOME))); //NON-NLS + sheetSet.put(new NodeProperty<>("office", Bundle.ContactNode_Office_Number(), "", + getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_OFFICE))); //NON-NLS + sheetSet.put(new NodeProperty<>("url", Bundle.ContactNode_URL(), "", + getAttributeDisplayString(artifact, TSK_URL))); //NON-NLS + + return sheet; + } + + private static String getAttributeDisplayString(final BlackboardArtifact artifact, final BlackboardAttribute.ATTRIBUTE_TYPE attributeType) { + try { + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(attributeType.getTypeID()))); + if (attribute == null) { + return ""; + } else if (attributeType.getValueType() == DATETIME) { + return TimeUtilities.epochToTime(attribute.getValueLong(), + TimeZone.getTimeZone(Utils.getUserPreferredZoneId())); + } else { + return attribute.getDisplayString(); + } + } catch (TskCoreException tskCoreException) { + logger.log(Level.WARNING, "Error getting attribute value.", tskCoreException); //NON-NLS + return ""; + } + } + + /** + * Circumvent DataResultFilterNode's slightly odd delegation to + * BlackboardArtifactNode.getSourceName(). + * + * @return the displayName of this Node, which is the type. + */ + @Override + public String getSourceName() { + return getDisplayName(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/ContactsChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/ContactsChildNodeFactory.java new file mode 100755 index 0000000000..08471c5324 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/ContactsChildNodeFactory.java @@ -0,0 +1,109 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.communications; + +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.BlackboardArtifact; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT; +import org.sleuthkit.datamodel.CommunicationsManager; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * ChildFactory for ContactNodes. + */ +final class ContactsChildNodeFactory extends ChildFactory{ + private static final Logger logger = Logger.getLogger(MessagesChildNodeFactory.class.getName()); + + private SelectionInfo selectionInfo; + + /** + * Construct a new ContactsChildNodeFactory from the currently selectionInfo + * + * @param selectionInfo SelectionInfo object for the currently selected + * accounts + */ + ContactsChildNodeFactory(SelectionInfo selectionInfo) { + this.selectionInfo = selectionInfo; + } + + /** + * Updates the current instance of selectionInfo and calls the refresh method. + * + * @param selectionInfo New instance of the currently selected accounts + */ + public void refresh(SelectionInfo selectionInfo) { + this.selectionInfo = selectionInfo; + refresh(true); + } + + /** + * Creates a list of Keys (BlackboardArtifact) for only contacts of the + * currently selected accounts + * @param list List of BlackboardArtifact to populate + * @return True on success + */ + @Override + protected boolean createKeys(List list) { + CommunicationsManager communicationManager; + try { + communicationManager = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager(); + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to get communications manager from case.", ex); //NON-NLS + return false; + } + + if(selectionInfo == null) { + return true; + } + + final Set relationshipSources; + + try { + relationshipSources = communicationManager.getRelationshipSources(selectionInfo.getAccountDevicesInstances(), selectionInfo.getCommunicationsFilter()); + + relationshipSources.stream().filter((content) -> (content instanceof BlackboardArtifact)).forEachOrdered((content) -> { + + BlackboardArtifact bba = (BlackboardArtifact) content; + BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(bba.getArtifactTypeID()); + + if (fromID == TSK_CONTACT) { + list.add(bba); + } + }); + + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to get relationship sources.", ex); //NON-NLS + } + + return true; + } + + @Override + protected Node createNodeForKey(BlackboardArtifact key) { + return new ContactNode(key); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/ContactsViewer.form b/Core/src/org/sleuthkit/autopsy/communications/ContactsViewer.form index 81e1e314ef..166fd065bf 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/ContactsViewer.form +++ b/Core/src/org/sleuthkit/autopsy/communications/ContactsViewer.form @@ -16,30 +16,27 @@ - - - - - + + - - - + + + - - - - - - + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/ContactsViewer.java b/Core/src/org/sleuthkit/autopsy/communications/ContactsViewer.java index 66c22fa4d2..3c8cebedd5 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/ContactsViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/ContactsViewer.java @@ -18,36 +18,139 @@ */ package org.sleuthkit.autopsy.communications; +import java.awt.Component; +import java.awt.KeyboardFocusManager; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import javax.swing.JPanel; +import javax.swing.ListSelectionModel; +import static javax.swing.SwingUtilities.isDescendingFrom; +import org.netbeans.swing.outline.DefaultOutlineModel; +import org.netbeans.swing.outline.Outline; +import org.openide.explorer.ExplorerManager; +import static org.openide.explorer.ExplorerUtils.createLookup; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.corecomponents.TableFilterNode; +import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; /** - * Visualization for contacts - * + * Visualization for contact nodes. + * */ -@ServiceProvider(service=RelationshipsViewer.class) -public class ContactsViewer extends JPanel implements RelationshipsViewer{ +@ServiceProvider(service = RelationshipsViewer.class) +public final class ContactsViewer extends JPanel implements RelationshipsViewer, ExplorerManager.Provider, Lookup.Provider { + + private final ExplorerManager tableEM; + private final Outline outline; + private final ModifiableProxyLookup proxyLookup; + private final PropertyChangeListener focusPropertyListener; + private final ContactsChildNodeFactory nodeFactory; + + @NbBundle.Messages({ + "ContactsViewer_tabTitle=Contacts", + "ContactsViewer_columnHeader_Name=Name", + "ContactsViewer_columnHeader_Phone=Phone", + "ContactsViewer_columnHeader_Email=Email",}) /** - * Creates new form ContactsViewer + * Visualization for contact nodes. */ public ContactsViewer() { + tableEM = new ExplorerManager(); + proxyLookup = new ModifiableProxyLookup(createLookup(tableEM, getActionMap())); + nodeFactory = new ContactsChildNodeFactory(null); + + // See org.sleuthkit.autopsy.timeline.TimeLineTopComponent for a detailed + // explaination of focusPropertyListener + focusPropertyListener = (final PropertyChangeEvent focusEvent) -> { + if (focusEvent.getPropertyName().equalsIgnoreCase("focusOwner")) { + final Component newFocusOwner = (Component) focusEvent.getNewValue(); + + if (newFocusOwner == null) { + return; + } + if (isDescendingFrom(newFocusOwner, contactPane)) { + //if the focus owner is within the MessageContentViewer (the attachments table) + proxyLookup.setNewLookups(createLookup(contactPane.getExplorerManager(), getActionMap())); + } else if (isDescendingFrom(newFocusOwner, ContactsViewer.this)) { + //... or if it is within the Results table. + proxyLookup.setNewLookups(createLookup(tableEM, getActionMap())); + + } + } + }; + initComponents(); + + outline = outlineView.getOutline(); + outlineView.setPropertyColumns( + "email", Bundle.ContactsViewer_columnHeader_Email(), + "phone", Bundle.ContactsViewer_columnHeader_Phone() + ); + outline.setRootVisible(false); + outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.ContactsViewer_columnHeader_Name()); + + tableEM.addPropertyChangeListener((PropertyChangeEvent evt) -> { + if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { + final Node[] nodes = tableEM.getSelectedNodes(); + + if (nodes != null && nodes.length > 0) { + contactPane.setEnabled(true); + contactPane.setNode(nodes); + } + } + }); + + tableEM.setRootContext(new TableFilterNode(new DataResultFilterNode(new AbstractNode(Children.create(nodeFactory, true)), getExplorerManager()), true)); } - + @Override public String getDisplayName() { - return "Contacts"; + return Bundle.ContactsViewer_tabTitle(); } - + @Override public JPanel getPanel() { return this; } - + @Override public void setSelectionInfo(SelectionInfo info) { - testLabel.setText(info.getString()); + contactPane.setNode(new Node[]{new AbstractNode(Children.LEAF)}); + contactPane.setEnabled(false); + + nodeFactory.refresh(info); + } + + @Override + public ExplorerManager getExplorerManager() { + return tableEM; + } + + @Override + public Lookup getLookup() { + return proxyLookup; + } + + @Override + public void addNotify() { + super.addNotify(); + //add listener that maintains correct selection in the Global Actions Context + KeyboardFocusManager.getCurrentKeyboardFocusManager() + .addPropertyChangeListener("focusOwner", focusPropertyListener); + } + + @Override + public void removeNotify() { + super.removeNotify(); + KeyboardFocusManager.getCurrentKeyboardFocusManager() + .removePropertyChangeListener("focusOwner", focusPropertyListener); } /** @@ -59,30 +162,28 @@ public class ContactsViewer extends JPanel implements RelationshipsViewer{ // //GEN-BEGIN:initComponents private void initComponents() { - testLabel = new javax.swing.JLabel(); - - org.openide.awt.Mnemonics.setLocalizedText(testLabel, org.openide.util.NbBundle.getMessage(ContactsViewer.class, "ContactsViewer.testLabel.text")); // NOI18N + outlineView = new org.openide.explorer.view.OutlineView(); + contactPane = new org.sleuthkit.autopsy.communications.ContactDetailsPane(); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addComponent(testLabel) - .addContainerGap(294, Short.MAX_VALUE)) + .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(contactPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addComponent(testLabel) - .addContainerGap(264, Short.MAX_VALUE)) + .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(contactPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JLabel testLabel; + private org.sleuthkit.autopsy.communications.ContactDetailsPane contactPane; + private org.openide.explorer.view.OutlineView outlineView; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java index 587bf47260..287b6f9ae9 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java @@ -49,6 +49,7 @@ import org.sleuthkit.datamodel.CommunicationsFilter.DateRangeFilter; import org.sleuthkit.datamodel.CommunicationsFilter.DeviceFilter; import org.sleuthkit.datamodel.DataSource; import static org.sleuthkit.datamodel.Relationship.Type.CALL_LOG; +import static org.sleuthkit.datamodel.Relationship.Type.CONTACT; import static org.sleuthkit.datamodel.Relationship.Type.MESSAGE; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; @@ -514,7 +515,7 @@ final public class FiltersPanel extends JPanel { commsFilter.addAndFilter(getAccountTypeFilter()); commsFilter.addAndFilter(getDateRangeFilter()); commsFilter.addAndFilter(new CommunicationsFilter.RelationshipTypeFilter( - ImmutableSet.of(CALL_LOG, MESSAGE))); + ImmutableSet.of(CALL_LOG, MESSAGE, CONTACT))); return commsFilter; } diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessageNode.java b/Core/src/org/sleuthkit/autopsy/communications/MessageNode.java index 12c1231628..c73336474b 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/MessageNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/MessageNode.java @@ -18,23 +18,168 @@ */ package org.sleuthkit.autopsy.communications; +import java.util.List; +import java.util.TimeZone; +import java.util.logging.Level; import org.apache.commons.lang3.StringUtils; +import org.openide.nodes.Sheet; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; +import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_FROM; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_TO; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SUBJECT; +import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME; +import org.sleuthkit.datamodel.Tag; +import org.sleuthkit.datamodel.TimeUtilities; +import org.sleuthkit.datamodel.TskCoreException; /** - * - * + * Wraps a BlackboardArtifact as an AbstractNode for use in an OutlookView */ final class MessageNode extends BlackboardArtifactNode { + private static final Logger logger = Logger.getLogger(RelationshipNode.class.getName()); - + MessageNode(BlackboardArtifact artifact) { super(artifact); - // Grabbed this from RelationshipNode, does always work even with internalization? + final String stripEnd = StringUtils.stripEnd(artifact.getDisplayName(), "s"); // NON-NLS String removeEndIgnoreCase = StringUtils.removeEndIgnoreCase(stripEnd, "message"); // NON-NLS setDisplayName(removeEndIgnoreCase.isEmpty() ? stripEnd : removeEndIgnoreCase); } + + @Messages({ + "MessageNode_Node_Property_Type=Type", + "MessageNode_Node_Property_From=From", + "MessageNode_Node_Property_To=To", + "MessageNode_Node_Property_Date=Date", + "MessageNode_Node_Property_Subject=Subject", + "MessageNode_Node_Property_Attms=Attachments" + }) + + @Override + protected Sheet createSheet() { + Sheet sheet = new Sheet(); + List tags = getAllTagsFromDatabase(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>("Type", Bundle.MessageNode_Node_Property_Type(), "", getDisplayName())); //NON-NLS + + addScoreProperty(sheetSet, tags); + + CorrelationAttributeInstance correlationAttribute = null; + if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) { + correlationAttribute = getCorrelationAttributeInstance(); + } + addCommentProperty(sheetSet, tags, correlationAttribute); + + if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) { + addCountProperty(sheetSet, correlationAttribute); + } + final BlackboardArtifact artifact = getArtifact(); + + BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); + if (null != fromID) { + //Consider refactoring this to reduce boilerplate + switch (fromID) { + case TSK_EMAIL_MSG: + sheetSet.put(new NodeProperty<>("From", Bundle.MessageNode_Node_Property_From(), "", + StringUtils.strip(getAttributeDisplayString(artifact, TSK_EMAIL_FROM), " \t\n;"))); //NON-NLS + sheetSet.put(new NodeProperty<>("To", Bundle.MessageNode_Node_Property_To(), "", + StringUtils.strip(getAttributeDisplayString(artifact, TSK_EMAIL_TO), " \t\n;"))); //NON-NLS + sheetSet.put(new NodeProperty<>("Date", Bundle.MessageNode_Node_Property_Date(), "", + getAttributeDisplayString(artifact, TSK_DATETIME_SENT))); //NON-NLS + sheetSet.put(new NodeProperty<>("Subject", Bundle.MessageNode_Node_Property_Subject(), "", + getAttributeDisplayString(artifact, TSK_SUBJECT))); //NON-NLS + try { + sheetSet.put(new NodeProperty<>("Attms", Bundle.MessageNode_Node_Property_Attms(), "", artifact.getChildrenCount())); //NON-NLS + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error loading attachment count for " + artifact, ex); //NON-NLS + } + + break; + case TSK_MESSAGE: + sheetSet.put(new NodeProperty<>("From", Bundle.MessageNode_Node_Property_From(), "", + getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_FROM))); //NON-NLS + sheetSet.put(new NodeProperty<>("To", Bundle.MessageNode_Node_Property_To(), "", + getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_TO))); //NON-NLS + sheetSet.put(new NodeProperty<>("Date", Bundle.MessageNode_Node_Property_Date(), "", + getAttributeDisplayString(artifact, TSK_DATETIME))); //NON-NLS + sheetSet.put(new NodeProperty<>("Subject", Bundle.MessageNode_Node_Property_Subject(), "", + getAttributeDisplayString(artifact, TSK_SUBJECT))); //NON-NLS + try { + sheetSet.put(new NodeProperty<>("Attms", Bundle.MessageNode_Node_Property_Attms(), "", artifact.getChildrenCount())); //NON-NLS + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error loading attachment count for " + artifact, ex); //NON-NLS + } + break; + case TSK_CALLLOG: + sheetSet.put(new NodeProperty<>("From", Bundle.MessageNode_Node_Property_From(), "", + getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_FROM))); //NON-NLS + sheetSet.put(new NodeProperty<>("To", Bundle.MessageNode_Node_Property_To(), "", + getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_TO))); //NON-NLS + sheetSet.put(new NodeProperty<>("Date", Bundle.MessageNode_Node_Property_Date(), "", + getAttributeDisplayString(artifact, TSK_DATETIME_START))); //NON-NLS + break; + default: + break; + } + } + + return sheet; + } + + /** + * + * Get the display string for the attribute of the given type from the given + * artifact. + * + * @param artifact the value of artifact + * @param attributeType the value of TSK_SUBJECT1 + * + * @return The display string, or an empty string if there is no such + * attribute or an an error. + */ + private static String getAttributeDisplayString(final BlackboardArtifact artifact, final BlackboardAttribute.ATTRIBUTE_TYPE attributeType) { + try { + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(attributeType.getTypeID()))); + if (attribute == null) { + return ""; + } else if (attributeType.getValueType() == DATETIME) { + return TimeUtilities.epochToTime(attribute.getValueLong(), + TimeZone.getTimeZone(Utils.getUserPreferredZoneId())); + } else { + return attribute.getDisplayString(); + } + } catch (TskCoreException tskCoreException) { + logger.log(Level.WARNING, "Error getting attribute value.", tskCoreException); //NON-NLS + return ""; + } + } + + /** + * Circumvent DataResultFilterNode's slightly odd delegation to + * BlackboardArtifactNode.getSourceName(). + * + * @return the displayName of this Node, which is the type. + */ + @Override + public String getSourceName() { + return getDisplayName(); + } } diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessageNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/MessageNodeFactory.java deleted file mode 100755 index c06bffe692..0000000000 --- a/Core/src/org/sleuthkit/autopsy/communications/MessageNodeFactory.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2019 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.communications; - -import java.util.Collection; -import java.util.List; -import org.openide.nodes.ChildFactory; -import org.openide.nodes.Node; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * - * - */ -public class MessageNodeFactory extends ChildFactory { - private final Collection artifacts; - - MessageNodeFactory(Collection artifacts) { - this.artifacts = artifacts; - } - - @Override - protected boolean createKeys(List list) { - list.addAll(artifacts); - return true; - } - - @Override - protected Node createNodeForKey(BlackboardArtifact key) { - return new MessageNode(key); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessagesChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/MessagesChildNodeFactory.java new file mode 100755 index 0000000000..38d5cfa8f0 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/MessagesChildNodeFactory.java @@ -0,0 +1,115 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.communications; + +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.CommunicationsManager; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * ChildFactory that creates createKeys and nodes from a given selectionInfo for + * only emails, call logs and messages. + * + */ +final class MessagesChildNodeFactory extends ChildFactory { + + private static final Logger logger = Logger.getLogger(MessagesChildNodeFactory.class.getName()); + + private SelectionInfo selectionInfo; + + /** + * Construct a new MessageChildNodeFactory from the currently selectionInfo + * + * @param selectionInfo SelectionInfo object for the currently selected + * accounts + */ + MessagesChildNodeFactory(SelectionInfo selectionInfo) { + this.selectionInfo = selectionInfo; + } + + /** + * Updates the current instance of selectionInfo and calls the refresh method. + * + * @param selectionInfo New instance of the currently selected accounts + */ + public void refresh(SelectionInfo selectionInfo) { + this.selectionInfo = selectionInfo; + refresh(true); + } + + /** + * Creates a list of Keys (BlackboardArtifact) for only messages for the + * currently selected accounts + * + * @param list List of BlackboardArtifact to populate + * + * @return True on success + */ + @Override + protected boolean createKeys(List list) { + CommunicationsManager communicationManager; + try { + communicationManager = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager(); + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to get communications manager from case.", ex); //NON-NLS + return false; + } + + if(selectionInfo == null) { + return true; + } + + final Set relationshipSources; + + try { + relationshipSources = communicationManager.getRelationshipSources(selectionInfo.getAccountDevicesInstances(), selectionInfo.getCommunicationsFilter()); + + relationshipSources.stream().filter((content) -> (content instanceof BlackboardArtifact)).forEachOrdered((content) -> { + + BlackboardArtifact bba = (BlackboardArtifact) content; + BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(bba.getArtifactTypeID()); + + if (fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG + || fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG + || fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE) { + list.add(bba); + } + }); + + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to get relationship sources.", ex); //NON-NLS + } + + return true; + } + + @Override + protected Node createNodeForKey(BlackboardArtifact key) { + return new MessageNode(key); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessagesViewer.form b/Core/src/org/sleuthkit/autopsy/communications/MessagesViewer.form index a80677b2ee..7368b264e5 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/MessagesViewer.form +++ b/Core/src/org/sleuthkit/autopsy/communications/MessagesViewer.form @@ -16,30 +16,27 @@ - - - - - + + + - - + - - - - - - + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessagesViewer.java b/Core/src/org/sleuthkit/autopsy/communications/MessagesViewer.java index 4624005140..45b121e243 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/MessagesViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/MessagesViewer.java @@ -18,36 +18,142 @@ */ package org.sleuthkit.autopsy.communications; +import java.awt.Component; +import java.awt.KeyboardFocusManager; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import javax.swing.JPanel; +import javax.swing.ListSelectionModel; +import static javax.swing.SwingUtilities.isDescendingFrom; +import org.netbeans.swing.outline.DefaultOutlineModel; +import org.netbeans.swing.outline.Outline; +import org.openide.explorer.ExplorerManager; +import static org.openide.explorer.ExplorerUtils.createLookup; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.util.Lookup; +import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.corecomponents.TableFilterNode; +import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; /** - * Visualation for Contacts - * + * Visualation for the messages of the currently selected accounts. */ -@ServiceProvider(service=RelationshipsViewer.class) -public class MessagesViewer extends JPanel implements RelationshipsViewer{ +@ServiceProvider(service = RelationshipsViewer.class) +public final class MessagesViewer extends JPanel implements RelationshipsViewer, ExplorerManager.Provider, Lookup.Provider { + + private final ExplorerManager tableEM; + private final Outline outline; + private final ModifiableProxyLookup proxyLookup; + private final PropertyChangeListener focusPropertyListener; + private final MessagesChildNodeFactory nodeFactory; + + @Messages({ + "MessageViewer_tabTitle=Messages", + "MessageViewer_columnHeader_From=From", + "MessageViewer_columnHeader_To=To", + "MessageViewer_columnHeader_Date=Date", + "MessageViewer_columnHeader_Subject=Subject", + "MessageViewer_columnHeader_Attms=Attachments" + }) /** - * Creates new form MessagesViewer + * Visualation for the messages of the currently selected accounts. */ public MessagesViewer() { + tableEM = new ExplorerManager(); + proxyLookup = new ModifiableProxyLookup(createLookup(tableEM, getActionMap())); + nodeFactory = new MessagesChildNodeFactory(null); + + // See org.sleuthkit.autopsy.timeline.TimeLineTopComponent for a detailed + // explaination of focusPropertyListener + focusPropertyListener = (final PropertyChangeEvent focusEvent) -> { + if (focusEvent.getPropertyName().equalsIgnoreCase("focusOwner")) { + final Component newFocusOwner = (Component) focusEvent.getNewValue(); + + if (newFocusOwner == null) { + return; + } + if (isDescendingFrom(newFocusOwner, contentViewer)) { + //if the focus owner is within the MessageContentViewer (the attachments table) + proxyLookup.setNewLookups(createLookup(((MessageDataContent) contentViewer).getExplorerManager(), getActionMap())); + } else if (isDescendingFrom(newFocusOwner, MessagesViewer.this)) { + //... or if it is within the Results table. + proxyLookup.setNewLookups(createLookup(tableEM, getActionMap())); + + } + } + }; + initComponents(); + + outline = outlineView.getOutline(); + outlineView.setPropertyColumns( + "From", Bundle.MessageViewer_columnHeader_From(), + "To", Bundle.MessageViewer_columnHeader_To(), + "Date", Bundle.MessageViewer_columnHeader_Date(), + "Subject", Bundle.MessageViewer_columnHeader_Subject(), + "Attms", Bundle.MessageViewer_columnHeader_Attms(), + "Type", "Type" + ); + outline.setRootVisible(false); + outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel("Type"); + + tableEM.addPropertyChangeListener((PropertyChangeEvent evt) -> { + if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { + final Node[] nodes = tableEM.getSelectedNodes(); + + if (nodes != null && nodes.length > 0) { + contentViewer.setNode(nodes[0]); + } + } + }); + + tableEM.setRootContext(new TableFilterNode(new DataResultFilterNode(new AbstractNode(Children.create(nodeFactory, true)), getExplorerManager()), true)); } - + @Override public String getDisplayName() { - return "Messages"; + return Bundle.MessageViewer_tabTitle(); } - + @Override public JPanel getPanel() { return this; } - + @Override public void setSelectionInfo(SelectionInfo info) { - testLabel.setText(info.getString()); +// tableEM.setRootContext(new TableFilterNode(new DataResultFilterNode(new AbstractNode(Children.create(new MessagesChildNodeFactory(info), true)), getExplorerManager()), true)); + nodeFactory.refresh(info); + } + + @Override + public ExplorerManager getExplorerManager() { + return tableEM; + } + + @Override + public Lookup getLookup() { + return proxyLookup; + } + + @Override + public void addNotify() { + super.addNotify(); + //add listener that maintains correct selection in the Global Actions Context + KeyboardFocusManager.getCurrentKeyboardFocusManager() + .addPropertyChangeListener("focusOwner", focusPropertyListener); + } + + @Override + public void removeNotify() { + super.removeNotify(); + KeyboardFocusManager.getCurrentKeyboardFocusManager() + .removePropertyChangeListener("focusOwner", focusPropertyListener); } /** @@ -59,30 +165,28 @@ public class MessagesViewer extends JPanel implements RelationshipsViewer{ // //GEN-BEGIN:initComponents private void initComponents() { - testLabel = new javax.swing.JLabel(); - - org.openide.awt.Mnemonics.setLocalizedText(testLabel, org.openide.util.NbBundle.getMessage(MessagesViewer.class, "MessagesViewer.testLabel.text")); // NOI18N + outlineView = new org.openide.explorer.view.OutlineView(); + contentViewer = new MessageDataContent(); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addComponent(testLabel) - .addContainerGap(294, Short.MAX_VALUE)) + .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(contentViewer, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addComponent(testLabel) - .addContainerGap(264, Short.MAX_VALUE)) + .addComponent(outlineView, javax.swing.GroupLayout.PREFERRED_SIZE, 190, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(contentViewer, javax.swing.GroupLayout.DEFAULT_SIZE, 778, Short.MAX_VALUE)) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JLabel testLabel; + private org.sleuthkit.autopsy.contentviewers.MessageContentViewer contentViewer; + private org.openide.explorer.view.OutlineView outlineView; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/communications/RelaionshipSetNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/RelaionshipSetNodeFactory.java deleted file mode 100644 index ac2e5d1c54..0000000000 --- a/Core/src/org/sleuthkit/autopsy/communications/RelaionshipSetNodeFactory.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2017-2019 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.communications; - -import java.util.Collection; -import java.util.List; -import org.openide.nodes.ChildFactory; -import org.openide.nodes.Node; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * - */ -public class RelaionshipSetNodeFactory extends ChildFactory { - - private final Collection artifacts; - - public RelaionshipSetNodeFactory(Collection artifacts) { - this.artifacts = artifacts; - } - - @Override - protected boolean createKeys(List list) { - list.addAll(artifacts); - return true; - } - - @Override - protected Node createNodeForKey(BlackboardArtifact key) { - return new RelationshipNode(key); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/communications/RelationshipBrowser.form b/Core/src/org/sleuthkit/autopsy/communications/RelationshipBrowser.form index 7b6fa1d18f..a664317372 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/RelationshipBrowser.form +++ b/Core/src/org/sleuthkit/autopsy/communications/RelationshipBrowser.form @@ -27,6 +27,9 @@ + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/RelationshipBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/RelationshipBrowser.java index c934ebade9..8fa082fc2c 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/RelationshipBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/RelationshipBrowser.java @@ -23,9 +23,11 @@ import org.openide.util.Lookup; /** * Displays the Relationship information for the currently selected accounts. - * + * */ -public class RelationshipBrowser extends JPanel { +final class RelationshipBrowser extends JPanel { + + private SelectionInfo currentSelection; /** * Creates new form RelationshipBrowser @@ -37,12 +39,16 @@ public class RelationshipBrowser extends JPanel { tabPane.add(viewer.getDisplayName(), viewer.getPanel()); }); } - + + /** + * Sets the value of currentSelection and passes the SelectionInfo onto the + * currently selected\visible tab. + * + * @param info Currently selected account nodes + */ public void setSelectionInfo(SelectionInfo info) { - ((RelationshipsViewer)tabPane.getSelectedComponent()).setSelectionInfo(info); - - // TODO If we only pass the info to the currently selected tab, we - // need to do something when the tab changes + currentSelection = info; + ((RelationshipsViewer) tabPane.getSelectedComponent()).setSelectionInfo(info); } /** @@ -56,6 +62,12 @@ public class RelationshipBrowser extends JPanel { tabPane = new javax.swing.JTabbedPane(); + tabPane.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + tabPaneStateChanged(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -68,6 +80,10 @@ public class RelationshipBrowser extends JPanel { ); }// //GEN-END:initComponents + private void tabPaneStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_tabPaneStateChanged + ((RelationshipsViewer) tabPane.getSelectedComponent()).setSelectionInfo(currentSelection); + }//GEN-LAST:event_tabPaneStateChanged + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JTabbedPane tabPane; diff --git a/Core/src/org/sleuthkit/autopsy/communications/RelationshipsViewer.java b/Core/src/org/sleuthkit/autopsy/communications/RelationshipsViewer.java index 82464de4ba..395182f5d6 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/RelationshipsViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/RelationshipsViewer.java @@ -24,26 +24,26 @@ import javax.swing.JPanel; * Interface for Controls wishing to appear in the RelationshipBrowser tabPane. */ public interface RelationshipsViewer { - + /** * Returns the value to be displayed on the "tab" - * + * * @return String display name */ public String getDisplayName(); - + /** * Returns the JPanel to be displayed in the RelationshipBrowser. - * + * * @return JPanel to be displayed */ public JPanel getPanel(); - + /** * Sets current SelectionInfo allowing the panel to update accordingly. - * + * * @param info SelectionInfo instance representing the currently selected - * accounts + * accounts */ - public void setSelectionInfo(SelectionInfo info); + public void setSelectionInfo(SelectionInfo info); } diff --git a/Core/src/org/sleuthkit/autopsy/communications/SelectionInfo.java b/Core/src/org/sleuthkit/autopsy/communications/SelectionInfo.java index 0b63e1c129..d1a28eb2d6 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/SelectionInfo.java +++ b/Core/src/org/sleuthkit/autopsy/communications/SelectionInfo.java @@ -18,29 +18,46 @@ */ package org.sleuthkit.autopsy.communications; -import java.util.Date; - +import java.util.Set; +import org.sleuthkit.datamodel.AccountDeviceInstance; +import org.sleuthkit.datamodel.CommunicationsFilter; /** * Class to wrap the details of the current selection from the AccountBrowser or * VisualizationPane - * */ -public class SelectionInfo { - - private final String displayString; - - SelectionInfo() { - displayString = (new Date()).toString(); - } - - /** - * Temporary function for testing data flow - * - * @return A String representing the time the object was created - */ - public String getString() { - return displayString; - } - +public final class SelectionInfo { + + private final Set accountDeviceInstances; + private final CommunicationsFilter communicationFilter; + + /** + * Wraps the details of the currently selected accounts. + * + * @param accountDeviceInstances Selected accountDecivedInstances + * @param communicationFilter Currently selected communications filters + */ + SelectionInfo(Set accountDeviceInstances, CommunicationsFilter communicationFilter) { + this.accountDeviceInstances = accountDeviceInstances; + this.communicationFilter = communicationFilter; + } + + /** + * Returns the currently selected accountDeviceInstances + * + * @return Set of AccountDeviceInstance + */ + public Set getAccountDevicesInstances() { + return accountDeviceInstances; + } + + /** + * Returns the currently selected communications filters. + * + * @return Instance of CommunicationsFilter + */ + public CommunicationsFilter getCommunicationsFilter() { + return communicationFilter; + } + }