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 javax.swing.event.TableModelEvent;
 
   42 import javax.swing.event.TableModelListener;
 
   43 import org.netbeans.swing.outline.DefaultOutlineModel;
 
   44 import org.netbeans.swing.outline.Outline;
 
   45 import org.openide.explorer.ExplorerManager;
 
   46 import static org.openide.explorer.ExplorerUtils.createLookup;
 
   47 import org.openide.nodes.AbstractNode;
 
   48 import org.openide.nodes.Children;
 
   49 import org.openide.nodes.Node;
 
   50 import org.openide.nodes.Node.Property;
 
   51 import org.openide.nodes.Node.PropertySet;
 
   52 import org.openide.util.Exceptions;
 
   53 import org.openide.util.Lookup;
 
   54 import org.openide.util.NbBundle.Messages;
 
   63 @SuppressWarnings(
"PMD.SingularField") 
 
   64 final class MessageViewer extends JPanel implements RelationshipsViewer {
 
   66     private static final Logger logger = Logger.
getLogger(MessageViewer.class.getName());
 
   67     private static final long serialVersionUID = 1L;
 
   69     private final ModifiableProxyLookup proxyLookup;
 
   70     private PropertyChangeListener focusPropertyListener;
 
   71     private final ThreadChildNodeFactory rootMessageFactory;
 
   72     private final MessagesChildNodeFactory threadMessageNodeFactory;
 
   74     private SelectionInfo currentSelectionInfo = null;
 
   76     private OutlineViewPanel currentPanel;
 
   79         "MessageViewer_tabTitle=Messages",
 
   80         "MessageViewer_columnHeader_From=From",
 
   81         "MessageViewer_columnHeader_Date=Date",
 
   82         "MessageViewer_columnHeader_To=To",
 
   83         "MessageViewer_columnHeader_EarlyDate=Earliest Message",
 
   84         "MessageViewer_columnHeader_Subject=Subject",
 
   85         "MessageViewer_columnHeader_Attms=Attachments",
 
   86         "MessageViewer_no_messages=<No messages found for selected account>",
 
   87         "MessageViewer_viewMessage_all=All",
 
   88         "MessageViewer_viewMessage_selected=Selected",
 
   89         "MessageViewer_viewMessage_unthreaded=Unthreaded",
 
   90         "MessageViewer_viewMessage_calllogs=Call Logs"})
 
   98         currentPanel = rootTablePane;
 
   99         proxyLookup = 
new ModifiableProxyLookup(createLookup(rootTablePane.getExplorerManager(), getActionMap()));
 
  100         rootMessageFactory = 
new ThreadChildNodeFactory(
new ShowThreadMessagesAction());
 
  101         threadMessageNodeFactory = 
