Autopsy  3.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
DataResultViewerTable.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2013-2014 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.corecomponents;
20 
21 import java.awt.Cursor;
22 import java.awt.FontMetrics;
23 import java.awt.Graphics;
24 import java.awt.dnd.DnDConstants;
25 import java.beans.PropertyChangeEvent;
26 import java.lang.reflect.InvocationTargetException;
27 import java.util.ArrayList;
28 import java.util.LinkedHashSet;
29 import java.util.List;
30 import java.util.Set;
31 import javax.swing.JTable;
32 import javax.swing.ListSelectionModel;
33 import javax.swing.SwingUtilities;
34 import org.netbeans.swing.outline.DefaultOutlineModel;
35 import org.openide.explorer.ExplorerManager;
36 import org.openide.explorer.view.OutlineView;
37 import org.openide.nodes.AbstractNode;
38 import org.openide.nodes.Children;
39 import org.openide.nodes.Node;
40 import org.openide.nodes.Node.Property;
41 import org.openide.nodes.Node.PropertySet;
42 import org.openide.nodes.NodeEvent;
43 import org.openide.nodes.NodeListener;
44 import org.openide.nodes.NodeMemberEvent;
45 import org.openide.nodes.NodeReorderEvent;
46 import org.openide.nodes.Sheet;
47 import org.openide.util.NbBundle;
49 
53 // @@@ Restore implementation of DataResultViewerTable as a DataResultViewer
54 // service provider when DataResultViewers can be made compatible with node
55 // multiple selection actions.
56 //@ServiceProvider(service = DataResultViewer.class)
57 public class DataResultViewerTable extends AbstractDataResultViewer {
58 
59  private String firstColumnLabel = NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.firstColLbl");
60  private Set<Property<?>> propertiesAcc = new LinkedHashSet<>();
62  private static final String DUMMY_NODE_DISPLAY_NAME = NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.dummyNodeDisplayName");
63 
68  public DataResultViewerTable(ExplorerManager explorerManager) {
69  super(explorerManager);
70  initialize();
71  }
72 
78  initialize();
79  }
80 
81  private void initialize() {
83 
84  OutlineView ov = ((OutlineView) this.tableScrollPanel);
85  ov.setAllowedDragActions(DnDConstants.ACTION_NONE);
86  ov.setAllowedDropActions(DnDConstants.ACTION_NONE);
87 
88  ov.getOutline().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
89 
90  // don't show the root node
91  ov.getOutline().setRootVisible(false);
92  ov.getOutline().setDragEnabled(false);
93  }
94 
100  @Override
101  public void expandNode(Node n) {
102  super.expandNode(n);
103 
104  if (this.tableScrollPanel != null) {
105  OutlineView ov = ((OutlineView) this.tableScrollPanel);
106  ov.expandNode(n);
107  }
108  }
109 
115  @SuppressWarnings("unchecked")
116  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
117  private void initComponents() {
118 
119  tableScrollPanel = new OutlineView(this.firstColumnLabel);
120 
121  //new TreeTableView()
122  tableScrollPanel.addComponentListener(new java.awt.event.ComponentAdapter() {
123  public void componentResized(java.awt.event.ComponentEvent evt) {
125  }
126  });
127 
128  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
129  this.setLayout(layout);
130  layout.setHorizontalGroup(
131  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
132  .addComponent(tableScrollPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE)
133  );
134  layout.setVerticalGroup(
135  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
136  .addComponent(tableScrollPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE)
137  );
138  }// </editor-fold>//GEN-END:initComponents
139 
140  private void tableScrollPanelComponentResized(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_tableScrollPanelComponentResized
141  }//GEN-LAST:event_tableScrollPanelComponentResized
142  // Variables declaration - do not modify//GEN-BEGIN:variables
143  private javax.swing.JScrollPane tableScrollPanel;
144  // End of variables declaration//GEN-END:variables
145 
152  private Node.Property<?>[] getChildPropertyHeaders(Node parent) {
153  Node firstChild = parent.getChildren().getNodeAt(0);
154 
155  if (firstChild == null) {
156  throw new IllegalArgumentException(
157  NbBundle.getMessage(this.getClass(), "DataResultViewerTable.illegalArgExc.noChildFromParent"));
158  } else {
159  for (PropertySet ps : firstChild.getPropertySets()) {
160  if (ps.getName().equals(Sheet.PROPERTIES)) {
161  return ps.getProperties();
162  }
163  }
164 
165  throw new IllegalArgumentException(
166  NbBundle.getMessage(this.getClass(), "DataResultViewerTable.illegalArgExc.childWithoutPropertySet"));
167  }
168  }
169 
178  @SuppressWarnings("rawtypes")
179  private Node.Property[] getAllChildPropertyHeaders(Node parent) {
180  Node firstChild = parent.getChildren().getNodeAt(0);
181 
182  Property[] properties = null;
183 
184  if (firstChild == null) {
185  throw new IllegalArgumentException(
186  NbBundle.getMessage(this.getClass(), "DataResultViewerTable.illegalArgExc.noChildFromParent"));
187  } else {
188  Set<Property> allProperties = new LinkedHashSet<Property>();
189  while (firstChild != null) {
190  for (PropertySet ps : firstChild.getPropertySets()) {
191  //if (ps.getName().equals(Sheet.PROPERTIES)) {
192  //return ps.getProperties();
193  final Property[] props = ps.getProperties();
194  final int propsNum = props.length;
195  for (int i = 0; i < propsNum; ++i) {
196  allProperties.add(props[i]);
197  }
198  //}
199  }
200  firstChild = firstChild.getChildren().getNodeAt(0);
201  }
202 
203  properties = allProperties.toArray(new Property[0]);
204  //throw new IllegalArgumentException("Child Node doesn't have the regular PropertySet.");
205  }
206  return properties;
207 
208  }
209 
219  private void getAllChildPropertyHeadersRec(Node parent, int rows) {
220  Children children = parent.getChildren();
221  int childCount = 0;
222  for (Node child : children.getNodes()) {
223  if (++childCount > rows) {
224  break;
225  }
226  for (PropertySet ps : child.getPropertySets()) {
227  final Property<?>[] props = ps.getProperties();
228  final int propsNum = props.length;
229  for (int j = 0; j < propsNum; ++j) {
230  propertiesAcc.add(props[j]);
231  }
232  }
233  getAllChildPropertyHeadersRec(child, rows);
234  }
235  }
236 
237  @Override
238  public boolean isSupported(Node selectedNode) {
239  return true;
240  }
241 
248  @Override
249  public void setNode(Node selectedNode) {
250  // change the cursor to "waiting cursor" for this operation
251  this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
252  try {
253  boolean hasChildren = false;
254 
255  if (selectedNode != null) {
256  // @@@ This just did a DB round trip to get the count and the results were not saved...
257  hasChildren = selectedNode.getChildren().getNodesCount() > 0;
258  }
259 
260  Node oldNode = this.em.getRootContext();
261  if (oldNode != null) {
262  oldNode.removeNodeListener(dummyNodeListener);
263  }
264 
265  // if there's no selection node, do nothing
266  if (hasChildren) {
267  Node root = selectedNode;
268  dummyNodeListener.reset();
269  root.addNodeListener(dummyNodeListener);
270  setupTable(root);
271  } else {
272  final OutlineView ov = ((OutlineView) this.tableScrollPanel);
273  Node emptyNode = new AbstractNode(Children.LEAF);
274  em.setRootContext(emptyNode); // make empty node
275  ov.getOutline().setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
276  ov.setPropertyColumns(); // set the empty property header
277  }
278  } finally {
279  this.setCursor(null);
280  }
281  }
282 
289  private void setupTable(final Node root) {
290  //wrap to filter out children
291  //note: this breaks the tree view mode in this generic viewer,
292  //so wrap nodes earlier if want 1 level view
293  //if (!(root instanceof TableFilterNode)) {
295  //}
296 
297  em.setRootContext(root);
298 
299 
300  final OutlineView ov = ((OutlineView) DataResultViewerTable.this.tableScrollPanel);
301 
302  if (ov == null) {
303  return;
304  }
305 
306  propertiesAcc.clear();
307 
309  List<Node.Property<?>> props = new ArrayList<>(propertiesAcc);
310 
311  /* OutlineView makes the first column be the result of node.getDisplayName with the icon. This
312  * duplicates our first column, which is the file name, etc. So, pop that property off the list, but
313  * use its display name as the header for the column so that the header can change depending on the
314  * type of data being displayed.
315  *
316  * NOTE: This assumes that the first property is always the one tha duplicates getDisplayName(). This
317  * seems like a big assumption and could be made more robust.
318  */
319  if (props.size() > 0) {
320  Node.Property<?> prop = props.remove(0);
321  ((DefaultOutlineModel) ov.getOutline().getOutlineModel()).setNodesColumnLabel(prop.getDisplayName());
322  }
323 
324 
325  // Get the columns setup with respect to names and sortability
326  String[] propStrings = new String[props.size() * 2];
327  for (int i = 0; i < props.size(); i++) {
328  props.get(i).setValue("ComparableColumnTTV", Boolean.TRUE); //NON-NLS
329  //First property column is sorted initially
330  if (i == 0) {
331  props.get(i).setValue("TreeColumnTTV", Boolean.TRUE); // Identifies special property representing first (tree) column. NON-NLS
332  props.get(i).setValue("SortingColumnTTV", Boolean.TRUE); // TreeTableView should be initially sorted by this property column. NON-NLS
333  }
334  propStrings[2 * i] = props.get(i).getName();
335  propStrings[2 * i + 1] = props.get(i).getDisplayName();
336  }
337 
338  ov.setPropertyColumns(propStrings);
339  // *****************************************************************
340 
341  // // set the first entry
342  // Children test = root.getChildren();
343  // Node firstEntryNode = test.getNodeAt(0);
344  // try {
345  // this.getExplorerManager().setSelectedNodes(new Node[]{firstEntryNode});
346  // } catch (PropertyVetoException ex) {}
347 
348 
349  // show the horizontal scroll panel and show all the content & header
350 
351  int totalColumns = props.size();
352 
353  //int scrollWidth = ttv.getWidth();
354  int margin = 4;
355  int startColumn = 1;
356 
357  // If there is only one column (which was removed from props above)
358  // Just let the table resize itself.
359  ov.getOutline().setAutoResizeMode((props.size() > 0) ? JTable.AUTO_RESIZE_OFF : JTable.AUTO_RESIZE_ALL_COLUMNS);
360 
361 
362 
363  // get first 100 rows values for the table
364  Object[][] content = null;
365  content = getRowValues(root, 100);
366 
367 
368  if (content != null) {
369  // get the fontmetrics
370  final Graphics graphics = ov.getGraphics();
371  if (graphics != null) {
372  final FontMetrics metrics = graphics.getFontMetrics();
373 
374  // for the "Name" column
375  int nodeColWidth = Math.min(getMaxColumnWidth(0, metrics, margin, 40, firstColumnLabel, content), 250); // Note: 40 is the width of the icon + node lines. Change this value if those values change!
376  ov.getOutline().getColumnModel().getColumn(0).setPreferredWidth(nodeColWidth);
377 
378  // get the max for each other column
379  for (int colIndex = startColumn; colIndex <= totalColumns; colIndex++) {
380  int colWidth = Math.min(getMaxColumnWidth(colIndex, metrics, margin, 8, props, content), 350);
381  ov.getOutline().getColumnModel().getColumn(colIndex).setPreferredWidth(colWidth);
382  }
383  }
384  }
385 
386  // if there's no content just auto resize all columns
387  if (!(content.length > 0)) {
388  // turn on the auto resize
389  ov.getOutline().setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
390  }
391  }
392 
393  // Populate a two-dimensional array with rows of property values for up
394  // to maxRows children of the node passed in.
395  private static Object[][] getRowValues(Node node, int maxRows) {
396  int numRows = Math.min(maxRows, node.getChildren().getNodesCount());
397  Object[][] rowValues = new Object[numRows][];
398  int rowCount = 0;
399  for (Node child : node.getChildren().getNodes()) {
400  if (rowCount >= maxRows) {
401  break;
402  }
403  // BC: I got this once, I think it was because the table
404  // refreshed while we were in this method
405  // could be better synchronized. Or it was from
406  // the lazy nodes updating... Didn't have time
407  // to fully debug it.
408  if (rowCount > numRows) {
409  break;
410  }
411  PropertySet[] propertySets = child.getPropertySets();
412  if (propertySets.length > 0) {
413  Property<?>[] properties = propertySets[0].getProperties();
414  rowValues[rowCount] = new Object[properties.length];
415  for (int j = 0; j < properties.length; ++j) {
416  try {
417  rowValues[rowCount][j] = properties[j].getValue();
418  } catch (IllegalAccessException | InvocationTargetException ignore) {
419  rowValues[rowCount][j] = "n/a"; //NON-NLS
420  }
421  }
422  }
423  ++rowCount;
424  }
425  return rowValues;
426  }
427 
428  @Override
429  public String getTitle() {
430  return NbBundle.getMessage(this.getClass(), "DataResultViewerTable.title");
431  }
432 
433  @Override
435  return new DataResultViewerTable();
436  }
437 
449  @SuppressWarnings("rawtypes")
450  private int getMaxColumnWidth(int index, FontMetrics metrics, int margin, int padding, List<Node.Property<?>> header, Object[][] table) {
451  // set the tree (the node / names column) width
452  String headerName = header.get(index - 1).getDisplayName();
453 
454  return getMaxColumnWidth(index, metrics, margin, padding, headerName, table);
455  }
456 
468  private synchronized int getMaxColumnWidth(int index, FontMetrics metrics, int margin, int padding, String header, Object[][] table) {
469  // set the tree (the node / names column) width
470  String headerName = header;
471  int headerWidth = metrics.stringWidth(headerName); // length of the header
472  int colWidth = 0;
473 
474  // Get maximum width of column data
475  for (int i = 0; i < table.length; i++) {
476  if (table[i] == null || index >= table[i].length) {
477  continue;
478  }
479  String test = table[i][index].toString();
480  colWidth = Math.max(colWidth, metrics.stringWidth(test));
481  }
482 
483  colWidth += padding; // add the padding on the most left gap
484  headerWidth += 8; // add the padding to the header (change this value if the header padding value is changed)
485 
486  // Set the width
487  int width = Math.max(headerWidth, colWidth);
488  width += 2 * margin; // Add margin
489 
490  return width;
491  }
492 
493  @Override
494  public void clearComponent() {
495  this.tableScrollPanel.removeAll();
496  this.tableScrollPanel = null;
497 
498  super.clearComponent();
499  }
500 
501  private class DummyNodeListener implements NodeListener {
502 
503  private volatile boolean load = true;
504 
505  public void reset() {
506  load = true;
507  }
508 
509  @Override
510  public void childrenAdded(final NodeMemberEvent nme) {
511  Node[] delta = nme.getDelta();
512  if (load && containsReal(delta)) {
513  load = false;
514  if (SwingUtilities.isEventDispatchThread()) {
515  setupTable(nme.getNode());
516  } else {
517  SwingUtilities.invokeLater(new Runnable() {
518  @Override
519  public void run() {
520  setupTable(nme.getNode());
521  }
522  });
523  }
524  }
525  }
526 
527  private boolean containsReal(Node[] delta) {
528  for (Node n : delta) {
529  if (!n.getDisplayName().equals(DUMMY_NODE_DISPLAY_NAME)) {
530  return true;
531  }
532  }
533  return false;
534  }
535 
536  @Override
537  public void childrenRemoved(NodeMemberEvent nme) {
538  }
539 
540  @Override
541  public void childrenReordered(NodeReorderEvent nre) {
542  }
543 
544  @Override
545  public void nodeDestroyed(NodeEvent ne) {
546  }
547 
548  @Override
549  public void propertyChange(PropertyChangeEvent evt) {
550  }
551  }
552 }
void tableScrollPanelComponentResized(java.awt.event.ComponentEvent evt)
int getMaxColumnWidth(int index, FontMetrics metrics, int margin, int padding, List< Node.Property<?>> header, Object[][] table)
synchronized int getMaxColumnWidth(int index, FontMetrics metrics, int margin, int padding, String header, Object[][] table)

Copyright © 2012-2015 Basis Technology. Generated on: Mon Oct 19 2015
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.