Autopsy  4.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.awt.event.MouseEvent;
26 import java.awt.event.MouseListener;
27 import java.beans.PropertyChangeEvent;
28 import java.lang.reflect.InvocationTargetException;
29 import java.util.ArrayList;
30 import java.util.LinkedHashSet;
31 import java.util.List;
32 import java.util.Set;
33 import javax.swing.Action;
34 import javax.swing.JTable;
35 import javax.swing.ListSelectionModel;
36 import javax.swing.SwingUtilities;
37 import org.netbeans.swing.outline.DefaultOutlineModel;
38 import org.openide.explorer.ExplorerManager;
39 import org.openide.explorer.view.OutlineView;
40 import org.openide.nodes.AbstractNode;
41 import org.openide.nodes.Children;
42 import org.openide.nodes.Node;
43 import org.openide.nodes.Node.Property;
44 import org.openide.nodes.Node.PropertySet;
45 import org.openide.nodes.NodeEvent;
46 import org.openide.nodes.NodeListener;
47 import org.openide.nodes.NodeMemberEvent;
48 import org.openide.nodes.NodeReorderEvent;
49 import org.openide.nodes.Sheet;
50 import org.openide.util.NbBundle;
52 
56 // @@@ Restore implementation of DataResultViewerTable as a DataResultViewer
57 // service provider when DataResultViewers can be made compatible with node
58 // multiple selection actions.
59 //@ServiceProvider(service = DataResultViewer.class)
60 public class DataResultViewerTable extends AbstractDataResultViewer {
61 
62  private String firstColumnLabel = NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.firstColLbl");
63  private Set<Property<?>> propertiesAcc = new LinkedHashSet<>();
65  private static final String DUMMY_NODE_DISPLAY_NAME = NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.dummyNodeDisplayName");
66  private Node currentRoot;
67 
72  public DataResultViewerTable(ExplorerManager explorerManager) {
73  super(explorerManager);
74  initialize();
75  }
76 
82  initialize();
83  }
84 
85  private void initialize() {
87 
88  OutlineView ov = ((OutlineView) this.tableScrollPanel);
89  ov.setAllowedDragActions(DnDConstants.ACTION_NONE);
90 
91  ov.getOutline().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
92 
93  // don't show the root node
94  ov.getOutline().setRootVisible(false);
95  ov.getOutline().setDragEnabled(false);
96 
97  /*
98  * TODO (AUT-1849): Correct or remove peristent column reordering code
99  *
100  * The following lines of code were added for this feature.
101  */
102 // ov.getOutline().getColumnModel().addColumnModelListener(new TableColumnModelListener() {
103 // @Override
104 // public void columnAdded(TableColumnModelEvent e) {}
105 // @Override
106 // public void columnRemoved(TableColumnModelEvent e) {}
107 // @Override
108 // public void columnMarginChanged(ChangeEvent e) {}
109 // @Override
110 // public void columnSelectionChanged(ListSelectionEvent e) {}
111 //
112 // @Override
113 // public void columnMoved(TableColumnModelEvent e) {
114 // // change the order of the column in the array/hashset
115 // List<Node.Property<?>> props = new ArrayList<>(propertiesAcc);
116 // Node.Property<?> prop = props.remove(e.getFromIndex());
117 // props.add(e.getToIndex(), prop);
118 //
119 // propertiesAcc.clear();
120 // for (int j = 0; j < props.size(); ++j) {
121 // propertiesAcc.add(props.get(j));
122 // }
123 // }
124 // });
125  }
126 
132  @Override
133  public void expandNode(Node n) {
134  super.expandNode(n);
135 
136  if (this.tableScrollPanel != null) {
137  OutlineView ov = ((OutlineView) this.tableScrollPanel);
138  ov.expandNode(n);
139  }
140  }
141 
147  @SuppressWarnings("unchecked")
148  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
149  private void initComponents() {
150 
151  tableScrollPanel = new OutlineView(this.firstColumnLabel);
152 
153  //new TreeTableView()
154  tableScrollPanel.addComponentListener(new java.awt.event.ComponentAdapter() {
155  public void componentResized(java.awt.event.ComponentEvent evt) {
157  }
158  });
159 
160  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
161  this.setLayout(layout);
162  layout.setHorizontalGroup(
163  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
164  .addComponent(tableScrollPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE)
165  );
166  layout.setVerticalGroup(
167  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
168  .addComponent(tableScrollPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE)
169  );
170  }// </editor-fold>//GEN-END:initComponents
171 
172  private void tableScrollPanelComponentResized(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_tableScrollPanelComponentResized
173  }//GEN-LAST:event_tableScrollPanelComponentResized
174  // Variables declaration - do not modify//GEN-BEGIN:variables
175  private javax.swing.JScrollPane tableScrollPanel;
176  // End of variables declaration//GEN-END:variables
177 
185  private Node.Property<?>[] getChildPropertyHeaders(Node parent) {
186  Node firstChild = parent.getChildren().getNodeAt(0);
187 
188  if (firstChild == null) {
189  throw new IllegalArgumentException(
190  NbBundle.getMessage(this.getClass(), "DataResultViewerTable.illegalArgExc.noChildFromParent"));
191  } else {
192  for (PropertySet ps : firstChild.getPropertySets()) {
193  if (ps.getName().equals(Sheet.PROPERTIES)) {
194  return ps.getProperties();
195  }
196  }
197 
198  throw new IllegalArgumentException(
199  NbBundle.getMessage(this.getClass(), "DataResultViewerTable.illegalArgExc.childWithoutPropertySet"));
200  }
201  }
202 
212  @SuppressWarnings("rawtypes")
213  private Node.Property[] getAllChildPropertyHeaders(Node parent) {
214  Node firstChild = parent.getChildren().getNodeAt(0);
215 
216  Property[] properties = null;
217 
218  if (firstChild == null) {
219  throw new IllegalArgumentException(
220  NbBundle.getMessage(this.getClass(), "DataResultViewerTable.illegalArgExc.noChildFromParent"));
221  } else {
222  Set<Property> allProperties = new LinkedHashSet<>();
223  while (firstChild != null) {
224  for (PropertySet ps : firstChild.getPropertySets()) {
225  final Property[] props = ps.getProperties();
226  final int propsNum = props.length;
227  for (int i = 0; i < propsNum; ++i) {
228  allProperties.add(props[i]);
229  }
230  }
231  firstChild = firstChild.getChildren().getNodeAt(0);
232  }
233 
234  properties = allProperties.toArray(new Property<?>[0]);
235  }
236  return properties;
237 
238  }
239 
249  private void getAllChildPropertyHeadersRec(Node parent, int rows) {
250  Children children = parent.getChildren();
251  int childCount = 0;
252  for (Node child : children.getNodes()) {
253  if (++childCount > rows) {
254  return;
255  }
256  for (PropertySet ps : child.getPropertySets()) {
257  final Property<?>[] props = ps.getProperties();
258  final int propsNum = props.length;
259  for (int j = 0; j < propsNum; ++j) {
260  propertiesAcc.add(props[j]);
261  }
262  }
263  getAllChildPropertyHeadersRec(child, rows);
264  }
265  }
266 
267  @Override
268  public boolean isSupported(Node selectedNode) {
269  return true;
270  }
271 
278  @Override
279  public void setNode(Node selectedNode) {
280  // change the cursor to "waiting cursor" for this operation
281  this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
282  try {
283  boolean hasChildren = false;
284 
285  if (selectedNode != null) {
286  // @@@ This just did a DB round trip to get the count and the results were not saved...
287  hasChildren = selectedNode.getChildren().getNodesCount() > 0;
288  }
289 
290  Node oldNode = this.em.getRootContext();
291  if (oldNode != null) {
292  oldNode.removeNodeListener(dummyNodeListener);
293  }
294 
295  // if there's no selection node, do nothing
296  if (hasChildren) {
297  Node root = selectedNode;
298  dummyNodeListener.reset();
299  root.addNodeListener(dummyNodeListener);
300  setupTable(root);
301  } else {
302  final OutlineView ov = ((OutlineView) this.tableScrollPanel);
303  Node emptyNode = new AbstractNode(Children.LEAF);
304  em.setRootContext(emptyNode); // make empty node
305  ov.getOutline().setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
306  ov.setPropertyColumns(); // set the empty property header
307  }
308  } finally {
309  this.setCursor(null);
310  }
311  }
312 
319  private void setupTable(final Node root) {
320 
321  em.setRootContext(root);
322 
323  final OutlineView ov = ((OutlineView) this.tableScrollPanel);
324 
325  if (ov == null) {
326  return;
327  }
328 
329  /*
330  * TODO (AUT-1849): Correct or remove peristent column reordering code
331  *
332  * The next three lines of code replaced the three lines of code that
333  * follow
334  */
335 // storeState();
336  // set the new root as current
337 // currentRoot = root;
338 // List<Node.Property<?>> props = loadState();
339  propertiesAcc.clear();
341  List<Node.Property<?>> props = new ArrayList<>(propertiesAcc);
342 
343  /*
344  * OutlineView makes the first column be the result of
345  * node.getDisplayName with the icon. This duplicates our first column,
346  * which is the file name, etc. So, pop that property off the list, but
347  * use its display name as the header for the column so that the header
348  * can change depending on the type of data being displayed.
349  *
350  * NOTE: This assumes that the first property is always the one tha
351  * duplicates getDisplayName(). This seems like a big assumption and
352  * could be made more robust.
353  */
354  if (props.size() > 0) {
355  Node.Property<?> prop = props.remove(0);
356  ((DefaultOutlineModel) ov.getOutline().getOutlineModel()).setNodesColumnLabel(prop.getDisplayName());
357  }
358 
359  // Get the columns setup with respect to names and sortability
360  String[] propStrings = new String[props.size() * 2];
361  for (int i = 0; i < props.size(); i++) {
362  props.get(i).setValue("ComparableColumnTTV", Boolean.TRUE); //NON-NLS
363  //First property column is sorted initially
364  if (i == 0) {
365  props.get(i).setValue("TreeColumnTTV", Boolean.TRUE); // Identifies special property representing first (tree) column. NON-NLS
366  props.get(i).setValue("SortingColumnTTV", Boolean.TRUE); // TreeTableView should be initially sorted by this property column. NON-NLS
367  }
368  propStrings[2 * i] = props.get(i).getName();
369  propStrings[2 * i + 1] = props.get(i).getDisplayName();
370  }
371 
372  ov.setPropertyColumns(propStrings);
373 
374  // show the horizontal scroll panel and show all the content & header
375  int totalColumns = props.size();
376 
377  //int scrollWidth = ttv.getWidth();
378  int margin = 4;
379  int startColumn = 1;
380 
381  // If there is only one column (which was removed from props above)
382  // Just let the table resize itself.
383  ov.getOutline().setAutoResizeMode((props.size() > 0) ? JTable.AUTO_RESIZE_OFF : JTable.AUTO_RESIZE_ALL_COLUMNS);
384 
385  // get first 100 rows values for the table
386  Object[][] content;
387  content = getRowValues(root, 100);
388 
389  if (content != null) {
390  // get the fontmetrics
391  final Graphics graphics = ov.getGraphics();
392  if (graphics != null) {
393  final FontMetrics metrics = graphics.getFontMetrics();
394 
395  // for the "Name" column
396  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!
397  ov.getOutline().getColumnModel().getColumn(0).setPreferredWidth(nodeColWidth);
398 
399  // get the max for each other column
400  for (int colIndex = startColumn; colIndex <= totalColumns; colIndex++) {
401  int colWidth = Math.min(getMaxColumnWidth(colIndex, metrics, margin, 8, props, content), 350);
402  ov.getOutline().getColumnModel().getColumn(colIndex).setPreferredWidth(colWidth);
403  }
404  }
405 
406  // if there's no content just auto resize all columns
407  if (content.length <= 0) {
408  // turn on the auto resize
409  ov.getOutline().setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
410  }
411  }
412  }
413 
414  /*
415  * TODO (AUT-1849): Correct or remove peristent column reordering code
416  *
417  * The following three methods were added for this feature
418  */
419  // Store the state of current root Node.
420 // private void storeState() {
421 // if(currentRoot == null || propertiesAcc.isEmpty())
422 // return;
423 //
424 // TableFilterNode tfn;
425 // if(currentRoot instanceof TableFilterNode)
426 // tfn = (TableFilterNode) currentRoot;
427 // else
428 // return;
429 //
430 // List<Node.Property<?>> props = new ArrayList<>(propertiesAcc);
431 // for (int i = 0; i < props.size(); i++) {
432 // Property<?> prop = props.get(i);
433 // NbPreferences.forModule(this.getClass()).put(getUniqueColName(prop, tfn.getItemType()), String.valueOf(i));
434 // }
435 // }
436  // Load the state of current root Node if exists.
437 // private List<Node.Property<?>> loadState() {
438 // propertiesAcc.clear();
439 // this.getAllChildPropertyHeadersRec(currentRoot, 100);
440 // List<Node.Property<?>> props = new ArrayList<>(propertiesAcc);
441 //
442 // // If node is not table filter node, use default order for columns
443 // TableFilterNode tfn;
444 // if (currentRoot instanceof TableFilterNode) {
445 // tfn = (TableFilterNode) currentRoot;
446 // } else {
447 // Logger.getLogger(DataResultViewerTable.class.getName()).log(Level.INFO,
448 // "Node {0} is not TableFilterNode, columns are going to be in default order", currentRoot.getName());
449 // return props;
450 // }
451 //
452 // List<Node.Property<?>> orderedProps = new ArrayList<>(propertiesAcc);
453 // for (Property<?> prop : props) {
454 // Integer value = Integer.valueOf(NbPreferences.forModule(this.getClass()).get(getUniqueColName(prop, tfn.getItemType()), "-1"));
455 // if (value >= 0) {
456 // /**
457 // * The original contents of orderedProps do not matter when
458 // * setting the new ordered values. The reason we copy
459 // * propertiesAcc into it first is to give it the currect size so
460 // * we can set() in any index.
461 // */
462 // orderedProps.set(value, prop);
463 // }
464 // }
465 // propertiesAcc.clear();
466 // for (Property<?> prop : orderedProps) {
467 // propertiesAcc.add(prop);
468 // }
469 // return orderedProps;
470 // }
471 //
472 // // Get unique name for node and it's property.
473 // private String getUniqueColName(Property<?> prop, String type) {
474 // return Case.getCurrentCase().getName() + "." + type + "."
475 // + prop.getName().replaceAll("[^a-zA-Z0-9_]", "") + ".columnOrder";
476 // }
477 
478  // Populate a two-dimensional array with rows of property values for up
479  // to maxRows children of the node passed in.
480  private static Object[][] getRowValues(Node node, int maxRows) {
481  int numRows = Math.min(maxRows, node.getChildren().getNodesCount());
482  Object[][] rowValues = new Object[numRows][];
483  int rowCount = 0;
484  for (Node child : node.getChildren().getNodes()) {
485  if (rowCount >= maxRows) {
486  break;
487  }
488  // BC: I got this once, I think it was because the table
489  // refreshed while we were in this method
490  // could be better synchronized. Or it was from
491  // the lazy nodes updating... Didn't have time
492  // to fully debug it.
493  if (rowCount > numRows) {
494  break;
495  }
496  PropertySet[] propertySets = child.getPropertySets();
497  if (propertySets.length > 0) {
498  Property<?>[] properties = propertySets[0].getProperties();
499  rowValues[rowCount] = new Object[properties.length];
500  for (int j = 0; j < properties.length; ++j) {
501  try {
502  rowValues[rowCount][j] = properties[j].getValue();
503  } catch (IllegalAccessException | InvocationTargetException ignore) {
504  rowValues[rowCount][j] = "n/a"; //NON-NLS
505  }
506  }
507  }
508  ++rowCount;
509  }
510  return rowValues;
511  }
512 
513  @Override
514  public String getTitle() {
515  return NbBundle.getMessage(this.getClass(), "DataResultViewerTable.title");
516  }
517 
518  @Override
520  return new DataResultViewerTable();
521  }
522 
535  @SuppressWarnings("rawtypes")
536  private int getMaxColumnWidth(int index, FontMetrics metrics, int margin, int padding, List<Node.Property<?>> header, Object[][] table) {
537  // set the tree (the node / names column) width
538  String headerName = header.get(index - 1).getDisplayName();
539 
540  return getMaxColumnWidth(index, metrics, margin, padding, headerName, table);
541  }
542 
555  private synchronized int getMaxColumnWidth(int index, FontMetrics metrics, int margin, int padding, String header, Object[][] table) {
556  // set the tree (the node / names column) width
557  String headerName = header;
558  int headerWidth = metrics.stringWidth(headerName); // length of the header
559  int colWidth = 0;
560 
561  // Get maximum width of column data
562  for (int i = 0; i < table.length; i++) {
563  if (table[i] == null || index >= table[i].length) {
564  continue;
565  }
566  String test = table[i][index].toString();
567  colWidth = Math.max(colWidth, metrics.stringWidth(test));
568  }
569 
570  colWidth += padding; // add the padding on the most left gap
571  headerWidth += 8; // add the padding to the header (change this value if the header padding value is changed)
572 
573  // Set the width
574  int width = Math.max(headerWidth, colWidth);
575  width += 2 * margin; // Add margin
576 
577  return width;
578  }
579 
580  @Override
581  public void clearComponent() {
582  this.tableScrollPanel.removeAll();
583  this.tableScrollPanel = null;
584 
585  super.clearComponent();
586  }
587 
588  private class DummyNodeListener implements NodeListener {
589 
590  private volatile boolean load = true;
591 
592  public void reset() {
593  load = true;
594  }
595 
596  @Override
597  public void childrenAdded(final NodeMemberEvent nme) {
598  Node[] delta = nme.getDelta();
599  if (load && containsReal(delta)) {
600  load = false;
601  if (SwingUtilities.isEventDispatchThread()) {
602  setupTable(nme.getNode());
603  } else {
604  SwingUtilities.invokeLater(new Runnable() {
605  @Override
606  public void run() {
607  setupTable(nme.getNode());
608  }
609  });
610  }
611  }
612  }
613 
614  private boolean containsReal(Node[] delta) {
615  for (Node n : delta) {
616  if (!n.getDisplayName().equals(DUMMY_NODE_DISPLAY_NAME)) {
617  return true;
618  }
619  }
620  return false;
621  }
622 
623  @Override
624  public void childrenRemoved(NodeMemberEvent nme) {
625  }
626 
627  @Override
628  public void childrenReordered(NodeReorderEvent nre) {
629  }
630 
631  @Override
632  public void nodeDestroyed(NodeEvent ne) {
633  }
634 
635  @Override
636  public void propertyChange(PropertyChangeEvent evt) {
637  }
638  }
639 }
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-2016 Basis Technology. Generated on: Tue Oct 25 2016
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.