From e22c9d413b469d6357277114d02a6a60d1a50780 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 3 May 2017 18:32:41 +0200 Subject: [PATCH 1/3] persist multi column sorting in DataResultViewerTable --- .../corecomponents/DataResultViewerTable.java | 90 +++++++++++++++---- 1 file changed, 74 insertions(+), 16 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index 9bedaeeee0..a1e9bc746f 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -35,6 +35,7 @@ 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; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; @@ -43,6 +44,7 @@ import javax.swing.event.ListSelectionEvent; import javax.swing.event.TableColumnModelEvent; import javax.swing.event.TableColumnModelListener; 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.openide.explorer.ExplorerManager; @@ -88,6 +90,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { // the column started and where it ended up. private int startColumnIndex = -1; private int endColumnIndex = -1; + private OutlineView ov; /** * Creates a DataResultViewerTable object that is compatible with node @@ -111,7 +114,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { private void initialize() { initComponents(); - OutlineView ov = ((OutlineView) this.tableScrollPanel); + ov = ((OutlineView) this.tableScrollPanel); ov.setAllowedDragActions(DnDConstants.ACTION_NONE); ov.getOutline().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); @@ -216,6 +219,11 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } startColumnIndex = -1; } + + @Override + public void mouseClicked(MouseEvent e) { + storeState(); + } }); } @@ -369,7 +377,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { return; } currentRoot = root; - List> props = loadState(); + List> props = loadColumnOrder(); /** * OutlineView makes the first column be the result of @@ -442,6 +450,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer { ov.getOutline().setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); } + loadSort(); + /** * This custom renderer extends the renderer that was already being used * by the outline table. This renderer colors a row if the tags property @@ -494,20 +504,53 @@ public class DataResultViewerTable extends AbstractDataResultViewer { if (currentRoot == null || propertiesMap.isEmpty()) { return; } - - TableFilterNode tfn; if (currentRoot instanceof TableFilterNode) { - tfn = (TableFilterNode) currentRoot; - } else { + TableFilterNode tfn = (TableFilterNode) currentRoot; + 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())); + } + + int numCols = ov.getOutline().getColumnModel().getColumnCount(); + for (int i = 0; i < numCols; i++) { + ETableColumn etc = (ETableColumn) ov.getOutline().getColumnModel().getColumn(i); + 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())); + } else { + preferences.remove(getColumnSortOrderKey(tfn.getColumnOrderKey(), etc.getIdentifier().toString())); + preferences.remove(getColumnSortRankKey(tfn.getColumnOrderKey(), etc.getIdentifier().toString())); + } + } + } + } + + private synchronized void loadSort() { + if (currentRoot == null || propertiesMap.isEmpty()) { return; } + if (currentRoot instanceof TableFilterNode) { - // Store the current order of the columns into settings - for (Map.Entry> entry : propertiesMap.entrySet()) { - Property prop = entry.getValue(); - int storeValue = entry.getKey(); - NbPreferences.forModule(this.getClass()).put(getColumnPreferenceKey(prop, tfn.getColumnOrderKey()), String.valueOf(storeValue)); + final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); + TableFilterNode tfn = (TableFilterNode) currentRoot; + + Map orderMap = new TreeMap<>(); + Map indexMap = new TreeMap<>(); + + propertiesMap.entrySet().forEach((entry) -> { + //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")); + 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()); + }); } + } /** @@ -515,7 +558,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * * @return a List> of the preferences in order */ - private synchronized List> loadState() { + 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); @@ -541,7 +584,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { int offset = props.size(); boolean noPreviousSettings = true; for (Property prop : props) { - Integer value = Integer.valueOf(NbPreferences.forModule(this.getClass()).get(getColumnPreferenceKey(prop, tfn.getColumnOrderKey()), "-1")); + 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); noPreviousSettings = false; @@ -573,9 +616,24 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * * @return A generated key for the preference file */ - private String getColumnPreferenceKey(Property prop, String type) { - return type.replaceAll("[^a-zA-Z0-9_]", "") + "." - + prop.getName().replaceAll("[^a-zA-Z0-9_]", "") + ".column"; + 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_]", ""); } @Override From b35b871d1f50a8a0cf383d443f0d4c9910ad1577 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Tue, 9 May 2017 16:12:48 +0200 Subject: [PATCH 2/3] cleanup sort persistence and fix bug caused by using column identifiers --- .../corecomponents/DataResultViewerTable.java | 82 +++++++++++++------ 1 file changed, 55 insertions(+), 27 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index a1e9bc746f..0254fd394a 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -26,15 +26,18 @@ import java.awt.Graphics; import java.awt.dnd.DnDConstants; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.beans.FeatureDescriptor; import java.beans.PropertyChangeEvent; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; +import java.util.TreeSet; import java.util.prefs.Preferences; import javax.swing.JTable; import javax.swing.ListSelectionModel; @@ -44,6 +47,7 @@ import javax.swing.event.ListSelectionEvent; import javax.swing.event.TableColumnModelEvent; import javax.swing.event.TableColumnModelListener; import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumnModel; import org.netbeans.swing.etable.ETableColumn; import org.netbeans.swing.outline.DefaultOutlineCellRenderer; import org.netbeans.swing.outline.DefaultOutlineModel; @@ -371,7 +375,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { private void setupTable(final Node root) { em.setRootContext(root); - final OutlineView ov = ((OutlineView) this.tableScrollPanel); + ov = ((OutlineView) this.tableScrollPanel); if (ov == null) { return; @@ -398,14 +402,15 @@ public class DataResultViewerTable extends AbstractDataResultViewer { // Get the columns setup with respect to names and sortability String[] propStrings = new String[props.size() * 2]; for (int i = 0; i < props.size(); i++) { - props.get(i).setValue("ComparableColumnTTV", Boolean.TRUE); //NON-NLS + final Property prop = props.get(i); + prop.setValue("ComparableColumnTTV", Boolean.TRUE); //NON-NLS //First property column is sorted initially if (i == 0) { - props.get(i).setValue("TreeColumnTTV", Boolean.TRUE); // Identifies special property representing first (tree) column. NON-NLS - props.get(i).setValue("SortingColumnTTV", Boolean.TRUE); // TreeTableView should be initially sorted by this property column. NON-NLS + prop.setValue("TreeColumnTTV", Boolean.TRUE); // Identifies special property representing first (tree) column. NON-NLS + prop.setValue("SortingColumnTTV", Boolean.TRUE); // TreeTableView should be initially sorted by this property column. NON-NLS } - propStrings[2 * i] = props.get(i).getName(); - propStrings[2 * i + 1] = props.get(i).getDisplayName(); + propStrings[2 * i] = prop.getName(); + propStrings[2 * i + 1] = prop.getDisplayName(); } ov.setPropertyColumns(propStrings); @@ -511,46 +516,67 @@ public class DataResultViewerTable extends AbstractDataResultViewer { for (Map.Entry> entry : propertiesMap.entrySet()) { preferences.put(getColumnPositionKey(tfn.getColumnOrderKey(), entry.getValue().getName()), String.valueOf(entry.getKey())); } + final TableColumnModel columnModel = ov.getOutline().getColumnModel(); - int numCols = ov.getOutline().getColumnModel().getColumnCount(); + //store the sorting information + int numCols = columnModel.getColumnCount(); for (int i = 0; i < numCols; i++) { - ETableColumn etc = (ETableColumn) ov.getOutline().getColumnModel().getColumn(i); + ETableColumn etc = (ETableColumn) columnModel.getColumn(i); + String columnName = ov.getOutline().getColumnName(i); 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(getColumnSortOrderKey(tfn.getColumnOrderKey(), columnName), String.valueOf(etc.isAscending())); + preferences.put(getColumnSortRankKey(tfn.getColumnOrderKey(), columnName), String.valueOf(etc.getSortRank())); } else { - preferences.remove(getColumnSortOrderKey(tfn.getColumnOrderKey(), etc.getIdentifier().toString())); - preferences.remove(getColumnSortRankKey(tfn.getColumnOrderKey(), etc.getIdentifier().toString())); + preferences.remove(getColumnSortOrderKey(tfn.getColumnOrderKey(), columnName)); + preferences.remove(getColumnSortRankKey(tfn.getColumnOrderKey(), columnName)); } } } } + static private final class ColumnSortInfo { + + private final int modelIndex; + private final int rank; + private final boolean order; + + private ColumnSortInfo(int modelIndex, int rank, boolean order) { + this.modelIndex = modelIndex; + this.rank = rank; + this.order = order; + } + + private int getRank() { + return rank; + } + } + private synchronized void loadSort() { if (currentRoot == null || propertiesMap.isEmpty()) { return; } + if (currentRoot instanceof TableFilterNode) { + final String columnOrderKey = ((TableFilterNode) currentRoot).getColumnOrderKey(); final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); - TableFilterNode tfn = (TableFilterNode) currentRoot; - Map orderMap = new TreeMap<>(); - Map indexMap = new TreeMap<>(); + //organize property sorting information, sorted by rank + TreeSet treeSet = new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank)); + propertiesMap.entrySet().stream() + .forEach(entry -> { + final String propName = entry.getValue().getName(); + //if the sort rank is undefined, it will be defaulted to 0 => unsorted. + Integer sortRank = Integer.valueOf(preferences.get(getColumnSortRankKey(columnOrderKey, propName), "0")); + //default to true => ascending + Boolean sortOrder = Boolean.valueOf(preferences.get(getColumnSortOrderKey(columnOrderKey, propName), "true")); - propertiesMap.entrySet().forEach((entry) -> { - //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")); - orderMap.put(sortRank, sortOrder); - indexMap.put(sortRank, ov.getOutline().getColumn(entry.getValue().getName()).getModelIndex()); - }); + treeSet.add(new ColumnSortInfo(entry.getKey(), sortRank, sortOrder)); + }); - orderMap.entrySet().forEach((entry) -> { - ov.getOutline().setColumnSorted(indexMap.get(entry.getKey()), orderMap.get(entry.getKey()), entry.getKey()); - }); + //apply sort information in rank order. + treeSet.forEach(sortInfo -> ov.getOutline().setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank)); } - } /** @@ -583,8 +609,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer { */ int offset = props.size(); boolean noPreviousSettings = true; + final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); + for (Property prop : props) { - Integer value = Integer.valueOf(NbPreferences.forModule(this.getClass()).get(getColumnPositionKey(tfn.getColumnOrderKey(), prop.getName()), "-1")); + Integer value = Integer.valueOf(preferences.get(getColumnPositionKey(tfn.getColumnOrderKey(), prop.getName()), "-1")); if (value >= 0 && value < offset && !propertiesMap.containsKey(value)) { propertiesMap.put(value, prop); noPreviousSettings = false; From 6988ebc97da73e2ceb3b8a0c3e176d6604439112 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Tue, 9 May 2017 17:09:18 +0200 Subject: [PATCH 3/3] minor cleanup --- .../autopsy/corecomponents/DataResultViewerTable.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index 0254fd394a..324dfe2513 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -375,11 +375,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { private void setupTable(final Node root) { em.setRootContext(root); - ov = ((OutlineView) this.tableScrollPanel); - if (ov == null) { - return; - } currentRoot = root; List> props = loadColumnOrder();