Autopsy  4.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
DataResultPanel.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2013 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.corecomponents;
20 
21 import java.awt.Cursor;
22 import java.beans.PropertyChangeEvent;
23 import java.beans.PropertyChangeListener;
24 import java.beans.PropertyChangeSupport;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.logging.Level;
28 import javax.swing.JTabbedPane;
29 import javax.swing.SwingUtilities;
30 import javax.swing.event.ChangeEvent;
31 import javax.swing.event.ChangeListener;
32 import org.openide.explorer.ExplorerManager;
33 import org.openide.nodes.Node;
34 import org.openide.nodes.NodeEvent;
35 import org.openide.nodes.NodeListener;
36 import org.openide.nodes.NodeMemberEvent;
37 import org.openide.nodes.NodeReorderEvent;
38 import org.openide.util.Lookup;
39 import org.openide.util.NbBundle;
45 
56 public class DataResultPanel extends javax.swing.JPanel implements DataResult, ChangeListener {
57 
58  private ExplorerManager explorerManager;
59  private Node rootNode;
60  private PropertyChangeSupport pcs;
61 
62  // Different DataResultsViewers
63  private final List<UpdateWrapper> viewers = new ArrayList<>();
64  //custom content viewer to send selections to, or null if the main one
66  private boolean isMain;
67  private String title;
69 
70  private static final Logger logger = Logger.getLogger(DataResultPanel.class.getName());
71  private boolean listeningToTabbedPane = false;
72  private static final String DUMMY_NODE_DISPLAY_NAME = NbBundle.getMessage(DataResultPanel.class,
73  "DataResultPanel.dummyNodeDisplayName");
74 
80  private DataResultPanel() {
81  this.isMain = true;
82  pcs = new PropertyChangeSupport(this);
84 
85  setName(title);
86 
87  this.title = "";
88  }
89 
97  DataResultPanel(boolean isMain, String title) {
98  this();
99 
100  setName(title);
101 
102  this.isMain = isMain;
103  this.title = title;
104  }
105 
115  DataResultPanel(String title, DataContent customContentViewer) {
116  this(false, title);
117  setName(title);
118 
119  //custom content viewer tc to setup for every result viewer
120  this.customContentViewer = customContentViewer;
121  }
122 
136  public static DataResultPanel createInstance(String title, String pathText, Node givenNode, int totalMatches) {
137  DataResultPanel newDataResult = new DataResultPanel(false, title);
138 
139  createInstanceCommon(pathText, givenNode, totalMatches, newDataResult);
140  newDataResult.open();
141  return newDataResult;
142  }
143 
158  public static DataResultPanel createInstance(String title, String pathText, Node givenNode, int totalMatches, DataContent dataContent) {
159  DataResultPanel newDataResult = new DataResultPanel(title, dataContent);
160 
161  createInstanceCommon(pathText, givenNode, totalMatches, newDataResult);
162  newDataResult.open();
163  return newDataResult;
164  }
165 
181  public static DataResultPanel createInstanceUninitialized(String title, String pathText, Node givenNode, int totalMatches, DataContent dataContent) {
182  DataResultPanel newDataResult = new DataResultPanel(title, dataContent);
183 
184  createInstanceCommon(pathText, givenNode, totalMatches, newDataResult);
185  return newDataResult;
186  }
187 
196  private static void createInstanceCommon(String pathText, Node givenNode, int totalMatches, DataResultPanel newDataResult) {
197  newDataResult.numberMatchLabel.setText(Integer.toString(totalMatches));
198 
199  // set the tree table view
200  newDataResult.setNode(givenNode);
201  newDataResult.setPath(pathText);
202  }
203 
210  public void setContentViewer(DataContent customContentViewer) {
211  this.customContentViewer = customContentViewer;
212  }
213 
219  public void open() {
220  if (null == explorerManager) {
221  // Get an ExplorerManager to pass to the child DataResultViewers. If the application
222  // components are put together as expected, this will be an ExplorerManager owned
223  // by an ancestor TopComponent. The TopComponent will have put this ExplorerManager
224  // in a Lookup that is set as the action global context when the TopComponent has
225  // focus. This makes Node selections available to Actions without coupling the
226  // actions to a particular Component. Note that getting the ExplorerManager in the
227  // constructor would be too soon, since the object has no ancestor TopComponent at
228  // that point.
229  explorerManager = ExplorerManager.find(this);
230 
231  // A DataResultPanel listens for Node selections in its DataResultViewers so it
232  // can push the selections both to its child DataResultViewers and to a DataContent object.
233  // The default DataContent object is a DataContentTopComponent in the data content mode (area),
234  // and is the parent of a DataContentPanel that hosts a set of DataContentViewers.
235  explorerManager.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener());
236  }
237 
238  // Add all the DataContentViewer to the tabbed pannel.
239  // (Only when the it's opened at the first time: tabCount = 0)
240  int totalTabs = this.dataResultTabbedPanel.getTabCount();
241  if (totalTabs == 0) {
242  // @@@ Restore the implementation of DataResultViewerTable and DataResultViewerThumbnail
243  // as DataResultViewer service providers when DataResultViewers are updated
244  // to better handle the ExplorerManager sharing implemented to support actions that operate on
245  // multiple selected nodes.
246  addDataResultViewer(new DataResultViewerTable(this.explorerManager));
247  addDataResultViewer(new DataResultViewerThumbnail(this.explorerManager));
248 
249  // Find all DataResultViewer service providers and add them to the tabbed pane.
250  for (DataResultViewer factory : Lookup.getDefault().lookupAll(DataResultViewer.class)) {
251  // @@@ Revist this isMain condition, it may be obsolete. If not,
252  // document the intent of DataResultViewer.createInstance() in the
253  // DataResultViewer interface defintion.
254  DataResultViewer drv;
255  if (isMain) {
256  //for main window, use the instance in the lookup
257  drv = factory;
258  } else {
259  //create a new instance of the viewer for non-main window
260  drv = factory.createInstance();
261  }
262  addDataResultViewer(drv);
263  }
264  }
265 
266  if (isMain) {
267  // if no node selected on DataExplorer, clear the field
268  if (rootNode == null) {
269  setNode(rootNode);
270  }
271  }
272 
273  this.setVisible(true);
274  }
275 
276  private class ExplorerManagerNodeSelectionListener implements PropertyChangeListener {
277 
278  @Override
279  public void propertyChange(PropertyChangeEvent evt) {
280  if (!Case.isCaseOpen()) {
281  // Handle the in-between condition when case is being closed
282  // and legacy selection events are pumped.
283  return;
284  }
285 
286  if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
287  setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
288 
289  // If a custom DataContent object has not been specified, get the default instance.
290  DataContent contentViewer = customContentViewer;
291  if (contentViewer == null) {
292  contentViewer = Lookup.getDefault().lookup(DataContent.class);
293  }
294 
295  try {
296  if (contentViewer != null) {
297  Node[] selectedNodes = explorerManager.getSelectedNodes();
298  for (UpdateWrapper drv : viewers) {
299  drv.setSelectedNodes(selectedNodes);
300  }
301 
302  // Passing null signals that either multiple nodes are selected, or no nodes are selected.
303  // This is important to the DataContent object, since the content mode (area) of the app is designed
304  // to show only the content underlying a single Node.
305  if (selectedNodes.length == 1) {
306  contentViewer.setNode(selectedNodes[0]);
307  } else {
308  contentViewer.setNode(null);
309  }
310  }
311  } finally {
312  setCursor(null);
313  }
314  }
315  }
316  }
317 
318  private void addDataResultViewer(DataResultViewer dataResultViewer) {
319  UpdateWrapper viewerWrapper = new UpdateWrapper(dataResultViewer);
320  if (null != this.customContentViewer) {
321  viewerWrapper.setContentViewer(this.customContentViewer);
322  }
323  this.viewers.add(viewerWrapper);
324  this.dataResultTabbedPanel.addTab(dataResultViewer.getTitle(), dataResultViewer.getComponent());
325  }
326 
332  void close() {
333  // try to remove any references to this class
334  PropertyChangeListener[] pcl = pcs.getPropertyChangeListeners();
335  for (int i = 0; i < pcl.length; i++) {
336  pcs.removePropertyChangeListener(pcl[i]);
337  }
338 
339  // clear all set nodes
340  for (UpdateWrapper drv : this.viewers) {
341  drv.setNode(null);
342  }
343 
344  if (!this.isMain) {
345  for (UpdateWrapper drv : this.viewers) {
346  drv.clearComponent();
347  }
348  this.directoryTablePath.removeAll();
349  this.directoryTablePath = null;
350  this.numberMatchLabel.removeAll();
351  this.numberMatchLabel = null;
352  this.matchLabel.removeAll();
353  this.matchLabel = null;
354  this.setLayout(null);
355  this.pcs = null;
356  this.removeAll();
357  this.setVisible(false);
358  }
359  }
360 
361  @Override
362  public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
363  if (pcs == null) {
364  logger.log(Level.WARNING, "Could not add listener to DataResultPanel, " //NON-NLS
365  + "listener support not fully initialized yet, listener: " + listener.toString()); //NON-NLS
366  } else {
367  this.pcs.addPropertyChangeListener(listener);
368  }
369  }
370 
371  @Override
372  public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
373  this.pcs.removePropertyChangeListener(listener);
374  }
375 
376  @Override
377  public String getPreferredID() {
378  return getName();
379  }
380 
381  @Override
382  public void setNode(Node selectedNode) {
383  if (this.rootNode != null) {
384  this.rootNode.removeNodeListener(rootNodeListener);
385  }
386  // Deferring becoming a listener to the tabbed pane until this point
387  // eliminates handling a superfluous stateChanged event during construction.
388  if (listeningToTabbedPane == false) {
389  dataResultTabbedPanel.addChangeListener(this);
390  listeningToTabbedPane = true;
391  }
392 
393  this.rootNode = selectedNode;
394  if (this.rootNode != null) {
395  rootNodeListener.reset();
396  this.rootNode.addNodeListener(rootNodeListener);
397  }
398 
399  resetTabs(selectedNode);
400  setupTabs(selectedNode);
401 
402  if (selectedNode != null) {
403  int childrenCount = selectedNode.getChildren().getNodesCount();
404  this.numberMatchLabel.setText(Integer.toString(childrenCount));
405  }
406  this.numberMatchLabel.setVisible(true);
407  }
408 
409  private void setupTabs(Node selectedNode) {
410  //update/disable tabs based on if supported for this node
411  int drvC = 0;
412  for (UpdateWrapper drv : viewers) {
413 
414  if (drv.isSupported(selectedNode)) {
415  dataResultTabbedPanel.setEnabledAt(drvC, true);
416  } else {
417  dataResultTabbedPanel.setEnabledAt(drvC, false);
418  }
419  ++drvC;
420  }
421 
422  // if the current tab is no longer enabled, then find one that is
423  boolean hasViewerEnabled = true;
424  int currentActiveTab = dataResultTabbedPanel.getSelectedIndex();
425  if ((currentActiveTab == -1) || (dataResultTabbedPanel.isEnabledAt(currentActiveTab) == false)) {
426  hasViewerEnabled = false;
427  for (int i = 0; i < dataResultTabbedPanel.getTabCount(); i++) {
428  if (dataResultTabbedPanel.isEnabledAt(i)) {
429  currentActiveTab = i;
430  hasViewerEnabled = true;
431  break;
432  }
433  }
434 
435  if (hasViewerEnabled) {
436  dataResultTabbedPanel.setSelectedIndex(currentActiveTab);
437  }
438  }
439 
440  if (hasViewerEnabled) {
441  viewers.get(currentActiveTab).setNode(selectedNode);
442  }
443  }
444 
445  @Override
446  public void setTitle(String title) {
447  setName(title);
448 
449  }
450 
451  @Override
452  public void setPath(String pathText) {
453  this.directoryTablePath.setText(pathText);
454  }
455 
456  @Override
457  public boolean isMain() {
458  return this.isMain;
459  }
460 
461  @Override
462  public List<DataResultViewer> getViewers() {
463  List<DataResultViewer> ret = new ArrayList<DataResultViewer>();
464  for (UpdateWrapper w : viewers) {
465  ret.add(w.getViewer());
466  }
467 
468  return ret;
469  }
470 
471  public boolean canClose() {
472  return (!this.isMain) || !Case.existsCurrentCase() || Case.getCurrentCase().hasData() == false; // only allow this window to be closed when there's no case opened or no image in this case
473  }
474 
475  @Override
476  public void stateChanged(ChangeEvent e) {
477  JTabbedPane pane = (JTabbedPane) e.getSource();
478 
479  // Get and set current selected tab
480  int currentTab = pane.getSelectedIndex();
481  if (currentTab != -1) {
482  UpdateWrapper drv = this.viewers.get(currentTab);
483  // @@@ Restore commented out isOutDated() check after DataResultViewers are updated
484  // to better handle the ExplorerManager sharing implemented to support actions that operate on
485  // multiple selected nodes.
486  //if (drv.isOutdated()) {
487  // change the cursor to "waiting cursor" for this operation
488  this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
489  try {
490  drv.setNode(rootNode);
491  } finally {
492  this.setCursor(null);
493  }
494  //}
495  }
496  }
497 
508  public void resetTabs(Node selectedNode) {
509 
510  for (UpdateWrapper drv : this.viewers) {
511  drv.resetComponent();
512  }
513  }
514 
515  public void setSelectedNodes(Node[] selected) {
516  for (UpdateWrapper drv : this.viewers) {
517  drv.setSelectedNodes(selected);
518  }
519  }
520 
521  public Node getRootNode() {
522  return this.rootNode;
523  }
524 
530  @SuppressWarnings("unchecked")
531  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
532  private void initComponents() {
533 
534  directoryTablePath = new javax.swing.JLabel();
535  numberMatchLabel = new javax.swing.JLabel();
536  matchLabel = new javax.swing.JLabel();
537  dataResultTabbedPanel = new javax.swing.JTabbedPane();
538 
539  setMinimumSize(new java.awt.Dimension(5, 5));
540  setPreferredSize(new java.awt.Dimension(5, 5));
541 
542  org.openide.awt.Mnemonics.setLocalizedText(directoryTablePath, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.directoryTablePath.text")); // NOI18N
543 
544  org.openide.awt.Mnemonics.setLocalizedText(numberMatchLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.numberMatchLabel.text")); // NOI18N
545 
546  org.openide.awt.Mnemonics.setLocalizedText(matchLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.matchLabel.text")); // NOI18N
547 
548  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
549  this.setLayout(layout);
550  layout.setHorizontalGroup(
551  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
552  .addGroup(layout.createSequentialGroup()
553  .addComponent(directoryTablePath)
554  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 518, Short.MAX_VALUE)
555  .addComponent(numberMatchLabel)
556  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
557  .addComponent(matchLabel))
558  .addComponent(dataResultTabbedPanel)
559  );
560  layout.setVerticalGroup(
561  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
562  .addGroup(layout.createSequentialGroup()
563  .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
564  .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
565  .addComponent(numberMatchLabel)
566  .addComponent(matchLabel))
567  .addComponent(directoryTablePath))
568  .addGap(0, 0, 0)
569  .addComponent(dataResultTabbedPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 340, Short.MAX_VALUE))
570  );
571  }// </editor-fold>//GEN-END:initComponents
572  // Variables declaration - do not modify//GEN-BEGIN:variables
573  private javax.swing.JTabbedPane dataResultTabbedPanel;
574  private javax.swing.JLabel directoryTablePath;
575  private javax.swing.JLabel matchLabel;
576  private javax.swing.JLabel numberMatchLabel;
577  // End of variables declaration//GEN-END:variables
578 
579  private static class UpdateWrapper {
580 
582  private boolean outdated;
583 
585  this.wrapped = wrapped;
586  this.outdated = true;
587  }
588 
589  DataResultViewer getViewer() {
590  return wrapped;
591  }
592 
593  void setNode(Node selectedNode) {
594  this.wrapped.setNode(selectedNode);
595  this.outdated = false;
596  }
597 
598  void resetComponent() {
599  this.wrapped.resetComponent();
600  this.outdated = true;
601  }
602 
603  void clearComponent() {
604  this.wrapped.clearComponent();
605  this.outdated = true;
606  }
607 
608  boolean isOutdated() {
609  return this.outdated;
610  }
611 
612  void setSelectedNodes(Node[] selected) {
613  this.wrapped.setSelectedNodes(selected);
614  }
615 
616  boolean isSupported(Node selectedNode) {
617  return this.wrapped.isSupported(selectedNode);
618  }
619 
620  void setContentViewer(DataContent contentViewer) {
621  this.wrapped.setContentViewer(contentViewer);
622  }
623  }
624 
630  public void setNumMatches(Integer numMatches) {
631  if (this.numberMatchLabel != null) {
632  this.numberMatchLabel.setText(Integer.toString(numMatches));
633  }
634  }
635 
636  private class RootNodeListener implements NodeListener {
637 
638  private volatile boolean waitingForData = true;
639 
640  public void reset() {
641  waitingForData = true;
642  }
643 
644  @Override
645  public void childrenAdded(final NodeMemberEvent nme) {
646  Node[] delta = nme.getDelta();
647  updateMatches();
648 
649  /*
650  * There is a known issue in this code whereby we will only call
651  * setupTabs() once even though childrenAdded could be called
652  * multiple times. That means that each panel may not have access to
653  * all of the children when they decide if they support the content
654  */
655  if (waitingForData && containsReal(delta)) {
656  waitingForData = false;
657  if (SwingUtilities.isEventDispatchThread()) {
658  setupTabs(nme.getNode());
659  } else {
660  SwingUtilities.invokeLater(new Runnable() {
661  @Override
662  public void run() {
663  setupTabs(nme.getNode());
664  }
665  });
666  }
667  }
668  }
669 
670  private boolean containsReal(Node[] delta) {
671  for (Node n : delta) {
672  if (!n.getDisplayName().equals(DUMMY_NODE_DISPLAY_NAME)) {
673  return true;
674  }
675  }
676  return false;
677  }
678 
683  private void updateMatches() {
684  if (rootNode != null && rootNode.getChildren() != null) {
685  setNumMatches(rootNode.getChildren().getNodesCount());
686  }
687  }
688 
689  @Override
690  public void childrenRemoved(NodeMemberEvent nme) {
691  updateMatches();
692  }
693 
694  @Override
695  public void childrenReordered(NodeReorderEvent nre) {
696  }
697 
698  @Override
699  public void nodeDestroyed(NodeEvent ne) {
700  }
701 
702  @Override
703  public void propertyChange(PropertyChangeEvent evt) {
704  }
705  }
706 }
static void createInstanceCommon(String pathText, Node givenNode, int totalMatches, DataResultPanel newDataResult)
static boolean existsCurrentCase()
Definition: Case.java:966
synchronized void addPropertyChangeListener(PropertyChangeListener listener)
synchronized void removePropertyChangeListener(PropertyChangeListener listener)
static DataResultPanel createInstanceUninitialized(String title, String pathText, Node givenNode, int totalMatches, DataContent dataContent)
static DataResultPanel createInstance(String title, String pathText, Node givenNode, int totalMatches, DataContent dataContent)
void setContentViewer(DataContent customContentViewer)
void addDataResultViewer(DataResultViewer dataResultViewer)
static DataResultPanel createInstance(String title, String pathText, Node givenNode, int totalMatches)
synchronized static Logger getLogger(String name)
Definition: Logger.java:166

Copyright © 2012-2015 Basis Technology. Generated on: Wed Apr 6 2016
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.