mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-13 00:16:16 +00:00
ViewContextAction fixes
This commit is contained in:
parent
b31384b025
commit
40b51e77e1
File diff suppressed because it is too large
Load Diff
@ -26,6 +26,7 @@ import java.awt.Graphics;
|
|||||||
import java.awt.dnd.DnDConstants;
|
import java.awt.dnd.DnDConstants;
|
||||||
import java.awt.event.MouseAdapter;
|
import java.awt.event.MouseAdapter;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.beans.PropertyVetoException;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -36,6 +37,7 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.prefs.Preferences;
|
import java.util.prefs.Preferences;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import javax.swing.JTable;
|
import javax.swing.JTable;
|
||||||
@ -47,7 +49,6 @@ import javax.swing.event.TableColumnModelEvent;
|
|||||||
import javax.swing.event.TableColumnModelListener;
|
import javax.swing.event.TableColumnModelListener;
|
||||||
import javax.swing.table.TableCellRenderer;
|
import javax.swing.table.TableCellRenderer;
|
||||||
import javax.swing.table.TableColumnModel;
|
import javax.swing.table.TableColumnModel;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.netbeans.swing.etable.ETableColumn;
|
import org.netbeans.swing.etable.ETableColumn;
|
||||||
import org.netbeans.swing.outline.DefaultOutlineCellRenderer;
|
import org.netbeans.swing.outline.DefaultOutlineCellRenderer;
|
||||||
import org.netbeans.swing.outline.DefaultOutlineModel;
|
import org.netbeans.swing.outline.DefaultOutlineModel;
|
||||||
@ -64,20 +65,22 @@ import org.openide.nodes.NodeMemberEvent;
|
|||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.openide.util.NbPreferences;
|
import org.openide.util.NbPreferences;
|
||||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
|
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||||
|
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DataResult sortable table viewer
|
* A tabular viewer for the results view.
|
||||||
*
|
*
|
||||||
* @@@ Restore implementation of DataResultViewerTable as a DataResultViewer
|
* TODO (JIRA-2658): Fix DataResultViewer extension point. When this is done,
|
||||||
* service provider when DataResultViewers can be made compatible with node
|
* restore implementation of DataResultViewerTable as a DataResultViewer service
|
||||||
* multiple selection actions.
|
* provider.
|
||||||
*/
|
*/
|
||||||
//@ServiceProvider(service = DataResultViewer.class)
|
//@ServiceProvider(service = DataResultViewer.class)
|
||||||
public class DataResultViewerTable extends AbstractDataResultViewer {
|
public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
private static final Logger logger = Logger.getLogger(DataResultViewerTable.class.getName());
|
||||||
@NbBundle.Messages("DataResultViewerTable.firstColLbl=Name")
|
@NbBundle.Messages("DataResultViewerTable.firstColLbl=Name")
|
||||||
static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl();
|
static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl();
|
||||||
@NbBundle.Messages("DataResultViewerTable.pleasewaitNodeDisplayName=Please Wait...")
|
@NbBundle.Messages("DataResultViewerTable.pleasewaitNodeDisplayName=Please Wait...")
|
||||||
@ -234,12 +237,26 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
|||||||
oldNode.removeNodeListener(pleasewaitNodeListener);
|
oldNode.removeNodeListener(pleasewaitNodeListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there's no selection node, do nothing
|
|
||||||
if (hasChildren) {
|
if (hasChildren) {
|
||||||
Node root = selectedNode;
|
currentRoot = selectedNode;
|
||||||
pleasewaitNodeListener.reset();
|
pleasewaitNodeListener.reset();
|
||||||
root.addNodeListener(pleasewaitNodeListener);
|
currentRoot.addNodeListener(pleasewaitNodeListener);
|
||||||
setupTable(root);
|
setupTable(selectedNode);
|
||||||
|
NodeSelectionInfo selectedChildInfo = ((TableFilterNode) currentRoot).getChildNodeSelectionInfo();
|
||||||
|
if (null != selectedChildInfo) {
|
||||||
|
Node[] childNodes = currentRoot.getChildren().getNodes(true);
|
||||||
|
for (int i = 0; i < childNodes.length; ++i) {
|
||||||
|
Node childNode = childNodes[i];
|
||||||
|
if (selectedChildInfo.matches(childNode)) {
|
||||||
|
try {
|
||||||
|
em.setSelectedNodes(new Node[]{childNode});
|
||||||
|
} catch (PropertyVetoException ex) {
|
||||||
|
logger.log(Level.SEVERE, "Failed to select node specified by selected child info", ex);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Node emptyNode = new AbstractNode(Children.LEAF);
|
Node emptyNode = new AbstractNode(Children.LEAF);
|
||||||
em.setRootContext(emptyNode); // make empty node
|
em.setRootContext(emptyNode); // make empty node
|
||||||
@ -258,11 +275,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
|||||||
* @param root The parent Node of the ContentNodes
|
* @param root The parent Node of the ContentNodes
|
||||||
*/
|
*/
|
||||||
private void setupTable(final Node root) {
|
private void setupTable(final Node root) {
|
||||||
|
|
||||||
em.setRootContext(root);
|
em.setRootContext(root);
|
||||||
|
|
||||||
currentRoot = root;
|
currentRoot = root;
|
||||||
List<Node.Property<?>> props = loadColumnOrder();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OutlineView makes the first column be the result of
|
* OutlineView makes the first column be the result of
|
||||||
@ -275,10 +289,12 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
|||||||
* duplicates getDisplayName(). The current implementation does not
|
* duplicates getDisplayName(). The current implementation does not
|
||||||
* allow the first property column to be moved.
|
* allow the first property column to be moved.
|
||||||
*/
|
*/
|
||||||
|
List<Node.Property<?>> props = loadColumnOrder();
|
||||||
if (props.isEmpty() == false) {
|
if (props.isEmpty() == false) {
|
||||||
Node.Property<?> prop = props.remove(0);
|
Node.Property<?> prop = props.remove(0);
|
||||||
((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(prop.getDisplayName());
|
((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(prop.getDisplayName());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* show the horizontal scroll panel and show all the content & header If
|
* show the horizontal scroll panel and show all the content & header If
|
||||||
* there is only one column (which was removed from props above) Just
|
* there is only one column (which was removed from props above) Just
|
||||||
@ -287,9 +303,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
|||||||
outline.setAutoResizeMode((props.isEmpty()) ? JTable.AUTO_RESIZE_ALL_COLUMNS : JTable.AUTO_RESIZE_OFF);
|
outline.setAutoResizeMode((props.isEmpty()) ? JTable.AUTO_RESIZE_ALL_COLUMNS : JTable.AUTO_RESIZE_OFF);
|
||||||
|
|
||||||
assignColumns(props);
|
assignColumns(props);
|
||||||
|
|
||||||
setColumnWidths();
|
setColumnWidths();
|
||||||
|
|
||||||
loadColumnSorting();
|
loadColumnSorting();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,22 +48,21 @@ import org.sleuthkit.datamodel.AbstractFile;
|
|||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thumbnail view of images in data result with paging support.
|
* A thumbnail viewer for the results view, with paging support.
|
||||||
*
|
*
|
||||||
* Paging is added to reduce memory footprint and load only up to (currently)
|
* The paging is intended to reduce memory footprint by load only up to
|
||||||
* 1000 images at a time. This works whether or not the underlying content nodes
|
* (currently) 1000 images at a time. This works whether or not the underlying
|
||||||
* are being lazy loaded or not.
|
* content nodes are being lazy loaded or not.
|
||||||
*
|
*
|
||||||
|
* TODO (JIRA-2658): Fix DataResultViewer extension point. When this is done,
|
||||||
|
* restore implementation of DataResultViewerTable as a DataResultViewer service
|
||||||
|
* provider.
|
||||||
*/
|
*/
|
||||||
// @@@ Restore implementation of DataResultViewerThumbnail as a DataResultViewer
|
|
||||||
// service provider when DataResultViewers can be made compatible with node
|
|
||||||
// multi-selection actions.
|
|
||||||
//@ServiceProvider(service = DataResultViewer.class)
|
//@ServiceProvider(service = DataResultViewer.class)
|
||||||
final class DataResultViewerThumbnail extends AbstractDataResultViewer {
|
final class DataResultViewerThumbnail extends AbstractDataResultViewer {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName());
|
private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName());
|
||||||
//flag to keep track if images are being loaded
|
|
||||||
private int curPage;
|
private int curPage;
|
||||||
private int totalPages;
|
private int totalPages;
|
||||||
private int curPageImages;
|
private int curPageImages;
|
||||||
@ -71,8 +70,10 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
|
|||||||
private final PageUpdater pageUpdater = new PageUpdater();
|
private final PageUpdater pageUpdater = new PageUpdater();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a DataResultViewerThumbnail object that is compatible with node
|
* Constructs a thumbnail viewer for the results view, with paging support,
|
||||||
* multiple selection actions.
|
* that is compatible with node multiple selection actions.
|
||||||
|
*
|
||||||
|
* @param explorerManager The shared ExplorerManager for the result viewers.
|
||||||
*/
|
*/
|
||||||
DataResultViewerThumbnail(ExplorerManager explorerManager) {
|
DataResultViewerThumbnail(ExplorerManager explorerManager) {
|
||||||
super(explorerManager);
|
super(explorerManager);
|
||||||
@ -80,8 +81,8 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a DataResultViewerThumbnail object that is NOT compatible with
|
* Constructs a thumbnail viewer for the results view, with paging support,
|
||||||
* node multiple selection actions.
|
* that is NOT compatible with node multiple selection actions.
|
||||||
*/
|
*/
|
||||||
DataResultViewerThumbnail() {
|
DataResultViewerThumbnail() {
|
||||||
initialize();
|
initialize();
|
||||||
@ -93,7 +94,6 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
|
|||||||
})
|
})
|
||||||
private void initialize() {
|
private void initialize() {
|
||||||
initComponents();
|
initComponents();
|
||||||
|
|
||||||
iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
||||||
em.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener());
|
em.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener());
|
||||||
thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(
|
thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(
|
||||||
@ -311,20 +311,22 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setNode(Node givenNode) {
|
public void setNode(Node givenNode) {
|
||||||
// change the cursor to "waiting cursor" for this operation
|
|
||||||
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||||
try {
|
try {
|
||||||
if (givenNode != null) {
|
if (givenNode != null) {
|
||||||
|
/*
|
||||||
|
* Wrap the given node in a ThumbnailViewChildren that will
|
||||||
|
* produce ThumbnailPageNodes with ThumbnailViewNode children
|
||||||
|
* from the child nodes of the given node.
|
||||||
|
*/
|
||||||
ThumbnailViewChildren childNode = new ThumbnailViewChildren(givenNode, iconSize);
|
ThumbnailViewChildren childNode = new ThumbnailViewChildren(givenNode, iconSize);
|
||||||
|
|
||||||
final Node root = new AbstractNode(childNode);
|
final Node root = new AbstractNode(childNode);
|
||||||
pageUpdater.setRoot(root);
|
pageUpdater.setRoot(root);
|
||||||
root.addNodeListener(pageUpdater);
|
root.addNodeListener(pageUpdater);
|
||||||
em.setRootContext(root);
|
em.setRootContext(root);
|
||||||
} else {
|
} else {
|
||||||
Node emptyNode = new AbstractNode(Children.LEAF);
|
Node emptyNode = new AbstractNode(Children.LEAF);
|
||||||
em.setRootContext(emptyNode); // make empty node
|
em.setRootContext(emptyNode);
|
||||||
|
|
||||||
iconView.setBackground(Color.BLACK);
|
iconView.setBackground(Color.BLACK);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@ -349,21 +351,18 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
|
|||||||
this.curPage = -1;
|
this.curPage = -1;
|
||||||
curPageImages = 0;
|
curPageImages = 0;
|
||||||
updateControls();
|
updateControls();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearComponent() {
|
public void clearComponent() {
|
||||||
this.iconView.removeAll();
|
this.iconView.removeAll();
|
||||||
this.iconView = null;
|
this.iconView = null;
|
||||||
|
|
||||||
super.clearComponent();
|
super.clearComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void nextPage() {
|
private void nextPage() {
|
||||||
if (curPage < totalPages) {
|
if (curPage < totalPages) {
|
||||||
curPage++;
|
curPage++;
|
||||||
|
|
||||||
switchPage();
|
switchPage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -371,7 +370,6 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
|
|||||||
private void previousPage() {
|
private void previousPage() {
|
||||||
if (curPage > 1) {
|
if (curPage > 1) {
|
||||||
curPage--;
|
curPage--;
|
||||||
|
|
||||||
switchPage();
|
switchPage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2011-2016 Basis Technology Corp.
|
* Copyright 2011-2017 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -23,12 +23,35 @@ import org.openide.nodes.FilterNode;
|
|||||||
import org.openide.nodes.Node;
|
import org.openide.nodes.Node;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A children (child factory) implementation for a TableFilterNode. A
|
* A Children implementation for a TableFilterNode. A TableFilterNode creates at
|
||||||
* TableFilterNode creates at most one layer of child nodes for the node it
|
* most one layer of child nodes for the node it wraps. It is designed to be
|
||||||
* wraps. It is designed to be used for nodes displayed in Autopsy table views.
|
* used in the results view to ensure the individual viewers display only the
|
||||||
|
* first layer of child nodes.
|
||||||
*/
|
*/
|
||||||
class TableFilterChildren extends FilterNode.Children {
|
class TableFilterChildren extends FilterNode.Children {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Children object for a TableFilterNode. A TableFilterNode
|
||||||
|
* creates at most one layer of child nodes for the node it wraps. It is
|
||||||
|
* designed to be used in the results view to ensure the individual viewers
|
||||||
|
* display only the first layer of child nodes.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param wrappedNode The node wrapped by the TableFilterNode.
|
||||||
|
* @param createChildren True if a children (child factory) object should be
|
||||||
|
* created for the wrapped node.
|
||||||
|
*
|
||||||
|
* @return A children (child factory) object for a node wrapped by a
|
||||||
|
* TableFilterNode.
|
||||||
|
*/
|
||||||
|
public static Children createInstance(Node wrappedNode, boolean createChildren) {
|
||||||
|
if (createChildren) {
|
||||||
|
return new TableFilterChildren(wrappedNode);
|
||||||
|
} else {
|
||||||
|
return Children.LEAF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a children (child factory) implementation for a
|
* Constructs a children (child factory) implementation for a
|
||||||
* TableFilterNode. A TableFilterNode creates at most one layer of child
|
* TableFilterNode. A TableFilterNode creates at most one layer of child
|
||||||
@ -67,25 +90,4 @@ class TableFilterChildren extends FilterNode.Children {
|
|||||||
return new Node[]{this.copyNode(key)};
|
return new Node[]{this.copyNode(key)};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a children (child factory) object for a node wrapped in a
|
|
||||||
* TableFilterNode. A TableFilterNode creates at most one layer of child
|
|
||||||
* nodes for the node it wraps. It is designed to be used for nodes
|
|
||||||
* displayed in Autopsy table views.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param wrappedNode The node wrapped by the TableFilterNode.
|
|
||||||
* @param createChildren True if a children (child factory) object should be
|
|
||||||
* created for the wrapped node.
|
|
||||||
*
|
|
||||||
* @return A children (child factory) object for a node wrapped by a
|
|
||||||
* TableFilterNode.
|
|
||||||
*/
|
|
||||||
public static Children createInstance(Node wrappedNode, boolean createChildren) {
|
|
||||||
if (createChildren) {
|
|
||||||
return new TableFilterChildren(wrappedNode);
|
|
||||||
} else {
|
|
||||||
return Children.LEAF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -22,12 +22,13 @@ import org.openide.nodes.FilterNode;
|
|||||||
import org.openide.nodes.Node;
|
import org.openide.nodes.Node;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.openide.util.lookup.Lookups;
|
import org.openide.util.lookup.Lookups;
|
||||||
|
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo;
|
||||||
|
import org.sleuthkit.autopsy.directorytree.DataResultFilterNode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A filter node that creates at most one layer of child nodes for the node it
|
* A filter node that creates at most one layer of child nodes for the node it
|
||||||
* wraps. It is designed to be used for nodes displayed in Autopsy table views.
|
* wraps. It is designed to be used in the results view to ensure the individual
|
||||||
* This ensures that the table view for the node will not recursively display
|
* viewers display only the first layer of child nodes.
|
||||||
* child nodes and display only the first layer of child nodes.
|
|
||||||
*/
|
*/
|
||||||
public class TableFilterNode extends FilterNode {
|
public class TableFilterNode extends FilterNode {
|
||||||
|
|
||||||
@ -36,37 +37,40 @@ public class TableFilterNode extends FilterNode {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a filter node that creates at most one layer of child nodes
|
* Constructs a filter node that creates at most one layer of child nodes
|
||||||
* for the node it wraps. It is designed to be used for nodes displayed in
|
* for the node it wraps. It is designed to be used in the results view to
|
||||||
* Autopsy table views.
|
* ensure the individual viewers display only the first layer of child
|
||||||
|
* nodes.
|
||||||
*
|
*
|
||||||
* @param wrappedNode The node to wrap in the filter node.
|
* @param node The node to wrap in the filter node.
|
||||||
* @param createChildren True if a children (child factory) object should be
|
* @param createChildren True if a Children object should be created for the
|
||||||
* created for the wrapped node.
|
* wrapped node.
|
||||||
* The constructor should include column order key. (See getColumnOrderKey)
|
|
||||||
*/
|
*/
|
||||||
public TableFilterNode(Node wrappedNode, boolean createChildren) {
|
public TableFilterNode(Node node, boolean createChildren) {
|
||||||
super(wrappedNode, TableFilterChildren.createInstance(wrappedNode, createChildren) , Lookups.proxy(wrappedNode));
|
super(node, TableFilterChildren.createInstance(node, createChildren), Lookups.proxy(node));
|
||||||
this.createChildren = createChildren;
|
this.createChildren = createChildren;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a filter node that has information about the node's type.
|
* Constructs a filter node that creates at most one layer of child nodes
|
||||||
|
* for the node it wraps. It is designed to be used in the results view to
|
||||||
|
* ensure the individual viewers display only the first layer of child
|
||||||
|
* nodes.
|
||||||
*
|
*
|
||||||
* @param wrappedNode The node to wrap in the filter node.
|
* @param node The node to wrap in the filter node.
|
||||||
* @param createChildren True if a children (child factory) object should be
|
* @param createChildren True if a Children object should be created for the
|
||||||
* created for the wrapped node.
|
* wrapped node.
|
||||||
* @param columnOrderKey A key that represents the type of the original
|
* @param columnOrderKey A key that represents the type of the original
|
||||||
* wrapped node and what is being displayed under that
|
* wrapped node and what is being displayed under that
|
||||||
* node.
|
* node.
|
||||||
*/
|
*/
|
||||||
public TableFilterNode(Node wrappedNode, boolean createChildren, String columnOrderKey) {
|
public TableFilterNode(Node node, boolean createChildren, String columnOrderKey) {
|
||||||
super(wrappedNode, TableFilterChildren.createInstance(wrappedNode, createChildren));
|
super(node, TableFilterChildren.createInstance(node, createChildren));
|
||||||
this.createChildren = createChildren;
|
this.createChildren = createChildren;
|
||||||
this.columnOrderKey = columnOrderKey;
|
this.columnOrderKey = columnOrderKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a display name for the wrapped node, for use in the first column
|
* Gets the display name for the wrapped node, for use in the first column
|
||||||
* of an Autopsy table view.
|
* of an Autopsy table view.
|
||||||
*
|
*
|
||||||
* @return The display name.
|
* @return The display name.
|
||||||
@ -80,6 +84,41 @@ public class TableFilterNode extends FilterNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds information about which child node of this node, if any, should be
|
||||||
|
* selected. Can be null.
|
||||||
|
*
|
||||||
|
* @param selectedChildNodeInfo The child node selection information.
|
||||||
|
*/
|
||||||
|
public void setChildNodeSelectionInfo(NodeSelectionInfo selectedChildNodeInfo) {
|
||||||
|
/*
|
||||||
|
* Currently, child selection is only supported for nodes selected in
|
||||||
|
* the tree view and decorated with a DataResultFilterNode.
|
||||||
|
*/
|
||||||
|
if (getOriginal() instanceof DataResultFilterNode) {
|
||||||
|
((DataResultFilterNode) getOriginal()).setChildNodeSelectionInfo(selectedChildNodeInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets information about which child node of this node, if any, should be
|
||||||
|
* selected.
|
||||||
|
*
|
||||||
|
* @return The child node selection information, or null if no child should
|
||||||
|
* be selected.
|
||||||
|
*/
|
||||||
|
public NodeSelectionInfo getChildNodeSelectionInfo() {
|
||||||
|
/*
|
||||||
|
* Currently, child selection is only supported for nodes selected in
|
||||||
|
* the tree view and decorated with a DataResultFilterNode.
|
||||||
|
*/
|
||||||
|
if (getOriginal() instanceof DataResultFilterNode) {
|
||||||
|
return ((DataResultFilterNode) getOriginal()).getChildNodeSelectionInfo();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the column order key, which allows custom column ordering to be
|
* @return the column order key, which allows custom column ordering to be
|
||||||
* written into a properties file and be reloaded for future use in
|
* written into a properties file and be reloaded for future use in
|
||||||
@ -90,4 +129,5 @@ public class TableFilterNode extends FilterNode {
|
|||||||
String getColumnOrderKey() {
|
String getColumnOrderKey() {
|
||||||
return columnOrderKey;
|
return columnOrderKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ public abstract class DisplayableItemNode extends AbstractNode {
|
|||||||
* An item type shared by DisplayableItemNodes that can be the parents of
|
* An item type shared by DisplayableItemNodes that can be the parents of
|
||||||
* file nodes.
|
* file nodes.
|
||||||
*/
|
*/
|
||||||
final static String FILE_PARENT_NODE_KEY = "orgsleuthkitautopsydatamodel" + "FileTypeParentNode";
|
static final String FILE_PARENT_NODE_KEY = "orgsleuthkitautopsydatamodel" + "FileTypeParentNode";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the file, if any, linked to an artifact via a TSK_PATH_ID attribute
|
* Gets the file, if any, linked to an artifact via a TSK_PATH_ID attribute
|
||||||
@ -62,6 +62,8 @@ public abstract class DisplayableItemNode extends AbstractNode {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private NodeSelectionInfo selectedChildNodeInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a node that is eligible for display in the tree view or
|
* Constructs a node that is eligible for display in the tree view or
|
||||||
* results view. Capabilitites include accepting a
|
* results view. Capabilitites include accepting a
|
||||||
@ -102,7 +104,8 @@ public abstract class DisplayableItemNode extends AbstractNode {
|
|||||||
public abstract <T> T accept(DisplayableItemNodeVisitor<T> visitor);
|
public abstract <T> T accept(DisplayableItemNodeVisitor<T> visitor);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates whether or not the node is a leaf node.
|
* Indicates whether or not the node is capable of having child nodes.
|
||||||
|
* Should only return true if the node is ALWAYS a leaf node.
|
||||||
*
|
*
|
||||||
* @return True or false.
|
* @return True or false.
|
||||||
*/
|
*/
|
||||||
@ -115,4 +118,25 @@ public abstract class DisplayableItemNode extends AbstractNode {
|
|||||||
*/
|
*/
|
||||||
public abstract String getItemType();
|
public abstract String getItemType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds information about which child node of this node, if any, should be
|
||||||
|
* selected. Can be null.
|
||||||
|
*
|
||||||
|
* @param selectedChildNodeInfo The child node selection information.
|
||||||
|
*/
|
||||||
|
public void setChildNodeSelectionInfo(NodeSelectionInfo selectedChildNodeInfo) {
|
||||||
|
this.selectedChildNodeInfo = selectedChildNodeInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets information about which child node of this node, if any, should be
|
||||||
|
* selected.
|
||||||
|
*
|
||||||
|
* @return The child node selection information, or null if no child should
|
||||||
|
* be selected.
|
||||||
|
*/
|
||||||
|
public NodeSelectionInfo getChildNodeSelectionInfo() {
|
||||||
|
return selectedChildNodeInfo;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
package org.sleuthkit.autopsy.datamodel;
|
package org.sleuthkit.autopsy.datamodel;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -30,7 +31,6 @@ import org.sleuthkit.autopsy.actions.AddContentTagAction;
|
|||||||
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
|
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
|
||||||
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
|
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
|
||||||
import org.sleuthkit.autopsy.coreutils.ImageUtils;
|
import org.sleuthkit.autopsy.coreutils.ImageUtils;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
|
||||||
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
|
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
|
||||||
import org.sleuthkit.autopsy.directorytree.ExtractAction;
|
import org.sleuthkit.autopsy.directorytree.ExtractAction;
|
||||||
import org.sleuthkit.autopsy.directorytree.HashSearchAction;
|
import org.sleuthkit.autopsy.directorytree.HashSearchAction;
|
||||||
@ -42,32 +42,84 @@ import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
|
|||||||
import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM;
|
import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is the Node for an AbstractFile. It may have derived files
|
* A node for representing an AbstractFile. It may have derived file node
|
||||||
* children.
|
* children.
|
||||||
*/
|
*/
|
||||||
public class FileNode extends AbstractFsContentNode<AbstractFile> {
|
public class FileNode extends AbstractFsContentNode<AbstractFile> {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(FileNode.class.getName());
|
/**
|
||||||
|
* Gets the path to the icon file that should be used to visually represent
|
||||||
|
* an AbstractFile, using the file name extension to select the icon.
|
||||||
|
*
|
||||||
|
* @param file An AbstractFile.
|
||||||
|
*
|
||||||
|
* @return An icon file path.
|
||||||
|
*/
|
||||||
|
static String getIconForFileType(AbstractFile file) {
|
||||||
|
String ext = file.getNameExtension();
|
||||||
|
if (StringUtils.isBlank(ext)) {
|
||||||
|
return "org/sleuthkit/autopsy/images/file-icon.png"; //NON-NLS
|
||||||
|
} else {
|
||||||
|
ext = "." + ext;
|
||||||
|
}
|
||||||
|
if (ImageUtils.isImageThumbnailSupported(file)
|
||||||
|
|| FileTypeExtensions.getImageExtensions().contains(ext)) {
|
||||||
|
return "org/sleuthkit/autopsy/images/image-file.png"; //NON-NLS
|
||||||
|
}
|
||||||
|
if (FileTypeExtensions.getVideoExtensions().contains(ext)) {
|
||||||
|
return "org/sleuthkit/autopsy/images/video-file.png"; //NON-NLS
|
||||||
|
}
|
||||||
|
if (FileTypeExtensions.getAudioExtensions().contains(ext)) {
|
||||||
|
return "org/sleuthkit/autopsy/images/audio-file.png"; //NON-NLS
|
||||||
|
}
|
||||||
|
if (FileTypeExtensions.getDocumentExtensions().contains(ext)) {
|
||||||
|
return "org/sleuthkit/autopsy/images/doc-file.png"; //NON-NLS
|
||||||
|
}
|
||||||
|
if (FileTypeExtensions.getExecutableExtensions().contains(ext)) {
|
||||||
|
return "org/sleuthkit/autopsy/images/exe-file.png"; //NON-NLS
|
||||||
|
}
|
||||||
|
if (FileTypeExtensions.getTextExtensions().contains(ext)) {
|
||||||
|
return "org/sleuthkit/autopsy/images/text-file.png"; //NON-NLS
|
||||||
|
}
|
||||||
|
if (FileTypeExtensions.getWebExtensions().contains(ext)) {
|
||||||
|
return "org/sleuthkit/autopsy/images/web-file.png"; //NON-NLS
|
||||||
|
}
|
||||||
|
if (FileTypeExtensions.getPDFExtensions().contains(ext)) {
|
||||||
|
return "org/sleuthkit/autopsy/images/pdf-file.png"; //NON-NLS
|
||||||
|
}
|
||||||
|
if (FileTypeExtensions.getArchiveExtensions().contains(ext)) {
|
||||||
|
return "org/sleuthkit/autopsy/images/archive-file.png"; //NON-NLS
|
||||||
|
}
|
||||||
|
return "org/sleuthkit/autopsy/images/file-icon.png"; //NON-NLS
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructs a node for representing an AbstractFile. It may have derived
|
||||||
|
* file node children.
|
||||||
*
|
*
|
||||||
* @param file underlying Content
|
* @param file An AbstractFile object.
|
||||||
*/
|
*/
|
||||||
public FileNode(AbstractFile file) {
|
public FileNode(AbstractFile file) {
|
||||||
this(file, true);
|
this(file, true);
|
||||||
|
|
||||||
setIcon(file);
|
setIcon(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a node for representing an AbstractFile. It may have derived
|
||||||
|
* file node children.
|
||||||
|
*
|
||||||
|
* @param file An AbstractFile object.
|
||||||
|
* @param directoryBrowseMode
|
||||||
|
*/
|
||||||
public FileNode(AbstractFile file, boolean directoryBrowseMode) {
|
public FileNode(AbstractFile file, boolean directoryBrowseMode) {
|
||||||
super(file, directoryBrowseMode);
|
super(file, directoryBrowseMode);
|
||||||
|
|
||||||
setIcon(file);
|
setIcon(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sets the icon for the node, based on properties of the AbstractFile.
|
||||||
|
*/
|
||||||
private void setIcon(AbstractFile file) {
|
private void setIcon(AbstractFile file) {
|
||||||
// set name, display name, and icon
|
|
||||||
if (file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC)) {
|
if (file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC)) {
|
||||||
if (file.getType().equals(TSK_DB_FILES_TYPE_ENUM.CARVED)) {
|
if (file.getType().equals(TSK_DB_FILES_TYPE_ENUM.CARVED)) {
|
||||||
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/carved-file-icon-16.png"); //NON-NLS
|
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/carved-file-icon-16.png"); //NON-NLS
|
||||||
@ -79,110 +131,92 @@ public class FileNode extends AbstractFsContentNode<AbstractFile> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the set of actions that are associated with this node. This set is
|
||||||
|
* used to construct the context menu for the node.
|
||||||
|
*
|
||||||
|
* @param context Whether to find actions for context meaning or for the
|
||||||
|
* node itself.
|
||||||
|
*
|
||||||
|
* @return An array of the actions.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
@NbBundle.Messages({
|
@NbBundle.Messages({
|
||||||
"FileNode.getActions.viewFileInDir.text=View File in Directory",
|
"FileNode.getActions.viewFileInDir.text=View File in Directory",
|
||||||
"FileNode.getActions.viewInNewWin.text=View in New Window",
|
"FileNode.getActions.viewInNewWin.text=View in New Window",
|
||||||
"FileNode.getActions.openInExtViewer.text=Open in External Viewer",
|
"FileNode.getActions.openInExtViewer.text=Open in External Viewer",
|
||||||
"FileNode.getActions.searchFilesSameMD5.text=Search for files with the same MD5 hash"})
|
"FileNode.getActions.searchFilesSameMD5.text=Search for files with the same MD5 hash"})
|
||||||
public Action[] getActions(boolean popup) {
|
public Action[] getActions(boolean context) {
|
||||||
List<Action> actionsList = new ArrayList<>();
|
List<Action> actionsList = new ArrayList<>();
|
||||||
for (Action a : super.getActions(true)) {
|
actionsList.addAll(Arrays.asList(super.getActions(true)));
|
||||||
actionsList.add(a);
|
|
||||||
}
|
|
||||||
if (!this.getDirectoryBrowseMode()) {
|
if (!this.getDirectoryBrowseMode()) {
|
||||||
actionsList.add(new ViewContextAction(Bundle.FileNode_getActions_viewFileInDir_text(), this));
|
actionsList.add(new ViewContextAction(Bundle.FileNode_getActions_viewFileInDir_text(), this));
|
||||||
actionsList.add(null); // creates a menu separator
|
actionsList.add(null); // Creates a item separator
|
||||||
}
|
}
|
||||||
|
|
||||||
actionsList.add(new NewWindowViewAction(Bundle.FileNode_getActions_viewInNewWin_text(), this));
|
actionsList.add(new NewWindowViewAction(Bundle.FileNode_getActions_viewInNewWin_text(), this));
|
||||||
actionsList.add(new ExternalViewerAction(Bundle.FileNode_getActions_openInExtViewer_text(), this));
|
actionsList.add(new ExternalViewerAction(Bundle.FileNode_getActions_openInExtViewer_text(), this));
|
||||||
actionsList.add(ViewFileInTimelineAction.createViewFileAction(getContent()));
|
actionsList.add(ViewFileInTimelineAction.createViewFileAction(getContent()));
|
||||||
|
actionsList.add(null); // Creates a item separator
|
||||||
|
|
||||||
actionsList.add(null); // creates a menu separator
|
|
||||||
actionsList.add(ExtractAction.getInstance());
|
actionsList.add(ExtractAction.getInstance());
|
||||||
actionsList.add(new HashSearchAction(Bundle.FileNode_getActions_searchFilesSameMD5_text(), this));
|
actionsList.add(new HashSearchAction(Bundle.FileNode_getActions_searchFilesSameMD5_text(), this));
|
||||||
actionsList.add(null); // creates a menu separator
|
actionsList.add(null); // Creates a item separator
|
||||||
actionsList.add(AddContentTagAction.getInstance());
|
|
||||||
|
|
||||||
|
actionsList.add(AddContentTagAction.getInstance());
|
||||||
final Collection<AbstractFile> selectedFilesList = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
|
final Collection<AbstractFile> selectedFilesList = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
|
||||||
if(selectedFilesList.size() == 1) {
|
if (1 == selectedFilesList.size()) {
|
||||||
actionsList.add(DeleteFileContentTagAction.getInstance());
|
actionsList.add(DeleteFileContentTagAction.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
actionsList.addAll(ContextMenuExtensionPoint.getActions());
|
actionsList.addAll(ContextMenuExtensionPoint.getActions());
|
||||||
return actionsList.toArray(new Action[actionsList.size()]);
|
return actionsList.toArray(new Action[actionsList.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts a ContentNodeVisitor.
|
||||||
|
*
|
||||||
|
* @param <T> The type parameter of the Visitor.
|
||||||
|
* @param visitor The Visitor.
|
||||||
|
*
|
||||||
|
* @return An object determied by the type parameter of the Visitor.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public <T> T accept(ContentNodeVisitor<T> v) {
|
public <T> T accept(ContentNodeVisitor<T> visitor) {
|
||||||
return v.visit(this);
|
return visitor.visit(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts a DisplayableItemNodeVisitor.
|
||||||
|
*
|
||||||
|
* @param <T> The type parameter of the Visitor.
|
||||||
|
* @param visitor The Visitor.
|
||||||
|
*
|
||||||
|
* @return An object determied by the type parameter of the Visitor.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public <T> T accept(DisplayableItemNodeVisitor<T> v) {
|
public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
|
||||||
return v.visit(this);
|
return visitor.visit(this);
|
||||||
}
|
|
||||||
|
|
||||||
// Given a file, returns the correct icon for said
|
|
||||||
// file based off it's extension
|
|
||||||
static String getIconForFileType(AbstractFile file) {
|
|
||||||
// Get the name, extension
|
|
||||||
String ext = file.getNameExtension();
|
|
||||||
|
|
||||||
if (StringUtils.isBlank(ext)) {
|
|
||||||
return "org/sleuthkit/autopsy/images/file-icon.png"; //NON-NLS
|
|
||||||
} else {
|
|
||||||
ext = "." + ext;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImageUtils.isImageThumbnailSupported(file)
|
|
||||||
|| FileTypeExtensions.getImageExtensions().contains(ext)) {
|
|
||||||
return "org/sleuthkit/autopsy/images/image-file.png"; //NON-NLS
|
|
||||||
}
|
|
||||||
// Videos
|
|
||||||
if (FileTypeExtensions.getVideoExtensions().contains(ext)) {
|
|
||||||
return "org/sleuthkit/autopsy/images/video-file.png"; //NON-NLS
|
|
||||||
}
|
|
||||||
// Audio Files
|
|
||||||
if (FileTypeExtensions.getAudioExtensions().contains(ext)) {
|
|
||||||
return "org/sleuthkit/autopsy/images/audio-file.png"; //NON-NLS
|
|
||||||
}
|
|
||||||
// Documents
|
|
||||||
if (FileTypeExtensions.getDocumentExtensions().contains(ext)) {
|
|
||||||
return "org/sleuthkit/autopsy/images/doc-file.png"; //NON-NLS
|
|
||||||
}
|
|
||||||
// Executables / System Files
|
|
||||||
if (FileTypeExtensions.getExecutableExtensions().contains(ext)) {
|
|
||||||
return "org/sleuthkit/autopsy/images/exe-file.png"; //NON-NLS
|
|
||||||
}
|
|
||||||
// Text Files
|
|
||||||
if (FileTypeExtensions.getTextExtensions().contains(ext)) {
|
|
||||||
return "org/sleuthkit/autopsy/images/text-file.png"; //NON-NLS
|
|
||||||
}
|
|
||||||
// Web Files
|
|
||||||
if (FileTypeExtensions.getWebExtensions().contains(ext)) {
|
|
||||||
return "org/sleuthkit/autopsy/images/web-file.png"; //NON-NLS
|
|
||||||
}
|
|
||||||
// PDFs
|
|
||||||
if (FileTypeExtensions.getPDFExtensions().contains(ext)) {
|
|
||||||
return "org/sleuthkit/autopsy/images/pdf-file.png"; //NON-NLS
|
|
||||||
}
|
|
||||||
// Archives
|
|
||||||
if (FileTypeExtensions.getArchiveExtensions().contains(ext)) {
|
|
||||||
return "org/sleuthkit/autopsy/images/archive-file.png"; //NON-NLS
|
|
||||||
}
|
|
||||||
// Else return the default
|
|
||||||
return "org/sleuthkit/autopsy/images/file-icon.png"; //NON-NLS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not the node is capable of having child nodes.
|
||||||
|
* Should only return true if the node is ALWAYS a leaf node.
|
||||||
|
*
|
||||||
|
* @return True or false.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isLeafTypeNode() {
|
public boolean isLeafTypeNode() {
|
||||||
// This seems wrong, but it also seems that it is never called
|
/*
|
||||||
// because the visitor to figure out if there are children or
|
* A FileNode may have FileNodes for derived files as children.
|
||||||
// not will check if it has children using the Content API
|
*/
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the item type string of the node, suitable for use as a key.
|
||||||
|
*
|
||||||
|
* @return A String representing the item type of node.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String getItemType() {
|
public String getItemType() {
|
||||||
return getClass().getName();
|
return getClass().getName();
|
||||||
|
@ -189,7 +189,7 @@ public class VirtualDirectoryNode extends AbstractAbstractFileNode<VirtualDirect
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLeafTypeNode() {
|
public boolean isLeafTypeNode() {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,6 +53,7 @@ import org.sleuthkit.autopsy.datamodel.FileNode;
|
|||||||
import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode;
|
import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode;
|
||||||
import org.sleuthkit.autopsy.datamodel.LayoutFileNode;
|
import org.sleuthkit.autopsy.datamodel.LayoutFileNode;
|
||||||
import org.sleuthkit.autopsy.datamodel.LocalFileNode;
|
import org.sleuthkit.autopsy.datamodel.LocalFileNode;
|
||||||
|
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo;
|
||||||
import org.sleuthkit.autopsy.datamodel.Reports;
|
import org.sleuthkit.autopsy.datamodel.Reports;
|
||||||
import org.sleuthkit.autopsy.datamodel.SlackFileNode;
|
import org.sleuthkit.autopsy.datamodel.SlackFileNode;
|
||||||
import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode;
|
import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode;
|
||||||
@ -208,6 +209,33 @@ public class DataResultFilterNode extends FilterNode {
|
|||||||
return propertySets;
|
return propertySets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds information about which child node of this node, if any, should be
|
||||||
|
* selected. Can be null.
|
||||||
|
*
|
||||||
|
* @param selectedChildNodeInfo The child node selection information.
|
||||||
|
*/
|
||||||
|
public void setChildNodeSelectionInfo(NodeSelectionInfo selectedChildNodeInfo) {
|
||||||
|
if (getOriginal() instanceof DisplayableItemNode) {
|
||||||
|
((DisplayableItemNode) getOriginal()).setChildNodeSelectionInfo(selectedChildNodeInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets information about which child node of this node, if any, should be
|
||||||
|
* selected.
|
||||||
|
*
|
||||||
|
* @return The child node selection information, or null if no child should
|
||||||
|
* be selected.
|
||||||
|
*/
|
||||||
|
public NodeSelectionInfo getChildNodeSelectionInfo() {
|
||||||
|
if (getOriginal() instanceof DisplayableItemNode) {
|
||||||
|
return ((DisplayableItemNode) getOriginal()).getChildNodeSelectionInfo();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is used for the creation of all the children for the
|
* This class is used for the creation of all the children for the
|
||||||
* DataResultFilterNode that created in the DataResultFilterNode.java.
|
* DataResultFilterNode that created in the DataResultFilterNode.java.
|
||||||
|
@ -62,7 +62,7 @@ class DirectoryTreeFilterNode extends FilterNode {
|
|||||||
DirectoryTreeFilterNode(Node nodeToWrap, boolean createChildren) {
|
DirectoryTreeFilterNode(Node nodeToWrap, boolean createChildren) {
|
||||||
super(nodeToWrap,
|
super(nodeToWrap,
|
||||||
DirectoryTreeFilterChildren.createInstance(nodeToWrap, createChildren),
|
DirectoryTreeFilterChildren.createInstance(nodeToWrap, createChildren),
|
||||||
new ProxyLookup(Lookups.singleton(new OriginalNode(nodeToWrap)), nodeToWrap.getLookup()));
|
new ProxyLookup(Lookups.singleton(nodeToWrap), nodeToWrap.getLookup()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -168,17 +168,27 @@ class DirectoryTreeFilterNode extends FilterNode {
|
|||||||
return actions.toArray(new Action[actions.size()]);
|
return actions.toArray(new Action[actions.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
//FIXME: this seems like a big hack -jm
|
/**
|
||||||
public static class OriginalNode {
|
* RJCTODO
|
||||||
|
*
|
||||||
private final Node original;
|
* @return
|
||||||
|
*/
|
||||||
OriginalNode(Node original) {
|
@Override
|
||||||
this.original = original;
|
public Node getOriginal() {
|
||||||
}
|
return super.getOriginal();
|
||||||
|
|
||||||
Node getNode() {
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// //FIXME: this seems like a big hack -jm
|
||||||
|
// public static class OriginalNode {
|
||||||
|
//
|
||||||
|
// private final Node original;
|
||||||
|
//
|
||||||
|
// OriginalNode(Node original) {
|
||||||
|
// this.original = original;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Node getNode() {
|
||||||
|
// return original;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
@ -605,7 +605,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
|
|||||||
}
|
}
|
||||||
} // change in node selection
|
} // change in node selection
|
||||||
else if (changed.equals(ExplorerManager.PROP_SELECTED_NODES)) {
|
else if (changed.equals(ExplorerManager.PROP_SELECTED_NODES)) {
|
||||||
respondSelection((Node[]) evt.getOldValue(), (Node[]) evt.getNewValue());
|
respondSelection((Node[]) evt.getOldValue(), (Node[]) evt.getNewValue());
|
||||||
} else if (changed.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
|
} else if (changed.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
|
||||||
// nothing to do here.
|
// nothing to do here.
|
||||||
// all nodes should be listening for these events and update accordingly.
|
// all nodes should be listening for these events and update accordingly.
|
||||||
@ -613,7 +613,6 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NbBundle.Messages("DirectoryTreeTopComponent.emptyMimeNode.text=Data not available. Run file type identification module.")
|
|
||||||
/**
|
/**
|
||||||
* Event handler to run when selection changed
|
* Event handler to run when selection changed
|
||||||
*
|
*
|
||||||
@ -622,10 +621,9 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
|
|||||||
* @param oldNodes
|
* @param oldNodes
|
||||||
* @param newNodes
|
* @param newNodes
|
||||||
*/
|
*/
|
||||||
|
@NbBundle.Messages("DirectoryTreeTopComponent.emptyMimeNode.text=Data not available. Run file type identification module.")
|
||||||
private void respondSelection(final Node[] oldNodes, final Node[] newNodes) {
|
private void respondSelection(final Node[] oldNodes, final Node[] newNodes) {
|
||||||
if (!Case.isCaseOpen()) {
|
if (!Case.isCaseOpen()) {
|
||||||
//handle in-between condition when case is being closed
|
|
||||||
//and legacy selection events are pumped
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -634,65 +632,52 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
|
|||||||
// selection-change event is processed.
|
// selection-change event is processed.
|
||||||
//TODO find a different way to refresh data result viewer, scheduling this
|
//TODO find a different way to refresh data result viewer, scheduling this
|
||||||
//to EDT breaks loading of nodes in the background
|
//to EDT breaks loading of nodes in the background
|
||||||
EventQueue.invokeLater(new Runnable() {
|
EventQueue.invokeLater(() -> {
|
||||||
@Override
|
// change the cursor to "waiting cursor" for this operation
|
||||||
public void run() {
|
DirectoryTreeTopComponent.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||||
// change the cursor to "waiting cursor" for this operation
|
try {
|
||||||
DirectoryTreeTopComponent.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
Node treeNode = DirectoryTreeTopComponent.this.getSelectedNode();
|
||||||
try {
|
if (treeNode != null) {
|
||||||
|
Node originNode = ((DirectoryTreeFilterNode) treeNode).getOriginal();
|
||||||
Node treeNode = DirectoryTreeTopComponent.this.getSelectedNode();
|
//set node, wrap in filter node first to filter out children
|
||||||
if (treeNode != null) {
|
Node drfn = new DataResultFilterNode(originNode, DirectoryTreeTopComponent.this.em);
|
||||||
DirectoryTreeFilterNode.OriginalNode origin = treeNode.getLookup().lookup(DirectoryTreeFilterNode.OriginalNode.class);
|
// Create a TableFilterNode with knowledge of the node's type to allow for column order settings
|
||||||
if (origin == null) {
|
if (FileTypesByMimeType.isEmptyMimeTypeNode(originNode)) {
|
||||||
return;
|
//Special case for when File Type Identification has not yet been run and
|
||||||
}
|
//there are no mime types to populate Files by Mime Type Tree
|
||||||
|
EmptyNode emptyNode = new EmptyNode(Bundle.DirectoryTreeTopComponent_emptyMimeNode_text());
|
||||||
Node originNode = origin.getNode();
|
dataResult.setNode(new TableFilterNode(emptyNode, true, "This Node Is Empty")); //NON-NLS
|
||||||
|
} else if (originNode instanceof DisplayableItemNode) {
|
||||||
//set node, wrap in filter node first to filter out children
|
dataResult.setNode(new TableFilterNode(drfn, true, ((DisplayableItemNode) originNode).getItemType()));
|
||||||
Node drfn = new DataResultFilterNode(originNode, DirectoryTreeTopComponent.this.em);
|
} else {
|
||||||
|
dataResult.setNode(new TableFilterNode(drfn, true));
|
||||||
// Create a TableFilterNode with knowledge of the node's type to allow for column order settings
|
|
||||||
if (FileTypesByMimeType.isEmptyMimeTypeNode(originNode)) {
|
|
||||||
//Special case for when File Type Identification has not yet been run and
|
|
||||||
//there are no mime types to populate Files by Mime Type Tree
|
|
||||||
EmptyNode emptyNode = new EmptyNode(Bundle.DirectoryTreeTopComponent_emptyMimeNode_text());
|
|
||||||
dataResult.setNode(new TableFilterNode(emptyNode, true, "This Node Is Empty")); //NON-NLS
|
|
||||||
} else if (originNode instanceof DisplayableItemNode) {
|
|
||||||
dataResult.setNode(new TableFilterNode(drfn, true, ((DisplayableItemNode) originNode).getItemType()));
|
|
||||||
} else {
|
|
||||||
dataResult.setNode(new TableFilterNode(drfn, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
String displayName = "";
|
|
||||||
Content content = originNode.getLookup().lookup(Content.class);
|
|
||||||
if (content != null) {
|
|
||||||
try {
|
|
||||||
displayName = content.getUniquePath();
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
LOGGER.log(Level.SEVERE, "Exception while calling Content.getUniquePath() for node: {0}", originNode); //NON-NLS
|
|
||||||
}
|
|
||||||
} else if (originNode.getLookup().lookup(String.class) != null) {
|
|
||||||
displayName = originNode.getLookup().lookup(String.class);
|
|
||||||
}
|
|
||||||
dataResult.setPath(displayName);
|
|
||||||
}
|
}
|
||||||
|
String displayName = "";
|
||||||
// set the directory listing to be active
|
Content content = originNode.getLookup().lookup(Content.class);
|
||||||
if (oldNodes != null && newNodes != null
|
if (content != null) {
|
||||||
&& (oldNodes.length == newNodes.length)) {
|
try {
|
||||||
boolean sameNodes = true;
|
displayName = content.getUniquePath();
|
||||||
for (int i = 0; i < oldNodes.length; i++) {
|
} catch (TskCoreException ex) {
|
||||||
sameNodes = sameNodes && oldNodes[i].getName().equals(newNodes[i].getName());
|
LOGGER.log(Level.SEVERE, "Exception while calling Content.getUniquePath() for node: {0}", originNode); //NON-NLS
|
||||||
}
|
|
||||||
if (!sameNodes) {
|
|
||||||
dataResult.requestActive();
|
|
||||||
}
|
}
|
||||||
|
} else if (originNode.getLookup().lookup(String.class) != null) {
|
||||||
|
displayName = originNode.getLookup().lookup(String.class);
|
||||||
}
|
}
|
||||||
} finally {
|
dataResult.setPath(displayName);
|
||||||
setCursor(null);
|
|
||||||
}
|
}
|
||||||
|
// set the directory listing to be active
|
||||||
|
if (oldNodes != null && newNodes != null
|
||||||
|
&& (oldNodes.length == newNodes.length)) {
|
||||||
|
boolean sameNodes = true;
|
||||||
|
for (int i = 0; i < oldNodes.length; i++) {
|
||||||
|
sameNodes = sameNodes && oldNodes[i].getName().equals(newNodes[i].getName());
|
||||||
|
}
|
||||||
|
if (!sameNodes) {
|
||||||
|
dataResult.requestActive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setCursor(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -774,29 +759,16 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
|
|||||||
private void refreshDataSourceTree() {
|
private void refreshDataSourceTree() {
|
||||||
Node selectedNode = getSelectedNode();
|
Node selectedNode = getSelectedNode();
|
||||||
final String[] selectedPath = NodeOp.createPath(selectedNode, em.getRootContext());
|
final String[] selectedPath = NodeOp.createPath(selectedNode, em.getRootContext());
|
||||||
|
|
||||||
Children rootChildren = em.getRootContext().getChildren();
|
Children rootChildren = em.getRootContext().getChildren();
|
||||||
Node dataSourcesFilterNode = rootChildren.findChild(DataSourcesNode.NAME);
|
Node dataSourcesFilterNode = rootChildren.findChild(DataSourcesNode.NAME);
|
||||||
if (dataSourcesFilterNode == null) {
|
if (dataSourcesFilterNode == null) {
|
||||||
LOGGER.log(Level.SEVERE, "Cannot find data sources filter node, won't refresh the content tree"); //NON-NLS
|
LOGGER.log(Level.SEVERE, "Cannot find data sources filter node, won't refresh the content tree"); //NON-NLS
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DirectoryTreeFilterNode.OriginalNode imagesNodeOrig = dataSourcesFilterNode.getLookup().lookup(DirectoryTreeFilterNode.OriginalNode.class);
|
Node dataSourcesNode = ((DirectoryTreeFilterNode) dataSourcesFilterNode).getOriginal();
|
||||||
|
DataSourcesNode.DataSourcesNodeChildren contentRootChildren = (DataSourcesNode.DataSourcesNodeChildren) dataSourcesNode.getChildren();
|
||||||
if (imagesNodeOrig == null) {
|
|
||||||
LOGGER.log(Level.SEVERE, "Cannot find data sources node, won't refresh the content tree"); //NON-NLS
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Node imagesNode = imagesNodeOrig.getNode();
|
|
||||||
|
|
||||||
DataSourcesNode.DataSourcesNodeChildren contentRootChildren = (DataSourcesNode.DataSourcesNodeChildren) imagesNode.getChildren();
|
|
||||||
contentRootChildren.refreshContentKeys();
|
contentRootChildren.refreshContentKeys();
|
||||||
|
|
||||||
//final TreeView tree = getTree();
|
|
||||||
//tree.expandNode(imagesNode);
|
|
||||||
setSelectedNode(selectedPath, DataSourcesNode.NAME);
|
setSelectedNode(selectedPath, DataSourcesNode.NAME);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -963,6 +935,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RJCTODO: What is this all about?
|
||||||
@Override
|
@Override
|
||||||
public void viewArtifactContent(BlackboardArtifact art) {
|
public void viewArtifactContent(BlackboardArtifact art) {
|
||||||
new ViewContextAction(
|
new ViewContextAction(
|
||||||
|
@ -24,49 +24,46 @@ import java.beans.PropertyVetoException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CancellationException;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import javax.swing.AbstractAction;
|
import javax.swing.AbstractAction;
|
||||||
import javax.swing.SwingWorker;
|
|
||||||
import org.openide.nodes.AbstractNode;
|
import org.openide.nodes.AbstractNode;
|
||||||
import org.openide.explorer.ExplorerManager;
|
import org.openide.explorer.ExplorerManager;
|
||||||
import org.openide.explorer.view.TreeView;
|
import org.openide.explorer.view.TreeView;
|
||||||
import org.openide.nodes.Children;
|
import org.openide.nodes.Children;
|
||||||
import org.openide.nodes.Node;
|
import org.openide.nodes.Node;
|
||||||
import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent;
|
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||||
import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode;
|
import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode;
|
||||||
import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
|
import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
|
||||||
|
import org.sleuthkit.autopsy.datamodel.ContentNodeSelectionInfo;
|
||||||
import org.sleuthkit.autopsy.datamodel.DataSourcesNode;
|
import org.sleuthkit.autopsy.datamodel.DataSourcesNode;
|
||||||
|
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
|
||||||
import org.sleuthkit.autopsy.datamodel.RootContentChildren;
|
import org.sleuthkit.autopsy.datamodel.RootContentChildren;
|
||||||
import org.sleuthkit.datamodel.AbstractFile;
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
import org.sleuthkit.datamodel.Content;
|
import org.sleuthkit.datamodel.Content;
|
||||||
import org.sleuthkit.datamodel.ContentVisitor;
|
import org.sleuthkit.datamodel.ContentVisitor;
|
||||||
import org.sleuthkit.datamodel.Directory;
|
|
||||||
import org.sleuthkit.datamodel.FileSystem;
|
import org.sleuthkit.datamodel.FileSystem;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
import org.sleuthkit.datamodel.TskData;
|
||||||
import org.sleuthkit.datamodel.VolumeSystem;
|
import org.sleuthkit.datamodel.VolumeSystem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An action that displays the context for some content by expanding the data
|
* An action that displays the context for some content by expanding the data
|
||||||
* sources branch of the tree view to the level of the parent of the content,
|
* sources branch of the tree view to the level of the parent of the content,
|
||||||
* selecting the parent in the tree view, then selecting the content in the
|
* selecting the parent in the tree view, then selecting the content in the
|
||||||
* results view. This is commonly called "view file in directory."
|
* results view.
|
||||||
*/
|
*/
|
||||||
public final class ViewContextAction extends AbstractAction {
|
public final class ViewContextAction extends AbstractAction {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private static final long ROOT_DIR_META_ADDR = 5L;
|
|
||||||
private static final Logger logger = Logger.getLogger(ViewContextAction.class.getName());
|
private static final Logger logger = Logger.getLogger(ViewContextAction.class.getName());
|
||||||
private Content content;
|
private final Content content;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An action that displays the context for the source content of an artifact
|
* An action that displays the context for the source content of an artifact
|
||||||
* by expanding the data sources branch of the tree view to the level of the
|
* by expanding the data sources branch of the tree view to the level of the
|
||||||
* parent of the content, selecting the parent in the tree view, then
|
* parent of the content, selecting the parent in the tree view, then
|
||||||
* selecting the content in the results view. This is commonly called "view
|
* selecting the content in the results view.
|
||||||
* file in directory."
|
|
||||||
*
|
*
|
||||||
* @param displayName The display name for the action.
|
* @param displayName The display name for the action.
|
||||||
* @param artifactNode The artifact node for the artifact.
|
* @param artifactNode The artifact node for the artifact.
|
||||||
@ -74,14 +71,20 @@ public final class ViewContextAction extends AbstractAction {
|
|||||||
public ViewContextAction(String displayName, BlackboardArtifactNode artifactNode) {
|
public ViewContextAction(String displayName, BlackboardArtifactNode artifactNode) {
|
||||||
super(displayName);
|
super(displayName);
|
||||||
this.content = artifactNode.getLookup().lookup(Content.class);
|
this.content = artifactNode.getLookup().lookup(Content.class);
|
||||||
|
if (content instanceof AbstractFile) {
|
||||||
|
AbstractFile file = (AbstractFile) content;
|
||||||
|
if ((TskData.FileKnown.KNOWN == file.getKnown() && UserPreferences.hideKnownFilesInDataSourcesTree())
|
||||||
|
|| (TskData.TSK_DB_FILES_TYPE_ENUM.SLACK == file.getType() && UserPreferences.hideSlackFilesInDataSourcesTree())) {
|
||||||
|
this.setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An action that displays the context for some file system content by
|
* An action that displays the context for some file system content by
|
||||||
* expanding the data sources branch of the tree view to the level of the
|
* expanding the data sources branch of the tree view to the level of the
|
||||||
* parent of the content, selecting the parent in the tree view, then
|
* parent of the content, selecting the parent in the tree view, then
|
||||||
* selecting the content in the results view. This is commonly called "view
|
* selecting the content in the results view.
|
||||||
* file in directory."
|
|
||||||
*
|
*
|
||||||
* @param displayName The display name for the action.
|
* @param displayName The display name for the action.
|
||||||
* @param fileSystemContentNode The file system content node for the
|
* @param fileSystemContentNode The file system content node for the
|
||||||
@ -96,8 +99,7 @@ public final class ViewContextAction extends AbstractAction {
|
|||||||
* An action that displays the context for some content by expanding the
|
* An action that displays the context for some content by expanding the
|
||||||
* data sources branch of the tree view to the level of the parent of the
|
* data sources branch of the tree view to the level of the parent of the
|
||||||
* content, selecting the parent in the tree view, then selecting the
|
* content, selecting the parent in the tree view, then selecting the
|
||||||
* content in the results view. This is commonly called "view file in
|
* content in the results view.
|
||||||
* directory."
|
|
||||||
*
|
*
|
||||||
* @param displayName The display name for the action.
|
* @param displayName The display name for the action.
|
||||||
* @param content The content.
|
* @param content The content.
|
||||||
@ -108,259 +110,153 @@ public final class ViewContextAction extends AbstractAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked when the action occurs.
|
* Displays the context for some content by expanding the data sources
|
||||||
|
* branch of the tree view to the level of the parent of the content,
|
||||||
|
* selecting the parent in the tree view, then selecting the content in the
|
||||||
|
* results view.
|
||||||
*
|
*
|
||||||
* @param event
|
* @param event The action event.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent event) {
|
public void actionPerformed(ActionEvent event) {
|
||||||
/*
|
|
||||||
* Ensure that the action manipulates the tree view in the GUI event
|
|
||||||
* thread.
|
|
||||||
*/
|
|
||||||
EventQueue.invokeLater(() -> {
|
EventQueue.invokeLater(() -> {
|
||||||
/*
|
/*
|
||||||
* Create a flattened copy of the branch of the tree view that leads
|
* Get the "Data Sources" node from the tree view.
|
||||||
* to the specified content, starting with the data source of the
|
|
||||||
* content, which actually could be the content itself. Note that
|
|
||||||
* the "dummy" root node used to create the branch needs to be
|
|
||||||
* wrapped in a DirectoryTreeFilterNode so that its child nodes will
|
|
||||||
* also be wrapped in DirectoryTreeFilterNodes via
|
|
||||||
* DirectoryTreeFilterNodeChildren. Otherwise, the display names of
|
|
||||||
* the nodes in the branch will not have child node counts, and will
|
|
||||||
* not match the display names of the corresponding nodes in the
|
|
||||||
* actual tree view.
|
|
||||||
*/
|
|
||||||
LeafToRootContentBranchVisitor branchBuilder = new LeafToRootContentBranchVisitor();
|
|
||||||
List<Content> branch = content.accept(branchBuilder);
|
|
||||||
Collections.reverse(branch);
|
|
||||||
Node dummyRootNode = new DirectoryTreeFilterNode(new AbstractNode(new RootContentChildren(branch)), true);
|
|
||||||
Children branchChildren = dummyRootNode.getChildren();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Use the flattened copy of the branch of the tree view that leads
|
|
||||||
* to the specified content to do a depth-first search for the
|
|
||||||
* parent node of the content in the actual tree view, starting from
|
|
||||||
* the tree view's "Data Sources" node.
|
|
||||||
*/
|
*/
|
||||||
DirectoryTreeTopComponent treeViewTopComponent = DirectoryTreeTopComponent.findInstance();
|
DirectoryTreeTopComponent treeViewTopComponent = DirectoryTreeTopComponent.findInstance();
|
||||||
TreeView treeView = treeViewTopComponent.getTree();
|
|
||||||
ExplorerManager treeViewExplorerMgr = treeViewTopComponent.getExplorerManager();
|
ExplorerManager treeViewExplorerMgr = treeViewTopComponent.getExplorerManager();
|
||||||
Node treeViewRootNode = treeViewExplorerMgr.getRootContext();
|
Node parentTreeViewNode = treeViewExplorerMgr.getRootContext().getChildren().findChild(DataSourcesNode.NAME);
|
||||||
Children treeViewRootNodeChildren = treeViewRootNode.getChildren();
|
|
||||||
Node dataSourcesNode = treeViewRootNodeChildren.findChild(DataSourcesNode.NAME);
|
/*
|
||||||
Children currentTreeViewNodeChildren = dataSourcesNode.getChildren();
|
* Get the parent content for the content to be selected in the
|
||||||
Node contentParentNode = null;
|
* results view. If the parent content is null, then the specified
|
||||||
for (int i = 0; i < branchChildren.getNodesCount(); i++) {
|
* content is a data source, and the parent tree view node is the
|
||||||
Node currentBranchNode = branchChildren.getNodeAt(i);
|
* "Data Sources" node. Otherwise, the tree view needs to be
|
||||||
for (int j = 0; j < currentTreeViewNodeChildren.getNodesCount(); j++) {
|
* searched to find the parent treeview node.
|
||||||
Node currentTreeViewNode = currentTreeViewNodeChildren.getNodeAt(j);
|
*/
|
||||||
if (currentBranchNode.getDisplayName().equals(currentTreeViewNode.getDisplayName())) {
|
Content parentContent = null;
|
||||||
contentParentNode = currentTreeViewNode;
|
try {
|
||||||
treeView.expandNode(contentParentNode);
|
parentContent = content.getParent();
|
||||||
currentTreeViewNodeChildren = currentTreeViewNode.getChildren();
|
} catch (TskCoreException ex) {
|
||||||
break;
|
// RJCTODO: Pop up
|
||||||
|
logger.log(Level.SEVERE, String.format("Could not get parent of Content object: %s", content), ex); //NON-NLS
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (null != parentContent) {
|
||||||
|
/*
|
||||||
|
* Get an ordered list of the ancestors of the specified
|
||||||
|
* content, starting with its data source.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
AncestorVisitor ancestorVisitor = new AncestorVisitor();
|
||||||
|
List<Content> contentBranch = parentContent.accept(ancestorVisitor);
|
||||||
|
Collections.reverse(contentBranch);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the list of ancestors into a list of tree nodes.
|
||||||
|
*
|
||||||
|
* IMPORTANT: The "dummy" root node used to create this single
|
||||||
|
* layer of children needs to be wrapped in a
|
||||||
|
* DirectoryTreeFilterNode so that its child nodes will also be
|
||||||
|
* wrapped in DirectoryTreeFilterNodes, via
|
||||||
|
* DirectoryTreeFilterNodeChildren. Otherwise, the display names
|
||||||
|
* of the nodes in the branch will not have child node counts
|
||||||
|
* and will not match the display names of the corresponding
|
||||||
|
* nodes in the actual tree view.
|
||||||
|
*/
|
||||||
|
Node dummyRootNode = new DirectoryTreeFilterNode(new AbstractNode(new RootContentChildren(contentBranch)), true);
|
||||||
|
Children ancestorChildren = dummyRootNode.getChildren();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Search the tree for the parent node. Note that this algorithm
|
||||||
|
* simply discards "extra" ancestor nodes not shown in the tree,
|
||||||
|
* such as the root directory of the file system for file system
|
||||||
|
* content.
|
||||||
|
*/
|
||||||
|
Children treeNodeChildren = parentTreeViewNode.getChildren();
|
||||||
|
for (int i = 0; i < ancestorChildren.getNodesCount(); i++) {
|
||||||
|
Node ancestorNode = ancestorChildren.getNodeAt(i);
|
||||||
|
for (int j = 0; j < treeNodeChildren.getNodesCount(); j++) {
|
||||||
|
Node treeNode = treeNodeChildren.getNodeAt(j);
|
||||||
|
if (ancestorNode.getDisplayName().equals(treeNode.getDisplayName())) {
|
||||||
|
parentTreeViewNode = treeNode;
|
||||||
|
treeNodeChildren = treeNode.getChildren();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (null == contentParentNode) {
|
|
||||||
logger.log(Level.SEVERE, "Failed to find the parent node of Content node to be selected in the results view in the tree view"); //NON-NLS
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (branchChildren.getNodesCount() != 1) {
|
|
||||||
DirectoryTreeFilterNode contentParentFilterNode = (DirectoryTreeFilterNode) contentParentNode;
|
|
||||||
DirectoryTreeFilterNode.OriginalNode decoratedNodeHolder = contentParentFilterNode.getLookup().lookup(DirectoryTreeFilterNode.OriginalNode.class);
|
|
||||||
if (decoratedNodeHolder == null) {
|
|
||||||
logger.log(Level.SEVERE, "Failed to extract decorated node holder of the DirectoryTreeFilterNode decorator of the parent node of Content node to be selected in the results view"); //NON-NLS
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Node originNode = decoratedNodeHolder.getNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Select the parent node of content node to be selected in the
|
* Set the child selection info of the parent tree node, then select
|
||||||
* results view in the tree view.
|
* the parent node in the tree view. The results view will retrieve
|
||||||
|
* this selection info and use it to complete this action when the
|
||||||
|
* tree view top component responds to the selection of the parent
|
||||||
|
* node by pushing it into the results view top component.
|
||||||
*/
|
*/
|
||||||
|
DisplayableItemNode undecoratedParentNode = (DisplayableItemNode) ((DirectoryTreeFilterNode) parentTreeViewNode).getOriginal();
|
||||||
|
undecoratedParentNode.setChildNodeSelectionInfo(new ContentNodeSelectionInfo(content));
|
||||||
|
TreeView treeView = treeViewTopComponent.getTree();
|
||||||
|
treeView.expandNode(parentTreeViewNode);
|
||||||
try {
|
try {
|
||||||
treeView.expandNode(contentParentNode);
|
// RJCTODO: Pop up
|
||||||
treeViewExplorerMgr.setExploredContextAndSelection(contentParentNode, new Node[]{contentParentNode});
|
treeViewExplorerMgr.setExploredContextAndSelection(parentTreeViewNode, new Node[]{parentTreeViewNode});
|
||||||
} catch (PropertyVetoException ex) {
|
} catch (PropertyVetoException ex) {
|
||||||
logger.log(Level.SEVERE, "Failed to select the parent node of Content node to be selected in the results view in the tree view", ex); //NON-NLS
|
logger.log(Level.SEVERE, "Failed to select the parent node in the tree view", ex); //NON-NLS
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (branchChildren.getNodesCount() != 1) {
|
|
||||||
/*
|
|
||||||
* The target content is not a data source, queue another task
|
|
||||||
* for the GUI event thread to get the current root node of the
|
|
||||||
* result view top component, get the display name of the
|
|
||||||
* content node to be selected in the results view, and then
|
|
||||||
* dispatch a ResultViewNodeSelectionTask (a SwingWorker).
|
|
||||||
*
|
|
||||||
* TODO (JIRA-1655): This participates in a race condition.
|
|
||||||
*/
|
|
||||||
// EventQueue.invokeLater(() -> {
|
|
||||||
// DataResultTopComponent resultViewTopComponent = treeViewTopComponent.getDirectoryListing();
|
|
||||||
// Node currentRootNodeOfResultsView = resultViewTopComponent.getRootNode();
|
|
||||||
// Node contentNode = content.accept(new RootContentChildren.CreateSleuthkitNodeVisitor());
|
|
||||||
// new ResultViewNodeSelectionTask(resultViewTopComponent, contentNode.getName(), currentRootNodeOfResultsView).execute();
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a clone of this action.
|
* A ContentVisitor that returns a list of content objects by starting with
|
||||||
*
|
* a given content and following its chain of ancestors to the root content
|
||||||
* @return The cloned action.
|
* of the lineage.
|
||||||
*
|
|
||||||
* @throws CloneNotSupportedException Exception thrown if there is a problem
|
|
||||||
* creating the clone.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
private static class AncestorVisitor extends ContentVisitor.Default<List<Content>> {
|
||||||
public Object clone() throws CloneNotSupportedException {
|
|
||||||
ViewContextAction clone = (ViewContextAction) super.clone();
|
|
||||||
clone.setContent(this.content);
|
|
||||||
return clone;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
List<Content> lineage = new ArrayList<>();
|
||||||
* Sets the content object associated with the action.
|
|
||||||
*
|
|
||||||
* @param content A content object.
|
|
||||||
*/
|
|
||||||
private void setContent(Content content) {
|
|
||||||
this.content = content;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Waits for a Node's children to be generated, regardless of whether they
|
|
||||||
* are lazily loaded, then sets the correct selection in a specified
|
|
||||||
* DataResultTopComponent.
|
|
||||||
*/
|
|
||||||
private class ResultViewNodeSelectionTask extends SwingWorker<Node[], Integer> {
|
|
||||||
|
|
||||||
DataResultTopComponent resultViewTopComponent;
|
|
||||||
String nameOfNodeToSelect;
|
|
||||||
Node currentRootNodeOfResultsView;
|
|
||||||
|
|
||||||
ResultViewNodeSelectionTask(DataResultTopComponent resultViewTopComponent, String nameToSelect, Node currentRootNodeOfResultsView) {
|
|
||||||
this.resultViewTopComponent = resultViewTopComponent;
|
|
||||||
this.nameOfNodeToSelect = nameToSelect;
|
|
||||||
this.currentRootNodeOfResultsView = currentRootNodeOfResultsView;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Node[] doInBackground() throws Exception {
|
protected List<Content> defaultVisit(Content content) {
|
||||||
/*
|
lineage.add(content);
|
||||||
* Return all of the child nodes of the current root node of the
|
Content parent = null;
|
||||||
* results view. Note that calls to Children.getNodes(true) block
|
|
||||||
* until all child nodes have been created, regardless of whether
|
|
||||||
* they are created lazily). There are two ideas here: 1) avoid
|
|
||||||
* getting a proxy "wait" node, and 2) do this in the background to
|
|
||||||
* avoid monopolizing the EDT for this potentially lengthy
|
|
||||||
* operation.
|
|
||||||
*
|
|
||||||
* RJCTODO: Is this all true? What if the user selects another node
|
|
||||||
* in the tree while this is going on?
|
|
||||||
*/
|
|
||||||
return currentRootNodeOfResultsView.getChildren().getNodes(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void done() {
|
|
||||||
Node[] nodesDisplayedInResultsView;
|
|
||||||
try {
|
try {
|
||||||
nodesDisplayedInResultsView = get();
|
parent = content.getParent();
|
||||||
} catch (InterruptedException | CancellationException | ExecutionException ex) {
|
} catch (TskCoreException ex) {
|
||||||
logger.log(Level.SEVERE, "ResultViewNodeSelectionTask failed to get nodes displayed in results view.", ex); //NON-NLS
|
logger.log(Level.SEVERE, String.format("Could not get parent of Content object: %s", content), ex); //NON-NLS
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// It is possible the user selected a different Node to be displayed
|
|
||||||
// in the DataResultViewer while the child Nodes were being generated.
|
|
||||||
// In that case, we don't want to set the selection because it the
|
|
||||||
// nodes returned from get() won't be in the DataResultTopComponent's
|
|
||||||
// ExplorerManager. If we did call setSelectedNodes, it would clear
|
|
||||||
// the current selection, which is not good.
|
|
||||||
if (resultViewTopComponent.getRootNode().equals(currentRootNodeOfResultsView) == false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the correct node to select from the nodes that are displayed
|
|
||||||
// in the data result viewer and set it as the selection of the
|
|
||||||
// DataResultTopComponent.
|
|
||||||
for (Node node : nodesDisplayedInResultsView) {
|
|
||||||
if (nameOfNodeToSelect.equals(node.getName())) {
|
|
||||||
resultViewTopComponent.requestActive();
|
|
||||||
resultViewTopComponent.setSelectedNodes(new Node[]{node});
|
|
||||||
DirectoryTreeTopComponent.getDefault().fireViewerComplete();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return parent == null ? lineage : parent.accept(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A ReverseHierarchyVisitor is a ContentVisitor that returns a list of
|
|
||||||
* content objects by starting with a given content and following its chain
|
|
||||||
* of ancestors to the root content of the hierarchy.
|
|
||||||
*/
|
|
||||||
private class LeafToRootContentBranchVisitor extends ContentVisitor.Default<List<Content>> {
|
|
||||||
|
|
||||||
List<Content> hierarchy = new ArrayList<>();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Content> visit(VolumeSystem volumeSystem) {
|
public List<Content> visit(VolumeSystem volumeSystem) {
|
||||||
/*
|
/*
|
||||||
* Volume systems are not shown in the tree view.
|
* Volume systems are not shown in the tree view. This is not
|
||||||
|
* strictly necesssary given the algorithm above, but it is a simple
|
||||||
|
* optimization.
|
||||||
*/
|
*/
|
||||||
return visitParentButDontAddMe(volumeSystem);
|
return skipToParent(volumeSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Content> visit(FileSystem fileSystem) {
|
public List<Content> visit(FileSystem fileSystem) {
|
||||||
/*
|
/*
|
||||||
* File systems are not shown in the tree view.
|
* File systems are not shown in the tree view. This is not strictly
|
||||||
|
* necesssary given the algorithm above, but it is a simple
|
||||||
|
* optimization.
|
||||||
*/
|
*/
|
||||||
return visitParentButDontAddMe(fileSystem);
|
return skipToParent(fileSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private List<Content> skipToParent(Content content) {
|
||||||
public List<Content> visit(Directory directory) {
|
|
||||||
/*
|
|
||||||
* File system root directories are not shown in the tree view.
|
|
||||||
*/
|
|
||||||
if (ROOT_DIR_META_ADDR == directory.getMetaAddr()) {
|
|
||||||
return visitParentButDontAddMe(directory);
|
|
||||||
} else {
|
|
||||||
return defaultVisit(directory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<Content> defaultVisit(Content content) {
|
|
||||||
hierarchy.add(content);
|
|
||||||
Content parent = null;
|
Content parent = null;
|
||||||
try {
|
try {
|
||||||
parent = content.getParent();
|
parent = content.getParent();
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
logger.log(Level.SEVERE, String.format("Could not get parent of Content object: %s", content), ex); //NON-NLS
|
logger.log(Level.SEVERE, String.format("Could not get parent of Content object: %s", content), ex); //NON-NLS
|
||||||
}
|
}
|
||||||
return parent == null ? hierarchy : parent.accept(this);
|
return parent == null ? lineage : parent.accept(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Content> visitParentButDontAddMe(Content content) {
|
|
||||||
Content parent = null;
|
|
||||||
try {
|
|
||||||
parent = content.getParent();
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
logger.log(Level.SEVERE, String.format("Could not get parent of Content object: %s", content), ex); //NON-NLS
|
|
||||||
}
|
|
||||||
return parent == null ? hierarchy : parent.accept(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user