19 package org.sleuthkit.autopsy.corecomponents;
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;
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.JTable;
41 import javax.swing.ListSelectionModel;
42 import javax.swing.SwingUtilities;
43 import javax.swing.event.ChangeEvent;
44 import javax.swing.event.ListSelectionEvent;
45 import javax.swing.event.TableColumnModelEvent;
46 import javax.swing.event.TableColumnModelListener;
47 import javax.swing.table.TableCellRenderer;
48 import javax.swing.table.TableColumn;
49 import javax.swing.table.TableColumnModel;
50 import org.netbeans.swing.etable.ETableColumn;
51 import org.netbeans.swing.etable.ETableColumnModel;
52 import org.netbeans.swing.outline.DefaultOutlineCellRenderer;
53 import org.netbeans.swing.outline.DefaultOutlineModel;
54 import org.netbeans.swing.outline.Outline;
55 import org.openide.explorer.ExplorerManager;
56 import org.openide.explorer.view.OutlineView;
57 import org.openide.nodes.AbstractNode;
58 import org.openide.nodes.Children;
59 import org.openide.nodes.Node;
60 import org.openide.nodes.Node.Property;
61 import org.openide.util.NbBundle;
62 import org.openide.util.NbPreferences;
63 import org.openide.util.lookup.ServiceProvider;
79 @ServiceProvider(service = DataResultViewer.class)
80 @SuppressWarnings(
"PMD.SingularField")
83 private static final long serialVersionUID = 1L;
85 @NbBundle.Messages(
"DataResultViewerTable.firstColLbl=Name")
86 static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl();
87 static private final Color TAGGED_ROW_COLOR =
new Color(255, 255, 195);
103 this(null, Bundle.DataResultViewerTable_title());
116 this(explorerManager, Bundle.DataResultViewerTable_title());
130 super(explorerManager);
132 this.columnMap =
new HashMap<>();
133 this.propertiesMap =
new TreeMap<>();
143 outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE);
145 outline = outlineView.getOutline();
146 outline.setRowSelectionAllowed(
true);
147 outline.setColumnSelectionAllowed(
true);
148 outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
149 outline.setRootVisible(
false);
150 outline.setDragEnabled(
false);
158 outline.getColumnModel().addColumnModelListener(outlineViewListener);
164 outline.getTableHeader().addMouseListener(outlineViewListener);
185 @NbBundle.Messages(
"DataResultViewerTable.title=Table")
210 public
void setNode(Node rootNode) {
218 outline.unsetQuickFilter();
220 this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
233 if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) {
234 this.rootNode = rootNode;
235 this.getExplorerManager().setRootContext(this.rootNode);
238 Node emptyNode =
new AbstractNode(Children.LEAF);
239 this.getExplorerManager().setRootContext(emptyNode);
240 outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
242 outlineView.setPropertyColumns();
245 this.setCursor(null);
271 List<Node.Property<?>> props = loadColumnOrder();
272 boolean propsExist = props.isEmpty() ==
false;
273 Node.Property<?> firstProp = null;
275 firstProp = props.remove(0);
283 outline.setAutoResizeMode((props.isEmpty()) ? JTable.AUTO_RESIZE_ALL_COLUMNS : JTable.AUTO_RESIZE_OFF);
285 assignColumns(props);
286 if (firstProp != null) {
287 ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(firstProp.getDisplayName());
311 loadColumnVisibility();
317 SwingUtilities.invokeLater(() -> {
319 NodeSelectionInfo selectedChildInfo = ((TableFilterNode) rootNode).getChildNodeSelectionInfo();
320 if (null != selectedChildInfo) {
321 Node[] childNodes = rootNode.getChildren().getNodes(
true);
322 for (
int i = 0; i < childNodes.length; ++i) {
323 Node childNode = childNodes[i];
324 if (selectedChildInfo.
matches(childNode)) {
326 this.getExplorerManager().setSelectedNodes(
new Node[]{childNode});
327 }
catch (PropertyVetoException ex) {
328 LOGGER.log(Level.SEVERE,
"Failed to select node specified by selected child info", ex);
333 ((TableFilterNode) rootNode).setChildNodeSelectionInfo(null);
352 TableColumnModel columnModel = outline.getColumnModel();
353 int columnCount = columnModel.getColumnCount();
355 for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
356 final String propName = entry.getValue().getName();
357 if (entry.getKey() < columnCount) {
358 final ETableColumn column = (ETableColumn) columnModel.getColumn(entry.getKey());
359 columnMap.put(propName, column);
369 if (rootNode.getChildren().getNodesCount() != 0) {
370 final Graphics graphics = outlineView.getGraphics();
371 if (graphics != null) {
372 final FontMetrics metrics = graphics.getFontMetrics();
377 for (
int column = 0; column < outline.getModel().getColumnCount(); column++) {
378 int firstColumnPadding = (column == 0) ? 32 : 0;
379 int columnWidthLimit = (column == 0) ? 350 : 300;
383 for (
int row = 0; row < Math.min(100, outline.getRowCount()); row++) {
384 TableCellRenderer renderer = outline.getCellRenderer(row, column);
385 Component comp = outline.prepareRenderer(renderer, row, column);
386 valuesWidth = Math.max(comp.getPreferredSize().width, valuesWidth);
389 int headerWidth = metrics.stringWidth(outline.getColumnName(column));
390 valuesWidth += firstColumnPadding;
392 int columnWidth = Math.max(valuesWidth, headerWidth);
393 columnWidth += 2 * margin + padding;
394 columnWidth = Math.min(columnWidth, columnWidthLimit);
396 outline.getColumnModel().getColumn(column).setPreferredWidth(columnWidth);
401 outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
410 String[] propStrings =
new String[props.size() * 2];
411 for (
int i = 0; i < props.size(); i++) {
412 final Property<?> prop = props.get(i);
413 prop.setValue(
"ComparableColumnTTV", Boolean.TRUE);
416 prop.setValue(
"TreeColumnTTV", Boolean.TRUE);
417 prop.setValue(
"SortingColumnTTV", Boolean.TRUE);
419 propStrings[2 * i] = prop.getName();
420 propStrings[2 * i + 1] = prop.getDisplayName();
422 outlineView.setPropertyColumns(propStrings);
430 if (rootNode == null || propertiesMap.isEmpty()) {
434 TableFilterNode tfn = (TableFilterNode) rootNode;
436 final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
437 for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
438 String columnName = entry.getKey();
439 final String columnHiddenKey = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName);
440 final TableColumn column = entry.getValue();
441 boolean columnHidden = columnModel.isColumnHidden(column);
443 preferences.putBoolean(columnHiddenKey,
true);
445 preferences.remove(columnHiddenKey);
456 if (rootNode == null || propertiesMap.isEmpty()) {
460 TableFilterNode tfn = (TableFilterNode) rootNode;
463 for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
464 preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey());
473 if (rootNode == null || propertiesMap.isEmpty()) {
477 final TableFilterNode tfn = ((TableFilterNode) rootNode);
479 ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
480 for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
481 ETableColumn etc = entry.getValue();
482 String columnName = entry.getKey();
484 final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName);
485 final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, columnName);
486 if (etc.isSorted() && (columnModel.isColumnHidden(etc) ==
false)) {
487 preferences.putBoolean(columnSortOrderKey, etc.isAscending());
488 preferences.putInt(columnSortRankKey, etc.getSortRank());
490 columnModel.setColumnSorted(etc,
true, 0);
491 preferences.remove(columnSortOrderKey);
492 preferences.remove(columnSortRankKey);
505 if (rootNode == null || propertiesMap.isEmpty()) {
509 final TableFilterNode tfn = (TableFilterNode) rootNode;
512 TreeSet<ColumnSortInfo> sortInfos =
new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank));
513 propertiesMap.entrySet().stream().forEach(entry -> {
514 final String propName = entry.getValue().getName();
516 Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0);
518 Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName),
true);
519 sortInfos.add(
new ColumnSortInfo(entry.getKey(), sortRank, sortOrder));
522 sortInfos.forEach(sortInfo -> outline.setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank));
531 if (rootNode == null || propertiesMap.isEmpty()) {
536 final TableFilterNode tfn = ((TableFilterNode) rootNode);
537 ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
538 for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
539 final String propName = entry.getValue().getName();
540 boolean hidden = preferences.getBoolean(ResultViewerPersistence.getColumnHiddenKey(tfn, propName),
false);
541 final TableColumn column = columnMap.get(propName);
542 columnModel.setColumnHidden(column, hidden);
557 List<Property<?>> props = ResultViewerPersistence.getAllChildProperties(rootNode, 100);
564 final TableFilterNode tfn = ((TableFilterNode) rootNode);
565 propertiesMap.clear();
573 int offset = props.size();
574 boolean noPreviousSettings =
true;
578 for (Property<?> prop : props) {
579 Integer value = preferences.getInt(ResultViewerPersistence.getColumnPositionKey(tfn, prop.getName()), -1);
580 if (value >= 0 && value < offset && !propertiesMap.containsKey(value)) {
581 propertiesMap.put(value, prop);
582 noPreviousSettings =
false;
584 propertiesMap.put(offset, prop);
591 if (noPreviousSettings) {
592 ArrayList<Integer> keys =
new ArrayList<>(propertiesMap.keySet());
593 for (
int key : keys) {
594 propertiesMap.put(key - props.size(), propertiesMap.remove(key));
598 return new ArrayList<>(propertiesMap.values());
607 this.outlineView.removeAll();
608 this.outlineView = null;
609 super.clearComponent();
622 this.modelIndex = modelIndex;
636 private class TableListener extends MouseAdapter implements TableColumnModelListener {
640 private int startColumnIndex = -1;
641 private int endColumnIndex = -1;
646 int fromIndex = e.getFromIndex();
647 int toIndex = e.getToIndex();
648 if (fromIndex == toIndex) {
662 if (startColumnIndex == -1) {
663 startColumnIndex = fromIndex;
665 endColumnIndex = toIndex;
668 ArrayList<Integer> indicesList =
new ArrayList<>(propertiesMap.keySet());
669 int leftIndex = Math.min(fromIndex, toIndex);
670 int rightIndex = Math.max(fromIndex, toIndex);
673 List<Integer> range = indicesList.subList(leftIndex, rightIndex + 1);
674 int rangeSize = range.size();
676 if (fromIndex < toIndex) {
679 Property<?> movedProp = propertiesMap.get(range.get(0));
680 for (
int i = 0; i < rangeSize - 1; i++) {
681 propertiesMap.put(range.get(i), propertiesMap.get(range.get(i + 1)));
683 propertiesMap.put(range.get(rangeSize - 1), movedProp);
687 Property<?> movedProp = propertiesMap.get(range.get(rangeSize - 1));
688 for (
int i = rangeSize - 1; i > 0; i--) {
689 propertiesMap.put(range.get(i), propertiesMap.get(range.get(i - 1)));
691 propertiesMap.put(range.get(0), movedProp);
710 if (startColumnIndex != -1 && (startColumnIndex == 0 || endColumnIndex == 0)) {
711 outline.moveColumn(endColumnIndex, startColumnIndex);
713 startColumnIndex = -1;
719 storeColumnSorting();
724 columnAddedOrRemoved();
729 columnAddedOrRemoved();
738 if (listenToVisibilitEvents) {
762 this.listenToVisibilitEvents = b;
773 private static final long serialVersionUID = 1L;
778 Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
780 if (rootNode != null && !isSelected) {
781 Node node = rootNode.getChildren().getNodeAt(table.convertRowIndexToModel(row));
782 boolean tagFound =
false;
784 Node.PropertySet[] propSets = node.getPropertySets();
785 if (propSets.length != 0) {
787 Node.Property<?>[] props = propSets[0].getProperties();
788 for (Property<?> prop : props) {
789 if (
"Tags".equals(prop.getName())) {
791 tagFound = !prop.getValue().equals(
"");
792 }
catch (IllegalAccessException | InvocationTargetException ignore) {
801 component.setBackground(TAGGED_ROW_COLOR);
813 @SuppressWarnings(
"unchecked")
815 private
void initComponents() {
819 javax.swing.GroupLayout layout =
new javax.swing.GroupLayout(
this);
820 this.setLayout(layout);
821 layout.setHorizontalGroup(
822 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
823 .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE)
825 layout.setVerticalGroup(
826 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
827 .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE)
void mouseReleased(MouseEvent e)
boolean matches(Node candidateNode)
final Map< String, ETableColumn > columnMap
org.openide.explorer.view.OutlineView outlineView
void columnRemoved(TableColumnModelEvent e)
synchronized List< Node.Property<?> > loadColumnOrder()
synchronized void storeColumnVisibility()
synchronized void storeColumnSorting()
synchronized void assignColumns(List< Property<?>> props)
static final String FIRST_COLUMN_LABEL
void mouseClicked(MouseEvent e)
void columnSelectionChanged(ListSelectionEvent e)
final TableListener outlineViewListener
void columnAddedOrRemoved()
boolean listenToVisibilitEvents
DataResultViewer createInstance()
Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col)
final Map< Integer, Property<?> > propertiesMap
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)