Autopsy  4.0
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 // });
130  ov.getOutline().addMouseListener(new MouseListener() {
131  @Override
132  public void mousePressed(MouseEvent e) {
133  }
134 
135  @Override
136  public void mouseReleased(MouseEvent e) {
137  }
138 
139  @Override
140  public void mouseEntered(MouseEvent e) {
141  }
142 
143  @Override
144  public void mouseExited(MouseEvent e) {
145  }
146 
147  @Override
148  public void mouseClicked(MouseEvent e) {
149  if (e.getClickCount() == 2) {
150  Node[] nodes = DataResultViewerTable.this.em.getSelectedNodes();
151  for (Node node : nodes) {
152  Action action = node.getPreferredAction();
153  if (action != null) {
154  action.actionPerformed(null);
155  }
156  }
157  }
158  }
159  });
160  }
161 
167  @Override
168  public void expandNode(Node n) {
169  super.expandNode(n);
170 
171  if (this.tableScrollPanel != null) {
172  OutlineView ov = ((OutlineView) this.tableScrollPanel);
173  ov.expandNode(n);
174  }
175  }
176 
182  @SuppressWarnings("unchecked")
183  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
184  private void initComponents() {
185 
186  tableScrollPanel = new OutlineView(this.firstColumnLabel);
187 
188  //new TreeTableView()
189  tableScrollPanel.addComponentListener(new java.awt.event.ComponentAdapter() {
190  public void componentResized(java.awt.event.ComponentEvent evt) {
192  }
193  });
194 
195  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
196  this.setLayout(layout);
197  layout.setHorizontalGroup(
198  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
199  .addComponent(tableScrollPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE)
200  );
201  layout.setVerticalGroup(
202  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
203  .addComponent(tableScrollPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE)
204  );
205  }// </editor-fold>//GEN-END:initComponents
206 
207  private void tableScrollPanelComponentResized(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_tableScrollPanelComponentResized
208  }//GEN-LAST:event_tableScrollPanelComponentResized
209  // Variables declaration - do not modify//GEN-BEGIN:variables
210  private javax.swing.JScrollPane tableScrollPanel;
211  // End of variables declaration//GEN-END:variables
212 
220  private Node.Property<?>[] getChildPropertyHeaders(Node parent) {
221  Node firstChild = parent.getChildren().getNodeAt(0);
222 
223  if (firstChild == null) {
224  throw new IllegalArgumentException(
225  NbBundle.getMessage(this.getClass(), "DataResultViewerTable.illegalArgExc.noChildFromParent"));
226  } else {
227  for (PropertySet ps : firstChild.getPropertySets()) {
228  if (ps.getName().equals(Sheet.PROPERTIES)) {
229  return ps.getProperties();
230  }
231  }
232 
233  throw new IllegalArgumentException(
234  NbBundle.getMessage(this.getClass(), "DataResultViewerTable.illegalArgExc.childWithoutPropertySet"));
235  }
236  }
237 
247  @SuppressWarnings("rawtypes")
248  private Node.Property[] getAllChildPropertyHeaders(Node parent) {
249  Node firstChild = parent.getChildren().getNodeAt(0);
250 
251  Property[] properties = null;
252 
253  if (firstChild == null) {
254  throw new IllegalArgumentException(
255  NbBundle.getMessage(this.getClass(), "DataResultViewerTable.illegalArgExc.noChildFromParent"));
256  } else {
257  Set<Property> allProperties = new LinkedHashSet<>();
258  while (firstChild != null) {
259  for (PropertySet ps : firstChild.getPropertySets()) {
260  final Property[] props = ps.getProperties();
261  final int propsNum = props.length;
262  for (int i = 0; i < propsNum; ++i) {
263  allProperties.add(props[i]);
264  }
265  }
266  firstChild = firstChild.getChildren().getNodeAt(0);
267  }
268 
269  properties = allProperties.toArray(new Property<?>[0]);
270  }
271  return properties;
272 
273  }
274 
284  private void getAllChildPropertyHeadersRec(Node parent, int rows) {
285  Children children = parent.getChildren();
286  int childCount = 0;
287  for (Node child : children.getNodes()) {
288  if (++childCount > rows) {
289  return;
290  }
291  for (PropertySet ps : child.getPropertySets()) {
292  final Property<?>[] props = ps.getProperties();
293  final int propsNum = props.length;
294  for (int j = 0; j < propsNum; ++j) {
295  propertiesAcc.add(props[j]);
296  }
297  }
298  getAllChildPropertyHeadersRec(child, rows);
299  }
300  }
301 
302  @Override
303  public boolean isSupported(Node selectedNode) {
304  return true;
305  }
306 
313  @Override
314  public void setNode(Node selectedNode) {
315  // change the cursor to "waiting cursor" for this operation
316  this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
317  try {
318  boolean hasChildren = false;
319 
320  if (selectedNode != null) {
321  // @@@ This just did a DB round trip to get the count and the results were not saved...
322  hasChildren = selectedNode.getChildren().getNodesCount() > 0;
323  }
324 
325  Node oldNode = this.em.getRootContext();
326  if (oldNode != null) {
327  oldNode.removeNodeListener(dummyNodeListener);
328  }
329 
330  // if there's no selection node, do nothing
331  if (hasChildren) {
332  Node root = selectedNode;
333  dummyNodeListener.reset();
334  root.addNodeListener(dummyNodeListener);
335  setupTable(root);
336  } else {
337  final OutlineView ov = ((OutlineView) this.tableScrollPanel);
338  Node emptyNode = new AbstractNode(Children.LEAF);
339  em.setRootContext(emptyNode); // make empty node
340  ov.getOutline().setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
341  ov.setPropertyColumns(); // set the empty property header
342  }
343  } finally {
344  this.setCursor(null);
345  }
346  }
347 
354  private void setupTable(final Node root) {
355 
356  em.setRootContext(root);
357 
358  final OutlineView ov = ((OutlineView) this.tableScrollPanel);
359 
360  if (ov == null) {
361  return;
362  }
363 
364  /*
365  * TODO (AUT-1849): Correct or remove peristent column reordering code
366  *
367  * The next three lines of code replaced the three lines of code that
368  * follow
369  */
370 // storeState();
371  // set the new root as current
372 // currentRoot = root;
373 // List<Node.Property<?>> props = loadState();
374  propertiesAcc.clear();
376  List<Node.Property<?>> props = new ArrayList<>(propertiesAcc);
377 
378  /*
379  * OutlineView makes the first column be the result of
380  * node.getDisplayName with the icon. This duplicates our first column,
381  * which is the file name, etc. So, pop that property off the list, but
382  * use its display name as the header for the column so that the header
383  * can change depending on the type of data being displayed.
384  *
385  * NOTE: This assumes that the first property is always the one tha
386  * duplicates getDisplayName(). This seems like a big assumption and
387  * could be made more robust.
388  */
389  if (props.size() > 0) {
390  Node.Property<?> prop = props.remove(0);
391  ((DefaultOutlineModel) ov.getOutline().getOutlineModel()).setNodesColumnLabel(prop.getDisplayName());
392  }
393 
394  // Get the columns setup with respect to names and sortability
395  String[] propStrings = new String[props.size() * 2];
396  for (int i = 0; i < props.size(); i++) {
397  props.get(i).setValue("ComparableColumnTTV", Boolean.TRUE); //NON-NLS
398  //First property column is sorted initially
399  if (i == 0) {
400  props.get(i).setValue("TreeColumnTTV", Boolean.TRUE); // Identifies special property representing first (tree) column. NON-NLS
401  props.get(i).setValue("SortingColumnTTV", Boolean.TRUE); // TreeTableView should be initially sorted by this property column. NON-NLS
402  }
403  propStrings[2 * i] = props.get(i).getName();
404  propStrings[2 * i + 1] = props.get(i).getDisplayName();
405  }
406 
407  ov.setPropertyColumns(propStrings);
408 
409  // show the horizontal scroll panel and show all the content & header
410  int totalColumns = props.size();
411 
412  //int scrollWidth = ttv.getWidth();
413  int margin = 4;
414  int startColumn = 1;
415 
416  // If there is only one column (which was removed from props above)
417  // Just let the table resize itself.
418  ov.getOutline().setAutoResizeMode((props.size() > 0) ? JTable.AUTO_RESIZE_OFF : JTable.AUTO_RESIZE_ALL_COLUMNS);
419 
420  // get first 100 rows values for the table
421  Object[][] content;
422  content = getRowValues(root, 100);
423 
424  if (content != null) {
425  // get the fontmetrics
426  final Graphics graphics = ov.getGraphics();
427  if (graphics != null) {
428  final FontMetrics metrics = graphics.getFontMetrics();
429 
430  // for the "Name" column
431  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!
432  ov.getOutline().getColumnModel().getColumn(0).setPreferredWidth(nodeColWidth);
433 
434  // get the max for each other column
435  for (int colIndex = startColumn; colIndex <= totalColumns; colIndex++) {
436  int colWidth = Math.min(getMaxColumnWidth(colIndex, metrics, margin, 8, props, content), 350);
437  ov.getOutline().getColumnModel().getColumn(colIndex).setPreferredWidth(colWidth);
438  }
439  }
440 
441  // if there's no content just auto resize all columns
442  if (content.length <= 0) {
443  // turn on the auto resize
444  ov.getOutline().setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
445  }
446  }
447  }
448 
449  /*
450  * TODO (AUT-1849): Correct or remove peristent column reordering code
451  *
452  * The following three methods were added for this feature
453  */
454  // Store the state of current root Node.
455 // private void storeState() {
456 // if(currentRoot == null || propertiesAcc.isEmpty())
457 // return;
458 //
459 // TableFilterNode tfn;
460 // if(currentRoot instanceof TableFilterNode)
461 // tfn = (TableFilterNode) currentRoot;
462 // else
463 // return;
464 //
465 // List<Node.Property<?>> props = new ArrayList<>(propertiesAcc);
466 // for (int i = 0; i < props.size(); i++) {
467 // Property<?> prop = props.get(i);
468 // NbPreferences.forModule(this.getClass()).put(getUniqueColName(prop, tfn.getItemType()), String.valueOf(i));
469 // }
470 // }
471  // Load the state of current root Node if exists.
472 // private List<Node.Property<?>> loadState() {
473 // propertiesAcc.clear();
474 // this.getAllChildPropertyHeadersRec(currentRoot, 100);
475 // List<Node.Property<?>> props = new ArrayList<>(propertiesAcc);
476 //
477 // // If node is not table filter node, use default order for columns
478 // TableFilterNode tfn;
479 // if (currentRoot instanceof TableFilterNode) {
480 // tfn = (TableFilterNode) currentRoot;
481 // } else {
482 // Logger.getLogger(DataResultViewerTable.class.getName()).log(Level.INFO,
483 // "Node {0} is not TableFilterNode, columns are going to be in default order", currentRoot.getName());
484 // return props;
485 // }
486 //
487 // List<Node.Property<?>> orderedProps = new ArrayList<>(propertiesAcc);
488 // for (Property<?> prop : props) {
489 // Integer value = Integer.valueOf(NbPreferences.forModule(this.getClass()).get(getUniqueColName(prop, tfn.getItemType()), "-1"));
490 // if (value >= 0) {
491 // /**
492 // * The original contents of orderedProps do not matter when
493 // * setting the new ordered values. The reason we copy
494 // * propertiesAcc into it first is to give it the currect size so
495 // * we can set() in any index.
496 // */
497 // orderedProps.set(value, prop);
498 // }
499 // }
500 // propertiesAcc.clear();
501 // for (Property<?> prop : orderedProps) {
502 // propertiesAcc.add(prop);
503 // }
504 // return orderedProps;
505 // }
506 //
507 // // Get unique name for node and it's property.
508 // private String getUniqueColName(Property<?> prop, String type) {
509 // return Case.getCurrentCase().getName() + "." + type + "."
510 // + prop.getName().replaceAll("[^a-zA-Z0-9_]", "") + ".columnOrder";
511 // }
512 
513  // Populate a two-dimensional array with rows of property values for up
514  // to maxRows children of the node passed in.
515  private static Object[][] getRowValues(Node node, int maxRows) {
516  int numRows = Math.min(maxRows, node.getChildren().getNodesCount());
517  Object[][] rowValues = new Object[numRows][];
518  int rowCount = 0;
519  for (Node child : node.getChildren().getNodes()) {
520  if (rowCount >= maxRows) {
521  break;
522  }
523  // BC: I got this once, I think it was because the table
524  // refreshed while we were in this method
525  // could be better synchronized. Or it was from
526  // the lazy nodes updating... Didn't have time
527  // to fully debug it.
528  if (rowCount > numRows) {
529  break;
530  }
531  PropertySet[] propertySets = child.getPropertySets();
532  if (propertySets.length > 0) {
533  Property<?>[] properties = propertySets[0].getProperties();
534  rowValues[rowCount] = new Object[properties.length];
535  for (int j = 0; j < properties.length; ++j) {
536  try {
537  rowValues[rowCount][j] = properties[j].getValue();
538  } catch (IllegalAccessException | InvocationTargetException ignore) {
539  rowValues[rowCount][j] = "n/a"; //NON-NLS
540  }
541  }
542  }
543  ++rowCount;
544  }
545  return rowValues;
546  }
547 
548  @Override
549  public String getTitle() {
550  return NbBundle.getMessage(this.getClass(), "DataResultViewerTable.title");
551  }
552 
553  @Override
555  return new DataResultViewerTable();
556  }
557 
570  @SuppressWarnings("rawtypes")
571  private int getMaxColumnWidth(int index, FontMetrics metrics, int margin, int padding, List<Node.Property<?>> header, Object[][] table) {
572  // set the tree (the node / names column) width
573  String headerName = header.get(index - 1).getDisplayName();
574 
575  return getMaxColumnWidth(index, metrics, margin, padding, headerName, table);
576  }
577 
590  private synchronized int getMaxColumnWidth(int index, FontMetrics metrics, int margin, int padding, String header, Object[][] table) {
591  // set the tree (the node / names column) width
592  String headerName = header;
593  int headerWidth = metrics.stringWidth(headerName); // length of the header
594  int colWidth = 0;
595 
596  // Get maximum width of column data
597  for (int i = 0; i < table.length; i++) {
598  if (table[i] == null || index >= table[i].length) {
599  continue;
600  }
601  String test = table[i][index].toString();
602  colWidth = Math.max(colWidth, metrics.stringWidth(test));
603  }
604 
605  colWidth += padding; // add the padding on the most left gap
606  headerWidth += 8; // add the padding to the header (change this value if the header padding value is changed)
607 
608  // Set the width
609  int width = Math.max(headerWidth, colWidth);
610  width += 2 * margin; // Add margin
611 
612  return width;
613  }
614 
615  @Override
616  public void clearComponent() {
617  this.tableScrollPanel.removeAll();
618  this.tableScrollPanel = null;
619 
620  super.clearComponent();
621  }
622 
623  private class DummyNodeListener implements NodeListener {
624 
625  private volatile boolean load = true;
626 
627  public void reset() {
628  load = true;
629  }
630 
631  @Override
632  public void childrenAdded(final NodeMemberEvent nme) {
633  Node[] delta = nme.getDelta();
634  if (load && containsReal(delta)) {
635  load = false;
636  if (SwingUtilities.isEventDispatchThread()) {
637  setupTable(nme.getNode());
638  } else {
639  SwingUtilities.invokeLater(new Runnable() {
640  @Override
641  public void run() {
642  setupTable(nme.getNode());
643  }
644  });
645  }
646  }
647  }
648 
649  private boolean containsReal(Node[] delta) {
650  for (Node n : delta) {
651  if (!n.getDisplayName().equals(DUMMY_NODE_DISPLAY_NAME)) {
652  return true;
653  }
654  }
655  return false;
656  }
657 
658  @Override
659  public void childrenRemoved(NodeMemberEvent nme) {
660  }
661 
662  @Override
663  public void childrenReordered(NodeReorderEvent nre) {
664  }
665 
666  @Override
667  public void nodeDestroyed(NodeEvent ne) {
668  }
669 
670  @Override
671  public void propertyChange(PropertyChangeEvent evt) {
672  }
673  }
674 }
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: Wed Apr 6 2016
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.