From d3ff7f78e5df15ead33c97f172dfe5315049e2fd Mon Sep 17 00:00:00 2001 From: millmanorama Date: Tue, 9 Jan 2018 12:04:09 +0100 Subject: [PATCH 01/11] WIP link selection WIP --- .../RelaionshipSetNodeFactory.java | 35 +++++++ .../communications/VisualizationPanel.java | 98 ++++++++++--------- 2 files changed, 87 insertions(+), 46 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/communications/RelaionshipSetNodeFactory.java diff --git a/Core/src/org/sleuthkit/autopsy/communications/RelaionshipSetNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/RelaionshipSetNodeFactory.java new file mode 100644 index 0000000000..b147ea9537 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/RelaionshipSetNodeFactory.java @@ -0,0 +1,35 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.communications; + +import java.util.Collection; +import java.util.List; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Node; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * + */ +public class RelaionshipSetNodeFactory extends ChildFactory { + + private final Collection artifacts; + + public RelaionshipSetNodeFactory(Collection artifacts) { + this.artifacts = artifacts; + } + + @Override + protected boolean createKeys(List list) { + list.addAll(artifacts); + return true; + } + + @Override + protected Node createNodeForKey(BlackboardArtifact key) { + return new RelationshipNode(key); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index 320e7d493b..78b2d23e71 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.communications; +import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; import com.google.common.eventbus.Subscribe; import com.mxgraph.layout.mxOrganicLayout; import com.mxgraph.model.mxCell; @@ -28,8 +30,11 @@ import com.mxgraph.view.mxStylesheet; import java.awt.BorderLayout; import java.awt.Color; import java.beans.PropertyVetoException; +import java.util.Arrays; +import java.util.Collection; import java.util.EnumSet; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; @@ -83,6 +88,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private final mxGraphComponent graphComponent; private final mxGraph graph; private final Map nodeMap = new HashMap<>(); + Multimap edgeMap = MultimapBuilder.hashKeys().hashSetValues().build(); private CommunicationsManager commsManager; @@ -126,8 +132,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider vizEM.setSelectedNodes(new Node[]{accountDeviceInstanceNode}); } else if (selectionCell.isEdge()) { - System.out.println(selectionCell.getId()); -// explorerManager.setRootContext(new CommunicationsBundleNode(adiKey, commsManager)); + vizEM.setRootContext(new AbstractNode(Children.create(new RelaionshipSetNodeFactory((Set) selectionCell.getValue()), true))); } } catch (PropertyVetoException ex) { logger.log(Level.SEVERE, "Account selection vetoed.", ex); @@ -141,43 +146,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider return proxyLookup; } - @Subscribe - public void pinAccounts(PinAccountEvent pinEvent) { - - final Set adiKeys = pinEvent.getAccountDeviceInstances(); - final CommunicationsFilter commsFilter = filterProvider.getFilter(); - - graph.getModel().beginUpdate(); - try { - nodeMap.clear(); - graph.removeCells(graph.getChildCells(graph.getDefaultParent(), true, true)); - - for (AccountDeviceInstanceKey adiKey : adiKeys) { - mxCell pinnedAccountVertex = getOrCreateVertex(adiKey); - - List relatedAccountDeviceInstances = - commsManager.getRelatedAccountDeviceInstances(adiKey.getAccountDeviceInstance(), commsFilter); - - for (AccountDeviceInstance relatedADI : relatedAccountDeviceInstances) { - long communicationsCount = commsManager.getRelationshipSourcesCount(relatedADI, commsFilter); - AccountDeviceInstanceKey relatedADIKey = new AccountDeviceInstanceKey(relatedADI, commsFilter, communicationsCount); - mxCell relatedAccountVertex = getOrCreateVertex(relatedADIKey); - - addEdge(pinnedAccountVertex, relatedAccountVertex); - } - } - } catch (TskCoreException ex) { - Exceptions.printStackTrace(ex); - } finally { - // Updates the display - graph.getModel().endUpdate(); - - } - - applyOrganicLayout(); - revalidate(); - } - private mxCell getOrCreateVertex(AccountDeviceInstanceKey accountDeviceInstanceKey) { final AccountDeviceInstance accountDeviceInstance = accountDeviceInstanceKey.getAccountDeviceInstance(); final String name =// accountDeviceInstance.getDeviceId() + ":" + @@ -197,19 +165,57 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } return vertex; } - - private void addEdge(mxCell pinnedAccountVertex, mxCell relatedAccountVertex) { - + @SuppressWarnings("unchecked") + private void addEdge(BlackboardArtifact artifact, mxCell pinnedAccountVertex, mxCell relatedAccountVertex) throws TskCoreException { Object[] edgesBetween = graph.getEdgesBetween(pinnedAccountVertex, relatedAccountVertex); - if (edgesBetween.length == 0) { final String edgeName = pinnedAccountVertex.getId() + " <-> " + relatedAccountVertex.getId(); - graph.insertEdge(graph.getDefaultParent(), edgeName, 1d, pinnedAccountVertex, relatedAccountVertex); + mxCell edge = (mxCell) graph.insertEdge(graph.getDefaultParent(), edgeName, new HashSet<>(Arrays.asList(artifact)), pinnedAccountVertex, relatedAccountVertex); + edgeMap.put(artifact, edge); } else if (edgesBetween.length == 1) { final mxCell edge = (mxCell) edgesBetween[0]; - edge.setValue(1d + (double) edge.getValue()); - edge.setStyle("strokeWidth=" + Math.log((double) edge.getValue())); + ((Collection) edge.getValue()).add(artifact); + edge.setStyle("strokeWidth=" + Math.sqrt(((Collection) edge.getValue()).size())); + @Subscribe + public void pinAccounts(PinAccountEvent pinEvent) { + + final Set adiKeys = pinEvent.getAccountDeviceInstances(); + final CommunicationsFilter commsFilter = filterProvider.getFilter(); + + graph.getModel().beginUpdate(); + try { + nodeMap.clear(); + graph.removeCells(graph.getChildCells(graph.getDefaultParent(), true, true)); + + for (AccountDeviceInstanceKey adiKey : adiKeys) { + mxCell pinnedAccountVertex = getOrCreateVertex(adiKey); + + List relatedAccountDeviceInstances = + commsManager.getRelatedAccountDeviceInstances(adiKey.getAccountDeviceInstance(), commsFilter); + + for (AccountDeviceInstance relatedADI : relatedAccountDeviceInstances) { + + List relationships = commsManager.getRelationships(adiKey.getAccountDeviceInstance(), relatedADI, commsFilter); + + long communicationsCount = relationships.size(); + AccountDeviceInstanceKey relatedADIKey = + new AccountDeviceInstanceKey(relatedADI, commsFilter, communicationsCount); + mxCell relatedAccountVertex = getOrCreateVertex(relatedADIKey); + for (BlackboardArtifact relationship : relationships) { + addEdge(relationship, pinnedAccountVertex, relatedAccountVertex); + } + } + } + } catch (TskCoreException ex) { + Exceptions.printStackTrace(ex); + } finally { + // Updates the display + graph.getModel().endUpdate(); + } + + applyOrganicLayout(); + revalidate(); } @Override From 55569a189fa924fe0ce9ff4a6e227cf6815bc119 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Tue, 9 Jan 2018 16:11:39 +0100 Subject: [PATCH 02/11] working edge selection --- .../autopsy/communications/Bundle.properties | 5 +- .../communications/MessageBrowser.java | 2 + .../communications/VisualizationPanel.form | 108 ++++++++++++- .../communications/VisualizationPanel.java | 144 ++++++++++++++++-- 4 files changed, 240 insertions(+), 19 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties index d0fa082fd9..abdbd65f69 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties @@ -15,9 +15,12 @@ FiltersPanel.refreshButton.text=Refresh FiltersPanel.deviceRequiredLabel.text=Select at least one. FiltersPanel.accountTypeRequiredLabel.text=Select at least one. FiltersPanel.needsRefreshLabel.text=Displayed data is out of date. Press Refresh. -VisualizationPanel.jButton1.text=redo layout +VisualizationPanel.jButton1.text=Organic CVTTopComponent.vizPanel.TabConstraints.tabTitle=Visualize VisualizationPanel.jButton2.text=pan CVTTopComponent.accountsBrowser.TabConstraints.tabTitle_1=Browse CVTTopComponent.browseVisualizeTabPane.AccessibleContext.accessibleName=Visualize CVTTopComponent.vizPanel.TabConstraints.tabTitle_1=Visualize +VisualizationPanel.jButton3.text=Orthogonal +VisualizationPanel.jButton4.text=- +VisualizationPanel.jButton5.text=+ diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java index 8fe4873f54..2f76267edf 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java @@ -103,6 +103,8 @@ public final class MessageBrowser extends JPanel implements ExplorerManager.Prov TableFilterNode wrappedNode = new TableFilterNode(new DataResultFilterNode(accountDetailsNode, gacExplorerManager), true); messagesResultPanel.setNode(wrappedNode); + }else{ + messagesResultPanel.setNode(selectedNode); } } } diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form index 3262c06010..1084d8f2da 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form @@ -17,6 +17,10 @@ + + + + @@ -34,17 +38,63 @@ - - - - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -72,6 +122,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index 78b2d23e71..f07eaa32f1 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -22,6 +22,7 @@ import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; import com.google.common.eventbus.Subscribe; import com.mxgraph.layout.mxOrganicLayout; +import com.mxgraph.layout.orthogonal.mxOrthogonalLayout; import com.mxgraph.model.mxCell; import com.mxgraph.swing.mxGraphComponent; import com.mxgraph.util.mxConstants; @@ -53,12 +54,13 @@ import org.sleuthkit.autopsy.casemodule.Case; import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AccountDeviceInstance; +import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.CommunicationsFilter; import org.sleuthkit.datamodel.CommunicationsManager; import org.sleuthkit.datamodel.TskCoreException; /** - * * A panel that goes in the Visualize tab of the Communications Visualization + * A panel that goes in the Visualize tab of the Communications Visualization * Tool. Hosts an JGraphX mxGraphComponent that host the communications network * visualization and a MessageBrowser for viewing details of communications. * @@ -76,6 +78,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider static { //initialize defaul cell (Vertex and/or Edge) properties mxStylesheet.getDefaultVertexStyle().put(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_ELLIPSE); + mxStylesheet.getDefaultVertexStyle().put(mxConstants.STYLE_FONTCOLOR, "#000000"); mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_NOLABEL, true); } @@ -88,11 +91,11 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private final mxGraphComponent graphComponent; private final mxGraph graph; private final Map nodeMap = new HashMap<>(); - Multimap edgeMap = MultimapBuilder.hashKeys().hashSetValues().build(); + private final Multimap edgeMap = MultimapBuilder.hashKeys().hashSetValues().build(); private CommunicationsManager commsManager; - protected void setFilterProvider(FilterProvider filterProvider) { + void setFilterProvider(FilterProvider filterProvider) { this.filterProvider = filterProvider; } private FilterProvider filterProvider; @@ -100,6 +103,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider public VisualizationPanel() { initComponents(); graph = new mxGraph(); + graph.setCellsCloneable(false); + graph.setDropEnabled(false); + graph.setCellsCloneable(false); graph.setCellsEditable(false); graph.setCellsResizable(false); graph.setCellsMovable(true); @@ -108,11 +114,19 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graph.setDisconnectOnMove(false); graph.setEdgeLabelsMovable(false); graph.setVertexLabelsMovable(false); + graph.setAllowDanglingEdges(false); + graph.setCellsDeletable(false); + graph.setCellsBendable(true); + graph.setKeepEdgesInBackground(true); + graphComponent = new mxGraphComponent(graph); graphComponent.setAutoScroll(true); graphComponent.setOpaque(true); graphComponent.setBackground(Color.WHITE); jPanel1.add(graphComponent, BorderLayout.CENTER); + + graphComponent.getGraphControl().addMouseMotionListener(graphComponent.getPanningHandler()); + splitPane.setRightComponent(new MessageBrowser(vizEM, gacEM)); CVTEvents.getCVTEventBus().register(this); @@ -132,7 +146,10 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider vizEM.setSelectedNodes(new Node[]{accountDeviceInstanceNode}); } else if (selectionCell.isEdge()) { - vizEM.setRootContext(new AbstractNode(Children.create(new RelaionshipSetNodeFactory((Set) selectionCell.getValue()), true))); + @SuppressWarnings("unchecked") + AbstractNode abstractNode = new AbstractNode(Children.create(new RelaionshipSetNodeFactory((Set) selectionCell.getValue()), true)); + vizEM.setRootContext(abstractNode); + vizEM.setSelectedNodes(new Node[]{abstractNode}); } } catch (PropertyVetoException ex) { logger.log(Level.SEVERE, "Account selection vetoed.", ex); @@ -165,6 +182,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } return vertex; } + @SuppressWarnings("unchecked") private void addEdge(BlackboardArtifact artifact, mxCell pinnedAccountVertex, mxCell relatedAccountVertex) throws TskCoreException { Object[] edgesBetween = graph.getEdgesBetween(pinnedAccountVertex, relatedAccountVertex); @@ -176,6 +194,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider final mxCell edge = (mxCell) edgesBetween[0]; ((Collection) edge.getValue()).add(artifact); edge.setStyle("strokeWidth=" + Math.sqrt(((Collection) edge.getValue()).size())); + } + } + @Subscribe public void pinAccounts(PinAccountEvent pinEvent) { @@ -185,6 +206,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graph.getModel().beginUpdate(); try { nodeMap.clear(); + edgeMap.clear(); graph.removeCells(graph.getChildCells(graph.getDefaultParent(), true, true)); for (AccountDeviceInstanceKey adiKey : adiKeys) { @@ -236,6 +258,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graph.getModel().beginUpdate(); try { nodeMap.clear(); + edgeMap.clear(); graph.removeCells(graph.getChildCells(graph.getDefaultParent(), true, true)); } finally { graph.getModel().endUpdate(); @@ -255,7 +278,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } @Override - public void removeNotify() { super.removeNotify(); // IngestManager.getInstance().removeIngestModuleEventListener(ingestListener); @@ -272,15 +294,20 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider splitPane = new javax.swing.JSplitPane(); jPanel1 = new javax.swing.JPanel(); - jToolBar1 = new javax.swing.JToolBar(); + jPanel2 = new javax.swing.JPanel(); jButton2 = new javax.swing.JButton(); jButton1 = new javax.swing.JButton(); + jButton3 = new javax.swing.JButton(); + jSeparator1 = new javax.swing.JToolBar.Separator(); + jButton4 = new javax.swing.JButton(); + jButton5 = new javax.swing.JButton(); setLayout(new java.awt.BorderLayout()); - jPanel1.setLayout(new java.awt.BorderLayout()); + splitPane.setDividerLocation(400); + splitPane.setResizeWeight(0.5); - jToolBar1.setRollover(true); + jPanel1.setLayout(new java.awt.BorderLayout()); jButton2.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jButton2.text")); // NOI18N jButton2.setFocusable(false); @@ -291,7 +318,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider jButton2ActionPerformed(evt); } }); - jToolBar1.add(jButton2); jButton1.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jButton1.text")); // NOI18N jButton1.setFocusable(false); @@ -302,9 +328,82 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider jButton1ActionPerformed(evt); } }); - jToolBar1.add(jButton1); - jPanel1.add(jToolBar1, java.awt.BorderLayout.PAGE_START); + jButton3.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jButton3.text")); // NOI18N + jButton3.setFocusable(false); + jButton3.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + jButton3.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + jButton3.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButton3ActionPerformed(evt); + } + }); + + jSeparator1.setPreferredSize(new java.awt.Dimension(10, 10)); + + jButton4.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jButton4.text")); // NOI18N + jButton4.setFocusable(false); + jButton4.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + jButton4.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + jButton4.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButton4ActionPerformed(evt); + } + }); + + jButton5.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jButton5.text")); // NOI18N + jButton5.setFocusable(false); + jButton5.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + jButton5.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + jButton5.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButton5ActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2); + jPanel2.setLayout(jPanel2Layout); + jPanel2Layout.setHorizontalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jButton2) + .addGap(5, 5, 5) + .addComponent(jButton1) + .addGap(5, 5, 5) + .addComponent(jButton3) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 1, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jButton4) + .addGap(5, 5, 5) + .addComponent(jButton5) + .addContainerGap()) + ); + jPanel2Layout.setVerticalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addGap(5, 5, 5) + .addComponent(jButton2)) + .addGroup(jPanel2Layout.createSequentialGroup() + .addGap(5, 5, 5) + .addComponent(jButton1)) + .addGroup(jPanel2Layout.createSequentialGroup() + .addGap(5, 5, 5) + .addComponent(jButton3)) + .addGroup(jPanel2Layout.createSequentialGroup() + .addGap(14, 14, 14) + .addComponent(jSeparator1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGap(10, 10, 10)) + .addGroup(jPanel2Layout.createSequentialGroup() + .addGap(5, 5, 5) + .addComponent(jButton4)) + .addGroup(jPanel2Layout.createSequentialGroup() + .addGap(5, 5, 5) + .addComponent(jButton5)) + ); + + jPanel1.add(jPanel2, java.awt.BorderLayout.NORTH); splitPane.setLeftComponent(jPanel1); @@ -320,17 +419,38 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider applyOrganicLayout(); }//GEN-LAST:event_jButton1ActionPerformed + private void jButton3ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton3ActionPerformed + applyOrthogonalLayout(); + }//GEN-LAST:event_jButton3ActionPerformed + + private void jButton5ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton5ActionPerformed + graphComponent.zoomIn(); + }//GEN-LAST:event_jButton5ActionPerformed + + private void jButton4ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton4ActionPerformed + graphComponent.zoomOut(); + }//GEN-LAST:event_jButton4ActionPerformed + private void applyOrganicLayout() { new mxOrganicLayout(graph).execute(graph.getDefaultParent()); graphComponent.zoomAndCenter(); } + private void applyOrthogonalLayout() { + new mxOrthogonalLayout(graph).execute(graph.getDefaultParent()); + graphComponent.zoomAndCenter(); + } + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton jButton1; private javax.swing.JButton jButton2; + private javax.swing.JButton jButton3; + private javax.swing.JButton jButton4; + private javax.swing.JButton jButton5; private javax.swing.JPanel jPanel1; - private javax.swing.JToolBar jToolBar1; + private javax.swing.JPanel jPanel2; + private javax.swing.JToolBar.Separator jSeparator1; private javax.swing.JSplitPane splitPane; // End of variables declaration//GEN-END:variables From 7fd1562427f79b8413b0975806478470c4be730a Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 10 Jan 2018 15:28:24 +0100 Subject: [PATCH 03/11] experimenting with actions. got panning to work --- .../autopsy/communications/Bundle.properties | 1 + .../communications/RelationshipNode.java | 1 - .../communications/VisualizationPanel.form | 82 ++----- .../communications/VisualizationPanel.java | 215 ++++++++++-------- 4 files changed, 138 insertions(+), 161 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties index abdbd65f69..71d89548f8 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties @@ -24,3 +24,4 @@ CVTTopComponent.vizPanel.TabConstraints.tabTitle_1=Visualize VisualizationPanel.jButton3.text=Orthogonal VisualizationPanel.jButton4.text=- VisualizationPanel.jButton5.text=+ +VisualizationPanel.jButton6.text=Hierarchy diff --git a/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java b/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java index 31f5871b81..0025455a51 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java @@ -143,5 +143,4 @@ public class RelationshipNode extends BlackboardArtifactNode { return ""; } } - } diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form index 1084d8f2da..8a2130752f 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form @@ -4,14 +4,14 @@ - + - + - + @@ -38,63 +38,17 @@ - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -109,6 +63,19 @@ + + + + + + + + + + + + + @@ -135,13 +102,6 @@ - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index f07eaa32f1..8f39aecc37 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.communications; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; import com.google.common.eventbus.Subscribe; +import com.mxgraph.layout.hierarchical.mxHierarchicalLayout; import com.mxgraph.layout.mxOrganicLayout; import com.mxgraph.layout.orthogonal.mxOrthogonalLayout; import com.mxgraph.model.mxCell; @@ -30,6 +31,9 @@ import com.mxgraph.view.mxGraph; import com.mxgraph.view.mxStylesheet; import java.awt.BorderLayout; import java.awt.Color; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; import java.beans.PropertyVetoException; import java.util.Arrays; import java.util.Collection; @@ -41,7 +45,11 @@ import java.util.Map; import java.util.Random; import java.util.Set; import java.util.logging.Level; +import javax.swing.JButton; import javax.swing.JPanel; +import javax.swing.JSplitPane; +import javax.swing.JToolBar; +import javax.swing.SwingConstants; import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerUtils; import org.openide.nodes.AbstractNode; @@ -49,6 +57,7 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.Exceptions; import org.openide.util.Lookup; +import org.openide.util.NbBundle; import org.openide.util.lookup.ProxyLookup; import org.sleuthkit.autopsy.casemodule.Case; import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE; @@ -119,13 +128,37 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graph.setCellsBendable(true); graph.setKeepEdgesInBackground(true); - graphComponent = new mxGraphComponent(graph); + graphComponent = new mxGraphComponent(graph){ + @Override + public boolean isPanningEvent(MouseEvent event) { + return true; //To change body of generated methods, choose Tools | Templates. + } + + }; graphComponent.setAutoScroll(true); graphComponent.setOpaque(true); graphComponent.setBackground(Color.WHITE); jPanel1.add(graphComponent, BorderLayout.CENTER); - graphComponent.getGraphControl().addMouseMotionListener(graphComponent.getPanningHandler()); +// graphComponent.getGraphControl().addMouseListener(new MouseAdapter() { +// @Override +// public void mouseReleased(MouseEvent e) { +// graphComponent.setPanning(false); +// } +// +// @Override +// public void mouseMoved(MouseEvent e) { +// super.mouseMoved(e); +// if (graphComponent.isPanning()) { +// graphComponent.getGraphControl().setTranslate(e.getPoint()); +// } +// } +// +// @Override +// public void mousePressed(MouseEvent e) { +// graphComponent.setPanning(true); +// } +// }); splitPane.setRightComponent(new MessageBrowser(vizEM, gacEM)); CVTEvents.getCVTEventBus().register(this); @@ -292,145 +325,124 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider // //GEN-BEGIN:initComponents private void initComponents() { - splitPane = new javax.swing.JSplitPane(); - jPanel1 = new javax.swing.JPanel(); - jPanel2 = new javax.swing.JPanel(); - jButton2 = new javax.swing.JButton(); - jButton1 = new javax.swing.JButton(); - jButton3 = new javax.swing.JButton(); - jSeparator1 = new javax.swing.JToolBar.Separator(); - jButton4 = new javax.swing.JButton(); - jButton5 = new javax.swing.JButton(); + splitPane = new JSplitPane(); + jPanel1 = new JPanel(); + jToolBar1 = new JToolBar(); + jButton2 = new JButton(); + jButton6 = new JButton(); + jButton1 = new JButton(); + jButton3 = new JButton(); + jButton4 = new JButton(); + jButton5 = new JButton(); - setLayout(new java.awt.BorderLayout()); + setLayout(new BorderLayout()); splitPane.setDividerLocation(400); splitPane.setResizeWeight(0.5); - jPanel1.setLayout(new java.awt.BorderLayout()); + jPanel1.setLayout(new BorderLayout()); - jButton2.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jButton2.text")); // NOI18N + jToolBar1.setRollover(true); + + jButton2.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jButton2.text")); // NOI18N jButton2.setFocusable(false); - jButton2.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); - jButton2.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); - jButton2.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { + jButton2.setHorizontalTextPosition(SwingConstants.CENTER); + jButton2.setVerticalTextPosition(SwingConstants.BOTTOM); + jButton2.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { jButton2ActionPerformed(evt); } }); + jToolBar1.add(jButton2); - jButton1.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jButton1.text")); // NOI18N + jButton6.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jButton6.text")); // NOI18N + jButton6.setFocusable(false); + jButton6.setHorizontalTextPosition(SwingConstants.CENTER); + jButton6.setVerticalTextPosition(SwingConstants.BOTTOM); + jButton6.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + jButton6ActionPerformed(evt); + } + }); + jToolBar1.add(jButton6); + + jButton1.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jButton1.text")); // NOI18N jButton1.setFocusable(false); - jButton1.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); - jButton1.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); - jButton1.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { + jButton1.setHorizontalTextPosition(SwingConstants.CENTER); + jButton1.setVerticalTextPosition(SwingConstants.BOTTOM); + jButton1.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { jButton1ActionPerformed(evt); } }); + jToolBar1.add(jButton1); - jButton3.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jButton3.text")); // NOI18N + jButton3.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jButton3.text")); // NOI18N jButton3.setFocusable(false); - jButton3.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); - jButton3.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); - jButton3.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { + jButton3.setHorizontalTextPosition(SwingConstants.CENTER); + jButton3.setVerticalTextPosition(SwingConstants.BOTTOM); + jButton3.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { jButton3ActionPerformed(evt); } }); + jToolBar1.add(jButton3); - jSeparator1.setPreferredSize(new java.awt.Dimension(10, 10)); - - jButton4.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jButton4.text")); // NOI18N + jButton4.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jButton4.text")); // NOI18N jButton4.setFocusable(false); - jButton4.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); - jButton4.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); - jButton4.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { + jButton4.setHorizontalTextPosition(SwingConstants.CENTER); + jButton4.setVerticalTextPosition(SwingConstants.BOTTOM); + jButton4.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { jButton4ActionPerformed(evt); } }); + jToolBar1.add(jButton4); - jButton5.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jButton5.text")); // NOI18N + jButton5.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jButton5.text")); // NOI18N jButton5.setFocusable(false); - jButton5.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); - jButton5.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); - jButton5.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { + jButton5.setHorizontalTextPosition(SwingConstants.CENTER); + jButton5.setVerticalTextPosition(SwingConstants.BOTTOM); + jButton5.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { jButton5ActionPerformed(evt); } }); + jToolBar1.add(jButton5); - javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2); - jPanel2.setLayout(jPanel2Layout); - jPanel2Layout.setHorizontalGroup( - jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanel2Layout.createSequentialGroup() - .addContainerGap() - .addComponent(jButton2) - .addGap(5, 5, 5) - .addComponent(jButton1) - .addGap(5, 5, 5) - .addComponent(jButton3) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 1, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jButton4) - .addGap(5, 5, 5) - .addComponent(jButton5) - .addContainerGap()) - ); - jPanel2Layout.setVerticalGroup( - jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanel2Layout.createSequentialGroup() - .addGap(5, 5, 5) - .addComponent(jButton2)) - .addGroup(jPanel2Layout.createSequentialGroup() - .addGap(5, 5, 5) - .addComponent(jButton1)) - .addGroup(jPanel2Layout.createSequentialGroup() - .addGap(5, 5, 5) - .addComponent(jButton3)) - .addGroup(jPanel2Layout.createSequentialGroup() - .addGap(14, 14, 14) - .addComponent(jSeparator1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGap(10, 10, 10)) - .addGroup(jPanel2Layout.createSequentialGroup() - .addGap(5, 5, 5) - .addComponent(jButton4)) - .addGroup(jPanel2Layout.createSequentialGroup() - .addGap(5, 5, 5) - .addComponent(jButton5)) - ); - - jPanel1.add(jPanel2, java.awt.BorderLayout.NORTH); + jPanel1.add(jToolBar1, BorderLayout.NORTH); splitPane.setLeftComponent(jPanel1); - add(splitPane, java.awt.BorderLayout.CENTER); + add(splitPane, BorderLayout.CENTER); }// //GEN-END:initComponents - private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton2ActionPerformed + private void jButton2ActionPerformed(ActionEvent evt) {//GEN-FIRST:event_jButton2ActionPerformed // graphComponent.addMouseListener(new mxPanningHandler(graphComponent)); - graphComponent.getPanningHandler().setEnabled(true); + graphComponent.setPanning(true); }//GEN-LAST:event_jButton2ActionPerformed - private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed + private void jButton1ActionPerformed(ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed applyOrganicLayout(); }//GEN-LAST:event_jButton1ActionPerformed - private void jButton3ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton3ActionPerformed + private void jButton3ActionPerformed(ActionEvent evt) {//GEN-FIRST:event_jButton3ActionPerformed + applyOrthogonalLayout(); }//GEN-LAST:event_jButton3ActionPerformed - private void jButton5ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton5ActionPerformed + private void jButton5ActionPerformed(ActionEvent evt) {//GEN-FIRST:event_jButton5ActionPerformed graphComponent.zoomIn(); }//GEN-LAST:event_jButton5ActionPerformed - private void jButton4ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton4ActionPerformed + private void jButton4ActionPerformed(ActionEvent evt) {//GEN-FIRST:event_jButton4ActionPerformed graphComponent.zoomOut(); }//GEN-LAST:event_jButton4ActionPerformed + private void jButton6ActionPerformed(ActionEvent evt) {//GEN-FIRST:event_jButton6ActionPerformed + applyHierarchicalLayout(); + }//GEN-LAST:event_jButton6ActionPerformed + private void applyOrganicLayout() { new mxOrganicLayout(graph).execute(graph.getDefaultParent()); graphComponent.zoomAndCenter(); @@ -441,17 +453,22 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graphComponent.zoomAndCenter(); } + private void applyHierarchicalLayout() { + new mxHierarchicalLayout(graph).execute(graph.getDefaultParent()); + graphComponent.zoomAndCenter(); + } + // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton jButton1; - private javax.swing.JButton jButton2; - private javax.swing.JButton jButton3; - private javax.swing.JButton jButton4; - private javax.swing.JButton jButton5; - private javax.swing.JPanel jPanel1; - private javax.swing.JPanel jPanel2; - private javax.swing.JToolBar.Separator jSeparator1; - private javax.swing.JSplitPane splitPane; + private JButton jButton1; + private JButton jButton2; + private JButton jButton3; + private JButton jButton4; + private JButton jButton5; + private JButton jButton6; + private JPanel jPanel1; + private JToolBar jToolBar1; + private JSplitPane splitPane; // End of variables declaration//GEN-END:variables private static class SimpleParentNode extends AbstractNode { From 64aa3bb929bdcb8ba4d2b25ba70065d7fa17c367 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Thu, 11 Jan 2018 17:17:37 +0100 Subject: [PATCH 04/11] first pass at pinning from the VisualizationPanel --- .../communications/VisualizationPanel.java | 87 ++++++++++++------- 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index 7563e97a29..2dd1ecbaff 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -25,19 +25,22 @@ import com.mxgraph.layout.hierarchical.mxHierarchicalLayout; import com.mxgraph.layout.mxOrganicLayout; import com.mxgraph.layout.orthogonal.mxOrthogonalLayout; import com.mxgraph.model.mxCell; +import com.mxgraph.model.mxICell; import com.mxgraph.swing.mxGraphComponent; import com.mxgraph.util.mxConstants; +import com.mxgraph.util.mxRectangle; import com.mxgraph.view.mxGraph; -import com.mxgraph.view.mxGraphView; import com.mxgraph.view.mxStylesheet; import java.awt.BorderLayout; import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.beans.PropertyVetoException; import java.util.Arrays; import java.util.Collection; +import static java.util.Collections.singleton; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; @@ -46,11 +49,15 @@ import java.util.Map; import java.util.Random; import java.util.Set; import java.util.logging.Level; +import javax.swing.AbstractAction; import javax.swing.JButton; +import javax.swing.JMenuItem; import javax.swing.JPanel; +import javax.swing.JPopupMenu; import javax.swing.JSplitPane; import javax.swing.JToolBar; import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerUtils; import org.openide.nodes.AbstractNode; @@ -128,39 +135,45 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graph.setCellsDeletable(false); graph.setCellsBendable(true); graph.setKeepEdgesInBackground(true); - - graphComponent = new mxGraphComponent(graph){ + graphComponent = new mxGraphComponent(graph) { @Override public boolean isPanningEvent(MouseEvent event) { - return true; //To change body of generated methods, choose Tools | Templates. + return true; } - + }; + + graphComponent.setAutoScroll(true); graphComponent.setOpaque(true); graphComponent.setBackground(Color.WHITE); jPanel1.add(graphComponent, BorderLayout.CENTER); -// graphComponent.getGraphControl().addMouseListener(new MouseAdapter() { -// @Override -// public void mouseReleased(MouseEvent e) { -// graphComponent.setPanning(false); -// } -// -// @Override -// public void mouseMoved(MouseEvent e) { -// super.mouseMoved(e); -// if (graphComponent.isPanning()) { -// graphComponent.getGraphControl().setTranslate(e.getPoint()); -// } -// } -// -// @Override -// public void mousePressed(MouseEvent e) { -// graphComponent.setPanning(true); -// } -// }); + graphComponent.getGraphControl().addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + super.mouseClicked(e); + if (SwingUtilities.isRightMouseButton(e)) { + mxICell cellAt = (mxICell) graphComponent.getCellAt(e.getX(), e.getY()); + if (cellAt != null && cellAt.isVertex()) { + JPopupMenu jPopupMenu = new JPopupMenu(); + jPopupMenu.add(new JMenuItem() { + { + setAction(new AbstractAction("Pin Account " + cellAt.getId()) { + @Override + public void actionPerformed(ActionEvent e) { + pinAccounts(new PinAccountEvent(singleton((AccountDeviceInstanceKey) cellAt.getValue()))); + } + }); + } + }); + + jPopupMenu.show(graphComponent.getGraphControl(), e.getX(), e.getY()); + } + } + } + }); splitPane.setRightComponent(new MessageBrowser(vizEM, gacEM)); CVTEvents.getCVTEventBus().register(this); @@ -446,23 +459,31 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider }//GEN-LAST:event_jButton6ActionPerformed private void applyOrganicLayout() { + graph.setMaximumGraphBounds(new mxRectangle(0, 0, graphComponent.getWidth(), + graphComponent.getHeight())); new mxOrganicLayout(graph).execute(graph.getDefaultParent()); - mxGraphView view = graphComponent.getGraph().getView(); - int compLen = graphComponent.getWidth(); - int viewLen = (int) view.getGraphBounds().getWidth(); - view.setScale((double) compLen / viewLen * view.getScale()); - graphComponent.zoomAndCenter(); + fitGraph(); } - private void applyOrthogonalLayout() { + private void fitGraph() { +// mxGraphView view = graphComponent.getGraph().getView(); +// int compLen = graphComponent.getWidth(); +// int viewLen = (int) view.getGraphBounds().getWidth(); +// view.setScale((double) compLen / viewLen * view.getScale()); + + } + + private void applyOrthogonalLayout() { graph.setMaximumGraphBounds(new mxRectangle(0, 0, graphComponent.getWidth(), + graphComponent.getHeight())); new mxOrthogonalLayout(graph).execute(graph.getDefaultParent()); - graphComponent.zoomAndCenter(); + fitGraph(); } - private void applyHierarchicalLayout() { + private void applyHierarchicalLayout() { graph.setMaximumGraphBounds(new mxRectangle(0, 0, graphComponent.getWidth(), + graphComponent.getHeight())); new mxHierarchicalLayout(graph).execute(graph.getDefaultParent()); - graphComponent.zoomAndCenter(); + fitGraph(); } From ed19487607b320a0f272eb9f9fc5f66dcdbfcfab Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 12 Jan 2018 13:11:08 +0100 Subject: [PATCH 05/11] distinguish pin events that should replace the pinned nodes, and those that should append to them. --- .../AccountDeviceInstanceNode.java | 7 ++- .../autopsy/communications/CVTEvents.java | 20 +++++++ .../communications/CVTTopComponent.java | 3 +- .../communications/PinAccountEvent.java | 25 -------- .../communications/VisualizationPanel.java | 54 ++++++++++-------- .../images/icons8-neural-network.png | Bin 0 -> 1046 bytes 6 files changed, 58 insertions(+), 51 deletions(-) delete mode 100644 Core/src/org/sleuthkit/autopsy/communications/PinAccountEvent.java create mode 100644 Core/src/org/sleuthkit/autopsy/communications/images/icons8-neural-network.png diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java index 5b88b01999..6b60d75935 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java @@ -24,6 +24,7 @@ import java.util.Arrays; import java.util.Collection; import javax.swing.AbstractAction; import javax.swing.Action; +import javax.swing.ImageIcon; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.nodes.Sheet; @@ -113,20 +114,22 @@ final class AccountDeviceInstanceNode extends AbstractNode { private static final long serialVersionUID = 1L; private static PinAccountsAction instance = new PinAccountsAction(); + static final private ImageIcon imageIcon = + new ImageIcon("images/icons8-neural-network.png"); private static PinAccountsAction getInstance() { return instance; } private PinAccountsAction() { - super("Visualize Account"); + super("Visualize Account", imageIcon); } @Override public void actionPerformed(ActionEvent e) { Collection lookupAll = Utilities.actionsGlobalContext().lookupAll(AccountDeviceInstanceKey.class); - CVTEvents.getCVTEventBus().post(new PinAccountEvent(lookupAll)); + CVTEvents.getCVTEventBus().post(new CVTEvents.PinAccountsEvent(lookupAll, true)); } } } diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTEvents.java b/Core/src/org/sleuthkit/autopsy/communications/CVTEvents.java index 663b711c99..6c0001fd21 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/CVTEvents.java +++ b/Core/src/org/sleuthkit/autopsy/communications/CVTEvents.java @@ -18,7 +18,9 @@ */ package org.sleuthkit.autopsy.communications; +import com.google.common.collect.ImmutableSet; import com.google.common.eventbus.EventBus; +import java.util.Collection; /** * Provide the singleton EventBus. @@ -34,4 +36,22 @@ final class CVTEvents { private CVTEvents() { } + static final class PinAccountsEvent { + + private final ImmutableSet accountDeviceInstances; + private final boolean replace; + + public boolean isReplace() { + return replace; + } + + ImmutableSet getAccountDeviceInstances() { + return accountDeviceInstances; + } + + PinAccountsEvent(Collection accountDeviceInstances, boolean replace) { + this.accountDeviceInstances = ImmutableSet.copyOf(accountDeviceInstances); + this.replace = replace; + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java index d19ad25496..a7c55ab631 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java @@ -75,10 +75,11 @@ public final class CVTTopComponent extends TopComponent { } @Subscribe - public void pinAccount(PinAccountEvent pinEvent) { + public void pinAccount(CVTEvents.PinAccountsEvent pinEvent) { browseVisualizeTabPane.setSelectedIndex(1); } + /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always diff --git a/Core/src/org/sleuthkit/autopsy/communications/PinAccountEvent.java b/Core/src/org/sleuthkit/autopsy/communications/PinAccountEvent.java deleted file mode 100644 index c8ba063adb..0000000000 --- a/Core/src/org/sleuthkit/autopsy/communications/PinAccountEvent.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.sleuthkit.autopsy.communications; - -import com.google.common.collect.ImmutableSet; -import java.util.Collection; - -/** - * - */ -final class PinAccountEvent { - - private final ImmutableSet accountDeviceInstances; - - ImmutableSet getAccountDeviceInstances() { - return accountDeviceInstances; - } - - PinAccountEvent(Collection accountDeviceInstances) { - this.accountDeviceInstances = ImmutableSet.copyOf(accountDeviceInstances); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index 2dd1ecbaff..39fc676f80 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -50,6 +50,7 @@ import java.util.Random; import java.util.Set; import java.util.logging.Level; import javax.swing.AbstractAction; +import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JMenuItem; import javax.swing.JPanel; @@ -88,7 +89,10 @@ import org.sleuthkit.datamodel.TskCoreException; final public class VisualizationPanel extends JPanel implements Lookup.Provider { private static final long serialVersionUID = 1L; - private Logger logger = Logger.getLogger(VisualizationPanel.class.getName()); + private static final Logger logger = Logger.getLogger(VisualizationPanel.class.getName()); + + static final private ImageIcon imageIcon = + new ImageIcon("images/icons8-neural-network.png"); static final private mxStylesheet mxStylesheet = new mxStylesheet(); @@ -143,7 +147,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider }; - + graphComponent.setAutoExtend(true); graphComponent.setAutoScroll(true); graphComponent.setOpaque(true); @@ -158,12 +162,12 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider mxICell cellAt = (mxICell) graphComponent.getCellAt(e.getX(), e.getY()); if (cellAt != null && cellAt.isVertex()) { JPopupMenu jPopupMenu = new JPopupMenu(); - jPopupMenu.add(new JMenuItem() { + jPopupMenu.add(new JMenuItem(imageIcon) { { setAction(new AbstractAction("Pin Account " + cellAt.getId()) { @Override public void actionPerformed(ActionEvent e) { - pinAccounts(new PinAccountEvent(singleton((AccountDeviceInstanceKey) cellAt.getValue()))); + pinAccounts(new CVTEvents.PinAccountsEvent(singleton((AccountDeviceInstanceKey) cellAt.getValue()), false)); } }); } @@ -215,20 +219,18 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider final AccountDeviceInstance accountDeviceInstance = accountDeviceInstanceKey.getAccountDeviceInstance(); final String name =// accountDeviceInstance.getDeviceId() + ":" + accountDeviceInstance.getAccount().getTypeSpecificID(); - mxCell vertex = nodeMap.get(name); - if (vertex == null) { + return nodeMap.computeIfAbsent(name, vertexName -> { double size = Math.sqrt(accountDeviceInstanceKey.getMessageCount()) + 10; - vertex = (mxCell) graph.insertVertex( + mxCell vertex = (mxCell) graph.insertVertex( graph.getDefaultParent(), - name, accountDeviceInstanceKey, + vertexName, accountDeviceInstanceKey, new Random().nextInt(200), new Random().nextInt(200), size, size); - graph.getView().getState(vertex, true).setLabel(name); - nodeMap.put(name, vertex); - } - return vertex; + graph.getView().getState(vertex, true).setLabel(vertexName); + return vertex; + }); } @SuppressWarnings("unchecked") @@ -246,17 +248,17 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } @Subscribe - public void pinAccounts(PinAccountEvent pinEvent) { + public void pinAccounts(CVTEvents.PinAccountsEvent pinEvent) { final Set adiKeys = pinEvent.getAccountDeviceInstances(); final CommunicationsFilter commsFilter = filterProvider.getFilter(); graph.getModel().beginUpdate(); try { - nodeMap.clear(); - edgeMap.clear(); - graph.removeCells(graph.getChildCells(graph.getDefaultParent(), true, true)); - + if (pinEvent.isReplace()) { + clearGraph(); + } else { + } for (AccountDeviceInstanceKey adiKey : adiKeys) { mxCell pinnedAccountVertex = getOrCreateVertex(adiKey); @@ -288,6 +290,12 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider revalidate(); } + private void clearGraph() { + nodeMap.clear(); + edgeMap.clear(); + graph.removeCells(graph.getChildCells(graph.getDefaultParent(), true, true)); + } + @Override public void addNotify() { super.addNotify(); @@ -305,9 +313,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider Case.addEventTypeSubscriber(EnumSet.of(CURRENT_CASE), evt -> { graph.getModel().beginUpdate(); try { - nodeMap.clear(); - edgeMap.clear(); - graph.removeCells(graph.getChildCells(graph.getDefaultParent(), true, true)); + clearGraph(); } finally { graph.getModel().endUpdate(); } @@ -459,7 +465,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider }//GEN-LAST:event_jButton6ActionPerformed private void applyOrganicLayout() { - graph.setMaximumGraphBounds(new mxRectangle(0, 0, graphComponent.getWidth(), + graph.setMaximumGraphBounds(new mxRectangle(0, 0, graphComponent.getWidth(), graphComponent.getHeight())); new mxOrganicLayout(graph).execute(graph.getDefaultParent()); @@ -474,13 +480,15 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } - private void applyOrthogonalLayout() { graph.setMaximumGraphBounds(new mxRectangle(0, 0, graphComponent.getWidth(), + private void applyOrthogonalLayout() { + graph.setMaximumGraphBounds(new mxRectangle(0, 0, graphComponent.getWidth(), graphComponent.getHeight())); new mxOrthogonalLayout(graph).execute(graph.getDefaultParent()); fitGraph(); } - private void applyHierarchicalLayout() { graph.setMaximumGraphBounds(new mxRectangle(0, 0, graphComponent.getWidth(), + private void applyHierarchicalLayout() { + graph.setMaximumGraphBounds(new mxRectangle(0, 0, graphComponent.getWidth(), graphComponent.getHeight())); new mxHierarchicalLayout(graph).execute(graph.getDefaultParent()); fitGraph(); diff --git a/Core/src/org/sleuthkit/autopsy/communications/images/icons8-neural-network.png b/Core/src/org/sleuthkit/autopsy/communications/images/icons8-neural-network.png new file mode 100644 index 0000000000000000000000000000000000000000..331be052314b9451e92d0378ce954e9a4ea56e4c GIT binary patch literal 1046 zcmV+x1nK*UP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1FK0yK~z{r#g|P? z6HyR=F?uk1(3?L|6Au_K8gC|g;6i^ev<*K6^dLs?00%+68I>4*T1va6g##*r33?$) zqQMv~2}av)yKf8SPeBv^BD9qf3Q3JbrM_8aYpHE1hJ`PgWZ%y0%-c7!@6Bec1-Ui+ zwwwm{p{$zzEqH7F&^8nEnbv{nOAp=zv{pV)z>rc@NlzZ*F2B+9e(uBUV&r3JF)|+f z6)C$j6jU16qi7;6B~}kNIQ2saF3RJfnMk3PG@*&KZ1@E7n@L%G3eMm526J@IBWNN$ zS_?6jv<`%;?~jFO5&H8?p-@Q0Sdry!?N%%>6s%jgDN9Y(2jN zZ?8ypqkXSj=j_E{Qd0WY7`YS0R*#Q){%IZruHhc4Q=-thGzvX?6Nd>&?LQLPRA}M9 z3{L+2oZ!01LOz3T$tXDx943Loe_6@DtjTq-x@*iYDn+^2H5vdNx><(2mfNpp_T#nSXfnFaRKnkusnwU!~1eV5BhAVx3iGXyZ&U;42A1LX5BaV1i QRR91007*qoM6N<$g5AQ^PXGV_ literal 0 HcmV?d00001 From 330d9ad4f6f95b7da1400b84b3a9ef0bbacd6c63 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 12 Jan 2018 15:49:05 +0100 Subject: [PATCH 06/11] simplify panning (use default ctrl+shift + drag) reinstate fiting in pinning, sore pinned nodes. --- .../communications/VisualizationPanel.java | 51 ++++++++----------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index 39fc676f80..bc686fcd47 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -22,7 +22,7 @@ import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; import com.google.common.eventbus.Subscribe; import com.mxgraph.layout.hierarchical.mxHierarchicalLayout; -import com.mxgraph.layout.mxOrganicLayout; +import com.mxgraph.layout.mxFastOrganicLayout; import com.mxgraph.layout.orthogonal.mxOrthogonalLayout; import com.mxgraph.model.mxCell; import com.mxgraph.model.mxICell; @@ -30,6 +30,7 @@ import com.mxgraph.swing.mxGraphComponent; import com.mxgraph.util.mxConstants; import com.mxgraph.util.mxRectangle; import com.mxgraph.view.mxGraph; +import com.mxgraph.view.mxGraphView; import com.mxgraph.view.mxStylesheet; import java.awt.BorderLayout; import java.awt.Color; @@ -46,7 +47,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Random; import java.util.Set; import java.util.logging.Level; import javax.swing.AbstractAction; @@ -115,6 +115,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private final Multimap edgeMap = MultimapBuilder.hashKeys().hashSetValues().build(); private CommunicationsManager commsManager; + private final HashSet pinnedAccountDevices = new HashSet<>(); void setFilterProvider(FilterProvider filterProvider) { this.filterProvider = filterProvider; @@ -139,17 +140,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graph.setCellsDeletable(false); graph.setCellsBendable(true); graph.setKeepEdgesInBackground(true); - graphComponent = new mxGraphComponent(graph) { - @Override - public boolean isPanningEvent(MouseEvent event) { - return true; - } - - }; - - graphComponent.setAutoExtend(true); - graphComponent.setAutoScroll(true); - + graphComponent = new mxGraphComponent(graph); + graphComponent.setConnectable(false); + graphComponent.setKeepSelectionVisibleOnZoom(true); graphComponent.setOpaque(true); graphComponent.setBackground(Color.WHITE); jPanel1.add(graphComponent, BorderLayout.CENTER); @@ -164,7 +157,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider JPopupMenu jPopupMenu = new JPopupMenu(); jPopupMenu.add(new JMenuItem(imageIcon) { { - setAction(new AbstractAction("Pin Account " + cellAt.getId()) { + setAction(new AbstractAction("Pin Account " + graph.getLabel(cellAt)) { @Override public void actionPerformed(ActionEvent e) { pinAccounts(new CVTEvents.PinAccountsEvent(singleton((AccountDeviceInstanceKey) cellAt.getValue()), false)); @@ -219,18 +212,20 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider final AccountDeviceInstance accountDeviceInstance = accountDeviceInstanceKey.getAccountDeviceInstance(); final String name =// accountDeviceInstance.getDeviceId() + ":" + accountDeviceInstance.getAccount().getTypeSpecificID(); - return nodeMap.computeIfAbsent(name, vertexName -> { + final mxCell computeIfAbsent = nodeMap.computeIfAbsent(name, vertexName -> { double size = Math.sqrt(accountDeviceInstanceKey.getMessageCount()) + 10; + mxCell vertex = (mxCell) graph.insertVertex( graph.getDefaultParent(), vertexName, accountDeviceInstanceKey, - new Random().nextInt(200), - new Random().nextInt(200), + 0, + 0, size, size); graph.getView().getState(vertex, true).setLabel(vertexName); return vertex; }); + return computeIfAbsent; } @SuppressWarnings("unchecked") @@ -250,28 +245,27 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider @Subscribe public void pinAccounts(CVTEvents.PinAccountsEvent pinEvent) { - final Set adiKeys = pinEvent.getAccountDeviceInstances(); final CommunicationsFilter commsFilter = filterProvider.getFilter(); graph.getModel().beginUpdate(); try { if (pinEvent.isReplace()) { + pinnedAccountDevices.clear(); clearGraph(); - } else { } - for (AccountDeviceInstanceKey adiKey : adiKeys) { + pinnedAccountDevices.addAll(pinEvent.getAccountDeviceInstances()); + for (AccountDeviceInstanceKey adiKey : pinnedAccountDevices) { mxCell pinnedAccountVertex = getOrCreateVertex(adiKey); List relatedAccountDeviceInstances = commsManager.getRelatedAccountDeviceInstances(adiKey.getAccountDeviceInstance(), commsFilter); for (AccountDeviceInstance relatedADI : relatedAccountDeviceInstances) { - + long adiRelationshipsCount = commsManager.getRelationshipSourcesCount(relatedADI, commsFilter); List relationships = commsManager.getRelationships(adiKey.getAccountDeviceInstance(), relatedADI, commsFilter); - long communicationsCount = relationships.size(); AccountDeviceInstanceKey relatedADIKey = - new AccountDeviceInstanceKey(relatedADI, commsFilter, communicationsCount); + new AccountDeviceInstanceKey(relatedADI, commsFilter, adiRelationshipsCount); mxCell relatedAccountVertex = getOrCreateVertex(relatedADIKey); for (BlackboardArtifact relationship : relationships) { addEdge(relationship, pinnedAccountVertex, relatedAccountVertex); @@ -467,17 +461,16 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private void applyOrganicLayout() { graph.setMaximumGraphBounds(new mxRectangle(0, 0, graphComponent.getWidth(), graphComponent.getHeight())); - new mxOrganicLayout(graph).execute(graph.getDefaultParent()); + new mxFastOrganicLayout(graph).execute(graph.getDefaultParent()); fitGraph(); } private void fitGraph() { -// mxGraphView view = graphComponent.getGraph().getView(); -// int compLen = graphComponent.getWidth(); -// int viewLen = (int) view.getGraphBounds().getWidth(); -// view.setScale((double) compLen / viewLen * view.getScale()); - + mxGraphView view = graphComponent.getGraph().getView(); + int compLen = graphComponent.getWidth(); + int viewLen = (int) view.getGraphBounds().getWidth(); + view.setScale((double) compLen / viewLen * view.getScale()); } private void applyOrthogonalLayout() { From 79194a2758a796901e2582d78ea63a9475da5534 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 12 Jan 2018 16:25:11 +0100 Subject: [PATCH 07/11] wire filters via the Eventbus instead of by passing a reference to the ExplorerManager --- .../communications/AccountsBrowser.java | 31 ++++++-- .../autopsy/communications/CVTEvents.java | 15 ++++ .../communications/CVTTopComponent.java | 22 ++---- .../communications/FilterProvider.java | 26 ------- .../autopsy/communications/FiltersPanel.java | 37 ++------- .../communications/VisualizationPanel.java | 75 +++++++++++-------- 6 files changed, 96 insertions(+), 110 deletions(-) delete mode 100644 Core/src/org/sleuthkit/autopsy/communications/FilterProvider.java diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java index 828d654298..b1d705f6e7 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java @@ -18,7 +18,9 @@ */ package org.sleuthkit.autopsy.communications; +import com.google.common.eventbus.Subscribe; import java.awt.Component; +import java.util.logging.Level; import javax.swing.JPanel; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; @@ -27,8 +29,14 @@ import org.netbeans.swing.outline.DefaultOutlineModel; import org.netbeans.swing.outline.Outline; import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerUtils; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; import org.openide.util.Lookup; import org.openide.util.lookup.ProxyLookup; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.CommunicationsManager; +import org.sleuthkit.datamodel.TskCoreException; /** * A panel that goes in the Browse tab of the Communications Visualization Tool. @@ -42,17 +50,18 @@ import org.openide.util.lookup.ProxyLookup; public final class AccountsBrowser extends JPanel implements ExplorerManager.Provider, Lookup.Provider { private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(AccountsBrowser.class.getName()); private final Outline outline; private final ExplorerManager messageBrowserEM = new ExplorerManager(); - private ExplorerManager accountsTableEM; + private final ExplorerManager accountsTableEM = new ExplorerManager(); /* * This lookup proxies the selection lookup of both he accounts table and * the messages table. */ - private ProxyLookup proxyLookup; + private final ProxyLookup proxyLookup; public AccountsBrowser() { initComponents(); @@ -68,11 +77,7 @@ public final class AccountsBrowser extends JPanel implements ExplorerManager.Pro outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); outline.setColumnSorted(3, false, 1); //it would be nice if the column index wasn't hardcoded - } - - void init(ExplorerManager tableExplorerManager) { - this.accountsTableEM = tableExplorerManager; - tableExplorerManager.addPropertyChangeListener(evt -> { + accountsTableEM.addPropertyChangeListener(evt -> { if (ExplorerManager.PROP_ROOT_CONTEXT.equals(evt.getPropertyName())) { SwingUtilities.invokeLater(this::setColumnWidths); } else if (ExplorerManager.PROP_EXPLORED_CONTEXT.equals(evt.getPropertyName())) { @@ -80,7 +85,7 @@ public final class AccountsBrowser extends JPanel implements ExplorerManager.Pro } }); - jSplitPane1.setRightComponent(new MessageBrowser(tableExplorerManager, messageBrowserEM)); + jSplitPane1.setRightComponent(new MessageBrowser(accountsTableEM, messageBrowserEM)); proxyLookup = new ProxyLookup( ExplorerUtils.createLookup(messageBrowserEM, getActionMap()), @@ -111,6 +116,16 @@ public final class AccountsBrowser extends JPanel implements ExplorerManager.Pro } } + @Subscribe + public void handleFilterEvent(CVTEvents.FilterChangeEvent filterChangeEvent) { + try { + final CommunicationsManager commsManager = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager(); + accountsTableEM.setRootContext(new AbstractNode(Children.create(new AccountDeviceInstanceNodeFactory(commsManager, filterChangeEvent.getNewFilter()), true))); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "There was an error getting the CommunicationsManager for the current case.", ex); + } + } + /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTEvents.java b/Core/src/org/sleuthkit/autopsy/communications/CVTEvents.java index 6c0001fd21..f13128f5c3 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/CVTEvents.java +++ b/Core/src/org/sleuthkit/autopsy/communications/CVTEvents.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.communications; import com.google.common.collect.ImmutableSet; import com.google.common.eventbus.EventBus; import java.util.Collection; +import org.sleuthkit.datamodel.CommunicationsFilter; /** * Provide the singleton EventBus. @@ -36,6 +37,20 @@ final class CVTEvents { private CVTEvents() { } + static final class FilterChangeEvent { + + private final CommunicationsFilter newFilter; + + CommunicationsFilter getNewFilter() { + return newFilter; + } + + FilterChangeEvent(CommunicationsFilter newFilter) { + this.newFilter = newFilter; + } + + } + static final class PinAccountsEvent { private final ImmutableSet accountDeviceInstances; diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java index a7c55ab631..39361de47e 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.communications; import com.google.common.eventbus.Subscribe; import java.util.List; import java.util.stream.Collectors; -import org.openide.explorer.ExplorerManager; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.lookup.ProxyLookup; @@ -42,22 +41,13 @@ public final class CVTTopComponent extends TopComponent { private static final long serialVersionUID = 1L; - private final ExplorerManager filterToTableEXplorerManager = new ExplorerManager(); - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) public CVTTopComponent() { initComponents(); setName(Bundle.CVTTopComponent_name()); - /* - * Connect the filtersPane and the accountsBrowser via a shared - * ExplorerMmanager - */ - filtersPane.setExplorerManager(filterToTableEXplorerManager); - accountsBrowser.init(filterToTableEXplorerManager); - /* - * Associate an Lookup with the GlobalActionContext (GAC) so that + * Associate a Lookup with the GlobalActionContext (GAC) so that * selections in the sub views can be exposed to context-sensitive * actions. */ @@ -69,9 +59,14 @@ public final class CVTTopComponent extends TopComponent { proxyLookup.changeLookups(selectedComponent.getLookup()); }); - vizPanel.setFilterProvider(filtersPane); - CVTEvents.getCVTEventBus().register(this); + /* + * Connect the filtersPane to the accountsBrowser and visualizaionPanel + * via an Eventbus + */ + CVTEvents.getCVTEventBus().register(this); + CVTEvents.getCVTEventBus().register(vizPanel); + CVTEvents.getCVTEventBus().register(accountsBrowser); } @Subscribe @@ -79,7 +74,6 @@ public final class CVTTopComponent extends TopComponent { browseVisualizeTabPane.setSelectedIndex(1); } - /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always diff --git a/Core/src/org/sleuthkit/autopsy/communications/FilterProvider.java b/Core/src/org/sleuthkit/autopsy/communications/FilterProvider.java deleted file mode 100644 index 54f48f9dd1..0000000000 --- a/Core/src/org/sleuthkit/autopsy/communications/FilterProvider.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2018 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.communications; - -import org.sleuthkit.datamodel.CommunicationsFilter; - -interface FilterProvider { - - CommunicationsFilter getFilter(); -} diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java index 24e589290c..36232eaf15 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java @@ -31,9 +31,6 @@ import java.util.logging.Level; import java.util.stream.Collectors; import javax.swing.JCheckBox; import javax.swing.JPanel; -import org.openide.explorer.ExplorerManager; -import org.openide.nodes.AbstractNode; -import org.openide.nodes.Children; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE; @@ -49,7 +46,6 @@ import org.sleuthkit.datamodel.CommunicationsFilter; import org.sleuthkit.datamodel.CommunicationsFilter.AccountTypeFilter; import org.sleuthkit.datamodel.CommunicationsFilter.DateRangeFilter; import org.sleuthkit.datamodel.CommunicationsFilter.DeviceFilter; -import org.sleuthkit.datamodel.CommunicationsManager; import org.sleuthkit.datamodel.DataSource; import static org.sleuthkit.datamodel.Relationship.Type.CALL_LOG; import static org.sleuthkit.datamodel.Relationship.Type.MESSAGE; @@ -60,13 +56,11 @@ import org.sleuthkit.datamodel.TskCoreException; * Panel that holds the Filter control widgets and triggers queries against the * CommunicationsManager on user filtering changes. */ -final public class FiltersPanel extends JPanel implements FilterProvider { +final public class FiltersPanel extends JPanel { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(FiltersPanel.class.getName()); - private ExplorerManager em; - @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private final Map accountTypeMap = new HashMap<>(); @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @@ -134,10 +128,6 @@ final public class FiltersPanel extends JPanel implements FilterProvider { refreshButton.addActionListener(e -> applyFilters()); } - void setExplorerManager(ExplorerManager explorerManager) { - em = explorerManager; - } - /** * Validate that filters are in a consistent state and will result in some * results. Checks that at least one device and at least one account type is @@ -161,9 +151,7 @@ final public class FiltersPanel extends JPanel implements FilterProvider { */ void updateAndApplyFilters() { updateFilters(); - if (em != null) { - applyFilters(); - } + applyFilters(); } private void updateTimeZone() { @@ -224,8 +212,7 @@ final public class FiltersPanel extends JPanel implements FilterProvider { return jCheckBox; }); } - } - ); + }); } /** @@ -490,27 +477,15 @@ final public class FiltersPanel extends JPanel implements FilterProvider { }// //GEN-END:initComponents /** - * Push a new root AccountDeviceInstanceNodeFactory with he current filters - * into the explorer manager. The factory will do he actual queries. - * - * + * Post an event with the new filters. */ private void applyFilters() { - CommunicationsFilter commsFilter = getFilter(); - - try { - final CommunicationsManager commsManager = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager(); - em.setRootContext(new AbstractNode(Children.create(new AccountDeviceInstanceNodeFactory(commsManager, commsFilter), true))); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "There was an error getting the CommunicationsManager for the current case.", ex); - } - + CVTEvents.getCVTEventBus().post(new CVTEvents.FilterChangeEvent(getFilter())); needsRefresh = false; validateFilters(); } - @Override - public CommunicationsFilter getFilter() { + private CommunicationsFilter getFilter() { CommunicationsFilter commsFilter = new CommunicationsFilter(); commsFilter.addAndFilter(getDeviceFilter()); commsFilter.addAndFilter(getAccountTypeFilter()); diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index bc686fcd47..d202b54ee8 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -64,7 +64,6 @@ import org.openide.explorer.ExplorerUtils; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.nodes.Node; -import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.lookup.ProxyLookup; @@ -116,11 +115,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private CommunicationsManager commsManager; private final HashSet pinnedAccountDevices = new HashSet<>(); - - void setFilterProvider(FilterProvider filterProvider) { - this.filterProvider = filterProvider; - } - private FilterProvider filterProvider; + private CommunicationsFilter currentFilter; public VisualizationPanel() { initComponents(); @@ -160,7 +155,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider setAction(new AbstractAction("Pin Account " + graph.getLabel(cellAt)) { @Override public void actionPerformed(ActionEvent e) { - pinAccounts(new CVTEvents.PinAccountsEvent(singleton((AccountDeviceInstanceKey) cellAt.getValue()), false)); + handlePinEvent(new CVTEvents.PinAccountsEvent(singleton((AccountDeviceInstanceKey) cellAt.getValue()), false)); } }); } @@ -173,7 +168,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider }); splitPane.setRightComponent(new MessageBrowser(vizEM, gacEM)); - CVTEvents.getCVTEventBus().register(this); graph.setStylesheet(mxStylesheet); @@ -243,9 +237,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } @Subscribe - public void pinAccounts(CVTEvents.PinAccountsEvent pinEvent) { - - final CommunicationsFilter commsFilter = filterProvider.getFilter(); + public void handlePinEvent(CVTEvents.PinAccountsEvent pinEvent) { graph.getModel().beginUpdate(); try { @@ -254,36 +246,57 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider clearGraph(); } pinnedAccountDevices.addAll(pinEvent.getAccountDeviceInstances()); - for (AccountDeviceInstanceKey adiKey : pinnedAccountDevices) { - mxCell pinnedAccountVertex = getOrCreateVertex(adiKey); - - List relatedAccountDeviceInstances = - commsManager.getRelatedAccountDeviceInstances(adiKey.getAccountDeviceInstance(), commsFilter); - - for (AccountDeviceInstance relatedADI : relatedAccountDeviceInstances) { - long adiRelationshipsCount = commsManager.getRelationshipSourcesCount(relatedADI, commsFilter); - List relationships = commsManager.getRelationships(adiKey.getAccountDeviceInstance(), relatedADI, commsFilter); - - AccountDeviceInstanceKey relatedADIKey = - new AccountDeviceInstanceKey(relatedADI, commsFilter, adiRelationshipsCount); - mxCell relatedAccountVertex = getOrCreateVertex(relatedADIKey); - for (BlackboardArtifact relationship : relationships) { - addEdge(relationship, pinnedAccountVertex, relatedAccountVertex); - } - } - } + rebuildGraph(); } catch (TskCoreException ex) { - Exceptions.printStackTrace(ex); + logger.log(Level.SEVERE, "Error pinning accounts", ex); } finally { // Updates the display graph.getModel().endUpdate(); - } applyOrganicLayout(); revalidate(); } + @Subscribe + public void handleFilterEvent(CVTEvents.FilterChangeEvent filterChangeEvent) { + graph.getModel().beginUpdate(); + try { + clearGraph(); + currentFilter = filterChangeEvent.getNewFilter(); + rebuildGraph(); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error filtering accounts", ex); + } finally { + // Updates the display + graph.getModel().endUpdate(); + } + + applyOrganicLayout(); + revalidate(); + } + + private void rebuildGraph() throws TskCoreException { + for (AccountDeviceInstanceKey adiKey : pinnedAccountDevices) { + mxCell pinnedAccountVertex = getOrCreateVertex(adiKey); + + List relatedAccountDeviceInstances = + commsManager.getRelatedAccountDeviceInstances(adiKey.getAccountDeviceInstance(), currentFilter); + + for (AccountDeviceInstance relatedADI : relatedAccountDeviceInstances) { + long adiRelationshipsCount = commsManager.getRelationshipSourcesCount(relatedADI, currentFilter); + List relationships = commsManager.getRelationships(adiKey.getAccountDeviceInstance(), relatedADI, currentFilter); + + AccountDeviceInstanceKey relatedADIKey = + new AccountDeviceInstanceKey(relatedADI, currentFilter, adiRelationshipsCount); + mxCell relatedAccountVertex = getOrCreateVertex(relatedADIKey); + for (BlackboardArtifact relationship : relationships) { + addEdge(relationship, pinnedAccountVertex, relatedAccountVertex); + } + } + } + } + private void clearGraph() { nodeMap.clear(); edgeMap.clear(); From b3faf438af043385aca74088a97724adab790cea Mon Sep 17 00:00:00 2001 From: millmanorama Date: Mon, 15 Jan 2018 17:06:46 +0100 Subject: [PATCH 08/11] fix filtering begin adding rubberband and multiselect --- .../communications/VisualizationPanel.java | 94 ++++++++++++------- 1 file changed, 62 insertions(+), 32 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index d202b54ee8..ff25a753a0 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -26,8 +26,11 @@ import com.mxgraph.layout.mxFastOrganicLayout; import com.mxgraph.layout.orthogonal.mxOrthogonalLayout; import com.mxgraph.model.mxCell; import com.mxgraph.model.mxICell; +import com.mxgraph.swing.handler.mxRubberband; import com.mxgraph.swing.mxGraphComponent; import com.mxgraph.util.mxConstants; +import com.mxgraph.util.mxEventObject; +import com.mxgraph.util.mxEventSource; import com.mxgraph.util.mxRectangle; import com.mxgraph.view.mxGraph; import com.mxgraph.view.mxGraphView; @@ -49,6 +52,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; +import java.util.stream.Stream; import javax.swing.AbstractAction; import javax.swing.ImageIcon; import javax.swing.JButton; @@ -116,6 +120,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private CommunicationsManager commsManager; private final HashSet pinnedAccountDevices = new HashSet<>(); private CommunicationsFilter currentFilter; + private final mxRubberband rubberband; public VisualizationPanel() { initComponents(); @@ -132,16 +137,23 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graph.setEdgeLabelsMovable(false); graph.setVertexLabelsMovable(false); graph.setAllowDanglingEdges(false); - graph.setCellsDeletable(false); graph.setCellsBendable(true); graph.setKeepEdgesInBackground(true); + graph.setStylesheet(mxStylesheet); graphComponent = new mxGraphComponent(graph); + graphComponent.setAutoExtend(true); + graphComponent.setAutoScroll(true); + graphComponent.setAutoscrolls(true); graphComponent.setConnectable(false); graphComponent.setKeepSelectionVisibleOnZoom(true); graphComponent.setOpaque(true); graphComponent.setBackground(Color.WHITE); jPanel1.add(graphComponent, BorderLayout.CENTER); + //install rubber band selection handler + rubberband = new mxRubberband(graphComponent); + + //right click handler graphComponent.getGraphControl().addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { @@ -169,32 +181,8 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider splitPane.setRightComponent(new MessageBrowser(vizEM, gacEM)); - graph.setStylesheet(mxStylesheet); - - graph.getSelectionModel().addListener(null, (sender, evt) -> { - Object[] selectionCells = graph.getSelectionCells(); - if (selectionCells.length == 1) { - mxCell selectionCell = (mxCell) selectionCells[0]; - try { - - if (selectionCell.isVertex()) { - final AccountDeviceInstanceNode accountDeviceInstanceNode = - new AccountDeviceInstanceNode(((AccountDeviceInstanceKey) selectionCell.getValue()), - commsManager); - vizEM.setRootContext(SimpleParentNode.createFromChildNodes(accountDeviceInstanceNode)); - vizEM.setSelectedNodes(new Node[]{accountDeviceInstanceNode}); - - } else if (selectionCell.isEdge()) { - @SuppressWarnings("unchecked") - AbstractNode abstractNode = new AbstractNode(Children.create(new RelaionshipSetNodeFactory((Set) selectionCell.getValue()), true)); - vizEM.setRootContext(abstractNode); - vizEM.setSelectedNodes(new Node[]{abstractNode}); - } - } catch (PropertyVetoException ex) { - logger.log(Level.SEVERE, "Account selection vetoed.", ex); - } - } - }); + //feed selection to explorermanager + graph.getSelectionModel().addListener(null, new SelectionListener()); } @Override @@ -238,7 +226,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider @Subscribe public void handlePinEvent(CVTEvents.PinAccountsEvent pinEvent) { - graph.getModel().beginUpdate(); try { if (pinEvent.isReplace()) { @@ -255,11 +242,11 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } applyOrganicLayout(); - revalidate(); } @Subscribe public void handleFilterEvent(CVTEvents.FilterChangeEvent filterChangeEvent) { + graph.getModel().beginUpdate(); try { clearGraph(); @@ -273,7 +260,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } applyOrganicLayout(); - revalidate(); } private void rebuildGraph() throws TskCoreException { @@ -300,7 +286,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private void clearGraph() { nodeMap.clear(); edgeMap.clear(); - graph.removeCells(graph.getChildCells(graph.getDefaultParent(), true, true)); + graph.removeCells(graph.getChildVertices(graph.getDefaultParent())); } @Override @@ -483,7 +469,8 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider mxGraphView view = graphComponent.getGraph().getView(); int compLen = graphComponent.getWidth(); int viewLen = (int) view.getGraphBounds().getWidth(); - view.setScale((double) compLen / viewLen * view.getScale()); +// view.setScale((double) compLen / viewLen * view.getScale()); + } private void applyOrthogonalLayout() { @@ -525,4 +512,47 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider super(children); } } + + private class SelectionListener implements mxEventSource.mxIEventListener { + + @Override + + public void invoke(Object sender, mxEventObject evt) { + Object[] selectionCells = graph.getSelectionCells(); + if (selectionCells.length == 0) { + vizEM.setRootContext(Node.EMPTY); + } else { + Node rootNode; + Node[] selectedNodes; + mxICell[] selectedCells = Arrays.asList(selectionCells).toArray(new mxCell[selectionCells.length]); + + if (Stream.of(selectedCells).allMatch(mxICell::isVertex)) { + HashSet adis = new HashSet<>(); + for (mxICell vertex : selectedCells) { + adis.add(((AccountDeviceInstanceKey) vertex.getValue()).getAccountDeviceInstance()); + } + + final AccountDetailsNode accountDeviceInstanceNode = + new AccountDetailsNode(adis, currentFilter, commsManager); + rootNode = SimpleParentNode.createFromChildNodes(accountDeviceInstanceNode); + selectedNodes = new Node[]{accountDeviceInstanceNode}; + + } else { + HashSet relationshipArtifacts = new HashSet<>(); + for (mxICell edge : selectedCells) { + relationshipArtifacts.addAll((Set) edge.getValue()); + } + + rootNode = new AbstractNode(Children.create(new RelaionshipSetNodeFactory(relationshipArtifacts), true)); + selectedNodes = new Node[]{rootNode}; + } + vizEM.setRootContext(rootNode); + try { + vizEM.setSelectedNodes(selectedNodes); + } catch (PropertyVetoException ex) { + logger.log(Level.SEVERE, "Account selection vetoed.", ex); + } + } + } + } } From fcd170a393bd88517785b8f5c0f969c429726367 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Tue, 16 Jan 2018 12:57:09 +0100 Subject: [PATCH 09/11] refactoring multi select in MessageBrowser --- .../communications/AccountDetailsNode.java | 5 ++ .../AccountDeviceInstanceNode.java | 1 + .../autopsy/communications/Bundle.properties | 1 + .../communications/MessageBrowser.java | 66 ++++++++----------- .../communications/VisualizationPanel.form | 13 ++++ .../communications/VisualizationPanel.java | 50 ++++++++++---- 6 files changed, 85 insertions(+), 51 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountDetailsNode.java b/Core/src/org/sleuthkit/autopsy/communications/AccountDetailsNode.java index 7bc1a0faae..39eae2b9f3 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/AccountDetailsNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/AccountDetailsNode.java @@ -25,6 +25,7 @@ import org.openide.nodes.AbstractNode; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; +import org.python.google.common.collect.Iterables; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AccountDeviceInstance; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -44,6 +45,10 @@ final class AccountDetailsNode extends AbstractNode { AccountDetailsNode(Set accountDeviceInstances, CommunicationsFilter filter, CommunicationsManager commsManager) { super(Children.create(new AccountRelationshipChildren(accountDeviceInstances, commsManager, filter), true)); + String displayName = (accountDeviceInstances.size() == 1) + ? Iterables.getOnlyElement(accountDeviceInstances).getAccount().getTypeSpecificID() + : accountDeviceInstances.size() + " accounts"; + setDisplayName(displayName); } /** diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java index 6b60d75935..c0208b1bce 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java @@ -52,6 +52,7 @@ final class AccountDeviceInstanceNode extends AbstractNode { this.commsManager = commsManager; this.account = accountDeviceInstanceKey.getAccountDeviceInstance().getAccount(); setName(account.getTypeSpecificID()); + setDisplayName(getName()); setIconBaseWithExtension("org/sleuthkit/autopsy/communications/images/" + Utils.getIconFileName(account.getAccountType())); } diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties index 71d89548f8..63f58253a0 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties @@ -25,3 +25,4 @@ VisualizationPanel.jButton3.text=Orthogonal VisualizationPanel.jButton4.text=- VisualizationPanel.jButton5.text=+ VisualizationPanel.jButton6.text=Hierarchy +VisualizationPanel.jButton7.text=Compact Tree diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java index d0cbdc6e64..b04f232a29 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java @@ -18,7 +18,8 @@ */ package org.sleuthkit.autopsy.communications; -import java.util.Collections; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -31,8 +32,6 @@ import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; import org.sleuthkit.autopsy.corecomponents.TableFilterNode; import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; import org.sleuthkit.datamodel.AccountDeviceInstance; -import org.sleuthkit.datamodel.CommunicationsFilter; -import org.sleuthkit.datamodel.CommunicationsManager; /** * The right hand side of the CVT. Has a DataResultPanel to show messages and @@ -57,7 +56,7 @@ public final class MessageBrowser extends JPanel implements ExplorerManager.Prov * context-sensitive actions. */ @NbBundle.Messages({"MessageBrowser.DataResultViewerTable.title=Messages"}) - MessageBrowser(ExplorerManager tableEM, ExplorerManager gacExplorerManager) { + MessageBrowser(ExplorerManager tableEM, ExplorerManager gacExplorerManager) { this.tableEM = tableEM; this.gacExplorerManager = gacExplorerManager; initComponents(); @@ -69,45 +68,38 @@ public final class MessageBrowser extends JPanel implements ExplorerManager.Prov Bundle.MessageBrowser_DataResultViewerTable_title())); messagesResultPanel.open(); - this.tableEM.addPropertyChangeListener(pce -> { - if (pce.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { - final Node[] selectedNodes = this.tableEM.getSelectedNodes(); - - messagesResultPanel.setNumMatches(0); - messagesResultPanel.setNode(null); - - if (selectedNodes.length == 0) { - //reset panel when there is no selection + this.tableEM.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent pce) { + if (pce.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { + final Node[] selectedNodes = MessageBrowser.this.tableEM.getSelectedNodes(); + messagesResultPanel.setNumMatches(0); + messagesResultPanel.setNode(null); messagesResultPanel.setPath(""); - } else { - final Node selectedNode = selectedNodes[0]; - if (selectedNode instanceof AccountDeviceInstanceNode) { - AccountDeviceInstanceNode adiNode = (AccountDeviceInstanceNode) selectedNode; - CommunicationsFilter filter = adiNode.getFilter(); - CommunicationsManager commsManager = adiNode.getCommsManager(); - final Set accountDeviceInstances; - - if (selectedNodes.length == 1) { - final AccountDeviceInstance accountDeviceInstance = adiNode.getAccountDeviceInstance(); - accountDeviceInstances = Collections.singleton(accountDeviceInstance); - messagesResultPanel.setPath(accountDeviceInstance.getAccount().getTypeSpecificID()); + if (selectedNodes.length > 0) { + Node rootNode; + final Node selectedNode = selectedNodes[0]; + String path = selectedNode.getDisplayName(); + if (selectedNode instanceof AccountDeviceInstanceNode) { + rootNode = makeRootNodeFromAccountDeviceInstanceNodes(selectedNodes); } else { - accountDeviceInstances = Stream.of(selectedNodes) - .map(node -> (AccountDeviceInstanceNode) node) - .map(AccountDeviceInstanceNode::getAccountDeviceInstance) - .collect(Collectors.toSet()); - messagesResultPanel.setPath(selectedNodes.length + " accounts"); + rootNode = selectedNode; } - AccountDetailsNode accountDetailsNode = - new AccountDetailsNode(accountDeviceInstances, filter, commsManager); - TableFilterNode wrappedNode = - new TableFilterNode(new DataResultFilterNode(accountDetailsNode, gacExplorerManager), true); - messagesResultPanel.setNode(wrappedNode); - }else{ - messagesResultPanel.setNode(selectedNode); + messagesResultPanel.setPath(path); + messagesResultPanel.setNode(new TableFilterNode(new DataResultFilterNode(rootNode, gacExplorerManager), true)); } } } + + private Node makeRootNodeFromAccountDeviceInstanceNodes(final Node[] selectedNodes) { + AccountDeviceInstanceNode adiNode = (AccountDeviceInstanceNode) selectedNodes[0]; + final Set accountDeviceInstances; + accountDeviceInstances = Stream.of(selectedNodes) + .map(AccountDeviceInstanceNode.class::cast) + .map(AccountDeviceInstanceNode::getAccountDeviceInstance) + .collect(Collectors.toSet()); + return new AccountDetailsNode(accountDeviceInstances, adiNode.getFilter(), adiNode.getCommsManager()); + } }); } diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form index 8a2130752f..da37189ec5 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form @@ -102,6 +102,19 @@ + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index ff25a753a0..1f9fcf66ee 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -22,6 +22,7 @@ import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; import com.google.common.eventbus.Subscribe; import com.mxgraph.layout.hierarchical.mxHierarchicalLayout; +import com.mxgraph.layout.mxCompactTreeLayout; import com.mxgraph.layout.mxFastOrganicLayout; import com.mxgraph.layout.orthogonal.mxOrthogonalLayout; import com.mxgraph.model.mxCell; @@ -346,6 +347,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider jButton6 = new JButton(); jButton1 = new JButton(); jButton3 = new JButton(); + jButton7 = new JButton(); jButton4 = new JButton(); jButton5 = new JButton(); @@ -402,6 +404,17 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider }); jToolBar1.add(jButton3); + jButton7.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jButton7.text")); // NOI18N + jButton7.setFocusable(false); + jButton7.setHorizontalTextPosition(SwingConstants.CENTER); + jButton7.setVerticalTextPosition(SwingConstants.BOTTOM); + jButton7.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + jButton7ActionPerformed(evt); + } + }); + jToolBar1.add(jButton7); + jButton4.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jButton4.text")); // NOI18N jButton4.setFocusable(false); jButton4.setHorizontalTextPosition(SwingConstants.CENTER); @@ -457,6 +470,10 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider applyHierarchicalLayout(); }//GEN-LAST:event_jButton6ActionPerformed + private void jButton7ActionPerformed(ActionEvent evt) {//GEN-FIRST:event_jButton7ActionPerformed + applyCompactTreeLayout(); + }//GEN-LAST:event_jButton7ActionPerformed + private void applyOrganicLayout() { graph.setMaximumGraphBounds(new mxRectangle(0, 0, graphComponent.getWidth(), graphComponent.getHeight())); @@ -487,6 +504,13 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider fitGraph(); } + private void applyCompactTreeLayout() { + graph.setMaximumGraphBounds(new mxRectangle(0, 0, graphComponent.getWidth(), + graphComponent.getHeight())); + new mxCompactTreeLayout(graph).execute(graph.getDefaultParent()); + fitGraph(); + } + // Variables declaration - do not modify//GEN-BEGIN:variables private JButton jButton1; @@ -495,6 +519,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private JButton jButton4; private JButton jButton5; private JButton jButton6; + private JButton jButton7; private JPanel jPanel1; private JToolBar jToolBar1; private JSplitPane splitPane; @@ -516,14 +541,12 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private class SelectionListener implements mxEventSource.mxIEventListener { @Override - + public void invoke(Object sender, mxEventObject evt) { Object[] selectionCells = graph.getSelectionCells(); - if (selectionCells.length == 0) { - vizEM.setRootContext(Node.EMPTY); - } else { - Node rootNode; - Node[] selectedNodes; + Node rootNode = Node.EMPTY; + Node[] selectedNodes = new Node[0]; + if (selectionCells.length > 0) { mxICell[] selectedCells = Arrays.asList(selectionCells).toArray(new mxCell[selectionCells.length]); if (Stream.of(selectedCells).allMatch(mxICell::isVertex)) { @@ -537,21 +560,20 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider rootNode = SimpleParentNode.createFromChildNodes(accountDeviceInstanceNode); selectedNodes = new Node[]{accountDeviceInstanceNode}; - } else { + } else if (Stream.of(selectedCells).allMatch(mxICell::isEdge)) { HashSet relationshipArtifacts = new HashSet<>(); for (mxICell edge : selectedCells) { relationshipArtifacts.addAll((Set) edge.getValue()); } - rootNode = new AbstractNode(Children.create(new RelaionshipSetNodeFactory(relationshipArtifacts), true)); selectedNodes = new Node[]{rootNode}; } - vizEM.setRootContext(rootNode); - try { - vizEM.setSelectedNodes(selectedNodes); - } catch (PropertyVetoException ex) { - logger.log(Level.SEVERE, "Account selection vetoed.", ex); - } + } + vizEM.setRootContext(rootNode); + try { + vizEM.setSelectedNodes(selectedNodes); + } catch (PropertyVetoException ex) { + logger.log(Level.SEVERE, "Selection vetoed.", ex); } } } From 85fb7ee11e06c08fb61cebcde0104b0a03837ee7 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Tue, 16 Jan 2018 13:46:28 +0100 Subject: [PATCH 10/11] fix Type column of RelationshipNode --- .../autopsy/communications/RelationshipNode.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java b/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java index 27c39d80ec..e952573f50 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java @@ -47,7 +47,7 @@ final class RelationshipNode extends BlackboardArtifactNode { private static final Logger logger = Logger.getLogger(RelationshipNode.class.getName()); - RelationshipNode(BlackboardArtifact artifact) { + RelationshipNode(BlackboardArtifact artifact) { super(artifact); final String stripEnd = StringUtils.stripEnd(artifact.getDisplayName(), "s"); String removeEndIgnoreCase = StringUtils.removeEndIgnoreCase(stripEnd, "message"); @@ -143,4 +143,15 @@ final class RelationshipNode extends BlackboardArtifactNode { return ""; } } + + /** + * Circumvent DataResultFilterNode's slightly odd delegation to + * BlackboardArtifactNode.getSourceName(). + * + * @return the displayName of this Node, which is the type. + */ + @Override + public String getSourceName() { + return getDisplayName(); + } } From f63d76a9673238d39c6efd74e6a1247cb4c2655b Mon Sep 17 00:00:00 2001 From: millmanorama Date: Tue, 16 Jan 2018 17:15:55 +0100 Subject: [PATCH 11/11] uniform multiselect using SelectionNode --- .../communications/CVTTopComponent.java | 2 +- .../communications/MessageBrowser.java | 21 +-- .../autopsy/communications/SelectionNode.java | 133 ++++++++++++++++++ .../communications/VisualizationPanel.java | 64 +++------ 4 files changed, 165 insertions(+), 55 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/communications/SelectionNode.java diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java index 39361de47e..6585f6324a 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java @@ -70,7 +70,7 @@ public final class CVTTopComponent extends TopComponent { } @Subscribe - public void pinAccount(CVTEvents.PinAccountsEvent pinEvent) { + void pinAccount(CVTEvents.PinAccountsEvent pinEvent) { browseVisualizeTabPane.setSelectedIndex(1); } diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java index b04f232a29..226c0cf508 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java @@ -20,9 +20,8 @@ package org.sleuthkit.autopsy.communications; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.HashSet; import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.swing.JPanel; import org.openide.explorer.ExplorerManager; import org.openide.nodes.Node; @@ -79,26 +78,28 @@ public final class MessageBrowser extends JPanel implements ExplorerManager.Prov if (selectedNodes.length > 0) { Node rootNode; final Node selectedNode = selectedNodes[0]; - String path = selectedNode.getDisplayName(); + if (selectedNode instanceof AccountDeviceInstanceNode) { rootNode = makeRootNodeFromAccountDeviceInstanceNodes(selectedNodes); } else { rootNode = selectedNode; } - messagesResultPanel.setPath(path); + messagesResultPanel.setPath(rootNode.getDisplayName()); messagesResultPanel.setNode(new TableFilterNode(new DataResultFilterNode(rootNode, gacExplorerManager), true)); } } } private Node makeRootNodeFromAccountDeviceInstanceNodes(final Node[] selectedNodes) { + //Use lookup here? AccountDeviceInstanceNode adiNode = (AccountDeviceInstanceNode) selectedNodes[0]; - final Set accountDeviceInstances; - accountDeviceInstances = Stream.of(selectedNodes) - .map(AccountDeviceInstanceNode.class::cast) - .map(AccountDeviceInstanceNode::getAccountDeviceInstance) - .collect(Collectors.toSet()); - return new AccountDetailsNode(accountDeviceInstances, adiNode.getFilter(), adiNode.getCommsManager()); + + final Set accountDeviceInstances = new HashSet<>(); + for (Node n : selectedNodes) { + //Use lookup here? + accountDeviceInstances.add(((AccountDeviceInstanceNode) n).getAccountDeviceInstance()); + } + return SelectionNode.createFromAccounts(accountDeviceInstances, adiNode.getFilter(), adiNode.getCommsManager()); } }); } diff --git a/Core/src/org/sleuthkit/autopsy/communications/SelectionNode.java b/Core/src/org/sleuthkit/autopsy/communications/SelectionNode.java new file mode 100644 index 0000000000..40739b49cc --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/SelectionNode.java @@ -0,0 +1,133 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.communications; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AccountDeviceInstance; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.CommunicationsFilter; +import org.sleuthkit.datamodel.CommunicationsManager; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * 'Root' Node for the Account/Messages area. Represents all the relationships + * that are selected in the AccountsBrowser or the VisualizationPanel. Can be + * populated with AccountDeviceInstance and/or directly with relationships + * (Content). + */ +final class SelectionNode extends AbstractNode { + + private SelectionNode(Children children) { + super(children); + } + + static SelectionNode createFromAccountsAndRelationships( + Set edgeRelationshipArtifacts, + Set accountDeviceInstances, + CommunicationsFilter filter, + CommunicationsManager commsManager) { + + SelectionNode node = new SelectionNode(Children.create( + new RelationshipChildren( + edgeRelationshipArtifacts, + accountDeviceInstances, + commsManager, + filter), + true)); + + //This is not good for internationalization!!! + String name = ""; + final int accounts = accountDeviceInstances.size(); + if (accounts > 1) { + name = accounts + " accounts"; + } else if (accounts == 1) { + name = Iterables.getOnlyElement(accountDeviceInstances).getAccount().getTypeSpecificID(); + } + + final int edges = edgeRelationshipArtifacts.size(); + + if (edges > 0) { + name = name + (name.isEmpty() ? "" : " and ") + edges + " relationship" + (edges > 1 ? "s" : ""); + } + + node.setDisplayName(name); + return node; + } + + static SelectionNode createFromAccounts( + Set accountDeviceInstances, + CommunicationsFilter filter, + CommunicationsManager commsManager) { + + return createFromAccountsAndRelationships(Collections.emptySet(), accountDeviceInstances, filter, commsManager); + } + + /** + * Children object for the relationships that the accounts are part of. + */ + private static class RelationshipChildren extends ChildFactory { + + static final private Logger logger = Logger.getLogger(RelationshipChildren.class.getName()); + + private final Set edgeRelationshipArtifacts; + + private final Set accountDeviceInstances; + + private final CommunicationsManager commsManager; + private final CommunicationsFilter filter; + + private RelationshipChildren(Set selectedEdgeRelationshipSources, Set selecedAccountDeviceInstances, CommunicationsManager commsManager, CommunicationsFilter filter) { + this.edgeRelationshipArtifacts = selectedEdgeRelationshipSources; + this.accountDeviceInstances = selecedAccountDeviceInstances; + this.commsManager = commsManager; + this.filter = filter; + } + + @Override + protected boolean createKeys(List list) { + try { + final Set relationshipSources = commsManager.getRelationshipSources(accountDeviceInstances, filter); + list.addAll(Sets.union(relationshipSources, edgeRelationshipArtifacts)); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error getting communications", ex); + } + return true; + } + + @Override + protected Node createNodeForKey(Content t) { + if (t instanceof BlackboardArtifact) { + return new RelationshipNode((BlackboardArtifact) t); + } else { + throw new UnsupportedOperationException("Cannot create a RelationshipNode for non BlackboardArtifact content."); + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index 1f9fcf66ee..5db792c344 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -53,7 +53,6 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; -import java.util.stream.Stream; import javax.swing.AbstractAction; import javax.swing.ImageIcon; import javax.swing.JButton; @@ -66,8 +65,6 @@ import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerUtils; -import org.openide.nodes.AbstractNode; -import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.Lookup; import org.openide.util.NbBundle; @@ -76,9 +73,9 @@ import org.sleuthkit.autopsy.casemodule.Case; import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AccountDeviceInstance; -import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.CommunicationsFilter; import org.sleuthkit.datamodel.CommunicationsManager; +import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; /** @@ -116,7 +113,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private final mxGraphComponent graphComponent; private final mxGraph graph; private final Map nodeMap = new HashMap<>(); - private final Multimap edgeMap = MultimapBuilder.hashKeys().hashSetValues().build(); + private final Multimap edgeMap = MultimapBuilder.hashKeys().hashSetValues().build(); private CommunicationsManager commsManager; private final HashSet pinnedAccountDevices = new HashSet<>(); @@ -212,21 +209,21 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } @SuppressWarnings("unchecked") - private void addEdge(BlackboardArtifact artifact, mxCell pinnedAccountVertex, mxCell relatedAccountVertex) throws TskCoreException { + private void addEdge(Content relSource, mxCell pinnedAccountVertex, mxCell relatedAccountVertex) throws TskCoreException { Object[] edgesBetween = graph.getEdgesBetween(pinnedAccountVertex, relatedAccountVertex); if (edgesBetween.length == 0) { final String edgeName = pinnedAccountVertex.getId() + " <-> " + relatedAccountVertex.getId(); - mxCell edge = (mxCell) graph.insertEdge(graph.getDefaultParent(), edgeName, new HashSet<>(Arrays.asList(artifact)), pinnedAccountVertex, relatedAccountVertex); - edgeMap.put(artifact, edge); + mxCell edge = (mxCell) graph.insertEdge(graph.getDefaultParent(), edgeName, new HashSet<>(Arrays.asList(relSource)), pinnedAccountVertex, relatedAccountVertex); + edgeMap.put(relSource, edge); } else if (edgesBetween.length == 1) { final mxCell edge = (mxCell) edgesBetween[0]; - ((Collection) edge.getValue()).add(artifact); + ((Collection) edge.getValue()).add(relSource); edge.setStyle("strokeWidth=" + Math.sqrt(((Collection) edge.getValue()).size())); } } @Subscribe - public void handlePinEvent(CVTEvents.PinAccountsEvent pinEvent) { + void handlePinEvent(CVTEvents.PinAccountsEvent pinEvent) { graph.getModel().beginUpdate(); try { if (pinEvent.isReplace()) { @@ -246,7 +243,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } @Subscribe - public void handleFilterEvent(CVTEvents.FilterChangeEvent filterChangeEvent) { + void handleFilterEvent(CVTEvents.FilterChangeEvent filterChangeEvent) { graph.getModel().beginUpdate(); try { @@ -272,12 +269,12 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider for (AccountDeviceInstance relatedADI : relatedAccountDeviceInstances) { long adiRelationshipsCount = commsManager.getRelationshipSourcesCount(relatedADI, currentFilter); - List relationships = commsManager.getRelationships(adiKey.getAccountDeviceInstance(), relatedADI, currentFilter); + List relationships = commsManager.getRelationshipSources(adiKey.getAccountDeviceInstance(), relatedADI, currentFilter); AccountDeviceInstanceKey relatedADIKey = new AccountDeviceInstanceKey(relatedADI, currentFilter, adiRelationshipsCount); mxCell relatedAccountVertex = getOrCreateVertex(relatedADIKey); - for (BlackboardArtifact relationship : relationships) { + for (Content relationship : relationships) { addEdge(relationship, pinnedAccountVertex, relatedAccountVertex); } } @@ -525,49 +522,28 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private JSplitPane splitPane; // End of variables declaration//GEN-END:variables - private static class SimpleParentNode extends AbstractNode { - - private static SimpleParentNode createFromChildNodes(Node... nodes) { - Children.Array array = new Children.Array(); - array.add(nodes); - return new SimpleParentNode(array); - } - - private SimpleParentNode(Children children) { - super(children); - } - } - private class SelectionListener implements mxEventSource.mxIEventListener { @Override + @SuppressWarnings("unchecked") public void invoke(Object sender, mxEventObject evt) { Object[] selectionCells = graph.getSelectionCells(); Node rootNode = Node.EMPTY; Node[] selectedNodes = new Node[0]; if (selectionCells.length > 0) { mxICell[] selectedCells = Arrays.asList(selectionCells).toArray(new mxCell[selectionCells.length]); - - if (Stream.of(selectedCells).allMatch(mxICell::isVertex)) { - HashSet adis = new HashSet<>(); - for (mxICell vertex : selectedCells) { - adis.add(((AccountDeviceInstanceKey) vertex.getValue()).getAccountDeviceInstance()); + HashSet relationshipSources = new HashSet<>(); + HashSet adis = new HashSet<>(); + for (mxICell cell : selectedCells) { + if (cell.isEdge()) { + relationshipSources.addAll((Set) cell.getValue()); + } else if (cell.isVertex()) { + adis.add(((AccountDeviceInstanceKey) cell.getValue()).getAccountDeviceInstance()); } - - final AccountDetailsNode accountDeviceInstanceNode = - new AccountDetailsNode(adis, currentFilter, commsManager); - rootNode = SimpleParentNode.createFromChildNodes(accountDeviceInstanceNode); - selectedNodes = new Node[]{accountDeviceInstanceNode}; - - } else if (Stream.of(selectedCells).allMatch(mxICell::isEdge)) { - HashSet relationshipArtifacts = new HashSet<>(); - for (mxICell edge : selectedCells) { - relationshipArtifacts.addAll((Set) edge.getValue()); - } - rootNode = new AbstractNode(Children.create(new RelaionshipSetNodeFactory(relationshipArtifacts), true)); - selectedNodes = new Node[]{rootNode}; } + rootNode = SelectionNode.createFromAccountsAndRelationships(relationshipSources, adis, currentFilter, commsManager); + selectedNodes = new Node[]{rootNode}; } vizEM.setRootContext(rootNode); try {