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)