Autopsy  4.17.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-2019 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.FontMetrics;
25 import java.awt.Graphics;
26 import java.awt.dnd.DnDConstants;
27 import java.awt.event.MouseAdapter;
28 import java.awt.event.MouseEvent;
29 import java.beans.FeatureDescriptor;
30 import java.beans.PropertyChangeEvent;
31 import java.beans.PropertyVetoException;
32 import java.lang.reflect.InvocationTargetException;
33 import java.util.ArrayList;
34 import java.util.Comparator;
35 import java.util.HashMap;
36 import java.util.LinkedList;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Queue;
40 import java.util.TreeMap;
41 import java.util.TreeSet;
42 import java.util.concurrent.ConcurrentHashMap;
43 import java.util.logging.Level;
44 import java.util.prefs.PreferenceChangeEvent;
45 import java.util.prefs.Preferences;
46 import javax.swing.ImageIcon;
47 import javax.swing.JOptionPane;
48 import javax.swing.JTable;
49 import javax.swing.ListSelectionModel;
50 import static javax.swing.SwingConstants.CENTER;
51 import javax.swing.SwingUtilities;
52 import javax.swing.event.ChangeEvent;
53 import javax.swing.event.ListSelectionEvent;
54 import javax.swing.event.TableColumnModelEvent;
55 import javax.swing.event.TableColumnModelListener;
56 import javax.swing.event.TreeExpansionListener;
57 import javax.swing.table.TableCellRenderer;
58 import javax.swing.table.TableColumn;
59 import javax.swing.table.TableColumnModel;
60 import org.netbeans.swing.etable.ETableColumn;
61 import org.netbeans.swing.etable.ETableColumnModel;
62 import org.netbeans.swing.outline.DefaultOutlineCellRenderer;
63 import org.netbeans.swing.outline.DefaultOutlineModel;
64 import org.netbeans.swing.outline.Outline;
65 import org.openide.explorer.ExplorerManager;
66 import org.openide.explorer.view.OutlineView;
67 import org.openide.nodes.AbstractNode;
68 import org.openide.nodes.Children;
69 import org.openide.nodes.Node;
70 import org.openide.nodes.Node.Property;
71 import org.openide.nodes.NodeEvent;
72 import org.openide.nodes.NodeListener;
73 import org.openide.nodes.NodeMemberEvent;
74 import org.openide.nodes.NodeReorderEvent;
75 import org.openide.util.ImageUtilities;
76 import org.openide.util.NbBundle;
77 import org.openide.util.NbPreferences;
78 import org.openide.util.lookup.ServiceProvider;
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  private static final String NOTEPAD_ICON_PATH = "org/sleuthkit/autopsy/images/notepad16.png";
109  private static final String RED_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/red-circle-exclamation.png";
110  private static final String YELLOW_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/yellow-circle-yield.png";
111  private static final ImageIcon COMMENT_ICON = new ImageIcon(ImageUtilities.loadImage(NOTEPAD_ICON_PATH, false));
112  private static final ImageIcon INTERESTING_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(YELLOW_CIRCLE_ICON_PATH, false));
113  private static final ImageIcon NOTABLE_ICON_SCORE = new ImageIcon(ImageUtilities.loadImage(RED_CIRCLE_ICON_PATH, false));
114  @NbBundle.Messages("DataResultViewerTable.firstColLbl=Name")
115  static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl();
116  private final String title;
117  private final Map<String, ETableColumn> columnMap;
118  private final Map<Integer, Property<?>> propertiesMap;
119  private final Outline outline;
122  private Node rootNode;
123 
129  private final Map<String, PagingSupport> nodeNameToPagingSupportMap = new ConcurrentHashMap<>();
130 
134  private PagingSupport pagingSupport = null;
135 
144  this(null, Bundle.DataResultViewerTable_title());
145  }
146 
156  public DataResultViewerTable(ExplorerManager explorerManager) {
157  this(explorerManager, Bundle.DataResultViewerTable_title());
158  }
159 
170  public DataResultViewerTable(ExplorerManager explorerManager, String title) {
171  super(explorerManager);
172  this.title = title;
173  this.columnMap = new HashMap<>();
174  this.propertiesMap = new TreeMap<>();
175 
176  /*
177  * Execute the code generated by the GUI builder.
178  */
179  initComponents();
180 
181  initializePagingSupport();
182 
183  /*
184  * Disable the CSV export button for the common properties results
185  */
187  exportCSVButton.setEnabled(false);
188  }
189 
190  /*
191  * Configure the child OutlineView (explorer view) component.
192  */
193  outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE);
194 
195  outline = outlineView.getOutline();
196  outline.setRowSelectionAllowed(true);
197  outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
198  outline.setRootVisible(false);
199  outline.setDragEnabled(false);
200 
201  /*
202  * Add a table listener to the child OutlineView (explorer view) to
203  * persist the order of the table columns when a column is moved.
204  */
205  outlineViewListener = new TableListener();
206  outline.getColumnModel().addColumnModelListener(outlineViewListener);
207 
208  iconRendererListener = new IconRendererTableListener();
209  outline.getColumnModel().addColumnModelListener(iconRendererListener);
210 
211  /*
212  * Add a mouse listener to the child OutlineView (explorer view) to make
213  * sure the first column of the table is kept in place.
214  */
215  outline.getTableHeader().addMouseListener(outlineViewListener);
216  }
217 
218  private void initializePagingSupport() {
219  if (pagingSupport == null) {
220  pagingSupport = new PagingSupport("");
221  }
222 
223  // Start out with paging controls invisible
224  pagingSupport.togglePageControls(false);
225 
230  UserPreferences.addChangeListener((PreferenceChangeEvent evt) -> {
231  if (evt.getKey().equals(UserPreferences.RESULTS_TABLE_PAGE_SIZE)) {
232  setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
237  nodeNameToPagingSupportMap.values().forEach((ps) -> {
238  ps.postPageSizeChangeEvent();
239  });
240  }
241  });
242  }
243 
253  @Override
255  return new DataResultViewerTable();
256  }
257 
263  @Override
264  @NbBundle.Messages("DataResultViewerTable.title=Table")
265  public String getTitle() {
266  return title;
267  }
268 
277  @Override
278  public boolean isSupported(Node candidateRootNode) {
279  return true;
280  }
281 
287  @Override
289  public void setNode(Node rootNode) {
290  if (!SwingUtilities.isEventDispatchThread()) {
291  LOGGER.log(Level.SEVERE, "Attempting to run setNode() from non-EDT thread");
292  return;
293  }
294 
295  /*
296  * The quick filter must be reset because when determining column width,
297  * ETable.getRowCount is called, and the documentation states that quick
298  * filters must be unset for the method to work "If the quick-filter is
299  * applied the number of rows do not match the number of rows in the
300  * model."
301  */
302  outline.unsetQuickFilter();
303 
304  this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
305  try {
306  if (rootNode != null) {
307  this.rootNode = rootNode;
308 
313  String nodeName = rootNode.getName();
314  pagingSupport = nodeNameToPagingSupportMap.get(nodeName);
315  if (pagingSupport == null) {
316  pagingSupport = new PagingSupport(nodeName);
317  nodeNameToPagingSupportMap.put(nodeName, pagingSupport);
318  }
319  pagingSupport.updateControls();
320 
321  rootNode.addNodeListener(new NodeListener() {
322  @Override
323  public void childrenAdded(NodeMemberEvent nme) {
330  SwingUtilities.invokeLater(() -> {
331  setCursor(null);
332  });
333  }
334 
335  @Override
336  public void childrenRemoved(NodeMemberEvent nme) {
337  SwingUtilities.invokeLater(() -> {
338  setCursor(null);
339  });
340  }
341 
342  @Override
343  public void childrenReordered(NodeReorderEvent nre) {
344  // No-op
345  }
346 
347  @Override
348  public void nodeDestroyed(NodeEvent ne) {
349  // No-op
350  }
351 
352  @Override
353  public void propertyChange(PropertyChangeEvent evt) {
354  // No-op
355  }
356  });
357  }
358 
359  /*
360  * If the given node is not null and has children, set it as the
361  * root context of the child OutlineView, otherwise make an
362  * "empty"node the root context.
363  *
364  * IMPORTANT NOTE: This is the first of many times where a
365  * getChildren call on the current root node causes all of the
366  * children of the root node to be created and defeats lazy child
367  * node creation, if it is enabled. It also likely leads to many
368  * case database round trips.
369  */
370  if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) {
371  this.getExplorerManager().setRootContext(this.rootNode);
372  setupTable();
373  } else {
374  Node emptyNode = new AbstractNode(Children.LEAF);
375  this.getExplorerManager().setRootContext(emptyNode);
376  outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
377  outlineViewListener.listenToVisibilityChanges(false);
378  outlineView.setPropertyColumns();
379  }
380  } finally {
381  this.setCursor(null);
382  }
383  }
384 
391  protected void addTreeExpansionListener(TreeExpansionListener listener) {
392  outlineView.addTreeExpansionListener(listener);
393  }
394 
400  private void setupTable() {
401  /*
402  * Since we are modifying the columns, we don't want to listen to
403  * added/removed events as un-hide/hide, until the table setup is done.
404  */
405  outlineViewListener.listenToVisibilityChanges(false);
406  /*
407  * OutlineView makes the first column be the result of
408  * node.getDisplayName with the icon. This duplicates our first column,
409  * which is the file name, etc. So, pop that property off the list, but
410  * use its display name as the header for the column so that the header
411  * can change depending on the type of data being displayed.
412  *
413  * NOTE: This assumes that the first property is always the one that
414  * duplicates getDisplayName(). The current implementation does not
415  * allow the first property column to be moved.
416  */
417  List<Node.Property<?>> props = loadColumnOrder();
418  boolean propsExist = props.isEmpty() == false;
419  Node.Property<?> firstProp = null;
420  if (propsExist) {
421  firstProp = props.remove(0);
422  }
423 
424  /*
425  * show the horizontal scroll panel and show all the content & header If
426  * there is only one column (which was removed from props above) Just
427  * let the table resize itself.
428  */
429  outline.setAutoResizeMode((props.isEmpty()) ? JTable.AUTO_RESIZE_ALL_COLUMNS : JTable.AUTO_RESIZE_OFF);
430 
431  assignColumns(props); // assign columns to match the properties
432  if (firstProp != null) {
433  ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(firstProp.getDisplayName());
434  }
435 
436  setColumnWidths();
437 
438  /*
439  * Load column sorting information from preferences file and apply it to
440  * columns.
441  */
442  loadColumnSorting();
443 
444  /*
445  * Save references to columns before we deal with their visibility. This
446  * has to happen after the sorting is applied, because that actually
447  * causes the columns to be recreated. It has to happen before
448  * loadColumnVisibility so we have referenecs to the columns to pass to
449  * setColumnHidden.
450  */
451  populateColumnMap();
452 
453  /*
454  * Load column visibility information from preferences file and apply it
455  * to columns.
456  */
457  loadColumnVisibility();
458 
459  /*
460  * If one of the child nodes of the root node is to be selected, select
461  * it.
462  */
463  if (rootNode instanceof TableFilterNode) {
464  NodeSelectionInfo selectedChildInfo = ((TableFilterNode) rootNode).getChildNodeSelectionInfo();
465  if (null != selectedChildInfo) {
466  Node[] childNodes = rootNode.getChildren().getNodes(true);
467  for (int i = 0; i < childNodes.length; ++i) {
468  Node childNode = childNodes[i];
469  if (selectedChildInfo.matches(childNode)) {
470  SwingUtilities.invokeLater(() -> {
471  try {
472  this.getExplorerManager().setExploredContextAndSelection(this.rootNode, new Node[]{childNode});
473  } catch (PropertyVetoException ex) {
474  LOGGER.log(Level.SEVERE, "Failed to select node specified by selected child info", ex);
475  }
476  });
477 
478  break;
479  }
480  }
481  ((TableFilterNode) rootNode).setChildNodeSelectionInfo(null);
482  }
483  }
484 
485  /*
486  * The table setup is done, so any added/removed events can now be
487  * treated as un-hide/hide.
488  */
489  outlineViewListener.listenToVisibilityChanges(true);
490 
491  }
492 
493  /*
494  * Populates the column map for the child OutlineView of this tabular result
495  * viewer with references to the column objects for use when loading/storing
496  * the visibility info.
497  */
498  private void populateColumnMap() {
499  columnMap.clear();
500  TableColumnModel columnModel = outline.getColumnModel();
501  int columnCount = columnModel.getColumnCount();
502  //for each property get a reference to the column object from the column model.
503  for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
504  final String propName = entry.getValue().getName();
505  if (entry.getKey() < columnCount) {
506  final ETableColumn column = (ETableColumn) columnModel.getColumn(entry.getKey());
507  columnMap.put(propName, column);
508 
509  }
510  }
511  }
512 
513  /*
514  * Sets the column widths for the child OutlineView of this tabular results
515  * viewer.
516  */
517  protected void setColumnWidths() {
518  if (rootNode.getChildren().getNodesCount() != 0) {
519  final Graphics graphics = outlineView.getGraphics();
520 
521  if (graphics != null) {
522  // Current width of the outlineView
523  double outlineViewWidth = outlineView.getSize().getWidth();
524  // List of the column widths
525  List<Integer> columnWidths = new ArrayList<>();
526  final FontMetrics metrics = graphics.getFontMetrics();
527 
528  int margin = 4;
529  int padding = 8;
530 
531  int totalColumnWidth = 0;
532  int cntMaxSizeColumns = 0;
533 
534  // Calulate the width for each column keeping track of the number
535  // of columns that were set to columnwidthLimit.
536  for (int column = 0; column < outline.getModel().getColumnCount(); column++) {
537  int firstColumnPadding = (column == 0) ? 32 : 0;
538  int columnWidthLimit = (column == 0) ? 350 : 300;
539  int valuesWidth = 0;
540 
541  // find the maximum width needed to fit the values for the first 100 rows, at most
542  for (int row = 0; row < Math.min(100, outline.getRowCount()); row++) {
543  TableCellRenderer renderer = outline.getCellRenderer(row, column);
544  Component comp = outline.prepareRenderer(renderer, row, column);
545  valuesWidth = Math.max(comp.getPreferredSize().width, valuesWidth);
546  }
547 
548  int headerWidth = metrics.stringWidth(outline.getColumnName(column));
549  valuesWidth += firstColumnPadding; // add extra padding for first column
550 
551  int columnWidth = Math.max(valuesWidth, headerWidth);
552  columnWidth += 2 * margin + padding; // add margin and regular padding
553 
554  columnWidth = Math.min(columnWidth, columnWidthLimit);
555  columnWidths.add(columnWidth);
556 
557  totalColumnWidth += columnWidth;
558 
559  if (columnWidth == columnWidthLimit) {
560  cntMaxSizeColumns++;
561  }
562  }
563 
564  // Figure out how much extra, if any can be given to the columns
565  // so that the table is as wide as outlineViewWidth. If cntMaxSizeColumns
566  // is greater than 0 divide the extra space between the columns
567  // that could use more space. Otherwise divide evenly amoung
568  // all columns.
569  int extraWidth = 0;
570 
571  if (totalColumnWidth < outlineViewWidth) {
572  if (cntMaxSizeColumns > 0) {
573  extraWidth = (int) ((outlineViewWidth - totalColumnWidth) / cntMaxSizeColumns);
574  } else {
575  extraWidth = (int) ((outlineViewWidth - totalColumnWidth) / columnWidths.size());
576  }
577  }
578 
579  for (int column = 0; column < columnWidths.size(); column++) {
580  int columnWidth = columnWidths.get(column);
581 
582  if (cntMaxSizeColumns > 0) {
583  if (columnWidth >= ((column == 0) ? 350 : 300)) {
584  columnWidth += extraWidth;
585  }
586  } else {
587  columnWidth += extraWidth;
588  }
589 
590  outline.getColumnModel().getColumn(column).setPreferredWidth(columnWidth);
591  }
592  }
593  } else {
594  // if there's no content just auto resize all columns
595  outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
596  }
597  }
598 
599  protected TableColumnModel getColumnModel() {
600  return outline.getColumnModel();
601  }
602 
603  /*
604  * Sets up the columns for the child OutlineView of this tabular results
605  * viewer with respect to column names and visisbility.
606  */
607  synchronized private void assignColumns(List<Property<?>> props) {
608  String[] propStrings = new String[props.size() * 2];
609  for (int i = 0; i < props.size(); i++) {
610  final Property<?> prop = props.get(i);
611  prop.setValue("ComparableColumnTTV", Boolean.TRUE); //NON-NLS
612  //First property column is sorted initially
613  if (i == 0) {
614  prop.setValue("TreeColumnTTV", Boolean.TRUE); // Identifies special property representing first (tree) column. NON-NLS
615  prop.setValue("SortingColumnTTV", Boolean.TRUE); // TreeTableView should be initially sorted by this property column. NON-NLS
616  }
617  propStrings[2 * i] = prop.getName();
618  propStrings[2 * i + 1] = prop.getDisplayName();
619  }
620  outlineView.setPropertyColumns(propStrings);
621  }
622 
627  private synchronized void storeColumnVisibility() {
628  if (rootNode == null || propertiesMap.isEmpty()) {
629  return;
630  }
631  if (rootNode instanceof TableFilterNode) {
632  TableFilterNode tfn = (TableFilterNode) rootNode;
633  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
634  final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
635  for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
636  String columnName = entry.getKey();
637  final String columnHiddenKey = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName);
638  final TableColumn column = entry.getValue();
639  boolean columnHidden = columnModel.isColumnHidden(column);
640  if (columnHidden) {
641  preferences.putBoolean(columnHiddenKey, true);
642  } else {
643  preferences.remove(columnHiddenKey);
644  }
645  }
646  }
647  }
648 
653  private synchronized void storeColumnOrder() {
654  if (rootNode == null || propertiesMap.isEmpty()) {
655  return;
656  }
657  if (rootNode instanceof TableFilterNode) {
658  TableFilterNode tfn = (TableFilterNode) rootNode;
659  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
660  // Store the current order of the columns into settings
661  for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
662  preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey());
663  }
664  }
665  }
666 
670  private synchronized void storeColumnSorting() {
671  if (rootNode == null || propertiesMap.isEmpty()) {
672  return;
673  }
674  if (rootNode instanceof TableFilterNode) {
675  final TableFilterNode tfn = ((TableFilterNode) rootNode);
676  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
677  ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
678  for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
679  ETableColumn etc = entry.getValue();
680  String columnName = entry.getKey();
681  //store sort rank and order
682  final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName);
683  final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, columnName);
684  if (etc.isSorted() && (columnModel.isColumnHidden(etc) == false)) {
685  preferences.putBoolean(columnSortOrderKey, etc.isAscending());
686  preferences.putInt(columnSortRankKey, etc.getSortRank());
687  } else {
688  columnModel.setColumnSorted(etc, true, 0);
689  preferences.remove(columnSortOrderKey);
690  preferences.remove(columnSortRankKey);
691  }
692  }
693  }
694  }
695 
702  private synchronized void loadColumnSorting() {
703  if (rootNode == null || propertiesMap.isEmpty()) {
704  return;
705  }
706  if (rootNode instanceof TableFilterNode) {
707  final TableFilterNode tfn = (TableFilterNode) rootNode;
708  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
709  //organize property sorting information, sorted by rank
710  TreeSet<ColumnSortInfo> sortInfos = new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank));
711  propertiesMap.entrySet().stream().forEach(entry -> {
712  final String propName = entry.getValue().getName();
713  //if the sort rank is undefined, it will be defaulted to 0 => unsorted.
714  Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0);
715  //default to true => ascending
716  Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName), true);
717  sortInfos.add(new ColumnSortInfo(entry.getKey(), sortRank, sortOrder));
718  });
719  //apply sort information in rank order.
720  sortInfos.forEach(sortInfo -> outline.setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank));
721  }
722  }
723 
728  private synchronized void loadColumnVisibility() {
729  if (rootNode == null || propertiesMap.isEmpty()) {
730  return;
731  }
732  if (rootNode instanceof TableFilterNode) {
733  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
734  final TableFilterNode tfn = ((TableFilterNode) rootNode);
735  ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
736  for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
737  final String propName = entry.getValue().getName();
738  boolean hidden = preferences.getBoolean(ResultViewerPersistence.getColumnHiddenKey(tfn, propName), false);
739  final TableColumn column = columnMap.get(propName);
740  columnModel.setColumnHidden(column, hidden);
741  }
742  }
743  }
744 
753  private synchronized List<Node.Property<?>> loadColumnOrder() {
754 
755  List<Property<?>> props = ResultViewerPersistence.getAllChildProperties(rootNode, 100);
756 
757  // If node is not table filter node, use default order for columns
758  if (!(rootNode instanceof TableFilterNode)) {
759  return props;
760  }
761 
762  final TableFilterNode tfn = ((TableFilterNode) rootNode);
763  propertiesMap.clear();
764 
765  /*
766  * We load column index values into the properties map. If a property's
767  * index is outside the range of the number of properties or the index
768  * has already appeared as the position of another property, we put that
769  * property at the end.
770  */
771  int offset = props.size();
772 
773  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
774 
775  for (Property<?> prop : props) {
776  Integer value = preferences.getInt(ResultViewerPersistence.getColumnPositionKey(tfn, prop.getName()), -1);
777  if (value >= 0 && value < offset && !propertiesMap.containsKey(value)) {
778  propertiesMap.put(value, prop);
779  } else {
780  propertiesMap.put(offset, prop);
781  offset++;
782  }
783  }
784 
785  /*
786  NOTE: it is possible to have "discontinuities" in the keys (i.e. column numbers)
787  of the map. This happens when some of the columns had a previous setting, and
788  other columns did not. We need to make the keys 0-indexed and continuous.
789  */
790  compactPropertiesMap();
791 
792  return new ArrayList<>(propertiesMap.values());
793  }
794 
799  private void compactPropertiesMap() {
800 
801  // check if there are discontinuities in the map keys.
802  int size = propertiesMap.size();
803  Queue<Integer> availablePositions = new LinkedList<>();
804  for (int i = 0; i < size; i++) {
805  if (!propertiesMap.containsKey(i)) {
806  availablePositions.add(i);
807  }
808  }
809 
810  // if there are no discontinuities, we are done
811  if (availablePositions.isEmpty()) {
812  return;
813  }
814 
815  // otherwise, move map elements into the available positions.
816  // we don't want to just move down all elements, as we want to preserve the order
817  // of the ones that had previous setting (i.e. ones that have key < size)
818  ArrayList<Integer> keys = new ArrayList<>(propertiesMap.keySet());
819  for (int key : keys) {
820  if (key >= size) {
821  propertiesMap.put(availablePositions.remove(), propertiesMap.remove(key));
822  }
823  }
824  }
825 
830  @Override
831  public void clearComponent() {
832  this.outlineView.removeAll();
833  this.outlineView = null;
834  super.clearComponent();
835  }
836 
840  static private final class ColumnSortInfo {
841 
842  private final int modelIndex;
843  private final int rank;
844  private final boolean order;
845 
846  private ColumnSortInfo(int modelIndex, int rank, boolean order) {
847  this.modelIndex = modelIndex;
848  this.rank = rank;
849  this.order = order;
850  }
851 
852  private int getRank() {
853  return rank;
854  }
855  }
856 
862  private class PagingSupport {
863 
864  private int currentPage;
865  private int totalPages;
866  private final String nodeName;
867 
868  PagingSupport(String nodeName) {
869  currentPage = 1;
870  totalPages = 0;
871  this.nodeName = nodeName;
872  initialize();
873  }
874 
875  private void initialize() {
876  if (!nodeName.isEmpty()) {
877  BaseChildFactory.register(nodeName, this);
878  }
879  updateControls();
880  }
881 
882  void nextPage() {
883  currentPage++;
884  postPageChangeEvent();
885  }
886 
887  void previousPage() {
888  currentPage--;
889  postPageChangeEvent();
890  }
891 
892  @NbBundle.Messages({"# {0} - totalPages",
893  "DataResultViewerTable.goToPageTextField.msgDlg=Please enter a valid page number between 1 and {0}",
894  "DataResultViewerTable.goToPageTextField.err=Invalid page number"})
895  void gotoPage() {
896  int originalPage = currentPage;
897 
898  try {
899  currentPage = Integer.decode(gotoPageTextField.getText());
900  } catch (NumberFormatException e) {
901  //ignore input
902  return;
903  }
904 
905  if (currentPage > totalPages || currentPage < 1) {
906  currentPage = originalPage;
907  JOptionPane.showMessageDialog(DataResultViewerTable.this,
908  Bundle.DataResultViewerTable_goToPageTextField_msgDlg(totalPages),
909  Bundle.DataResultViewerTable_goToPageTextField_err(),
910  JOptionPane.WARNING_MESSAGE);
911  return;
912  }
913  postPageChangeEvent();
914  }
915 
920  void postPageChangeEvent() {
921  try {
922  BaseChildFactory.post(nodeName, new PageChangeEvent(currentPage));
923  } catch (BaseChildFactory.NoSuchEventBusException ex) {
924  LOGGER.log(Level.WARNING, "Failed to post page change event.", ex); //NON-NLS
925  }
926  DataResultViewerTable.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
927  updateControls();
928  }
929 
934  void postPageSizeChangeEvent() {
935  // Reset page variables when page size changes
936  currentPage = 1;
937 
938  if (this == pagingSupport) {
939  updateControls();
940  }
941  try {
942  BaseChildFactory.post(nodeName, new PageSizeChangeEvent(UserPreferences.getResultsTablePageSize()));
943  } catch (BaseChildFactory.NoSuchEventBusException ex) {
944  LOGGER.log(Level.WARNING, "Failed to post page size change event.", ex); //NON-NLS
945  }
946  }
947 
953  @Subscribe
955  if (event != null) {
956  totalPages = event.getPageCount();
957  if (totalPages > 1) {
958  // Make paging controls visible if there is more than one page.
959  togglePageControls(true);
960  }
961 
962  // Only update UI controls if this event is for the node currently being viewed.
963  if (nodeName.equals(rootNode.getName())) {
964  updateControls();
965  }
966  }
967  }
968 
974  private void togglePageControls(boolean onOff) {
975  pageLabel.setVisible(onOff);
976  pagesLabel.setVisible(onOff);
977  pagePrevButton.setVisible(onOff);
978  pageNextButton.setVisible(onOff);
979  pageNumLabel.setVisible(onOff);
980  gotoPageLabel.setVisible(onOff);
981  gotoPageTextField.setVisible(onOff);
982  gotoPageTextField.setVisible(onOff);
983  validate();
984  repaint();
985  }
986 
987  @NbBundle.Messages({"# {0} - currentPage", "# {1} - totalPages",
988  "DataResultViewerTable.pageNumbers.curOfTotal={0} of {1}"})
989  private void updateControls() {
990  if (totalPages == 0) {
991  pagePrevButton.setEnabled(false);
992  pageNextButton.setEnabled(false);
993  pageNumLabel.setText("");
994  gotoPageTextField.setText("");
995  gotoPageTextField.setEnabled(false);
996  } else {
997  pageNumLabel.setText(Bundle.DataResultViewerTable_pageNumbers_curOfTotal(Integer.toString(currentPage), Integer.toString(totalPages)));
998 
999  pageNextButton.setEnabled(currentPage != totalPages);
1000  pagePrevButton.setEnabled(currentPage != 1);
1001  gotoPageTextField.setEnabled(totalPages > 1);
1002  gotoPageTextField.setText("");
1003  }
1004  }
1005  }
1006 
1011  private class IconRendererTableListener implements TableColumnModelListener {
1012 
1013  @NbBundle.Messages({"DataResultViewerTable.commentRender.name=C",
1014  "DataResultViewerTable.commentRender.toolTip=C(omments) indicates whether the item has a comment",
1015  "DataResultViewerTable.scoreRender.name=S",
1016  "DataResultViewerTable.scoreRender.toolTip=S(core) indicates whether the item is interesting or notable",
1017  "DataResultViewerTable.countRender.name=O",
1018  "DataResultViewerTable.countRender.toolTip=O(ccurrences) indicates the number of data sources containing the item in the Central Repository"})
1019  @Override
1020  public void columnAdded(TableColumnModelEvent e) {
1021  if (e.getSource() instanceof ETableColumnModel) {
1022  TableColumn column = ((TableColumnModel) e.getSource()).getColumn(e.getToIndex());
1023  if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_commentRender_name())) {
1024  //if the current column is a comment column set the cell renderer to be the HasCommentCellRenderer
1025  outlineView.setPropertyColumnDescription(column.getHeaderValue().toString(), Bundle.DataResultViewerTable_commentRender_toolTip());
1026  column.setCellRenderer(new HasCommentCellRenderer());
1027  } else if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_scoreRender_name())) {
1028  //if the current column is a score column set the cell renderer to be the ScoreCellRenderer
1029  outlineView.setPropertyColumnDescription(column.getHeaderValue().toString(), Bundle.DataResultViewerTable_scoreRender_toolTip());
1030  column.setCellRenderer(new ScoreCellRenderer());
1031  } else if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_countRender_name())) {
1032  outlineView.setPropertyColumnDescription(column.getHeaderValue().toString(), Bundle.DataResultViewerTable_countRender_toolTip());
1033  column.setCellRenderer(new CountCellRenderer());
1034  }
1035  }
1036  }
1037 
1038  @Override
1039  public void columnRemoved(TableColumnModelEvent e
1040  ) {
1041  //Don't do anything when column removed
1042  }
1043 
1044  @Override
1045  public void columnMoved(TableColumnModelEvent e
1046  ) {
1047  //Don't do anything when column moved
1048  }
1049 
1050  @Override
1051  public void columnMarginChanged(ChangeEvent e
1052  ) {
1053  //Don't do anything when column margin changed
1054  }
1055 
1056  @Override
1057  public void columnSelectionChanged(ListSelectionEvent e
1058  ) {
1059  //Don't do anything when column selection changed
1060  }
1061 
1062  }
1063 
1068  private class TableListener extends MouseAdapter implements TableColumnModelListener {
1069 
1070  // When a column in the table is moved, these two variables keep track of where
1071  // the column started and where it ended up.
1072  private int startColumnIndex = -1;
1073  private int endColumnIndex = -1;
1074  private boolean listenToVisibilitEvents;
1075 
1076  @Override
1077  public void columnMoved(TableColumnModelEvent e) {
1078  int fromIndex = e.getFromIndex();
1079  int toIndex = e.getToIndex();
1080  if (fromIndex == toIndex) {
1081  return;
1082  }
1083 
1084  /*
1085  * Because a column may be dragged to several different positions
1086  * before the mouse is released (thus causing multiple
1087  * TableColumnModelEvents to be fired), we want to keep track of the
1088  * starting column index in this potential series of movements.
1089  * Therefore we only keep track of the original fromIndex in
1090  * startColumnIndex, but we always update endColumnIndex to know the
1091  * final position of the moved column. See the MouseListener
1092  * mouseReleased method.
1093  */
1094  if (startColumnIndex == -1) {
1095  startColumnIndex = fromIndex;
1096  }
1097  endColumnIndex = toIndex;
1098 
1099  // This list contains the keys of propertiesMap in order
1100  ArrayList<Integer> indicesList = new ArrayList<>(propertiesMap.keySet());
1101  int leftIndex = Math.min(fromIndex, toIndex);
1102  int rightIndex = Math.max(fromIndex, toIndex);
1103  // Now we can copy the range of keys that have been affected by
1104  // the column movement
1105  List<Integer> range = indicesList.subList(leftIndex, rightIndex + 1);
1106  int rangeSize = range.size();
1107 
1108  if (fromIndex < toIndex) {
1109  // column moved right, shift all properties left, put in moved
1110  // property at the rightmost index
1111  Property<?> movedProp = propertiesMap.get(range.get(0));
1112  for (int i = 0; i < rangeSize - 1; i++) {
1113  propertiesMap.put(range.get(i), propertiesMap.get(range.get(i + 1)));
1114  }
1115  propertiesMap.put(range.get(rangeSize - 1), movedProp);
1116  } else {
1117  // column moved left, shift all properties right, put in moved
1118  // property at the leftmost index
1119  Property<?> movedProp = propertiesMap.get(range.get(rangeSize - 1));
1120  for (int i = rangeSize - 1; i > 0; i--) {
1121  propertiesMap.put(range.get(i), propertiesMap.get(range.get(i - 1)));
1122  }
1123  propertiesMap.put(range.get(0), movedProp);
1124  }
1125 
1126  storeColumnOrder();
1127  }
1128 
1129  @Override
1130  public void mouseReleased(MouseEvent e) {
1131  /*
1132  * If the startColumnIndex is not -1 (which is the reset value),
1133  * that means columns have been moved around. We then check to see
1134  * if either the starting or end position is 0 (the first column),
1135  * and then swap them back if that is the case because we don't want
1136  * to allow movement of the first column. We then reset
1137  * startColumnIndex to -1, the reset value. We check if
1138  * startColumnIndex is at reset or not because it is possible for
1139  * the mouse to be released and a MouseEvent to be fired without
1140  * having moved any columns.
1141  */
1142  if (startColumnIndex != -1 && (startColumnIndex == 0 || endColumnIndex == 0)) {
1143  outline.moveColumn(endColumnIndex, startColumnIndex);
1144  }
1145  startColumnIndex = -1;
1146  }
1147 
1148  @Override
1149  public void mouseClicked(MouseEvent e) {
1150  //the user clicked a column header
1151  storeColumnSorting();
1152  }
1153 
1154  @Override
1155  public void columnAdded(TableColumnModelEvent e) {
1156  columnAddedOrRemoved();
1157  }
1158 
1159  @Override
1160  public void columnRemoved(TableColumnModelEvent e) {
1161  columnAddedOrRemoved();
1162  }
1163 
1169  private void columnAddedOrRemoved() {
1170  if (listenToVisibilitEvents) {
1171  SwingUtilities.invokeLater(DataResultViewerTable.this::storeColumnVisibility);
1172 
1173  }
1174  }
1175 
1176  @Override
1177  public void columnMarginChanged(ChangeEvent e) {
1178  }
1179 
1180  @Override
1181  public void columnSelectionChanged(ListSelectionEvent e) {
1182  }
1183 
1193  private void listenToVisibilityChanges(boolean b) {
1194  this.listenToVisibilitEvents = b;
1195  }
1196  }
1197 
1198  /*
1199  * A renderer which based on the contents of the cell will display an icon
1200  * to indicate the presence of a comment related to the content.
1201  */
1202  private final class HasCommentCellRenderer extends DefaultOutlineCellRenderer {
1203 
1204  private static final long serialVersionUID = 1L;
1205 
1206  @NbBundle.Messages({"DataResultViewerTable.commentRenderer.crComment.toolTip=Comment exists in Central Repository",
1207  "DataResultViewerTable.commentRenderer.tagComment.toolTip=Comment exists on associated tag(s)",
1208  "DataResultViewerTable.commentRenderer.crAndTagComment.toolTip=Comments exist both in Central Repository and on associated tag(s)",
1209  "DataResultViewerTable.commentRenderer.noComment.toolTip=No comments found"})
1210  @Override
1211  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
1212  Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
1213  setBackground(component.getBackground()); //inherit highlighting for selection
1214  setHorizontalAlignment(CENTER);
1215  Object switchValue = null;
1216  if ((value instanceof NodeProperty)) {
1217  //The Outline view has properties in the cell, the value contained in the property is what we want
1218  try {
1219  switchValue = ((Node.Property) value).getValue();
1220  } catch (IllegalAccessException | InvocationTargetException ex) {
1221  //Unable to get the value from the NodeProperty no Icon will be displayed
1222  }
1223  } else {
1224  //JTables contain the value we want directly in the cell
1225  switchValue = value;
1226  }
1227  setText("");
1228  if ((switchValue instanceof HasCommentStatus)) {
1229 
1230  switch ((HasCommentStatus) switchValue) {
1231  case CR_COMMENT:
1232  setIcon(COMMENT_ICON);
1233  setToolTipText(Bundle.DataResultViewerTable_commentRenderer_crComment_toolTip());
1234  break;
1235  case TAG_COMMENT:
1236  setIcon(COMMENT_ICON);
1237  setToolTipText(Bundle.DataResultViewerTable_commentRenderer_tagComment_toolTip());
1238  break;
1239  case CR_AND_TAG_COMMENTS:
1240  setIcon(COMMENT_ICON);
1241  setToolTipText(Bundle.DataResultViewerTable_commentRenderer_crAndTagComment_toolTip());
1242  break;
1243  case TAG_NO_COMMENT:
1244  case NO_COMMENT:
1245  default:
1246  setIcon(null);
1247  setToolTipText(Bundle.DataResultViewerTable_commentRenderer_noComment_toolTip());
1248  }
1249  } else {
1250  setIcon(null);
1251  }
1252 
1253  return this;
1254  }
1255 
1256  }
1257 
1258  /*
1259  * A renderer which based on the contents of the cell will display an icon
1260  * to indicate the score associated with the item.
1261  */
1262  private final class ScoreCellRenderer extends DefaultOutlineCellRenderer {
1263 
1264  private static final long serialVersionUID = 1L;
1265 
1266  @Override
1267  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
1268  Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
1269  setBackground(component.getBackground()); //inherit highlighting for selection
1270  setHorizontalAlignment(CENTER);
1271  Object switchValue = null;
1272  if ((value instanceof NodeProperty)) {
1273  //The Outline view has properties in the cell, the value contained in the property is what we want
1274  try {
1275  switchValue = ((Node.Property) value).getValue();
1276  setToolTipText(((FeatureDescriptor) value).getShortDescription());
1277  } catch (IllegalAccessException | InvocationTargetException ex) {
1278  //Unable to get the value from the NodeProperty no Icon will be displayed
1279  }
1280 
1281  } else {
1282  //JTables contain the value we want directly in the cell
1283  switchValue = value;
1284  }
1285  setText("");
1286  if ((switchValue instanceof Score)) {
1287 
1288  switch ((Score) switchValue) {
1289  case INTERESTING_SCORE:
1290  setIcon(INTERESTING_SCORE_ICON);
1291  break;
1292  case NOTABLE_SCORE:
1293  setIcon(NOTABLE_ICON_SCORE);
1294  break;
1295  case NO_SCORE:
1296  default:
1297  setIcon(null);
1298  }
1299  } else {
1300  setIcon(null);
1301  }
1302  return this;
1303  }
1304 
1305  }
1306 
1307  /*
1308  * A renderer which based on the contents of the cell will display an empty
1309  * cell if no count was available.
1310  */
1311  private final class CountCellRenderer extends DefaultOutlineCellRenderer {
1312 
1313  private static final long serialVersionUID = 1L;
1314 
1315  @Override
1316  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
1317  Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
1318  setBackground(component.getBackground()); //inherit highlighting for selection
1319  setHorizontalAlignment(LEFT);
1320  Object countValue = null;
1321  if ((value instanceof NodeProperty)) {
1322  //The Outline view has properties in the cell, the value contained in the property is what we want
1323  try {
1324  countValue = ((Node.Property) value).getValue();
1325  setToolTipText(((FeatureDescriptor) value).getShortDescription());
1326  } catch (IllegalAccessException | InvocationTargetException ex) {
1327  //Unable to get the value from the NodeProperty no Icon will be displayed
1328  }
1329  } else {
1330  //JTables contain the value we want directly in the cell
1331  countValue = value;
1332  }
1333  setText("");
1334  if ((countValue instanceof Long)) {
1335  //Don't display value if value is negative used so that sorting will behave as desired
1336  if ((Long) countValue >= 0) {
1337  setText(countValue.toString());
1338  }
1339  }
1340  return this;
1341  }
1342 
1343  }
1344 
1349  public enum HasCommentStatus {
1354  CR_AND_TAG_COMMENTS
1355  }
1356 
1360  public enum Score {
1363  NOTABLE_SCORE
1364  }
1365 
1371  @SuppressWarnings("unchecked")
1372  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
1373  private void initComponents() {
1374 
1375  pageLabel = new javax.swing.JLabel();
1376  pageNumLabel = new javax.swing.JLabel();
1377  pagesLabel = new javax.swing.JLabel();
1378  pagePrevButton = new javax.swing.JButton();
1379  pageNextButton = new javax.swing.JButton();
1380  outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL);
1381  gotoPageLabel = new javax.swing.JLabel();
1382  gotoPageTextField = new javax.swing.JTextField();
1383  exportCSVButton = new javax.swing.JButton();
1384 
1385  pageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageLabel.text")); // NOI18N
1386 
1387  pageNumLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageNumLabel.text")); // NOI18N
1388 
1389  pagesLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pagesLabel.text")); // NOI18N
1390 
1391  pagePrevButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"))); // NOI18N
1392  pagePrevButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pagePrevButton.text")); // NOI18N
1393  pagePrevButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); // NOI18N
1394  pagePrevButton.setFocusable(false);
1395  pagePrevButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
1396  pagePrevButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
1397  pagePrevButton.setPreferredSize(new java.awt.Dimension(55, 23));
1398  pagePrevButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png"))); // NOI18N
1399  pagePrevButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
1400  pagePrevButton.addActionListener(new java.awt.event.ActionListener() {
1401  public void actionPerformed(java.awt.event.ActionEvent evt) {
1402  pagePrevButtonActionPerformed(evt);
1403  }
1404  });
1405 
1406  pageNextButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"))); // NOI18N
1407  pageNextButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageNextButton.text")); // NOI18N
1408  pageNextButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); // NOI18N
1409  pageNextButton.setFocusable(false);
1410  pageNextButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
1411  pageNextButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
1412  pageNextButton.setMaximumSize(new java.awt.Dimension(27, 23));
1413  pageNextButton.setMinimumSize(new java.awt.Dimension(27, 23));
1414  pageNextButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_hover.png"))); // NOI18N
1415  pageNextButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
1416  pageNextButton.addActionListener(new java.awt.event.ActionListener() {
1417  public void actionPerformed(java.awt.event.ActionEvent evt) {
1418  pageNextButtonActionPerformed(evt);
1419  }
1420  });
1421 
1422  gotoPageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.gotoPageLabel.text")); // NOI18N
1423 
1424  gotoPageTextField.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.gotoPageTextField.text")); // NOI18N
1425  gotoPageTextField.addActionListener(new java.awt.event.ActionListener() {
1426  public void actionPerformed(java.awt.event.ActionEvent evt) {
1427  gotoPageTextFieldActionPerformed(evt);
1428  }
1429  });
1430 
1431  exportCSVButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.exportCSVButton.text")); // NOI18N
1432  exportCSVButton.addActionListener(new java.awt.event.ActionListener() {
1433  public void actionPerformed(java.awt.event.ActionEvent evt) {
1434  exportCSVButtonActionPerformed(evt);
1435  }
1436  });
1437 
1438  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
1439  this.setLayout(layout);
1440  layout.setHorizontalGroup(
1441  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
1442  .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 904, Short.MAX_VALUE)
1443  .addGroup(layout.createSequentialGroup()
1444  .addContainerGap()
1445  .addComponent(pageLabel)
1446  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
1447  .addComponent(pageNumLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE)
1448  .addGap(14, 14, 14)
1449  .addComponent(pagesLabel)
1450  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
1451  .addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)
1452  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
1453  .addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)
1454  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
1455  .addComponent(gotoPageLabel)
1456  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
1457  .addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 33, javax.swing.GroupLayout.PREFERRED_SIZE)
1458  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
1459  .addComponent(exportCSVButton))
1460  );
1461 
1462  layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {pageNextButton, pagePrevButton});
1463 
1464  layout.setVerticalGroup(
1465  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
1466  .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
1467  .addGap(3, 3, 3)
1468  .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
1469  .addComponent(pageLabel)
1470  .addComponent(pageNumLabel)
1471  .addComponent(pagesLabel)
1472  .addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE)
1473  .addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 15, javax.swing.GroupLayout.PREFERRED_SIZE)
1474  .addComponent(gotoPageLabel)
1475  .addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
1476  .addComponent(exportCSVButton))
1477  .addGap(3, 3, 3)
1478  .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 321, Short.MAX_VALUE)
1479  .addContainerGap())
1480  );
1481 
1482  layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {pageNextButton, pagePrevButton});
1483 
1484  gotoPageLabel.getAccessibleContext().setAccessibleName("");
1485  }// </editor-fold>//GEN-END:initComponents
1486 
1487  private void pagePrevButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pagePrevButtonActionPerformed
1488  pagingSupport.previousPage();
1489  }//GEN-LAST:event_pagePrevButtonActionPerformed
1490 
1491  private void pageNextButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pageNextButtonActionPerformed
1492  pagingSupport.nextPage();
1493  }//GEN-LAST:event_pageNextButtonActionPerformed
1494 
1495  private void gotoPageTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gotoPageTextFieldActionPerformed
1496  pagingSupport.gotoPage();
1497  }//GEN-LAST:event_gotoPageTextFieldActionPerformed
1498 
1499  @NbBundle.Messages({"DataResultViewerTable.exportCSVButtonActionPerformed.empty=No data to export"
1500  })
1501  private void exportCSVButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportCSVButtonActionPerformed
1502  Node currentRoot = this.getExplorerManager().getRootContext();
1503  if (currentRoot != null && currentRoot.getChildren().getNodesCount() > 0) {
1504  org.sleuthkit.autopsy.directorytree.ExportCSVAction.saveNodesToCSV(java.util.Arrays.asList(currentRoot.getChildren().getNodes()), this);
1505  } else {
1506  MessageNotifyUtil.Message.info(Bundle.DataResultViewerTable_exportCSVButtonActionPerformed_empty());
1507  }
1508  }//GEN-LAST:event_exportCSVButtonActionPerformed
1509 
1510  // Variables declaration - do not modify//GEN-BEGIN:variables
1511  private javax.swing.JButton exportCSVButton;
1512  private javax.swing.JLabel gotoPageLabel;
1513  private javax.swing.JTextField gotoPageTextField;
1514  private org.openide.explorer.view.OutlineView outlineView;
1515  private javax.swing.JLabel pageLabel;
1516  private javax.swing.JButton pageNextButton;
1517  private javax.swing.JLabel pageNumLabel;
1518  private javax.swing.JButton pagePrevButton;
1519  private javax.swing.JLabel pagesLabel;
1520  // End of variables declaration//GEN-END:variables
1521 
1522 }
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: Tue Jan 19 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.