19 package org.sleuthkit.autopsy.communications.relationships;
 
   21 import java.awt.CardLayout;
 
   22 import java.awt.Component;
 
   23 import java.awt.Graphics2D;
 
   24 import java.awt.Image;
 
   25 import java.awt.KeyboardFocusManager;
 
   26 import java.awt.RenderingHints;
 
   27 import java.awt.event.ActionEvent;
 
   28 import java.awt.image.BufferedImage;
 
   29 import java.beans.PropertyChangeEvent;
 
   30 import java.beans.PropertyChangeListener;
 
   31 import java.beans.PropertyVetoException;
 
   32 import java.lang.reflect.InvocationTargetException;
 
   33 import java.util.ArrayList;
 
   34 import java.util.logging.Level;
 
   35 import javax.swing.AbstractAction;
 
   36 import javax.swing.ImageIcon;
 
   37 import javax.swing.JPanel;
 
   38 import javax.swing.ListSelectionModel;
 
   39 import javax.swing.SwingUtilities;
 
   40 import static javax.swing.SwingUtilities.isDescendingFrom;
 
   41 import org.netbeans.swing.outline.DefaultOutlineModel;
 
   42 import org.netbeans.swing.outline.Outline;
 
   43 import org.openide.explorer.ExplorerManager;
 
   44 import static org.openide.explorer.ExplorerUtils.createLookup;
 
   45 import org.openide.nodes.AbstractNode;
 
   46 import org.openide.nodes.Children;
 
   47 import org.openide.nodes.Node;
 
   48 import org.openide.nodes.Node.Property;
 
   49 import org.openide.nodes.Node.PropertySet;
 
   50 import org.openide.util.Lookup;
 
   51 import org.openide.util.NbBundle.Messages;
 
   59 @SuppressWarnings(
"PMD.SingularField") 
 
   60 final class MessageViewer extends JPanel implements RelationshipsViewer {
 
   62     private static final Logger logger = Logger.
getLogger(MessageViewer.class.getName());
 
   63     private static final long serialVersionUID = 1L;
 
   65     private final ModifiableProxyLookup proxyLookup;
 
   66     private PropertyChangeListener focusPropertyListener;
 
   67     private final ThreadChildNodeFactory rootMessageFactory;
 
   68     private final MessagesChildNodeFactory threadMessageNodeFactory;
 
   70     private SelectionInfo currentSelectionInfo = null;
 
   72     private OutlineViewPanel currentPanel;
 
   75         "MessageViewer_tabTitle=Messages",
 
   76         "MessageViewer_columnHeader_From=From",
 
   77         "MessageViewer_columnHeader_Date=Date",
 
   78         "MessageViewer_columnHeader_To=To",
 
   79         "MessageViewer_columnHeader_EarlyDate=Earliest Message",
 
   80         "MessageViewer_columnHeader_Subject=Subject",
 
   81         "MessageViewer_columnHeader_Attms=Attachments",
 
   82         "MessageViewer_no_messages=<No messages found for selected account>",
 
   83         "MessageViewer_viewMessage_all=All",
 
   84         "MessageViewer_viewMessage_selected=Selected",
 
   85         "MessageViewer_viewMessage_unthreaded=Unthreaded",
 
   86         "MessageViewer_viewMessage_calllogs=Call Logs"})
 
   94         currentPanel = rootTablePane;
 
   95         proxyLookup = 
new ModifiableProxyLookup(createLookup(rootTablePane.getExplorerManager(), getActionMap()));
 
   96         rootMessageFactory = 
new ThreadChildNodeFactory(
new ShowThreadMessagesAction());
 
   97         threadMessageNodeFactory = 
