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 setMinimumSize(
new java.awt.Dimension(450, 292));
340 setLayout(
new java.awt.CardLayout());
342 rootMessagesPane.setOpaque(
false);
343 rootMessagesPane.setLayout(
new java.awt.GridBagLayout());
345 org.openide.awt.Mnemonics.setLocalizedText(threadsLabel,
org.openide.util.NbBundle.getMessage(MessageViewer.class,
"MessageViewer.threadsLabel.text"));
346 gridBagConstraints =
new java.awt.GridBagConstraints();
347 gridBagConstraints.gridx = 0;
348 gridBagConstraints.gridy = 0;
349 gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
350 gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
351 gridBagConstraints.weightx = 1.0;
352 gridBagConstraints.insets =
new java.awt.Insets(15, 15, 9, 0);
353 rootMessagesPane.add(threadsLabel, gridBagConstraints);
355 org.openide.awt.Mnemonics.setLocalizedText(showAllButton,
org.openide.util.NbBundle.getMessage(MessageViewer.class,
"MessageViewer.showAllButton.text"));
356 showAllButton.addActionListener(
new java.awt.event.ActionListener() {
357 public void actionPerformed(java.awt.event.ActionEvent evt) {
358 showAllButtonActionPerformed(evt);
361 gridBagConstraints =
new java.awt.GridBagConstraints();
362 gridBagConstraints.gridx = 0;
363 gridBagConstraints.gridy = 2;
364 gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
365 gridBagConstraints.insets =
new java.awt.Insets(0, 15, 15, 0);
366 rootMessagesPane.add(showAllButton, gridBagConstraints);
368 rootTablePane.setBorder(javax.swing.BorderFactory.createLineBorder(
new java.awt.Color(0, 0, 0)));
369 gridBagConstraints =
new java.awt.GridBagConstraints();
370 gridBagConstraints.gridx = 0;
371 gridBagConstraints.gridy = 1;
372 gridBagConstraints.gridwidth = 2;
373 gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
374 gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
375 gridBagConstraints.weightx = 1.0;
376 gridBagConstraints.weighty = 1.0;
377 gridBagConstraints.insets =
new java.awt.Insets(0, 15, 9, 15);
378 rootMessagesPane.add(rootTablePane, gridBagConstraints);
380 add(rootMessagesPane,
"threads");
382 messagePanel.setMinimumSize(
new java.awt.Dimension(450, 292));
383 messagePanel.setLayout(
new java.awt.GridBagLayout());
384 gridBagConstraints =
new java.awt.GridBagConstraints();
385 gridBagConstraints.gridx = 0;
386 gridBagConstraints.gridy = 3;
387 gridBagConstraints.gridwidth = 3;
388 gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
389 gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
390 gridBagConstraints.weightx = 1.0;
391 gridBagConstraints.weighty = 1.0;
392 gridBagConstraints.insets =
new java.awt.Insets(0, 15, 0, 15);
393 messagePanel.add(threadMessagesPanel, gridBagConstraints);
395 org.openide.awt.Mnemonics.setLocalizedText(backButton,
org.openide.util.NbBundle.getMessage(MessageViewer.class,
"MessageViewer.backButton.text"));
396 backButton.addActionListener(
new java.awt.event.ActionListener() {
397 public void actionPerformed(java.awt.event.ActionEvent evt) {
398 backButtonActionPerformed(evt);
401 gridBagConstraints =
new java.awt.GridBagConstraints();
402 gridBagConstraints.gridx = 2;
403 gridBagConstraints.gridy = 0;
404 gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
405 gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
406 gridBagConstraints.insets =
new java.awt.Insets(9, 0, 9, 15);
407 messagePanel.add(backButton, gridBagConstraints);
408 backButton.getAccessibleContext().setAccessibleDescription(
org.openide.util.NbBundle.getMessage(MessageViewer.class,
"MessageViewer.backButton.AccessibleContext.accessibleDescription"));
410 org.openide.awt.Mnemonics.setLocalizedText(showingMessagesLabel,
org.openide.util.NbBundle.getMessage(MessageViewer.class,
"MessageViewer.showingMessagesLabel.text"));
411 gridBagConstraints =
new java.awt.GridBagConstraints();
412 gridBagConstraints.gridx = 0;
413 gridBagConstraints.gridy = 0;
414 gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
415 gridBagConstraints.insets =
new java.awt.Insets(9, 15, 5, 0);
416 messagePanel.add(showingMessagesLabel, gridBagConstraints);
418 org.openide.awt.Mnemonics.setLocalizedText(threadNameLabel,
org.openide.util.NbBundle.getMessage(MessageViewer.class,
"MessageViewer.threadNameLabel.text"));
419 gridBagConstraints =
new java.awt.GridBagConstraints();
420 gridBagConstraints.gridx = 1;
421 gridBagConstraints.gridy = 0;
422 gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
423 gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
424 gridBagConstraints.weightx = 1.0;
425 gridBagConstraints.insets =
new java.awt.Insets(9, 5, 5, 15);
426 messagePanel.add(threadNameLabel, gridBagConstraints);
428 add(messagePanel,
"messages");
431 private void backButtonActionPerformed(java.awt.event.ActionEvent evt) {
433 rootTablePane.getExplorerManager().setSelectedNodes(
new Node[0]);
434 }
catch (PropertyVetoException ex) {
435 logger.log(Level.WARNING,
"Error setting selected nodes", ex);
440 private void showAllButtonActionPerformed(java.awt.event.ActionEvent evt) {
441 threadMessageNodeFactory.refresh(currentSelectionInfo, null);
442 threadNameLabel.setText(
"All Messages");
448 private javax.swing.JButton backButton;
449 private javax.swing.JPanel messagePanel;
450 private javax.swing.JPanel rootMessagesPane;
452 private javax.swing.JButton showAllButton;
453 private javax.swing.JLabel showingMessagesLabel;
455 private javax.swing.JLabel threadNameLabel;
456 private javax.swing.JLabel threadsLabel;
462 class ShowThreadMessagesAction
extends AbstractAction {
465 public void actionPerformed(ActionEvent e) {
467 SwingUtilities.invokeLater(
new Runnable() {
470 showSelectedThread();
synchronized static Logger getLogger(String name)