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.Lookup;
 
   53 import org.openide.util.NbBundle.Messages;
 
   62 @SuppressWarnings(
"PMD.SingularField") 
 
   63 final class MessageViewer extends JPanel implements RelationshipsViewer {
 
   65     private static final Logger logger = Logger.
getLogger(MessageViewer.class.getName());
 
   66     private static final long serialVersionUID = 1L;
 
   68     private final ModifiableProxyLookup proxyLookup;
 
   69     private PropertyChangeListener focusPropertyListener;
 
   70     private final ThreadChildNodeFactory rootMessageFactory;
 
   71     private final MessagesChildNodeFactory threadMessageNodeFactory;
 
   73     private SelectionInfo currentSelectionInfo = null;
 
   75     private OutlineViewPanel currentPanel;
 
   78         "MessageViewer_tabTitle=Messages",
 
   79         "MessageViewer_columnHeader_From=From",
 
   80         "MessageViewer_columnHeader_Date=Date",
 
   81         "MessageViewer_columnHeader_To=To",
 
   82         "MessageViewer_columnHeader_EarlyDate=Earliest Message",
 
   83         "MessageViewer_columnHeader_Subject=Subject",
 
   84         "MessageViewer_columnHeader_Attms=Attachments",
 
   85         "MessageViewer_no_messages=<No messages found for selected account>",
 
   86         "MessageViewer_viewMessage_all=All",
 
   87         "MessageViewer_viewMessage_selected=Selected",
 
   88         "MessageViewer_viewMessage_unthreaded=Unthreaded",
 
   89         "MessageViewer_viewMessage_calllogs=Call Logs"})
 
   97         currentPanel = rootTablePane;
 
   98         proxyLookup = 
new ModifiableProxyLookup(createLookup(rootTablePane.getExplorerManager(), getActionMap()));
 
   99         rootMessageFactory = 
new ThreadChildNodeFactory(
new ShowThreadMessagesAction());
 
  100         threadMessageNodeFactory = 
