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