From 258bc68a868fb06e4227f381f8cceb16e37e1951 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Mon, 1 May 2017 17:51:18 +0200 Subject: [PATCH 1/4] cleanup DataResultViewerTable in preparation for making column sorting persistent --- .../corecomponents/DataResultViewerTable.java | 98 ++++++++----------- 1 file changed, 41 insertions(+), 57 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index 064393111b..495cbfc4fc 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2016 Basis Technology Corp. + * Copyright 2011-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,6 +47,7 @@ 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; @@ -54,6 +55,7 @@ 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.NodeAdapter; import org.openide.nodes.NodeEvent; import org.openide.nodes.NodeListener; import org.openide.nodes.NodeMemberEvent; @@ -61,6 +63,7 @@ import org.openide.nodes.NodeReorderEvent; import org.openide.util.NbBundle; import org.openide.util.NbPreferences; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; /** * DataResult sortable table viewer @@ -90,7 +93,8 @@ 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; + private Outline outline; /** * Creates a DataResultViewerTable object that is compatible with node @@ -114,17 +118,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 +205,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 +220,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; } @@ -237,8 +242,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { super.expandNode(n); if (this.tableScrollPanel != null) { - OutlineView ov = ((OutlineView) this.tableScrollPanel); - ov.expandNode(n); + outlineView.expandNode(n); } } @@ -317,8 +321,9 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * @param selectedNode */ @Override + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) 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 +331,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * applied the number of rows do not match the number of rows in the * model." */ - ov.getOutline().unsetQuickFilter(); + outlineView.getOutline().unsetQuickFilter(); // change the cursor to "waiting cursor" for this operation this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { @@ -350,8 +355,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 + outlineView.getOutline().setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); + outlineView.setPropertyColumns(); // set the empty property header } } finally { this.setCursor(null); @@ -367,9 +372,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer { private void setupTable(final Node root) { em.setRootContext(root); - final OutlineView ov = ((OutlineView) this.tableScrollPanel); - if (ov == null) { + if (outlineView == null) { return; } currentRoot = root; @@ -388,7 +392,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { */ if (props.size() > 0) { Node.Property prop = props.remove(0); - ((DefaultOutlineModel) ov.getOutline().getOutlineModel()).setNodesColumnLabel(prop.getDisplayName()); + ((DefaultOutlineModel) outlineView.getOutline().getOutlineModel()).setNodesColumnLabel(prop.getDisplayName()); } // Get the columns setup with respect to names and sortability @@ -404,46 +408,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 +494,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { return component; } } - ov.getOutline().setDefaultRenderer(Object.class, new ColorTagCustomRenderer()); + outline.setDefaultRenderer(Object.class, new ColorTagCustomRenderer()); } /** @@ -634,7 +638,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { @Override public String getTitle() { - return NbBundle.getMessage(this.getClass(), "DataResultViewerTable.title"); + return NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.title"); } @Override @@ -650,7 +654,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { super.clearComponent(); } - private class PleasewaitNodeListener implements NodeListener { + private class PleasewaitNodeListener extends NodeAdapter{ private volatile boolean load = true; @@ -663,39 +667,19 @@ public class DataResultViewerTable extends AbstractDataResultViewer { Node[] delta = nme.getDelta(); if (load && containsReal(delta)) { load = false; + //JMTODO: this looks suspicious if (SwingUtilities.isEventDispatchThread()) { setupTable(nme.getNode()); } else { - SwingUtilities.invokeLater(() -> { - setupTable(nme.getNode()); - }); + SwingUtilities.invokeLater(() -> setupTable(nme.getNode())); } } } private boolean containsReal(Node[] delta) { - for (Node n : delta) { - if (!n.getDisplayName().equals(PLEASEWAIT_NODE_DISPLAY_NAME)) { - return true; - } - } - 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) { + return Stream.of(delta) + .map(Node::getDisplayName) + .noneMatch(PLEASEWAIT_NODE_DISPLAY_NAME::equals); } } } From 9863eade854c8c59a2196642a4a71a6274748957 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 3 May 2017 18:10:40 +0200 Subject: [PATCH 2/4] persist multicolumns sort order --- .../corecomponents/DataResultViewerTable.form | 9 +- .../corecomponents/DataResultViewerTable.java | 90 ++++++++++--------- 2 files changed, 51 insertions(+), 48 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form index 333fff3948..199e2be329 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form @@ -16,23 +16,22 @@ - + - + - + - + - diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index 495cbfc4fc..cb94bd231e 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -26,7 +26,6 @@ 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; @@ -38,13 +37,17 @@ import java.util.TreeMap; import java.util.prefs.Preferences; import javax.swing.JTable; import javax.swing.ListSelectionModel; +import javax.swing.RowSorter; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ListSelectionEvent; import javax.swing.event.TableColumnModelEvent; import javax.swing.event.TableColumnModelListener; import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; import org.netbeans.swing.etable.ETableColumn; +import org.netbeans.swing.etable.ETableColumnModel; import org.netbeans.swing.outline.DefaultOutlineCellRenderer; import org.netbeans.swing.outline.DefaultOutlineModel; import org.netbeans.swing.outline.Outline; @@ -56,10 +59,7 @@ import org.openide.nodes.Node; import org.openide.nodes.Node.Property; import org.openide.nodes.Node.PropertySet; import org.openide.nodes.NodeAdapter; -import org.openide.nodes.NodeEvent; -import org.openide.nodes.NodeListener; 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; @@ -78,11 +78,13 @@ public class DataResultViewerTable extends AbstractDataResultViewer { private final String firstColumnLabel = NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.firstColLbl"); /* - * The properties map maps key: stored value of column index -> value: - * property at that index We move around stored values instead of directly - * using the column indices in order to not override settings for a column - * that may not appear in the current table view due to its collection of - * its children's properties. + * The properties map: + * + * stored value of column index -> property at that index + * + * We move around stored values instead of directly using the column indices + * in order to not override settings for a column that may not appear in the + * current table view due to its collection of its children's properties. */ private final Map> propertiesMap = new TreeMap<>(); private final PleasewaitNodeListener pleasewaitNodeListener = new PleasewaitNodeListener(); @@ -93,7 +95,6 @@ public class DataResultViewerTable extends AbstractDataResultViewer { // the column started and where it ended up. private int startColumnIndex = -1; private int endColumnIndex = -1; - private OutlineView outlineView; private Outline outline; /** @@ -118,7 +119,6 @@ public class DataResultViewerTable extends AbstractDataResultViewer { private void initialize() { initComponents(); - outlineView = ((OutlineView) this.tableScrollPanel); outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE); outline = outlineView.getOutline(); @@ -241,7 +241,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { public void expandNode(Node n) { super.expandNode(n); - if (this.tableScrollPanel != null) { + if (this.outlineView != null) { outlineView.expandNode(n); } } @@ -255,12 +255,11 @@ public class DataResultViewerTable extends AbstractDataResultViewer { // //GEN-BEGIN:initComponents private void initComponents() { - tableScrollPanel = new OutlineView(this.firstColumnLabel); + outlineView = new OutlineView(this.firstColumnLabel); - //new TreeTableView() - tableScrollPanel.addComponentListener(new java.awt.event.ComponentAdapter() { + outlineView.addComponentListener(new java.awt.event.ComponentAdapter() { public void componentResized(java.awt.event.ComponentEvent evt) { - tableScrollPanelComponentResized(evt); + outlineViewComponentResized(evt); } }); @@ -268,18 +267,18 @@ public class DataResultViewerTable extends AbstractDataResultViewer { this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(tableScrollPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE) + .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(tableScrollPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE) + .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE) ); }// //GEN-END:initComponents - private void tableScrollPanelComponentResized(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_tableScrollPanelComponentResized - }//GEN-LAST:event_tableScrollPanelComponentResized + private void outlineViewComponentResized(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_outlineViewComponentResized + }//GEN-LAST:event_outlineViewComponentResized // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JScrollPane tableScrollPanel; + private org.openide.explorer.view.OutlineView outlineView; // End of variables declaration//GEN-END:variables /** @@ -331,7 +330,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * applied the number of rows do not match the number of rows in the * model." */ - outlineView.getOutline().unsetQuickFilter(); + outline.unsetQuickFilter(); // change the cursor to "waiting cursor" for this operation this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { @@ -373,9 +372,6 @@ public class DataResultViewerTable extends AbstractDataResultViewer { em.setRootContext(root); - if (outlineView == null) { - return; - } currentRoot = root; List> props = loadColumnOrder(); @@ -390,9 +386,9 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * duplicates getDisplayName(). The current implementation does not * allow the first property column to be moved. */ - if (props.size() > 0) { + if (props.isEmpty() == false) { Node.Property prop = props.remove(0); - ((DefaultOutlineModel) outlineView.getOutline().getOutlineModel()).setNodesColumnLabel(prop.getDisplayName()); + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(prop.getDisplayName()); } // Get the columns setup with respect to names and sortability @@ -413,7 +409,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { // 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. - outline.setAutoResizeMode((props.size() > 0) ? JTable.AUTO_RESIZE_OFF : JTable.AUTO_RESIZE_ALL_COLUMNS); + outline.setAutoResizeMode((props.isEmpty()) ? JTable.AUTO_RESIZE_ALL_COLUMNS : JTable.AUTO_RESIZE_OFF); if (root.getChildren().getNodesCount() != 0) { final Graphics graphics = outlineView.getGraphics(); @@ -518,6 +514,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer { 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(), 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())); @@ -539,15 +537,16 @@ public class DataResultViewerTable extends AbstractDataResultViewer { 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")); + Integer sortRank = Integer.valueOf(preferences.get(getColumnSortRankKey(tfn.getColumnOrderKey(), entry.getValue().getName()), "0")); + //if the sort rank is undefined, it will be defaulted to 0 , unsorted. + orderMap.put(sortRank, sortOrder); - indexMap.put(sortRank, ov.getOutline().getColumn(entry.getValue().getName()).getModelIndex()); + indexMap.put(sortRank, outline.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()); }); } @@ -583,8 +582,11 @@ 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; @@ -597,10 +599,9 @@ 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()]); + ArrayList keys = new ArrayList<>(propertiesMap.keySet()); for (int key : keys) { - propertiesMap.put(key - props.size(), propertiesMap.get(key)); - propertiesMap.remove(key); + propertiesMap.put(key - props.size(), propertiesMap.remove(key)); } } @@ -611,8 +612,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * 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 + * @param propName Property of the column + * @param type The type of the current node * * @return A generated key for the preference file */ @@ -637,8 +638,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } @Override - public String getTitle() { - return NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.title"); + public String + getTitle() { + return NbBundle.getMessage(DataResultViewerTable.class, + "DataResultViewerTable.title"); } @Override @@ -648,13 +651,14 @@ public class DataResultViewerTable extends AbstractDataResultViewer { @Override public void clearComponent() { - this.tableScrollPanel.removeAll(); - this.tableScrollPanel = null; + this.outlineView.removeAll(); + this.outlineView = null; super.clearComponent(); + } - private class PleasewaitNodeListener extends NodeAdapter{ + private class PleasewaitNodeListener extends NodeAdapter { private volatile boolean load = true; From 8df7151d15cedfffd4d934f0d2108f880eae778f Mon Sep 17 00:00:00 2001 From: millmanorama Date: Thu, 4 May 2017 15:00:00 +0200 Subject: [PATCH 3/4] cleanup, refactor DataResultViewerTable.java --- .../autopsy/corecomponents/Bundle.properties | 3 - .../corecomponents/DataResultViewerTable.form | 2 +- .../corecomponents/DataResultViewerTable.java | 301 +++++++++--------- 3 files changed, 153 insertions(+), 153 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index 183a66a263..14b3c47fa7 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -90,11 +90,8 @@ DataContentViewerString.setDataView.errorNoText=(offset {0}-{1} contains no text DataContentViewerString.title=Strings DataContentViewerString.toolTip=Displays ASCII and Unicode strings extracted from the file. DataResultPanel.pleasewaitNodeDisplayName=Please Wait... -DataResultViewerTable.firstColLbl=Name DataResultViewerTable.illegalArgExc.noChildFromParent=Could not get a child Node from the given parent. DataResultViewerTable.illegalArgExc.childWithoutPropertySet=Child Node does not have the regular PropertySet. -DataResultViewerTable.title=Table -DataResultViewerTable.pleasewaitNodeDisplayName=Please Wait... DataResultViewerThumbnail.title=Thumbnail DataResultViewerThumbnail.goToPageTextField.msgDlg=Please enter a valid page number between 1 and {0} DataResultViewerThumbnail.goToPageTextField.err=Invalid page number diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form index 199e2be329..d8b83f1dff 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form @@ -31,7 +31,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index cb94bd231e..a0c2635874 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -35,19 +35,16 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.prefs.Preferences; +import java.util.stream.Stream; import javax.swing.JTable; import javax.swing.ListSelectionModel; -import javax.swing.RowSorter; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ListSelectionEvent; import javax.swing.event.TableColumnModelEvent; import javax.swing.event.TableColumnModelListener; import javax.swing.table.TableCellRenderer; -import javax.swing.table.TableColumn; -import javax.swing.table.TableColumnModel; import org.netbeans.swing.etable.ETableColumn; -import org.netbeans.swing.etable.ETableColumnModel; import org.netbeans.swing.outline.DefaultOutlineCellRenderer; import org.netbeans.swing.outline.DefaultOutlineModel; import org.netbeans.swing.outline.Outline; @@ -67,16 +64,21 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined; /** * DataResult sortable table viewer + * + * @@@ Restore implementation of DataResultViewerTable as a DataResultViewer + * service provider when DataResultViewers can be made compatible with node + * multiple selection actions. */ -// @@@ Restore implementation of DataResultViewerTable as a DataResultViewer -// service provider when DataResultViewers can be made compatible with node -// multiple selection actions. //@ServiceProvider(service = DataResultViewer.class) public class DataResultViewerTable extends AbstractDataResultViewer { private static final long serialVersionUID = 1L; - private final String firstColumnLabel = NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.firstColLbl"); + @NbBundle.Messages("DataResultViewerTable.firstColLbl=Name") + static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl(); + @NbBundle.Messages("DataResultViewerTable.pleasewaitNodeDisplayName=Please Wait...") + private static final String PLEASEWAIT_NODE_DISPLAY_NAME = Bundle.DataResultViewerTable_pleasewaitNodeDisplayName(); + private static final Color TAGGED_COLOR = new Color(200, 210, 220); /* * The properties map: * @@ -88,13 +90,16 @@ public class DataResultViewerTable extends AbstractDataResultViewer { */ private final Map> propertiesMap = new TreeMap<>(); private final PleasewaitNodeListener pleasewaitNodeListener = new PleasewaitNodeListener(); - private static final String PLEASEWAIT_NODE_DISPLAY_NAME = NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pleasewaitNodeDisplayName"); - private static final Color TAGGED_COLOR = new Color(200, 210, 220); + private Node currentRoot; // When a column in the table is moved, these two variables keep track of where // the column started and where it ended up. private int startColumnIndex = -1; private int endColumnIndex = -1; + + /* + * Convience reference to internal Outline. + */ private Outline outline; /** @@ -123,28 +128,12 @@ public class DataResultViewerTable extends AbstractDataResultViewer { outline = outlineView.getOutline(); outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - - // don't show the root node - outline.setRootVisible(false); + outline.setRootVisible(false); // don't show the root node outline.setDragEnabled(false); + outline.setDefaultRenderer(Object.class, new ColorTagCustomRenderer()); // add a listener so that when columns are moved, the new order is stored outline.getColumnModel().addColumnModelListener(new TableColumnModelListener() { - @Override - public void columnAdded(TableColumnModelEvent e) { - } - - @Override - public void columnRemoved(TableColumnModelEvent e) { - } - - @Override - public void columnMarginChanged(ChangeEvent e) { - } - - @Override - public void columnSelectionChanged(ListSelectionEvent e) { - } @Override public void columnMoved(TableColumnModelEvent e) { @@ -169,39 +158,51 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } endColumnIndex = toIndex; - // This array contains the keys of propertiesMap in order - int[] indicesList = new int[propertiesMap.size()]; - int pos = 0; - for (int key : propertiesMap.keySet()) { - indicesList[pos++] = key; - } + // This list contains the keys of propertiesMap in order + ArrayList indicesList = new ArrayList<>(propertiesMap.keySet()); int leftIndex = Math.min(fromIndex, toIndex); int rightIndex = Math.max(fromIndex, toIndex); // Now we can copy the range of keys that have been affected by // the column movement - int[] range = Arrays.copyOfRange(indicesList, leftIndex, rightIndex + 1); - int rangeSize = range.length; + List range = indicesList.subList(leftIndex, rightIndex + 1); + int rangeSize = range.size(); - // column moved right, shift all properties left, put in moved - // property at the rightmost index if (fromIndex < toIndex) { - Property movedProp = propertiesMap.get(range[0]); + // column moved right, shift all properties left, put in moved + // property at the rightmost index + Property movedProp = propertiesMap.get(range.get(0)); for (int i = 0; i < rangeSize - 1; i++) { - propertiesMap.put(range[i], propertiesMap.get(range[i + 1])); + propertiesMap.put(range.get(i), propertiesMap.get(range.get(i + 1))); } - propertiesMap.put(range[rangeSize - 1], movedProp); - } // column moved left, shift all properties right, put in moved - // property at the leftmost index - else { - Property movedProp = propertiesMap.get(range[rangeSize - 1]); + propertiesMap.put(range.get(rangeSize - 1), movedProp); + } else { + // column moved left, shift all properties right, put in moved + // property at the leftmost index + Property movedProp = propertiesMap.get(range.get(rangeSize - 1)); for (int i = rangeSize - 1; i > 0; i--) { - propertiesMap.put(range[i], propertiesMap.get(range[i - 1])); + propertiesMap.put(range.get(i), propertiesMap.get(range.get(i - 1))); } - propertiesMap.put(range[0], movedProp); + propertiesMap.put(range.get(0), movedProp); } storeState(); } + + @Override + public void columnAdded(TableColumnModelEvent e) { + } + + @Override + public void columnRemoved(TableColumnModelEvent e) { + } + + @Override + public void columnMarginChanged(ChangeEvent e) { + } + + @Override + public void columnSelectionChanged(ListSelectionEvent e) { + } }); // add a listener to move columns back if user tries to move the first column out of place @@ -238,6 +239,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * @param n Node to expand */ @Override + public void expandNode(Node n) { super.expandNode(n); @@ -255,7 +257,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { // //GEN-BEGIN:initComponents private void initComponents() { - outlineView = new OutlineView(this.firstColumnLabel); + outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL); outlineView.addComponentListener(new java.awt.event.ComponentAdapter() { public void componentResized(java.awt.event.ComponentEvent evt) { @@ -282,29 +284,45 @@ public class DataResultViewerTable extends AbstractDataResultViewer { // 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 + * Gets 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) + * JMTODO: This is 'search' goes depth first. Is that really what we want + * when getting child headers? + * + * @param node the Node to get properties for. + * + * @return A List of properties. */ - private void getAllChildPropertyHeadersRec(Node parent, int rows, Set> propertiesAcc) { + static private 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<>(); + getAllChildPropertiesHelper(node, 100, propertiesAcc); + return new ArrayList<>(propertiesAcc); + } + + /** + * Helper to get the 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 maxRows max number of rows to retrieve properties for (can + * be used for memory optimization) + * @param propertiesAcc Accumulator for properties. + */ + static private void getAllChildPropertiesHelper(Node parent, int maxRows, Set> propertiesAcc) { Children children = parent.getChildren(); int childCount = 0; for (Node child : children.getNodes()) { - if (++childCount > rows) { + if (++childCount > maxRows) { 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]); - } + propertiesAcc.addAll(Arrays.asList(ps.getProperties())); } - getAllChildPropertyHeadersRec(child, rows, propertiesAcc); + getAllChildPropertiesHelper(child, maxRows, propertiesAcc); } } @@ -313,12 +331,6 @@ public class DataResultViewerTable extends AbstractDataResultViewer { return true; } - /** - * Thread note: Make sure to run this in the EDT as it causes GUI - * operations. - * - * @param selectedNode - */ @Override @ThreadConfined(type = ThreadConfined.ThreadType.AWT) public void setNode(Node selectedNode) { @@ -448,49 +460,6 @@ public class DataResultViewerTable extends AbstractDataResultViewer { 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 - * of the node is not empty. - */ - class ColorTagCustomRenderer extends DefaultOutlineCellRenderer { - - private static final long serialVersionUID = 1L; - - @Override - public Component getTableCellRendererComponent(JTable table, - Object value, boolean isSelected, boolean hasFocus, int row, int col) { - - Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col); - // only override the color if a node is not selected - if (!isSelected) { - Node node = currentRoot.getChildren().getNodeAt(table.convertRowIndexToModel(row)); - boolean tagFound = false; - if (node != null) { - Node.PropertySet[] propSets = node.getPropertySets(); - if (propSets.length != 0) { - // currently, a node has only one property set, named Sheet.PROPERTIES ("properties") - Node.Property[] props = propSets[0].getProperties(); - for (Property prop : props) { - if (prop.getName().equals("Tags")) { - try { - tagFound = !prop.getValue().equals(""); - } catch (IllegalAccessException | InvocationTargetException ignore) { - } - break; - } - } - } - } - //if the node does have associated tags, set its background color - if (tagFound) { - component.setBackground(TAGGED_COLOR); - } - } - return component; - } - } - outline.setDefaultRenderer(Object.class, new ColorTagCustomRenderer()); } /** @@ -505,20 +474,20 @@ 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())); + preferences.put(getColumnPositionKey(tfn, entry.getValue().getName()), String.valueOf(entry.getKey())); } - int numCols = ov.getOutline().getColumnModel().getColumnCount(); + int numCols = outline.getColumnModel().getColumnCount(); for (int i = 0; i < numCols; i++) { - ETableColumn etc = (ETableColumn) ov.getOutline().getColumnModel().getColumn(i); + ETableColumn etc = (ETableColumn) outline.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())); - 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, etc.getIdentifier().toString()), String.valueOf(etc.isAscending())); + preferences.put(getColumnSortRankKey(tfn, etc.getIdentifier().toString()), String.valueOf(etc.getSortRank())); + preferences.put(getColumnSortOrderKey(tfn, etc.getIdentifier().toString()), String.valueOf(etc.isAscending())); + preferences.put(getColumnSortRankKey(tfn, etc.getIdentifier().toString()), 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, etc.getIdentifier().toString())); + preferences.remove(getColumnSortRankKey(tfn, etc.getIdentifier().toString())); } } } @@ -537,8 +506,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer { Map indexMap = new TreeMap<>(); propertiesMap.entrySet().forEach((entry) -> { - Boolean sortOrder = Boolean.valueOf(preferences.get(getColumnSortOrderKey(tfn.getColumnOrderKey(), entry.getValue().getName()), "true")); - Integer sortRank = Integer.valueOf(preferences.get(getColumnSortRankKey(tfn.getColumnOrderKey(), entry.getValue().getName()), "0")); + Boolean sortOrder = Boolean.valueOf(preferences.get(getColumnSortOrderKey(tfn, entry.getValue().getName()), "true")); + Integer sortRank = Integer.valueOf(preferences.get(getColumnSortRankKey(tfn, entry.getValue().getName()), "0")); //if the sort rank is undefined, it will be defaulted to 0 , unsorted. orderMap.put(sortRank, sortOrder); @@ -558,20 +527,14 @@ 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); + List> props = getAllChildProperties(currentRoot); // If node is not table filter node, use default order for columns - TableFilterNode tfn; - if (currentRoot instanceof TableFilterNode) { - tfn = (TableFilterNode) currentRoot; - } else { + if (!(currentRoot instanceof TableFilterNode)) { // The node is not a TableFilterNode, columns are going to be in default order return props; } + TableFilterNode tfn = (TableFilterNode) currentRoot; propertiesMap.clear(); /* @@ -583,10 +546,9 @@ public class DataResultViewerTable extends AbstractDataResultViewer { int offset = props.size(); boolean noPreviousSettings = true; - final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class - ); + final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); for (Property prop : props) { - Integer value = Integer.valueOf(preferences.get(getColumnPositionKey(tfn.getColumnOrderKey(), prop.getName()), "-1")); + Integer value = Integer.valueOf(preferences.get(getColumnPositionKey(tfn, prop.getName()), "-1")); if (value >= 0 && value < offset && !propertiesMap.containsKey(value)) { propertiesMap.put(value, prop); noPreviousSettings = false; @@ -601,7 +563,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { if (noPreviousSettings) { ArrayList keys = new ArrayList<>(propertiesMap.keySet()); for (int key : keys) { - propertiesMap.put(key - props.size(), propertiesMap.remove(key)); + propertiesMap.put(key - offset, propertiesMap.remove(key)); } } @@ -613,35 +575,34 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * store the column position into a preference file. * * @param propName Property of the column - * @param type The type of the current node + * @param node The current node to use as a base for the property key. * * @return A generated key for the preference file */ - private String getColumnPositionKey(String type, String propName) { - return getColumnKeyBase(type, propName) + ".column"; + private String getColumnPositionKey(TableFilterNode node, String propName) { + return getColumnKeyBase(node, propName) + ".column"; //NON-NLS } - private String getColumnSortOrderKey(String type, String propName) { - return getColumnKeyBase(type, propName) + ".sortOrder"; + private String getColumnSortOrderKey(TableFilterNode node, String propName) { + return getColumnKeyBase(node, propName) + ".sortOrder";//NON-NLS } - private String getColumnSortRankKey(String type, String propName) { - return getColumnKeyBase(type, propName) + ".sortRank"; + private String getColumnSortRankKey(TableFilterNode node, String propName) { + return getColumnKeyBase(node, propName) + ".sortRank";//NON-NLS } - private static String getColumnKeyBase(String type, String propName) { - return stripNonAlphanumeric(type) + "." + stripNonAlphanumeric(propName); + 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_]", ""); + return str.replaceAll("[^a-zA-Z0-9_]", "");//NON-NLS } @Override - public String - getTitle() { - return NbBundle.getMessage(DataResultViewerTable.class, - "DataResultViewerTable.title"); + @NbBundle.Messages("DataResultViewerTable.title=Table") + public String getTitle() { + return Bundle.DataResultViewerTable_title(); } @Override @@ -686,4 +647,46 @@ public class DataResultViewerTable extends AbstractDataResultViewer { .noneMatch(PLEASEWAIT_NODE_DISPLAY_NAME::equals); } } + + /** + * This custom renderer extends the renderer that was already being used by + * the outline table. This renderer colors a row if the tags property of the + * node is not empty. + */ + class ColorTagCustomRenderer extends DefaultOutlineCellRenderer { + + private static final long serialVersionUID = 1L; + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { + + Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col); + // only override the color if a node is not selected + if (!isSelected) { + Node node = currentRoot.getChildren().getNodeAt(table.convertRowIndexToModel(row)); + boolean tagFound = false; + if (node != null) { + Node.PropertySet[] propSets = node.getPropertySets(); + if (propSets.length != 0) { + // currently, a node has only one property set, named Sheet.PROPERTIES ("properties") + Node.Property[] props = propSets[0].getProperties(); + for (Property prop : props) { + if ("Tags".equals(prop.getName())) {//NON-NLS + try { + tagFound = !prop.getValue().equals(""); + } catch (IllegalAccessException | InvocationTargetException ignore) { + } + break; + } + } + } + } + //if the node does have associated tags, set its background color + if (tagFound) { + component.setBackground(TAGGED_COLOR); + } + } + return component; + } + } } From 6af4d0a6318e71cad86d77c90114da8a179aec4b Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 12 May 2017 14:40:09 +0200 Subject: [PATCH 4/4] refactoring and cleanup of DataResultViewerTable --- .../corecomponents/DataResultViewerTable.form | 3 - .../corecomponents/DataResultViewerTable.java | 464 +++++++++--------- 2 files changed, 238 insertions(+), 229 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form index d8b83f1dff..d6c32623a4 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.form @@ -27,9 +27,6 @@ - - - diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index 81d68c3029..3d62c1a8fc 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -26,8 +26,6 @@ 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; @@ -37,10 +35,9 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; -import java.util.prefs.Preferences; -import java.util.stream.Stream; import java.util.TreeSet; import java.util.prefs.Preferences; +import java.util.stream.Stream; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; @@ -49,8 +46,8 @@ 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.apache.commons.lang3.StringUtils; import org.netbeans.swing.etable.ETableColumn; import org.netbeans.swing.outline.DefaultOutlineCellRenderer; import org.netbeans.swing.outline.DefaultOutlineModel; @@ -99,11 +96,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { private final PleasewaitNodeListener pleasewaitNodeListener = new PleasewaitNodeListener(); private Node currentRoot; - // When a column in the table is moved, these two variables keep track of where - // the column started and where it ended up. - private int startColumnIndex = -1; - private int endColumnIndex = -1; - private OutlineView ov; + /* * Convience reference to internal Outline. @@ -141,104 +134,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer { outline.setDefaultRenderer(Object.class, new ColorTagCustomRenderer()); // add a listener so that when columns are moved, the new order is stored - outline.getColumnModel().addColumnModelListener(new TableColumnModelListener() { - - @Override - public void columnMoved(TableColumnModelEvent e) { - int fromIndex = e.getFromIndex(); - int toIndex = e.getToIndex(); - if (fromIndex == toIndex) { - return; - } - - /* - * Because a column may be dragged to several different - * positions before the mouse is released (thus causing multiple - * TableColumnModelEvents to be fired), we want to keep track of - * the starting column index in this potential series of - * movements. Therefore we only keep track of the original - * fromIndex in startColumnIndex, but we always update - * endColumnIndex to know the final position of the moved - * column. See the MouseListener mouseReleased method. - */ - if (startColumnIndex == -1) { - startColumnIndex = fromIndex; - } - endColumnIndex = toIndex; - - // This list contains the keys of propertiesMap in order - ArrayList indicesList = new ArrayList<>(propertiesMap.keySet()); - int leftIndex = Math.min(fromIndex, toIndex); - int rightIndex = Math.max(fromIndex, toIndex); - // Now we can copy the range of keys that have been affected by - // the column movement - List range = indicesList.subList(leftIndex, rightIndex + 1); - int rangeSize = range.size(); - - if (fromIndex < toIndex) { - // column moved right, shift all properties left, put in moved - // property at the rightmost index - Property movedProp = propertiesMap.get(range.get(0)); - for (int i = 0; i < rangeSize - 1; i++) { - propertiesMap.put(range.get(i), propertiesMap.get(range.get(i + 1))); - } - propertiesMap.put(range.get(rangeSize - 1), movedProp); - } else { - // column moved left, shift all properties right, put in moved - // property at the leftmost index - Property movedProp = propertiesMap.get(range.get(rangeSize - 1)); - for (int i = rangeSize - 1; i > 0; i--) { - propertiesMap.put(range.get(i), propertiesMap.get(range.get(i - 1))); - } - propertiesMap.put(range.get(0), movedProp); - } - - storeState(); - } - - @Override - public void columnAdded(TableColumnModelEvent e) { - } - - @Override - public void columnRemoved(TableColumnModelEvent e) { - } - - @Override - public void columnMarginChanged(ChangeEvent e) { - } - - @Override - public void columnSelectionChanged(ListSelectionEvent e) { - } - }); - - // add a listener to move columns back if user tries to move the first column out of place - outline.getTableHeader().addMouseListener(new MouseAdapter() { - @Override - public void mouseReleased(MouseEvent e) { - /* - * If the startColumnIndex is not -1 (which is the reset value), - * that means columns have been moved around. We then check to - * see if either the starting or end position is 0 (the first - * column), and then swap them back if that is the case because - * we don't want to allow movement of the first column. We then - * reset startColumnIndex to -1, the reset value. We check if - * startColumnIndex is at reset or not because it is possible - * for the mouse to be released and a MouseEvent to be fired - * without having moved any columns. - */ - if (startColumnIndex != -1 && (startColumnIndex == 0 || endColumnIndex == 0)) { - outline.moveColumn(endColumnIndex, startColumnIndex); - } - startColumnIndex = -1; - } - - @Override - public void mouseClicked(MouseEvent e) { - storeState(); - } - }); + TableListener tableListener = new TableListener(); + outline.getColumnModel().addColumnModelListener(tableListener); + // the listener also moves columns back if user tries to move the first column out of place + outline.getTableHeader().addMouseListener(tableListener); } /** @@ -251,9 +150,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { public void expandNode(Node n) { super.expandNode(n); - if (this.outlineView != null) { - outlineView.expandNode(n); - } + outlineView.expandNode(n); } /** @@ -267,12 +164,6 @@ public class DataResultViewerTable extends AbstractDataResultViewer { outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL); - outlineView.addComponentListener(new java.awt.event.ComponentAdapter() { - public void componentResized(java.awt.event.ComponentEvent evt) { - outlineViewComponentResized(evt); - } - }); - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -285,42 +176,21 @@ public class DataResultViewerTable extends AbstractDataResultViewer { ); }// //GEN-END:initComponents - private void outlineViewComponentResized(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_outlineViewComponentResized - }//GEN-LAST:event_outlineViewComponentResized // Variables declaration - do not modify//GEN-BEGIN:variables private org.openide.explorer.view.OutlineView outlineView; // End of variables declaration//GEN-END:variables /** - * Gets 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 - * - * JMTODO: This is 'search' goes depth first. Is that really what we want - * when getting child headers? - * - * @param node the Node to get properties for. - * - * @return A List of properties. - */ - static private 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<>(); - getAllChildPropertiesHelper(node, 100, propertiesAcc); - return new ArrayList<>(propertiesAcc); - } - - /** - * Helper to get the 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 + * Get the 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 maxRows max number of rows to retrieve properties for (can * be used for memory optimization) * @param propertiesAcc Accumulator for properties. */ - static private void getAllChildPropertiesHelper(Node parent, int maxRows, Set> propertiesAcc) { + static private void getAllChildProperties(Node parent, int maxRows, Set> propertiesAcc) { Children children = parent.getChildren(); int childCount = 0; for (Node child : children.getNodes()) { @@ -330,7 +200,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { for (PropertySet ps : child.getPropertySets()) { propertiesAcc.addAll(Arrays.asList(ps.getProperties())); } - getAllChildPropertiesHelper(child, maxRows, propertiesAcc); + getAllChildProperties(child, maxRows, propertiesAcc); } } @@ -409,29 +279,22 @@ public class DataResultViewerTable extends AbstractDataResultViewer { Node.Property prop = props.remove(0); ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(prop.getDisplayName()); } - - // 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++) { - final Property prop = props.get(i); - prop.setValue("ComparableColumnTTV", Boolean.TRUE); //NON-NLS - //First property column is sorted initially - if (i == 0) { - 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] = prop.getName(); - propStrings[2 * i + 1] = prop.getDisplayName(); - } - - 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. + /* + * 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. + */ outline.setAutoResizeMode((props.isEmpty()) ? JTable.AUTO_RESIZE_ALL_COLUMNS : JTable.AUTO_RESIZE_OFF); - if (root.getChildren().getNodesCount() != 0) { + assignColumns(props); + + setColumnWidths(); + + loadColumnSorting(); + } + + private void setColumnWidths() { + if (currentRoot.getChildren().getNodesCount() != 0) { final Graphics graphics = outlineView.getGraphics(); if (graphics != null) { final FontMetrics metrics = graphics.getFontMetrics(); @@ -465,15 +328,31 @@ public class DataResultViewerTable extends AbstractDataResultViewer { // if there's no content just auto resize all columns outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); } + } - loadSort(); + private void assignColumns(List> props) { + // 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++) { + final Property prop = props.get(i); + prop.setValue("ComparableColumnTTV", Boolean.TRUE); //NON-NLS + //First property column is sorted initially + if (i == 0) { + 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] = prop.getName(); + propStrings[2 * i + 1] = prop.getDisplayName(); + } + outlineView.setPropertyColumns(propStrings); } /** - * Store the current column order into a preference file. + * Store the current column order and sort information into a preference + * file. */ - private synchronized void storeState() { + private synchronized void storeColumnState() { if (currentRoot == null || propertiesMap.isEmpty()) { return; } @@ -485,13 +364,13 @@ public class DataResultViewerTable extends AbstractDataResultViewer { preferences.put(getColumnPositionKey(tfn.getColumnOrderKey(), entry.getValue().getName()), String.valueOf(entry.getKey())); } - final TableColumnModel columnModel = ov.getOutline().getColumnModel(); + final TableColumnModel columnModel = outline.getColumnModel(); //store the sorting information int numCols = columnModel.getColumnCount(); for (int i = 0; i < numCols; i++) { ETableColumn etc = (ETableColumn) columnModel.getColumn(i); - String columnName = ov.getOutline().getColumnName(i); + String columnName = outline.getColumnName(i); if (etc.isSorted()) { preferences.put(getColumnSortOrderKey(tfn.getColumnOrderKey(), columnName), String.valueOf(etc.isAscending())); preferences.put(getColumnSortRankKey(tfn.getColumnOrderKey(), columnName), String.valueOf(etc.getSortRank())); @@ -503,73 +382,60 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } } - 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() { + /** + * Reads and applies the column sorting information persisted to the + * preferences file. Must be called after loadColumnOrder() since it depends + * on propertiesMap being initialized, and before assignColumns since it + * cannot set the sort on columns that have not been added to the table. + */ + private synchronized void loadColumnSorting() { if (currentRoot == null || propertiesMap.isEmpty()) { return; } + if (currentRoot instanceof TableFilterNode) { - + final String columnKey = ((TableFilterNode) currentRoot).getColumnOrderKey(); final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); - TableFilterNode tfn = (TableFilterNode) currentRoot; + //organize property sorting information, sorted by rank + TreeSet treeSet = new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank)); - if (currentRoot instanceof TableFilterNode) { - final String columnOrderKey = ((TableFilterNode) currentRoot).getColumnOrderKey(); + 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(columnKey, propName), "0")); + //default to true => ascending + Boolean sortOrder = Boolean.valueOf(preferences.get(getColumnSortOrderKey(columnKey, propName), "true")); - final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); - - //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")); - - treeSet.add(new ColumnSortInfo(entry.getKey(), sortRank, sortOrder)); - }); - - //apply sort information in rank order. - treeSet.forEach(sortInfo -> ov.getOutline().setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank)); - } + treeSet.add(new ColumnSortInfo(entry.getKey(), sortRank, sortOrder)); + }); + //apply sort information in rank order. + treeSet.forEach(sortInfo -> outline.setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank)); } } /** - * Loads the stored column order from the preference file. + * Gets a list of child properties (columns) in the order persisted in the + * preference file. Also initialized the propertiesMap with the column + * order. * - * @return a List> of the preferences in order + * @return a List> of the properties in the persisted + * order. */ private synchronized List> loadColumnOrder() { - List> props = getAllChildProperties(currentRoot); + // This is a set because we only want unique properties + Set> propertiesAcc = new LinkedHashSet<>(); + getAllChildProperties(currentRoot, 100, propertiesAcc); + List> props = new ArrayList<>(propertiesAcc); // If node is not table filter node, use default order for columns if (!(currentRoot instanceof TableFilterNode)) { - // The node is not a TableFilterNode, columns are going to be in default order return props; } - TableFilterNode tfn = (TableFilterNode) currentRoot; + final String columnKey = ((TableFilterNode) currentRoot).getColumnOrderKey(); propertiesMap.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 @@ -582,7 +448,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); for (Property prop : props) { - Integer value = Integer.valueOf(preferences.get(getColumnPositionKey(tfn.getColumnOrderKey(), prop.getName()), "-1")); + Integer value = Integer.valueOf(preferences.get(getColumnPositionKey(columnKey, prop.getName()), "-1")); if (value >= 0 && value < offset && !propertiesMap.containsKey(value)) { propertiesMap.put(value, prop); noPreviousSettings = false; @@ -608,25 +474,46 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * 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 + * @param keyBase The base of the key. Typically a fully quallified class + * name. + * @param propName The name of the specific property to make a key for. * * @return A generated key for the preference file */ - private String getColumnPositionKey(String type, String propName) { - return getColumnKeyBase(type, propName) + ".column"; + private String getColumnPositionKey(String keyBase, String propName) { + return getColumnKeyBase(keyBase, propName) + ".column"; } - private String getColumnSortOrderKey(String type, String propName) { - return getColumnKeyBase(type, propName) + ".sortOrder"; + /** + * Gets a key for the current node and a property of its child nodes to + * store the column sort ordering into a preference file. + * + * @param keyBase The base of the key. Typically a fully quallified class + * name. + * @param propName The name of the specific property to make a key for. + * + * @return A generated key for the preference file + */ + private String getColumnSortOrderKey(String keyBase, String propName) { + return getColumnKeyBase(keyBase, propName) + ".sortOrder"; } - private String getColumnSortRankKey(String type, String propName) { - return getColumnKeyBase(type, propName) + ".sortRank"; + /** + * Gets a key for the current node and a property of its child nodes to + * store the column sort rank into a preference file. + * + * @param keyBase The base of the key. Typically a fully quallified class + * name. + * @param propName The name of the specific property to make a key for. + * + * @return A generated key for the preference file + */ + private String getColumnSortRankKey(String keyBase, String propName) { + return getColumnKeyBase(keyBase, propName) + ".sortRank"; } - private static String getColumnKeyBase(String type, String propName) { - return stripNonAlphanumeric(type) + "." + stripNonAlphanumeric(propName); + private static String getColumnKeyBase(String keyBase, String propName) { + return stripNonAlphanumeric(keyBase) + "." + stripNonAlphanumeric(propName); } private static String stripNonAlphanumeric(String str) { @@ -653,6 +540,131 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } + /** + * Encapsulates sorting information for a column to make loadSort simpler. + */ + 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; + } + } + + /** + * Listens to mouse events and table column events and persists column order + * and sorting changes + */ + private class TableListener extends MouseAdapter implements TableColumnModelListener { + + // When a column in the table is moved, these two variables keep track of where + // the column started and where it ended up. + private int startColumnIndex = -1; + private int endColumnIndex = -1; + + @Override + public void columnMoved(TableColumnModelEvent e) { + int fromIndex = e.getFromIndex(); + int toIndex = e.getToIndex(); + if (fromIndex == toIndex) { + return; + } + + /* + * Because a column may be dragged to several different positions + * before the mouse is released (thus causing multiple + * TableColumnModelEvents to be fired), we want to keep track of the + * starting column index in this potential series of movements. + * Therefore we only keep track of the original fromIndex in + * startColumnIndex, but we always update endColumnIndex to know the + * final position of the moved column. See the MouseListener + * mouseReleased method. + */ + if (startColumnIndex == -1) { + startColumnIndex = fromIndex; + } + endColumnIndex = toIndex; + + // This list contains the keys of propertiesMap in order + ArrayList indicesList = new ArrayList<>(propertiesMap.keySet()); + int leftIndex = Math.min(fromIndex, toIndex); + int rightIndex = Math.max(fromIndex, toIndex); + // Now we can copy the range of keys that have been affected by + // the column movement + List range = indicesList.subList(leftIndex, rightIndex + 1); + int rangeSize = range.size(); + + if (fromIndex < toIndex) { + // column moved right, shift all properties left, put in moved + // property at the rightmost index + Property movedProp = propertiesMap.get(range.get(0)); + for (int i = 0; i < rangeSize - 1; i++) { + propertiesMap.put(range.get(i), propertiesMap.get(range.get(i + 1))); + } + propertiesMap.put(range.get(rangeSize - 1), movedProp); + } else { + // column moved left, shift all properties right, put in moved + // property at the leftmost index + Property movedProp = propertiesMap.get(range.get(rangeSize - 1)); + for (int i = rangeSize - 1; i > 0; i--) { + propertiesMap.put(range.get(i), propertiesMap.get(range.get(i - 1))); + } + propertiesMap.put(range.get(0), movedProp); + } + + storeColumnState(); + } + + @Override + public void mouseReleased(MouseEvent e) { + /* + * If the startColumnIndex is not -1 (which is the reset value), + * that means columns have been moved around. We then check to see + * if either the starting or end position is 0 (the first column), + * and then swap them back if that is the case because we don't want + * to allow movement of the first column. We then reset + * startColumnIndex to -1, the reset value. We check if + * startColumnIndex is at reset or not because it is possible for + * the mouse to be released and a MouseEvent to be fired without + * having moved any columns. + */ + if (startColumnIndex != -1 && (startColumnIndex == 0 || endColumnIndex == 0)) { + outline.moveColumn(endColumnIndex, startColumnIndex); + } + startColumnIndex = -1; + } + + @Override + public void mouseClicked(MouseEvent e) { + storeColumnState(); + } + + @Override + public void columnAdded(TableColumnModelEvent e) { + } + + @Override + public void columnRemoved(TableColumnModelEvent e) { + } + + @Override + public void columnMarginChanged(ChangeEvent e) { + } + + @Override + public void columnSelectionChanged(ListSelectionEvent e) { + } + } + private class PleasewaitNodeListener extends NodeAdapter { private volatile boolean load = true; @@ -687,7 +699,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * the outline table. This renderer colors a row if the tags property of the * node is not empty. */ - class ColorTagCustomRenderer extends DefaultOutlineCellRenderer { + private class ColorTagCustomRenderer extends DefaultOutlineCellRenderer { private static final long serialVersionUID = 1L;