Autopsy  4.4
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 2011-2017 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.Color;
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.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 java.util.stream.Stream;
41 import javax.swing.JTable;
42 import javax.swing.ListSelectionModel;
43 import javax.swing.SwingUtilities;
44 import javax.swing.event.ChangeEvent;
45 import javax.swing.event.ListSelectionEvent;
46 import javax.swing.event.TableColumnModelEvent;
47 import javax.swing.event.TableColumnModelListener;
48 import javax.swing.table.TableCellRenderer;
49 import javax.swing.table.TableColumn;
50 import javax.swing.table.TableColumnModel;
51 import org.netbeans.swing.etable.ETableColumn;
52 import org.netbeans.swing.etable.ETableColumnModel;
53 import org.netbeans.swing.outline.DefaultOutlineCellRenderer;
54 import org.netbeans.swing.outline.DefaultOutlineModel;
55 import org.netbeans.swing.outline.Outline;
56 import org.openide.explorer.ExplorerManager;
57 import org.openide.explorer.view.OutlineView;
58 import org.openide.nodes.AbstractNode;
59 import org.openide.nodes.Children;
60 import org.openide.nodes.Node;
61 import org.openide.nodes.Node.Property;
62 import org.openide.nodes.NodeAdapter;
63 import org.openide.nodes.NodeMemberEvent;
64 import org.openide.util.NbBundle;
65 import org.openide.util.NbPreferences;
70 
78 //@ServiceProvider(service = DataResultViewer.class)
79 public class DataResultViewerTable extends AbstractDataResultViewer {
80 
81  private static final long serialVersionUID = 1L;
82  private static final Logger logger = Logger.getLogger(DataResultViewerTable.class.getName());
83  @NbBundle.Messages("DataResultViewerTable.firstColLbl=Name")
84  static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl();
85  @NbBundle.Messages("DataResultViewerTable.pleasewaitNodeDisplayName=Please Wait...")
86  private static final String PLEASEWAIT_NODE_DISPLAY_NAME = Bundle.DataResultViewerTable_pleasewaitNodeDisplayName();
87  private static final Color TAGGED_COLOR = new Color(200, 210, 220);
97  private final Map<Integer, Property<?>> propertiesMap = new TreeMap<>();
98 
104  private final Map<String, ETableColumn> columnMap = new HashMap<>();
105 
107 
108  private Node currentRoot;
109 
110  /*
111  * Convience reference to internal Outline.
112  */
113  private Outline outline;
114 
119 
126  public DataResultViewerTable(ExplorerManager explorerManager) {
127  super(explorerManager);
128  initialize();
129  }
130 
136  initialize();
137  }
138 
139  private void initialize() {
140  initComponents();
141 
142  outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE);
143 
144  outline = outlineView.getOutline();
145  outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
146  outline.setRootVisible(false); // don't show the root node
147  outline.setDragEnabled(false);
148  outline.setDefaultRenderer(Object.class, new ColorTagCustomRenderer());
149 
150  // add a listener so that when columns are moved, the new order is stored
151  tableListener = new TableListener();
152  outline.getColumnModel().addColumnModelListener(tableListener);
153  // the listener also moves columns back if user tries to move the first column out of place
154  outline.getTableHeader().addMouseListener(tableListener);
155  }
156 
162  @Override
163  public void expandNode(Node n) {
164  super.expandNode(n);
165 
166  outlineView.expandNode(n);
167  }
168 
174  @SuppressWarnings("unchecked")
175  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
176  private void initComponents() {
177 
179 
180  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
181  this.setLayout(layout);
182  layout.setHorizontalGroup(
183  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
184  .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE)
185  );
186  layout.setVerticalGroup(
187  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
188  .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE)
189  );
190  }// </editor-fold>//GEN-END:initComponents
191  // Variables declaration - do not modify//GEN-BEGIN:variables
192  private org.openide.explorer.view.OutlineView outlineView;
193  // End of variables declaration//GEN-END:variables
194 
195  @Override
196  public boolean isSupported(Node selectedNode) {
197  return true;
198  }
199 
200  @Override
202  public void setNode(Node selectedNode) {
203 
204  /*
205  * The quick filter must be reset because when determining column width,
206  * ETable.getRowCount is called, and the documentation states that quick
207  * filters must be unset for the method to work "If the quick-filter is
208  * applied the number of rows do not match the number of rows in the
209  * model."
210  */
211  outline.unsetQuickFilter();
212  // change the cursor to "waiting cursor" for this operation
213  this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
214  try {
215  boolean hasChildren = false;
216  if (selectedNode != null) {
217  // @@@ This just did a DB round trip to get the count and the results were not saved...
218  hasChildren = selectedNode.getChildren().getNodesCount() > 0;
219  }
220 
221  Node oldNode = this.em.getRootContext();
222  if (oldNode != null) {
223  oldNode.removeNodeListener(pleasewaitNodeListener);
224  }
225 
226  if (hasChildren) {
227  currentRoot = selectedNode;
228  pleasewaitNodeListener.reset();
229  currentRoot.addNodeListener(pleasewaitNodeListener);
230  em.setRootContext(currentRoot);
231  setupTable();
232  } else {
233  Node emptyNode = new AbstractNode(Children.LEAF);
234  em.setRootContext(emptyNode); // make empty node
235  outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
236 
237  /*
238  * Since we are modifying the columns, we don't want to listen
239  * to added/removed events as un-hide/hide.
240  */
241  tableListener.listenToVisibilityChanges(false);
242  outlineView.setPropertyColumns(); // set the empty property header
243  }
244  } finally {
245  this.setCursor(null);
246  }
247  }
248 
253  private void setupTable() {
254  /*
255  * Since we are modifying the columns, we don't want to listen to
256  * added/removed events as un-hide/hide, until the table setup is done.
257  */
258  tableListener.listenToVisibilityChanges(false);
259 
271  List<Node.Property<?>> props = loadColumnOrder();
272  if (props.isEmpty() == false) {
273  Node.Property<?> prop = props.remove(0);
274  ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(prop.getDisplayName());
275  }
276 
277  /*
278  * show the horizontal scroll panel and show all the content & header If
279  * there is only one column (which was removed from props above) Just
280  * let the table resize itself.
281  */
282  outline.setAutoResizeMode((props.isEmpty()) ? JTable.AUTO_RESIZE_ALL_COLUMNS : JTable.AUTO_RESIZE_OFF);
283 
284  assignColumns(props); // assign columns to match the properties
285  setColumnWidths();
286 
287  //Load column sorting information from preferences file and apply it to columns.
289 
290  /*
291  * Save references to columns before we deal with their visibility. This
292  * has to happen after the sorting is applied, because that actually
293  * causes the columns to be recreated. It has to happen before
294  * loadColumnVisibility so we have referenecs to the columns to pass to
295  * setColumnHidden.
296  */
298 
299  //Load column visibility information from preferences file and apply it to columns.
301 
302  /*
303  * If one of the child nodes of the root node is to be selected, select
304  * it.
305  */
306  SwingUtilities.invokeLater(() -> {
307  if (currentRoot instanceof TableFilterNode) {
308  NodeSelectionInfo selectedChildInfo = ((TableFilterNode) currentRoot).getChildNodeSelectionInfo();
309  if (null != selectedChildInfo) {
310  Node[] childNodes = currentRoot.getChildren().getNodes(true);
311  for (int i = 0; i < childNodes.length; ++i) {
312  Node childNode = childNodes[i];
313  if (selectedChildInfo.matches(childNode)) {
314  try {
315  em.setSelectedNodes(new Node[]{childNode});
316  } catch (PropertyVetoException ex) {
317  logger.log(Level.SEVERE, "Failed to select node specified by selected child info", ex);
318  }
319  break;
320  }
321  }
322  ((TableFilterNode) currentRoot).setChildNodeSelectionInfo(null);
323  }
324  }
325  });
326 
327  //the table setup is done, so any added/removed events can now be treated as un-hide/hide.
328  tableListener.listenToVisibilityChanges(true);
329  }
330 
331  /*
332  * Populate the map with references to the column objects for use when
333  * loading/storing the visibility info.
334  */
335  private void populateColumnMap() {
336  columnMap.clear();
337  TableColumnModel columnModel = outline.getColumnModel();
338  //for each property get a reference to the column object from the column model.
339  for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
340  final String propName = entry.getValue().getName();
341  final ETableColumn column = (ETableColumn) columnModel.getColumn(entry.getKey());
342  columnMap.put(propName, column);
343  }
344  }
345 
346  private void setColumnWidths() {
347  if (currentRoot.getChildren().getNodesCount() != 0) {
348  final Graphics graphics = outlineView.getGraphics();
349  if (graphics != null) {
350  final FontMetrics metrics = graphics.getFontMetrics();
351 
352  int margin = 4;
353  int padding = 8;
354 
355  for (int column = 0; column < outline.getModel().getColumnCount(); column++) {
356  int firstColumnPadding = (column == 0) ? 32 : 0;
357  int columnWidthLimit = (column == 0) ? 350 : 300;
358  int valuesWidth = 0;
359 
360  // find the maximum width needed to fit the values for the first 100 rows, at most
361  for (int row = 0; row < Math.min(100, outline.getRowCount()); row++) {
362  TableCellRenderer renderer = outline.getCellRenderer(row, column);
363  Component comp = outline.prepareRenderer(renderer, row, column);
364  valuesWidth = Math.max(comp.getPreferredSize().width, valuesWidth);
365  }
366 
367  int headerWidth = metrics.stringWidth(outline.getColumnName(column));
368  valuesWidth += firstColumnPadding; // add extra padding for first column
369 
370  int columnWidth = Math.max(valuesWidth, headerWidth);
371  columnWidth += 2 * margin + padding; // add margin and regular padding
372  columnWidth = Math.min(columnWidth, columnWidthLimit);
373 
374  outline.getColumnModel().getColumn(column).setPreferredWidth(columnWidth);
375  }
376  }
377  } else {
378  // if there's no content just auto resize all columns
379  outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
380  }
381  }
382 
383  synchronized private void assignColumns(List<Property<?>> props) {
384  // Get the columns setup with respect to names and sortability
385  String[] propStrings = new String[props.size() * 2];
386  for (int i = 0; i < props.size(); i++) {
387  final Property<?> prop = props.get(i);
388  prop.setValue("ComparableColumnTTV", Boolean.TRUE); //NON-NLS
389  //First property column is sorted initially
390  if (i == 0) {
391  prop.setValue("TreeColumnTTV", Boolean.TRUE); // Identifies special property representing first (tree) column. NON-NLS
392  prop.setValue("SortingColumnTTV", Boolean.TRUE); // TreeTableView should be initially sorted by this property column. NON-NLS
393  }
394  propStrings[2 * i] = prop.getName();
395  propStrings[2 * i + 1] = prop.getDisplayName();
396  }
397 
398  outlineView.setPropertyColumns(propStrings);
399  }
400 
404  private synchronized void storeColumnVisibility() {
405  if (currentRoot == null || propertiesMap.isEmpty()) {
406  return;
407  }
408  if (currentRoot instanceof TableFilterNode) {
409  TableFilterNode tfn = (TableFilterNode) currentRoot;
410  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
411  final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
412 
413  //store hidden state
414  for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
415 
416  String columnName = entry.getKey();
417  final String columnHiddenKey = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName);
418  final TableColumn column = entry.getValue();
419 
420  boolean columnHidden = columnModel.isColumnHidden(column);
421  if (columnHidden) {
422  preferences.putBoolean(columnHiddenKey, true);
423  } else {
424  preferences.remove(columnHiddenKey);
425  }
426  }
427  }
428  }
429 
433  private synchronized void storeColumnOrder() {
434  if (currentRoot == null || propertiesMap.isEmpty()) {
435  return;
436  }
437  if (currentRoot instanceof TableFilterNode) {
438  TableFilterNode tfn = (TableFilterNode) currentRoot;
439  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
440 
441  // Store the current order of the columns into settings
442  for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
443  preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey());
444  }
445  }
446  }
447 
451  private synchronized void storeColumnSorting() {
452  if (currentRoot == null || propertiesMap.isEmpty()) {
453  return;
454  }
455  if (currentRoot instanceof TableFilterNode) {
456  final TableFilterNode tfn = ((TableFilterNode) currentRoot);
457  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
458 
459  for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
460  ETableColumn etc = entry.getValue();
461  String columnName = entry.getKey();
462 
463  //store sort rank and order
464  final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName);
465  final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, columnName);
466 
467  if (etc.isSorted()) {
468  preferences.putBoolean(columnSortOrderKey, etc.isAscending());
469  preferences.putInt(columnSortRankKey, etc.getSortRank());
470  } else {
471  preferences.remove(columnSortOrderKey);
472  preferences.remove(columnSortRankKey);
473  }
474  }
475 
476  }
477  }
478 
485  private synchronized void loadColumnSorting() {
486  if (currentRoot == null || propertiesMap.isEmpty()) {
487  return;
488  }
489 
490  if (currentRoot instanceof TableFilterNode) {
491  final TableFilterNode tfn = (TableFilterNode) currentRoot;
492 
493  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
494  //organize property sorting information, sorted by rank
495  TreeSet<ColumnSortInfo> treeSet = new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank));
496  propertiesMap.entrySet().stream().forEach(entry -> {
497  final String propName = entry.getValue().getName();
498  //if the sort rank is undefined, it will be defaulted to 0 => unsorted.
499 
500  Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0);
501  //default to true => ascending
502  Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName), true);
503 
504  treeSet.add(new ColumnSortInfo(entry.getKey(), sortRank, sortOrder));
505  });
506 
507  //apply sort information in rank order.
508  treeSet.forEach(sortInfo -> outline.setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank));
509  }
510  }
511 
512  private synchronized void loadColumnVisibility() {
513  if (currentRoot == null || propertiesMap.isEmpty()) {
514  return;
515  }
516 
517  if (currentRoot instanceof TableFilterNode) {
518 
519  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
520 
521  final TableFilterNode tfn = ((TableFilterNode) currentRoot);
522  ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
523  for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
524  final String propName = entry.getValue().getName();
525  boolean hidden = preferences.getBoolean(ResultViewerPersistence.getColumnHiddenKey(tfn, propName), false);
526  final TableColumn column = columnMap.get(propName);
527  columnModel.setColumnHidden(column, hidden);
528  }
529  }
530  }
531 
540  private synchronized List<Node.Property<?>> loadColumnOrder() {
541 
542  List<Property<?>> props = ResultViewerPersistence.getAllChildProperties(currentRoot, 100);
543 
544  // If node is not table filter node, use default order for columns
545  if (!(currentRoot instanceof TableFilterNode)) {
546  return props;
547  }
548 
549  final TableFilterNode tfn = ((TableFilterNode) currentRoot);
550  propertiesMap.clear();
551 
552  /*
553  * We load column index values into the properties map. If a property's
554  * index is outside the range of the number of properties or the index
555  * has already appeared as the position of another property, we put that
556  * property at the end.
557  */
558  int offset = props.size();
559  boolean noPreviousSettings = true;
560 
561  final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class);
562 
563  for (Property<?> prop : props) {
564 
565  Integer value = preferences.getInt(ResultViewerPersistence.getColumnPositionKey(tfn, prop.getName()), -1);
566  if (value >= 0 && value < offset && !propertiesMap.containsKey(value)) {
567  propertiesMap.put(value, prop);
568  noPreviousSettings = false;
569  } else {
570  propertiesMap.put(offset, prop);
571  offset++;
572  }
573  }
574 
575  // If none of the properties had previous settings, we should decrement
576  // each value by the number of properties to make the values 0-indexed.
577  if (noPreviousSettings) {
578  ArrayList<Integer> keys = new ArrayList<>(propertiesMap.keySet());
579  for (int key : keys) {
580  propertiesMap.put(key - props.size(), propertiesMap.remove(key));
581  }
582  }
583 
584  return new ArrayList<>(propertiesMap.values());
585  }
586 
587  @Override
588  @NbBundle.Messages("DataResultViewerTable.title=Table")
589  public String getTitle() {
590  return Bundle.DataResultViewerTable_title();
591  }
592 
593  @Override
595  return new DataResultViewerTable();
596  }
597 
598  @Override
599  public void clearComponent() {
600  this.outlineView.removeAll();
601  this.outlineView = null;
602 
603  super.clearComponent();
604 
605  }
606 
610  static private final class ColumnSortInfo {
611 
612  private final int modelIndex;
613  private final int rank;
614  private final boolean order;
615 
616  private ColumnSortInfo(int modelIndex, int rank, boolean order) {
617  this.modelIndex = modelIndex;
618  this.rank = rank;
619  this.order = order;
620  }
621 
622  private int getRank() {
623  return rank;
624  }
625  }
626 
631  private class TableListener extends MouseAdapter implements TableColumnModelListener {
632 
633  // When a column in the table is moved, these two variables keep track of where
634  // the column started and where it ended up.
635  private int startColumnIndex = -1;
636  private int endColumnIndex = -1;
637  private boolean listenToVisibilitEvents;
638 
639  @Override
640  public void columnMoved(TableColumnModelEvent e) {
641  int fromIndex = e.getFromIndex();
642  int toIndex = e.getToIndex();
643  if (fromIndex == toIndex) {
644  return;
645  }
646 
647  /*
648  * Because a column may be dragged to several different positions
649  * before the mouse is released (thus causing multiple
650  * TableColumnModelEvents to be fired), we want to keep track of the
651  * starting column index in this potential series of movements.
652  * Therefore we only keep track of the original fromIndex in
653  * startColumnIndex, but we always update endColumnIndex to know the
654  * final position of the moved column. See the MouseListener
655  * mouseReleased method.
656  */
657  if (startColumnIndex == -1) {
658  startColumnIndex = fromIndex;
659  }
660  endColumnIndex = toIndex;
661 
662  // This list contains the keys of propertiesMap in order
663  ArrayList<Integer> indicesList = new ArrayList<>(propertiesMap.keySet());
664  int leftIndex = Math.min(fromIndex, toIndex);
665  int rightIndex = Math.max(fromIndex, toIndex);
666  // Now we can copy the range of keys that have been affected by
667  // the column movement
668  List<Integer> range = indicesList.subList(leftIndex, rightIndex + 1);
669  int rangeSize = range.size();
670 
671  if (fromIndex < toIndex) {
672  // column moved right, shift all properties left, put in moved
673  // property at the rightmost index
674  Property<?> movedProp = propertiesMap.get(range.get(0));
675  for (int i = 0; i < rangeSize - 1; i++) {
676  propertiesMap.put(range.get(i), propertiesMap.get(range.get(i + 1)));
677  }
678  propertiesMap.put(range.get(rangeSize - 1), movedProp);
679  } else {
680  // column moved left, shift all properties right, put in moved
681  // property at the leftmost index
682  Property<?> movedProp = propertiesMap.get(range.get(rangeSize - 1));
683  for (int i = rangeSize - 1; i > 0; i--) {
684  propertiesMap.put(range.get(i), propertiesMap.get(range.get(i - 1)));
685  }
686  propertiesMap.put(range.get(0), movedProp);
687  }
688 
690  }
691 
692  @Override
693  public void mouseReleased(MouseEvent e) {
694  /*
695  * If the startColumnIndex is not -1 (which is the reset value),
696  * that means columns have been moved around. We then check to see
697  * if either the starting or end position is 0 (the first column),
698  * and then swap them back if that is the case because we don't want
699  * to allow movement of the first column. We then reset
700  * startColumnIndex to -1, the reset value. We check if
701  * startColumnIndex is at reset or not because it is possible for
702  * the mouse to be released and a MouseEvent to be fired without
703  * having moved any columns.
704  */
705  if (startColumnIndex != -1 && (startColumnIndex == 0 || endColumnIndex == 0)) {
706  outline.moveColumn(endColumnIndex, startColumnIndex);
707  }
708  startColumnIndex = -1;
709  }
710 
711  @Override
712  public void mouseClicked(MouseEvent e) {
713  //the user clicked a column header
715  }
716 
717  @Override
718  public void columnAdded(TableColumnModelEvent e) {
720  }
721 
722  @Override
723  public void columnRemoved(TableColumnModelEvent e) {
725  }
726 
732  private void columnAddedOrRemoved() {
733  if (listenToVisibilitEvents) {
734  SwingUtilities.invokeLater(DataResultViewerTable.this::storeColumnVisibility);
735 
736  }
737  }
738 
739  @Override
740  public void columnMarginChanged(ChangeEvent e) {
741  }
742 
743  @Override
744  public void columnSelectionChanged(ListSelectionEvent e) {
745  }
746 
756  private void listenToVisibilityChanges(boolean b) {
757  this.listenToVisibilitEvents = b;
758  }
759  }
760 
761  private class PleasewaitNodeListener extends NodeAdapter {
762 
763  private volatile boolean load = true;
764 
765  public void reset() {
766  load = true;
767  }
768 
769  @Override
770  public void childrenAdded(final NodeMemberEvent nme) {
771  Node[] delta = nme.getDelta();
772  if (load && containsReal(delta)) {
773  load = false;
774  //JMTODO: this looks suspicious
775  if (SwingUtilities.isEventDispatchThread()) {
776  setupTable();
777  } else {
778  SwingUtilities.invokeLater(() -> setupTable());
779  }
780  }
781  }
782 
783  private boolean containsReal(Node[] delta) {
784  return Stream.of(delta)
785  .map(Node::getDisplayName)
786  .noneMatch(PLEASEWAIT_NODE_DISPLAY_NAME::equals);
787  }
788  }
789 
795  private class ColorTagCustomRenderer extends DefaultOutlineCellRenderer {
796 
797  private static final long serialVersionUID = 1L;
798 
799  @Override
800  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
801 
802  Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
803  // only override the color if a node is not selected
804  if (!isSelected) {
805  Node node = currentRoot.getChildren().getNodeAt(table.convertRowIndexToModel(row));
806  boolean tagFound = false;
807  if (node != null) {
808  Node.PropertySet[] propSets = node.getPropertySets();
809  if (propSets.length != 0) {
810  // currently, a node has only one property set, named Sheet.PROPERTIES ("properties")
811  Node.Property<?>[] props = propSets[0].getProperties();
812  for (Property<?> prop : props) {
813  if ("Tags".equals(prop.getName())) {//NON-NLS
814  try {
815  tagFound = !prop.getValue().equals("");
816  } catch (IllegalAccessException | InvocationTargetException ignore) {
817  }
818  break;
819  }
820  }
821  }
822  }
823  //if the node does have associated tags, set its background color
824  if (tagFound) {
825  component.setBackground(TAGGED_COLOR);
826  }
827  }
828  return component;
829  }
830  }
831 }
synchronized void assignColumns(List< Property<?>> props)
Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col)
synchronized static Logger getLogger(String name)
Definition: Logger.java:161

Copyright © 2012-2016 Basis Technology. Generated on: Tue Jun 13 2017
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.