new MessagesChildNodeFactory();
 
   99         rootTablePane.getExplorerManager().setRootContext(
 
  100                 new AbstractNode(Children.create(rootMessageFactory, 
true)));
 
  102         rootTablePane.getOutlineView().setPopupAllowed(
false);
 
  104         Outline outline = rootTablePane.getOutlineView().getOutline();
 
  105         rootTablePane.getOutlineView().setPropertyColumns(
 
  106                 "Date", Bundle.MessageViewer_columnHeader_EarlyDate(),
 
  107                 "Subject", Bundle.MessageViewer_columnHeader_Subject()
 
  109         outline.setRootVisible(
false);
 
  110         ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(
"Type");
 
  111         outline.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
 
  113         rootTablePane.getExplorerManager().addPropertyChangeListener((PropertyChangeEvent evt) -> {
 
  114             if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
 
  115                 showSelectedThread();
 
  119         threadMessagesPanel.setChildFactory(threadMessageNodeFactory);
 
  121         rootTablePane.setTableColumnsWidth(10, 20, 70);
 
  123         Image image = getScaledImage((
new ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/timeline/images/arrow-180.png"))).getImage(), 16, 16);
 
  124         backButton.setIcon(
new ImageIcon(image));
 
  128     public String getDisplayName() {
 
  129         return Bundle.MessageViewer_tabTitle();
 
  133     public JPanel getPanel() {
 
  138     public void setSelectionInfo(SelectionInfo info) {
 
  139         currentSelectionInfo = info;
 
  141         currentPanel = rootTablePane;
 
  143         CardLayout layout = (CardLayout) this.getLayout();
 
  144         layout.show(
this, 
"threads");
 
  146         rootMessageFactory.refresh(info);
 
  150     public Lookup getLookup() {
 
  155     public void addNotify() {
 
  158         if (focusPropertyListener == null) {
 
  161             focusPropertyListener = (
final PropertyChangeEvent focusEvent) -> {
 
  162                 if (focusEvent.getPropertyName().equalsIgnoreCase(
"focusOwner")) {
 
  163                     handleFocusChange((Component) focusEvent.getNewValue());
 
  169         KeyboardFocusManager.getCurrentKeyboardFocusManager()
 
  170                 .addPropertyChangeListener(
"focusOwner", focusPropertyListener);
 
  178     private void handleFocusChange(Component newFocusOwner) {
 
  179         if (newFocusOwner == null) {
 
  182         if (isDescendingFrom(newFocusOwner, rootTablePane)) {
 
  183             proxyLookup.setNewLookups(createLookup(rootTablePane.getExplorerManager(), getActionMap()));
 
  184         } 
else if (isDescendingFrom(newFocusOwner, 
this)) {
 
  185             proxyLookup.setNewLookups(createLookup(currentPanel.getExplorerManager(), getActionMap()));
 
  190     public void removeNotify() {
 
  191         super.removeNotify();
 
  192         KeyboardFocusManager.getCurrentKeyboardFocusManager()
 
  193                 .removePropertyChangeListener(
"focusOwner", focusPropertyListener);
 
  196     @SuppressWarnings(
"rawtypes")
 
  197     private 
void showSelectedThread() {
 
  198         final Node[] nodes = rootTablePane.getExplorerManager().getSelectedNodes();
 
  204         if (nodes.length == 0 || nodes.length > 1) {
 
  208         ArrayList<String> threadIDList = 
new ArrayList<>();
 
  211         PropertySet[] propertySets = nodes[0].getPropertySets();
 
  212         for (PropertySet pset : propertySets) {
 
  213             Property[] properties = pset.getProperties();
 
  214             for (Property prop : properties) {
 
  215                 if (prop.getName().equalsIgnoreCase(
"threadid")) {
 
  217                         String threadID = prop.getValue().toString();
 
  218                         if (!threadIDList.contains(threadID)) {
 
  219                             threadIDList.add(threadID);
 
  221                     } 
catch (IllegalAccessException | InvocationTargetException ex) {
 
  222                         logger.log(Level.WARNING, String.format(
"Unable to get threadid for node: %s", nodes[0].getDisplayName()), ex);
 
  224                 } 
else if (prop.getName().equalsIgnoreCase(
"subject")) {
 
  226                         subject = prop.getValue().toString();
 
  227                     } 
catch (IllegalAccessException | InvocationTargetException ex) {
 
  228                         logger.log(Level.WARNING, String.format(
"Unable to get subject for node: %s", nodes[0].getDisplayName()), ex);
 
  229                         subject = 
"<unavailable>";
 
  236         if (!threadIDList.isEmpty()) {
 
  237             threadMessageNodeFactory.refresh(currentSelectionInfo, threadIDList);
 
  239             if (!subject.isEmpty()) {
 
  240                 threadNameLabel.setText(subject);
 
  242                 threadNameLabel.setText(Bundle.MessageViewer_viewMessage_unthreaded());
 
  252     private void showThreadsPane() {
 
  253         switchCard(
"threads");
 
  259     private void showMessagesPane() {
 
  260         switchCard(
"messages");
 
  261         Outline outline = rootTablePane.getOutlineView().getOutline();
 
  262         outline.clearSelection();
 
  270     private void switchCard(String cardName) {
 
  271         SwingUtilities.invokeLater(
new Runnable() {
 
  274                 CardLayout layout = (CardLayout) getLayout();
 
  275                 layout.show(MessageViewer.this, cardName);
 
  289     private Image getScaledImage(Image srcImg, 
int w, 
int h) {
 
  290         BufferedImage resizedImg = 
new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
 
  291         Graphics2D g2 = resizedImg.createGraphics();
 
  293         g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
 
  294         g2.drawImage(srcImg, 0, 0, w, h, null);
 
  305     @SuppressWarnings(
"unchecked")
 
  307     private 
void initComponents() {
 
  308         java.awt.GridBagConstraints gridBagConstraints;
 
  310         rootMessagesPane = 
new javax.swing.JPanel();
 
  311         threadsLabel = 
new javax.swing.JLabel();
 
  312         showAllButton = 
new javax.swing.JButton();
 
  314         messagePanel = 
new javax.swing.JPanel();
 
  315         threadMessagesPanel = 
new MessagesPanel();
 
  316         backButton = 
new javax.swing.JButton();
 
  317         showingMessagesLabel = 
new javax.swing.JLabel();
 
  318         threadNameLabel = 
new javax.swing.JLabel();
 
  320         setLayout(
new java.awt.CardLayout());
 
  322         rootMessagesPane.setOpaque(
false);
 
  323         rootMessagesPane.setLayout(
new java.awt.GridBagLayout());
 
  325         org.openide.awt.Mnemonics.setLocalizedText(threadsLabel, 
org.openide.util.NbBundle.getMessage(MessageViewer.class, 
"MessageViewer.threadsLabel.text")); 
 
  326         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  327         gridBagConstraints.gridx = 0;
 
  328         gridBagConstraints.gridy = 0;
 
  329         gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
 
  330         gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
 
  331         gridBagConstraints.weightx = 1.0;
 
  332         gridBagConstraints.insets = 
new java.awt.Insets(15, 15, 9, 0);
 
  333         rootMessagesPane.add(threadsLabel, gridBagConstraints);
 
  335         org.openide.awt.Mnemonics.setLocalizedText(showAllButton, 
org.openide.util.NbBundle.getMessage(MessageViewer.class, 
"MessageViewer.showAllButton.text")); 
 
  336         showAllButton.addActionListener(
new java.awt.event.ActionListener() {
 
  337             public void actionPerformed(java.awt.event.ActionEvent evt) {
 
  338                 showAllButtonActionPerformed(evt);
 
  341         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  342         gridBagConstraints.gridx = 0;
 
  343         gridBagConstraints.gridy = 2;
 
  344         gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
 
  345         gridBagConstraints.insets = 
new java.awt.Insets(0, 15, 15, 0);
 
  346         rootMessagesPane.add(showAllButton, gridBagConstraints);
 
  348         rootTablePane.setBorder(javax.swing.BorderFactory.createLineBorder(
new java.awt.Color(0, 0, 0)));
 
  349         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  350         gridBagConstraints.gridx = 0;
 
  351         gridBagConstraints.gridy = 1;
 
  352         gridBagConstraints.gridwidth = 2;
 
  353         gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
 
  354         gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
 
  355         gridBagConstraints.weightx = 1.0;
 
  356         gridBagConstraints.weighty = 1.0;
 
  357         gridBagConstraints.insets = 
new java.awt.Insets(0, 15, 9, 15);
 
  358         rootMessagesPane.add(rootTablePane, gridBagConstraints);
 
  360         add(rootMessagesPane, 
"threads");
 
  362         messagePanel.setLayout(
new java.awt.GridBagLayout());
 
  363         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  364         gridBagConstraints.gridx = 0;
 
  365         gridBagConstraints.gridy = 3;
 
  366         gridBagConstraints.gridwidth = 3;
 
  367         gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
 
  368         gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
 
  369         gridBagConstraints.weightx = 1.0;
 
  370         gridBagConstraints.weighty = 1.0;
 
  371         gridBagConstraints.insets = 
new java.awt.Insets(0, 15, 0, 15);
 
  372         messagePanel.add(threadMessagesPanel, gridBagConstraints);
 
  374         org.openide.awt.Mnemonics.setLocalizedText(backButton, 
org.openide.util.NbBundle.getMessage(MessageViewer.class, 
"MessageViewer.backButton.text")); 
 
  375         backButton.addActionListener(
new java.awt.event.ActionListener() {
 
  376             public void actionPerformed(java.awt.event.ActionEvent evt) {
 
  377                 backButtonActionPerformed(evt);
 
  380         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  381         gridBagConstraints.gridx = 2;
 
  382         gridBagConstraints.gridy = 0;
 
  383         gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
 
  384         gridBagConstraints.weightx = 1.0;
 
  385         gridBagConstraints.insets = 
new java.awt.Insets(9, 0, 9, 15);
 
  386         messagePanel.add(backButton, gridBagConstraints);
 
  387         backButton.getAccessibleContext().setAccessibleDescription(
org.openide.util.NbBundle.getMessage(MessageViewer.class, 
"MessageViewer.backButton.AccessibleContext.accessibleDescription")); 
 
  389         org.openide.awt.Mnemonics.setLocalizedText(showingMessagesLabel, 
org.openide.util.NbBundle.getMessage(MessageViewer.class, 
"MessageViewer.showingMessagesLabel.text")); 
 
  390         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  391         gridBagConstraints.gridx = 0;
 
  392         gridBagConstraints.gridy = 0;
 
  393         gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
 
  394         gridBagConstraints.insets = 
new java.awt.Insets(9, 15, 5, 0);
 
  395         messagePanel.add(showingMessagesLabel, gridBagConstraints);
 
  397         org.openide.awt.Mnemonics.setLocalizedText(threadNameLabel, 
org.openide.util.NbBundle.getMessage(MessageViewer.class, 
"MessageViewer.threadNameLabel.text")); 
 
  398         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  399         gridBagConstraints.gridx = 1;
 
  400         gridBagConstraints.gridy = 0;
 
  401         gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
 
  402         gridBagConstraints.insets = 
new java.awt.Insets(9, 5, 5, 15);
 
  403         messagePanel.add(threadNameLabel, gridBagConstraints);
 
  405         add(messagePanel, 
"messages");
 
  408     private void backButtonActionPerformed(java.awt.event.ActionEvent evt) {
 
  410             rootTablePane.getExplorerManager().setSelectedNodes(
new Node[0]);
 
  411         } 
catch (PropertyVetoException ex) {
 
  412             logger.log(Level.WARNING, 
"Error setting selected nodes", ex);
 
  417     private void showAllButtonActionPerformed(java.awt.event.ActionEvent evt) {
 
  418         threadMessageNodeFactory.refresh(currentSelectionInfo, null);
 
  419         threadNameLabel.setText(
"All Messages");
 
  425     private javax.swing.JButton backButton;
 
  426     private javax.swing.JPanel messagePanel;
 
  427     private javax.swing.JPanel rootMessagesPane;
 
  429     private javax.swing.JButton showAllButton;
 
  430     private javax.swing.JLabel showingMessagesLabel;
 
  432     private javax.swing.JLabel threadNameLabel;
 
  433     private javax.swing.JLabel threadsLabel;
 
  439     class ShowThreadMessagesAction 
extends AbstractAction {
 
  442         public void actionPerformed(ActionEvent e) {
 
  444             SwingUtilities.invokeLater(
new Runnable() {
 
  447                     showSelectedThread();
 
synchronized static Logger getLogger(String name)