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.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;
83 @ServiceProvider(service = DataResultViewer.class)
84 @SuppressWarnings(
"PMD.SingularField")
87 private static final long serialVersionUID = 1L;
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();
114 this(null, Bundle.DataResultViewerTable_title());
127 this(explorerManager, Bundle.DataResultViewerTable_title());
141 super(explorerManager);
143 this.columnMap =
new HashMap<>();
144 this.propertiesMap =
new TreeMap<>();
154 outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE);
156 outline = outlineView.getOutline();
157 outline.setRowSelectionAllowed(
true);
158 outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
159 outline.setRootVisible(
false);
160 outline.setDragEnabled(
false);
167 outline.getColumnModel().addColumnModelListener(outlineViewListener);
170 outline.getColumnModel().addColumnModelListener(iconRendererListener);
176 outline.getTableHeader().addMouseListener(outlineViewListener);
199 @NbBundle.Messages(
"DataResultViewerTable.title=Table")
224 public
void setNode(Node rootNode) {
225 if (!SwingUtilities.isEventDispatchThread()) {
226 LOGGER.log(Level.SEVERE,
"Attempting to run setNode() from non-EDT thread");
237 outline.unsetQuickFilter();
239 this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
252 if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) {
253 this.rootNode = rootNode;
254 this.getExplorerManager().setRootContext(this.rootNode);
257 Node emptyNode =
new AbstractNode(Children.LEAF);
258 this.getExplorerManager().setRootContext(emptyNode);
259 outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
261 outlineView.setPropertyColumns();
264 this.setCursor(null);
290 List<Node.Property<?>> props = loadColumnOrder();
291 boolean propsExist = props.isEmpty() ==
false;
292 Node.Property<?> firstProp = null;
294 firstProp = props.remove(0);
302 outline.setAutoResizeMode((props.isEmpty()) ? JTable.AUTO_RESIZE_ALL_COLUMNS : JTable.AUTO_RESIZE_OFF);
304 assignColumns(props);
305 if (firstProp != null) {
306 ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(firstProp.getDisplayName());
330 loadColumnVisibility();
336 SwingUtilities.invokeLater(() -> {
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)) {
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);
352 ((TableFilterNode) rootNode).setChildNodeSelectionInfo(null);
372 TableColumnModel columnModel = outline.getColumnModel();
373 int columnCount = columnModel.getColumnCount();
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);
390 if (rootNode.getChildren().getNodesCount() != 0) {
391 final Graphics graphics = outlineView.getGraphics();
392 if (graphics != null) {
393 final FontMetrics metrics = graphics.getFontMetrics();
398 for (
int column = 0; column < outline.getModel().getColumnCount(); column++) {
399 int firstColumnPadding = (column == 0) ? 32 : 0;
400 int columnWidthLimit = (column == 0) ? 350 : 300;
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);
410 int headerWidth = metrics.stringWidth(outline.getColumnName(column));
411 valuesWidth += firstColumnPadding;
413 int columnWidth = Math.max(valuesWidth, headerWidth);
414 columnWidth += 2 * margin + padding;
415 columnWidth = Math.min(columnWidth, columnWidthLimit);
417 outline.getColumnModel().getColumn(column).setPreferredWidth(columnWidth);
422 outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
427 return outline.getColumnModel();
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);
441 prop.setValue(
"TreeColumnTTV", Boolean.TRUE);
442 prop.setValue(
"SortingColumnTTV", Boolean.TRUE);
444 propStrings[2 * i] = prop.getName();
445 propStrings[2 * i + 1] = prop.getDisplayName();
447 outlineView.setPropertyColumns(propStrings);
455 if (rootNode == null || propertiesMap.isEmpty()) {
459 TableFilterNode tfn = (TableFilterNode) rootNode;
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);
468 preferences.putBoolean(columnHiddenKey,
true);
470 preferences.remove(columnHiddenKey);
481 if (rootNode == null || propertiesMap.isEmpty()) {
485 TableFilterNode tfn = (TableFilterNode) rootNode;
488 for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
489 preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey());
498 if (rootNode == null || propertiesMap.isEmpty()) {
502 final TableFilterNode tfn = ((TableFilterNode) rootNode);
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();
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());
515 columnModel.setColumnSorted(etc,
true, 0);
516 preferences.remove(columnSortOrderKey);
517 preferences.remove(columnSortRankKey);
530 if (rootNode == null || propertiesMap.isEmpty()) {
534 final TableFilterNode tfn = (TableFilterNode) rootNode;
537 TreeSet<ColumnSortInfo> sortInfos =
new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank));
538 propertiesMap.entrySet().stream().forEach(entry -> {
539 final String propName = entry.getValue().getName();
541 Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0);
543 Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName),
true);
544 sortInfos.add(
new ColumnSortInfo(entry.getKey(), sortRank, sortOrder));
547 sortInfos.forEach(sortInfo -> outline.setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank));
556 if (rootNode == null || propertiesMap.isEmpty()) {
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);
582 List<Property<?>> props = ResultViewerPersistence.getAllChildProperties(rootNode, 100);
589 final TableFilterNode tfn = ((TableFilterNode) rootNode);
590 propertiesMap.clear();
598 int offset = props.size();
599 boolean noPreviousSettings =
true;
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;
609 propertiesMap.put(offset, prop);
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));
623 return new ArrayList<>(propertiesMap.values());
632 this.outlineView.removeAll();
633 this.outlineView = null;
634 super.clearComponent();
647 this.modelIndex = modelIndex;
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"})
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())) {
675 outlineView.setPropertyColumnDescription(column.getHeaderValue().toString(), Bundle.DataResultViewerTable_commentRender_toolTip());
677 }
else if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_scoreRender_name())) {
679 outlineView.setPropertyColumnDescription(column.getHeaderValue().toString(), Bundle.DataResultViewerTable_scoreRender_toolTip());
681 }
else if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_countRender_name())) {
682 outlineView.setPropertyColumnDescription(column.getHeaderValue().toString(), Bundle.DataResultViewerTable_countRender_toolTip());
718 private class TableListener extends MouseAdapter implements TableColumnModelListener {
722 private int startColumnIndex = -1;
723 private int endColumnIndex = -1;
728 int fromIndex = e.getFromIndex();
729 int toIndex = e.getToIndex();
730 if (fromIndex == toIndex) {
744 if (startColumnIndex == -1) {
745 startColumnIndex = fromIndex;
747 endColumnIndex = toIndex;
750 ArrayList<Integer> indicesList =
new ArrayList<>(propertiesMap.keySet());
751 int leftIndex = Math.min(fromIndex, toIndex);
752 int rightIndex = Math.max(fromIndex, toIndex);
755 List<Integer> range = indicesList.subList(leftIndex, rightIndex + 1);
756 int rangeSize = range.size();
758 if (fromIndex < toIndex) {
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)));
765 propertiesMap.put(range.get(rangeSize - 1), movedProp);
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)));
773 propertiesMap.put(range.get(0), movedProp);
792 if (startColumnIndex != -1 && (startColumnIndex == 0 || endColumnIndex == 0)) {
793 outline.moveColumn(endColumnIndex, startColumnIndex);
795 startColumnIndex = -1;
801 storeColumnSorting();
806 columnAddedOrRemoved();
811 columnAddedOrRemoved();
820 if (listenToVisibilitEvents) {
844 this.listenToVisibilitEvents = b;
854 private static final long serialVersionUID = 1L;
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"})
862 Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
863 setBackground(component.getBackground());
864 setHorizontalAlignment(CENTER);
865 Object switchValue = null;
869 switchValue = ((Node.Property) value).getValue();
870 }
catch (IllegalAccessException | InvocationTargetException ex) {
880 switch ((HasCommentStatus) switchValue) {
882 setIcon(COMMENT_ICON);
883 setToolTipText(Bundle.DataResultViewerTable_commentRenderer_crComment_toolTip());
886 setIcon(COMMENT_ICON);
887 setToolTipText(Bundle.DataResultViewerTable_commentRenderer_tagComment_toolTip());
889 case CR_AND_TAG_COMMENTS:
890 setIcon(COMMENT_ICON);
891 setToolTipText(Bundle.DataResultViewerTable_commentRenderer_crAndTagComment_toolTip());
897 setToolTipText(Bundle.DataResultViewerTable_commentRenderer_noComment_toolTip());
914 private static final long serialVersionUID = 1L;
918 Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
919 setBackground(component.getBackground());
920 setHorizontalAlignment(CENTER);
921 Object switchValue = null;
925 switchValue = ((Node.Property) value).getValue();
926 setToolTipText(((FeatureDescriptor) value).getShortDescription());
927 }
catch (IllegalAccessException | InvocationTargetException ex) {
936 if ((switchValue instanceof
Score)) {
938 switch ((Score) switchValue) {
939 case INTERESTING_SCORE:
940 setIcon(INTERESTING_SCORE_ICON);
943 setIcon(NOTABLE_ICON_SCORE);
963 private static final long serialVersionUID = 1L;
967 Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
968 setBackground(component.getBackground());
969 setHorizontalAlignment(LEFT);
970 Object countValue = null;
974 countValue = ((Node.Property) value).getValue();
975 setToolTipText(((FeatureDescriptor) value).getShortDescription());
976 }
catch (IllegalAccessException | InvocationTargetException ex) {
984 if ((countValue instanceof Long)) {
986 if ((Long) countValue >= 0) {
987 setText(countValue.toString());
1021 @SuppressWarnings(
"unchecked")
1023 private
void initComponents() {
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)
1033 layout.setVerticalGroup(
1034 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
1035 .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 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)