new MessagesChildNodeFactory();
 
  102         rootTablePane.getExplorerManager().setRootContext(
 
  103                 new AbstractNode(Children.create(rootMessageFactory, 
true)));
 
  105         rootTablePane.getOutlineView().setPopupAllowed(
false);
 
  107         Outline outline = rootTablePane.getOutlineView().getOutline();
 
  108         rootTablePane.getOutlineView().setPropertyColumns(
 
  109                 "Date", Bundle.MessageViewer_columnHeader_EarlyDate(),
 
  110                 "Subject", Bundle.MessageViewer_columnHeader_Subject()
 
  112         outline.setRootVisible(
false);
 
  113         ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(
"Type");
 
  114         outline.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
 
  116         rootTablePane.getExplorerManager().addPropertyChangeListener((PropertyChangeEvent evt) -> {
 
  117             if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
 
  118                 showSelectedThread();
 
  122         rootTablePane.getOutlineView().getOutline().getModel().addTableModelListener(
new TableModelListener() {
 
  124             public void tableChanged(TableModelEvent e) {
 
  125                 Utils.setColumnWidths(rootTablePane.getOutlineView().getOutline());
 
  129         threadMessagesPanel.setChildFactory(threadMessageNodeFactory);
 
  131         rootTablePane.setTableColumnsWidth(10, 20, 70);
 
  133         Image image = getScaledImage((
new ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/timeline/images/arrow-180.png"))).getImage(), 16, 16);
 
  134         backButton.setIcon(
new ImageIcon(image));
 
  138     public String getDisplayName() {
 
  139         return Bundle.MessageViewer_tabTitle();
 
  143     public JPanel getPanel() {
 
  148     public void setSelectionInfo(SelectionInfo info) {
 
  149         currentSelectionInfo = info;
 
  151         currentPanel = rootTablePane;
 
  153         CardLayout layout = (CardLayout) this.getLayout();
 
  154         layout.show(
this, 
"threads");
 
  156         rootMessageFactory.refresh(info);
 
  160     public Lookup getLookup() {
 
  165     public void addNotify() {
 
  168         if (focusPropertyListener == null) {
 
  171             focusPropertyListener = (
final PropertyChangeEvent focusEvent) -> {
 
  172                 if (focusEvent.getPropertyName().equalsIgnoreCase(
"focusOwner")) {
 
  173                     handleFocusChange((Component) focusEvent.getNewValue());
 
  179         KeyboardFocusManager.getCurrentKeyboardFocusManager()
 
  180                 .addPropertyChangeListener(
"focusOwner", focusPropertyListener);
 
  188     private void handleFocusChange(Component newFocusOwner) {
 
  189         if (newFocusOwner == null) {
 
  192         if (isDescendingFrom(newFocusOwner, rootTablePane)) {
 
  193             proxyLookup.setNewLookups(createLookup(rootTablePane.getExplorerManager(), getActionMap()));
 
  194         } 
else if (isDescendingFrom(newFocusOwner, 
this)) {
 
  195             proxyLookup.setNewLookups(createLookup(currentPanel.getExplorerManager(), getActionMap()));
 
  200     public void removeNotify() {
 
  201         super.removeNotify();
 
  202         KeyboardFocusManager.getCurrentKeyboardFocusManager()
 
  203                 .removePropertyChangeListener(
"focusOwner", focusPropertyListener);
 
  206     @SuppressWarnings(
"rawtypes")
 
  207     private 
void showSelectedThread() {
 
  208         final Node[] nodes = rootTablePane.getExplorerManager().getSelectedNodes();
 
  214         if (nodes.length == 0 || nodes.length > 1) {
 
  218         ArrayList<String> threadIDList = 
new ArrayList<>();
 
  221         PropertySet[] propertySets = nodes[0].getPropertySets();
 
  222         for (PropertySet pset : propertySets) {
 
  223             Property[] properties = pset.getProperties();
 
  224             for (Property prop : properties) {
 
  225                 if (prop.getName().equalsIgnoreCase(
"threadid")) {
 
  227                         String threadID = prop.getValue().toString();
 
  228                         if (!threadIDList.contains(threadID)) {
 
  229                             threadIDList.add(threadID);
 
  231                     } 
catch (IllegalAccessException | InvocationTargetException ex) {
 
  232                         logger.log(Level.WARNING, String.format(
"Unable to get threadid for node: %s", nodes[0].getDisplayName()), ex);
 
  234                 } 
else if (prop.getName().equalsIgnoreCase(
"subject")) {
 
  236                         subject = prop.getValue().toString();
 
  237                     } 
catch (IllegalAccessException | InvocationTargetException ex) {
 
  238                         logger.log(Level.WARNING, String.format(
"Unable to get subject for node: %s", nodes[0].getDisplayName()), ex);
 
  239                         subject = 
"<unavailable>";
 
  246         if (!threadIDList.isEmpty()) {
 
  247             threadMessageNodeFactory.refresh(currentSelectionInfo, threadIDList);
 
  249             if (!subject.isEmpty()) {
 
  250                 threadNameLabel.setText(subject);
 
  252                 threadNameLabel.setText(Bundle.MessageViewer_viewMessage_unthreaded());
 
  262     private void showThreadsPane() {
 
  263         switchCard(
"threads");
 
  269     private void showMessagesPane() {
 
  270         switchCard(
"messages");
 
  278     private void switchCard(String cardName) {
 
  279         SwingUtilities.invokeLater(
new Runnable() {
 
  282                 CardLayout layout = (CardLayout) getLayout();
 
  283                 layout.show(MessageViewer.this, cardName);
 
  297     private Image getScaledImage(Image srcImg, 
int w, 
int h) {
 
  298         BufferedImage resizedImg = 
new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
 
  299         Graphics2D g2 = resizedImg.createGraphics();
 
  301         g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
 
  302         g2.drawImage(srcImg, 0, 0, w, h, null);
 
  313     @SuppressWarnings(
"unchecked")
 
  315     private 
void initComponents() {
 
  316         java.awt.GridBagConstraints gridBagConstraints;
 
  318         rootMessagesPane = 
new javax.swing.JPanel();
 
  319         threadsLabel = 
new javax.swing.JLabel();
 
  320         showAllButton = 
new javax.swing.JButton();
 
  322         messagePanel = 
new javax.swing.JPanel();
 
  323         threadMessagesPanel = 
new MessagesPanel();
 
  324         backButton = 
new javax.swing.JButton();
 
  325         showingMessagesLabel = 
new javax.swing.JLabel();
 
  326         threadNameLabel = 
new javax.swing.JLabel();
 
  328         setLayout(
new java.awt.CardLayout());
 
  330         rootMessagesPane.setOpaque(
false);
 
  331         rootMessagesPane.setLayout(
new java.awt.GridBagLayout());
 
  333         org.openide.awt.Mnemonics.setLocalizedText(threadsLabel, 
org.openide.util.NbBundle.getMessage(MessageViewer.class, 
"MessageViewer.threadsLabel.text")); 
 
  334         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  335         gridBagConstraints.gridx = 0;
 
  336         gridBagConstraints.gridy = 0;
 
  337         gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
 
  338         gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
 
  339         gridBagConstraints.weightx = 1.0;
 
  340         gridBagConstraints.insets = 
new java.awt.Insets(15, 15, 9, 0);
 
  341         rootMessagesPane.add(threadsLabel, gridBagConstraints);
 
  343         org.openide.awt.Mnemonics.setLocalizedText(showAllButton, 
org.openide.util.NbBundle.getMessage(MessageViewer.class, 
"MessageViewer.showAllButton.text")); 
 
  344         showAllButton.addActionListener(
new java.awt.event.ActionListener() {
 
  345             public void actionPerformed(java.awt.event.ActionEvent evt) {
 
  346                 showAllButtonActionPerformed(evt);
 
  349         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  350         gridBagConstraints.gridx = 0;
 
  351         gridBagConstraints.gridy = 2;
 
  352         gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
 
  353         gridBagConstraints.insets = 
new java.awt.Insets(0, 15, 15, 0);
 
  354         rootMessagesPane.add(showAllButton, gridBagConstraints);
 
  356         rootTablePane.setBorder(javax.swing.BorderFactory.createLineBorder(
new java.awt.Color(0, 0, 0)));
 
  357         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  358         gridBagConstraints.gridx = 0;
 
  359         gridBagConstraints.gridy = 1;
 
  360         gridBagConstraints.gridwidth = 2;
 
  361         gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
 
  362         gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
 
  363         gridBagConstraints.weightx = 1.0;
 
  364         gridBagConstraints.weighty = 1.0;
 
  365         gridBagConstraints.insets = 
new java.awt.Insets(0, 15, 9, 15);
 
  366         rootMessagesPane.add(rootTablePane, gridBagConstraints);
 
  368         add(rootMessagesPane, 
"threads");
 
  370         messagePanel.setLayout(
new java.awt.GridBagLayout());
 
  371         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  372         gridBagConstraints.gridx = 0;
 
  373         gridBagConstraints.gridy = 3;
 
  374         gridBagConstraints.gridwidth = 3;
 
  375         gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
 
  376         gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
 
  377         gridBagConstraints.weightx = 1.0;
 
  378         gridBagConstraints.weighty = 1.0;
 
  379         gridBagConstraints.insets = 
new java.awt.Insets(0, 15, 0, 15);
 
  380         messagePanel.add(threadMessagesPanel, gridBagConstraints);
 
  382         org.openide.awt.Mnemonics.setLocalizedText(backButton, 
org.openide.util.NbBundle.getMessage(MessageViewer.class, 
"MessageViewer.backButton.text")); 
 
  383         backButton.addActionListener(
new java.awt.event.ActionListener() {
 
  384             public void actionPerformed(java.awt.event.ActionEvent evt) {
 
  385                 backButtonActionPerformed(evt);
 
  388         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  389         gridBagConstraints.gridx = 2;
 
  390         gridBagConstraints.gridy = 0;
 
  391         gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
 
  392         gridBagConstraints.weightx = 1.0;
 
  393         gridBagConstraints.insets = 
new java.awt.Insets(9, 0, 9, 15);
 
  394         messagePanel.add(backButton, gridBagConstraints);
 
  395         backButton.getAccessibleContext().setAccessibleDescription(
org.openide.util.NbBundle.getMessage(MessageViewer.class, 
"MessageViewer.backButton.AccessibleContext.accessibleDescription")); 
 
  397         org.openide.awt.Mnemonics.setLocalizedText(showingMessagesLabel, 
org.openide.util.NbBundle.getMessage(MessageViewer.class, 
"MessageViewer.showingMessagesLabel.text")); 
 
  398         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  399         gridBagConstraints.gridx = 0;
 
  400         gridBagConstraints.gridy = 0;
 
  401         gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
 
  402         gridBagConstraints.insets = 
new java.awt.Insets(9, 15, 5, 0);
 
  403         messagePanel.add(showingMessagesLabel, gridBagConstraints);
 
  405         org.openide.awt.Mnemonics.setLocalizedText(threadNameLabel, 
org.openide.util.NbBundle.getMessage(MessageViewer.class, 
"MessageViewer.threadNameLabel.text")); 
 
  406         gridBagConstraints = 
new java.awt.GridBagConstraints();
 
  407         gridBagConstraints.gridx = 1;
 
  408         gridBagConstraints.gridy = 0;
 
  409         gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
 
  410         gridBagConstraints.insets = 
new java.awt.Insets(9, 5, 5, 15);
 
  411         messagePanel.add(threadNameLabel, gridBagConstraints);
 
  413         add(messagePanel, 
"messages");
 
  416     private void backButtonActionPerformed(java.awt.event.ActionEvent evt) {
 
  418             rootTablePane.getExplorerManager().setSelectedNodes(
new Node[0]);
 
  419         } 
catch (PropertyVetoException ex) {
 
  420             logger.log(Level.WARNING, 
"Error setting selected nodes", ex);
 
  425     private void showAllButtonActionPerformed(java.awt.event.ActionEvent evt) {
 
  426         threadMessageNodeFactory.refresh(currentSelectionInfo, null);
 
  427         threadNameLabel.setText(
"All Messages");
 
  433     private javax.swing.JButton backButton;
 
  434     private javax.swing.JPanel messagePanel;
 
  435     private javax.swing.JPanel rootMessagesPane;
 
  437     private javax.swing.JButton showAllButton;
 
  438     private javax.swing.JLabel showingMessagesLabel;
 
  440     private javax.swing.JLabel threadNameLabel;
 
  441     private javax.swing.JLabel threadsLabel;
 
  447     class ShowThreadMessagesAction 
extends AbstractAction {
 
  450         public void actionPerformed(ActionEvent e) {
 
  452             SwingUtilities.invokeLater(
new Runnable() {
 
  455                     showSelectedThread();
 
synchronized static Logger getLogger(String name)