19 package org.sleuthkit.autopsy.corecomponents;
 
   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;
 
   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;
 
   84 @ServiceProvider(service = DataResultViewer.class)
 
   85 @SuppressWarnings(
"PMD.SingularField") 
 
   88     private static final long serialVersionUID = 1L;
 
   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();
 
  115         this(null, Bundle.DataResultViewerTable_title());
 
  128         this(explorerManager, Bundle.DataResultViewerTable_title());
 
  142         super(explorerManager);
 
  144         this.columnMap = 
new HashMap<>();
 
  145         this.propertiesMap = 
new TreeMap<>();
 
  155         outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE);
 
  157         outline = outlineView.getOutline();
 
  158         outline.setRowSelectionAllowed(
true);
 
  159         outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
 
  160         outline.setRootVisible(
false);
 
  161         outline.setDragEnabled(
false);
 
  168         outline.getColumnModel().addColumnModelListener(outlineViewListener);
 
  171         outline.getColumnModel().addColumnModelListener(iconRendererListener);
 
  177         outline.getTableHeader().addMouseListener(outlineViewListener);
 
  200     @NbBundle.Messages(
"DataResultViewerTable.title=Table")
 
  225     public 
void setNode(Node rootNode) {
 
  226         if (!SwingUtilities.isEventDispatchThread()) {
 
  227             LOGGER.log(Level.SEVERE, 
"Attempting to run setNode() from non-EDT thread");
 
  238         outline.unsetQuickFilter();
 
  240         this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
 
  253             if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) {
 
  254                 this.rootNode = rootNode;
 
  255                 this.getExplorerManager().setRootContext(this.rootNode);
 
  258                 Node emptyNode = 
new AbstractNode(Children.LEAF);
 
  259                 this.getExplorerManager().setRootContext(emptyNode);
 
  260                 outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
 
  262                 outlineView.setPropertyColumns();
 
  265             this.setCursor(null);
 
  276         outlineView.addTreeExpansionListener(listener);
 
  301         List<Node.Property<?>> props = loadColumnOrder();
 
  302         boolean propsExist = props.isEmpty() == 
false;
 
  303         Node.Property<?> firstProp = null;
 
  305             firstProp = props.remove(0);
 
  313         outline.setAutoResizeMode((props.isEmpty()) ? JTable.AUTO_RESIZE_ALL_COLUMNS : JTable.AUTO_RESIZE_OFF);
 
  315         assignColumns(props); 
 
  316         if (firstProp != null) {
 
  317             ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(firstProp.getDisplayName());
 
  341         loadColumnVisibility();
 
  347         SwingUtilities.invokeLater(() -> {
 
  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)) {
 
  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);
 
  363                     ((TableFilterNode) rootNode).setChildNodeSelectionInfo(null);
 
  383         TableColumnModel columnModel = outline.getColumnModel();
 
  384         int columnCount = columnModel.getColumnCount();
 
  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);
 
  401         if (rootNode.getChildren().getNodesCount() != 0) {
 
  402             final Graphics graphics = outlineView.getGraphics();
 
  403             if (graphics != null) {
 
  404                 final FontMetrics metrics = graphics.getFontMetrics();
 
  409                 for (
int column = 0; column < outline.getModel().getColumnCount(); column++) {
 
  410                     int firstColumnPadding = (column == 0) ? 32 : 0;
 
  411                     int columnWidthLimit = (column == 0) ? 350 : 300;
 
  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);
 
  421                     int headerWidth = metrics.stringWidth(outline.getColumnName(column));
 
  422                     valuesWidth += firstColumnPadding; 
 
  424                     int columnWidth = Math.max(valuesWidth, headerWidth);
 
  425                     columnWidth += 2 * margin + padding; 
 
  426                     columnWidth = Math.min(columnWidth, columnWidthLimit);
 
  428                     outline.getColumnModel().getColumn(column).setPreferredWidth(columnWidth);
 
  433             outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
 
  438         return outline.getColumnModel();
 
  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); 
 
  452                 prop.setValue(
"TreeColumnTTV", Boolean.TRUE); 
 
  453                 prop.setValue(
"SortingColumnTTV", Boolean.TRUE); 
 
  455             propStrings[2 * i] = prop.getName();
 
  456             propStrings[2 * i + 1] = prop.getDisplayName();
 
  458         outlineView.setPropertyColumns(propStrings);
 
  466         if (rootNode == null || propertiesMap.isEmpty()) {
 
  470             TableFilterNode tfn = (TableFilterNode) rootNode;
 
  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);
 
  479                     preferences.putBoolean(columnHiddenKey, 
true);
 
  481                     preferences.remove(columnHiddenKey);
 
  492         if (rootNode == null || propertiesMap.isEmpty()) {
 
  496             TableFilterNode tfn = (TableFilterNode) rootNode;
 
  499             for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
 
  500                 preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey());
 
  509         if (rootNode == null || propertiesMap.isEmpty()) {
 
  513             final TableFilterNode tfn = ((TableFilterNode) rootNode);
 
  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();
 
  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());
 
  526                     columnModel.setColumnSorted(etc, 
true, 0);
 
  527                     preferences.remove(columnSortOrderKey);
 
  528                     preferences.remove(columnSortRankKey);
 
  541         if (rootNode == null || propertiesMap.isEmpty()) {
 
  545             final TableFilterNode tfn = (TableFilterNode) rootNode;
 
  548             TreeSet<ColumnSortInfo> sortInfos = 
new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank));
 
  549             propertiesMap.entrySet().stream().forEach(entry -> {
 
  550                 final String propName = entry.getValue().getName();
 
  552                 Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0);
 
  554                 Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName), 
true);
 
  555                 sortInfos.add(
new ColumnSortInfo(entry.getKey(), sortRank, sortOrder));
 
  558             sortInfos.forEach(sortInfo -> outline.setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank));
 
  567         if (rootNode == null || propertiesMap.isEmpty()) {
 
  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);
 
  593         List<Property<?>> props = ResultViewerPersistence.getAllChildProperties(rootNode, 100);
 
  600         final TableFilterNode tfn = ((TableFilterNode) rootNode);
 
  601         propertiesMap.clear();
 
  609         int offset = props.size();
 
  610         boolean noPreviousSettings = 
