Autopsy  4.19.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 2012-2021 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 com.google.common.eventbus.Subscribe;
22 import java.awt.Component;
23 import java.awt.Cursor;
24 import java.awt.dnd.DnDConstants;
25 import java.awt.event.MouseAdapter;
26 import java.awt.event.MouseEvent;
27 import java.beans.FeatureDescriptor;
28 import java.beans.PropertyChangeEvent;
29 import java.beans.PropertyVetoException;
30 import java.lang.reflect.InvocationTargetException;
31 import java.util.ArrayList;
32 import java.util.Comparator;
33 import java.util.HashMap;
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Queue;
38 import java.util.TreeMap;
39 import java.util.TreeSet;
40 import java.util.concurrent.ConcurrentHashMap;
41 import java.util.logging.Level;
42 import java.util.prefs.PreferenceChangeEvent;
43 import java.util.prefs.Preferences;
44 import javax.swing.ImageIcon;
45 import javax.swing.JOptionPane;
46 import javax.swing.JTable;
47 import javax.swing.ListSelectionModel;
48 import static javax.swing.SwingConstants.CENTER;
49 import javax.swing.SwingUtilities;
50 import javax.swing.UIManager;
51 import javax.swing.event.ChangeEvent;
52 import javax.swing.event.ListSelectionEvent;
53 import javax.swing.event.TableColumnModelEvent;
54 import javax.swing.event.TableColumnModelListener;
55 import javax.swing.event.TreeExpansionListener;
56 import javax.swing.table.TableCellRenderer;
57 import javax.swing.table.TableColumn;
58 import javax.swing.table.TableColumnModel;
59 import org.netbeans.swing.etable.ETableColumn;
60 import org.netbeans.swing.etable.ETableColumnModel;
61 import org.netbeans.swing.outline.DefaultOutlineCellRenderer;
62 import org.netbeans.swing.outline.DefaultOutlineModel;
63 import org.netbeans.swing.outline.Outline;
64 import org.openide.explorer.ExplorerManager;
65 import org.openide.explorer.view.OutlineView;
66 import org.openide.nodes.AbstractNode;
67 import org.openide.nodes.Children;
68 import org.openide.nodes.Node;
69 import org.openide.nodes.Node.Property;
70 import org.openide.nodes.NodeEvent;
71 import org.openide.nodes.NodeListener;
72 import org.openide.nodes.NodeMemberEvent;
73 import org.openide.nodes.NodeReorderEvent;
74 import org.openide.util.ImageUtilities;
75 import org.openide.util.NbBundle;
76 import org.openide.util.NbPreferences;
77 import org.openide.util.lookup.ServiceProvider;
89 import org.sleuthkit.datamodel.Score.Significance;
90 
101 @ServiceProvider(service = DataResultViewer.class)
102 @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
104 
105  private static final long serialVersionUID = 1L;
106  private static final Logger LOGGER = Logger.getLogger(DataResultViewerTable.class.getName());
107 
108  // How many rows to sample in order to determine column width.
109  private static final int SAMPLE_ROW_NUM = 100;
110 
111  // The padding to be added in addition to content size when considering column width.
112  private static final int COLUMN_PADDING = 15;
113 
114  // The minimum column width.
115  private static final int MIN_COLUMN_WIDTH = 30;
116 
117  // The maximum column width.
118  private static final int MAX_COLUMN_WIDTH = 300;
119 
120  // The minimum row height to use when calculating whether scroll bar will be used.
121  private static final int MIN_ROW_HEIGHT = 10;
122 
123  // The width of the scroll bar.
124  private static final int SCROLL_BAR_WIDTH = ((Integer) UIManager.get("ScrollBar.width")).intValue();
125 
126  // Any additional padding to be used for the first column.
127  private static final int FIRST_COL_ADDITIONAL_WIDTH = 0;
128 
129  private static final String NOTEPAD_ICON_PATH = "org/sleuthkit/autopsy/images/notepad16.png";
130  private static final String RED_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/red-circle-exclamation.png";
131  private static final String YELLOW_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/yellow-circle-yield.png";
132  private static final ImageIcon COMMENT_ICON = new ImageIcon(ImageUtilities.loadImage(NOTEPAD_ICON_PATH, false));
133  private static final ImageIcon INTERESTING_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(YELLOW_CIRCLE_ICON_PATH, false));
134  private static final ImageIcon NOTABLE_ICON_SCORE = new ImageIcon(ImageUtilities.loadImage(RED_CIRCLE_ICON_PATH, false));
135  @NbBundle.Messages("DataResultViewerTable.firstColLbl=Name")
136  static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl();
137  private final String title;
138  private final Map<String, ETableColumn> columnMap;
139  private final Map<Integer, Property<?>> propertiesMap;
140  private final Outline outline;
143  private Node rootNode;
144 
150  private final Map<String, PagingSupport> nodeNameToPagingSupportMap = new ConcurrentHashMap<>();
151 
155  private PagingSupport pagingSupport = null;
156 
165  this(null, Bundle.DataResultViewerTable_title());
166  }
167 
177  public DataResultViewerTable(ExplorerManager explorerManager) {
178  this(explorerManager, Bundle.DataResultViewerTable_title());
179  }
180 
191  public DataResultViewerTable(ExplorerManager explorerManager, String title) {
192  super(explorerManager);
193  this.title = title;
194  this.columnMap = new HashMap<>();
195  this.propertiesMap = new TreeMap<>();
196 
197  /*
198  * Execute the code generated by the GUI builder.
199  */
200  initComponents();
201 
202  initializePagingSupport();
203 
204  /*
205  * Disable the CSV export button for the common properties results
206  */
208  exportCSVButton.setEnabled(false);
209  }
210 
211  /*
212  * Configure the child OutlineView (explorer view) component.
213  */
214  outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE);
215 
216  outline = outlineView.getOutline();
217  outline.setRowSelectionAllowed(true);
218  outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
219  outline.setRootVisible(false);
220  outline.setDragEnabled(false);
221 
222  /*
223  * Add a table listener to the child OutlineView (explorer view) to
224  * persist the order of the table columns when a column is moved.
225  */
226  outlineViewListener = new TableListener();
227  outline.getColumnModel().addColumnModelListener(outlineViewListener);
228 
229  iconRendererListener = new IconRendererTableListener();
230  outline.getColumnModel().addColumnModelListener(iconRendererListener);
231 
232  /*
233  * Add a mouse listener to the child OutlineView (explorer view) to make
234  * sure the first column of the table is kept in place.
235  */
236  outline.getTableHeader().addMouseListener(outlineViewListener);
237  }
238 
239  private void initializePagingSupport() {
240  if (pagingSupport == null) {
241  pagingSupport = new PagingSupport("");
242  }
243 
244  // Start out with paging controls invisible
245  pagingSupport.togglePageControls(false);
246 
251  UserPreferences.addChangeListener((PreferenceChangeEvent evt) -> {
252  if (evt.getKey().equals(UserPreferences.RESULTS_TABLE_PAGE_SIZE)) {
253  setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
258  nodeNameToPagingSupportMap.values().forEach((ps) -> {
259  ps.postPageSizeChangeEvent();
260  });
261  }
262  });
263  }
264 
274  @Override
276  return new DataResultViewerTable();
277  }
278 
284  @Override
285  @NbBundle.Messages("DataResultViewerTable.title=Table")
286  public String getTitle() {
287  return title;
288  }
289 
298  @Override
299  public boolean isSupported(Node candidateRootNode) {
300  return true;
301  }
302 
308  @Override
310  public void setNode(Node rootNode) {
311  if (!SwingUtilities.isEventDispatchThread()) {
312  LOGGER.log(Level.SEVERE, "Attempting to run setNode() from non-EDT thread");
313  return;
314  }
315 
316  /*
317  * The quick filter must be reset because when determining column width,
318  * ETable.getRowCount is called, and the documentation states that quick
319  * filters must be unset for the method to work "If the quick-filter is
320  * applied the number of rows do not match the number of rows in the
321  * model."
322  */
323  outline.unsetQuickFilter();
324 
325  this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
326  try {
327  if (rootNode != null) {
328  this.rootNode = rootNode;
329 
334  String nodeName = rootNode.getName();
335  pagingSupport = nodeNameToPagingSupportMap.get(nodeName);
336  if (pagingSupport == null) {
337  pagingSupport = new PagingSupport(nodeName);
338  nodeNameToPagingSupportMap.put(nodeName, pagingSupport);
339  }
340  pagingSupport.updateControls();
341 
342  rootNode.addNodeListener(new NodeListener() {
343  @Override
344  public void childrenAdded(NodeMemberEvent nme) {
351  SwingUtilities.invokeLater(() -> {
352  setCursor(null);
353  });
354  }
355 
356  @Override
357  public void childrenRemoved(NodeMemberEvent nme) {
358  SwingUtilities.invokeLater(() -> {
359  setCursor(null);
360  });
361  }
362 
363  @Override
364  public void childrenReordered(NodeReorderEvent nre) {
365  // No-op
366  }
367 
368  @Override
369  public void nodeDestroyed(NodeEvent ne) {
370  // No-op
371  }
372 
373  @Override
374  public void propertyChange(PropertyChangeEvent evt) {
375  // No-op
376  }
377  });
378  }
379 
380  /*
381  * If the given node is not null and has children, set it as the
382  * root context of the child OutlineView, otherwise make an
383  * "empty"node the root context.
384  */
385  if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) {
386  this.getExplorerManager().setRootContext(this.rootNode);
387  outline.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
388  setupTable();
389  } else {
390  Node emptyNode = new AbstractNode(Children.LEAF);
391  this.getExplorerManager().setRootContext(emptyNode);
392  outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
393  outlineViewListener.listenToVisibilityChanges(false);
394  outlineView.setPropertyColumns();
395  }
396  } finally {
397  this.setCursor(null);
398  }
399  }
400 
407  protected void addTreeExpansionListener(TreeExpansionListener listener) {
408  outlineView.addTreeExpansionListener(listener);
409  }
410 
416  private void setupTable() {
417  /*
418  * Since we are modifying the columns, we don't want to listen to
419  * added/removed events as un-hide/hide, until the table setup is done.
420  */
421  outlineViewListener.listenToVisibilityChanges(false);
422  /*
423  * OutlineView makes the first column be the result of
424  * node.getDisplayName with the icon. This duplicates our first column,
425  * which is the file name, etc. So, pop that property off the list, but
426  * use its display name as the header for the column so that the header
427  * can change depending on the type of data being displayed.
428  *
429  * NOTE: This assumes that the first property is always the one that
430  * duplicates getDisplayName(). The current implementation does not
431  * allow the first property column to be moved.
432  */
433  List<Node.Property<?>> props = loadColumnOrder();
434  boolean propsExist = props.isEmpty() == false;
435  Node.Property<?> firstProp = null;
436  if (propsExist) {
437  firstProp = props.remove(0);
438  }
439 
440  assignColumns(props); // assign columns to match the properties
441  if (firstProp != null) {
442  ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(firstProp.getDisplayName());
443  }
444 
445  /*
446  * Load column sorting information from preferences file and apply it to
447  * columns.
448  */
449  loadColumnSorting();
450 
451  /*
452  * Save references to columns before we deal with their visibility. This
453  * has to happen after the sorting is applied, because that actually
454  * causes the columns to be recreated. It has to happen before
455  * loadColumnVisibility so we have referenecs to the columns to pass to
456  * setColumnHidden.
457  */
458  populateColumnMap();
459 
460  /*
461  * Load column visibility information from preferences file and apply it
462  * to columns.
463  */
464  loadColumnVisibility();
465 
466  /*
467  * Set the column widths.
468  *
469  * IMPORTANT: This needs to come after the preceding calls to determine
470  * the columns that will be displayed and their layout, which includes a
471  * call to ResultViewerPersistence.getAllChildProperties(). That method
472  * calls Children.getNodes(true) on the root node to ensure ALL of the
473  * nodes have been created in the NetBeans asynch child creation thread,
474  * and then uses the first one hundred nodes to determine which columns
475  * to display, including their header text.
476  */
477  setColumnWidths();
478 
479  /*
480  * If one of the child nodes of the root node is to be selected, select
481  * it.
482  */
483  if (rootNode instanceof TableFilterNode) {
484  NodeSelectionInfo selectedChildInfo = ((TableFilterNode) rootNode).getChildNodeSelectionInfo();
485  if (null != selectedChildInfo) {
486  Node[] childNodes = rootNode.getChildren().getNodes(true);
487  for (int i = 0; i < childNodes.length; ++i) {
488  Node childNode = childNodes[i];
489  if (selectedChildInfo.matches(childNode)) {
490  SwingUtilities.invokeLater(() -> {
491  try {
492  this.getExplorerManager().setExploredContextAndSelection(this.rootNode, new Node[]{childNode});
493  } catch (PropertyVetoException ex) {
494  LOGGER.log(Level.SEVERE, "Failed to select node specified by selected child info", ex);
495  }
496  });
497 
498  break;
499  }
500  }
501  ((TableFilterNode) rootNode).setChildNodeSelectionInfo(null);
502  }
503  }
504 
505  /*
506  * The table setup is done, so any added/removed events can now be
507  * treated as un-hide/hide.
508  */
509  outlineViewListener.listenToVisibilityChanges(true);
510 
511  }
512 
513  /*
514  * Populates the column map for the child OutlineView of this tabular result
515  * viewer with references to the column objects for use when loading/storing
516  * the visibility info.
517  */
518  private void populateColumnMap() {
519  columnMap.clear();
520  TableColumnModel columnModel = outline.getColumnModel();
521  int columnCount = columnModel.getColumnCount();
522  //for each property get a reference to the column object from the column model.
523  for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
524  final String propName = entry.getValue().getName();
525  if (entry.getKey() < columnCount) {
526  final ETableColumn column = (ETableColumn) columnModel.getColumn(entry.getKey());
527  columnMap.put(propName, column);
528 
529  }
530  }
531  }
532 
533  /*
534  * Sets the column widths for the child OutlineView of this tabular results
535  * viewer providing any additional width to last column.
536  */
537  protected void setColumnWidths() {
538  // based on https://stackoverflow.com/questions/17627431/auto-resizing-the-jtable-column-widths
539  final TableColumnModel columnModel = outline.getColumnModel();
540 
541  // the remaining table width that can be used in last row
542  double availableTableWidth = outlineView.getSize().getWidth();
543 
544  for (int columnIdx = 0; columnIdx < outline.getColumnCount(); columnIdx++) {
545  int columnPadding = (columnIdx == 0) ? FIRST_COL_ADDITIONAL_WIDTH + COLUMN_PADDING : COLUMN_PADDING;
546  TableColumn tableColumn = columnModel.getColumn(columnIdx);
547 
548  // The width of this column
549  int width = MIN_COLUMN_WIDTH;
550 
551  // get header cell width
552  // taken in part from https://stackoverflow.com/a/18381924
553  TableCellRenderer headerRenderer = tableColumn.getHeaderRenderer();
554  if (headerRenderer == null) {
555  headerRenderer = outline.getTableHeader().getDefaultRenderer();
556  }
557  Object headerValue = tableColumn.getHeaderValue();
558  Component headerComp = headerRenderer.getTableCellRendererComponent(outline, headerValue, false, false, 0, columnIdx);
559  width = Math.max(headerComp.getPreferredSize().width + columnPadding, width);
560 
561  // get the max of row widths from the first SAMPLE_ROW_NUM rows
562  Component comp = null;
563  int rowCount = outline.getRowCount();
564  for (int row = 0; row < Math.min(rowCount, SAMPLE_ROW_NUM); row++) {
565  TableCellRenderer renderer = outline.getCellRenderer(row, columnIdx);
566  comp = outline.prepareRenderer(renderer, row, columnIdx);
567  width = Math.max(comp.getPreferredSize().width + columnPadding, width);
568  }
569 
570  // no higher than maximum column width
571  if (width > MAX_COLUMN_WIDTH) {
572  width = MAX_COLUMN_WIDTH;
573  }
574 
575  // if last column, calculate remaining width factoring in the possibility of a scroll bar.
576  if (columnIdx == outline.getColumnCount() - 1) {
577  int rowHeight = comp == null ? MIN_ROW_HEIGHT : comp.getPreferredSize().height;
578  if (headerComp.getPreferredSize().height + rowCount * rowHeight > outlineView.getSize().getHeight()) {
579  availableTableWidth -= SCROLL_BAR_WIDTH;
580  }
581 
582  columnModel.getColumn(columnIdx).setPreferredWidth(Math.max(width, (int) availableTableWidth));
583  } else {
584  // otherwise set preferred width to width and decrement availableTableWidth accordingly
585  columnModel.getColumn(columnIdx).setPreferredWidth(width);
586  availableTableWidth -= width;
587  }
588  }
589  }
590 
591  protected TableColumnModel getColumnModel() {
592  return outline.getColumnModel();
593  }
594 
595  /*
596  * Sets up the columns for the child OutlineView of this tabular results
597  * viewer with respect to column names and visisbility.
598  */
599  synchronized private void assignColumns(List<Property<?>> props) {
600  String[] propStrings = new String[props.size() * 2];
601  for (int i = 0; i < props.size(); i++) {
602  final Property<?> prop = props.get(i);
603  prop.setValue("ComparableColumnTTV", Boolean.TRUE); //NON-NLS
604  //First property column is sorted initially
605  if (i == 0) {
606  prop.setValue("TreeColumnTTV", Boolean.TRUE); // Identifies special property representing first (tree) column. NON-NLS
607  prop.setValue("SortingColumnTTV", Boolean.TRUE); // TreeTableView should be initially sorted by this property column. NON-NLS
608  }
609  propStrings[2 * i] = prop.getName();
610  propStrings[2 * i + 1] = prop.getDisplayName();
611  }
612  outlineView.setPropertyColumns(propStrings);
613  }
614 
619  private synchronized void storeColumnVisibility() {
620  if (rootNode == null || propertiesMap.isEmpty()) {
621  return;
622  }
623  if (rootNode instanceof TableFilterNode) {
624  TableFilterNode tfn = (TableFilterNode) rootNode;
625  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
626  final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
627  for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
628  String columnName = entry.getKey();
629  final String columnHiddenKey = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName);
630  final TableColumn column = entry.getValue();
631  boolean columnHidden = columnModel.isColumnHidden(column);
632  if (columnHidden) {
633  preferences.putBoolean(columnHiddenKey, true);
634  } else {
635  preferences.remove(columnHiddenKey);
636  }
637  }
638  }
639  }
640 
645  private synchronized void storeColumnOrder() {
646  if (rootNode == null || propertiesMap.isEmpty()) {
647  return;
648  }
649  if (rootNode instanceof TableFilterNode) {
650  TableFilterNode tfn = (TableFilterNode) rootNode;
651  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
652  // Store the current order of the columns into settings
653  for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
654  preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey());
655  }
656  }
657  }
658 
662  private synchronized void storeColumnSorting() {
663  if (rootNode == null || propertiesMap.isEmpty()) {
664  return;
665  }
666  if (rootNode instanceof TableFilterNode) {
667  final TableFilterNode tfn = ((TableFilterNode) rootNode);
668  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
669  ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
670  for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
671  ETableColumn etc = entry.getValue();
672  String columnName = entry.getKey();
673  //store sort rank and order
674  final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName);
675  final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, columnName);
676  if (etc.isSorted() && (columnModel.isColumnHidden(etc) == false)) {
677  preferences.putBoolean(columnSortOrderKey, etc.isAscending());
678  preferences.putInt(columnSortRankKey, etc.getSortRank());
679  } else {
680  columnModel.setColumnSorted(etc, true, 0);
681  preferences.remove(columnSortOrderKey);
682  preferences.remove(columnSortRankKey);
683  }
684  }
685  }
686  }
687 
694  private synchronized void loadColumnSorting() {
695  if (rootNode == null || propertiesMap.isEmpty()) {
696  return;
697  }
698  if (rootNode instanceof TableFilterNode) {
699  final TableFilterNode tfn = (TableFilterNode) rootNode;
700  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
701  //organize property sorting information, sorted by rank
702  TreeSet<ColumnSortInfo> sortInfos = new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank));
703  propertiesMap.entrySet().stream().forEach(entry -> {
704  final String propName = entry.getValue().getName();
705  //if the sort rank is undefined, it will be defaulted to 0 => unsorted.
706  Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0);
707  //default to true => ascending
708  Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName), true);
709  sortInfos.add(new ColumnSortInfo(entry.getKey(), sortRank, sortOrder));
710  });
711  //apply sort information in rank order.
712  sortInfos.forEach(sortInfo -> outline.setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank));
713  }
714  }
715 
720  private synchronized void loadColumnVisibility() {
721  if (rootNode == null || propertiesMap.isEmpty()) {
722  return;
723  }
724  if (rootNode instanceof TableFilterNode) {
725  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
726  final TableFilterNode tfn = ((TableFilterNode) rootNode);
727  ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
728  for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
729  final String propName = entry.getValue().getName();
730  boolean hidden = preferences.getBoolean(ResultViewerPersistence.getColumnHiddenKey(tfn, propName), false);
731  final TableColumn column = columnMap.get(propName);
732  columnModel.setColumnHidden(column, hidden);
733  }
734  }
735  }
736 
745  private synchronized List<Node.Property<?>> loadColumnOrder() {
746 
747  List<Property<?>> props = ResultViewerPersistence.getAllChildProperties(rootNode, 100);
748 
749  // If node is not table filter node, use default order for columns
750  if (!(rootNode instanceof TableFilterNode)) {
751  return props;
752  }
753 
754  final TableFilterNode tfn = ((TableFilterNode) rootNode);
755  propertiesMap.clear();
756 
757  /*
758  * We load column index values into the properties map. If a property's
759  * index is outside the range of the number of properties or the index
760  * has already appeared as the position of another property, we put that
761  * property at the end.
762  */
763  int offset = props.size();
764 
765  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
766 
767  for (Property<?> prop : props) {
768  Integer value = preferences.getInt(ResultViewerPersistence.getColumnPositionKey(tfn, prop.getName()), -1);
769  if (value >= 0 && value < offset && !propertiesMap.containsKey(value)) {
770  propertiesMap.put(value, prop);
771  } else {
772  propertiesMap.put(offset, prop);
773  offset++;
774  }
775  }
776 
777  /*
778  * NOTE: it is possible to have "discontinuities" in the keys (i.e.
779  * column numbers) of the map. This happens when some of the columns had
780  * a previous setting, and other columns did not. We need to make the
781  * keys 0-indexed and continuous.
782  */
783  compactPropertiesMap();
784 
785  return new ArrayList<>(propertiesMap.values());
786  }
787 
792  private void compactPropertiesMap() {
793 
794  // check if there are discontinuities in the map keys.
795  int size = propertiesMap.size();
796  Queue<Integer> availablePositions = new LinkedList<>();
797  for (int i = 0; i < size; i++) {
798  if (!propertiesMap.containsKey(i)) {
799  availablePositions.add(i);
800  }
801  }
802 
803  // if there are no discontinuities, we are done
804  if (availablePositions.isEmpty()) {
805  return;
806  }
807 
808  // otherwise, move map elements into the available positions.
809  // we don't want to just move down all elements, as we want to preserve the order
810  // of the ones that had previous setting (i.e. ones that have key < size)
811  ArrayList<Integer> keys = new ArrayList<>(propertiesMap.keySet());
812  for (int key : keys) {
813  if (key >= size) {
814  propertiesMap.put(availablePositions.remove(), propertiesMap.remove(key));
815  }
816  }
817  }
818 
823  @Override
824  public void clearComponent() {
825  this.outlineView.removeAll();
826  this.outlineView = null;
827  super.clearComponent();
828  }
829 
833  static private final class ColumnSortInfo {
834 
835  private final int modelIndex;
836  private final int rank;
837  private final boolean order;
838 
839  private ColumnSortInfo(int modelIndex, int rank, boolean order) {
840  this.modelIndex = modelIndex;
841  this.rank = rank;
842  this.order = order;
843  }
844 
845  private int getRank() {
846  return rank;
847  }
848  }
849 
855  private class PagingSupport {
856 
857  private int currentPage;
858  private int totalPages;
859  private final String nodeName;
860 
861  PagingSupport(String nodeName) {
862  currentPage = 1;
863  totalPages = 0;
864  this.nodeName = nodeName;
865  initialize();
866  }
867 
868  private void initialize() {
869  if (!nodeName.isEmpty()) {
870  BaseChildFactory.register(nodeName, this);
871  }
872  updateControls();
873  }
874 
875  void nextPage() {
876  currentPage++;
877  postPageChangeEvent();
878  }
879 
880  void previousPage() {
881  currentPage--;
882  postPageChangeEvent();
883  }
884 
885  @NbBundle.Messages({"# {0} - totalPages",
886  "DataResultViewerTable.goToPageTextField.msgDlg=Please enter a valid page number between 1 and {0}",
887  "DataResultViewerTable.goToPageTextField.err=Invalid page number"})
888  void gotoPage() {
889  int originalPage = currentPage;
890 
891  try {
892  currentPage = Integer.decode(gotoPageTextField.getText());
893  } catch (NumberFormatException e) {
894  //ignore input
895  return;
896  }
897 
898  if (currentPage > totalPages || currentPage < 1) {
899  currentPage = originalPage;
900  JOptionPane.showMessageDialog(DataResultViewerTable.this,
901  Bundle.DataResultViewerTable_goToPageTextField_msgDlg(totalPages),
902  Bundle.DataResultViewerTable_goToPageTextField_err(),
903  JOptionPane.WARNING_MESSAGE);
904  return;
905  }
906  postPageChangeEvent();
907  }
908 
913  void postPageChangeEvent() {
914  try {
915  BaseChildFactory.post(nodeName, new PageChangeEvent(currentPage));
916  } catch (BaseChildFactory.NoSuchEventBusException ex) {
917  LOGGER.log(Level.WARNING, "Failed to post page change event.", ex); //NON-NLS
918  }
919  DataResultViewerTable.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
920  updateControls();
921  }
922 
927  void postPageSizeChangeEvent() {
928  // Reset page variables when page size changes
929  currentPage = 1;
930 
931  if (this == pagingSupport) {
932  updateControls();
933  }
934  try {
935  BaseChildFactory.post(nodeName, new PageSizeChangeEvent(UserPreferences.getResultsTablePageSize()));
936  } catch (BaseChildFactory.NoSuchEventBusException ex) {
937  LOGGER.log(Level.WARNING, "Failed to post page size change event.", ex); //NON-NLS
938  }
939  }
940 
946  @Subscribe
948  if (event != null) {
949  totalPages = event.getPageCount();
950  if (totalPages > 1) {
951  // Make paging controls visible if there is more than one page.
952  togglePageControls(true);
953  }
954 
955  // Only update UI controls if this event is for the node currently being viewed.
956  if (nodeName.equals(rootNode.getName())) {
957  updateControls();
958  }
959  }
960  }
961 
967  private void togglePageControls(boolean onOff) {
968  pageLabel.setVisible(onOff);
969  pagesLabel.setVisible(onOff);
970  pagePrevButton.setVisible(onOff);
971  pageNextButton.setVisible(onOff);
972  pageNumLabel.setVisible(onOff);
973  gotoPageLabel.setVisible(onOff);
974  gotoPageTextField.setVisible(onOff);
975  gotoPageTextField.setVisible(onOff);
976  validate();
977  repaint();
978  }
979 
980  @NbBundle.Messages({"# {0} - currentPage", "# {1} - totalPages",
981  "DataResultViewerTable.pageNumbers.curOfTotal={0} of {1}"})
982  private void updateControls() {
983  if (totalPages == 0) {
984  pagePrevButton.setEnabled(false);
985  pageNextButton.setEnabled(false);
986  pageNumLabel.setText("");
987  gotoPageTextField.setText("");
988  gotoPageTextField.setEnabled(false);
989  } else {
990  pageNumLabel.setText(Bundle.DataResultViewerTable_pageNumbers_curOfTotal(Integer.toString(currentPage), Integer.toString(totalPages)));
991 
992  pageNextButton.setEnabled(currentPage != totalPages);
993  pagePrevButton.setEnabled(currentPage != 1);
994  gotoPageTextField.setEnabled(totalPages > 1);
995  gotoPageTextField.setText("");
996  }
997  }
998  }
999 
1004  private class IconRendererTableListener implements TableColumnModelListener {
1005 
1006  @NbBundle.Messages({"DataResultViewerTable.commentRender.name=C",
1007  "DataResultViewerTable.commentRender.toolTip=C(omments) indicates whether the item has a comment",
1008  "DataResultViewerTable.scoreRender.name=S",
1009  "DataResultViewerTable.scoreRender.toolTip=S(core) indicates whether the item is interesting or notable",
1010  "DataResultViewerTable.countRender.name=O",
1011  "DataResultViewerTable.countRender.toolTip=O(ccurrences) indicates the number of data sources containing the item in the Central Repository"})
1012  @Override
1013  public void columnAdded(TableColumnModelEvent e) {
1014  if (e.getSource() instanceof ETableColumnModel) {
1015  TableColumn column = ((TableColumnModel) e.getSource()).getColumn(e.getToIndex());
1016  if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_commentRender_name())) {
1017  //if the current column is a comment column set the cell renderer to be the HasCommentCellRenderer
1018  outlineView.setPropertyColumnDescription(column.getHeaderValue().toString(), Bundle.DataResultViewerTable_commentRender_toolTip());
1019  column.setCellRenderer(new HasCommentCellRenderer());
1020  } else if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_scoreRender_name())) {
1021  //if the current column is a score column set the cell renderer to be the ScoreCellRenderer
1022  outlineView.setPropertyColumnDescription(column.getHeaderValue().toString(), Bundle.DataResultViewerTable_scoreRender_toolTip());
1023  column.setCellRenderer(new ScoreCellRenderer());
1024  } else if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_countRender_name())) {
1025  outlineView.setPropertyColumnDescription(column.getHeaderValue().toString(), Bundle.DataResultViewerTable_countRender_toolTip());
1026  column.setCellRenderer(new CountCellRenderer());
1027  }
1028  }
1029  }
1030 
1031  @Override
1032  public void columnRemoved(TableColumnModelEvent e
1033  ) {
1034  //Don't do anything when column removed
1035  }
1036 
1037  @Override
1038  public void columnMoved(TableColumnModelEvent e
1039  ) {
1040  //Don't do anything when column moved
1041  }
1042 
1043  @Override
1044  public void columnMarginChanged(ChangeEvent e
1045  ) {
1046  //Don't do anything when column margin changed
1047  }
1048 
1049  @Override
1050  public void columnSelectionChanged(ListSelectionEvent e
1051  ) {
1052  //Don't do anything when column selection changed
1053  }
1054 
1055  }
1056 
1061  private class TableListener extends MouseAdapter implements TableColumnModelListener {
1062 
1063  // When a column in the table is moved, these two variables keep track of where
1064  // the column started and where it ended up.
1065  private int startColumnIndex = -1;
1066  private int endColumnIndex = -1;
1067  private boolean listenToVisibilitEvents;
1068 
1069  @Override
1070  public void columnMoved(TableColumnModelEvent e) {
1071  int fromIndex = e.getFromIndex();
1072  int toIndex = e.getToIndex();
1073  if (fromIndex == toIndex) {
1074  return;
1075  }
1076 
1077  /*
1078  * Because a column may be dragged to several different positions
1079  * before the mouse is released (thus causing multiple
1080  * TableColumnModelEvents to be fired), we want to keep track of the
1081  * starting column index in this potential series of movements.
1082  * Therefore we only keep track of the original fromIndex in
1083  * startColumnIndex, but we always update endColumnIndex to know the
1084  * final position of the moved column. See the MouseListener
1085  * mouseReleased method.
1086  */
1087  if (startColumnIndex == -1) {
1088  startColumnIndex = fromIndex;
1089  }
1090  endColumnIndex = toIndex;
1091 
1092  // This list contains the keys of propertiesMap in order
1093  ArrayList<Integer> indicesList = new ArrayList<>(propertiesMap.keySet());
1094  int leftIndex = Math.min(fromIndex, toIndex);
1095  int rightIndex = Math.max(fromIndex, toIndex);
1096  // Now we can copy the range of keys that have been affected by
1097  // the column movement
1098  List<Integer> range = indicesList.subList(leftIndex, rightIndex + 1);
1099  int rangeSize = range.size();
1100 
1101  if (fromIndex < toIndex) {
1102  // column moved right, shift all properties left, put in moved
1103  // property at the rightmost index
1104  Property<?> movedProp = propertiesMap.get(range.get(0));
1105  for (int i = 0; i < rangeSize - 1; i++) {
1106  propertiesMap.put(range.get(i), propertiesMap.get(range.get(i + 1)));
1107  }
1108  propertiesMap.put(range.get(rangeSize - 1), movedProp);
1109  } else {
1110  // column moved left, shift all properties right, put in moved
1111  // property at the leftmost index
1112  Property<?> movedProp = propertiesMap.get(range.get(rangeSize - 1));
1113  for (int i = rangeSize - 1; i > 0; i--) {
1114  propertiesMap.put(range.get(i), propertiesMap.get(range.get(i - 1)));
1115  }
1116  propertiesMap.put(range.get(0), movedProp);
1117  }
1118 
1119  storeColumnOrder();
1120  }
1121 
1122  @Override
1123  public void mouseReleased(MouseEvent e) {
1124  /*
1125  * If the startColumnIndex is not -1 (which is the reset value),
1126  * that means columns have been moved around. We then check to see
1127  * if either the starting or end position is 0 (the first column),
1128  * and then swap them back if that is the case because we don't want
1129  * to allow movement of the first column. We then reset
1130  * startColumnIndex to -1, the reset value. We check if
1131  * startColumnIndex is at reset or not because it is possible for
1132  * the mouse to be released and a MouseEvent to be fired without
1133  * having moved any columns.
1134  */
1135  if (startColumnIndex != -1 && (startColumnIndex == 0 || endColumnIndex == 0)) {
1136  outline.moveColumn(endColumnIndex, startColumnIndex);
1137  }
1138  startColumnIndex = -1;
1139  }
1140 
1141  @Override
1142  public void mouseClicked(MouseEvent e) {
1143  //the user clicked a column header
1144  storeColumnSorting();
1145  }
1146 
1147  @Override
1148  public void columnAdded(TableColumnModelEvent e) {
1149  columnAddedOrRemoved();
1150  }
1151 
1152  @Override
1153  public void columnRemoved(TableColumnModelEvent e) {
1154  columnAddedOrRemoved();
1155  }
1156 
1162  private void columnAddedOrRemoved() {
1163  if (listenToVisibilitEvents) {
1164  SwingUtilities.invokeLater(DataResultViewerTable.this::storeColumnVisibility);
1165 
1166  }
1167  }
1168 
1169  @Override
1170  public void columnMarginChanged(ChangeEvent e) {
1171  }
1172 
1173  @Override
1174  public void columnSelectionChanged(ListSelectionEvent e) {
1175  }
1176 
1186  private void listenToVisibilityChanges(boolean b) {
1187  this.listenToVisibilitEvents = b;
1188  }
1189  }
1190 
1191  /*
1192  * A renderer which based on the contents of the cell will display an icon
1193  * to indicate the presence of a comment related to the content.
1194  */
1195  private final class HasCommentCellRenderer extends DefaultOutlineCellRenderer {
1196 
1197  private static final long serialVersionUID = 1L;
1198 
1199  @NbBundle.Messages({"DataResultViewerTable.commentRenderer.crComment.toolTip=Comment exists in Central Repository",
1200  "DataResultViewerTable.commentRenderer.tagComment.toolTip=Comment exists on associated tag(s)",
1201  "DataResultViewerTable.commentRenderer.crAndTagComment.toolTip=Comments exist both in Central Repository and on associated tag(s)",
1202  "DataResultViewerTable.commentRenderer.noComment.toolTip=No comments found"})
1203  @Override
1204  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
1205  Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
1206  setBackground(component.getBackground()); //inherit highlighting for selection
1207  setHorizontalAlignment(CENTER);
1208  Object switchValue = null;
1209  if ((value instanceof NodeProperty)) {
1210  //The Outline view has properties in the cell, the value contained in the property is what we want
1211  try {
1212  switchValue = ((Node.Property) value).getValue();
1213  } catch (IllegalAccessException | InvocationTargetException ex) {
1214  //Unable to get the value from the NodeProperty no Icon will be displayed
1215  }
1216  } else {
1217  //JTables contain the value we want directly in the cell
1218  switchValue = value;
1219  }
1220  setText("");
1221  if ((switchValue instanceof HasCommentStatus)) {
1222 
1223  switch ((HasCommentStatus) switchValue) {
1224  case CR_COMMENT:
1225  setIcon(COMMENT_ICON);
1226  setToolTipText(Bundle.DataResultViewerTable_commentRenderer_crComment_toolTip());
1227  break;
1228  case TAG_COMMENT:
1229  setIcon(COMMENT_ICON);
1230  setToolTipText(Bundle.DataResultViewerTable_commentRenderer_tagComment_toolTip());
1231  break;
1232  case CR_AND_TAG_COMMENTS:
1233  setIcon(COMMENT_ICON);
1234  setToolTipText(Bundle.DataResultViewerTable_commentRenderer_crAndTagComment_toolTip());
1235  break;
1236  case TAG_NO_COMMENT:
1237  case NO_COMMENT:
1238  default:
1239  setIcon(null);
1240  setToolTipText(Bundle.DataResultViewerTable_commentRenderer_noComment_toolTip());
1241  }
1242  } else {
1243  setIcon(null);
1244  }
1245 
1246  return this;
1247  }
1248 
1249  }
1250 
1251  /*
1252  * A renderer which based on the contents of the cell will display an icon
1253  * to indicate the score associated with the item.
1254  */
1255  private final class ScoreCellRenderer extends DefaultOutlineCellRenderer {
1256 
1257  private static final long serialVersionUID = 1L;
1258 
1266  private ImageIcon getIcon(Significance significance) {
1267  if (significance == null) {
1268  return null;
1269  }
1270 
1271  switch (significance) {
1272  case NOTABLE:
1273  return NOTABLE_ICON_SCORE;
1274  case LIKELY_NOTABLE:
1275  return INTERESTING_SCORE_ICON;
1276  case LIKELY_NONE:
1277  case NONE:
1278  case UNKNOWN:
1279  default:
1280  return null;
1281  }
1282  }
1283 
1284  @Override
1285  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
1286  Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
1287  setBackground(component.getBackground()); //inherit highlighting for selection
1288  setHorizontalAlignment(CENTER);
1289  Object switchValue = null;
1290  if ((value instanceof NodeProperty)) {
1291  //The Outline view has properties in the cell, the value contained in the property is what we want
1292  try {
1293  switchValue = ((Node.Property) value).getValue();
1294  setToolTipText(((FeatureDescriptor) value).getShortDescription());
1295  } catch (IllegalAccessException | InvocationTargetException ex) {
1296  //Unable to get the value from the NodeProperty no Icon will be displayed
1297  }
1298 
1299  } else {
1300  //JTables contain the value we want directly in the cell
1301  switchValue = value;
1302  }
1303  setText("");
1304  if ((switchValue instanceof org.sleuthkit.datamodel.Score)) {
1305  setIcon(getIcon(((org.sleuthkit.datamodel.Score) switchValue).getSignificance()));
1306  } else {
1307  setIcon(null);
1308  }
1309  return this;
1310  }
1311 
1312  }
1313 
1314  /*
1315  * A renderer which based on the contents of the cell will display an empty
1316  * cell if no count was available.
1317  */
1318  private final class CountCellRenderer extends DefaultOutlineCellRenderer {
1319 
1320  private static final long serialVersionUID = 1L;
1321 
1322  @Override
1323  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
1324  Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
1325  setBackground(component.getBackground()); //inherit highlighting for selection
1326  setHorizontalAlignment(LEFT);
1327  Object countValue = null;
1328  if ((value instanceof NodeProperty)) {
1329  //The Outline view has properties in the cell, the value contained in the property is what we want
1330  try {
1331  countValue = ((Node.Property) value).getValue();
1332  setToolTipText(((FeatureDescriptor) value).getShortDescription());
1333  } catch (IllegalAccessException | InvocationTargetException ex) {
1334  //Unable to get the value from the NodeProperty no Icon will be displayed
1335  }
1336  } else {
1337  //JTables contain the value we want directly in the cell
1338  countValue = value;
1339  }
1340  setText("");
1341  if ((countValue instanceof Long)) {
1342  //Don't display value if value is negative used so that sorting will behave as desired
1343  if ((Long) countValue >= 0) {
1344  setText(countValue.toString());
1345  }
1346  }
1347  return this;
1348  }
1349 
1350  }
1351 
1356  public enum HasCommentStatus {
1361  CR_AND_TAG_COMMENTS
1362  }
1363 
1367  public enum Score {
1370  NOTABLE_SCORE
1371  }
1372 
1378  @SuppressWarnings("unchecked")
1379  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
1380  private void initComponents() {
1381 
1382  pageLabel = new javax.swing.JLabel();
1383  pageNumLabel = new javax.swing.JLabel();
1384  pagesLabel = new javax.swing.JLabel();
1385  pagePrevButton = new javax.swing.JButton();
1386  pageNextButton = new javax.swing.JButton();
1387  outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL);
1388  gotoPageLabel = new javax.swing.JLabel();
1389  gotoPageTextField = new javax.swing.JTextField();
1390  exportCSVButton = new javax.swing.JButton();
1391 
1392  pageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageLabel.text")); // NOI18N
1393 
1394  pageNumLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageNumLabel.text")); // NOI18N
1395 
1396  pagesLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pagesLabel.text")); // NOI18N
1397 
1398  pagePrevButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"))); // NOI18N
1399  pagePrevButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pagePrevButton.text")); // NOI18N
1400  pagePrevButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); // NOI18N
1401  pagePrevButton.setFocusable(false);
1402  pagePrevButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
1403  pagePrevButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
1404  pagePrevButton.setPreferredSize(new java.awt.Dimension(55, 23));
1405  pagePrevButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png"))); // NOI18N
1406  pagePrevButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
1407  pagePrevButton.addActionListener(new java.awt.event.ActionListener() {
1408  public void actionPerformed(java.awt.event.ActionEvent evt) {
1409  pagePrevButtonActionPerformed(evt);
1410  }
1411  });
1412 
1413  pageNextButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"))); // NOI18N
1414  pageNextButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageNextButton.text")); // NOI18N
1415  pageNextButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); // NOI18N
1416  pageNextButton.setFocusable(false);
1417  pageNextButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
1418  pageNextButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
1419  pageNextButton.setMaximumSize(new java.awt.Dimension(27, 23));
1420  pageNextButton.setMinimumSize(new java.awt.Dimension(27, 23));
1421  pageNextButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_hover.png"))); // NOI18N
1422  pageNextButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
1423  pageNextButton.addActionListener(new java.awt.event.ActionListener() {
1424  public void actionPerformed(java.awt.event.ActionEvent evt) {
1425  pageNextButtonActionPerformed(evt);
1426  }
1427  });
1428 
1429  gotoPageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.gotoPageLabel.text")); // NOI18N
1430 
1431  gotoPageTextField.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.gotoPageTextField.text")); // NOI18N
1432  gotoPageTextField.addActionListener(new java.awt.event.ActionListener() {
1433  public void actionPerformed(java.awt.event.ActionEvent evt) {
1434  gotoPageTextFieldActionPerformed(evt);
1435  }
1436  });
1437 
1438  exportCSVButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.exportCSVButton.text")); // NOI18N
1439  exportCSVButton.addActionListener(new java.awt.event.ActionListener() {
1440  public void actionPerformed(java.awt.event.ActionEvent evt) {
1441  exportCSVButtonActionPerformed(evt);
1442  }
1443  });
1444 
1445  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
1446  this.setLayout(layout);
1447  layout.setHorizontalGroup(
1448  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
1449  .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 904, Short.MAX_VALUE)
1450  .addGroup(layout.createSequentialGroup()
1451  .addContainerGap()
1452  .addComponent(pageLabel)
1453  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
1454  .addComponent(pageNumLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE)
1455  .addGap(14, 14, 14)
1456  .addComponent(pagesLabel)
1457  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
1458  .addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)
1459  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
1460  .addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)
1461  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
1462  .addComponent(gotoPageLabel)
1463  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
1464  .addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 33, javax.swing.GroupLayout.PREFERRED_SIZE)
1465  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
1466  .addComponent(exportCSVButton))
1467  );
1468 
1469  layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {pageNextButton, pagePrevButton});
1470 
1471  layout.setVerticalGroup(
1472  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
1473  .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
1474  .addGap(3, 3, 3)
1475  .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
1476  .addComponent(pageLabel)
1477  .addComponent(pageNumLabel)
1478  .addComponent(pagesLabel)
1479  .addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE)
1480  .addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 15, javax.swing.GroupLayout.PREFERRED_SIZE)
1481  .addComponent(gotoPageLabel)
1482  .addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
1483  .addComponent(exportCSVButton))
1484  .addGap(3, 3, 3)
1485  .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 321, Short.MAX_VALUE)
1486  .addContainerGap())
1487  );
1488 
1489  layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {pageNextButton, pagePrevButton});
1490 
1491  gotoPageLabel.getAccessibleContext().setAccessibleName("");
1492  }// </editor-fold>//GEN-END:initComponents
1493 
1494  private void pagePrevButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pagePrevButtonActionPerformed
1495  pagingSupport.previousPage();
1496  }//GEN-LAST:event_pagePrevButtonActionPerformed
1497 
1498  private void pageNextButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pageNextButtonActionPerformed
1499  pagingSupport.nextPage();
1500  }//GEN-LAST:event_pageNextButtonActionPerformed
1501 
1502  private void gotoPageTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gotoPageTextFieldActionPerformed
1503  pagingSupport.gotoPage();
1504  }//GEN-LAST:event_gotoPageTextFieldActionPerformed
1505 
1506  @NbBundle.Messages({"DataResultViewerTable.exportCSVButtonActionPerformed.empty=No data to export"
1507  })
1508  private void exportCSVButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportCSVButtonActionPerformed
1509  Node currentRoot = this.getExplorerManager().getRootContext();
1510  if (currentRoot != null && currentRoot.getChildren().getNodesCount() > 0) {
1511  org.sleuthkit.autopsy.directorytree.ExportCSVAction.saveNodesToCSV(java.util.Arrays.asList(currentRoot.getChildren().getNodes()), this);
1512  } else {
1513  MessageNotifyUtil.Message.info(Bundle.DataResultViewerTable_exportCSVButtonActionPerformed_empty());
1514  }
1515  }//GEN-LAST:event_exportCSVButtonActionPerformed
1516 
1517  // Variables declaration - do not modify//GEN-BEGIN:variables
1518  private javax.swing.JButton exportCSVButton;
1519  private javax.swing.JLabel gotoPageLabel;
1520  private javax.swing.JTextField gotoPageTextField;
1521  private org.openide.explorer.view.OutlineView outlineView;
1522  private javax.swing.JLabel pageLabel;
1523  private javax.swing.JButton pageNextButton;
1524  private javax.swing.JLabel pageNumLabel;
1525  private javax.swing.JButton pagePrevButton;
1526  private javax.swing.JLabel pagesLabel;
1527  // End of variables declaration//GEN-END:variables
1528 
1529 }
Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
static void register(String nodeName, Object subscriber)
synchronized void assignColumns(List< Property<?>> props)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void addChangeListener(PreferenceChangeListener listener)
Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
static void saveNodesToCSV(Collection<?extends Node > nodesToExport, Component component)
DataResultViewerTable(ExplorerManager explorerManager, String title)

Copyright © 2012-2021 Basis Technology. Generated on: Thu Sep 30 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.