Autopsy 4.22.1
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-2018 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 */
19package org.sleuthkit.autopsy.corecomponents;
20
21import java.awt.Cursor;
22import java.beans.PropertyChangeEvent;
23import java.beans.PropertyChangeListener;
24import java.util.ArrayList;
25import java.util.Collection;
26import java.util.Collections;
27import java.util.List;
28import javax.swing.JTabbedPane;
29import javax.swing.SwingUtilities;
30import javax.swing.event.ChangeEvent;
31import javax.swing.event.ChangeListener;
32import org.openide.explorer.ExplorerManager;
33import org.openide.nodes.Node;
34import org.openide.nodes.NodeEvent;
35import org.openide.nodes.NodeListener;
36import org.openide.nodes.NodeMemberEvent;
37import org.openide.nodes.NodeReorderEvent;
38import org.openide.util.Lookup;
39import org.openide.util.NbBundle;
40import org.sleuthkit.autopsy.casemodule.Case;
41import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
42import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
43import org.sleuthkit.autopsy.corecomponentinterfaces.DataResult;
44import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
45import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo;
46
75@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
76public class DataResultPanel extends javax.swing.JPanel implements DataResult, ChangeListener, ExplorerManager.Provider {
77
78 private static final long serialVersionUID = 1L;
79 private static final int NO_TAB_SELECTED = -1;
80 private static final String PLEASE_WAIT_NODE_DISPLAY_NAME = NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.pleasewaitNodeDisplayName");
81 private final boolean isMain;
82 private final List<DataResultViewer> resultViewers;
86 private ExplorerManager explorerManager;
87 private Node currentRootNode;
88 private boolean listeningToTabbedPane;
89
107 public static DataResultPanel createInstance(String title, String description, Node currentRootNode, int childNodeCount) {
108 DataResultPanel resultPanel = new DataResultPanel(title, false, Collections.emptyList(), DataContentTopComponent.findInstance());
109 createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel);
110 resultPanel.open();
111 return resultPanel;
112 }
113
134 public static DataResultPanel createInstance(String title, String description, Node currentRootNode, int childNodeCount, Collection<DataResultViewer> viewers) {
135 DataResultPanel resultPanel = new DataResultPanel(title, false, viewers, DataContentTopComponent.findInstance());
136 createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel);
137 resultPanel.open();
138 return resultPanel;
139 }
140
163 public static DataResultPanel createInstance(String title, String description, Node currentRootNode, int childNodeCount, DataContent customContentView) {
164 DataResultPanel resultPanel = new DataResultPanel(title, false, Collections.emptyList(), customContentView);
165 createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel);
166 resultPanel.open();
167 return resultPanel;
168 }
169
189 public static DataResultPanel createInstanceUninitialized(String title, String description, Node currentRootNode, int childNodeCount, DataContent customContentView) {
190 DataResultPanel resultPanel = new DataResultPanel(title, false, Collections.emptyList(), customContentView);
191 createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel);
192 return resultPanel;
193 }
194
206 private static void createInstanceCommon(String title, String description, Node currentRootNode, int childNodeCount, DataResultPanel resultViewPanel) {
207 resultViewPanel.setTitle(title);
208 resultViewPanel.setName(title);
209 resultViewPanel.setNumberOfChildNodes(childNodeCount);
210 resultViewPanel.setNode(currentRootNode);
211 resultViewPanel.setPath(description);
212 }
213
231 DataResultPanel(String title, boolean isMain, Collection<DataResultViewer> viewers, DataContent contentView) {
232 this.setTitle(title);
233 this.isMain = isMain;
234 this.contentView = contentView;
235 this.resultViewers = new ArrayList<>(viewers);
236 this.explorerManagerListener = new ExplorerManagerListener();
237 this.rootNodeListener = new RootNodeListener();
238 initComponents();
239 }
240
247 @Override
248 public String getPreferredID() {
249 return getName();
250 }
251
257 @Override
258 public void setTitle(String title) {
259 setName(title);
260 }
261
268 @Override
269 public void setPath(String description) {
270 this.descriptionLabel.setText(description);
271 }
272
278 public void addResultViewer(DataResultViewer resultViewer) {
279 resultViewers.add(resultViewer);
280 resultViewerTabs.addTab(resultViewer.getTitle(), resultViewer.getComponent());
281 }
282
288 @Override
289 public List<DataResultViewer> getViewers() {
290 return Collections.unmodifiableList(resultViewers);
291 }
292
300 public void setContentViewer(DataContent customContentView) {
301 this.contentView = customContentView;
302 }
303
308 public void open() {
309 /*
310 * The parent top component is expected to be an explorer manager
311 * provider that exposes a lookup maintained by its explorer manager to
312 * the actions global context. The child result view panel will then
313 * find the parent top component's explorer manager at runtime, so that
314 * it can act as an explorer manager provider for its child result
315 * viewers. This connects the nodes displayed in the result viewers to
316 * the actions global context.
317 */
318 if (this.explorerManager == null) {
319 this.explorerManager = ExplorerManager.find(this);
320 this.explorerManager.addPropertyChangeListener(this.explorerManagerListener);
321 }
322
323 /*
324 * Load either the supplied result viewers or the result viewers
325 * provided by the result viewer extension point into the tabbed pane.
326 * If loading from the extension point and distinct result viewer
327 * instances MUST be created if this is not the "main" result view.
328 */
329 if (this.resultViewerTabs.getTabCount() == 0) {
330 if (this.resultViewers.isEmpty()) {
331 for (DataResultViewer resultViewer : Lookup.getDefault().lookupAll(DataResultViewer.class)) {
332 if (this.isMain) {
333 this.resultViewers.add(resultViewer);
334 } else {
335 this.resultViewers.add(resultViewer.createInstance());
336 }
337 }
338 }
339 this.resultViewers.forEach((resultViewer) -> resultViewerTabs.addTab(resultViewer.getTitle(), resultViewer.getComponent()));
340 }
341
342 this.setVisible(true);
343 }
344
355 @Override
356 public void setNode(Node rootNode) {
357 if (this.currentRootNode != null) {
358 this.currentRootNode.removeNodeListener(rootNodeListener);
359 }
360
361 /*
362 * Deferring becoming a listener to the tabbed pane until this point
363 * eliminates handling a superfluous stateChanged event during
364 * construction.
365 */
366 if (listeningToTabbedPane == false) {
367 resultViewerTabs.addChangeListener(this);
369 }
370
371 this.currentRootNode = rootNode;
372 if (this.currentRootNode != null) {
373 /*
374 * The only place we reset the rootNodeListener allowing the
375 * contents of the results tab represented by this node to be
376 * changed a single time before it is necessary to reset it again.
377 * Necessary when transitioning from "Please wait..." node to having
378 * contents.
379 */
380 rootNodeListener.reset();
381 this.currentRootNode.addNodeListener(rootNodeListener);
382 }
383
384 this.resultViewers.forEach((viewer) -> {
385 viewer.resetComponent();
386 });
387 setupTabs(this.currentRootNode);
388
389 if (this.currentRootNode != null) {
390 int childrenCount = this.currentRootNode.getChildren().getNodesCount();
391 this.numberOfChildNodesLabel.setText(Integer.toString(childrenCount));
392 }
393 this.numberOfChildNodesLabel.setVisible(true);
394 }
395
403 public Node getRootNode() {
404 return currentRootNode;
405 }
406
413 public void setNumberOfChildNodes(Integer numberOfChildNodes) {
414 this.numberOfChildNodesLabel.setText(Integer.toString(numberOfChildNodes));
415 }
416
423 public void setSelectedNodes(Node[] selectedNodes) {
424 this.resultViewers.forEach((viewer) -> viewer.setSelectedNodes(selectedNodes));
425 }
426
433 private void setupTabs(Node selectedNode) {
434 /*
435 * Enable or disable the result viewer tabs based on whether or not the
436 * corresponding results viewer supports display of the selected node.
437 */
438 for (int i = 0; i < resultViewerTabs.getTabCount(); i++) {
439 if (resultViewers.get(i).isSupported(selectedNode)) {
440 resultViewerTabs.setEnabledAt(i, true);
441 } else {
442 resultViewerTabs.setEnabledAt(i, false);
443 }
444 }
445
446 /*
447 * If the selected node has a child to be selected, default the selected
448 * tab to the table result viewer. Otherwise, use the last selected tab,
449 * if it is enabled. If not, select the first enabled tab that can be
450 * found.
451 */
452 int tabToSelect = NO_TAB_SELECTED;
453 if (selectedNode instanceof TableFilterNode) {
454 NodeSelectionInfo selectedChildInfo = ((TableFilterNode) selectedNode).getChildNodeSelectionInfo();
455 if (null != selectedChildInfo) {
456 for (int i = 0; i < resultViewers.size(); ++i) {
457 if (resultViewers.get(i) instanceof DataResultViewerTable && resultViewerTabs.isEnabledAt(i)) {
458 tabToSelect = i;
459 }
460 }
461 }
462 }
463 if (tabToSelect == NO_TAB_SELECTED) {
464 if ((tabToSelect == NO_TAB_SELECTED) || (!resultViewerTabs.isEnabledAt(tabToSelect))) {
465 for (int i = 0; i < resultViewerTabs.getTabCount(); ++i) {
466 if (resultViewerTabs.isEnabledAt(i)) {
467 tabToSelect = i;
468 break;
469 }
470 }
471 }
472 }
473
474 /*
475 * If there is a tab to select, do so, and push the selected node to the
476 * corresponding result viewer.
477 */
478 if (tabToSelect != NO_TAB_SELECTED) {
479 resultViewerTabs.setSelectedIndex(tabToSelect);
480 resultViewers.get(tabToSelect).setNode(selectedNode);
481 }
482 }
483
490 @Override
491 public void stateChanged(ChangeEvent event) {
492 JTabbedPane pane = (JTabbedPane) event.getSource();
493 int currentTab = pane.getSelectedIndex();
494 if (currentTab != DataResultPanel.NO_TAB_SELECTED) {
495 DataResultViewer currentViewer = this.resultViewers.get(currentTab);
496 this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
497 try {
498 currentViewer.setNode(currentRootNode);
499 } finally {
500 this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
501 }
502 }
503 }
504
511 public boolean canClose() {
512 /*
513 * If this is the "main" panel, only allow it to be closed when no case
514 * is open or no there are no data sources in the current case.
515 */
516 Case openCase;
517 try {
518 openCase = Case.getCurrentCaseThrows();
519 } catch (NoCurrentCaseException ex) {
520 return true;
521 }
522 return (!this.isMain) || openCase.hasData() == false;
523 }
524
529 void close() {
530 if (explorerManager != null && explorerManagerListener != null) {
531 explorerManager.removePropertyChangeListener(explorerManagerListener);
532 explorerManager = null;
533 }
534
535 this.resultViewers.forEach((viewer) -> viewer.setNode(null));
536
537 if (!this.isMain) {
538 this.resultViewers.forEach(DataResultViewer::clearComponent);
539 this.descriptionLabel.removeAll();
540 this.numberOfChildNodesLabel.removeAll();
541 this.matchLabel.removeAll();
542 this.setLayout(null);
543 this.removeAll();
544 this.setVisible(false);
545 }
546 }
547
548 @Override
549 public ExplorerManager getExplorerManager() {
550 return explorerManager;
551
552 }
553
564 private class ExplorerManagerListener implements PropertyChangeListener {
565
566 @Override
567 public void propertyChange(PropertyChangeEvent evt) {
568 if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES) && contentView != null) {
569 /*
570 * Pass a single node selection in a result viewer to the
571 * content view. Note that passing null to the content view
572 * signals that either multiple nodes are selected, or a
573 * previous selection has been cleared. This is important to the
574 * content view, since its child content viewers only work for a
575 * single node.
576 */
577 Node[] selectedNodes = explorerManager.getSelectedNodes();
578 if (selectedNodes.length == 1) {
579 contentView.setNode(selectedNodes[0]);
580 } else {
581 contentView.setNode(null);
582 }
583 }
584 }
585 }
586
593 private class RootNodeListener implements NodeListener {
594
595 //it is assumed we are still waiting for data when the node is initially constructed
596 private volatile boolean waitingForData = true;
597
598 public void reset() {
599 waitingForData = true;
600 }
601
602 @Override
603 public void childrenAdded(final NodeMemberEvent nme) {
604 Node[] delta = nme.getDelta();
606
607 /*
608 * Ensures that after the initial call to setupTabs in the
609 * DataResultPanel.setNode method that we only call setupTabs one
610 * additional time. This is to account for the transition that is
611 * possible from a "Please wait..." node or a tab with no results in
612 * it and a tab containing data and thereby having all of it's
613 * columns.
614 */
615 if (waitingForData && containsReal(delta)) {
616 waitingForData = false;
617 if (SwingUtilities.isEventDispatchThread()) {
618 setupTabs(nme.getNode());
619 } else {
620 SwingUtilities.invokeLater(() -> {
621 setupTabs(nme.getNode());
622 });
623 }
624 }
625 }
626
627 private boolean containsReal(Node[] delta) {
628 for (Node n : delta) {
629 if (!n.getDisplayName().equals(PLEASE_WAIT_NODE_DISPLAY_NAME)) {
630 return true;
631 }
632 }
633 return false;
634 }
635
640 private void updateMatches() {
641 if (currentRootNode != null && currentRootNode.getChildren() != null) {
642 setNumMatches(currentRootNode.getChildren().getNodesCount());
643 }
644 }
645
646 @Override
647 public void childrenRemoved(NodeMemberEvent nme) {
649 }
650
651 @Override
652 public void childrenReordered(NodeReorderEvent nre) {
653 }
654
655 @Override
656 public void nodeDestroyed(NodeEvent ne) {
657 }
658
659 @Override
660 public void propertyChange(PropertyChangeEvent evt) {
661 }
662 }
663
669 @SuppressWarnings("unchecked")
670 // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
671 private void initComponents() {
672
673 descriptionLabel = new javax.swing.JLabel();
674 numberOfChildNodesLabel = new javax.swing.JLabel();
675 matchLabel = new javax.swing.JLabel();
676 resultViewerTabs = new javax.swing.JTabbedPane();
677
678 setMinimumSize(new java.awt.Dimension(0, 5));
679 setPreferredSize(new java.awt.Dimension(5, 5));
680
681 org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.descriptionLabel.text")); // NOI18N
682 descriptionLabel.setMinimumSize(new java.awt.Dimension(5, 14));
683
684 org.openide.awt.Mnemonics.setLocalizedText(numberOfChildNodesLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.numberOfChildNodesLabel.text")); // NOI18N
685
686 org.openide.awt.Mnemonics.setLocalizedText(matchLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.matchLabel.text")); // NOI18N
687
688 resultViewerTabs.setMinimumSize(new java.awt.Dimension(0, 5));
689
690 javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
691 this.setLayout(layout);
692 layout.setHorizontalGroup(
693 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
694 .addGroup(layout.createSequentialGroup()
695 .addComponent(descriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
696 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
697 .addComponent(numberOfChildNodesLabel)
698 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
699 .addComponent(matchLabel))
700 .addComponent(resultViewerTabs, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
701 );
702 layout.setVerticalGroup(
703 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
704 .addGroup(layout.createSequentialGroup()
705 .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
706 .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
707 .addComponent(numberOfChildNodesLabel)
708 .addComponent(matchLabel))
709 .addComponent(descriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
710 .addGap(0, 0, 0)
711 .addComponent(resultViewerTabs, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
712 );
713 }// </editor-fold>//GEN-END:initComponents
714 // Variables declaration - do not modify//GEN-BEGIN:variables
715 private javax.swing.JLabel descriptionLabel;
716 private javax.swing.JLabel matchLabel;
717 private javax.swing.JLabel numberOfChildNodesLabel;
718 private javax.swing.JTabbedPane resultViewerTabs;
719 // End of variables declaration//GEN-END:variables
720
731 @Deprecated
732 @Override
733 public boolean isMain() {
734 return this.isMain;
735 }
736
745 @Deprecated
746 public void setNumMatches(Integer numberOfChildNodes) {
747 this.setNumberOfChildNodes(numberOfChildNodes);
748 }
749
757 @Deprecated
758 public void resetTabs(Node unusedSelectedNode) {
759 this.setNode(null);
760 }
761
762}
void addResultViewer(DataResultViewer resultViewer)
static DataResultPanel createInstance(String title, String description, Node currentRootNode, int childNodeCount, Collection< DataResultViewer > viewers)
static void createInstanceCommon(String title, String description, Node currentRootNode, int childNodeCount, DataResultPanel resultViewPanel)
static DataResultPanel createInstance(String title, String description, Node currentRootNode, int childNodeCount)
void setContentViewer(DataContent customContentView)
static DataResultPanel createInstanceUninitialized(String title, String description, Node currentRootNode, int childNodeCount, DataContent customContentView)
static DataResultPanel createInstance(String title, String description, Node currentRootNode, int childNodeCount, DataContent customContentView)

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.