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(255, 255, 195);
95 private final Map<Integer, Property<?>>
propertiesMap =
new TreeMap<>();
102 private final Map<String, ETableColumn>
columnMap =
new HashMap<>();
123 this(explorerManager, Bundle.DataResultViewerTable_title());
134 super(explorerManager);
139 outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE);
141 outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
142 outline.setRootVisible(
false);
143 outline.setDragEnabled(
false);
147 outline.getColumnModel().addColumnModelListener(tableListener);
149 outline.getTableHeader().addMouseListener(tableListener);
157 this(
new ExplorerManager(),Bundle.DataResultViewerTable_title());
178 @SuppressWarnings(
"unchecked")
184 javax.swing.GroupLayout layout =
new javax.swing.GroupLayout(
this);
185 this.setLayout(layout);
186 layout.setHorizontalGroup(
187 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
188 .addComponent(
outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE)
190 layout.setVerticalGroup(
191 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
192 .addComponent(
outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE)
215 outline.unsetQuickFilter();
217 this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
219 boolean hasChildren =
false;
220 if (selectedNode != null) {
222 hasChildren = selectedNode.getChildren().getNodesCount() > 0;
226 currentRoot = selectedNode;
227 em.setRootContext(currentRoot);
230 Node emptyNode =
new AbstractNode(Children.LEAF);
231 em.setRootContext(emptyNode);
232 outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
242 this.setCursor(null);
269 boolean propsExist = props.isEmpty() ==
false;
270 Node.Property<?> firstProp = null;
272 firstProp = props.remove(0);
280 outline.setAutoResizeMode((props.isEmpty()) ? JTable.AUTO_RESIZE_ALL_COLUMNS : JTable.AUTO_RESIZE_OFF);
283 if (firstProp != null) {
284 ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(firstProp.getDisplayName());
308 SwingUtilities.invokeLater(() -> {
310 NodeSelectionInfo selectedChildInfo = ((TableFilterNode) currentRoot).getChildNodeSelectionInfo();
311 if (null != selectedChildInfo) {
312 Node[] childNodes = currentRoot.getChildren().getNodes(
true);
313 for (
int i = 0; i < childNodes.length; ++i) {
314 Node childNode = childNodes[i];
315 if (selectedChildInfo.
matches(childNode)) {
317 em.setSelectedNodes(
new Node[]{childNode});
318 }
catch (PropertyVetoException ex) {
319 logger.log(Level.SEVERE,
"Failed to select node specified by selected child info", ex);
324 ((TableFilterNode) currentRoot).setChildNodeSelectionInfo(null);
339 TableColumnModel columnModel = outline.getColumnModel();
340 int columnCount = columnModel.getColumnCount();
342 for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
343 final String propName = entry.getValue().getName();
344 if (entry.getKey() < columnCount) {
345 final ETableColumn column = (ETableColumn) columnModel.getColumn(entry.getKey());
346 columnMap.put(propName, column);
352 if (currentRoot.getChildren().getNodesCount() != 0) {
353 final Graphics graphics =
outlineView.getGraphics();
354 if (graphics != null) {
355 final FontMetrics metrics = graphics.getFontMetrics();
360 for (
int column = 0; column < outline.getModel().getColumnCount(); column++) {
361 int firstColumnPadding = (column == 0) ? 32 : 0;
362 int columnWidthLimit = (column == 0) ? 350 : 300;
366 for (
int row = 0; row < Math.min(100, outline.getRowCount()); row++) {
367 TableCellRenderer renderer = outline.getCellRenderer(row, column);
368 Component comp = outline.prepareRenderer(renderer, row, column);
369 valuesWidth = Math.max(comp.getPreferredSize().width, valuesWidth);
372 int headerWidth = metrics.stringWidth(outline.getColumnName(column));
373 valuesWidth += firstColumnPadding;
375 int columnWidth = Math.max(valuesWidth, headerWidth);
376 columnWidth += 2 * margin + padding;
377 columnWidth = Math.min(columnWidth, columnWidthLimit);
379 outline.getColumnModel().getColumn(column).setPreferredWidth(columnWidth);
384 outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
390 String[] propStrings =
new String[props.size() * 2];
391 for (
int i = 0; i < props.size(); i++) {
392 final Property<?> prop = props.get(i);
393 prop.setValue(
"ComparableColumnTTV", Boolean.TRUE);
396 prop.setValue(
"TreeColumnTTV", Boolean.TRUE);
397 prop.setValue(
"SortingColumnTTV", Boolean.TRUE);
399 propStrings[2 * i] = prop.getName();
400 propStrings[2 * i + 1] = prop.getDisplayName();
410 if (currentRoot == null || propertiesMap.isEmpty()) {
414 TableFilterNode tfn = (TableFilterNode) currentRoot;
416 final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
419 for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
421 String columnName = entry.getKey();
422 final String columnHiddenKey = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName);
423 final TableColumn column = entry.getValue();
425 boolean columnHidden = columnModel.isColumnHidden(column);
427 preferences.putBoolean(columnHiddenKey,
true);
429 preferences.remove(columnHiddenKey);
439 if (currentRoot == null || propertiesMap.isEmpty()) {
443 TableFilterNode tfn = (TableFilterNode) currentRoot;
447 for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
448 preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey());
457 if (currentRoot == null || propertiesMap.isEmpty()) {
461 final TableFilterNode tfn = ((TableFilterNode) currentRoot);
463 ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
464 for (Map.Entry<String, ETableColumn> entry : columnMap.entrySet()) {
465 ETableColumn etc = entry.getValue();
466 String columnName = entry.getKey();
469 final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName);
470 final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, columnName);
471 if (etc.isSorted() && (columnModel.isColumnHidden(etc) ==
false)) {
472 preferences.putBoolean(columnSortOrderKey, etc.isAscending());
473 preferences.putInt(columnSortRankKey, etc.getSortRank());
475 columnModel.setColumnSorted(etc,
true, 0);
476 preferences.remove(columnSortOrderKey);
477 preferences.remove(columnSortRankKey);
490 if (currentRoot == null || propertiesMap.isEmpty()) {
495 final TableFilterNode tfn = (TableFilterNode) currentRoot;
500 propertiesMap.entrySet().stream().forEach(entry -> {
501 final String propName = entry.getValue().getName();
504 Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0);
506 Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName),
true);
508 sortInfos.add(
new ColumnSortInfo(entry.getKey(), sortRank, sortOrder));
512 sortInfos.forEach(sortInfo -> outline.setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank));
517 if (currentRoot == null || propertiesMap.isEmpty()) {
525 final TableFilterNode tfn = ((TableFilterNode) currentRoot);
526 ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel();
527 for (Map.Entry<Integer, Property<?>> entry : propertiesMap.entrySet()) {
528 final String propName = entry.getValue().getName();
529 boolean hidden = preferences.getBoolean(ResultViewerPersistence.getColumnHiddenKey(tfn, propName),
false);
530 final TableColumn column = columnMap.get(propName);
531 columnModel.setColumnHidden(column, hidden);
546 List<Property<?>> props = ResultViewerPersistence.getAllChildProperties(currentRoot, 100);
553 final TableFilterNode tfn = ((TableFilterNode) currentRoot);
554 propertiesMap.clear();
562 int offset = props.size();
563 boolean noPreviousSettings =
true;
567 for (Property<?> prop : props) {
568 Integer value = preferences.getInt(ResultViewerPersistence.getColumnPositionKey(tfn, prop.getName()), -1);
569 if (value >= 0 && value < offset && !propertiesMap.containsKey(value)) {
570 propertiesMap.put(value, prop);
571 noPreviousSettings =
false;
573 propertiesMap.put(offset, prop);
580 if (noPreviousSettings) {
581 ArrayList<Integer> keys =
new ArrayList<>(propertiesMap.keySet());
582 for (
int key : keys) {
583 propertiesMap.put(key - props.size(), propertiesMap.remove(key));
587 return new ArrayList<>(propertiesMap.values());
591 @NbBundle.Messages(
"DataResultViewerTable.title=Table")
606 super.clearComponent();
634 private class TableListener extends MouseAdapter implements TableColumnModelListener {
644 int fromIndex = e.getFromIndex();
645 int toIndex = e.getToIndex();
646 if (fromIndex == toIndex) {
660 if (startColumnIndex == -1) {
661 startColumnIndex = fromIndex;
663 endColumnIndex = toIndex;
666 ArrayList<Integer> indicesList =
new ArrayList<>(propertiesMap.keySet());
667 int leftIndex = Math.min(fromIndex, toIndex);
668 int rightIndex = Math.max(fromIndex, toIndex);
671 List<Integer> range = indicesList.subList(leftIndex, rightIndex + 1);
672 int rangeSize = range.size();
674 if (fromIndex < toIndex) {
677 Property<?> movedProp = propertiesMap.get(range.get(0));
678 for (
int i = 0; i < rangeSize - 1; i++) {
679 propertiesMap.put(range.get(i), propertiesMap.get(range.get(i + 1)));
681 propertiesMap.put(range.get(rangeSize - 1), movedProp);
685 Property<?> movedProp = propertiesMap.get(range.get(rangeSize - 1));
686 for (
int i = rangeSize - 1; i > 0; i--) {
687 propertiesMap.put(range.get(i), propertiesMap.get(range.get(i - 1)));
689 propertiesMap.put(range.get(0), movedProp);
708 if (startColumnIndex != -1 && (startColumnIndex == 0 || endColumnIndex == 0)) {
709 outline.moveColumn(endColumnIndex, startColumnIndex);
711 startColumnIndex = -1;
736 if (listenToVisibilitEvents) {
760 this.listenToVisibilitEvents = b;
771 private static final long serialVersionUID = 1L;
776 Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
778 if (currentRoot != null && !isSelected) {
779 Node node = currentRoot.getChildren().getNodeAt(table.convertRowIndexToModel(row));
780 boolean tagFound =
false;
782 Node.PropertySet[] propSets = node.getPropertySets();
783 if (propSets.length != 0) {
785 Node.Property<?>[] props = propSets[0].getProperties();
786 for (Property<?> prop : props) {
787 if (
"Tags".equals(prop.getName())) {
789 tagFound = !prop.getValue().equals(
"");
790 }
catch (IllegalAccessException | InvocationTargetException ignore) {
799 component.setBackground(TAGGED_COLOR);
void mouseReleased(MouseEvent e)
boolean matches(Node candidateNode)
final TableListener tableListener
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()
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()
DataResultViewerTable(ExplorerManager explorerManager, String title)