/*
* Autopsy Forensic Browser
*
* Copyright 2011-2014 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.corecomponents;
import java.awt.Cursor;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import javax.swing.JTabbedPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.openide.nodes.Node;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
/**
*
*/
public class DataContentPanel extends javax.swing.JPanel implements DataContent, ChangeListener {
private static Logger logger = Logger.getLogger(DataContentPanel.class.getName());
private final List<UpdateWrapper> viewers = new ArrayList<>();
;
private Node currentNode;
private final boolean isMain;
private boolean listeningToTabbedPane = false;
/**
* Creates new DataContentPanel panel The main data content panel can only
* be created by the data content top component, thus this constructor is
* not public.
*
* Use the createInstance factory method to create an external viewer data
* content panel.
*
*/
DataContentPanel(boolean isMain) {
this.isMain = isMain;
initComponents();
// add all implementors of DataContentViewer and put them in the tabbed pane
Collection<? extends DataContentViewer> dcvs = Lookup.getDefault().lookupAll(DataContentViewer.class);
for (DataContentViewer factory : dcvs) {
DataContentViewer dcv;
if (isMain) {
//use the instance from Lookup for the main viewer
dcv = factory;
} else {
dcv = factory.createInstance();
}
viewers.add(new UpdateWrapper(dcv));
jTabbedPane1.addTab(dcv.getTitle(), null,
dcv.getComponent(), dcv.getToolTip());
}
// disable the tabs
int numTabs = jTabbedPane1.getTabCount();
for (int tab = 0; tab < numTabs; ++tab) {
jTabbedPane1.setEnabledAt(tab, false);
}
}
/**
* Factory method to create an external (not main window) data content panel
* to be used in an external window
*
* @return a new instance of a data content panel
*/
public static DataContentPanel createInstance() {
return new DataContentPanel(false);
}
public JTabbedPane getTabPanels() {
return jTabbedPane1;
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
jTabbedPane1 = new javax.swing.JTabbedPane();
setMinimumSize(new java.awt.Dimension(5, 5));
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jTabbedPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jTabbedPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE)
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JTabbedPane jTabbedPane1;
// End of variables declaration//GEN-END:variables
@Override
public void setNode(Node selectedNode) {
// change the cursor to "waiting cursor" for this operation
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
String defaultName = NbBundle.getMessage(DataContentTopComponent.class, "CTL_DataContentTopComponent");
// set the file path
if (selectedNode == null) {
setName(defaultName);
} else {
Content content = selectedNode.getLookup().lookup(Content.class);
if (content != null) {
//String path = DataConversion.getformattedPath(ContentUtils.getDisplayPath(selectedNode.getLookup().lookup(Content.class)), 0);
String path = defaultName;
try {
path = content.getUniquePath();
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Exception while calling Content.getUniquePath() for {0}", content); //NON-NLS
}
setName(path);
} else {
setName(defaultName);
}
}
currentNode = selectedNode;
setupTabs(selectedNode);
} finally {
this.setCursor(null);
}
}
/**
* Resets the tabs based on the selected Node. If the selected node is null
* or not supported, disable that tab as well.
*
* @param selectedNode the selected content Node
*/
public void setupTabs(Node selectedNode) {
// Deferring becoming a listener to the tabbed pane until this point
// eliminates handling a superfluous stateChanged event during construction.
if (listeningToTabbedPane == false) {
jTabbedPane1.addChangeListener(this);
listeningToTabbedPane = true;
}
int currTabIndex = jTabbedPane1.getSelectedIndex();
int totalTabs = jTabbedPane1.getTabCount();
int maxPreferred = 0;
int preferredViewerIndex = 0;
for (int i = 0; i < totalTabs; ++i) {
UpdateWrapper dcv = viewers.get(i);
dcv.resetComponent();
// disable an unsupported tab (ex: picture viewer)
if ((selectedNode == null) || (dcv.isSupported(selectedNode) == false)) {
jTabbedPane1.setEnabledAt(i, false);
} else {
jTabbedPane1.setEnabledAt(i, true);
// remember the viewer with the highest preference value
int currentPreferred = dcv.isPreferred(selectedNode);
if (currentPreferred > maxPreferred) {
preferredViewerIndex = i;
maxPreferred = currentPreferred;
}
}
}
// let the user decide if we should stay with the current viewer
int tabIndex = UserPreferences.keepPreferredContentViewer() ? currTabIndex : preferredViewerIndex;
UpdateWrapper dcv = viewers.get(tabIndex);
// this is really only needed if no tabs were enabled
if (jTabbedPane1.isEnabledAt(tabIndex) == false) {
dcv.resetComponent();
} else {
dcv.setNode(selectedNode);
}
// set the tab to the one the user wants, then set that viewer's node.
jTabbedPane1.setSelectedIndex(tabIndex);
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
}
@Override
public void stateChanged(ChangeEvent evt) {
JTabbedPane pane = (JTabbedPane) evt.getSource();
// Get and set current selected tab
int currentTab = pane.getSelectedIndex();
if (currentTab != -1) {
UpdateWrapper dcv = viewers.get(currentTab);
if (dcv.isOutdated()) {
// change the cursor to "waiting cursor" for this operation
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
dcv.setNode(currentNode);
} finally {
this.setCursor(null);
}
}
}
}
private static class UpdateWrapper {
private DataContentViewer wrapped;
private boolean outdated;
UpdateWrapper(DataContentViewer wrapped) {
this.wrapped = wrapped;
this.outdated = true;
}
void setNode(Node selectedNode) {
this.wrapped.setNode(selectedNode);
this.outdated = false;
}
void resetComponent() {
this.wrapped.resetComponent();
this.outdated = true;
}
boolean isOutdated() {
return this.outdated;
}
boolean isSupported(Node node) {
return this.wrapped.isSupported(node);
}
int isPreferred(Node node) {
return this.wrapped.isPreferred(node);
}
}
}