Merge pull request #3371 from millmanorama/multiselect-in-visualization

Multiselect and other features in visualization
This commit is contained in:
Richard Cordovano 2018-01-29 16:47:33 -05:00 committed by GitHub
commit c1c2bfbc6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 707 additions and 276 deletions

View File

@ -25,6 +25,7 @@ import org.openide.nodes.AbstractNode;
import org.openide.nodes.ChildFactory; import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children; import org.openide.nodes.Children;
import org.openide.nodes.Node; import org.openide.nodes.Node;
import org.python.google.common.collect.Iterables;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AccountDeviceInstance; import org.sleuthkit.datamodel.AccountDeviceInstance;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
@ -44,6 +45,10 @@ final class AccountDetailsNode extends AbstractNode {
AccountDetailsNode(Set<AccountDeviceInstance> accountDeviceInstances, CommunicationsFilter filter, CommunicationsManager commsManager) { AccountDetailsNode(Set<AccountDeviceInstance> accountDeviceInstances, CommunicationsFilter filter, CommunicationsManager commsManager) {
super(Children.create(new AccountRelationshipChildren(accountDeviceInstances, commsManager, filter), true)); super(Children.create(new AccountRelationshipChildren(accountDeviceInstances, commsManager, filter), true));
String displayName = (accountDeviceInstances.size() == 1)
? Iterables.getOnlyElement(accountDeviceInstances).getAccount().getTypeSpecificID()
: accountDeviceInstances.size() + " accounts";
setDisplayName(displayName);
} }
/** /**

View File

@ -24,6 +24,7 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
import javax.swing.Action; import javax.swing.Action;
import javax.swing.ImageIcon;
import org.openide.nodes.AbstractNode; import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children; import org.openide.nodes.Children;
import org.openide.nodes.Sheet; import org.openide.nodes.Sheet;
@ -51,6 +52,7 @@ final class AccountDeviceInstanceNode extends AbstractNode {
this.commsManager = commsManager; this.commsManager = commsManager;
this.account = accountDeviceInstanceKey.getAccountDeviceInstance().getAccount(); this.account = accountDeviceInstanceKey.getAccountDeviceInstance().getAccount();
setName(account.getTypeSpecificID()); setName(account.getTypeSpecificID());
setDisplayName(getName());
setIconBaseWithExtension("org/sleuthkit/autopsy/communications/images/" + Utils.getIconFileName(account.getAccountType())); 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 final long serialVersionUID = 1L;
private static PinAccountsAction instance = new PinAccountsAction(); private static PinAccountsAction instance = new PinAccountsAction();
static final private ImageIcon imageIcon =
new ImageIcon("images/icons8-neural-network.png");
private static PinAccountsAction getInstance() { private static PinAccountsAction getInstance() {
return instance; return instance;
} }
private PinAccountsAction() { private PinAccountsAction() {
super("Visualize Account"); super("Visualize Account", imageIcon);
} }
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
Collection<? extends AccountDeviceInstanceKey> lookupAll = Collection<? extends AccountDeviceInstanceKey> lookupAll =
Utilities.actionsGlobalContext().lookupAll(AccountDeviceInstanceKey.class); Utilities.actionsGlobalContext().lookupAll(AccountDeviceInstanceKey.class);
CVTEvents.getCVTEventBus().post(new PinAccountEvent(lookupAll)); CVTEvents.getCVTEventBus().post(new CVTEvents.PinAccountsEvent(lookupAll, true));
} }
} }
} }

View File

@ -18,7 +18,9 @@
*/ */
package org.sleuthkit.autopsy.communications; package org.sleuthkit.autopsy.communications;
import com.google.common.eventbus.Subscribe;
import java.awt.Component; import java.awt.Component;
import java.util.logging.Level;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.ListSelectionModel; import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
@ -27,8 +29,14 @@ import org.netbeans.swing.outline.DefaultOutlineModel;
import org.netbeans.swing.outline.Outline; import org.netbeans.swing.outline.Outline;
import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerUtils; 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;
import org.openide.util.lookup.ProxyLookup; 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. * 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 { public final class AccountsBrowser extends JPanel implements ExplorerManager.Provider, Lookup.Provider {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(AccountsBrowser.class.getName());
private final Outline outline; private final Outline outline;
private final ExplorerManager messageBrowserEM = new ExplorerManager(); 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 * This lookup proxies the selection lookup of both he accounts table and
* the messages table. * the messages table.
*/ */
private ProxyLookup proxyLookup; private final ProxyLookup proxyLookup;
public AccountsBrowser() { public AccountsBrowser() {
initComponents(); initComponents();
@ -68,11 +77,7 @@ public final class AccountsBrowser extends JPanel implements ExplorerManager.Pro
outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
outline.setColumnSorted(3, false, 1); //it would be nice if the column index wasn't hardcoded outline.setColumnSorted(3, false, 1); //it would be nice if the column index wasn't hardcoded
} accountsTableEM.addPropertyChangeListener(evt -> {
void init(ExplorerManager tableExplorerManager) {
this.accountsTableEM = tableExplorerManager;
tableExplorerManager.addPropertyChangeListener(evt -> {
if (ExplorerManager.PROP_ROOT_CONTEXT.equals(evt.getPropertyName())) { if (ExplorerManager.PROP_ROOT_CONTEXT.equals(evt.getPropertyName())) {
SwingUtilities.invokeLater(this::setColumnWidths); SwingUtilities.invokeLater(this::setColumnWidths);
} else if (ExplorerManager.PROP_EXPLORED_CONTEXT.equals(evt.getPropertyName())) { } 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( proxyLookup = new ProxyLookup(
ExplorerUtils.createLookup(messageBrowserEM, getActionMap()), 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. * 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 * WARNING: Do NOT modify this code. The content of this method is always

View File

@ -15,9 +15,14 @@ FiltersPanel.refreshButton.text=Refresh
FiltersPanel.deviceRequiredLabel.text=Select at least one. FiltersPanel.deviceRequiredLabel.text=Select at least one.
FiltersPanel.accountTypeRequiredLabel.text=Select at least one. FiltersPanel.accountTypeRequiredLabel.text=Select at least one.
FiltersPanel.needsRefreshLabel.text=Displayed data is out of date. Press Refresh. 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 CVTTopComponent.vizPanel.TabConstraints.tabTitle=Visualize
VisualizationPanel.jButton2.text=pan VisualizationPanel.jButton2.text=pan
CVTTopComponent.accountsBrowser.TabConstraints.tabTitle_1=Browse CVTTopComponent.accountsBrowser.TabConstraints.tabTitle_1=Browse
CVTTopComponent.browseVisualizeTabPane.AccessibleContext.accessibleName=Visualize CVTTopComponent.browseVisualizeTabPane.AccessibleContext.accessibleName=Visualize
CVTTopComponent.vizPanel.TabConstraints.tabTitle_1=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

View File

@ -18,7 +18,10 @@
*/ */
package org.sleuthkit.autopsy.communications; package org.sleuthkit.autopsy.communications;
import com.google.common.collect.ImmutableSet;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
import java.util.Collection;
import org.sleuthkit.datamodel.CommunicationsFilter;
/** /**
* Provide the singleton EventBus. * Provide the singleton EventBus.
@ -34,4 +37,36 @@ final class CVTEvents {
private 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<AccountDeviceInstanceKey> accountDeviceInstances;
private final boolean replace;
public boolean isReplace() {
return replace;
}
ImmutableSet<AccountDeviceInstanceKey> getAccountDeviceInstances() {
return accountDeviceInstances;
}
PinAccountsEvent(Collection<? extends AccountDeviceInstanceKey> accountDeviceInstances, boolean replace) {
this.accountDeviceInstances = ImmutableSet.copyOf(accountDeviceInstances);
this.replace = replace;
}
}
} }

View File

@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.communications;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.openide.explorer.ExplorerManager;
import org.openide.util.Lookup; import org.openide.util.Lookup;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.lookup.ProxyLookup; import org.openide.util.lookup.ProxyLookup;
@ -42,22 +41,13 @@ public final class CVTTopComponent extends TopComponent {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final ExplorerManager filterToTableEXplorerManager = new ExplorerManager();
@ThreadConfined(type = ThreadConfined.ThreadType.AWT) @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
public CVTTopComponent() { public CVTTopComponent() {
initComponents(); initComponents();
setName(Bundle.CVTTopComponent_name()); 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 * selections in the sub views can be exposed to context-sensitive
* actions. * actions.
*/ */
@ -69,13 +59,18 @@ public final class CVTTopComponent extends TopComponent {
proxyLookup.changeLookups(selectedComponent.getLookup()); 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 @Subscribe
public void pinAccount(PinAccountEvent pinEvent) { void pinAccount(CVTEvents.PinAccountsEvent pinEvent) {
browseVisualizeTabPane.setSelectedIndex(1); browseVisualizeTabPane.setSelectedIndex(1);
} }

View File

@ -1,26 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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();
}

View File

@ -31,9 +31,6 @@ import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.swing.JCheckBox; import javax.swing.JCheckBox;
import javax.swing.JPanel; 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.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_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.AccountTypeFilter;
import org.sleuthkit.datamodel.CommunicationsFilter.DateRangeFilter; import org.sleuthkit.datamodel.CommunicationsFilter.DateRangeFilter;
import org.sleuthkit.datamodel.CommunicationsFilter.DeviceFilter; import org.sleuthkit.datamodel.CommunicationsFilter.DeviceFilter;
import org.sleuthkit.datamodel.CommunicationsManager;
import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.DataSource;
import static org.sleuthkit.datamodel.Relationship.Type.CALL_LOG; import static org.sleuthkit.datamodel.Relationship.Type.CALL_LOG;
import static org.sleuthkit.datamodel.Relationship.Type.MESSAGE; 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 * Panel that holds the Filter control widgets and triggers queries against the
* CommunicationsManager on user filtering changes. * 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 long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(FiltersPanel.class.getName()); private static final Logger logger = Logger.getLogger(FiltersPanel.class.getName());
private ExplorerManager em;
@ThreadConfined(type = ThreadConfined.ThreadType.AWT) @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
private final Map<Account.Type, JCheckBox> accountTypeMap = new HashMap<>(); private final Map<Account.Type, JCheckBox> accountTypeMap = new HashMap<>();
@ThreadConfined(type = ThreadConfined.ThreadType.AWT) @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@ -134,10 +128,6 @@ final public class FiltersPanel extends JPanel implements FilterProvider {
refreshButton.addActionListener(e -> applyFilters()); refreshButton.addActionListener(e -> applyFilters());
} }
void setExplorerManager(ExplorerManager explorerManager) {
em = explorerManager;
}
/** /**
* Validate that filters are in a consistent state and will result in some * 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 * 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() { void updateAndApplyFilters() {
updateFilters(); updateFilters();
if (em != null) { applyFilters();
applyFilters();
}
} }
private void updateTimeZone() { private void updateTimeZone() {
@ -224,8 +212,7 @@ final public class FiltersPanel extends JPanel implements FilterProvider {
return jCheckBox; return jCheckBox;
}); });
} }
} });
);
} }
/** /**
@ -490,27 +477,15 @@ final public class FiltersPanel extends JPanel implements FilterProvider {
}// </editor-fold>//GEN-END:initComponents }// </editor-fold>//GEN-END:initComponents
/** /**
* Push a new root AccountDeviceInstanceNodeFactory with he current filters * Post an event with the new filters.
* into the explorer manager. The factory will do he actual queries.
*
*
*/ */
private void applyFilters() { private void applyFilters() {
CommunicationsFilter commsFilter = getFilter(); CVTEvents.getCVTEventBus().post(new CVTEvents.FilterChangeEvent(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);
}
needsRefresh = false; needsRefresh = false;
validateFilters(); validateFilters();
} }
@Override private CommunicationsFilter getFilter() {
public CommunicationsFilter getFilter() {
CommunicationsFilter commsFilter = new CommunicationsFilter(); CommunicationsFilter commsFilter = new CommunicationsFilter();
commsFilter.addAndFilter(getDeviceFilter()); commsFilter.addAndFilter(getDeviceFilter());
commsFilter.addAndFilter(getAccountTypeFilter()); commsFilter.addAndFilter(getAccountTypeFilter());

View File

@ -18,10 +18,10 @@
*/ */
package org.sleuthkit.autopsy.communications; 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.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.JPanel; import javax.swing.JPanel;
import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerManager;
import org.openide.nodes.Node; 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.corecomponents.TableFilterNode;
import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; import org.sleuthkit.autopsy.directorytree.DataResultFilterNode;
import org.sleuthkit.datamodel.AccountDeviceInstance; 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 * 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. * context-sensitive actions.
*/ */
@NbBundle.Messages({"MessageBrowser.DataResultViewerTable.title=Messages"}) @NbBundle.Messages({"MessageBrowser.DataResultViewerTable.title=Messages"})
MessageBrowser(ExplorerManager tableEM, ExplorerManager gacExplorerManager) { MessageBrowser(ExplorerManager tableEM, ExplorerManager gacExplorerManager) {
this.tableEM = tableEM; this.tableEM = tableEM;
this.gacExplorerManager = gacExplorerManager; this.gacExplorerManager = gacExplorerManager;
initComponents(); initComponents();
@ -69,43 +67,40 @@ public final class MessageBrowser extends JPanel implements ExplorerManager.Prov
Bundle.MessageBrowser_DataResultViewerTable_title())); Bundle.MessageBrowser_DataResultViewerTable_title()));
messagesResultPanel.open(); messagesResultPanel.open();
this.tableEM.addPropertyChangeListener(pce -> { this.tableEM.addPropertyChangeListener(new PropertyChangeListener() {
if (pce.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { @Override
final Node[] selectedNodes = this.tableEM.getSelectedNodes(); public void propertyChange(PropertyChangeEvent pce) {
if (pce.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
messagesResultPanel.setNumMatches(0); final Node[] selectedNodes = MessageBrowser.this.tableEM.getSelectedNodes();
messagesResultPanel.setNode(null); messagesResultPanel.setNumMatches(0);
messagesResultPanel.setNode(null);
if (selectedNodes.length == 0) {
//reset panel when there is no selection
messagesResultPanel.setPath(""); messagesResultPanel.setPath("");
} else { if (selectedNodes.length > 0) {
final Node selectedNode = selectedNodes[0]; Node rootNode;
if (selectedNode instanceof AccountDeviceInstanceNode) { final Node selectedNode = selectedNodes[0];
AccountDeviceInstanceNode adiNode = (AccountDeviceInstanceNode) selectedNode;
CommunicationsFilter filter = adiNode.getFilter();
CommunicationsManager commsManager = adiNode.getCommsManager();
final Set<AccountDeviceInstance> accountDeviceInstances;
if (selectedNodes.length == 1) { if (selectedNode instanceof AccountDeviceInstanceNode) {
final AccountDeviceInstance accountDeviceInstance = adiNode.getAccountDeviceInstance(); rootNode = makeRootNodeFromAccountDeviceInstanceNodes(selectedNodes);
accountDeviceInstances = Collections.singleton(accountDeviceInstance);
messagesResultPanel.setPath(accountDeviceInstance.getAccount().getTypeSpecificID());
} else { } else {
accountDeviceInstances = Stream.of(selectedNodes) rootNode = selectedNode;
.map(node -> (AccountDeviceInstanceNode) node)
.map(AccountDeviceInstanceNode::getAccountDeviceInstance)
.collect(Collectors.toSet());
messagesResultPanel.setPath(selectedNodes.length + " accounts");
} }
AccountDetailsNode accountDetailsNode = messagesResultPanel.setPath(rootNode.getDisplayName());
new AccountDetailsNode(accountDeviceInstances, filter, commsManager); messagesResultPanel.setNode(new TableFilterNode(new DataResultFilterNode(rootNode, gacExplorerManager), true));
TableFilterNode wrappedNode =
new TableFilterNode(new DataResultFilterNode(accountDetailsNode, gacExplorerManager), true);
messagesResultPanel.setNode(wrappedNode);
} }
} }
} }
private Node makeRootNodeFromAccountDeviceInstanceNodes(final Node[] selectedNodes) {
//Use lookup here?
AccountDeviceInstanceNode adiNode = (AccountDeviceInstanceNode) selectedNodes[0];
final Set<AccountDeviceInstance> accountDeviceInstances = new HashSet<>();
for (Node n : selectedNodes) {
//Use lookup here?
accountDeviceInstances.add(((AccountDeviceInstanceNode) n).getAccountDeviceInstance());
}
return SelectionNode.createFromAccounts(accountDeviceInstances, adiNode.getFilter(), adiNode.getCommsManager());
}
}); });
} }

View File

@ -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<AccountDeviceInstanceKey> accountDeviceInstances;
ImmutableSet<AccountDeviceInstanceKey> getAccountDeviceInstances() {
return accountDeviceInstances;
}
PinAccountEvent(Collection<? extends AccountDeviceInstanceKey> accountDeviceInstances) {
this.accountDeviceInstances = ImmutableSet.copyOf(accountDeviceInstances);
}
}

View File

@ -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<BlackboardArtifact> {
private final Collection<BlackboardArtifact> artifacts;
public RelaionshipSetNodeFactory(Collection<BlackboardArtifact> artifacts) {
this.artifacts = artifacts;
}
@Override
protected boolean createKeys(List<BlackboardArtifact> list) {
list.addAll(artifacts);
return true;
}
@Override
protected Node createNodeForKey(BlackboardArtifact key) {
return new RelationshipNode(key);
}
}

View File

@ -47,7 +47,7 @@ final class RelationshipNode extends BlackboardArtifactNode {
private static final Logger logger = Logger.getLogger(RelationshipNode.class.getName()); private static final Logger logger = Logger.getLogger(RelationshipNode.class.getName());
RelationshipNode(BlackboardArtifact artifact) { RelationshipNode(BlackboardArtifact artifact) {
super(artifact); super(artifact);
final String stripEnd = StringUtils.stripEnd(artifact.getDisplayName(), "s"); final String stripEnd = StringUtils.stripEnd(artifact.getDisplayName(), "s");
String removeEndIgnoreCase = StringUtils.removeEndIgnoreCase(stripEnd, "message"); 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();
}
} }

View File

@ -0,0 +1,133 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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<Content> edgeRelationshipArtifacts,
Set<AccountDeviceInstance> 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<AccountDeviceInstance> 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<Content> {
static final private Logger logger = Logger.getLogger(RelationshipChildren.class.getName());
private final Set<Content> edgeRelationshipArtifacts;
private final Set<AccountDeviceInstance> accountDeviceInstances;
private final CommunicationsManager commsManager;
private final CommunicationsFilter filter;
private RelationshipChildren(Set<Content> selectedEdgeRelationshipSources, Set<AccountDeviceInstance> selecedAccountDeviceInstances, CommunicationsManager commsManager, CommunicationsFilter filter) {
this.edgeRelationshipArtifacts = selectedEdgeRelationshipSources;
this.accountDeviceInstances = selecedAccountDeviceInstances;
this.commsManager = commsManager;
this.filter = filter;
}
@Override
protected boolean createKeys(List<Content> list) {
try {
final Set<Content> 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.");
}
}
}
}

View File

@ -4,19 +4,23 @@
<AuxValues> <AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/> <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/> <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/> <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/> <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/> <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/> <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="2"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/> <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/> <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/> <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/> <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,0,52,0,0,2,117"/>
</AuxValues> </AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/> <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents> <SubComponents>
<Container class="javax.swing.JSplitPane" name="splitPane"> <Container class="javax.swing.JSplitPane" name="splitPane">
<Properties>
<Property name="dividerLocation" type="int" value="400"/>
<Property name="resizeWeight" type="double" value="0.5"/>
</Properties>
<Constraints> <Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription"> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="Center"/> <BorderConstraints direction="Center"/>
@ -40,7 +44,7 @@
</Properties> </Properties>
<Constraints> <Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription"> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="First"/> <BorderConstraints direction="North"/>
</Constraint> </Constraint>
</Constraints> </Constraints>
@ -59,6 +63,19 @@
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButton2ActionPerformed"/> <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButton2ActionPerformed"/>
</Events> </Events>
</Component> </Component>
<Component class="javax.swing.JButton" name="jButton6">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.jButton6.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="focusable" type="boolean" value="false"/>
<Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="verticalTextPosition" type="int" value="3"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButton6ActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="jButton1"> <Component class="javax.swing.JButton" name="jButton1">
<Properties> <Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
@ -72,6 +89,58 @@
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButton1ActionPerformed"/> <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButton1ActionPerformed"/>
</Events> </Events>
</Component> </Component>
<Component class="javax.swing.JButton" name="jButton3">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.jButton3.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="focusable" type="boolean" value="false"/>
<Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="verticalTextPosition" type="int" value="3"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButton3ActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="jButton7">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.jButton7.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="focusable" type="boolean" value="false"/>
<Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="verticalTextPosition" type="int" value="3"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButton7ActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="jButton4">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.jButton4.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="focusable" type="boolean" value="false"/>
<Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="verticalTextPosition" type="int" value="3"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButton4ActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="jButton5">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.jButton5.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="focusable" type="boolean" value="false"/>
<Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="verticalTextPosition" type="int" value="3"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButton5ActionPerformed"/>
</Events>
</Component>
</SubComponents> </SubComponents>
</Container> </Container>
</SubComponents> </SubComponents>

View File

@ -18,32 +18,56 @@
*/ */
package org.sleuthkit.autopsy.communications; 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.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.mxCell;
import com.mxgraph.model.mxICell;
import com.mxgraph.swing.handler.mxRubberband;
import com.mxgraph.swing.mxGraphComponent; import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.util.mxConstants; 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.mxGraph;
import com.mxgraph.view.mxGraphView; import com.mxgraph.view.mxGraphView;
import com.mxgraph.view.mxStylesheet; import com.mxgraph.view.mxStylesheet;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Color; 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.beans.PropertyVetoException;
import java.util.Arrays;
import java.util.Collection;
import static java.util.Collections.singleton;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; 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.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.ExplorerManager;
import org.openide.explorer.ExplorerUtils; import org.openide.explorer.ExplorerUtils;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node; import org.openide.nodes.Node;
import org.openide.util.Exceptions;
import org.openide.util.Lookup; import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.lookup.ProxyLookup; import org.openide.util.lookup.ProxyLookup;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_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.AccountDeviceInstance;
import org.sleuthkit.datamodel.CommunicationsFilter; import org.sleuthkit.datamodel.CommunicationsFilter;
import org.sleuthkit.datamodel.CommunicationsManager; import org.sleuthkit.datamodel.CommunicationsManager;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException; 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 * Tool. Hosts an JGraphX mxGraphComponent that host the communications network
* visualization and a MessageBrowser for viewing details of communications. * 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 { final public class VisualizationPanel extends JPanel implements Lookup.Provider {
private static final long serialVersionUID = 1L; 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 final private mxStylesheet mxStylesheet = new mxStylesheet();
static { static {
//initialize defaul cell (Vertex and/or Edge) properties //initialize defaul cell (Vertex and/or Edge) properties
mxStylesheet.getDefaultVertexStyle().put(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_ELLIPSE); mxStylesheet.getDefaultVertexStyle().put(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_ELLIPSE);
mxStylesheet.getDefaultVertexStyle().put(mxConstants.STYLE_FONTCOLOR, "#000000");
mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_NOLABEL, true); 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 mxGraphComponent graphComponent;
private final mxGraph graph; private final mxGraph graph;
private final Map<String, mxCell> nodeMap = new HashMap<>(); private final Map<String, mxCell> nodeMap = new HashMap<>();
private final Multimap<Content, mxCell> edgeMap = MultimapBuilder.hashKeys().hashSetValues().build();
private CommunicationsManager commsManager; private CommunicationsManager commsManager;
private final HashSet<AccountDeviceInstanceKey> pinnedAccountDevices = new HashSet<>();
void setFilterProvider(FilterProvider filterProvider) { private CommunicationsFilter currentFilter;
this.filterProvider = filterProvider; private final mxRubberband rubberband;
}
private FilterProvider filterProvider;
public VisualizationPanel() { public VisualizationPanel() {
initComponents(); initComponents();
graph = new mxGraph(); graph = new mxGraph();
graph.setCellsCloneable(false);
graph.setDropEnabled(false);
graph.setCellsCloneable(false);
graph.setCellsEditable(false); graph.setCellsEditable(false);
graph.setCellsResizable(false); graph.setCellsResizable(false);
graph.setCellsMovable(true); graph.setCellsMovable(true);
@ -103,40 +134,53 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
graph.setDisconnectOnMove(false); graph.setDisconnectOnMove(false);
graph.setEdgeLabelsMovable(false); graph.setEdgeLabelsMovable(false);
graph.setVertexLabelsMovable(false); graph.setVertexLabelsMovable(false);
graph.setAutoOrigin(true); graph.setAllowDanglingEdges(false);
graph.setCellsBendable(true);
graph.setKeepEdgesInBackground(true);
graph.setStylesheet(mxStylesheet);
graphComponent = new mxGraphComponent(graph); graphComponent = new mxGraphComponent(graph);
graphComponent.setAutoExtend(true);
graphComponent.setAutoScroll(true); graphComponent.setAutoScroll(true);
graphComponent.setAutoscrolls(true);
graphComponent.setConnectable(false);
graphComponent.setKeepSelectionVisibleOnZoom(true);
graphComponent.setOpaque(true); graphComponent.setOpaque(true);
graphComponent.setBackground(Color.WHITE); graphComponent.setBackground(Color.WHITE);
jPanel1.add(graphComponent, BorderLayout.CENTER); 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) -> { //right click handler
Object[] selectionCells = graph.getSelectionCells(); graphComponent.getGraphControl().addMouseListener(new MouseAdapter() {
if (selectionCells.length == 1) { @Override
mxCell selectionCell = (mxCell) selectionCells[0]; public void mouseClicked(MouseEvent e) {
try { 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()) { jPopupMenu.show(graphComponent.getGraphControl(), e.getX(), e.getY());
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));
} }
} 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 @Override
@ -144,77 +188,105 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
return proxyLookup; return proxyLookup;
} }
@Subscribe
public void pinAccounts(PinAccountEvent pinEvent) {
final Set<AccountDeviceInstanceKey> 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<AccountDeviceInstance> 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) { private mxCell getOrCreateVertex(AccountDeviceInstanceKey accountDeviceInstanceKey) {
final AccountDeviceInstance accountDeviceInstance = accountDeviceInstanceKey.getAccountDeviceInstance(); final AccountDeviceInstance accountDeviceInstance = accountDeviceInstanceKey.getAccountDeviceInstance();
final String name =// accountDeviceInstance.getDeviceId() + ":" + final String name =// accountDeviceInstance.getDeviceId() + ":" +
accountDeviceInstance.getAccount().getTypeSpecificID(); accountDeviceInstance.getAccount().getTypeSpecificID();
mxCell vertex = nodeMap.get(name); final mxCell computeIfAbsent = nodeMap.computeIfAbsent(name, vertexName -> {
if (vertex == null) {
double size = Math.sqrt(accountDeviceInstanceKey.getMessageCount()) + 10; double size = Math.sqrt(accountDeviceInstanceKey.getMessageCount()) + 10;
vertex = (mxCell) graph.insertVertex(
mxCell vertex = (mxCell) graph.insertVertex(
graph.getDefaultParent(), graph.getDefaultParent(),
name, accountDeviceInstanceKey, vertexName, accountDeviceInstanceKey,
new Random().nextInt(200), 0,
new Random().nextInt(200), 0,
size, size,
size); size);
graph.getView().getState(vertex, true).setLabel(name); graph.getView().getState(vertex, true).setLabel(vertexName);
nodeMap.put(name, vertex); return vertex;
} });
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); Object[] edgesBetween = graph.getEdgesBetween(pinnedAccountVertex, relatedAccountVertex);
if (edgesBetween.length == 0) { if (edgesBetween.length == 0) {
final String edgeName = pinnedAccountVertex.getId() + " <-> " + relatedAccountVertex.getId(); 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) { } else if (edgesBetween.length == 1) {
final mxCell edge = (mxCell) edgesBetween[0]; final mxCell edge = (mxCell) edgesBetween[0];
edge.setValue(1d + (double) edge.getValue()); ((Collection<Content>) edge.getValue()).add(relSource);
edge.setStyle("strokeWidth=" + Math.log((double) edge.getValue())); 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<AccountDeviceInstance> relatedAccountDeviceInstances =
commsManager.getRelatedAccountDeviceInstances(adiKey.getAccountDeviceInstance(), currentFilter);
for (AccountDeviceInstance relatedADI : relatedAccountDeviceInstances) {
long adiRelationshipsCount = commsManager.getRelationshipSourcesCount(relatedADI, currentFilter);
List<Content> 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 @Override
public void addNotify() { public void addNotify() {
super.addNotify(); super.addNotify();
@ -232,8 +304,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
Case.addEventTypeSubscriber(EnumSet.of(CURRENT_CASE), evt -> { Case.addEventTypeSubscriber(EnumSet.of(CURRENT_CASE), evt -> {
graph.getModel().beginUpdate(); graph.getModel().beginUpdate();
try { try {
nodeMap.clear(); clearGraph();
graph.removeCells(graph.getChildCells(graph.getDefaultParent(), true, true));
} finally { } finally {
graph.getModel().endUpdate(); graph.getModel().endUpdate();
} }
@ -266,85 +337,220 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() { private void initComponents() {
splitPane = new javax.swing.JSplitPane(); splitPane = new JSplitPane();
jPanel1 = new javax.swing.JPanel(); jPanel1 = new JPanel();
jToolBar1 = new javax.swing.JToolBar(); jToolBar1 = new JToolBar();
jButton2 = new javax.swing.JButton(); jButton2 = new JButton();
jButton1 = new javax.swing.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); 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.setFocusable(false);
jButton2.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); jButton2.setHorizontalTextPosition(SwingConstants.CENTER);
jButton2.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); jButton2.setVerticalTextPosition(SwingConstants.BOTTOM);
jButton2.addActionListener(new java.awt.event.ActionListener() { jButton2.addActionListener(new ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) { public void actionPerformed(ActionEvent evt) {
jButton2ActionPerformed(evt); jButton2ActionPerformed(evt);
} }
}); });
jToolBar1.add(jButton2); 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.setFocusable(false);
jButton1.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); jButton1.setHorizontalTextPosition(SwingConstants.CENTER);
jButton1.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); jButton1.setVerticalTextPosition(SwingConstants.BOTTOM);
jButton1.addActionListener(new java.awt.event.ActionListener() { jButton1.addActionListener(new ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) { public void actionPerformed(ActionEvent evt) {
jButton1ActionPerformed(evt); jButton1ActionPerformed(evt);
} }
}); });
jToolBar1.add(jButton1); 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); splitPane.setLeftComponent(jPanel1);
add(splitPane, java.awt.BorderLayout.CENTER); add(splitPane, BorderLayout.CENTER);
}// </editor-fold>//GEN-END:initComponents }// </editor-fold>//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.addMouseListener(new mxPanningHandler(graphComponent));
graphComponent.getPanningHandler().setEnabled(true); graphComponent.setPanning(true);
}//GEN-LAST:event_jButton2ActionPerformed }//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(); applyOrganicLayout();
}//GEN-LAST:event_jButton1ActionPerformed }//GEN-LAST:event_jButton1ActionPerformed
private void applyOrganicLayout() { private void jButton3ActionPerformed(ActionEvent evt) {//GEN-FIRST:event_jButton3ActionPerformed
new mxOrganicLayout(graph).execute(graph.getDefaultParent());
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(); mxGraphView view = graphComponent.getGraph().getView();
int compLen = graphComponent.getWidth(); int compLen = graphComponent.getWidth();
int viewLen = (int) view.getGraphBounds().getWidth(); int viewLen = (int) view.getGraphBounds().getWidth();
view.setScale((double) compLen / viewLen * view.getScale()); // view.setScale((double) compLen / viewLen * view.getScale());
graphComponent.zoomAndCenter();
}
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 // Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton jButton1; private JButton jButton1;
private javax.swing.JButton jButton2; private JButton jButton2;
private javax.swing.JPanel jPanel1; private JButton jButton3;
private javax.swing.JToolBar jToolBar1; private JButton jButton4;
private javax.swing.JSplitPane splitPane; 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 // 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) { @Override
Children.Array array = new Children.Array();
array.add(nodes);
return new SimpleParentNode(array);
}
private SimpleParentNode(Children children) { @SuppressWarnings("unchecked")
super(children); 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<Content> relationshipSources = new HashSet<>();
HashSet<AccountDeviceInstance> adis = new HashSet<>();
for (mxICell cell : selectedCells) {
if (cell.isEdge()) {
relationshipSources.addAll((Set<Content>) 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);
}
} }
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB