From 08eef357b155a0044c96d1f83da5e57661f8ff5f Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 5 May 2017 11:17:48 +0200 Subject: [PATCH] move persistence related code to separate class, begin work on sorting thumbnail based on persisted info --- .../corecomponents/DataResultViewerTable.java | 207 ++++++------------ .../DataResultViewerThumbnail.java | 2 +- .../ResultViewerPersistence.java | 80 +++++++ .../corecomponents/ThumbnailViewChildren.java | 175 ++++++++++++++- .../corecomponents/ThumbnailViewNode.java | 128 ----------- 5 files changed, 319 insertions(+), 273 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/corecomponents/ResultViewerPersistence.java delete mode 100644 Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index 064393111b..f3dc086bf5 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -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> 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> props = loadColumnOrder(); + List> 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> 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 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> of the preferences in order */ - private synchronized List> loadColumnOrder() { - // This is a set because we add properties of up to 100 child nodes, and we want unique properties - Set> propertiesAcc = new LinkedHashSet<>(); - this.getAllChildPropertyHeadersRec(currentRoot, 100, propertiesAcc); - - List> props = new ArrayList<>(propertiesAcc); + private synchronized List> loadColumnOrder(Node Node, Map> propMap) { + List> 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) { - } } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index 63d47f8a81..2fbba03ff2 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -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); } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ResultViewerPersistence.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ResultViewerPersistence.java new file mode 100644 index 0000000000..3e59053835 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ResultViewerPersistence.java @@ -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> getAllChildProperties(Node Node) { + // This is a set because we add properties of up to 100 child nodes, and we want unique properties + Set> 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> 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); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java index bb16323625..9eb72bad00 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-15 Basis Technology Corp. + * Copyright 2011-17 Basis Technology Corp. * Contact: carrier sleuthkit 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 { + 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> 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 { suppContent.add(child); } } - + //sort suppContent! + Collections.sort(suppContent, loadSort()); if (totalImages == 0) { return; } @@ -119,6 +144,55 @@ class ThumbnailViewChildren extends Children.Keys { setKeys(pageNums); } + private synchronized Comparator loadSort() { + Comparator comp = (node1, node2) -> 0; + + if (!(parent instanceof TableFilterNode)) { + return comp; + } else { + List> properties = ResultViewerPersistence.getAllChildProperties(parent); + final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); + TableFilterNode tfn = (TableFilterNode) parent; + + java.util.Map orderMap = new TreeMap<>(); + java.util.Map> 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 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 { 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 { 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 iconCache = null; + private int iconSize = ImageUtils.ICON_SIZE_MEDIUM; + private SwingWorker 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() { + 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 { } } - //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 { //wrapped original nodes diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java deleted file mode 100644 index 8ca68b5689..0000000000 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011-16 Basis Technology Corp. - * Contact: carrier sleuthkit 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 iconCache = null; - private int iconSize = ImageUtils.ICON_SIZE_MEDIUM; - - private SwingWorker 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() { - 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; - } -}