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.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<AccountDeviceInstance> 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);
}
/**

View File

@ -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<? extends AccountDeviceInstanceKey> lookupAll =
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;
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

View File

@ -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

View File

@ -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<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 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);
}

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 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<Account.Type, JCheckBox> 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,10 +151,8 @@ final public class FiltersPanel extends JPanel implements FilterProvider {
*/
void updateAndApplyFilters() {
updateFilters();
if (em != null) {
applyFilters();
}
}
private void updateTimeZone() {
dateRangeLabel.setText("Date Range ( " + Utils.getUserPreferredZoneId().toString() + "):");
@ -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 {
}// </editor-fold>//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());

View File

@ -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
@ -69,43 +67,40 @@ public final class MessageBrowser extends JPanel implements ExplorerManager.Prov
Bundle.MessageBrowser_DataResultViewerTable_title()));
messagesResultPanel.open();
this.tableEM.addPropertyChangeListener(pce -> {
this.tableEM.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent pce) {
if (pce.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
final Node[] selectedNodes = this.tableEM.getSelectedNodes();
final Node[] selectedNodes = MessageBrowser.this.tableEM.getSelectedNodes();
messagesResultPanel.setNumMatches(0);
messagesResultPanel.setNode(null);
if (selectedNodes.length == 0) {
//reset panel when there is no selection
messagesResultPanel.setPath("");
} else {
if (selectedNodes.length > 0) {
Node rootNode;
final Node selectedNode = selectedNodes[0];
if (selectedNode instanceof AccountDeviceInstanceNode) {
AccountDeviceInstanceNode adiNode = (AccountDeviceInstanceNode) selectedNode;
CommunicationsFilter filter = adiNode.getFilter();
CommunicationsManager commsManager = adiNode.getCommsManager();
final Set<AccountDeviceInstance> accountDeviceInstances;
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<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

@ -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>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<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_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_variablesLocal" type="java.lang.Boolean" value="false"/>
<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>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<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>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="Center"/>
@ -40,7 +44,7 @@
</Properties>
<Constraints>
<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>
</Constraints>
@ -59,6 +63,19 @@
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButton2ActionPerformed"/>
</Events>
</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">
<Properties>
<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"/>
</Events>
</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>
</Container>
</SubComponents>

View File

@ -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<String, mxCell> nodeMap = new HashMap<>();
private final Multimap<Content, mxCell> edgeMap = MultimapBuilder.hashKeys().hashSetValues().build();
private CommunicationsManager commsManager;
void setFilterProvider(FilterProvider filterProvider) {
this.filterProvider = filterProvider;
}
private FilterProvider filterProvider;
private final HashSet<AccountDeviceInstanceKey> 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 {
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));
//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));
}
});
}
});
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<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) {
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);
}
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<Content>) 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<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
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
// <editor-fold defaultstate="collapsed" desc="Generated Code">//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);
}// </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.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<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