mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-17 10:17:41 +00:00
move persistence related code to separate class, begin work on sorting thumbnail based on persisted info
This commit is contained in:
parent
828323edbe
commit
08eef357b1
@ -26,14 +26,11 @@ import java.awt.Graphics;
|
||||
import java.awt.dnd.DnDConstants;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.prefs.Preferences;
|
||||
import javax.swing.JTable;
|
||||
@ -47,17 +44,15 @@ import javax.swing.table.TableCellRenderer;
|
||||
import org.netbeans.swing.etable.ETableColumn;
|
||||
import org.netbeans.swing.outline.DefaultOutlineCellRenderer;
|
||||
import org.netbeans.swing.outline.DefaultOutlineModel;
|
||||
import org.netbeans.swing.outline.Outline;
|
||||
import org.openide.explorer.ExplorerManager;
|
||||
import org.openide.explorer.view.OutlineView;
|
||||
import org.openide.nodes.AbstractNode;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.nodes.Node.Property;
|
||||
import org.openide.nodes.Node.PropertySet;
|
||||
import org.openide.nodes.NodeEvent;
|
||||
import org.openide.nodes.NodeListener;
|
||||
import org.openide.nodes.NodeAdapter;
|
||||
import org.openide.nodes.NodeMemberEvent;
|
||||
import org.openide.nodes.NodeReorderEvent;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbPreferences;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
|
||||
@ -90,7 +85,11 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
// the column started and where it ended up.
|
||||
private int startColumnIndex = -1;
|
||||
private int endColumnIndex = -1;
|
||||
private OutlineView ov;
|
||||
private OutlineView outlineView;
|
||||
/**
|
||||
* Convenience reference to internal Outline
|
||||
*/
|
||||
private Outline outline;
|
||||
|
||||
/**
|
||||
* Creates a DataResultViewerTable object that is compatible with node
|
||||
@ -114,17 +113,18 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
private void initialize() {
|
||||
initComponents();
|
||||
|
||||
ov = ((OutlineView) this.tableScrollPanel);
|
||||
ov.setAllowedDragActions(DnDConstants.ACTION_NONE);
|
||||
outlineView = ((OutlineView) this.tableScrollPanel);
|
||||
outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE);
|
||||
outline = outlineView.getOutline();
|
||||
|
||||
ov.getOutline().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
||||
outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
||||
|
||||
// don't show the root node
|
||||
ov.getOutline().setRootVisible(false);
|
||||
ov.getOutline().setDragEnabled(false);
|
||||
outline.setRootVisible(false);
|
||||
outline.setDragEnabled(false);
|
||||
|
||||
// add a listener so that when columns are moved, the new order is stored
|
||||
ov.getOutline().getColumnModel().addColumnModelListener(new TableColumnModelListener() {
|
||||
outline.getColumnModel().addColumnModelListener(new TableColumnModelListener() {
|
||||
@Override
|
||||
public void columnAdded(TableColumnModelEvent e) {
|
||||
}
|
||||
@ -200,7 +200,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
});
|
||||
|
||||
// add a listener to move columns back if user tries to move the first column out of place
|
||||
ov.getOutline().getTableHeader().addMouseListener(new MouseAdapter() {
|
||||
outline.getTableHeader().addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
/*
|
||||
@ -215,7 +215,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
* without having moved any columns.
|
||||
*/
|
||||
if (startColumnIndex != -1 && (startColumnIndex == 0 || endColumnIndex == 0)) {
|
||||
ov.getOutline().moveColumn(endColumnIndex, startColumnIndex);
|
||||
outline.moveColumn(endColumnIndex, startColumnIndex);
|
||||
}
|
||||
startColumnIndex = -1;
|
||||
}
|
||||
@ -278,33 +278,6 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
private javax.swing.JScrollPane tableScrollPanel;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
|
||||
/**
|
||||
* Gets regular Bean property set properties from all children and,
|
||||
* recursively, subchildren of Node. Note: won't work out the box for lazy
|
||||
* load - you need to set all children props for the parent by hand
|
||||
*
|
||||
* @param parent Node with at least one child to get properties from
|
||||
* @param rows max number of rows to retrieve properties for (can be used
|
||||
* for memory optimization)
|
||||
*/
|
||||
private void getAllChildPropertyHeadersRec(Node parent, int rows, Set<Property<?>> propertiesAcc) {
|
||||
Children children = parent.getChildren();
|
||||
int childCount = 0;
|
||||
for (Node child : children.getNodes()) {
|
||||
if (++childCount > rows) {
|
||||
return;
|
||||
}
|
||||
for (PropertySet ps : child.getPropertySets()) {
|
||||
final Property<?>[] props = ps.getProperties();
|
||||
final int propsNum = props.length;
|
||||
for (int j = 0; j < propsNum; ++j) {
|
||||
propertiesAcc.add(props[j]);
|
||||
}
|
||||
}
|
||||
getAllChildPropertyHeadersRec(child, rows, propertiesAcc);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported(Node selectedNode) {
|
||||
return true;
|
||||
@ -318,7 +291,6 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
*/
|
||||
@Override
|
||||
public void setNode(Node selectedNode) {
|
||||
final OutlineView ov = ((OutlineView) this.tableScrollPanel);
|
||||
/*
|
||||
* The quick filter must be reset because when determining column width,
|
||||
* ETable.getRowCount is called, and the documentation states that quick
|
||||
@ -326,7 +298,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
* applied the number of rows do not match the number of rows in the
|
||||
* model."
|
||||
*/
|
||||
ov.getOutline().unsetQuickFilter();
|
||||
outline.unsetQuickFilter();
|
||||
// change the cursor to "waiting cursor" for this operation
|
||||
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
try {
|
||||
@ -350,8 +322,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
} else {
|
||||
Node emptyNode = new AbstractNode(Children.LEAF);
|
||||
em.setRootContext(emptyNode); // make empty node
|
||||
ov.getOutline().setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
|
||||
ov.setPropertyColumns(); // set the empty property header
|
||||
outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
|
||||
outlineView.setPropertyColumns(); // set the empty property header
|
||||
}
|
||||
} finally {
|
||||
this.setCursor(null);
|
||||
@ -367,13 +339,9 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
private void setupTable(final Node root) {
|
||||
|
||||
em.setRootContext(root);
|
||||
final OutlineView ov = ((OutlineView) this.tableScrollPanel);
|
||||
|
||||
if (ov == null) {
|
||||
return;
|
||||
}
|
||||
currentRoot = root;
|
||||
List<Node.Property<?>> props = loadColumnOrder();
|
||||
List<Node.Property<?>> props = loadColumnOrder(currentRoot, propertiesMap);
|
||||
|
||||
/**
|
||||
* OutlineView makes the first column be the result of
|
||||
@ -388,7 +356,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
*/
|
||||
if (props.size() > 0) {
|
||||
Node.Property<?> prop = props.remove(0);
|
||||
((DefaultOutlineModel) ov.getOutline().getOutlineModel()).setNodesColumnLabel(prop.getDisplayName());
|
||||
((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(prop.getDisplayName());
|
||||
}
|
||||
|
||||
// Get the columns setup with respect to names and sortability
|
||||
@ -404,46 +372,46 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
propStrings[2 * i + 1] = props.get(i).getDisplayName();
|
||||
}
|
||||
|
||||
ov.setPropertyColumns(propStrings);
|
||||
outlineView.setPropertyColumns(propStrings);
|
||||
|
||||
// show the horizontal scroll panel and show all the content & header
|
||||
// If there is only one column (which was removed from props above)
|
||||
// Just let the table resize itself.
|
||||
ov.getOutline().setAutoResizeMode((props.size() > 0) ? JTable.AUTO_RESIZE_OFF : JTable.AUTO_RESIZE_ALL_COLUMNS);
|
||||
outline.setAutoResizeMode((props.size() > 0) ? JTable.AUTO_RESIZE_OFF : JTable.AUTO_RESIZE_ALL_COLUMNS);
|
||||
|
||||
if (root.getChildren().getNodesCount() != 0) {
|
||||
final Graphics graphics = ov.getGraphics();
|
||||
final Graphics graphics = outlineView.getGraphics();
|
||||
if (graphics != null) {
|
||||
final FontMetrics metrics = graphics.getFontMetrics();
|
||||
|
||||
int margin = 4;
|
||||
int padding = 8;
|
||||
|
||||
for (int column = 0; column < ov.getOutline().getModel().getColumnCount(); column++) {
|
||||
for (int column = 0; column < outline.getModel().getColumnCount(); column++) {
|
||||
int firstColumnPadding = (column == 0) ? 32 : 0;
|
||||
int columnWidthLimit = (column == 0) ? 350 : 300;
|
||||
int valuesWidth = 0;
|
||||
|
||||
// find the maximum width needed to fit the values for the first 100 rows, at most
|
||||
for (int row = 0; row < Math.min(100, ov.getOutline().getRowCount()); row++) {
|
||||
TableCellRenderer renderer = ov.getOutline().getCellRenderer(row, column);
|
||||
Component comp = ov.getOutline().prepareRenderer(renderer, row, column);
|
||||
for (int row = 0; row < Math.min(100, outline.getRowCount()); row++) {
|
||||
TableCellRenderer renderer = outline.getCellRenderer(row, column);
|
||||
Component comp = outline.prepareRenderer(renderer, row, column);
|
||||
valuesWidth = Math.max(comp.getPreferredSize().width, valuesWidth);
|
||||
}
|
||||
|
||||
int headerWidth = metrics.stringWidth(ov.getOutline().getColumnName(column));
|
||||
int headerWidth = metrics.stringWidth(outline.getColumnName(column));
|
||||
valuesWidth += firstColumnPadding; // add extra padding for first column
|
||||
|
||||
int columnWidth = Math.max(valuesWidth, headerWidth);
|
||||
columnWidth += 2 * margin + padding; // add margin and regular padding
|
||||
columnWidth = Math.min(columnWidth, columnWidthLimit);
|
||||
|
||||
ov.getOutline().getColumnModel().getColumn(column).setPreferredWidth(columnWidth);
|
||||
outline.getColumnModel().getColumn(column).setPreferredWidth(columnWidth);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if there's no content just auto resize all columns
|
||||
ov.getOutline().setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
|
||||
outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
|
||||
}
|
||||
|
||||
loadSort();
|
||||
@ -490,7 +458,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
return component;
|
||||
}
|
||||
}
|
||||
ov.getOutline().setDefaultRenderer(Object.class, new ColorTagCustomRenderer());
|
||||
outline.setDefaultRenderer(Object.class, new ColorTagCustomRenderer());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -505,18 +473,22 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
|
||||
// Store the current order of the columns into settings
|
||||
for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
|
||||
preferences.put(getColumnPositionKey(tfn.getColumnOrderKey(), entry.getValue().getName()), String.valueOf(entry.getKey()));
|
||||
String columnPositionKey = ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName());
|
||||
preferences.put(columnPositionKey, String.valueOf(entry.getKey()));
|
||||
}
|
||||
|
||||
int numCols = ov.getOutline().getColumnModel().getColumnCount();
|
||||
int numCols = outlineView.getOutline().getColumnModel().getColumnCount();
|
||||
for (int i = 0; i < numCols; i++) {
|
||||
ETableColumn etc = (ETableColumn) ov.getOutline().getColumnModel().getColumn(i);
|
||||
ETableColumn etc = (ETableColumn) outlineView.getOutline().getColumnModel().getColumn(i);
|
||||
String columnIdentifier = etc.getIdentifier().toString();
|
||||
String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnIdentifier);
|
||||
String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, columnIdentifier);
|
||||
if (etc.isSorted()) {
|
||||
preferences.put(getColumnSortOrderKey(tfn.getColumnOrderKey(), etc.getIdentifier().toString()), String.valueOf(etc.isAscending()));
|
||||
preferences.put(getColumnSortRankKey(tfn.getColumnOrderKey(), etc.getIdentifier().toString()), String.valueOf(etc.getSortRank()));
|
||||
preferences.put(columnSortOrderKey, String.valueOf(etc.isAscending()));
|
||||
preferences.put(columnSortRankKey, String.valueOf(etc.getSortRank()));
|
||||
} else {
|
||||
preferences.remove(getColumnSortOrderKey(tfn.getColumnOrderKey(), etc.getIdentifier().toString()));
|
||||
preferences.remove(getColumnSortRankKey(tfn.getColumnOrderKey(), etc.getIdentifier().toString()));
|
||||
preferences.remove(columnSortOrderKey);
|
||||
preferences.remove(columnSortRankKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -535,15 +507,21 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
Map<Integer, Integer> indexMap = new TreeMap<>();
|
||||
|
||||
propertiesMap.entrySet().forEach((entry) -> {
|
||||
String propName = entry.getValue().getName();
|
||||
String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, propName);
|
||||
String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, propName);
|
||||
|
||||
//if the sort rank is undefined, it will be defaulted to 0 => unsorted.
|
||||
Integer sortRank = Integer.valueOf(preferences.get(getColumnSortRankKey(tfn.getColumnOrderKey(), entry.getValue().getName()), "0"));
|
||||
Boolean sortOrder = Boolean.valueOf(preferences.get(getColumnSortOrderKey(tfn.getColumnOrderKey(), entry.getValue().getName()), "true"));
|
||||
Integer sortRank = Integer.valueOf(preferences.get(columnSortRankKey, "0"));
|
||||
|
||||
indexMap.put(sortRank, outline.getColumn(propName).getModelIndex());
|
||||
Boolean sortOrder = Boolean.valueOf(preferences.get(columnSortOrderKey, "true"));
|
||||
|
||||
orderMap.put(sortRank, sortOrder);
|
||||
indexMap.put(sortRank, ov.getOutline().getColumn(entry.getValue().getName()).getModelIndex());
|
||||
});
|
||||
|
||||
orderMap.entrySet().forEach((entry) -> {
|
||||
ov.getOutline().setColumnSorted(indexMap.get(entry.getKey()), orderMap.get(entry.getKey()), entry.getKey());
|
||||
outline.setColumnSorted(indexMap.get(entry.getKey()), orderMap.get(entry.getKey()), entry.getKey());
|
||||
});
|
||||
}
|
||||
|
||||
@ -554,23 +532,19 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
*
|
||||
* @return a List<Node.Property<?>> of the preferences in order
|
||||
*/
|
||||
private synchronized List<Node.Property<?>> loadColumnOrder() {
|
||||
// This is a set because we add properties of up to 100 child nodes, and we want unique properties
|
||||
Set<Property<?>> propertiesAcc = new LinkedHashSet<>();
|
||||
this.getAllChildPropertyHeadersRec(currentRoot, 100, propertiesAcc);
|
||||
|
||||
List<Node.Property<?>> props = new ArrayList<>(propertiesAcc);
|
||||
private synchronized List<Node.Property<?>> loadColumnOrder(Node Node, Map<Integer, Property<?>> propMap) {
|
||||
List<Property<?>> props = ResultViewerPersistence.getAllChildProperties(Node);
|
||||
|
||||
// If node is not table filter node, use default order for columns
|
||||
TableFilterNode tfn;
|
||||
if (currentRoot instanceof TableFilterNode) {
|
||||
tfn = (TableFilterNode) currentRoot;
|
||||
if (Node instanceof TableFilterNode) {
|
||||
tfn = (TableFilterNode) Node;
|
||||
} else {
|
||||
// The node is not a TableFilterNode, columns are going to be in default order
|
||||
return props;
|
||||
}
|
||||
|
||||
propertiesMap.clear();
|
||||
propMap.clear();
|
||||
/*
|
||||
* We load column index values into the properties map. If a property's
|
||||
* index is outside the range of the number of properties or the index
|
||||
@ -579,13 +553,15 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
*/
|
||||
int offset = props.size();
|
||||
boolean noPreviousSettings = true;
|
||||
final Preferences preferences = NbPreferences.forModule(this.getClass());
|
||||
for (Property<?> prop : props) {
|
||||
Integer value = Integer.valueOf(NbPreferences.forModule(this.getClass()).get(getColumnPositionKey(tfn.getColumnOrderKey(), prop.getName()), "-1"));
|
||||
if (value >= 0 && value < offset && !propertiesMap.containsKey(value)) {
|
||||
propertiesMap.put(value, prop);
|
||||
final String columnPositionKey = ResultViewerPersistence.getColumnPositionKey(tfn, prop.getName());
|
||||
Integer value = Integer.valueOf(preferences.get(columnPositionKey, "-1"));
|
||||
if (value >= 0 && value < offset && !propMap.containsKey(value)) {
|
||||
propMap.put(value, prop);
|
||||
noPreviousSettings = false;
|
||||
} else {
|
||||
propertiesMap.put(offset, prop);
|
||||
propMap.put(offset, prop);
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
@ -593,43 +569,14 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
// If none of the properties had previous settings, we should decrement
|
||||
// each value by the number of properties to make the values 0-indexed.
|
||||
if (noPreviousSettings) {
|
||||
Integer[] keys = propertiesMap.keySet().toArray(new Integer[propertiesMap.keySet().size()]);
|
||||
Integer[] keys = propMap.keySet().toArray(new Integer[propMap.keySet().size()]);
|
||||
for (int key : keys) {
|
||||
propertiesMap.put(key - props.size(), propertiesMap.get(key));
|
||||
propertiesMap.remove(key);
|
||||
propMap.put(key - props.size(), propMap.get(key));
|
||||
propMap.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
return new ArrayList<>(propertiesMap.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a key for the current node and a property of its child nodes to
|
||||
* store the column position into a preference file.
|
||||
*
|
||||
* @param prop Property of the column
|
||||
* @param type The type of the current node
|
||||
*
|
||||
* @return A generated key for the preference file
|
||||
*/
|
||||
private String getColumnPositionKey(String type, String propName) {
|
||||
return getColumnKeyBase(type, propName) + ".column";
|
||||
}
|
||||
|
||||
private String getColumnSortOrderKey(String type, String propName) {
|
||||
return getColumnKeyBase(type, propName) + ".sortOrder";
|
||||
}
|
||||
|
||||
private String getColumnSortRankKey(String type, String propName) {
|
||||
return getColumnKeyBase(type, propName) + ".sortRank";
|
||||
}
|
||||
|
||||
private static String getColumnKeyBase(String type, String propName) {
|
||||
return stripNonAlphanumeric(type) + "." + stripNonAlphanumeric(propName);
|
||||
}
|
||||
|
||||
private static String stripNonAlphanumeric(String str) {
|
||||
return str.replaceAll("[^a-zA-Z0-9_]", "");
|
||||
return new ArrayList<>(propMap.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -650,7 +597,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
super.clearComponent();
|
||||
}
|
||||
|
||||
private class PleasewaitNodeListener implements NodeListener {
|
||||
private class PleasewaitNodeListener extends NodeAdapter {
|
||||
|
||||
private volatile boolean load = true;
|
||||
|
||||
@ -681,21 +628,5 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void childrenRemoved(NodeMemberEvent nme) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void childrenReordered(NodeReorderEvent nre) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodeDestroyed(NodeEvent ne) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -272,7 +272,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer {
|
||||
|
||||
for (Node page : root.getChildren().getNodes()) {
|
||||
for (Node node : page.getChildren().getNodes()) {
|
||||
((ThumbnailViewNode) node).setIconSize(iconSize);
|
||||
((ThumbnailViewChildren.ThumbnailViewNode) node).setIconSize(iconSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.corecomponents;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Node;
|
||||
|
||||
final public class ResultViewerPersistence {
|
||||
|
||||
private ResultViewerPersistence() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a key for the current node and a property of its child nodes to
|
||||
* store the column position into a preference file.
|
||||
*
|
||||
*
|
||||
* @return A generated key for the preference file
|
||||
*/
|
||||
static String getColumnPositionKey(TableFilterNode node, String propName) {
|
||||
return getColumnKeyBase(node, propName) + ".column";
|
||||
}
|
||||
|
||||
static String getColumnSortOrderKey(TableFilterNode node, String propName) {
|
||||
return getColumnKeyBase(node, propName) + ".sortOrder";
|
||||
}
|
||||
|
||||
static String getColumnSortRankKey(TableFilterNode node, String propName) {
|
||||
return getColumnKeyBase(node, propName) + ".sortRank";
|
||||
}
|
||||
|
||||
private static String getColumnKeyBase(TableFilterNode node, String propName) {
|
||||
return stripNonAlphanumeric(node.getColumnOrderKey()) + "." + stripNonAlphanumeric(propName);
|
||||
}
|
||||
|
||||
private static String stripNonAlphanumeric(String str) {
|
||||
return str.replaceAll("[^a-zA-Z0-9_]", "");
|
||||
}
|
||||
|
||||
static List<Node.Property<?>> getAllChildProperties(Node Node) {
|
||||
// This is a set because we add properties of up to 100 child nodes, and we want unique properties
|
||||
Set<Node.Property<?>> propertiesAcc = new LinkedHashSet<>();
|
||||
getAllChildPropertyHeadersRec(Node, 100, propertiesAcc);
|
||||
return new ArrayList<>(propertiesAcc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets regular Bean property set properties from all children and,
|
||||
* recursively, subchildren of Node. Note: won't work out the box for lazy
|
||||
* load - you need to set all children props for the parent by hand
|
||||
*
|
||||
* @param parent Node with at least one child to get properties from
|
||||
* @param rows max number of rows to retrieve properties for (can be used
|
||||
* for memory optimization)
|
||||
*/
|
||||
static private void getAllChildPropertyHeadersRec(Node parent, int rows, Set<Node.Property<?>> propertiesAcc) {
|
||||
Children children = parent.getChildren();
|
||||
int childCount = 0;
|
||||
for (Node child : children.getNodes()) {
|
||||
if (++childCount > rows) {
|
||||
return;
|
||||
}
|
||||
for (Node.PropertySet ps : child.getPropertySets()) {
|
||||
final Node.Property<?>[] props = ps.getProperties();
|
||||
final int propsNum = props.length;
|
||||
for (int j = 0; j < propsNum; ++j) {
|
||||
propertiesAcc.add(props[j]);
|
||||
}
|
||||
}
|
||||
getAllChildPropertyHeadersRec(child, rows, propertiesAcc);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-15 Basis Technology Corp.
|
||||
* Copyright 2011-17 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,12 +18,32 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.corecomponents;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.prefs.Preferences;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.Timer;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.netbeans.api.progress.ProgressHandle;
|
||||
import org.openide.nodes.AbstractNode;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.FilterNode;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.util.Exceptions;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbPreferences;
|
||||
import org.openide.util.lookup.Lookups;
|
||||
import org.sleuthkit.autopsy.coreutils.ImageUtils;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
@ -41,16 +61,20 @@ import org.sleuthkit.datamodel.Content;
|
||||
*/
|
||||
class ThumbnailViewChildren extends Children.Keys<Integer> {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ThumbnailViewChildren.class.getName());
|
||||
|
||||
static final int IMAGES_PER_PAGE = 200;
|
||||
private Node parent;
|
||||
private final Node parent;
|
||||
private final HashMap<Integer, List<Node>> pages = new HashMap<>();
|
||||
private int totalImages = 0;
|
||||
private int totalPages = 0;
|
||||
private int iconSize = ImageUtils.ICON_SIZE_MEDIUM;
|
||||
private static final Logger logger = Logger.getLogger(ThumbnailViewChildren.class.getName());
|
||||
|
||||
/**
|
||||
* the constructor
|
||||
*
|
||||
* @param arg
|
||||
* @param iconSize
|
||||
*/
|
||||
ThumbnailViewChildren(Node arg, int iconSize) {
|
||||
super(true); //support lazy loading
|
||||
@ -89,7 +113,8 @@ class ThumbnailViewChildren extends Children.Keys<Integer> {
|
||||
suppContent.add(child);
|
||||
}
|
||||
}
|
||||
|
||||
//sort suppContent!
|
||||
Collections.sort(suppContent, loadSort());
|
||||
if (totalImages == 0) {
|
||||
return;
|
||||
}
|
||||
@ -119,6 +144,55 @@ class ThumbnailViewChildren extends Children.Keys<Integer> {
|
||||
setKeys(pageNums);
|
||||
}
|
||||
|
||||
private synchronized Comparator<Node> loadSort() {
|
||||
Comparator<Node> comp = (node1, node2) -> 0;
|
||||
|
||||
if (!(parent instanceof TableFilterNode)) {
|
||||
return comp;
|
||||
} else {
|
||||
List<Node.Property<?>> properties = ResultViewerPersistence.getAllChildProperties(parent);
|
||||
final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
|
||||
TableFilterNode tfn = (TableFilterNode) parent;
|
||||
|
||||
java.util.Map<Integer, Boolean> orderMap = new TreeMap<>();
|
||||
java.util.Map<Integer, Node.Property<?>> propMap = new TreeMap<>();
|
||||
|
||||
properties.forEach((prop) -> {
|
||||
//if the sort rank is undefined, it will be defaulted to 0 => unsorted.
|
||||
Integer sortRank = Integer.valueOf(preferences.get(ResultViewerPersistence.getColumnSortRankKey(tfn, prop.getName()), "0"));
|
||||
Boolean sortOrder = Boolean.valueOf(preferences.get(ResultViewerPersistence.getColumnSortOrderKey(tfn, prop.getName()), "true"));
|
||||
if (sortRank != 0) {
|
||||
orderMap.put(sortRank, sortOrder);
|
||||
propMap.put(sortRank, prop);
|
||||
}
|
||||
});
|
||||
|
||||
return propMap.keySet().stream()
|
||||
.map(rank -> {
|
||||
Comparator<Node> c = Comparator.nullsLast(Comparator.comparing(node -> getPropertyValue(node, propMap.get(rank))));
|
||||
return orderMap.get(rank) ? c : c.reversed();
|
||||
})
|
||||
.collect(Collectors.reducing(Comparator::thenComparing))
|
||||
.orElse(comp);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Comparable getPropertyValue(Node node, Node.Property<?> prop) {
|
||||
for (Node.PropertySet ps : node.getPropertySets()) {
|
||||
for (Node.Property<?> p : ps.getProperties()) {
|
||||
if (p.equals(prop)) {
|
||||
try {
|
||||
return (Comparable) p.getValue();
|
||||
} catch (IllegalAccessException | InvocationTargetException ex) {
|
||||
Exceptions.printStackTrace(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeNotify() {
|
||||
super.removeNotify();
|
||||
@ -130,11 +204,13 @@ class ThumbnailViewChildren extends Children.Keys<Integer> {
|
||||
protected Node[] createNodes(Integer pageNum) {
|
||||
final ThumbnailPageNode pageNode = new ThumbnailPageNode(pageNum);
|
||||
return new Node[]{pageNode};
|
||||
|
||||
}
|
||||
|
||||
static boolean isSupported(Node node) {
|
||||
if (node != null) {
|
||||
Content content = node.getLookup().lookup(Content.class);
|
||||
Content content = node.getLookup().lookup(Content.class
|
||||
);
|
||||
if (content != null) {
|
||||
return ImageUtils.thumbnailSupported(content);
|
||||
}
|
||||
@ -144,6 +220,93 @@ class ThumbnailViewChildren extends Children.Keys<Integer> {
|
||||
|
||||
public void setIconSize(int iconSize) {
|
||||
this.iconSize = iconSize;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Node that wraps around original node and adds the bitmap icon
|
||||
* representing the picture
|
||||
*/
|
||||
static class ThumbnailViewNode extends FilterNode {
|
||||
|
||||
private static final Image waitingIcon = Toolkit.getDefaultToolkit().createImage(ThumbnailViewNode.class.getResource("/org/sleuthkit/autopsy/images/working_spinner.gif"));
|
||||
private SoftReference<Image> iconCache = null;
|
||||
private int iconSize = ImageUtils.ICON_SIZE_MEDIUM;
|
||||
private SwingWorker<Image, Object> swingWorker;
|
||||
private Timer timer;
|
||||
|
||||
/**
|
||||
* the constructor
|
||||
*/
|
||||
ThumbnailViewNode(Node arg, int iconSize) {
|
||||
super(arg, Children.LEAF);
|
||||
this.iconSize = iconSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return StringUtils.abbreviate(super.getDisplayName(), 18);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages(value = {"# {0} - file name", "ThumbnailViewNode.progressHandle.text=Generating thumbnail for {0}"})
|
||||
public Image getIcon(int type) {
|
||||
Image icon = null;
|
||||
if (iconCache != null) {
|
||||
icon = iconCache.get();
|
||||
}
|
||||
if (icon != null) {
|
||||
return icon;
|
||||
} else {
|
||||
final Content content = this.getLookup().lookup(Content.class);
|
||||
if (content == null) {
|
||||
return ImageUtils.getDefaultThumbnail();
|
||||
}
|
||||
if (swingWorker == null || swingWorker.isDone()) {
|
||||
swingWorker = new SwingWorker<Image, Object>() {
|
||||
private final ProgressHandle progressHandle = ProgressHandle.createHandle(Bundle.ThumbnailViewNode_progressHandle_text(content.getName()));
|
||||
|
||||
@Override
|
||||
protected Image doInBackground() throws Exception {
|
||||
progressHandle.start();
|
||||
return ImageUtils.getThumbnail(content, iconSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
super.done();
|
||||
try {
|
||||
iconCache = new SoftReference<>(super.get());
|
||||
fireIconChange();
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
Logger.getLogger(ThumbnailViewNode.class.getName()).log(Level.SEVERE, "Error getting thumbnail icon for " + content.getName(), ex); //NON-NLS
|
||||
} finally {
|
||||
progressHandle.finish();
|
||||
if (timer != null) {
|
||||
timer.stop();
|
||||
timer = null;
|
||||
}
|
||||
swingWorker = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
swingWorker.execute();
|
||||
}
|
||||
if (timer == null) {
|
||||
timer = new Timer(100, (ActionEvent e) -> {
|
||||
fireIconChange();
|
||||
});
|
||||
timer.start();
|
||||
}
|
||||
return waitingIcon;
|
||||
}
|
||||
}
|
||||
|
||||
public void setIconSize(int iconSize) {
|
||||
this.iconSize = iconSize;
|
||||
iconCache = null;
|
||||
swingWorker = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -165,7 +328,7 @@ class ThumbnailViewChildren extends Children.Keys<Integer> {
|
||||
}
|
||||
}
|
||||
|
||||
//TODO insert node at beginning pressing which goes back to page view
|
||||
//TODO insert node at beginning pressing which goes back to page view
|
||||
private class ThumbnailPageNodeChildren extends Children.Keys<Node> {
|
||||
|
||||
//wrapped original nodes
|
||||
|
@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-16 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.Image;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.Timer;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.netbeans.api.progress.ProgressHandle;
|
||||
import org.openide.nodes.FilterNode;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.ImageUtils;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
|
||||
/**
|
||||
* Node that wraps around original node and adds the bitmap icon representing
|
||||
* the picture
|
||||
*/
|
||||
class ThumbnailViewNode extends FilterNode {
|
||||
|
||||
static private final Image waitingIcon = Toolkit.getDefaultToolkit().createImage(ThumbnailViewNode.class.getResource("/org/sleuthkit/autopsy/images/working_spinner.gif"));
|
||||
|
||||
private SoftReference<Image> iconCache = null;
|
||||
private int iconSize = ImageUtils.ICON_SIZE_MEDIUM;
|
||||
|
||||
private SwingWorker<Image, Object> swingWorker;
|
||||
private Timer timer;
|
||||
|
||||
/**
|
||||
* the constructor
|
||||
*/
|
||||
ThumbnailViewNode(Node arg, int iconSize) {
|
||||
super(arg, Children.LEAF);
|
||||
this.iconSize = iconSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return StringUtils.abbreviate(super.getDisplayName(), 18);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages({"# {0} - file name",
|
||||
"ThumbnailViewNode.progressHandle.text=Generating thumbnail for {0}"})
|
||||
public Image getIcon(int type) {
|
||||
Image icon = null;
|
||||
|
||||
if (iconCache != null) {
|
||||
icon = iconCache.get();
|
||||
}
|
||||
|
||||
if (icon != null) {
|
||||
return icon;
|
||||
} else {
|
||||
final Content content = this.getLookup().lookup(Content.class);
|
||||
if (content == null) {
|
||||
return ImageUtils.getDefaultThumbnail();
|
||||
}
|
||||
if (swingWorker == null || swingWorker.isDone()) {
|
||||
swingWorker = new SwingWorker<Image, Object>() {
|
||||
final private ProgressHandle progressHandle = ProgressHandle.createHandle(Bundle.ThumbnailViewNode_progressHandle_text(content.getName()));
|
||||
|
||||
@Override
|
||||
protected Image doInBackground() throws Exception {
|
||||
progressHandle.start();
|
||||
return ImageUtils.getThumbnail(content, iconSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
super.done();
|
||||
try {
|
||||
iconCache = new SoftReference<>(super.get());
|
||||
fireIconChange();
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
Logger.getLogger(ThumbnailViewNode.class.getName()).log(Level.SEVERE, "Error getting thumbnail icon for " + content.getName(), ex); //NON-NLS
|
||||
} finally {
|
||||
progressHandle.finish();
|
||||
if (timer != null) {
|
||||
timer.stop();
|
||||
timer = null;
|
||||
|
||||
}
|
||||
swingWorker = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
swingWorker.execute();
|
||||
}
|
||||
if (timer == null) {
|
||||
timer = new Timer(100, (ActionEvent e) -> {
|
||||
fireIconChange();
|
||||
});
|
||||
timer.start();
|
||||
}
|
||||
return waitingIcon;
|
||||
}
|
||||
}
|
||||
|
||||
public void setIconSize(int iconSize) {
|
||||
this.iconSize = iconSize;
|
||||
iconCache = null;
|
||||
swingWorker = null;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user