true;
 
  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;
 
  620                 propertiesMap.put(offset, prop);
 
  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));
 
  634         return new ArrayList<>(propertiesMap.values());
 
  643         this.outlineView.removeAll();
 
  644         this.outlineView = null;
 
  645         super.clearComponent();
 
  658             this.modelIndex = modelIndex;
 
  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"})
 
  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())) {
 
  686                     outlineView.setPropertyColumnDescription(column.getHeaderValue().toString(), Bundle.DataResultViewerTable_commentRender_toolTip());
 
  688                 } 
else if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_scoreRender_name())) {
 
  690                     outlineView.setPropertyColumnDescription(column.getHeaderValue().toString(), Bundle.DataResultViewerTable_scoreRender_toolTip());
 
  692                 } 
else if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_countRender_name())) {
 
  693                     outlineView.setPropertyColumnDescription(column.getHeaderValue().toString(), Bundle.DataResultViewerTable_countRender_toolTip());
 
  729     private class TableListener extends MouseAdapter implements TableColumnModelListener {
 
  733         private int startColumnIndex = -1;
 
  734         private int endColumnIndex = -1;
 
  739             int fromIndex = e.getFromIndex();
 
  740             int toIndex = e.getToIndex();
 
  741             if (fromIndex == toIndex) {
 
  755             if (startColumnIndex == -1) {
 
  756                 startColumnIndex = fromIndex;
 
  758             endColumnIndex = toIndex;
 
  761             ArrayList<Integer> indicesList = 
new ArrayList<>(propertiesMap.keySet());
 
  762             int leftIndex = Math.min(fromIndex, toIndex);
 
  763             int rightIndex = Math.max(fromIndex, toIndex);
 
  766             List<Integer> range = indicesList.subList(leftIndex, rightIndex + 1);
 
  767             int rangeSize = range.size();
 
  769             if (fromIndex < toIndex) {
 
  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)));
 
  776                 propertiesMap.put(range.get(rangeSize - 1), movedProp);
 
  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)));
 
  784                 propertiesMap.put(range.get(0), movedProp);
 
  803             if (startColumnIndex != -1 && (startColumnIndex == 0 || endColumnIndex == 0)) {
 
  804                 outline.moveColumn(endColumnIndex, startColumnIndex);
 
  806             startColumnIndex = -1;
 
  812             storeColumnSorting();
 
  817             columnAddedOrRemoved();
 
  822             columnAddedOrRemoved();
 
  831             if (listenToVisibilitEvents) {
 
  855             this.listenToVisibilitEvents = b;
 
  865         private static final long serialVersionUID = 1L;
 
  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"})
 
  873             Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
 
  874             setBackground(component.getBackground());  
 
  875             setHorizontalAlignment(CENTER);
 
  876             Object switchValue = null;
 
  880                     switchValue = ((Node.Property) value).getValue();
 
  881                 } 
catch (IllegalAccessException | InvocationTargetException ex) {
 
  891                 switch ((HasCommentStatus) switchValue) {
 
  893                         setIcon(COMMENT_ICON);
 
  894                         setToolTipText(Bundle.DataResultViewerTable_commentRenderer_crComment_toolTip());
 
  897                         setIcon(COMMENT_ICON);
 
  898                         setToolTipText(Bundle.DataResultViewerTable_commentRenderer_tagComment_toolTip());
 
  900                     case CR_AND_TAG_COMMENTS:
 
  901                         setIcon(COMMENT_ICON);
 
  902                         setToolTipText(Bundle.DataResultViewerTable_commentRenderer_crAndTagComment_toolTip());
 
  908                         setToolTipText(Bundle.DataResultViewerTable_commentRenderer_noComment_toolTip());
 
  925         private static final long serialVersionUID = 1L;
 
  929             Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
 
  930             setBackground(component.getBackground());  
 
  931             setHorizontalAlignment(CENTER);
 
  932             Object switchValue = null;
 
  936                     switchValue = ((Node.Property) value).getValue();
 
  937                     setToolTipText(((FeatureDescriptor) value).getShortDescription());
 
  938                 } 
catch (IllegalAccessException | InvocationTargetException ex) {
 
  947             if ((switchValue instanceof 
Score)) {
 
  949                 switch ((Score) switchValue) {
 
  950                     case INTERESTING_SCORE:
 
  951                         setIcon(INTERESTING_SCORE_ICON);
 
  954                         setIcon(NOTABLE_ICON_SCORE);
 
  974         private static final long serialVersionUID = 1L;
 
  978             Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
 
  979             setBackground(component.getBackground());  
 
  980             setHorizontalAlignment(LEFT);
 
  981             Object countValue = null;
 
  985                     countValue = ((Node.Property) value).getValue();
 
  986                     setToolTipText(((FeatureDescriptor) value).getShortDescription());
 
  987                 } 
catch (IllegalAccessException | InvocationTargetException ex) {
 
  995             if ((countValue instanceof Long)) {
 
  997                 if ((Long) countValue >= 0) {
 
  998                     setText(countValue.toString());
 
 1032     @SuppressWarnings(
"unchecked")
 
 1034     private 
void initComponents() {
 
 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)
 
 1044         layout.setVerticalGroup(
 
 1045             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
 
 1046             .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE)
 
void mouseReleased(MouseEvent e)
boolean matches(Node candidateNode)
Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
TableColumnModel getColumnModel()
final Map< String, ETableColumn > columnMap
Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
org.openide.explorer.view.OutlineView outlineView
void columnRemoved(TableColumnModelEvent e)
void columnAdded(TableColumnModelEvent e)
synchronized List< Node.Property<?> > loadColumnOrder()
synchronized void storeColumnVisibility()
synchronized void storeColumnSorting()
synchronized void assignColumns(List< Property<?>> props)
final IconRendererTableListener iconRendererListener
void columnMarginChanged(ChangeEvent e)
static final String FIRST_COLUMN_LABEL
void mouseClicked(MouseEvent e)
void columnSelectionChanged(ListSelectionEvent e)
void columnRemoved(TableColumnModelEvent e)
final TableListener outlineViewListener
void columnAddedOrRemoved()
boolean listenToVisibilitEvents
void columnMoved(TableColumnModelEvent e)
DataResultViewer createInstance()
final Map< Integer, Property<?> > propertiesMap
void columnSelectionChanged(ListSelectionEvent e)
synchronized void loadColumnVisibility()
ColumnSortInfo(int modelIndex, int rank, boolean order)
void addTreeExpansionListener(TreeExpansionListener listener)
void listenToVisibilityChanges(boolean b)
synchronized static Logger getLogger(String name)
synchronized void loadColumnSorting()
void columnMoved(TableColumnModelEvent e)
DataResultViewerTable(ExplorerManager explorerManager)
void columnMarginChanged(ChangeEvent e)
boolean isSupported(Node candidateRootNode)
void columnAdded(TableColumnModelEvent e)
synchronized void storeColumnOrder()
DataResultViewerTable(ExplorerManager explorerManager, String title)