Autopsy  4.20.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 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  setCursor(null);
263  }
264  });
265  }
266 
276  @Override
278  return new DataResultViewerTable();
279  }
280 
286  @Override
287  @NbBundle.Messages("DataResultViewerTable.title=Table")
288  public String getTitle() {
289  return title;
290  }
291 
300  @Override
301  public boolean isSupported(Node candidateRootNode) {
302  return true;
303  }
304 
310  @Override
312  public void setNode(Node rootNode) {
313  if (!SwingUtilities.isEventDispatchThread()) {
314  LOGGER.log(Level.SEVERE, "Attempting to run setNode() from non-EDT thread");
315  return;
316  }
317 
318  /*
319  * The quick filter must be reset because when determining column width,
320  * ETable.getRowCount is called, and the documentation states that quick
321  * filters must be unset for the method to work "If the quick-filter is
322  * applied the number of rows do not match the number of rows in the
323  * model."
324  */
325  outline.unsetQuickFilter();
326 
327  this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
328  try {
329  if (rootNode != null) {
330  this.rootNode = rootNode;
331 
336  if (!Node.EMPTY.equals(rootNode)) {
337  String nodeName = rootNode.getName();
338  pagingSupport = nodeNameToPagingSupportMap.get(nodeName);
339  if (pagingSupport == null) {
340  pagingSupport = new PagingSupport(nodeName);
341  nodeNameToPagingSupportMap.put(nodeName, pagingSupport);
342  }
343  pagingSupport.updateControls();
344 
345  rootNode.addNodeListener(new NodeListener() {
346  @Override
347  public void childrenAdded(NodeMemberEvent nme) {
354  SwingUtilities.invokeLater(() -> {
355  setCursor(null);
356  });
357  }
358 
359  @Override
360  public void childrenRemoved(NodeMemberEvent nme) {
361  SwingUtilities.invokeLater(() -> {
362  setCursor(null);
363  });
364  }
365 
366  @Override
367  public void childrenReordered(NodeReorderEvent nre) {
368  // No-op
369  }
370 
371  @Override
372  public void nodeDestroyed(NodeEvent ne) {
373  // No-op
374  }
375 
376  @Override
377  public void propertyChange(PropertyChangeEvent evt) {
378  // No-op
379  }
380  });
381  }
382  }
383 
384  /*
385  * If the given node is not null and has children, set it as the
386  * root context of the child OutlineView, otherwise make an
387  * "empty"node the root context.
388  */
389  if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) {
390  this.getExplorerManager().setRootContext(this.rootNode);
391  outline.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
392  setupTable();
393  } else {
394  Node emptyNode = new AbstractNode(Children.LEAF);
395  this.getExplorerManager().setRootContext(emptyNode);
396  outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
397  outlineViewListener.listenToVisibilityChanges(false);
398  outlineView.setPropertyColumns();
399  }
400  } finally {
401  this.setCursor(null);
402  }
403  }
404 
411  protected void addTreeExpansionListener(TreeExpansionListener listener) {
412  outlineView.addTreeExpansionListener(listener);
413  }
414 
420  private void setupTable() {
421  /*
422  * Since we are modifying the columns, we don't want to listen to
423  * added/removed events as un-hide/hide, until the table setup is done.
424  */
425  outlineViewListener.listenToVisibilityChanges(false);
426  /*
427  * OutlineView makes the first column be the result of
428  * node.getDisplayName with the icon. This duplicates our first column,
429  * which is the file name, etc. So, pop that property off the list, but
430  * use its display name as the header for the column so that the header
431  * can change depending on the type of data being displayed.
432  *
433  * NOTE: This assumes that the first property is always the one that
434  * duplicates getDisplayName(). The current implementation does not
435  * allow the first property column to be moved.
436  */
437  List<Node.Property<?>> props = loadColumnOrder();
438  boolean propsExist = props.isEmpty() == false;
439  Node.Property<?> firstProp = null;
440  if (propsExist) {
441  firstProp = props.remove(0);
442  }
443 
444  assignColumns(props); // assign columns to match the properties
445  if (firstProp != null) {
446  ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(firstProp.getDisplayName());
447  }
448 
449  /*
450  * Load column sorting information from preferences file and apply it to
451  * columns.
452  */
453  loadColumnSorting();
454 
455  /*
456  * Save references to columns before we deal with their visibility. This
457  * has to happen after the sorting is applied, because that actually
458  * causes the columns to be recreated. It has to happen before
459  * loadColumnVisibility so we have referenecs to the columns to pass to
460  * setColumnHidden.
461  */
462  populateColumnMap();
463 
464  /*
465  * Load column visibility information from preferences file and apply it
466  * to columns.
467  */
468  loadColumnVisibility();
469 
470  /*
471  * Set the column widths.
472  *
473  * IMPORTANT: This needs to come after the preceding calls to determine
474  * the columns that will be displayed and their layout, which includes a
475  * call to ResultViewerPersistence.getAllChildProperties(). That method
476  * calls Children.getNodes(true) on the root node to ensure ALL of the
477  * nodes have been created in the NetBeans asynch child creation thread,
478  * and then uses the first one hundred nodes to determine which columns
479  * to display, including their header text.
480  */
481  setColumnWidths();
482 
483  /*
484  * If one of the child nodes of the root node is to be selected, select
485  * it.
486  */
487  if (rootNode instanceof TableFilterNode) {
488  NodeSelectionInfo selectedChildInfo = ((TableFilterNode) rootNode).getChildNodeSelectionInfo();
489  if (null != selectedChildInfo) {
490  Node[] childNodes = rootNode.getChildren().getNodes(true);
491  for (int i = 0; i < childNodes.length; ++i) {
492  Node childNode = childNodes[i];
493  if (selectedChildInfo.matches(childNode)) {
494  SwingUtilities.invokeLater(() -> {
495  try {
496  this.getExplorerManager().setExploredContextAndSelection(this.rootNode, new Node[]{childNode});
497  } catch (PropertyVetoException ex) {
498  LOGGER.log(Level.SEVERE, "Failed to select node specified by selected child info", ex);
499  }
500  });
501 
502  break;
503  }
504  }
505  ((TableFilterNode) rootNode).setChildNodeSelectionInfo(null);
506  }
507  }
508 
509  /*
510  * The table setup is done, so any added/removed events can now be
511  * treated as un-hide/hide.
512  */
513  outlineViewListener.listenToVisibilityChanges(true);
514 
515  }
516 
517  /*
518  * Populates the column map for the child OutlineView of this tabular result
519  * viewer with references to the column objects for use when loading/storing
520  * the visibility info.
521  */
522  private void populateColumnMap() {
523  columnMap.clear();
524  TableColumnModel columnModel = outline.getColumnModel();
525  int columnCount = columnModel.getColumnCount();
526  //for each property get a reference to the column object from the column model.
527  for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
528  final String propName = entry.getValue().getName();
529  if (entry.getKey() < columnCount) {
530  final ETableColumn column = (ETableColumn) columnModel.getColumn(entry.getKey());
531  columnMap.put(propName, column);
532 
533  }
534  }
535  }
536 
537  /*
538  * Sets the column widths for the child OutlineView of this tabular results
539  * viewer providing any additional width to last column.
540  */
541  protected void setColumnWidths() {
542  // based on https://stackoverflow.com/questions/17627431/auto-resizing-the-jtable-column-widths
543  final TableColumnModel columnModel = outline.getColumnModel();
544 
545  // the remaining table width that can be used in last row
546  double availableTableWidth = outlineView.getSize().getWidth();
547 
548  for (int columnIdx = 0; columnIdx < outline.getColumnCount(); columnIdx++) {
549  int columnPadding = (columnIdx == 0) ? FIRST_COL_ADDITIONAL_WIDTH + COLUMN_PADDING : COLUMN_PADDING;
550  TableColumn tableColumn = columnModel.getColumn(columnIdx);
551 
552  // The width of this column
553  int width = MIN_COLUMN_WIDTH;
554 
555  // get header cell width
556  // taken in part from https://stackoverflow.com/a/18381924
557  TableCellRenderer headerRenderer = tableColumn.getHeaderRenderer();
558  if (headerRenderer == null) {
559  headerRenderer = outline.getTableHeader().getDefaultRenderer();
560  }
561  Object headerValue = tableColumn.getHeaderValue();
562  Component headerComp = headerRenderer.getTableCellRendererComponent(outline, headerValue, false, false, 0, columnIdx);
563  width = Math.max(headerComp.getPreferredSize().width + columnPadding, width);
564 
565  // get the max of row widths from the first SAMPLE_ROW_NUM rows
566  Component comp = null;
567  int rowCount = outline.getRowCount();
568  for (int row = 0; row < Math.min(rowCount, SAMPLE_ROW_NUM); row++) {
569  TableCellRenderer renderer = outline.getCellRenderer(row, columnIdx);
570  comp = outline.prepareRenderer(renderer, row, columnIdx);
571  width = Math.max(comp.getPreferredSize().width + columnPadding, width);
572  }
573 
574  // no higher than maximum column width
575  if (width > MAX_COLUMN_WIDTH) {
576  width = MAX_COLUMN_WIDTH;
577  }
578 
579  // if last column, calculate remaining width factoring in the possibility of a scroll bar.
580  if (columnIdx == outline.getColumnCount() - 1) {
581  int rowHeight = comp == null ? MIN_ROW_HEIGHT : comp.getPreferredSize().height;
582  if (headerComp.getPreferredSize().height + rowCount * rowHeight > outlineView.getSize().getHeight()) {
583  availableTableWidth -= SCROLL_BAR_WIDTH;
584  }
585 
586  columnModel.getColumn(columnIdx).setPreferredWidth(Math.max(width, (int) availableTableWidth));
587  } else {
588  // otherwise set preferred width to width and decrement availableTableWidth accordingly
589  columnModel.getColumn(columnIdx).setPreferredWidth(width);
590  availableTableWidth -= width;
591  }
592  }
593  }
594 
595  protected TableColumnModel getColumnModel() {
596  return outline.getColumnModel();
597  }
598 
599  /*
600  * Sets up the columns for the child OutlineView of this tabular results
601  * viewer with respect to column names and visisbility.
602  */
603  synchronized private void assignColumns(List<Property<?>> props) {
604  String[] propStrings = new String[props.size() * 2];
605  for (int i = 0; i < props.size(); i++) {
606  final Property<?> prop = props.get(i);
607  prop.setValue("ComparableColumnTTV", Boolean.TRUE); //NON-NLS
608  //First property column is sorted initially
609  if (i == 0) {
610  prop.setValue("TreeColumnTTV", Boolean.TRUE); // Identifies special property representing first (tree) column. NON-NLS
611  prop.setValue("SortingColumnTTV", Boolean.TRUE); // TreeTableView should be initially sorted by this property column. NON-NLS
612  }
613  propStrings[2 * i] = prop.getName();
614  propStrings[2 * i + 1] = prop.getDisplayName();
615  }
616  outlineView.setPropertyColumns(propStrings);
617  }
618 
623  private synchronized void storeColumnVisibility() {
624  if (rootNode == null || propertiesMap.isEmpty()) {
625  return;
626  }
627  if (rootNode instanceof TableFilterNode) {
628  TableFilterNode tfn = (TableFilterNode) rootNode;
629  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
630  final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
631  for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
632  String columnName = entry.getKey();
633  final String columnHiddenKey = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName);
634  final TableColumn column = entry.getValue();
635  boolean columnHidden = columnModel.isColumnHidden(column);
636  if (columnHidden) {
637  preferences.putBoolean(columnHiddenKey, true);
638  } else {
639  preferences.remove(columnHiddenKey);
640  }
641  }
642  }
643  }
644 
649  private synchronized void storeColumnOrder() {
650  if (rootNode == null || propertiesMap.isEmpty()) {
651  return;
652  }
653  if (rootNode instanceof TableFilterNode) {
654  TableFilterNode tfn = (TableFilterNode) rootNode;
655  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
656  // Store the current order of the columns into settings
657  for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
658  preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey());
659  }
660  }
661  }
662 
666  private synchronized void storeColumnSorting() {
667  if (rootNode == null || propertiesMap.isEmpty()) {
668  return;
669  }
670  if (rootNode instanceof TableFilterNode) {
671  final TableFilterNode tfn = ((TableFilterNode) rootNode);
672  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
673  ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
674  for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
675  ETableColumn etc = entry.getValue();
676  String columnName = entry.getKey();
677  //store sort rank and order
678  final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName);
679  final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, columnName);
680  if (etc.isSorted() && (columnModel.isColumnHidden(etc) == false)) {
681  preferences.putBoolean(columnSortOrderKey, etc.isAscending());
682  preferences.putInt(columnSortRankKey, etc.getSortRank());
683  } else {
684  columnModel.setColumnSorted(etc, true, 0);
685  preferences.remove(columnSortOrderKey);
686  preferences.remove(columnSortRankKey);
687  }
688  }
689  }
690  }
691 
698  private synchronized void loadColumnSorting() {
699  if (rootNode == null || propertiesMap.isEmpty()) {
700  return;
701  }
702  if (rootNode instanceof TableFilterNode) {
703  final TableFilterNode tfn = (TableFilterNode) rootNode;
704  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
705  //organize property sorting information, sorted by rank
706  TreeSet<ColumnSortInfo> sortInfos = new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank));
707  propertiesMap.entrySet().stream().forEach(entry -> {
708  final String propName = entry.getValue().getName();
709  //if the sort rank is undefined, it will be defaulted to 0 => unsorted.
710  Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0);
711  //default to true => ascending
712  Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName), true);
713  sortInfos.add(new ColumnSortInfo(entry.getKey(), sortRank, sortOrder));
714  });
715  //apply sort information in rank order.
716  sortInfos.forEach(sortInfo -> outline.setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank));
717  }
718  }
719 
724  private synchronized void loadColumnVisibility() {
725  if (rootNode == null || propertiesMap.isEmpty()) {
726  return;
727  }
728  if (rootNode instanceof TableFilterNode) {
729  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
730  final TableFilterNode tfn = ((TableFilterNode) rootNode);
731  ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
732  for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
733  final String propName = entry.getValue().getName();
734  boolean hidden = preferences.getBoolean(ResultViewerPersistence.getColumnHiddenKey(tfn, propName), false);
735  final TableColumn column = columnMap.get(propName);
736  columnModel.setColumnHidden(column, hidden);
737  }
738  }
739  }
740 
749  private synchronized List<Node.Property<?>> loadColumnOrder() {
750 
751  List<Property<?>> props = ResultViewerPersistence.getAllChildProperties(rootNode, 100);
752 
753  // If node is not table filter node, use default order for columns
754  if (!(rootNode instanceof TableFilterNode)) {
755  return props;
756  }
757 
758  final TableFilterNode tfn = ((TableFilterNode) rootNode);
759  propertiesMap.clear();
760 
761  /*
762  * We load column index values into the properties map. If a property's
763  * index is outside the range of the number of properties or the index
764  * has already appeared as the position of another property, we put that
765  * property at the end.
766  */
767  int offset = props.size();
768 
769  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
770 
771  for (Property<?> prop : props) {
772  Integer value = preferences.getInt(ResultViewerPersistence.getColumnPositionKey(tfn, prop.getName()), -1);
773  if (value >= 0 && value < offset && !propertiesMap.containsKey(value)) {
774  propertiesMap.put(value, prop);
775  } else {
776  propertiesMap.put(offset, prop);
777  offset++;
778  }
779  }
780 
781  /*
782  * NOTE: it is possible to have "discontinuities" in the keys (i.e.
783  * column numbers) of the map. This happens when some of the columns had
784  * a previous setting, and other columns did not. We need to make the
785  * keys 0-indexed and continuous.
786  */
787  compactPropertiesMap();
788 
789  return new ArrayList<>(propertiesMap.values());
790  }
791 
796  private void compactPropertiesMap() {
797 
798  // check if there are discontinuities in the map keys.
799  int size = propertiesMap.size();
800  Queue<Integer> availablePositions = new LinkedList<>();
801  for (int i = 0; i < size; i++) {
802  if (!propertiesMap.containsKey(i)) {
803  availablePositions.add(i);
804  }
805  }
806 
807  // if there are no discontinuities, we are done
808  if (availablePositions.isEmpty()) {
809  return;
810  }
811 
812  // otherwise, move map elements into the available positions.
813  // we don't want to just move down all elements, as we want to preserve the order
814  // of the ones that had previous setting (i.e. ones that have key < size)
815  ArrayList<Integer> keys = new ArrayList<>(propertiesMap.keySet());
816  for (int key : keys) {
817  if (key >= size) {
818  propertiesMap.put(availablePositions.remove(), propertiesMap.remove(key));
819  }
820  }
821  }
822 
827  @Override
828  public void clearComponent() {
829  this.outlineView.removeAll();
830  this.outlineView = null;
831  super.clearComponent();
832  }
833 
837  static private final class ColumnSortInfo {
838 
839  private final int modelIndex;
840  private final int rank;
841  private final boolean order;
842 
843  private ColumnSortInfo(int modelIndex, int rank, boolean order) {
844  this.modelIndex = modelIndex;
845  this.rank = rank;
846  this.order = order;
847  }
848 
849  private int getRank() {
850  return rank;
851  }
852  }
853 
859  private class PagingSupport {
860 
861  private int currentPage;
862  private int totalPages;
863  private final String nodeName;
864 
865  PagingSupport(String nodeName) {
866  currentPage = 1;
867  totalPages = 0;
868  this.nodeName = nodeName;
869  initialize();
870  }
871 
872  private void initialize() {
873  if (!nodeName.isEmpty()) {
874  BaseChildFactory.register(nodeName, this);
875  }
876  updateControls();
877  }
878 
879  void nextPage() {
880  currentPage++;
881  postPageChangeEvent();
882  }
883 
884  void previousPage() {
885  currentPage--;
886  postPageChangeEvent();
887  }
888 
889  @NbBundle.Messages({"# {0} - totalPages",
890  "DataResultViewerTable.goToPageTextField.msgDlg=Please enter a valid page number between 1 and {0}",
891  "DataResultViewerTable.goToPageTextField.err=Invalid page number"})
892  void gotoPage() {
893  int originalPage = currentPage;
894 
895  try {
896  currentPage = Integer.decode(gotoPageTextField.getText());
897  } catch (NumberFormatException e) {
898  //ignore input
899  return;
900  }
901 
902  if (currentPage > totalPages || currentPage < 1) {
903  currentPage = originalPage;
904  JOptionPane.showMessageDialog(DataResultViewerTable.this,
905  Bundle.DataResultViewerTable_goToPageTextField_msgDlg(totalPages),
906  Bundle.DataResultViewerTable_goToPageTextField_err(),
907  JOptionPane.WARNING_MESSAGE);
908  return;
909  }
910  postPageChangeEvent();
911  }
912 
917  void postPageChangeEvent() {
918  try {
919  BaseChildFactory.post(nodeName, new PageChangeEvent(currentPage));
920  } catch (BaseChildFactory.NoSuchEventBusException ex) {
921  LOGGER.log(Level.WARNING, "Failed to post page change event.", ex); //NON-NLS
922  }
923  DataResultViewerTable.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
924  updateControls();
925  }
926 
931  void postPageSizeChangeEvent() {
932  // Reset page variables when page size changes
933  currentPage = 1;
934 
935  if (this == pagingSupport) {
936  updateControls();
937  }
938  try {
939  BaseChildFactory.post(nodeName, new PageSizeChangeEvent(UserPreferences.getResultsTablePageSize()));
940  } catch (BaseChildFactory.NoSuchEventBusException ex) {
941  LOGGER.log(Level.WARNING, "Failed to post page size change event.", ex); //NON-NLS
942  }
943  }
944 
950  @Subscribe
952  if (event != null) {
953  totalPages = event.getPageCount();
954  if (totalPages > 1) {
955  // Make paging controls visible if there is more than one page.
956  togglePageControls(true);
957  }
958 
959  // Only update UI controls if this event is for the node currently being viewed.
960  if (nodeName.equals(rootNode.getName())) {
961  updateControls();
962  }
963  }
964  }
965 
971  private void togglePageControls(boolean onOff) {
972  pageLabel.setVisible(onOff);
973  pagesLabel.setVisible(onOff);
974  pagePrevButton.setVisible(onOff);
975  pageNextButton.setVisible(onOff);
976  pageNumLabel.setVisible(onOff);
977  gotoPageLabel.setVisible(onOff);
978  gotoPageTextField.setVisible(onOff);
979  gotoPageTextField.setVisible(onOff);
980  validate();
981  repaint();
982  }
983 
984  @NbBundle.Messages({"# {0} - currentPage", "# {1} - totalPages",
985  "DataResultViewerTable.pageNumbers.curOfTotal={0} of {1}"})
986  private void updateControls() {
987  if (totalPages == 0) {
988  pagePrevButton.setEnabled(false);
989  pageNextButton.setEnabled(false);
990  pageNumLabel.setText("");
991  gotoPageTextField.setText("");
992  gotoPageTextField.setEnabled(false);
993  } else {
994  pageNumLabel.setText(Bundle.DataResultViewerTable_pageNumbers_curOfTotal(Integer.toString(currentPage), Integer.toString(totalPages)));
995 
996  pageNextButton.setEnabled(currentPage != totalPages);
997  pagePrevButton.setEnabled(currentPage != 1);
998  gotoPageTextField.setEnabled(totalPages > 1);
999  gotoPageTextField.setText("");
1000  }
1001  }
1002  }
1003 
1008  private class IconRendererTableListener implements TableColumnModelListener {
1009 
1010  @NbBundle.Messages({"DataResultViewerTable.commentRender.name=C",
1011  "DataResultViewerTable.commentRender.toolTip=C(omments) indicates whether the item has a comment",
1012  "DataResultViewerTable.scoreRender.name=S",
1013  "DataResultViewerTable.scoreRender.toolTip=S(core) indicates whether the item is interesting or notable",
1014  "DataResultViewerTable.countRender.name=O",
1015  "DataResultViewerTable.countRender.toolTip=O(ccurrences) indicates the number of data sources containing the item in the Central Repository"})
1016  @Override
1017  public void columnAdded(TableColumnModelEvent e) {
1018  if (e.getSource() instanceof ETableColumnModel) {
1019  TableColumn column = ((TableColumnModel) e.getSource()).getColumn(e.getToIndex());
1020  if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_commentRender_name())) {
1021  //if the current column is a comment column set the cell renderer to be the HasCommentCellRenderer
1022  outlineView.setPropertyColumnDescription(column.getHeaderValue().toString(), Bundle.DataResultViewerTable_commentRender_toolTip());
1023  column.setCellRenderer(new HasCommentCellRenderer());
1024  } else if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_scoreRender_name())) {
1025  //if the current column is a score column set the cell renderer to be the ScoreCellRenderer
1026  outlineView.setPropertyColumnDescription(column.getHeaderValue().toString(), Bundle.DataResultViewerTable_scoreRender_toolTip());
1027  column.setCellRenderer(new ScoreCellRenderer());
1028  } else if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_countRender_name())) {
1029  outlineView.setPropertyColumnDescription(column.getHeaderValue().toString(), Bundle.DataResultViewerTable_countRender_toolTip());
1030  column.setCellRenderer(new CountCellRenderer());
1031  }
1032  }
1033  }
1034 
1035  @Override
1036  public void columnRemoved(TableColumnModelEvent e
1037  ) {
1038  //Don't do anything when column removed
1039  }
1040 
1041  @Override
1042  public void columnMoved(TableColumnModelEvent e
1043  ) {
1044  //Don't do anything when column moved
1045  }
1046 
1047  @Override
1048  public void columnMarginChanged(ChangeEvent e
1049  ) {
1050  //Don't do anything when column margin changed
1051  }
1052 
1053  @Override
1054  public void columnSelectionChanged(ListSelectionEvent e
1055  ) {
1056  //Don't do anything when column selection changed
1057  }
1058 
1059  }
1060 
1065  private class TableListener extends MouseAdapter implements TableColumnModelListener {
1066 
1067  // When a column in the table is moved, these two variables keep track of where
1068  // the column started and where it ended up.
1069  private int startColumnIndex = -1;
1070  private int endColumnIndex = -1;
1071  private boolean listenToVisibilitEvents;
1072 
1073  @Override
1074  public void columnMoved(TableColumnModelEvent e) {
1075  int fromIndex = e.getFromIndex();
1076  int toIndex = e.getToIndex();
1077  if (fromIndex == toIndex) {
1078  return;
1079  }
1080 
1081  /*
1082  * Because a column may be dragged to several different positions
1083  * before the mouse is released (thus causing multiple
1084  * TableColumnModelEvents to be fired), we want to keep track of the
1085  * starting column index in this potential series of movements.
1086  * Therefore we only keep track of the original fromIndex in
1087  * startColumnIndex, but we always update endColumnIndex to know the
1088  * final position of the moved column. See the MouseListener
1089  * mouseReleased method.
1090  */
1091  if (startColumnIndex == -1) {
1092  startColumnIndex = fromIndex;
1093  }
1094  endColumnIndex = toIndex;
1095 
1096  // This list contains the keys of propertiesMap in order
1097  ArrayList<Integer> indicesList = new ArrayList<>(propertiesMap.keySet());
1098  int leftIndex = Math.min(fromIndex, toIndex);
1099  int rightIndex = Math.max(fromIndex, toIndex);
1100  // Now we can copy the range of keys that have been affected by
1101  // the column movement
1102  List<Integer> range = indicesList.subList(leftIndex, rightIndex + 1);
1103  int rangeSize = range.size();
1104 
1105  if (fromIndex < toIndex) {
1106  // column moved right, shift all properties left, put in moved
1107  // property at the rightmost index
1108  Property<?> movedProp = propertiesMap.get(range.get(0));
1109  for (int i = 0; i < rangeSize - 1; i++) {
1110  propertiesMap.put(range.get(i), propertiesMap.get(range.get(i + 1)));
1111  }
1112  propertiesMap.put(range.get(rangeSize - 1), movedProp);
1113  } else {
1114  // column moved left, shift all properties right, put in moved
1115  // property at the leftmost index
1116  Property<?> movedProp = propertiesMap.get(range.get(rangeSize - 1));
1117  for (int i = rangeSize - 1; i > 0; i--) {
1118  propertiesMap.put(range.get(i), propertiesMap.get(range.get(i - 1)));
1119  }
1120  propertiesMap.put(range.get(0), movedProp);
1121  }
1122 
1123  storeColumnOrder();
1124  }
1125 
1126  @Override
1127  public void mouseReleased(MouseEvent e) {
1128  /*
1129  * If the startColumnIndex is not -1 (which is the reset value),
1130  * that means columns have been moved around. We then check to see
1131  * if either the starting or end position is 0 (the first column),
1132  * and then swap them back if that is the case because we don't want
1133  * to allow movement of the first column. We then reset
1134  * startColumnIndex to -1, the reset value. We check if
1135  * startColumnIndex is at reset or not because it is possible for
1136  * the mouse to be released and a MouseEvent to be fired without
1137  * having moved any columns.
1138  */
1139  if (startColumnIndex != -1 && (startColumnIndex == 0 || endColumnIndex == 0)) {
1140  outline.moveColumn(endColumnIndex, startColumnIndex);
1141  }
1142  startColumnIndex = -1;
1143  }
1144 
1145  @Override
1146  public void mouseClicked(MouseEvent e) {
1147  //the user clicked a column header
1148  storeColumnSorting();
1149  }
1150 
1151  @Override
1152  public void columnAdded(TableColumnModelEvent e) {
1153  columnAddedOrRemoved();
1154  }
1155 
1156  @Override
1157  public void columnRemoved(TableColumnModelEvent e) {
1158  columnAddedOrRemoved();
1159  }
1160 
1166  private void columnAddedOrRemoved() {
1167  if (listenToVisibilitEvents) {
1168  SwingUtilities.invokeLater(DataResultViewerTable.this::storeColumnVisibility);
1169 
1170  }
1171  }
1172 
1173  @Override
1174  public void columnMarginChanged(ChangeEvent e) {
1175  }
1176 
1177  @Override
1178  public void columnSelectionChanged(ListSelectionEvent e) {
1179  }
1180 
1190  private void listenToVisibilityChanges(boolean b) {
1191  this.listenToVisibilitEvents = b;
1192  }
1193  }
1194 
1195  /*
1196  * A renderer which based on the contents of the cell will display an icon
1197  * to indicate the presence of a comment related to the content.
1198  */
1199  private final class HasCommentCellRenderer extends DefaultOutlineCellRenderer {
1200 
1201  private static final long serialVersionUID = 1L;
1202 
1203  @NbBundle.Messages({"DataResultViewerTable.commentRenderer.crComment.toolTip=Comment exists in Central Repository",
1204  "DataResultViewerTable.commentRenderer.tagComment.toolTip=Comment exists on associated tag(s)",
1205  "DataResultViewerTable.commentRenderer.crAndTagComment.toolTip=Comments exist both in Central Repository and on associated tag(s)",
1206  "DataResultViewerTable.commentRenderer.noComment.toolTip=No comments found"})
1207  @Override
1208  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
1209  Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
1210  setBackground(component.getBackground()); //inherit highlighting for selection
1211  setHorizontalAlignment(CENTER);
1212  Object switchValue = null;
1213  if ((value instanceof NodeProperty)) {
1214  //The Outline view has properties in the cell, the value contained in the property is what we want
1215  try {
1216  switchValue = ((Node.Property) value).getValue();
1217  } catch (IllegalAccessException | InvocationTargetException ex) {
1218  //Unable to get the value from the NodeProperty no Icon will be displayed
1219  }
1220  } else {
1221  //JTables contain the value we want directly in the cell
1222  switchValue = value;
1223  }
1224  setText("");
1225  if ((switchValue instanceof HasCommentStatus)) {
1226 
1227  switch ((HasCommentStatus) switchValue) {
1228  case CR_COMMENT:
1229  setIcon(COMMENT_ICON);
1230  setToolTipText(Bundle.DataResultViewerTable_commentRenderer_crComment_toolTip());
1231  break;
1232  case TAG_COMMENT:
1233  setIcon(COMMENT_ICON);
1234  setToolTipText(Bundle.DataResultViewerTable_commentRenderer_tagComment_toolTip());
1235  break;
1236  case CR_AND_TAG_COMMENTS:
1237  setIcon(COMMENT_ICON);
1238  setToolTipText(Bundle.DataResultViewerTable_commentRenderer_crAndTagComment_toolTip());
1239  break;
1240  case TAG_NO_COMMENT:
1241  case NO_COMMENT:
1242  default:
1243  setIcon(null);
1244  setToolTipText(Bundle.DataResultViewerTable_commentRenderer_noComment_toolTip());
1245  }
1246  } else {
1247  setIcon(null);
1248  }
1249 
1250  return this;
1251  }
1252 
1253  }
1254 
1255  /*
1256  * A renderer which based on the contents of the cell will display an icon
1257  * to indicate the score associated with the item.
1258  */
1259  private final class ScoreCellRenderer extends DefaultOutlineCellRenderer {
1260 
1261  private static final long serialVersionUID = 1L;
1262 
1270  private ImageIcon getIcon(Significance significance) {
1271  if (significance == null) {
1272  return null;
1273  }
1274 
1275  switch (significance) {
1276  case NOTABLE:
1277  return NOTABLE_ICON_SCORE;
1278  case LIKELY_NOTABLE:
1279  return INTERESTING_SCORE_ICON;
1280  case LIKELY_NONE:
1281  case NONE:
1282  case UNKNOWN:
1283  default:
1284  return null;
1285  }
1286  }
1287 
1288  @Override
1289  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
1290  Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
1291  setBackground(component.getBackground()); //inherit highlighting for selection
1292  setHorizontalAlignment(CENTER);
1293  Object switchValue = null;
1294  if ((value instanceof NodeProperty)) {
1295  //The Outline view has properties in the cell, the value contained in the property is what we want
1296  try {
1297  switchValue = ((Node.Property) value).getValue();
1298  setToolTipText(((FeatureDescriptor) value).getShortDescription());
1299  } catch (IllegalAccessException | InvocationTargetException ex) {
1300  //Unable to get the value from the NodeProperty no Icon will be displayed
1301  }
1302 
1303  } else {
1304  //JTables contain the value we want directly in the cell
1305  switchValue = value;
1306  }
1307  setText("");
1308  if ((switchValue instanceof org.sleuthkit.datamodel.Score)) {
1309  setIcon(getIcon(((org.sleuthkit.datamodel.Score) switchValue).getSignificance()));
1310  } else {
1311  setIcon(null);
1312  }
1313  return this;
1314  }
1315 
1316  }
1317 
1318  /*
1319  * A renderer which based on the contents of the cell will display an empty
1320  * cell if no count was available.
1321  */
1322  private final class CountCellRenderer extends DefaultOutlineCellRenderer {
1323 
1324  private static final long serialVersionUID = 1L;
1325 
1326  @Override
1327  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
1328  Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
1329  setBackground(component.getBackground()); //inherit highlighting for selection
1330  setHorizontalAlignment(LEFT);
1331  Object countValue = null;
1332  if ((value instanceof NodeProperty)) {
1333  //The Outline view has properties in the cell, the value contained in the property is what we want
1334  try {
1335  countValue = ((Node.Property) value).getValue();
1336  setToolTipText(((FeatureDescriptor) value).getShortDescription());
1337  } catch (IllegalAccessException | InvocationTargetException ex) {
1338  //Unable to get the value from the NodeProperty no Icon will be displayed
1339  }
1340  } else {
1341  //JTables contain the value we want directly in the cell
1342  countValue = value;
1343  }
1344  setText("");
1345  if ((countValue instanceof Long)) {
1346  //Don't display value if value is negative used so that sorting will behave as desired
1347  if ((Long) countValue >= 0) {
1348  setText(countValue.toString());
1349  }
1350  }
1351  return this;
1352  }
1353 
1354  }
1355 
1360  public enum HasCommentStatus {
1365  CR_AND_TAG_COMMENTS
1366  }
1367 
1371  public enum Score {
1374  NOTABLE_SCORE
1375  }
1376 
1382  @SuppressWarnings("unchecked")
1383  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
1384  private void initComponents() {
1385 
1386  pageLabel = new javax.swing.JLabel();
1387  pageNumLabel = new javax.swing.JLabel();
1388  pagesLabel = new javax.swing.JLabel();
1389  pagePrevButton = new javax.swing.JButton();
1390  pageNextButton = new javax.swing.JButton();
1391  outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL);
1392  gotoPageLabel = new javax.swing.JLabel();
1393  gotoPageTextField = new javax.swing.JTextField();
1394  exportCSVButton = new javax.swing.JButton();
1395 
1396  pageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageLabel.text")); // NOI18N
1397 
1398  pageNumLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageNumLabel.text")); // NOI18N
1399 
1400  pagesLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pagesLabel.text")); // NOI18N
1401 
1402  pagePrevButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"))); // NOI18N
1403  pagePrevButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pagePrevButton.text")); // NOI18N
1404  pagePrevButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); // NOI18N
1405  pagePrevButton.setFocusable(false);
1406  pagePrevButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
1407  pagePrevButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
1408  pagePrevButton.setPreferredSize(new java.awt.Dimension(55, 23));
1409  pagePrevButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png"))); // NOI18N
1410  pagePrevButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
1411  pagePrevButton.addActionListener(new java.awt.event.ActionListener() {
1412  public void actionPerformed(java.awt.event.ActionEvent evt) {
1413  pagePrevButtonActionPerformed(evt);
1414  }
1415  });
1416 
1417  pageNextButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"))); // NOI18N
1418  pageNextButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageNextButton.text")); // NOI18N
1419  pageNextButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); // NOI18N
1420  pageNextButton.setFocusable(false);
1421  pageNextButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
1422  pageNextButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
1423  pageNextButton.setMaximumSize(new java.awt.Dimension(27, 23));
1424  pageNextButton.setMinimumSize(new java.awt.Dimension(27, 23));
1425  pageNextButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_hover.png"))); // NOI18N
1426  pageNextButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
1427  pageNextButton.addActionListener(new java.awt.event.ActionListener() {
1428  public void actionPerformed(java.awt.event.ActionEvent evt) {
1429  pageNextButtonActionPerformed(evt);
1430  }
1431  });
1432 
1433  gotoPageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.gotoPageLabel.text")); // NOI18N
1434 
1435  gotoPageTextField.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.gotoPageTextField.text")); // NOI18N
1436  gotoPageTextField.addActionListener(new java.awt.event.ActionListener() {
1437  public void actionPerformed(java.awt.event.ActionEvent evt) {
1438  gotoPageTextFieldActionPerformed(evt);
1439  }
1440  });
1441 
1442  exportCSVButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.exportCSVButton.text")); // NOI18N
1443  exportCSVButton.addActionListener(new java.awt.event.ActionListener() {
1444  public void actionPerformed(java.awt.event.ActionEvent evt) {
1445  exportCSVButtonActionPerformed(evt);
1446  }
1447  });
1448 
1449  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
1450  this.setLayout(layout);
1451  layout.setHorizontalGroup(
1452  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
1453  .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 904, Short.MAX_VALUE)
1454  .addGroup(layout.createSequentialGroup()
1455  .addContainerGap()
1456  .addComponent(pageLabel)
1457  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
1458  .addComponent(pageNumLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE)
1459  .addGap(14, 14, 14)
1460  .addComponent(pagesLabel)
1461  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
1462  .addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)
1463  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
1464  .addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)
1465  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
1466  .addComponent(gotoPageLabel)
1467  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
1468  .addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 33, javax.swing.GroupLayout.PREFERRED_SIZE)
1469  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
1470  .addComponent(exportCSVButton))
1471  );
1472 
1473  layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {pageNextButton, pagePrevButton});
1474 
1475  layout.setVerticalGroup(
1476  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
1477  .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
1478  .addGap(3, 3, 3)
1479  .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
1480  .addComponent(pageLabel)
1481  .addComponent(pageNumLabel)
1482  .addComponent(pagesLabel)
1483  .addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE)
1484  .addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 15, javax.swing.GroupLayout.PREFERRED_SIZE)
1485  .addComponent(gotoPageLabel)
1486  .addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
1487  .addComponent(exportCSVButton))
1488  .addGap(3, 3, 3)
1489  .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 321, Short.MAX_VALUE)
1490  .addContainerGap())
1491  );
1492 
1493  layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {pageNextButton, pagePrevButton});
1494 
1495  gotoPageLabel.getAccessibleContext().setAccessibleName("");
1496  }// </editor-fold>//GEN-END:initComponents
1497 
1498  private void pagePrevButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pagePrevButtonActionPerformed
1499  pagingSupport.previousPage();
1500  }//GEN-LAST:event_pagePrevButtonActionPerformed
1501 
1502  private void pageNextButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pageNextButtonActionPerformed
1503  pagingSupport.nextPage();
1504  }//GEN-LAST:event_pageNextButtonActionPerformed
1505 
1506  private void gotoPageTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gotoPageTextFieldActionPerformed
1507  pagingSupport.gotoPage();
1508  }//GEN-LAST:event_gotoPageTextFieldActionPerformed
1509 
1510  @NbBundle.Messages({"DataResultViewerTable.exportCSVButtonActionPerformed.empty=No data to export"
1511  })
1512  private void exportCSVButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportCSVButtonActionPerformed
1513  Node currentRoot = this.getExplorerManager().getRootContext();
1514  if (currentRoot != null && currentRoot.getChildren().getNodesCount() > 0) {
1515  org.sleuthkit.autopsy.directorytree.ExportCSVAction.saveNodesToCSV(java.util.Arrays.asList(currentRoot.getChildren().getNodes()), this);
1516  } else {
1517  MessageNotifyUtil.Message.info(Bundle.DataResultViewerTable_exportCSVButtonActionPerformed_empty());
1518  }
1519  }//GEN-LAST:event_exportCSVButtonActionPerformed
1520 
1521  // Variables declaration - do not modify//GEN-BEGIN:variables
1522  private javax.swing.JButton exportCSVButton;
1523  private javax.swing.JLabel gotoPageLabel;
1524  private javax.swing.JTextField gotoPageTextField;
1525  private org.openide.explorer.view.OutlineView outlineView;
1526  private javax.swing.JLabel pageLabel;
1527  private javax.swing.JButton pageNextButton;
1528  private javax.swing.JLabel pageNumLabel;
1529  private javax.swing.JButton pagePrevButton;
1530  private javax.swing.JLabel pagesLabel;
1531  // End of variables declaration//GEN-END:variables
1532 
1533 }
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-2022 Basis Technology. Generated on: Tue Aug 1 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.