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)