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)