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