Autopsy  4.12.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  SwingUtilities.invokeLater(() -> {
464  if (rootNode instanceof TableFilterNode) {
465  NodeSelectionInfo selectedChildInfo = ((TableFilterNode) rootNode).getChildNodeSelectionInfo();
466  if (null != selectedChildInfo) {
467  Node[] childNodes = rootNode.getChildren().getNodes(true);
468  for (int i = 0; i < childNodes.length; ++i) {
469  Node childNode = childNodes[i];
470  if (selectedChildInfo.matches(childNode)) {
471  try {
472  this.getExplorerManager().setSelectedNodes(new Node[]{childNode});
473  } catch (PropertyVetoException ex) {
474  LOGGER.log(Level.SEVERE, "Failed to select node specified by selected child info", ex);
475  }
476  break;
477  }
478  }
479  ((TableFilterNode) rootNode).setChildNodeSelectionInfo(null);
480  }
481  }
482  });
483 
484  /*
485  * The table setup is done, so any added/removed events can now be
486  * treated as un-hide/hide.
487  */
488  outlineViewListener.listenToVisibilityChanges(true);
489 
490  }
491 
492  /*
493  * Populates the column map for the child OutlineView of this tabular result
494  * viewer with references to the column objects for use when loading/storing
495  * the visibility info.
496  */
497  private void populateColumnMap() {
498  columnMap.clear();
499  TableColumnModel columnModel = outline.getColumnModel();
500  int columnCount = columnModel.getColumnCount();
501  //for each property get a reference to the column object from the column model.
502  for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
503  final String propName = entry.getValue().getName();
504  if (entry.getKey() < columnCount) {
505  final ETableColumn column = (ETableColumn) columnModel.getColumn(entry.getKey());
506  columnMap.put(propName, column);
507 
508  }
509  }
510  }
511 
512  /*
513  * Sets the column widths for the child OutlineView of this tabular results
514  * viewer.
515  */
516  protected void setColumnWidths() {
517  if (rootNode.getChildren().getNodesCount() != 0) {
518  final Graphics graphics = outlineView.getGraphics();
519 
520  if (graphics != null) {
521  // Current width of the outlineView
522  double outlineViewWidth = outlineView.getSize().getWidth();
523  // List of the column widths
524  List<Integer> columnWidths = new ArrayList<>();
525  final FontMetrics metrics = graphics.getFontMetrics();
526 
527  int margin = 4;
528  int padding = 8;
529 
530  int totalColumnWidth = 0;
531  int cntMaxSizeColumns =0;
532 
533  // Calulate the width for each column keeping track of the number
534  // of columns that were set to columnwidthLimit.
535  for (int column = 0; column < outline.getModel().getColumnCount(); column++) {
536  int firstColumnPadding = (column == 0) ? 32 : 0;
537  int columnWidthLimit = (column == 0) ? 350 : 300;
538  int valuesWidth = 0;
539 
540  // find the maximum width needed to fit the values for the first 100 rows, at most
541  for (int row = 0; row < Math.min(100, outline.getRowCount()); row++) {
542  TableCellRenderer renderer = outline.getCellRenderer(row, column);
543  Component comp = outline.prepareRenderer(renderer, row, column);
544  valuesWidth = Math.max(comp.getPreferredSize().width, valuesWidth);
545  }
546 
547  int headerWidth = metrics.stringWidth(outline.getColumnName(column));
548  valuesWidth += firstColumnPadding; // add extra padding for first column
549 
550  int columnWidth = Math.max(valuesWidth, headerWidth);
551  columnWidth += 2 * margin + padding; // add margin and regular padding
552 
553  columnWidth = Math.min(columnWidth, columnWidthLimit);
554  columnWidths.add(columnWidth);
555 
556  totalColumnWidth += columnWidth;
557 
558  if( columnWidth == columnWidthLimit) {
559  cntMaxSizeColumns++;
560  }
561  }
562 
563  // Figure out how much extra, if any can be given to the columns
564  // so that the table is as wide as outlineViewWidth. If cntMaxSizeColumns
565  // is greater than 0 divide the extra space between the columns
566  // that could use more space. Otherwise divide evenly amoung
567  // all columns.
568  int extraWidth = 0;
569 
570  if (totalColumnWidth < outlineViewWidth) {
571  if (cntMaxSizeColumns > 0) {
572  extraWidth = (int) ((outlineViewWidth - totalColumnWidth)/cntMaxSizeColumns);
573  } else {
574  extraWidth = (int) ((outlineViewWidth - totalColumnWidth)/columnWidths.size());
575  }
576  }
577 
578  for(int column = 0; column < columnWidths.size(); column++) {
579  int columnWidth = columnWidths.get(column);
580 
581  if(cntMaxSizeColumns > 0) {
582  if(columnWidth >= ((column == 0) ? 350 : 300)) {
583  columnWidth += extraWidth;
584  }
585  } else {
586  columnWidth += extraWidth;
587  }
588 
589  outline.getColumnModel().getColumn(column).setPreferredWidth(columnWidth);
590  }
591  }
592  } else {
593  // if there's no content just auto resize all columns
594  outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
595  }
596  }
597 
598  protected TableColumnModel getColumnModel() {
599  return outline.getColumnModel();
600  }
601 
602  /*
603  * Sets up the columns for the child OutlineView of this tabular results
604  * viewer with respect to column names and visisbility.
605  */
606  synchronized private void assignColumns(List<Property<?>> props) {
607  String[] propStrings = new String[props.size() * 2];
608  for (int i = 0; i < props.size(); i++) {
609  final Property<?> prop = props.get(i);
610  prop.setValue("ComparableColumnTTV", Boolean.TRUE); //NON-NLS
611  //First property column is sorted initially
612  if (i == 0) {
613  prop.setValue("TreeColumnTTV", Boolean.TRUE); // Identifies special property representing first (tree) column. NON-NLS
614  prop.setValue("SortingColumnTTV", Boolean.TRUE); // TreeTableView should be initially sorted by this property column. NON-NLS
615  }
616  propStrings[2 * i] = prop.getName();
617  propStrings[2 * i + 1] = prop.getDisplayName();
618  }
619  outlineView.setPropertyColumns(propStrings);
620  }
621 
626  private synchronized void storeColumnVisibility() {
627  if (rootNode == null || propertiesMap.isEmpty()) {
628  return;
629  }
630  if (rootNode instanceof TableFilterNode) {
631  TableFilterNode tfn = (TableFilterNode) rootNode;
632  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
633  final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
634  for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
635  String columnName = entry.getKey();
636  final String columnHiddenKey = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName);
637  final TableColumn column = entry.getValue();
638  boolean columnHidden = columnModel.isColumnHidden(column);
639  if (columnHidden) {
640  preferences.putBoolean(columnHiddenKey, true);
641  } else {
642  preferences.remove(columnHiddenKey);
643  }
644  }
645  }
646  }
647 
652  private synchronized void storeColumnOrder() {
653  if (rootNode == null || propertiesMap.isEmpty()) {
654  return;
655  }
656  if (rootNode instanceof TableFilterNode) {
657  TableFilterNode tfn = (TableFilterNode) rootNode;
658  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
659  // Store the current order of the columns into settings
660  for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
661  preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey());
662  }
663  }
664  }
665 
669  private synchronized void storeColumnSorting() {
670  if (rootNode == null || propertiesMap.isEmpty()) {
671  return;
672  }
673  if (rootNode instanceof TableFilterNode) {
674  final TableFilterNode tfn = ((TableFilterNode) rootNode);
675  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
676  ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
677  for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
678  ETableColumn etc = entry.getValue();
679  String columnName = entry.getKey();
680  //store sort rank and order
681  final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName);
682  final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, columnName);
683  if (etc.isSorted() && (columnModel.isColumnHidden(etc) == false)) {
684  preferences.putBoolean(columnSortOrderKey, etc.isAscending());
685  preferences.putInt(columnSortRankKey, etc.getSortRank());
686  } else {
687  columnModel.setColumnSorted(etc, true, 0);
688  preferences.remove(columnSortOrderKey);
689  preferences.remove(columnSortRankKey);
690  }
691  }
692  }
693  }
694 
701  private synchronized void loadColumnSorting() {
702  if (rootNode == null || propertiesMap.isEmpty()) {
703  return;
704  }
705  if (rootNode instanceof TableFilterNode) {
706  final TableFilterNode tfn = (TableFilterNode) rootNode;
707  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
708  //organize property sorting information, sorted by rank
709  TreeSet<ColumnSortInfo> sortInfos = new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank));
710  propertiesMap.entrySet().stream().forEach(entry -> {
711  final String propName = entry.getValue().getName();
712  //if the sort rank is undefined, it will be defaulted to 0 => unsorted.
713  Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0);
714  //default to true => ascending
715  Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName), true);
716  sortInfos.add(new ColumnSortInfo(entry.getKey(), sortRank, sortOrder));
717  });
718  //apply sort information in rank order.
719  sortInfos.forEach(sortInfo -> outline.setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank));
720  }
721  }
722 
727  private synchronized void loadColumnVisibility() {
728  if (rootNode == null || propertiesMap.isEmpty()) {
729  return;
730  }
731  if (rootNode instanceof TableFilterNode) {
732  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
733  final TableFilterNode tfn = ((TableFilterNode) rootNode);
734  ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
735  for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
736  final String propName = entry.getValue().getName();
737  boolean hidden = preferences.getBoolean(ResultViewerPersistence.getColumnHiddenKey(tfn, propName), false);
738  final TableColumn column = columnMap.get(propName);
739  columnModel.setColumnHidden(column, hidden);
740  }
741  }
742  }
743 
752  private synchronized List<Node.Property<?>> loadColumnOrder() {
753 
754  List<Property<?>> props = ResultViewerPersistence.getAllChildProperties(rootNode, 100);
755 
756  // If node is not table filter node, use default order for columns
757  if (!(rootNode instanceof TableFilterNode)) {
758  return props;
759  }
760 
761  final TableFilterNode tfn = ((TableFilterNode) rootNode);
762  propertiesMap.clear();
763 
764  /*
765  * We load column index values into the properties map. If a property's
766  * index is outside the range of the number of properties or the index
767  * has already appeared as the position of another property, we put that
768  * property at the end.
769  */
770  int offset = props.size();
771 
772  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
773 
774  for (Property<?> prop : props) {
775  Integer value = preferences.getInt(ResultViewerPersistence.getColumnPositionKey(tfn, prop.getName()), -1);
776  if (value >= 0 && value < offset && !propertiesMap.containsKey(value)) {
777  propertiesMap.put(value, prop);
778  } else {
779  propertiesMap.put(offset, prop);
780  offset++;
781  }
782  }
783 
784  /*
785  NOTE: it is possible to have "discontinuities" in the keys (i.e. column numbers)
786  of the map. This happens when some of the columns had a previous setting, and
787  other columns did not. We need to make the keys 0-indexed and continuous.
788  */
789  compactPropertiesMap();
790 
791  return new ArrayList<>(propertiesMap.values());
792  }
793 
798  private void compactPropertiesMap() {
799 
800  // check if there are discontinuities in the map keys.
801  int size = propertiesMap.size();
802  Queue<Integer> availablePositions = new LinkedList<>();
803  for (int i = 0; i < size; i++) {
804  if (!propertiesMap.containsKey(i)) {
805  availablePositions.add(i);
806  }
807  }
808 
809  // if there are no discontinuities, we are done
810  if (availablePositions.isEmpty()) {
811  return;
812  }
813 
814  // otherwise, move map elements into the available positions.
815  // we don't want to just move down all elements, as we want to preserve the order
816  // of the ones that had previous setting (i.e. ones that have key < size)
817  ArrayList<Integer> keys = new ArrayList<>(propertiesMap.keySet());
818  for (int key : keys) {
819  if (key >= size) {
820  propertiesMap.put(availablePositions.remove(), propertiesMap.remove(key));
821  }
822  }
823  }
824 
829  @Override
830  public void clearComponent() {
831  this.outlineView.removeAll();
832  this.outlineView = null;
833  super.clearComponent();
834  }
835 
839  static private final class ColumnSortInfo {
840 
841  private final int modelIndex;
842  private final int rank;
843  private final boolean order;
844 
845  private ColumnSortInfo(int modelIndex, int rank, boolean order) {
846  this.modelIndex = modelIndex;
847  this.rank = rank;
848  this.order = order;
849  }
850 
851  private int getRank() {
852  return rank;
853  }
854  }
855 
861  private class PagingSupport {
862 
863  private int currentPage;
864  private int totalPages;
865  private final String nodeName;
866 
867  PagingSupport(String nodeName) {
868  currentPage = 1;
869  totalPages = 0;
870  this.nodeName = nodeName;
871  initialize();
872  }
873 
874  private void initialize() {
875  if (!nodeName.isEmpty()) {
876  BaseChildFactory.register(nodeName, this);
877  }
878  updateControls();
879  }
880 
881  void nextPage() {
882  currentPage++;
883  postPageChangeEvent();
884  }
885 
886  void previousPage() {
887  currentPage--;
888  postPageChangeEvent();
889  }
890 
891  @NbBundle.Messages({"# {0} - totalPages",
892  "DataResultViewerTable.goToPageTextField.msgDlg=Please enter a valid page number between 1 and {0}",
893  "DataResultViewerTable.goToPageTextField.err=Invalid page number"})
894  void gotoPage() {
895  try {
896  currentPage = Integer.decode(gotoPageTextField.getText());
897  } catch (NumberFormatException e) {
898  //ignore input
899  return;
900  }
901 
902  if (currentPage > totalPages || currentPage < 1) {
903  currentPage = 1;
904  JOptionPane.showMessageDialog(DataResultViewerTable.this,
905  Bundle.DataResultViewerTable_goToPageTextField_msgDlg(totalPages),
906  Bundle.DataResultViewerTable_goToPageTextField_err(),
907  JOptionPane.WARNING_MESSAGE);
908  return;
909  }
910  postPageChangeEvent();
911  }
912 
917  void postPageChangeEvent() {
918  try {
919  BaseChildFactory.post(nodeName, new PageChangeEvent(currentPage));
920  } catch (BaseChildFactory.NoSuchEventBusException ex) {
921  LOGGER.log(Level.WARNING, "Failed to post page change event.", ex); //NON-NLS
922  }
923  DataResultViewerTable.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
924  updateControls();
925  }
926 
931  void postPageSizeChangeEvent() {
932  // Reset page variables when page size changes
933  currentPage = 1;
934 
935  if (this == pagingSupport) {
936  updateControls();
937  }
938  try {
939  BaseChildFactory.post(nodeName, new PageSizeChangeEvent(UserPreferences.getResultsTablePageSize()));
940  } catch (BaseChildFactory.NoSuchEventBusException ex) {
941  LOGGER.log(Level.WARNING, "Failed to post page size change event.", ex); //NON-NLS
942  }
943  }
944 
950  @Subscribe
952  if (event != null) {
953  totalPages = event.getPageCount();
954  if (totalPages > 1) {
955  // Make paging controls visible if there is more than one page.
956  togglePageControls(true);
957  }
958 
959  // Only update UI controls if this event is for the node currently being viewed.
960  if (nodeName.equals(rootNode.getName())) {
961  updateControls();
962  }
963  }
964  }
965 
971  private void togglePageControls(boolean onOff) {
972  pageLabel.setVisible(onOff);
973  pagesLabel.setVisible(onOff);
974  pagePrevButton.setVisible(onOff);
975  pageNextButton.setVisible(onOff);
976  pageNumLabel.setVisible(onOff);
977  gotoPageLabel.setVisible(onOff);
978  gotoPageTextField.setVisible(onOff);
979  gotoPageTextField.setVisible(onOff);
980  validate();
981  repaint();
982  }
983 
984  @NbBundle.Messages({"# {0} - currentPage", "# {1} - totalPages",
985  "DataResultViewerTable.pageNumbers.curOfTotal={0} of {1}"})
986  private void updateControls() {
987  if (totalPages == 0) {
988  pagePrevButton.setEnabled(false);
989  pageNextButton.setEnabled(false);
990  pageNumLabel.setText("");
991  gotoPageTextField.setText("");
992  gotoPageTextField.setEnabled(false);
993  } else {
994  pageNumLabel.setText(Bundle.DataResultViewerTable_pageNumbers_curOfTotal(Integer.toString(currentPage), Integer.toString(totalPages)));
995 
996  pageNextButton.setEnabled(currentPage != totalPages);
997  pagePrevButton.setEnabled(currentPage != 1);
998  gotoPageTextField.setEnabled(totalPages > 1);
999  gotoPageTextField.setText("");
1000  }
1001  }
1002  }
1003 
1008  private class IconRendererTableListener implements TableColumnModelListener {
1009 
1010  @NbBundle.Messages({"DataResultViewerTable.commentRender.name=C",
1011  "DataResultViewerTable.commentRender.toolTip=C(omments) indicates whether the item has a comment",
1012  "DataResultViewerTable.scoreRender.name=S",
1013  "DataResultViewerTable.scoreRender.toolTip=S(core) indicates whether the item is interesting or notable",
1014  "DataResultViewerTable.countRender.name=O",
1015  "DataResultViewerTable.countRender.toolTip=O(ccurrences) indicates the number of data sources containing the item in the Central Repository"})
1016  @Override
1017  public void columnAdded(TableColumnModelEvent e) {
1018  if (e.getSource() instanceof ETableColumnModel) {
1019  TableColumn column = ((TableColumnModel) e.getSource()).getColumn(e.getToIndex());
1020  if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_commentRender_name())) {
1021  //if the current column is a comment column set the cell renderer to be the HasCommentCellRenderer
1022  outlineView.setPropertyColumnDescription(column.getHeaderValue().toString(), Bundle.DataResultViewerTable_commentRender_toolTip());
1023  column.setCellRenderer(new HasCommentCellRenderer());
1024  } else if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_scoreRender_name())) {
1025  //if the current column is a score column set the cell renderer to be the ScoreCellRenderer
1026  outlineView.setPropertyColumnDescription(column.getHeaderValue().toString(), Bundle.DataResultViewerTable_scoreRender_toolTip());
1027  column.setCellRenderer(new ScoreCellRenderer());
1028  } else if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_countRender_name())) {
1029  outlineView.setPropertyColumnDescription(column.getHeaderValue().toString(), Bundle.DataResultViewerTable_countRender_toolTip());
1030  column.setCellRenderer(new CountCellRenderer());
1031  }
1032  }
1033  }
1034 
1035  @Override
1036  public void columnRemoved(TableColumnModelEvent e
1037  ) {
1038  //Don't do anything when column removed
1039  }
1040 
1041  @Override
1042  public void columnMoved(TableColumnModelEvent e
1043  ) {
1044  //Don't do anything when column moved
1045  }
1046 
1047  @Override
1048  public void columnMarginChanged(ChangeEvent e
1049  ) {
1050  //Don't do anything when column margin changed
1051  }
1052 
1053  @Override
1054  public void columnSelectionChanged(ListSelectionEvent e
1055  ) {
1056  //Don't do anything when column selection changed
1057  }
1058 
1059  }
1060 
1065  private class TableListener extends MouseAdapter implements TableColumnModelListener {
1066 
1067  // When a column in the table is moved, these two variables keep track of where
1068  // the column started and where it ended up.
1069  private int startColumnIndex = -1;
1070  private int endColumnIndex = -1;
1071  private boolean listenToVisibilitEvents;
1072 
1073  @Override
1074  public void columnMoved(TableColumnModelEvent e) {
1075  int fromIndex = e.getFromIndex();
1076  int toIndex = e.getToIndex();
1077  if (fromIndex == toIndex) {
1078  return;
1079  }
1080 
1081  /*
1082  * Because a column may be dragged to several different positions
1083  * before the mouse is released (thus causing multiple
1084  * TableColumnModelEvents to be fired), we want to keep track of the
1085  * starting column index in this potential series of movements.
1086  * Therefore we only keep track of the original fromIndex in
1087  * startColumnIndex, but we always update endColumnIndex to know the
1088  * final position of the moved column. See the MouseListener
1089  * mouseReleased method.
1090  */
1091  if (startColumnIndex == -1) {
1092  startColumnIndex = fromIndex;
1093  }
1094  endColumnIndex = toIndex;
1095 
1096  // This list contains the keys of propertiesMap in order
1097  ArrayList<Integer> indicesList = new ArrayList<>(propertiesMap.keySet());
1098  int leftIndex = Math.min(fromIndex, toIndex);
1099  int rightIndex = Math.max(fromIndex, toIndex);
1100  // Now we can copy the range of keys that have been affected by
1101  // the column movement
1102  List<Integer> range = indicesList.subList(leftIndex, rightIndex + 1);
1103  int rangeSize = range.size();
1104 
1105  if (fromIndex < toIndex) {
1106  // column moved right, shift all properties left, put in moved
1107  // property at the rightmost index
1108  Property<?> movedProp = propertiesMap.get(range.get(0));
1109  for (int i = 0; i < rangeSize - 1; i++) {
1110  propertiesMap.put(range.get(i), propertiesMap.get(range.get(i + 1)));
1111  }
1112  propertiesMap.put(range.get(rangeSize - 1), movedProp);
1113  } else {
1114  // column moved left, shift all properties right, put in moved
1115  // property at the leftmost index
1116  Property<?> movedProp = propertiesMap.get(range.get(rangeSize - 1));
1117  for (int i = rangeSize - 1; i > 0; i--) {
1118  propertiesMap.put(range.get(i), propertiesMap.get(range.get(i - 1)));
1119  }
1120  propertiesMap.put(range.get(0), movedProp);
1121  }
1122 
1123  storeColumnOrder();
1124  }
1125 
1126  @Override
1127  public void mouseReleased(MouseEvent e) {
1128  /*
1129  * If the startColumnIndex is not -1 (which is the reset value),
1130  * that means columns have been moved around. We then check to see
1131  * if either the starting or end position is 0 (the first column),
1132  * and then swap them back if that is the case because we don't want
1133  * to allow movement of the first column. We then reset
1134  * startColumnIndex to -1, the reset value. We check if
1135  * startColumnIndex is at reset or not because it is possible for
1136  * the mouse to be released and a MouseEvent to be fired without
1137  * having moved any columns.
1138  */
1139  if (startColumnIndex != -1 && (startColumnIndex == 0 || endColumnIndex == 0)) {
1140  outline.moveColumn(endColumnIndex, startColumnIndex);
1141  }
1142  startColumnIndex = -1;
1143  }
1144 
1145  @Override
1146  public void mouseClicked(MouseEvent e) {
1147  //the user clicked a column header
1148  storeColumnSorting();
1149  }
1150 
1151  @Override
1152  public void columnAdded(TableColumnModelEvent e) {
1153  columnAddedOrRemoved();
1154  }
1155 
1156  @Override
1157  public void columnRemoved(TableColumnModelEvent e) {
1158  columnAddedOrRemoved();
1159  }
1160 
1166  private void columnAddedOrRemoved() {
1167  if (listenToVisibilitEvents) {
1168  SwingUtilities.invokeLater(DataResultViewerTable.this::storeColumnVisibility);
1169 
1170  }
1171  }
1172 
1173  @Override
1174  public void columnMarginChanged(ChangeEvent e) {
1175  }
1176 
1177  @Override
1178  public void columnSelectionChanged(ListSelectionEvent e) {
1179  }
1180 
1190  private void listenToVisibilityChanges(boolean b) {
1191  this.listenToVisibilitEvents = b;
1192  }
1193  }
1194 
1195  /*
1196  * A renderer which based on the contents of the cell will display an icon
1197  * to indicate the presence of a comment related to the content.
1198  */
1199  private final class HasCommentCellRenderer extends DefaultOutlineCellRenderer {
1200 
1201  private static final long serialVersionUID = 1L;
1202 
1203  @NbBundle.Messages({"DataResultViewerTable.commentRenderer.crComment.toolTip=Comment exists in Central Repository",
1204  "DataResultViewerTable.commentRenderer.tagComment.toolTip=Comment exists on associated tag(s)",
1205  "DataResultViewerTable.commentRenderer.crAndTagComment.toolTip=Comments exist both in Central Repository and on associated tag(s)",
1206  "DataResultViewerTable.commentRenderer.noComment.toolTip=No comments found"})
1207  @Override
1208  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
1209  Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
1210  setBackground(component.getBackground()); //inherit highlighting for selection
1211  setHorizontalAlignment(CENTER);
1212  Object switchValue = null;
1213  if ((value instanceof NodeProperty)) {
1214  //The Outline view has properties in the cell, the value contained in the property is what we want
1215  try {
1216  switchValue = ((Node.Property) value).getValue();
1217  } catch (IllegalAccessException | InvocationTargetException ex) {
1218  //Unable to get the value from the NodeProperty no Icon will be displayed
1219  }
1220  } else {
1221  //JTables contain the value we want directly in the cell
1222  switchValue = value;
1223  }
1224  setText("");
1225  if ((switchValue instanceof HasCommentStatus)) {
1226 
1227  switch ((HasCommentStatus) switchValue) {
1228  case CR_COMMENT:
1229  setIcon(COMMENT_ICON);
1230  setToolTipText(Bundle.DataResultViewerTable_commentRenderer_crComment_toolTip());
1231  break;
1232  case TAG_COMMENT:
1233  setIcon(COMMENT_ICON);
1234  setToolTipText(Bundle.DataResultViewerTable_commentRenderer_tagComment_toolTip());
1235  break;
1236  case CR_AND_TAG_COMMENTS:
1237  setIcon(COMMENT_ICON);
1238  setToolTipText(Bundle.DataResultViewerTable_commentRenderer_crAndTagComment_toolTip());
1239  break;
1240  case TAG_NO_COMMENT:
1241  case NO_COMMENT:
1242  default:
1243  setIcon(null);
1244  setToolTipText(Bundle.DataResultViewerTable_commentRenderer_noComment_toolTip());
1245  }
1246  } else {
1247  setIcon(null);
1248  }
1249 
1250  return this;
1251  }
1252 
1253  }
1254 
1255  /*
1256  * A renderer which based on the contents of the cell will display an icon
1257  * to indicate the score associated with the item.
1258  */
1259  private final class ScoreCellRenderer extends DefaultOutlineCellRenderer {
1260 
1261  private static final long serialVersionUID = 1L;
1262 
1263  @Override
1264  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
1265  Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
1266  setBackground(component.getBackground()); //inherit highlighting for selection
1267  setHorizontalAlignment(CENTER);
1268  Object switchValue = null;
1269  if ((value instanceof NodeProperty)) {
1270  //The Outline view has properties in the cell, the value contained in the property is what we want
1271  try {
1272  switchValue = ((Node.Property) value).getValue();
1273  setToolTipText(((FeatureDescriptor) value).getShortDescription());
1274  } catch (IllegalAccessException | InvocationTargetException ex) {
1275  //Unable to get the value from the NodeProperty no Icon will be displayed
1276  }
1277 
1278  } else {
1279  //JTables contain the value we want directly in the cell
1280  switchValue = value;
1281  }
1282  setText("");
1283  if ((switchValue instanceof Score)) {
1284 
1285  switch ((Score) switchValue) {
1286  case INTERESTING_SCORE:
1287  setIcon(INTERESTING_SCORE_ICON);
1288  break;
1289  case NOTABLE_SCORE:
1290  setIcon(NOTABLE_ICON_SCORE);
1291  break;
1292  case NO_SCORE:
1293  default:
1294  setIcon(null);
1295  }
1296  } else {
1297  setIcon(null);
1298  }
1299  return this;
1300  }
1301 
1302  }
1303 
1304  /*
1305  * A renderer which based on the contents of the cell will display an empty
1306  * cell if no count was available.
1307  */
1308  private final class CountCellRenderer extends DefaultOutlineCellRenderer {
1309 
1310  private static final long serialVersionUID = 1L;
1311 
1312  @Override
1313  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
1314  Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
1315  setBackground(component.getBackground()); //inherit highlighting for selection
1316  setHorizontalAlignment(LEFT);
1317  Object countValue = null;
1318  if ((value instanceof NodeProperty)) {
1319  //The Outline view has properties in the cell, the value contained in the property is what we want
1320  try {
1321  countValue = ((Node.Property) value).getValue();
1322  setToolTipText(((FeatureDescriptor) value).getShortDescription());
1323  } catch (IllegalAccessException | InvocationTargetException ex) {
1324  //Unable to get the value from the NodeProperty no Icon will be displayed
1325  }
1326  } else {
1327  //JTables contain the value we want directly in the cell
1328  countValue = value;
1329  }
1330  setText("");
1331  if ((countValue instanceof Long)) {
1332  //Don't display value if value is negative used so that sorting will behave as desired
1333  if ((Long) countValue >= 0) {
1334  setText(countValue.toString());
1335  }
1336  }
1337  return this;
1338  }
1339 
1340  }
1341 
1346  public enum HasCommentStatus {
1351  CR_AND_TAG_COMMENTS
1352  }
1353 
1357  public enum Score {
1360  NOTABLE_SCORE
1361  }
1362 
1368  @SuppressWarnings("unchecked")
1369  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
1370  private void initComponents() {
1371 
1372  pageLabel = new javax.swing.JLabel();
1373  pageNumLabel = new javax.swing.JLabel();
1374  pagesLabel = new javax.swing.JLabel();
1375  pagePrevButton = new javax.swing.JButton();
1376  pageNextButton = new javax.swing.JButton();
1377  outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL);
1378  gotoPageLabel = new javax.swing.JLabel();
1379  gotoPageTextField = new javax.swing.JTextField();
1380  exportCSVButton = new javax.swing.JButton();
1381 
1382  pageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageLabel.text")); // NOI18N
1383 
1384  pageNumLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageNumLabel.text")); // NOI18N
1385 
1386  pagesLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pagesLabel.text")); // NOI18N
1387 
1388  pagePrevButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"))); // NOI18N
1389  pagePrevButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pagePrevButton.text")); // NOI18N
1390  pagePrevButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); // NOI18N
1391  pagePrevButton.setFocusable(false);
1392  pagePrevButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
1393  pagePrevButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
1394  pagePrevButton.setPreferredSize(new java.awt.Dimension(55, 23));
1395  pagePrevButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png"))); // NOI18N
1396  pagePrevButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
1397  pagePrevButton.addActionListener(new java.awt.event.ActionListener() {
1398  public void actionPerformed(java.awt.event.ActionEvent evt) {
1399  pagePrevButtonActionPerformed(evt);
1400  }
1401  });
1402 
1403  pageNextButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"))); // NOI18N
1404  pageNextButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageNextButton.text")); // NOI18N
1405  pageNextButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); // NOI18N
1406  pageNextButton.setFocusable(false);
1407  pageNextButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
1408  pageNextButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
1409  pageNextButton.setMaximumSize(new java.awt.Dimension(27, 23));
1410  pageNextButton.setMinimumSize(new java.awt.Dimension(27, 23));
1411  pageNextButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_hover.png"))); // NOI18N
1412  pageNextButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
1413  pageNextButton.addActionListener(new java.awt.event.ActionListener() {
1414  public void actionPerformed(java.awt.event.ActionEvent evt) {
1415  pageNextButtonActionPerformed(evt);
1416  }
1417  });
1418 
1419  gotoPageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.gotoPageLabel.text")); // NOI18N
1420 
1421  gotoPageTextField.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.gotoPageTextField.text")); // NOI18N
1422  gotoPageTextField.addActionListener(new java.awt.event.ActionListener() {
1423  public void actionPerformed(java.awt.event.ActionEvent evt) {
1424  gotoPageTextFieldActionPerformed(evt);
1425  }
1426  });
1427 
1428  exportCSVButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.exportCSVButton.text")); // NOI18N
1429  exportCSVButton.addActionListener(new java.awt.event.ActionListener() {
1430  public void actionPerformed(java.awt.event.ActionEvent evt) {
1431  exportCSVButtonActionPerformed(evt);
1432  }
1433  });
1434 
1435  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
1436  this.setLayout(layout);
1437  layout.setHorizontalGroup(
1438  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
1439  .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 904, Short.MAX_VALUE)
1440  .addGroup(layout.createSequentialGroup()
1441  .addContainerGap()
1442  .addComponent(pageLabel)
1443  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
1444  .addComponent(pageNumLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE)
1445  .addGap(14, 14, 14)
1446  .addComponent(pagesLabel)
1447  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
1448  .addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)
1449  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
1450  .addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)
1451  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
1452  .addComponent(gotoPageLabel)
1453  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
1454  .addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 33, javax.swing.GroupLayout.PREFERRED_SIZE)
1455  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
1456  .addComponent(exportCSVButton))
1457  );
1458 
1459  layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {pageNextButton, pagePrevButton});
1460 
1461  layout.setVerticalGroup(
1462  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
1463  .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
1464  .addGap(3, 3, 3)
1465  .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
1466  .addComponent(pageLabel)
1467  .addComponent(pageNumLabel)
1468  .addComponent(pagesLabel)
1469  .addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE)
1470  .addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 15, javax.swing.GroupLayout.PREFERRED_SIZE)
1471  .addComponent(gotoPageLabel)
1472  .addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
1473  .addComponent(exportCSVButton))
1474  .addGap(3, 3, 3)
1475  .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 321, Short.MAX_VALUE)
1476  .addContainerGap())
1477  );
1478 
1479  layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {pageNextButton, pagePrevButton});
1480 
1481  gotoPageLabel.getAccessibleContext().setAccessibleName("");
1482  }// </editor-fold>//GEN-END:initComponents
1483 
1484  private void pagePrevButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pagePrevButtonActionPerformed
1485  pagingSupport.previousPage();
1486  }//GEN-LAST:event_pagePrevButtonActionPerformed
1487 
1488  private void pageNextButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pageNextButtonActionPerformed
1489  pagingSupport.nextPage();
1490  }//GEN-LAST:event_pageNextButtonActionPerformed
1491 
1492  private void gotoPageTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gotoPageTextFieldActionPerformed
1493  pagingSupport.gotoPage();
1494  }//GEN-LAST:event_gotoPageTextFieldActionPerformed
1495 
1496  @NbBundle.Messages({"DataResultViewerTable.exportCSVButtonActionPerformed.empty=No data to export"
1497  })
1498  private void exportCSVButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportCSVButtonActionPerformed
1499  Node currentRoot = this.getExplorerManager().getRootContext();
1500  if (currentRoot != null && currentRoot.getChildren().getNodesCount() > 0) {
1501  org.sleuthkit.autopsy.directorytree.ExportCSVAction.saveNodesToCSV(java.util.Arrays.asList(currentRoot.getChildren().getNodes()), this);
1502  } else {
1503  MessageNotifyUtil.Message.info(Bundle.DataResultViewerTable_exportCSVButtonActionPerformed_empty());
1504  }
1505  }//GEN-LAST:event_exportCSVButtonActionPerformed
1506 
1507  // Variables declaration - do not modify//GEN-BEGIN:variables
1508  private javax.swing.JButton exportCSVButton;
1509  private javax.swing.JLabel gotoPageLabel;
1510  private javax.swing.JTextField gotoPageTextField;
1511  private org.openide.explorer.view.OutlineView outlineView;
1512  private javax.swing.JLabel pageLabel;
1513  private javax.swing.JButton pageNextButton;
1514  private javax.swing.JLabel pageNumLabel;
1515  private javax.swing.JButton pagePrevButton;
1516  private javax.swing.JLabel pagesLabel;
1517  // End of variables declaration//GEN-END:variables
1518 
1519 }
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-2018 Basis Technology. Generated on: Wed Sep 18 2019
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.