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)