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;
80 @NbBundle.Messages(
"DataResultViewerTable.firstColLbl=Name")
82 private static final Color
TAGGED_COLOR =
new Color(200, 210, 220);
92 private final Map<Integer, Property<?>>
propertiesMap =
new TreeMap<>();
99 private final Map<String, ETableColumn>
columnMap =
new HashMap<>();
120 super(explorerManager);
135 outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE);
138 outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
139 outline.setRootVisible(
false);
140 outline.setDragEnabled(
false);
145 outline.getColumnModel().addColumnModelListener(tableListener);
147 outline.getTableHeader().addMouseListener(tableListener);
167 @SuppressWarnings(
"unchecked")
173 javax.swing.GroupLayout layout =
new javax.swing.GroupLayout(
this);
174 this.setLayout(layout);
175 layout.setHorizontalGroup(
176 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
177 .addComponent(
outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE)
179 layout.setVerticalGroup(
180 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
181 .addComponent(
outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE)
204 outline.unsetQuickFilter();
206 this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
208 boolean hasChildren =
false;
209 if (selectedNode != null) {
211 hasChildren = selectedNode.getChildren().getNodesCount() > 0;
215 currentRoot = selectedNode;
216 em.setRootContext(currentRoot);
219 Node emptyNode =
new AbstractNode(Children.LEAF);
220 em.setRootContext(emptyNode);
221 outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
231 this.setCursor(null);
258 boolean propsExist = props.isEmpty() ==
false;
259 Node.Property<?> firstProp = null;
261 firstProp = props.remove(0);
269 outline.setAutoResizeMode((props.isEmpty()) ? JTable.AUTO_RESIZE_ALL_COLUMNS : JTable.AUTO_RESIZE_OFF);
272 if (firstProp != null) {
273 ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(firstProp.getDisplayName());
297 SwingUtilities.invokeLater(() -> {
299 NodeSelectionInfo selectedChildInfo = ((TableFilterNode) currentRoot).getChildNodeSelectionInfo();
300 if (null != selectedChildInfo) {
301 Node[] childNodes = currentRoot.getChildren().getNodes(
true);
302 for (
int i = 0; i < childNodes.length; ++i) {
303 Node childNode = childNodes[i];
304 if (selectedChildInfo.
matches(childNode)) {
306 em.setSelectedNodes(
new Node[]{childNode});
307 }
catch (PropertyVetoException ex) {
308 logger.log(Level.SEVERE,
"Failed to select node specified by selected child info", ex);
313 ((TableFilterNode) currentRoot).setChildNodeSelectionInfo(null);
328 TableColumnModel columnModel = outline.getColumnModel();
329 int columnCount = columnModel.getColumnCount();
331 for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
332 final String propName = entry.getValue().getName();
333 if (entry.getKey() < columnCount) {
334 final ETableColumn column = (ETableColumn) columnModel.getColumn(entry.getKey());
335 columnMap.put(propName, column);
341 if (currentRoot.getChildren().getNodesCount() != 0) {
342 final Graphics graphics =
outlineView.getGraphics();
343 if (graphics != null) {
344 final FontMetrics metrics = graphics.getFontMetrics();
349 for (
int column = 0; column < outline.getModel().getColumnCount(); column++) {
350 int firstColumnPadding = (column == 0) ? 32 : 0;
351 int columnWidthLimit = (column == 0) ? 350 : 300;
355 for (
int row = 0; row < Math.min(100, outline.getRowCount()); row++) {
356 TableCellRenderer renderer = outline.getCellRenderer(row, column);
357 Component comp = outline.prepareRenderer(renderer, row, column);
358 valuesWidth = Math.max(comp.getPreferredSize().width, valuesWidth);
361 int headerWidth = metrics.stringWidth(outline.getColumnName(column));
362 valuesWidth += firstColumnPadding;
364 int columnWidth = Math.max(valuesWidth, headerWidth);
365 columnWidth += 2 * margin + padding;
366 columnWidth = Math.min(columnWidth, columnWidthLimit);
368 outline.getColumnModel().getColumn(column).setPreferredWidth(columnWidth);
373 outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
379 String[] propStrings =
new String[props.size() * 2];
380 for (
int i = 0; i < props.size(); i++) {
381 final Property<?> prop = props.get(i);
382 prop.setValue(
"ComparableColumnTTV", Boolean.TRUE);
385 prop.setValue(
"TreeColumnTTV", Boolean.TRUE);
386 prop.setValue(
"SortingColumnTTV", Boolean.TRUE);
388 propStrings[2 * i] = prop.getName();
389 propStrings[2 * i + 1] = prop.getDisplayName();
399 if (currentRoot == null || propertiesMap.isEmpty()) {
403 TableFilterNode tfn = (TableFilterNode) currentRoot;
405 final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
408 for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
410 String columnName = entry.getKey();
411 final String columnHiddenKey = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName);
412 final TableColumn column = entry.getValue();
414 boolean columnHidden = columnModel.isColumnHidden(column);
416 preferences.putBoolean(columnHiddenKey,
true);
418 preferences.remove(columnHiddenKey);
428 if (currentRoot == null || propertiesMap.isEmpty()) {
432 TableFilterNode tfn = (TableFilterNode) currentRoot;
436 for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
437 preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey());
446 if (currentRoot == null || propertiesMap.isEmpty()) {
450 final TableFilterNode tfn = ((TableFilterNode) currentRoot);
452 ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
453 for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
454 ETableColumn etc = entry.getValue();
455 String columnName = entry.getKey();
458 final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName);
459 final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, columnName);
460 if (etc.isSorted() && (columnModel.isColumnHidden(etc) ==
false)) {
461 preferences.putBoolean(columnSortOrderKey, etc.isAscending());
462 preferences.putInt(columnSortRankKey, etc.getSortRank());
464 columnModel.setColumnSorted(etc,
true, 0);
465 preferences.remove(columnSortOrderKey);
466 preferences.remove(columnSortRankKey);
479 if (currentRoot == null || propertiesMap.isEmpty()) {
484 final TableFilterNode tfn = (TableFilterNode) currentRoot;
489 propertiesMap.entrySet().stream().forEach(entry -> {
490 final String propName = entry.getValue().getName();
493 Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0);
495 Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName),
true);
497 sortInfos.add(
new ColumnSortInfo(entry.getKey(), sortRank, sortOrder));
501 sortInfos.forEach(sortInfo -> outline.setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank));
506 if (currentRoot == null || propertiesMap.isEmpty()) {
514 final TableFilterNode tfn = ((TableFilterNode) currentRoot);
515 ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
516 for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
517 final String propName = entry.getValue().getName();
518 boolean hidden = preferences.getBoolean(ResultViewerPersistence.getColumnHiddenKey(tfn, propName),
false);
519 final TableColumn column = columnMap.get(propName);
520 columnModel.setColumnHidden(column, hidden);
535 List<Property<?>> props = ResultViewerPersistence.getAllChildProperties(currentRoot, 100);
542 final TableFilterNode tfn = ((TableFilterNode) currentRoot);
543 propertiesMap.clear();
551 int offset = props.size();
552 boolean noPreviousSettings =
true;
556 for (Property<?> prop : props) {
557 Integer value = preferences.getInt(ResultViewerPersistence.getColumnPositionKey(tfn, prop.getName()), -1);
558 if (value >= 0 && value < offset && !propertiesMap.containsKey(value)) {
559 propertiesMap.put(value, prop);
560 noPreviousSettings =
false;
562 propertiesMap.put(offset, prop);
569 if (noPreviousSettings) {
570 ArrayList<Integer> keys =
new ArrayList<>(propertiesMap.keySet());
571 for (
int key : keys) {
572 propertiesMap.put(key - props.size(), propertiesMap.remove(key));
576 return new ArrayList<>(propertiesMap.values());
580 @NbBundle.Messages(
"DataResultViewerTable.title=Table")
582 return Bundle.DataResultViewerTable_title();
595 super.clearComponent();
623 private class TableListener extends MouseAdapter implements TableColumnModelListener {
633 int fromIndex = e.getFromIndex();
634 int toIndex = e.getToIndex();
635 if (fromIndex == toIndex) {
649 if (startColumnIndex == -1) {
650 startColumnIndex = fromIndex;
652 endColumnIndex = toIndex;
655 ArrayList<Integer> indicesList =
new ArrayList<>(propertiesMap.keySet());
656 int leftIndex = Math.min(fromIndex, toIndex);
657 int rightIndex = Math.max(fromIndex, toIndex);
660 List<Integer> range = indicesList.subList(leftIndex, rightIndex + 1);
661 int rangeSize = range.size();
663 if (fromIndex < toIndex) {
666 Property<?> movedProp = propertiesMap.get(range.get(0));
667 for (
int i = 0; i < rangeSize - 1; i++) {
668 propertiesMap.put(range.get(i), propertiesMap.get(range.get(i + 1)));
670 propertiesMap.put(range.get(rangeSize - 1), movedProp);
674 Property<?> movedProp = propertiesMap.get(range.get(rangeSize - 1));
675 for (
int i = rangeSize - 1; i > 0; i--) {
676 propertiesMap.put(range.get(i), propertiesMap.get(range.get(i - 1)));
678 propertiesMap.put(range.get(0), movedProp);
697 if (startColumnIndex != -1 && (startColumnIndex == 0 || endColumnIndex == 0)) {
698 outline.moveColumn(endColumnIndex, startColumnIndex);
700 startColumnIndex = -1;
725 if (listenToVisibilitEvents) {
749 this.listenToVisibilitEvents = b;
760 private static final long serialVersionUID = 1L;
765 Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
768 Node node = currentRoot.getChildren().getNodeAt(table.convertRowIndexToModel(row));
769 boolean tagFound =
false;
771 Node.PropertySet[] propSets = node.getPropertySets();
772 if (propSets.length != 0) {
774 Node.Property<?>[] props = propSets[0].getProperties();
775 for (Property<?> prop : props) {
776 if (
"Tags".equals(prop.getName())) {
778 tagFound = !prop.getValue().equals(
"");
779 }
catch (IllegalAccessException | InvocationTargetException ignore) {
788 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)
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)
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 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()