new MessagesChildNodeFactory();
 
  103         rootTablePane.getExplorerManager().setRootContext(
 
  104                 new AbstractNode(Children.create(rootMessageFactory, 
true)));
 
  106         rootTablePane.getOutlineView().setPopupAllowed(
false);
 
  108         Outline outline = rootTablePane.getOutlineView().getOutline();
 
  109         rootTablePane.getOutlineView().setPropertyColumns(
 
  110                 "Date", Bundle.MessageViewer_columnHeader_EarlyDate(),
 
  111                 "Subject", Bundle.MessageViewer_columnHeader_Subject()
 
  113         outline.setRootVisible(
false);
 
  114         ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(
"Type");
 
  115         outline.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
 
  117         rootTablePane.getExplorerManager().addPropertyChangeListener((PropertyChangeEvent evt) -> {
 
  118             if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
 
  119                 showSelectedThread();
 
  123         rootTablePane.getOutlineView().getOutline().getModel().addTableModelListener(
new TableModelListener() {
 
  125             public void tableChanged(TableModelEvent e) {
 
  126                 Utils.setColumnWidths(rootTablePane.getOutlineView().getOutline());
 
  130         threadMessagesPanel.setChildFactory(threadMessageNodeFactory);
 
  132         rootTablePane.setTableColumnsWidth(10, 20, 70);
 
  134         Image image = getScaledImage((
new ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/timeline/images/arrow-180.png"))).getImage(), 16, 16);
 
  135         backButton.setIcon(
new ImageIcon(image));
 
  139     public String getDisplayName() {
 
  140         return Bundle.MessageViewer_tabTitle();
 
  144     public JPanel getPanel() {
 
  149     public void setSelectionInfo(SelectionInfo info) {
 
  150         if(currentSelectionInfo != null && currentSelectionInfo.equals(info)) {
 
  154                 rootTablePane.getExplorerManager().setSelectedNodes(
new Node[0]);
 
  155             } 
catch (PropertyVetoException ex) {
 
  156                 logger.log(Level.WARNING, 
"Error clearing the selected node", ex);
 
  159             currentSelectionInfo = info;
 
  160             rootMessageFactory.refresh(info);
 
  163         currentPanel = rootTablePane;
 
  165         CardLayout layout = (CardLayout) this.getLayout();
 
  166         layout.show(
this, 
"threads"); 
 
  170     public Lookup getLookup() {
 
  175     public void addNotify() {
 
  178         if (focusPropertyListener == null) {
 
  181             focusPropertyListener = (
final PropertyChangeEvent focusEvent) -> {
 
  182                 if (focusEvent.getPropertyName().equalsIgnoreCase(
"focusOwner")) {
 
  183                     handleFocusChange((Component) focusEvent.getNewValue());
 
  189         KeyboardFocusManager.getCurrentKeyboardFocusManager()
 
  190                 .addPropertyChangeListener(
"focusOwner", focusPropertyListener);
 
  198     private void handleFocusChange(Component newFocusOwner) {
 
  199         if (newFocusOwner == null) {
 
  202         if (isDescendingFrom(newFocusOwner, rootTablePane)) {
 
  203             proxyLookup.setNewLookups(createLookup(rootTablePane.getExplorerManager(), getActionMap()));
 
  204         } 
else if (isDescendingFrom(newFocusOwner, 
this)) {
 
  205             proxyLookup.setNewLookups(createLookup(threadMessagesPanel.getExplorerManager(), getActionMap()));
 
  210     public void removeNotify() {
 
  211         super.removeNotify();
 
  212         KeyboardFocusManager.getCurrentKeyboardFocusManager()
 
  213                 .removePropertyChangeListener(
"focusOwner", focusPropertyListener);
 
  216     @SuppressWarnings(
"rawtypes")
 
  217     private 
void showSelectedThread() {
 
  218         final Node[] nodes = rootTablePane.getExplorerManager().getSelectedNodes();
 
  224         if (nodes.length == 0 || nodes.length > 1) {
 
  228         ArrayList<String> threadIDList = 
new ArrayList<>();
 
  231         PropertySet[] propertySets = nodes[0].getPropertySets();
 
  232         for (PropertySet pset : propertySets) {
 
  233             Property[] properties = pset.getProperties();
 
  234             for (Property prop : properties) {
 
  235                 if (prop.getName().equalsIgnoreCase(
"threadid")) {
 
  237                         String threadID = prop.getValue().toString();
 
  238                         if (!threadIDList.contains(threadID)) {
 
  239                             threadIDList.add(threadID);
 
  241                     } 
catch (IllegalAccessException | InvocationTargetException ex) {
 
  242                         logger.log(Level.WARNING, String.format(
"Unable to get threadid for node: %s", nodes[0].getDisplayName()), ex);
 
  244                 } 
else if (prop.getName().equalsIgnoreCase(
"subject")) {
 
  246                         subject = prop.getValue().toString();
 
  247                     } 
catch (IllegalAccessException | InvocationTargetException ex) {
 
  248                         logger.log(Level.WARNING, String.format(
"Unable to get subject for node: %s", nodes[0].getDisplayName()), ex);
 
  249                         subject = 
"<unavailable>";
 
  256         if (!threadIDList.isEmpty()) {
 
  257             threadMessageNodeFactory.refresh(currentSelectionInfo, threadIDList);
 
  259             if (!subject.isEmpty()) {
 
  260                 threadNameLabel.setText(subject);
 
  262                 threadNameLabel.setText(Bundle.MessageViewer_viewMessage_unthreaded());
 
  272     private void showThreadsPane() {
 
  273         switchCard(
"threads");
 
  279     private void showMessagesPane() {
 
  280         switchCard(
"messages");
 
  288     private void switchCard(String cardName) {
 
  289         SwingUtilities.invokeLater(
new Runnable() {
 
  292                 CardLayout layout = (CardLayout) getLayout();
 
  293                 layout.show(MessageViewer.this, cardName);
 
  307     private Image getScaledImage(Image srcImg, 
int w, 
int h) {
 
  308         BufferedImage resizedImg = 
new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
 
  309         Graphics2D g2 = resizedImg.createGraphics();
 
  311         g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
 
  312         g2.drawImage(srcImg, 0, 0, w, h, null);
 
  323     @SuppressWarnings(
"unchecked")
 
  325     private 
void initComponents() {
 
  326         java.awt.GridBagConstraints gridBagConstraints;
 
  328         rootMessagesPane = 
new javax.swing.JPanel();
 
  329         threadsLabel = 
new javax.swing.JLabel();
 
  330         showAllButton = 
new javax.swing.JButton();
 
  332         messagePanel = 
new javax.swing.JPanel();
 
  333         threadMessagesPanel = 
new MessagesPanel();
 
  334         backButton = 
new javax.swing.JButton();
 
  335         showingMessagesLabel = 
new javax.swing.JLabel();
 
  336         threadNameLabel = 
new javax.swing.JLabel();
 
  338         setLayout(
new java.awt.CardLayout());
 
  340         rootMessagesPane.setOpaque(
false);
 
  341         rootMessagesPane.setLayout(
new java.awt.GridBagLayout());
 
  343         org.openide.awt.Mnemonics.setLocalizedText(threadsLabel, 
org.openide.util.NbBundle.getMessage(MessageViewer.class, 
"MessageViewer.threadsLabel.text")); 
 
  344         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  345         gridBagConstraints.gridx = 0;
 
  346         gridBagConstraints.gridy = 0;
 
  347         gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
 
  348         gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
 
  349         gridBagConstraints.weightx = 1.0;
 
  350         gridBagConstraints.insets = 
new java.awt.Insets(15, 15, 9, 0);
 
  351         rootMessagesPane.add(threadsLabel, gridBagConstraints);
 
  353         org.openide.awt.Mnemonics.setLocalizedText(showAllButton, 
org.openide.util.NbBundle.getMessage(MessageViewer.class, 
"MessageViewer.showAllButton.text")); 
 
  354         showAllButton.addActionListener(
new java.awt.event.ActionListener() {
 
  355             public void actionPerformed(java.awt.event.ActionEvent evt) {
 
  356                 showAllButtonActionPerformed(evt);
 
  359         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  360         gridBagConstraints.gridx = 0;
 
  361         gridBagConstraints.gridy = 2;
 
  362         gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
 
  363         gridBagConstraints.insets = 
new java.awt.Insets(0, 15, 15, 0);
 
  364         rootMessagesPane.add(showAllButton, gridBagConstraints);
 
  366         rootTablePane.setBorder(javax.swing.BorderFactory.createLineBorder(
new java.awt.Color(0, 0, 0)));
 
  367         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  368         gridBagConstraints.gridx = 0;
 
  369         gridBagConstraints.gridy = 1;
 
  370         gridBagConstraints.gridwidth = 2;
 
  371         gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
 
  372         gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
 
  373         gridBagConstraints.weightx = 1.0;
 
  374         gridBagConstraints.weighty = 1.0;
 
  375         gridBagConstraints.insets = 
new java.awt.Insets(0, 15, 9, 15);
 
  376         rootMessagesPane.add(rootTablePane, gridBagConstraints);
 
  378         add(rootMessagesPane, 
"threads");
 
  380         messagePanel.setLayout(
new java.awt.GridBagLayout());
 
  381         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  382         gridBagConstraints.gridx = 0;
 
  383         gridBagConstraints.gridy = 3;
 
  384         gridBagConstraints.gridwidth = 3;
 
  385         gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
 
  386         gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
 
  387         gridBagConstraints.weightx = 1.0;
 
  388         gridBagConstraints.weighty = 1.0;
 
  389         gridBagConstraints.insets = 
new java.awt.Insets(0, 15, 0, 15);
 
  390         messagePanel.add(threadMessagesPanel, gridBagConstraints);
 
  392         org.openide.awt.Mnemonics.setLocalizedText(backButton, 
org.openide.util.NbBundle.getMessage(MessageViewer.class, 
"MessageViewer.backButton.text")); 
 
  393         backButton.addActionListener(
new java.awt.event.ActionListener() {
 
  394             public void actionPerformed(java.awt.event.ActionEvent evt) {
 
  395                 backButtonActionPerformed(evt);
 
  398         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  399         gridBagConstraints.gridx = 2;
 
  400         gridBagConstraints.gridy = 0;
 
  401         gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
 
  402         gridBagConstraints.weightx = 1.0;
 
  403         gridBagConstraints.insets = 
new java.awt.Insets(9, 0, 9, 15);
 
  404         messagePanel.add(backButton, gridBagConstraints);
 
  405         backButton.getAccessibleContext().setAccessibleDescription(
org.openide.util.NbBundle.getMessage(MessageViewer.class, 
"MessageViewer.backButton.AccessibleContext.accessibleDescription")); 
 
  407         org.openide.awt.Mnemonics.setLocalizedText(showingMessagesLabel, 
org.openide.util.NbBundle.getMessage(MessageViewer.class, 
"MessageViewer.showingMessagesLabel.text")); 
 
  408         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  409         gridBagConstraints.gridx = 0;
 
  410         gridBagConstraints.gridy = 0;
 
  411         gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
 
  412         gridBagConstraints.insets = 
new java.awt.Insets(9, 15, 5, 0);
 
  413         messagePanel.add(showingMessagesLabel, gridBagConstraints);
 
  415         org.openide.awt.Mnemonics.setLocalizedText(threadNameLabel, 
org.openide.util.NbBundle.getMessage(MessageViewer.class, 
"MessageViewer.threadNameLabel.text")); 
 
  416         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  417         gridBagConstraints.gridx = 1;
 
  418         gridBagConstraints.gridy = 0;
 
  419         gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
 
  420         gridBagConstraints.insets = 
new java.awt.Insets(9, 5, 5, 15);
 
  421         messagePanel.add(threadNameLabel, gridBagConstraints);
 
  423         add(messagePanel, 
"messages");
 
  426     private void backButtonActionPerformed(java.awt.event.ActionEvent evt) {
 
  428             rootTablePane.getExplorerManager().setSelectedNodes(
new Node[0]);
 
  429         } 
catch (PropertyVetoException ex) {
 
  430             logger.log(Level.WARNING, 
"Error setting selected nodes", ex);
 
  435     private void showAllButtonActionPerformed(java.awt.event.ActionEvent evt) {
 
  436         threadMessageNodeFactory.refresh(currentSelectionInfo, null);
 
  437         threadNameLabel.setText(
"All Messages");
 
  443     private javax.swing.JButton backButton;
 
  444     private javax.swing.JPanel messagePanel;
 
  445     private javax.swing.JPanel rootMessagesPane;
 
  447     private javax.swing.JButton showAllButton;
 
  448     private javax.swing.JLabel showingMessagesLabel;
 
  450     private javax.swing.JLabel threadNameLabel;
 
  451     private javax.swing.JLabel threadsLabel;
 
  457     class ShowThreadMessagesAction 
extends AbstractAction {
 
  460         public void actionPerformed(ActionEvent e) {
 
  462             SwingUtilities.invokeLater(
new Runnable() {
 
  465                     showSelectedThread();
 
synchronized static Logger getLogger(String name)