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 java.util.stream.Stream;
41 import javax.swing.JTable;
42 import javax.swing.ListSelectionModel;
43 import javax.swing.SwingUtilities;
44 import javax.swing.event.ChangeEvent;
45 import javax.swing.event.ListSelectionEvent;
46 import javax.swing.event.TableColumnModelEvent;
47 import javax.swing.event.TableColumnModelListener;
48 import javax.swing.table.TableCellRenderer;
49 import javax.swing.table.TableColumn;
50 import javax.swing.table.TableColumnModel;
51 import org.netbeans.swing.etable.ETableColumn;
52 import org.netbeans.swing.etable.ETableColumnModel;
53 import org.netbeans.swing.outline.DefaultOutlineCellRenderer;
54 import org.netbeans.swing.outline.DefaultOutlineModel;
55 import org.netbeans.swing.outline.Outline;
56 import org.openide.explorer.ExplorerManager;
57 import org.openide.explorer.view.OutlineView;
58 import org.openide.nodes.AbstractNode;
59 import org.openide.nodes.Children;
60 import org.openide.nodes.Node;
61 import org.openide.nodes.Node.Property;
62 import org.openide.nodes.NodeAdapter;
63 import org.openide.nodes.NodeMemberEvent;
64 import org.openide.util.NbBundle;
65 import org.openide.util.NbPreferences;
83 @NbBundle.Messages(
"DataResultViewerTable.firstColLbl=Name")
85 @NbBundle.Messages(
"DataResultViewerTable.pleasewaitNodeDisplayName=Please Wait...")
87 private static final Color
TAGGED_COLOR =
new Color(200, 210, 220);
97 private final Map<Integer, Property<?>>
propertiesMap =
new TreeMap<>();
104 private final Map<String, ETableColumn>
columnMap =
new HashMap<>();
127 super(explorerManager);
142 outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE);
145 outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
146 outline.setRootVisible(
false);
147 outline.setDragEnabled(
false);
152 outline.getColumnModel().addColumnModelListener(tableListener);
154 outline.getTableHeader().addMouseListener(tableListener);
174 @SuppressWarnings(
"unchecked")
180 javax.swing.GroupLayout layout =
new javax.swing.GroupLayout(
this);
181 this.setLayout(layout);
182 layout.setHorizontalGroup(
183 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
184 .addComponent(
outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE)
186 layout.setVerticalGroup(
187 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
188 .addComponent(
outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE)
211 outline.unsetQuickFilter();
213 this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
215 boolean hasChildren =
false;
216 if (selectedNode != null) {
218 hasChildren = selectedNode.getChildren().getNodesCount() > 0;
221 Node oldNode = this.em.getRootContext();
222 if (oldNode != null) {
223 oldNode.removeNodeListener(pleasewaitNodeListener);
227 currentRoot = selectedNode;
228 pleasewaitNodeListener.
reset();
229 currentRoot.addNodeListener(pleasewaitNodeListener);
230 em.setRootContext(currentRoot);
233 Node emptyNode =
new AbstractNode(Children.LEAF);
234 em.setRootContext(emptyNode);
235 outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
245 this.setCursor(null);
272 if (props.isEmpty() ==
false) {
273 Node.Property<?> prop = props.remove(0);
274 ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(prop.getDisplayName());
282 outline.setAutoResizeMode((props.isEmpty()) ? JTable.AUTO_RESIZE_ALL_COLUMNS : JTable.AUTO_RESIZE_OFF);
306 SwingUtilities.invokeLater(() -> {
308 NodeSelectionInfo selectedChildInfo = ((TableFilterNode) currentRoot).getChildNodeSelectionInfo();
309 if (null != selectedChildInfo) {
310 Node[] childNodes = currentRoot.getChildren().getNodes(
true);
311 for (
int i = 0; i < childNodes.length; ++i) {
312 Node childNode = childNodes[i];
313 if (selectedChildInfo.
matches(childNode)) {
315 em.setSelectedNodes(
new Node[]{childNode});
316 }
catch (PropertyVetoException ex) {
317 logger.log(Level.SEVERE,
"Failed to select node specified by selected child info", ex);
322 ((TableFilterNode) currentRoot).setChildNodeSelectionInfo(null);
337 TableColumnModel columnModel = outline.getColumnModel();
339 for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
340 final String propName = entry.getValue().getName();
341 final ETableColumn column = (ETableColumn) columnModel.getColumn(entry.getKey());
342 columnMap.put(propName, column);
347 if (currentRoot.getChildren().getNodesCount() != 0) {
348 final Graphics graphics =
outlineView.getGraphics();
349 if (graphics != null) {
350 final FontMetrics metrics = graphics.getFontMetrics();
355 for (
int column = 0; column < outline.getModel().getColumnCount(); column++) {
356 int firstColumnPadding = (column == 0) ? 32 : 0;
357 int columnWidthLimit = (column == 0) ? 350 : 300;
361 for (
int row = 0; row < Math.min(100, outline.getRowCount()); row++) {
362 TableCellRenderer renderer = outline.getCellRenderer(row, column);
363 Component comp = outline.prepareRenderer(renderer, row, column);
364 valuesWidth = Math.max(comp.getPreferredSize().width, valuesWidth);
367 int headerWidth = metrics.stringWidth(outline.getColumnName(column));
368 valuesWidth += firstColumnPadding;
370 int columnWidth = Math.max(valuesWidth, headerWidth);
371 columnWidth += 2 * margin + padding;
372 columnWidth = Math.min(columnWidth, columnWidthLimit);
374 outline.getColumnModel().getColumn(column).setPreferredWidth(columnWidth);
379 outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
385 String[] propStrings =
new String[props.size() * 2];
386 for (
int i = 0; i < props.size(); i++) {
387 final Property<?> prop = props.get(i);
388 prop.setValue(
"ComparableColumnTTV", Boolean.TRUE);
391 prop.setValue(
"TreeColumnTTV", Boolean.TRUE);
392 prop.setValue(
"SortingColumnTTV", Boolean.TRUE);
394 propStrings[2 * i] = prop.getName();
395 propStrings[2 * i + 1] = prop.getDisplayName();
405 if (currentRoot == null || propertiesMap.isEmpty()) {
409 TableFilterNode tfn = (TableFilterNode) currentRoot;
411 final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
414 for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
416 String columnName = entry.getKey();
417 final String columnHiddenKey = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName);
418 final TableColumn column = entry.getValue();
420 boolean columnHidden = columnModel.isColumnHidden(column);
422 preferences.putBoolean(columnHiddenKey,
true);
424 preferences.remove(columnHiddenKey);
434 if (currentRoot == null || propertiesMap.isEmpty()) {
438 TableFilterNode tfn = (TableFilterNode) currentRoot;
442 for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
443 preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey());
452 if (currentRoot == null || propertiesMap.isEmpty()) {
456 final TableFilterNode tfn = ((TableFilterNode) currentRoot);
459 for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
460 ETableColumn etc = entry.getValue();
461 String columnName = entry.getKey();
464 final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName);
465 final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, columnName);
467 if (etc.isSorted()) {
468 preferences.putBoolean(columnSortOrderKey, etc.isAscending());
469 preferences.putInt(columnSortRankKey, etc.getSortRank());
471 preferences.remove(columnSortOrderKey);
472 preferences.remove(columnSortRankKey);
486 if (currentRoot == null || propertiesMap.isEmpty()) {
491 final TableFilterNode tfn = (TableFilterNode) currentRoot;
496 propertiesMap.entrySet().stream().forEach(entry -> {
497 final String propName = entry.getValue().getName();
500 Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0);
502 Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName),
true);
504 treeSet.add(
new ColumnSortInfo(entry.getKey(), sortRank, sortOrder));
508 treeSet.forEach(sortInfo -> outline.setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank));
513 if (currentRoot == null || propertiesMap.isEmpty()) {
521 final TableFilterNode tfn = ((TableFilterNode) currentRoot);
522 ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
523 for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
524 final String propName = entry.getValue().getName();
525 boolean hidden = preferences.getBoolean(ResultViewerPersistence.getColumnHiddenKey(tfn, propName),
false);
526 final TableColumn column = columnMap.get(propName);
527 columnModel.setColumnHidden(column, hidden);
542 List<Property<?>> props = ResultViewerPersistence.getAllChildProperties(currentRoot, 100);
549 final TableFilterNode tfn = ((TableFilterNode) currentRoot);
550 propertiesMap.clear();
558 int offset = props.size();
559 boolean noPreviousSettings =
true;
563 for (Property<?> prop : props) {
565 Integer value = preferences.getInt(ResultViewerPersistence.getColumnPositionKey(tfn, prop.getName()), -1);
566 if (value >= 0 && value < offset && !propertiesMap.containsKey(value)) {
567 propertiesMap.put(value, prop);
568 noPreviousSettings =
false;
570 propertiesMap.put(offset, prop);
577 if (noPreviousSettings) {
578 ArrayList<Integer> keys =
new ArrayList<>(propertiesMap.keySet());
579 for (
int key : keys) {
580 propertiesMap.put(key - props.size(), propertiesMap.remove(key));
584 return new ArrayList<>(propertiesMap.values());
588 @NbBundle.Messages(
"DataResultViewerTable.title=Table")
590 return Bundle.DataResultViewerTable_title();
603 super.clearComponent();
631 private class TableListener extends MouseAdapter implements TableColumnModelListener {
641 int fromIndex = e.getFromIndex();
642 int toIndex = e.getToIndex();
643 if (fromIndex == toIndex) {
657 if (startColumnIndex == -1) {
658 startColumnIndex = fromIndex;
660 endColumnIndex = toIndex;
663 ArrayList<Integer> indicesList =
new ArrayList<>(propertiesMap.keySet());
664 int leftIndex = Math.min(fromIndex, toIndex);
665 int rightIndex = Math.max(fromIndex, toIndex);
668 List<Integer> range = indicesList.subList(leftIndex, rightIndex + 1);
669 int rangeSize = range.size();
671 if (fromIndex < toIndex) {
674 Property<?> movedProp = propertiesMap.get(range.get(0));
675 for (
int i = 0; i < rangeSize - 1; i++) {
676 propertiesMap.put(range.get(i), propertiesMap.get(range.get(i + 1)));
678 propertiesMap.put(range.get(rangeSize - 1), movedProp);
682 Property<?> movedProp = propertiesMap.get(range.get(rangeSize - 1));
683 for (
int i = rangeSize - 1; i > 0; i--) {
684 propertiesMap.put(range.get(i), propertiesMap.get(range.get(i - 1)));
686 propertiesMap.put(range.get(0), movedProp);
705 if (startColumnIndex != -1 && (startColumnIndex == 0 || endColumnIndex == 0)) {
706 outline.moveColumn(endColumnIndex, startColumnIndex);
708 startColumnIndex = -1;
733 if (listenToVisibilitEvents) {
757 this.listenToVisibilitEvents = b;
763 private volatile boolean load =
true;
771 Node[] delta = nme.getDelta();
775 if (SwingUtilities.isEventDispatchThread()) {
778 SwingUtilities.invokeLater(() ->
setupTable());
784 return Stream.of(delta)
785 .map(Node::getDisplayName)
786 .noneMatch(PLEASEWAIT_NODE_DISPLAY_NAME::equals);
797 private static final long serialVersionUID = 1L;
802 Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
805 Node node = currentRoot.getChildren().getNodeAt(table.convertRowIndexToModel(row));
806 boolean tagFound =
false;
808 Node.PropertySet[] propSets = node.getPropertySets();
809 if (propSets.length != 0) {
811 Node.Property<?>[] props = propSets[0].getProperties();
812 for (Property<?> prop : props) {
813 if (
"Tags".equals(prop.getName())) {
815 tagFound = !prop.getValue().equals(
"");
816 }
catch (IllegalAccessException | InvocationTargetException ignore) {
825 component.setBackground(TAGGED_COLOR);
void mouseReleased(MouseEvent e)
boolean matches(Node candidateNode)
final Map< String, ETableColumn > columnMap
org.openide.explorer.view.OutlineView outlineView
void columnRemoved(TableColumnModelEvent e)
void childrenAdded(final NodeMemberEvent nme)
synchronized List< Node.Property<?> > loadColumnOrder()
synchronized void storeColumnVisibility()
synchronized void storeColumnSorting()
synchronized void assignColumns(List< Property<?>> props)
final PleasewaitNodeListener pleasewaitNodeListener
static final String FIRST_COLUMN_LABEL
void mouseClicked(MouseEvent e)
void columnSelectionChanged(ListSelectionEvent e)
void columnAddedOrRemoved()
boolean listenToVisibilitEvents
boolean isSupported(Node selectedNode)
DataResultViewer createInstance()
Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col)
final Map< Integer, Property<?> > propertiesMap
synchronized void loadColumnVisibility()
TableListener tableListener
ColumnSortInfo(int modelIndex, int rank, boolean order)
static final String PLEASEWAIT_NODE_DISPLAY_NAME
static final long serialVersionUID
void listenToVisibilityChanges(boolean b)
void setNode(Node selectedNode)
synchronized static Logger getLogger(String name)
synchronized void loadColumnSorting()
static final Color TAGGED_COLOR
static final Logger logger
void columnMoved(TableColumnModelEvent e)
DataResultViewerTable(ExplorerManager explorerManager)
void columnMarginChanged(ChangeEvent e)
void columnAdded(TableColumnModelEvent e)
synchronized void storeColumnOrder()
boolean containsReal(Node[] delta)