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 5b88b01999..c0208b1bce 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; @@ -51,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())); } @@ -113,20 +115,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/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/Bundle.properties b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties index d0fa082fd9..63f58253a0 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties @@ -15,9 +15,14 @@ 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=+ +VisualizationPanel.jButton6.text=Hierarchy +VisualizationPanel.jButton7.text=Compact Tree diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTEvents.java b/Core/src/org/sleuthkit/autopsy/communications/CVTEvents.java index 663b711c99..f13128f5c3 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/CVTEvents.java +++ b/Core/src/org/sleuthkit/autopsy/communications/CVTEvents.java @@ -18,7 +18,10 @@ */ 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. @@ -34,4 +37,36 @@ 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; + 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..6585f6324a 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,13 +59,18 @@ 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 - public void pinAccount(PinAccountEvent pinEvent) { + void pinAccount(CVTEvents.PinAccountsEvent pinEvent) { browseVisualizeTabPane.setSelectedIndex(1); } 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/MessageBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java index 7d0146715b..226c0cf508 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java @@ -18,10 +18,10 @@ */ package org.sleuthkit.autopsy.communications; -import java.util.Collections; +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; @@ -31,8 +31,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 +55,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,43 +67,40 @@ 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 > 0) { + Node rootNode; + final Node selectedNode = selectedNodes[0]; - if (selectedNodes.length == 1) { - final AccountDeviceInstance accountDeviceInstance = adiNode.getAccountDeviceInstance(); - accountDeviceInstances = Collections.singleton(accountDeviceInstance); - messagesResultPanel.setPath(accountDeviceInstance.getAccount().getTypeSpecificID()); + 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); + 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 = 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/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/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/RelationshipNode.java b/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java index fff7dfa9ad..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"); @@ -144,4 +144,14 @@ final class RelationshipNode extends BlackboardArtifactNode { } } + /** + * 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(); + } } 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.form b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form index 3262c06010..da37189ec5 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form @@ -4,19 +4,23 @@ - + - + - + + + + + @@ -40,7 +44,7 @@ - + @@ -59,6 +63,19 @@ + + + + + + + + + + + + + @@ -72,6 +89,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index 678b6eabac..5db792c344 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -18,32 +18,56 @@ */ 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.layout.hierarchical.mxHierarchicalLayout; +import com.mxgraph.layout.mxCompactTreeLayout; +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; 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; 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; +import javax.swing.ImageIcon; +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; -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; @@ -51,10 +75,11 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AccountDeviceInstance; import org.sleuthkit.datamodel.CommunicationsFilter; import org.sleuthkit.datamodel.CommunicationsManager; +import org.sleuthkit.datamodel.Content; 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. * @@ -65,13 +90,17 @@ 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(); 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); } @@ -84,17 +113,19 @@ 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 CommunicationsManager commsManager; - - void setFilterProvider(FilterProvider filterProvider) { - this.filterProvider = filterProvider; - } - private FilterProvider filterProvider; + private final HashSet pinnedAccountDevices = new HashSet<>(); + private CommunicationsFilter currentFilter; + private final mxRubberband rubberband; public VisualizationPanel() { initComponents(); graph = new mxGraph(); + graph.setCellsCloneable(false); + graph.setDropEnabled(false); + graph.setCellsCloneable(false); graph.setCellsEditable(false); graph.setCellsResizable(false); graph.setCellsMovable(true); @@ -103,40 +134,53 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graph.setDisconnectOnMove(false); graph.setEdgeLabelsMovable(false); graph.setVertexLabelsMovable(false); - graph.setAutoOrigin(true); + graph.setAllowDanglingEdges(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); - splitPane.setRightComponent(new MessageBrowser(vizEM, gacEM)); - CVTEvents.getCVTEventBus().register(this); - graph.setStylesheet(mxStylesheet); + //install rubber band selection handler + rubberband = new mxRubberband(graphComponent); - graph.getSelectionModel().addListener(null, (sender, evt) -> { - Object[] selectionCells = graph.getSelectionCells(); - if (selectionCells.length == 1) { - mxCell selectionCell = (mxCell) selectionCells[0]; - try { + //right click handler + 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(imageIcon) { + { + setAction(new AbstractAction("Pin Account " + graph.getLabel(cellAt)) { + @Override + public void actionPerformed(ActionEvent e) { + handlePinEvent(new CVTEvents.PinAccountsEvent(singleton((AccountDeviceInstanceKey) cellAt.getValue()), false)); + } + }); + } + }); - 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()) { - System.out.println(selectionCell.getId()); -// explorerManager.setRootContext(new CommunicationsBundleNode(adiKey, commsManager)); + jPopupMenu.show(graphComponent.getGraphControl(), e.getX(), e.getY()); } - } catch (PropertyVetoException ex) { - logger.log(Level.SEVERE, "Account selection vetoed.", ex); } } }); + + splitPane.setRightComponent(new MessageBrowser(vizEM, gacEM)); + + //feed selection to explorermanager + graph.getSelectionModel().addListener(null, new SelectionListener()); } @Override @@ -144,77 +188,105 @@ 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() + ":" + accountDeviceInstance.getAccount().getTypeSpecificID(); - mxCell vertex = nodeMap.get(name); - if (vertex == null) { + final mxCell computeIfAbsent = nodeMap.computeIfAbsent(name, vertexName -> { double size = Math.sqrt(accountDeviceInstanceKey.getMessageCount()) + 10; - vertex = (mxCell) graph.insertVertex( + + mxCell vertex = (mxCell) graph.insertVertex( graph.getDefaultParent(), - name, accountDeviceInstanceKey, - new Random().nextInt(200), - new Random().nextInt(200), + vertexName, accountDeviceInstanceKey, + 0, + 0, size, size); - graph.getView().getState(vertex, true).setLabel(name); - nodeMap.put(name, vertex); - } - return vertex; + graph.getView().getState(vertex, true).setLabel(vertexName); + return vertex; + }); + return computeIfAbsent; } - private void addEdge(mxCell pinnedAccountVertex, mxCell relatedAccountVertex) { - + @SuppressWarnings("unchecked") + 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(); - graph.insertEdge(graph.getDefaultParent(), edgeName, 1d, pinnedAccountVertex, relatedAccountVertex); + 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]; - edge.setValue(1d + (double) edge.getValue()); - edge.setStyle("strokeWidth=" + Math.log((double) edge.getValue())); + ((Collection) edge.getValue()).add(relSource); + edge.setStyle("strokeWidth=" + Math.sqrt(((Collection) edge.getValue()).size())); } } + @Subscribe + void handlePinEvent(CVTEvents.PinAccountsEvent pinEvent) { + graph.getModel().beginUpdate(); + try { + if (pinEvent.isReplace()) { + pinnedAccountDevices.clear(); + clearGraph(); + } + pinnedAccountDevices.addAll(pinEvent.getAccountDeviceInstances()); + rebuildGraph(); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error pinning accounts", ex); + } finally { + // Updates the display + graph.getModel().endUpdate(); + } + + applyOrganicLayout(); + } + + @Subscribe + 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(); + } + + 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.getRelationshipSources(adiKey.getAccountDeviceInstance(), relatedADI, currentFilter); + + AccountDeviceInstanceKey relatedADIKey = + new AccountDeviceInstanceKey(relatedADI, currentFilter, adiRelationshipsCount); + mxCell relatedAccountVertex = getOrCreateVertex(relatedADIKey); + for (Content relationship : relationships) { + addEdge(relationship, pinnedAccountVertex, relatedAccountVertex); + } + } + } + } + + private void clearGraph() { + nodeMap.clear(); + edgeMap.clear(); + graph.removeCells(graph.getChildVertices(graph.getDefaultParent())); + } + @Override public void addNotify() { super.addNotify(); @@ -232,8 +304,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider Case.addEventTypeSubscriber(EnumSet.of(CURRENT_CASE), evt -> { graph.getModel().beginUpdate(); try { - nodeMap.clear(); - graph.removeCells(graph.getChildCells(graph.getDefaultParent(), true, true)); + clearGraph(); } finally { graph.getModel().endUpdate(); } @@ -266,85 +337,220 @@ 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(); - jToolBar1 = new javax.swing.JToolBar(); - jButton2 = new javax.swing.JButton(); - jButton1 = 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(); + jButton7 = new JButton(); + jButton4 = new JButton(); + jButton5 = new JButton(); - setLayout(new java.awt.BorderLayout()); + setLayout(new BorderLayout()); - jPanel1.setLayout(new java.awt.BorderLayout()); + splitPane.setDividerLocation(400); + splitPane.setResizeWeight(0.5); + + jPanel1.setLayout(new BorderLayout()); jToolBar1.setRollover(true); - jButton2.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jButton2.text")); // NOI18N + 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); - jPanel1.add(jToolBar1, java.awt.BorderLayout.PAGE_START); + jButton3.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jButton3.text")); // NOI18N + jButton3.setFocusable(false); + jButton3.setHorizontalTextPosition(SwingConstants.CENTER); + jButton3.setVerticalTextPosition(SwingConstants.BOTTOM); + jButton3.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + jButton3ActionPerformed(evt); + } + }); + 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); + jButton4.setVerticalTextPosition(SwingConstants.BOTTOM); + jButton4.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + jButton4ActionPerformed(evt); + } + }); + jToolBar1.add(jButton4); + + jButton5.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jButton5.text")); // NOI18N + jButton5.setFocusable(false); + jButton5.setHorizontalTextPosition(SwingConstants.CENTER); + jButton5.setVerticalTextPosition(SwingConstants.BOTTOM); + jButton5.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + jButton5ActionPerformed(evt); + } + }); + jToolBar1.add(jButton5); + + 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 applyOrganicLayout() { - new mxOrganicLayout(graph).execute(graph.getDefaultParent()); + private void jButton3ActionPerformed(ActionEvent evt) {//GEN-FIRST:event_jButton3ActionPerformed + applyOrthogonalLayout(); + }//GEN-LAST:event_jButton3ActionPerformed + + private void jButton5ActionPerformed(ActionEvent evt) {//GEN-FIRST:event_jButton5ActionPerformed + graphComponent.zoomIn(); + }//GEN-LAST:event_jButton5ActionPerformed + + 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 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())); + 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()); - graphComponent.zoomAndCenter(); +// 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()); + fitGraph(); + } + + private void applyHierarchicalLayout() { + graph.setMaximumGraphBounds(new mxRectangle(0, 0, graphComponent.getWidth(), + graphComponent.getHeight())); + new mxHierarchicalLayout(graph).execute(graph.getDefaultParent()); + 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 javax.swing.JButton jButton1; - private javax.swing.JButton jButton2; - private javax.swing.JPanel jPanel1; - private javax.swing.JToolBar jToolBar1; - private javax.swing.JSplitPane splitPane; + private JButton jButton1; + private JButton jButton2; + private JButton jButton3; + private JButton jButton4; + private JButton jButton5; + private JButton jButton6; + private JButton jButton7; + private JPanel jPanel1; + private JToolBar jToolBar1; + private JSplitPane splitPane; // End of variables declaration//GEN-END:variables - private static class SimpleParentNode extends AbstractNode { + private class SelectionListener implements mxEventSource.mxIEventListener { - private static SimpleParentNode createFromChildNodes(Node... nodes) { - Children.Array array = new Children.Array(); - array.add(nodes); - return new SimpleParentNode(array); - } + @Override - private SimpleParentNode(Children children) { - super(children); + @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]); + 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()); + } + } + rootNode = SelectionNode.createFromAccountsAndRelationships(relationshipSources, adis, currentFilter, commsManager); + selectedNodes = new Node[]{rootNode}; + } + vizEM.setRootContext(rootNode); + try { + vizEM.setSelectedNodes(selectedNodes); + } catch (PropertyVetoException ex) { + logger.log(Level.SEVERE, "Selection vetoed.", ex); + } } } } 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 0000000000..331be05231 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/communications/images/icons8-neural-network.png differ