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)
82 private static final long serialVersionUID = 1L;
84 @NbBundle.Messages(
"DataResultViewerTable.firstColLbl=Name")
85 static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl();
86 static private final Color TAGGED_ROW_COLOR =
new Color(255, 255, 195);
102 this(null, Bundle.DataResultViewerTable_title());
115 this(explorerManager, Bundle.DataResultViewerTable_title());
129 super(explorerManager);
131 this.columnMap =
new HashMap<>();
132 this.propertiesMap =
new TreeMap<>();
142 outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE);
144 outline = outlineView.getOutline();
145 outline.setRowSelectionAllowed(
true);
146 outline.setColumnSelectionAllowed(
true);
147 outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
148 outline.setRootVisible(
false);
149 outline.setDragEnabled(
false);
157 outline.getColumnModel().addColumnModelListener(outlineViewListener);
163 outline.getTableHeader().addMouseListener(outlineViewListener);
184 @NbBundle.Messages(
"DataResultViewerTable.title=Table")
209 public
void setNode(Node rootNode) {
217 outline.unsetQuickFilter();
219 this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
232 if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) {
233 this.rootNode = rootNode;
234 this.getExplorerManager().setRootContext(this.rootNode);
237 Node emptyNode =
new AbstractNode(Children.LEAF);
238 this.getExplorerManager().setRootContext(emptyNode);
239 outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
241 outlineView.setPropertyColumns();
244 this.setCursor(null);
270 List<Node.Property<?>> props = loadColumnOrder();
271 boolean propsExist = props.isEmpty() ==
false;
272 Node.Property<?> firstProp = null;
274 firstProp = props.remove(0);
282 outline.setAutoResizeMode((props.isEmpty()) ? JTable.AUTO_RESIZE_ALL_COLUMNS : JTable.AUTO_RESIZE_OFF);
284 assignColumns(props);
285 if (firstProp != null) {
286 ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(firstProp.getDisplayName());
310 loadColumnVisibility();
316 SwingUtilities.invokeLater(() -> {
318 NodeSelectionInfo selectedChildInfo = ((TableFilterNode) rootNode).getChildNodeSelectionInfo();
319 if (null != selectedChildInfo) {
320 Node[] childNodes = rootNode.getChildren().getNodes(
true);
321 for (
int i = 0; i < childNodes.length; ++i) {
322 Node childNode = childNodes[i];
323 if (selectedChildInfo.
matches(childNode)) {
325 this.getExplorerManager().setSelectedNodes(
new Node[]{childNode});
326 }
catch (PropertyVetoException ex) {
327 LOGGER.log(Level.SEVERE,
"Failed to select node specified by selected child info", ex);
332 ((TableFilterNode) rootNode).setChildNodeSelectionInfo(null);
351 TableColumnModel columnModel = outline.getColumnModel();
352 int columnCount = columnModel.getColumnCount();
354 for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
355 final String propName = entry.getValue().getName();
356 if (entry.getKey() < columnCount) {
357 final ETableColumn column = (ETableColumn) columnModel.getColumn(entry.getKey());
358 columnMap.put(propName, column);
368 if (rootNode.getChildren().getNodesCount() != 0) {
369 final Graphics graphics = outlineView.getGraphics();
370 if (graphics != null) {
371 final FontMetrics metrics = graphics.getFontMetrics();
376 for (
int column = 0; column < outline.getModel().getColumnCount(); column++) {
377 int firstColumnPadding = (column == 0) ? 32 : 0;
378 int columnWidthLimit = (column == 0) ? 350 : 300;
382 for (
int row = 0; row < Math.min(100, outline.getRowCount()); row++) {
383 TableCellRenderer renderer = outline.getCellRenderer(row, column);
384 Component comp = outline.prepareRenderer(renderer, row, column);
385 valuesWidth = Math.max(comp.getPreferredSize().width, valuesWidth);
388 int headerWidth = metrics.stringWidth(outline.getColumnName(column));
389 valuesWidth += firstColumnPadding;
391 int columnWidth = Math.max(valuesWidth, headerWidth);
392 columnWidth += 2 * margin + padding;
393 columnWidth = Math.min(columnWidth, columnWidthLimit);
395 outline.getColumnModel().getColumn(column).setPreferredWidth(columnWidth);
400 outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
409 String[] propStrings =
new String[props.size() * 2];
410 for (
int i = 0; i < props.size(); i++) {
411 final Property<?> prop = props.get(i);
412 prop.setValue(
"ComparableColumnTTV", Boolean.TRUE);
415 prop.setValue(
"TreeColumnTTV", Boolean.TRUE);
416 prop.setValue(
"SortingColumnTTV", Boolean.TRUE);
418 propStrings[2 * i] = prop.getName();
419 propStrings[2 * i + 1] = prop.getDisplayName();
421 outlineView.setPropertyColumns(propStrings);
429 if (rootNode == null || propertiesMap.isEmpty()) {
433 TableFilterNode tfn = (TableFilterNode) rootNode;
435 final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
436 for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
437 String columnName = entry.getKey();
438 final String columnHiddenKey = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName);
439 final TableColumn column = entry.getValue();
440 boolean columnHidden = columnModel.isColumnHidden(column);
442 preferences.putBoolean(columnHiddenKey,
true);
444 preferences.remove(columnHiddenKey);
455 if (rootNode == null || propertiesMap.isEmpty()) {
459 TableFilterNode tfn = (TableFilterNode) rootNode;
462 for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
463 preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey());
472 if (rootNode == null || propertiesMap.isEmpty()) {
476 final TableFilterNode tfn = ((TableFilterNode) rootNode);
478 ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
479 for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
480 ETableColumn etc = entry.getValue();
481 String columnName = entry.getKey();
483 final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName);
484 final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, columnName);
485 if (etc.isSorted() && (columnModel.isColumnHidden(etc) ==
false)) {
486 preferences.putBoolean(columnSortOrderKey, etc.isAscending());
487 preferences.putInt(columnSortRankKey, etc.getSortRank());
489 columnModel.setColumnSorted(etc,
true, 0);
490 preferences.remove(columnSortOrderKey);
491 preferences.remove(columnSortRankKey);
504 if (rootNode == null || propertiesMap.isEmpty()) {
508 final TableFilterNode tfn = (TableFilterNode) rootNode;
511 TreeSet<ColumnSortInfo> sortInfos =
new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank));
512 propertiesMap.entrySet().stream().forEach(entry -> {
513 final String propName = entry.getValue().getName();
515 Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0);
517 Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName),
true);
518 sortInfos.add(
new ColumnSortInfo(entry.getKey(), sortRank, sortOrder));
521 sortInfos.forEach(sortInfo -> outline.setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank));
530 if (rootNode == null || propertiesMap.isEmpty()) {
535 final TableFilterNode tfn = ((TableFilterNode) rootNode);
536 ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
537 for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
538 final String propName = entry.getValue().getName();
539 boolean hidden = preferences.getBoolean(ResultViewerPersistence.getColumnHiddenKey(tfn, propName),
false);
540 final TableColumn column = columnMap.get(propName);
541 columnModel.setColumnHidden(column, hidden);
556 List<Property<?>> props = ResultViewerPersistence.getAllChildProperties(rootNode, 100);
563 final TableFilterNode tfn = ((TableFilterNode) rootNode);
564 propertiesMap.clear();
572 int offset = props.size();
573 boolean noPreviousSettings =
true;
577 for (Property<?> prop : props) {
578 Integer value = preferences.getInt(ResultViewerPersistence.getColumnPositionKey(tfn, prop.getName()), -1);
579 if (value >= 0 && value < offset && !propertiesMap.containsKey(value)) {
580 propertiesMap.put(value, prop);
581 noPreviousSettings =
false;
583 propertiesMap.put(offset, prop);
590 if (noPreviousSettings) {
591 ArrayList<Integer> keys =
new ArrayList<>(propertiesMap.keySet());
592 for (
int key : keys) {
593 propertiesMap.put(key - props.size(), propertiesMap.remove(key));
597 return new ArrayList<>(propertiesMap.values());
606 this.outlineView.removeAll();
607 this.outlineView = null;
608 super.clearComponent();
621 this.modelIndex = modelIndex;
635 private class TableListener extends MouseAdapter implements TableColumnModelListener {
639 private int startColumnIndex = -1;
640 private int endColumnIndex = -1;
645 int fromIndex = e.getFromIndex();
646 int toIndex = e.getToIndex();
647 if (fromIndex == toIndex) {
661 if (startColumnIndex == -1) {
662 startColumnIndex = fromIndex;
664 endColumnIndex = toIndex;
667 ArrayList<Integer> indicesList =
new ArrayList<>(propertiesMap.keySet());
668 int leftIndex = Math.min(fromIndex, toIndex);
669 int rightIndex = Math.max(fromIndex, toIndex);
672 List<Integer> range = indicesList.subList(leftIndex, rightIndex + 1);
673 int rangeSize = range.size();
675 if (fromIndex < toIndex) {
678 Property<?> movedProp = propertiesMap.get(range.get(0));
679 for (
int i = 0; i < rangeSize - 1; i++) {
680 propertiesMap.put(range.get(i), propertiesMap.get(range.get(i + 1)));
682 propertiesMap.put(range.get(rangeSize - 1), movedProp);
686 Property<?> movedProp = propertiesMap.get(range.get(rangeSize - 1));
687 for (
int i = rangeSize - 1; i > 0; i--) {
688 propertiesMap.put(range.get(i), propertiesMap.get(range.get(i - 1)));
690 propertiesMap.put(range.get(0), movedProp);
709 if (startColumnIndex != -1 && (startColumnIndex == 0 || endColumnIndex == 0)) {
710 outline.moveColumn(endColumnIndex, startColumnIndex);
712 startColumnIndex = -1;
718 storeColumnSorting();
723 columnAddedOrRemoved();
728 columnAddedOrRemoved();
737 if (listenToVisibilitEvents) {
761 this.listenToVisibilitEvents = b;
772 private static final long serialVersionUID = 1L;
777 Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
779 if (rootNode != null && !isSelected) {
780 Node node = rootNode.getChildren().getNodeAt(table.convertRowIndexToModel(row));
781 boolean tagFound =
false;
783 Node.PropertySet[] propSets = node.getPropertySets();
784 if (propSets.length != 0) {
786 Node.Property<?>[] props = propSets[0].getProperties();
787 for (Property<?> prop : props) {
788 if (
"Tags".equals(prop.getName())) {
790 tagFound = !prop.getValue().equals(
"");
791 }
catch (IllegalAccessException | InvocationTargetException ignore) {
800 component.setBackground(TAGGED_ROW_COLOR);
812 @SuppressWarnings(
"unchecked")
814 private
void initComponents() {
818 javax.swing.GroupLayout layout =
new javax.swing.GroupLayout(
this);
819 this.setLayout(layout);
820 layout.setHorizontalGroup(
821 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
822 .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE)
824 layout.setVerticalGroup(
825 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
826 .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)