Autopsy  4.9.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
DataResultViewerTable.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2012-2018 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 java.awt.Component;
22 import java.awt.Cursor;
23 import java.awt.FontMetrics;
24 import java.awt.Graphics;
25 import java.awt.dnd.DnDConstants;
26 import java.awt.event.MouseAdapter;
27 import java.awt.event.MouseEvent;
28 import java.beans.FeatureDescriptor;
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.List;
35 import java.util.Map;
36 import java.util.TreeMap;
37 import java.util.TreeSet;
38 import java.util.logging.Level;
39 import java.util.prefs.Preferences;
40 import javax.swing.ImageIcon;
41 import javax.swing.JTable;
42 import javax.swing.ListSelectionModel;
43 import static javax.swing.SwingConstants.CENTER;
44 import javax.swing.SwingUtilities;
45 import javax.swing.event.ChangeEvent;
46 import javax.swing.event.ListSelectionEvent;
47 import javax.swing.event.TableColumnModelEvent;
48 import javax.swing.event.TableColumnModelListener;
49 import javax.swing.event.TreeExpansionListener;
50 import javax.swing.table.TableCellRenderer;
51 import javax.swing.table.TableColumn;
52 import javax.swing.table.TableColumnModel;
53 import org.netbeans.swing.etable.ETableColumn;
54 import org.netbeans.swing.etable.ETableColumnModel;
55 import org.netbeans.swing.outline.DefaultOutlineCellRenderer;
56 import org.netbeans.swing.outline.DefaultOutlineModel;
57 import org.netbeans.swing.outline.Outline;
58 import org.openide.explorer.ExplorerManager;
59 import org.openide.explorer.view.OutlineView;
60 import org.openide.nodes.AbstractNode;
61 import org.openide.nodes.Children;
62 import org.openide.nodes.Node;
63 import org.openide.nodes.Node.Property;
64 import org.openide.util.ImageUtilities;
65 import org.openide.util.NbBundle;
66 import org.openide.util.NbPreferences;
67 import org.openide.util.lookup.ServiceProvider;
73 
84 @ServiceProvider(service = DataResultViewer.class)
85 @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
87 
88  private static final long serialVersionUID = 1L;
89  private static final Logger LOGGER = Logger.getLogger(DataResultViewerTable.class.getName());
90 
91  private static final String NOTEPAD_ICON_PATH = "org/sleuthkit/autopsy/images/notepad16.png";
92  private static final String RED_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/red-circle-exclamation.png";
93  private static final String YELLOW_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/yellow-circle-yield.png";
94  private static final ImageIcon COMMENT_ICON = new ImageIcon(ImageUtilities.loadImage(NOTEPAD_ICON_PATH, false));
95  private static final ImageIcon INTERESTING_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(YELLOW_CIRCLE_ICON_PATH, false));
96  private static final ImageIcon NOTABLE_ICON_SCORE = new ImageIcon(ImageUtilities.loadImage(RED_CIRCLE_ICON_PATH, false));
97  @NbBundle.Messages("DataResultViewerTable.firstColLbl=Name")
98  static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl();
99  private final String title;
100  private final Map<String, ETableColumn> columnMap;
101  private final Map<Integer, Property<?>> propertiesMap;
102  private final Outline outline;
105  private Node rootNode;
106 
115  this(null, Bundle.DataResultViewerTable_title());
116  }
117 
127  public DataResultViewerTable(ExplorerManager explorerManager) {
128  this(explorerManager, Bundle.DataResultViewerTable_title());
129  }
130 
141  public DataResultViewerTable(ExplorerManager explorerManager, String title) {
142  super(explorerManager);
143  this.title = title;
144  this.columnMap = new HashMap<>();
145  this.propertiesMap = new TreeMap<>();
146 
147  /*
148  * Execute the code generated by the GUI builder.
149  */
150  initComponents();
151 
152  /*
153  * Configure the child OutlineView (explorer view) component.
154  */
155  outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE);
156 
157  outline = outlineView.getOutline();
158  outline.setRowSelectionAllowed(true);
159  outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
160  outline.setRootVisible(false);
161  outline.setDragEnabled(false);
162 
163  /*
164  * Add a table listener to the child OutlineView (explorer view) to
165  * persist the order of the table columns when a column is moved.
166  */
167  outlineViewListener = new TableListener();
168  outline.getColumnModel().addColumnModelListener(outlineViewListener);
169 
170  iconRendererListener = new IconRendererTableListener();
171  outline.getColumnModel().addColumnModelListener(iconRendererListener);
172 
173  /*
174  * Add a mouse listener to the child OutlineView (explorer view) to make
175  * sure the first column of the table is kept in place.
176  */
177  outline.getTableHeader().addMouseListener(outlineViewListener);
178  }
179 
189  @Override
191  return new DataResultViewerTable();
192  }
193 
199  @Override
200  @NbBundle.Messages("DataResultViewerTable.title=Table")
201  public String getTitle() {
202  return title;
203  }
204 
213  @Override
214  public boolean isSupported(Node candidateRootNode) {
215  return true;
216  }
217 
223  @Override
225  public void setNode(Node rootNode) {
226  if (!SwingUtilities.isEventDispatchThread()) {
227  LOGGER.log(Level.SEVERE, "Attempting to run setNode() from non-EDT thread");
228  return;
229  }
230 
231  /*
232  * The quick filter must be reset because when determining column width,
233  * ETable.getRowCount is called, and the documentation states that quick
234  * filters must be unset for the method to work "If the quick-filter is
235  * applied the number of rows do not match the number of rows in the
236  * model."
237  */
238  outline.unsetQuickFilter();
239 
240  this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
241  try {
242  /*
243  * If the given node is not null and has children, set it as the
244  * root context of the child OutlineView, otherwise make an
245  * "empty"node the root context.
246  *
247  * IMPORTANT NOTE: This is the first of many times where a
248  * getChildren call on the current root node causes all of the
249  * children of the root node to be created and defeats lazy child
250  * node creation, if it is enabled. It also likely leads to many
251  * case database round trips.
252  */
253  if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) {
254  this.rootNode = rootNode;
255  this.getExplorerManager().setRootContext(this.rootNode);
256  setupTable();
257  } else {
258  Node emptyNode = new AbstractNode(Children.LEAF);
259  this.getExplorerManager().setRootContext(emptyNode);
260  outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
261  outlineViewListener.listenToVisibilityChanges(false);
262  outlineView.setPropertyColumns();
263  }
264  } finally {
265  this.setCursor(null);
266  }
267  }
268 
275  protected void addTreeExpansionListener(TreeExpansionListener listener) {
276  outlineView.addTreeExpansionListener(listener);
277  }
278 
284  private void setupTable() {
285  /*
286  * Since we are modifying the columns, we don't want to listen to
287  * added/removed events as un-hide/hide, until the table setup is done.
288  */
289  outlineViewListener.listenToVisibilityChanges(false);
290  /*
291  * OutlineView makes the first column be the result of
292  * node.getDisplayName with the icon. This duplicates our first column,
293  * which is the file name, etc. So, pop that property off the list, but
294  * use its display name as the header for the column so that the header
295  * can change depending on the type of data being displayed.
296  *
297  * NOTE: This assumes that the first property is always the one that
298  * duplicates getDisplayName(). The current implementation does not
299  * allow the first property column to be moved.
300  */
301  List<Node.Property<?>> props = loadColumnOrder();
302  boolean propsExist = props.isEmpty() == false;
303  Node.Property<?> firstProp = null;
304  if (propsExist) {
305  firstProp = props.remove(0);
306  }
307 
308  /*
309  * show the horizontal scroll panel and show all the content & header If
310  * there is only one column (which was removed from props above) Just
311  * let the table resize itself.
312  */
313  outline.setAutoResizeMode((props.isEmpty()) ? JTable.AUTO_RESIZE_ALL_COLUMNS : JTable.AUTO_RESIZE_OFF);
314 
315  assignColumns(props); // assign columns to match the properties
316  if (firstProp != null) {
317  ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(firstProp.getDisplayName());
318  }
319 
320  setColumnWidths();
321 
322  /*
323  * Load column sorting information from preferences file and apply it to
324  * columns.
325  */
326  loadColumnSorting();
327 
328  /*
329  * Save references to columns before we deal with their visibility. This
330  * has to happen after the sorting is applied, because that actually
331  * causes the columns to be recreated. It has to happen before
332  * loadColumnVisibility so we have referenecs to the columns to pass to
333  * setColumnHidden.
334  */
335  populateColumnMap();
336 
337  /*
338  * Load column visibility information from preferences file and apply it
339  * to columns.
340  */
341  loadColumnVisibility();
342 
343  /*
344  * If one of the child nodes of the root node is to be selected, select
345  * it.
346  */
347  SwingUtilities.invokeLater(() -> {
348  if (rootNode instanceof TableFilterNode) {
349  NodeSelectionInfo selectedChildInfo = ((TableFilterNode) rootNode).getChildNodeSelectionInfo();
350  if (null != selectedChildInfo) {
351  Node[] childNodes = rootNode.getChildren().getNodes(true);
352  for (int i = 0; i < childNodes.length; ++i) {
353  Node childNode = childNodes[i];
354  if (selectedChildInfo.matches(childNode)) {
355  try {
356  this.getExplorerManager().setSelectedNodes(new Node[]{childNode});
357  } catch (PropertyVetoException ex) {
358  LOGGER.log(Level.SEVERE, "Failed to select node specified by selected child info", ex);
359  }
360  break;
361  }
362  }
363  ((TableFilterNode) rootNode).setChildNodeSelectionInfo(null);
364  }
365  }
366  });
367 
368  /*
369  * The table setup is done, so any added/removed events can now be
370  * treated as un-hide/hide.
371  */
372  outlineViewListener.listenToVisibilityChanges(true);
373 
374  }
375 
376  /*
377  * Populates the column map for the child OutlineView of this tabular result
378  * viewer with references to the column objects for use when loading/storing
379  * the visibility info.
380  */
381  private void populateColumnMap() {
382  columnMap.clear();
383  TableColumnModel columnModel = outline.getColumnModel();
384  int columnCount = columnModel.getColumnCount();
385  //for each property get a reference to the column object from the column model.
386  for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
387  final String propName = entry.getValue().getName();
388  if (entry.getKey() < columnCount) {
389  final ETableColumn column = (ETableColumn) columnModel.getColumn(entry.getKey());
390  columnMap.put(propName, column);
391 
392  }
393  }
394  }
395 
396  /*
397  * Sets the column widths for the child OutlineView of this tabular results
398  * viewer.
399  */
400  protected void setColumnWidths() {
401  if (rootNode.getChildren().getNodesCount() != 0) {
402  final Graphics graphics = outlineView.getGraphics();
403  if (graphics != null) {
404  final FontMetrics metrics = graphics.getFontMetrics();
405 
406  int margin = 4;
407  int padding = 8;
408 
409  for (int column = 0; column < outline.getModel().getColumnCount(); column++) {
410  int firstColumnPadding = (column == 0) ? 32 : 0;
411  int columnWidthLimit = (column == 0) ? 350 : 300;
412  int valuesWidth = 0;
413 
414  // find the maximum width needed to fit the values for the first 100 rows, at most
415  for (int row = 0; row < Math.min(100, outline.getRowCount()); row++) {
416  TableCellRenderer renderer = outline.getCellRenderer(row, column);
417  Component comp = outline.prepareRenderer(renderer, row, column);
418  valuesWidth = Math.max(comp.getPreferredSize().width, valuesWidth);
419  }
420 
421  int headerWidth = metrics.stringWidth(outline.getColumnName(column));
422  valuesWidth += firstColumnPadding; // add extra padding for first column
423 
424  int columnWidth = Math.max(valuesWidth, headerWidth);
425  columnWidth += 2 * margin + padding; // add margin and regular padding
426  columnWidth = Math.min(columnWidth, columnWidthLimit);
427 
428  outline.getColumnModel().getColumn(column).setPreferredWidth(columnWidth);
429  }
430  }
431  } else {
432  // if there's no content just auto resize all columns
433  outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
434  }
435  }
436 
437  protected TableColumnModel getColumnModel() {
438  return outline.getColumnModel();
439  }
440 
441  /*
442  * Sets up the columns for the child OutlineView of this tabular results
443  * viewer with respect to column names and visisbility.
444  */
445  synchronized private void assignColumns(List<Property<?>> props) {
446  String[] propStrings = new String[props.size() * 2];
447  for (int i = 0; i < props.size(); i++) {
448  final Property<?> prop = props.get(i);
449  prop.setValue("ComparableColumnTTV", Boolean.TRUE); //NON-NLS
450  //First property column is sorted initially
451  if (i == 0) {
452  prop.setValue("TreeColumnTTV", Boolean.TRUE); // Identifies special property representing first (tree) column. NON-NLS
453  prop.setValue("SortingColumnTTV", Boolean.TRUE); // TreeTableView should be initially sorted by this property column. NON-NLS
454  }
455  propStrings[2 * i] = prop.getName();
456  propStrings[2 * i + 1] = prop.getDisplayName();
457  }
458  outlineView.setPropertyColumns(propStrings);
459  }
460 
465  private synchronized void storeColumnVisibility() {
466  if (rootNode == null || propertiesMap.isEmpty()) {
467  return;
468  }
469  if (rootNode instanceof TableFilterNode) {
470  TableFilterNode tfn = (TableFilterNode) rootNode;
471  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
472  final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
473  for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
474  String columnName = entry.getKey();
475  final String columnHiddenKey = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName);
476  final TableColumn column = entry.getValue();
477  boolean columnHidden = columnModel.isColumnHidden(column);
478  if (columnHidden) {
479  preferences.putBoolean(columnHiddenKey, true);
480  } else {
481  preferences.remove(columnHiddenKey);
482  }
483  }
484  }
485  }
486 
491  private synchronized void storeColumnOrder() {
492  if (rootNode == null || propertiesMap.isEmpty()) {
493  return;
494  }
495  if (rootNode instanceof TableFilterNode) {
496  TableFilterNode tfn = (TableFilterNode) rootNode;
497  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
498  // Store the current order of the columns into settings
499  for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
500  preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey());
501  }
502  }
503  }
504 
508  private synchronized void storeColumnSorting() {
509  if (rootNode == null || propertiesMap.isEmpty()) {
510  return;
511  }
512  if (rootNode instanceof TableFilterNode) {
513  final TableFilterNode tfn = ((TableFilterNode) rootNode);
514  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
515  ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
516  for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
517  ETableColumn etc = entry.getValue();
518  String columnName = entry.getKey();
519  //store sort rank and order
520  final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName);
521  final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, columnName);
522  if (etc.isSorted() && (columnModel.isColumnHidden(etc) == false)) {
523  preferences.putBoolean(columnSortOrderKey, etc.isAscending());
524  preferences.putInt(columnSortRankKey, etc.getSortRank());
525  } else {
526  columnModel.setColumnSorted(etc, true, 0);
527  preferences.remove(columnSortOrderKey);
528  preferences.remove(columnSortRankKey);
529  }
530  }
531  }
532  }
533 
540  private synchronized void loadColumnSorting() {
541  if (rootNode == null || propertiesMap.isEmpty()) {
542  return;
543  }
544  if (rootNode instanceof TableFilterNode) {
545  final TableFilterNode tfn = (TableFilterNode) rootNode;
546  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
547  //organize property sorting information, sorted by rank
548  TreeSet<ColumnSortInfo> sortInfos = new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank));
549  propertiesMap.entrySet().stream().forEach(entry -> {
550  final String propName = entry.getValue().getName();
551  //if the sort rank is undefined, it will be defaulted to 0 => unsorted.
552  Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0);
553  //default to true => ascending
554  Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName), true);
555  sortInfos.add(new ColumnSortInfo(entry.getKey(), sortRank, sortOrder));
556  });
557  //apply sort information in rank order.
558  sortInfos.forEach(sortInfo -> outline.setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank));
559  }
560  }
561 
566  private synchronized void loadColumnVisibility() {
567  if (rootNode == null || propertiesMap.isEmpty()) {
568  return;
569  }
570  if (rootNode instanceof TableFilterNode) {
571  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
572  final TableFilterNode tfn = ((TableFilterNode) rootNode);
573  ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
574  for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
575  final String propName = entry.getValue().getName();
576  boolean hidden = preferences.getBoolean(ResultViewerPersistence.getColumnHiddenKey(tfn, propName), false);
577  final TableColumn column = columnMap.get(propName);
578  columnModel.setColumnHidden(column, hidden);
579  }
580  }
581  }
582 
591  private synchronized List<Node.Property<?>> loadColumnOrder() {
592 
593  List<Property<?>> props = ResultViewerPersistence.getAllChildProperties(rootNode, 100);
594 
595  // If node is not table filter node, use default order for columns
596  if (!(rootNode instanceof TableFilterNode)) {
597  return props;
598  }
599 
600  final TableFilterNode tfn = ((TableFilterNode) rootNode);
601  propertiesMap.clear();
602 
603  /*
604  * We load column index values into the properties map. If a property's
605  * index is outside the range of the number of properties or the index
606  * has already appeared as the position of another property, we put that
607  * property at the end.
608  */
609  int offset = props.size();
610  boolean noPreviousSettings = true;
611 
612  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
613 
614  for (Property<?> prop : props) {
615  Integer value = preferences.getInt(ResultViewerPersistence.getColumnPositionKey(tfn, prop.getName()), -1);
616  if (value >= 0 && value < offset && !propertiesMap.containsKey(value)) {
617  propertiesMap.put(value, prop);
618  noPreviousSettings = false;
619  } else {
620  propertiesMap.put(offset, prop);
621  offset++;
622  }
623  }
624 
625  // If none of the properties had previous settings, we should decrement
626  // each value by the number of properties to make the values 0-indexed.
627  if (noPreviousSettings) {
628  ArrayList<Integer> keys = new ArrayList<>(propertiesMap.keySet());
629  for (int key : keys) {
630  propertiesMap.put(key - props.size(), propertiesMap.remove(key));
631  }
632  }
633 
634  return new ArrayList<>(propertiesMap.values());
635  }
636 
641  @Override
642  public void clearComponent() {
643  this.outlineView.removeAll();
644  this.outlineView = null;
645  super.clearComponent();
646  }
647 
651  static private final class ColumnSortInfo {
652 
653  private final int modelIndex;
654  private final int rank;
655  private final boolean order;
656 
657  private ColumnSortInfo(int modelIndex, int rank, boolean order) {
658  this.modelIndex = modelIndex;
659  this.rank = rank;
660  this.order = order;
661  }
662 
663  private int getRank() {
664  return rank;
665  }
666  }
667 
672  private class IconRendererTableListener implements TableColumnModelListener {
673 
674  @NbBundle.Messages({"DataResultViewerTable.commentRender.name=C",
675  "DataResultViewerTable.commentRender.toolTip=C(omments) indicates whether the item has a comment",
676  "DataResultViewerTable.scoreRender.name=S",
677  "DataResultViewerTable.scoreRender.toolTip=S(core) indicates whether the item is interesting or notable",
678  "DataResultViewerTable.countRender.name=O",
679  "DataResultViewerTable.countRender.toolTip=O(ccurrences) indicates the number of data sources containing the item in the Central Repository"})
680  @Override
681  public void columnAdded(TableColumnModelEvent e) {
682  if (e.getSource() instanceof ETableColumnModel) {
683  TableColumn column = ((TableColumnModel) e.getSource()).getColumn(e.getToIndex());
684  if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_commentRender_name())) {
685  //if the current column is a comment column set the cell renderer to be the HasCommentCellRenderer
686  outlineView.setPropertyColumnDescription(column.getHeaderValue().toString(), Bundle.DataResultViewerTable_commentRender_toolTip());
687  column.setCellRenderer(new HasCommentCellRenderer());
688  } else if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_scoreRender_name())) {
689  //if the current column is a score column set the cell renderer to be the ScoreCellRenderer
690  outlineView.setPropertyColumnDescription(column.getHeaderValue().toString(), Bundle.DataResultViewerTable_scoreRender_toolTip());
691  column.setCellRenderer(new ScoreCellRenderer());
692  } else if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_countRender_name())) {
693  outlineView.setPropertyColumnDescription(column.getHeaderValue().toString(), Bundle.DataResultViewerTable_countRender_toolTip());
694  column.setCellRenderer(new CountCellRenderer());
695  }
696  }
697  }
698 
699  @Override
700  public void columnRemoved(TableColumnModelEvent e
701  ) {
702  //Don't do anything when column removed
703  }
704 
705  @Override
706  public void columnMoved(TableColumnModelEvent e
707  ) {
708  //Don't do anything when column moved
709  }
710 
711  @Override
712  public void columnMarginChanged(ChangeEvent e
713  ) {
714  //Don't do anything when column margin changed
715  }
716 
717  @Override
718  public void columnSelectionChanged(ListSelectionEvent e
719  ) {
720  //Don't do anything when column selection changed
721  }
722 
723  }
724 
729  private class TableListener extends MouseAdapter implements TableColumnModelListener {
730 
731  // When a column in the table is moved, these two variables keep track of where
732  // the column started and where it ended up.
733  private int startColumnIndex = -1;
734  private int endColumnIndex = -1;
735  private boolean listenToVisibilitEvents;
736 
737  @Override
738  public void columnMoved(TableColumnModelEvent e) {
739  int fromIndex = e.getFromIndex();
740  int toIndex = e.getToIndex();
741  if (fromIndex == toIndex) {
742  return;
743  }
744 
745  /*
746  * Because a column may be dragged to several different positions
747  * before the mouse is released (thus causing multiple
748  * TableColumnModelEvents to be fired), we want to keep track of the
749  * starting column index in this potential series of movements.
750  * Therefore we only keep track of the original fromIndex in
751  * startColumnIndex, but we always update endColumnIndex to know the
752  * final position of the moved column. See the MouseListener
753  * mouseReleased method.
754  */
755  if (startColumnIndex == -1) {
756  startColumnIndex = fromIndex;
757  }
758  endColumnIndex = toIndex;
759 
760  // This list contains the keys of propertiesMap in order
761  ArrayList<Integer> indicesList = new ArrayList<>(propertiesMap.keySet());
762  int leftIndex = Math.min(fromIndex, toIndex);
763  int rightIndex = Math.max(fromIndex, toIndex);
764  // Now we can copy the range of keys that have been affected by
765  // the column movement
766  List<Integer> range = indicesList.subList(leftIndex, rightIndex + 1);
767  int rangeSize = range.size();
768 
769  if (fromIndex < toIndex) {
770  // column moved right, shift all properties left, put in moved
771  // property at the rightmost index
772  Property<?> movedProp = propertiesMap.get(range.get(0));
773  for (int i = 0; i < rangeSize - 1; i++) {
774  propertiesMap.put(range.get(i), propertiesMap.get(range.get(i + 1)));
775  }
776  propertiesMap.put(range.get(rangeSize - 1), movedProp);
777  } else {
778  // column moved left, shift all properties right, put in moved
779  // property at the leftmost index
780  Property<?> movedProp = propertiesMap.get(range.get(rangeSize - 1));
781  for (int i = rangeSize - 1; i > 0; i--) {
782  propertiesMap.put(range.get(i), propertiesMap.get(range.get(i - 1)));
783  }
784  propertiesMap.put(range.get(0), movedProp);
785  }
786 
787  storeColumnOrder();
788  }
789 
790  @Override
791  public void mouseReleased(MouseEvent e) {
792  /*
793  * If the startColumnIndex is not -1 (which is the reset value),
794  * that means columns have been moved around. We then check to see
795  * if either the starting or end position is 0 (the first column),
796  * and then swap them back if that is the case because we don't want
797  * to allow movement of the first column. We then reset
798  * startColumnIndex to -1, the reset value. We check if
799  * startColumnIndex is at reset or not because it is possible for
800  * the mouse to be released and a MouseEvent to be fired without
801  * having moved any columns.
802  */
803  if (startColumnIndex != -1 && (startColumnIndex == 0 || endColumnIndex == 0)) {
804  outline.moveColumn(endColumnIndex, startColumnIndex);
805  }
806  startColumnIndex = -1;
807  }
808 
809  @Override
810  public void mouseClicked(MouseEvent e) {
811  //the user clicked a column header
812  storeColumnSorting();
813  }
814 
815  @Override
816  public void columnAdded(TableColumnModelEvent e) {
817  columnAddedOrRemoved();
818  }
819 
820  @Override
821  public void columnRemoved(TableColumnModelEvent e) {
822  columnAddedOrRemoved();
823  }
824 
830  private void columnAddedOrRemoved() {
831  if (listenToVisibilitEvents) {
832  SwingUtilities.invokeLater(DataResultViewerTable.this::storeColumnVisibility);
833 
834  }
835  }
836 
837  @Override
838  public void columnMarginChanged(ChangeEvent e) {
839  }
840 
841  @Override
842  public void columnSelectionChanged(ListSelectionEvent e) {
843  }
844 
854  private void listenToVisibilityChanges(boolean b) {
855  this.listenToVisibilitEvents = b;
856  }
857  }
858 
859  /*
860  * A renderer which based on the contents of the cell will display an icon
861  * to indicate the presence of a comment related to the content.
862  */
863  private final class HasCommentCellRenderer extends DefaultOutlineCellRenderer {
864 
865  private static final long serialVersionUID = 1L;
866 
867  @NbBundle.Messages({"DataResultViewerTable.commentRenderer.crComment.toolTip=Comment exists in Central Repository",
868  "DataResultViewerTable.commentRenderer.tagComment.toolTip=Comment exists on associated tag(s)",
869  "DataResultViewerTable.commentRenderer.crAndTagComment.toolTip=Comments exist both in Central Repository and on associated tag(s)",
870  "DataResultViewerTable.commentRenderer.noComment.toolTip=No comments found"})
871  @Override
872  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
873  Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
874  setBackground(component.getBackground()); //inherit highlighting for selection
875  setHorizontalAlignment(CENTER);
876  Object switchValue = null;
877  if ((value instanceof NodeProperty)) {
878  //The Outline view has properties in the cell, the value contained in the property is what we want
879  try {
880  switchValue = ((Node.Property) value).getValue();
881  } catch (IllegalAccessException | InvocationTargetException ex) {
882  //Unable to get the value from the NodeProperty no Icon will be displayed
883  }
884  } else {
885  //JTables contain the value we want directly in the cell
886  switchValue = value;
887  }
888  setText("");
889  if ((switchValue instanceof HasCommentStatus)) {
890 
891  switch ((HasCommentStatus) switchValue) {
892  case CR_COMMENT:
893  setIcon(COMMENT_ICON);
894  setToolTipText(Bundle.DataResultViewerTable_commentRenderer_crComment_toolTip());
895  break;
896  case TAG_COMMENT:
897  setIcon(COMMENT_ICON);
898  setToolTipText(Bundle.DataResultViewerTable_commentRenderer_tagComment_toolTip());
899  break;
900  case CR_AND_TAG_COMMENTS:
901  setIcon(COMMENT_ICON);
902  setToolTipText(Bundle.DataResultViewerTable_commentRenderer_crAndTagComment_toolTip());
903  break;
904  case TAG_NO_COMMENT:
905  case NO_COMMENT:
906  default:
907  setIcon(null);
908  setToolTipText(Bundle.DataResultViewerTable_commentRenderer_noComment_toolTip());
909  }
910  } else {
911  setIcon(null);
912  }
913 
914  return this;
915  }
916 
917  }
918 
919  /*
920  * A renderer which based on the contents of the cell will display an icon
921  * to indicate the score associated with the item.
922  */
923  private final class ScoreCellRenderer extends DefaultOutlineCellRenderer {
924 
925  private static final long serialVersionUID = 1L;
926 
927  @Override
928  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
929  Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
930  setBackground(component.getBackground()); //inherit highlighting for selection
931  setHorizontalAlignment(CENTER);
932  Object switchValue = null;
933  if ((value instanceof NodeProperty)) {
934  //The Outline view has properties in the cell, the value contained in the property is what we want
935  try {
936  switchValue = ((Node.Property) value).getValue();
937  setToolTipText(((FeatureDescriptor) value).getShortDescription());
938  } catch (IllegalAccessException | InvocationTargetException ex) {
939  //Unable to get the value from the NodeProperty no Icon will be displayed
940  }
941 
942  } else {
943  //JTables contain the value we want directly in the cell
944  switchValue = value;
945  }
946  setText("");
947  if ((switchValue instanceof Score)) {
948 
949  switch ((Score) switchValue) {
950  case INTERESTING_SCORE:
951  setIcon(INTERESTING_SCORE_ICON);
952  break;
953  case NOTABLE_SCORE:
954  setIcon(NOTABLE_ICON_SCORE);
955  break;
956  case NO_SCORE:
957  default:
958  setIcon(null);
959  }
960  } else {
961  setIcon(null);
962  }
963  return this;
964  }
965 
966  }
967 
968  /*
969  * A renderer which based on the contents of the cell will display an empty
970  * cell if no count was available.
971  */
972  private final class CountCellRenderer extends DefaultOutlineCellRenderer {
973 
974  private static final long serialVersionUID = 1L;
975 
976  @Override
977  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
978  Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
979  setBackground(component.getBackground()); //inherit highlighting for selection
980  setHorizontalAlignment(LEFT);
981  Object countValue = null;
982  if ((value instanceof NodeProperty)) {
983  //The Outline view has properties in the cell, the value contained in the property is what we want
984  try {
985  countValue = ((Node.Property) value).getValue();
986  setToolTipText(((FeatureDescriptor) value).getShortDescription());
987  } catch (IllegalAccessException | InvocationTargetException ex) {
988  //Unable to get the value from the NodeProperty no Icon will be displayed
989  }
990  } else {
991  //JTables contain the value we want directly in the cell
992  countValue = value;
993  }
994  setText("");
995  if ((countValue instanceof Long)) {
996  //Don't display value if value is negative used so that sorting will behave as desired
997  if ((Long) countValue >= 0) {
998  setText(countValue.toString());
999  }
1000  }
1001  return this;
1002  }
1003 
1004  }
1005 
1010  public enum HasCommentStatus {
1015  CR_AND_TAG_COMMENTS
1016  }
1017 
1021  public enum Score {
1024  NOTABLE_SCORE
1025  }
1026 
1032  @SuppressWarnings("unchecked")
1033  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
1034  private void initComponents() {
1035 
1036  outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL);
1037 
1038  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
1039  this.setLayout(layout);
1040  layout.setHorizontalGroup(
1041  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
1042  .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE)
1043  );
1044  layout.setVerticalGroup(
1045  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
1046  .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE)
1047  );
1048  }// </editor-fold>//GEN-END:initComponents
1049  // Variables declaration - do not modify//GEN-BEGIN:variables
1050  private org.openide.explorer.view.OutlineView outlineView;
1051  // End of variables declaration//GEN-END:variables
1052 
1053 }
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)
synchronized void assignColumns(List< Property<?>> props)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
DataResultViewerTable(ExplorerManager explorerManager, String title)

Copyright © 2012-2018 Basis Technology. Generated on: Tue Dec 18 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.