diff --git a/Core/src/org/sleuthkit/autopsy/communications/CommunicationsGraph.java b/Core/src/org/sleuthkit/autopsy/communications/CommunicationsGraph.java index b7559141ec..dd0adef1d4 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/CommunicationsGraph.java +++ b/Core/src/org/sleuthkit/autopsy/communications/CommunicationsGraph.java @@ -20,9 +20,9 @@ package org.sleuthkit.autopsy.communications; import com.github.mustachejava.DefaultMustacheFactory; import com.github.mustachejava.Mustache; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; +import com.google.common.eventbus.Subscribe; import com.mxgraph.model.mxCell; import com.mxgraph.model.mxICell; import com.mxgraph.util.mxConstants; @@ -44,6 +44,7 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.SwingWorker; +import org.sleuthkit.autopsy.communications.visualization.EventHandler; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.progress.ProgressIndicator; import org.sleuthkit.datamodel.AccountDeviceInstance; @@ -52,6 +53,10 @@ import org.sleuthkit.datamodel.CommunicationsManager; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; +/** + * Implementation of mxGraph customized for our use in the CVT visualize mode. + * Acts as the primary entry point into the JGraphX API. + */ final class CommunicationsGraph extends mxGraph { private static final Logger logger = Logger.getLogger(CommunicationsGraph.class.getName()); @@ -68,12 +73,11 @@ final class CommunicationsGraph extends mxGraph { labelMustache = new DefaultMustacheFactory().compile(new InputStreamReader(templateStream), "Vertex_Label"); } + /** + * Style sheet for default vertex and edge styles. These are initialized in + * the static block below. + */ static final private mxStylesheet mxStylesheet = new mxStylesheet(); - private final Set pinnedAccountDevices = new HashSet<>(); - private final Set lockedVertices = new HashSet<>(); - - private final Map nodeMap = new HashMap<>(); - private final Multimap edgeMap = MultimapBuilder.hashKeys().hashSetValues().build(); static { //initialize defaul vertex properties @@ -88,8 +92,22 @@ final class CommunicationsGraph extends mxGraph { mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_STARTARROW, mxConstants.NONE); } + /** + * Map from type specific account identifier to mxCell(vertex). + */ + private final Map nodeMap = new HashMap<>(); + + /** + * Map from relationship source (Content) to mxCell (edge). + */ + private final Multimap edgeMap = MultimapBuilder.hashKeys().hashSetValues().build(); + private final LockedVertexModel lockedVertexModel; + + private final PinnedAccountModel pinnedAccountModel; + CommunicationsGraph() { super(mxStylesheet); + //set fixed properties of graph. setAutoSizeCells(true); setCellsCloneable(false); setDropEnabled(false); @@ -107,6 +125,33 @@ final class CommunicationsGraph extends mxGraph { setKeepEdgesInBackground(true); setResetEdgesOnMove(true); setHtmlLabels(true); + + lockedVertexModel = new LockedVertexModel(); + lockedVertexModel.registerhandler(new EventHandler() { + @Override + @Subscribe + public void handle(LockedVertexModel.VertexLockEvent event) { + if (event.isVertexLocked()) { + getView().clear(event.getVertex(), true, true); + getView().validate(); + } else { + final mxCellState state = getView().getState(event.getVertex(), true); + getView().updateLabel(state); + getView().updateLabelBounds(state); + getView().updateBoundingBox(state); + } + } + }); + + pinnedAccountModel = new PinnedAccountModel(this); + } + + public LockedVertexModel getLockedVertexModel() { + return lockedVertexModel; + } + + public PinnedAccountModel getPinnedAccountModel() { + return pinnedAccountModel; } void clear() { @@ -115,10 +160,6 @@ final class CommunicationsGraph extends mxGraph { removeCells(getChildVertices(getDefaultParent())); } - boolean isAccountPinned(AccountDeviceInstanceKey account) { - return pinnedAccountDevices.contains(account); - } - @Override public String convertValueToString(Object cell) { final StringWriter stringWriter = new StringWriter(); @@ -132,9 +173,9 @@ final class CommunicationsGraph extends mxGraph { scopes.put("size", Math.round(Math.log(adiKey.getMessageCount()) + 5)); scopes.put("iconFileName", CommunicationsGraph.class.getResource("/org/sleuthkit/autopsy/communications/images/" + Utils.getIconFileName(adiKey.getAccountDeviceInstance().getAccount().getAccountType()))); - scopes.put("pinned", pinnedAccountDevices.contains(adiKey)); + scopes.put("pinned", pinnedAccountModel.isAccountPinned(adiKey)); scopes.put("MARKER_PIN_URL", MARKER_PIN_URL); - scopes.put("locked", lockedVertices.contains((mxCell) cell)); + scopes.put("locked", lockedVertexModel.isVertexLocked((mxCell) cell)); scopes.put("LOCK_URL", LOCK_URL); labelMustache.execute(stringWriter, scopes); @@ -158,9 +199,9 @@ final class CommunicationsGraph extends mxGraph { scopes.put("size", 12);// Math.round(Math.log(adiKey.getMessageCount()) + 5)); scopes.put("iconFileName", CommunicationsGraph.class.getResource("/org/sleuthkit/autopsy/communications/images/" + Utils.getIconFileName(adiKey.getAccountDeviceInstance().getAccount().getAccountType()))); - scopes.put("pinned", pinnedAccountDevices.contains(adiKey)); + scopes.put("pinned", pinnedAccountModel.isAccountPinned(adiKey)); scopes.put("MARKER_PIN_URL", MARKER_PIN_URL); - scopes.put("locked", lockedVertices.contains((mxCell) cell)); + scopes.put("locked", lockedVertexModel.isVertexLocked((mxCell) cell)); scopes.put("LOCK_URL", LOCK_URL); labelMustache.execute(stringWriter, scopes); @@ -171,54 +212,6 @@ final class CommunicationsGraph extends mxGraph { } } - /** - * Unpin the given accounts from the graph. Pinned accounts will always be - * shown regardless of the filter state. Furthermore, accounts with - * relationships that pass the filters will also be shown. - * - * @param accountDeviceInstances The accounts to unpin. - */ - void unpinAccount(ImmutableSet accountDeviceInstances) { - pinnedAccountDevices.removeAll(accountDeviceInstances); - } - - /** - * Pin the given accounts to the graph. Pinned accounts will always be shown - * regardless of the filter state. Furthermore, accounts with relationships - * that pass the filters will also be shown. - * - * @param accountDeviceInstances The accounts to pin. - */ - void pinAccount(ImmutableSet accountDeviceInstances) { - pinnedAccountDevices.addAll(accountDeviceInstances); - } - - /** - * Lock the given vertex so that applying a layout algorithm doesn't move - * it. The user can still manually position the vertex. - * - * @param vertex The vertex to lock. - */ - void lockVertex(mxCell vertex) { - lockedVertices.add(vertex); - getView().clear(vertex, true, true); - getView().validate(); - } - - /** - * Lock the given vertex so that applying a layout algorithm can move it. - * - * @param vertex The vertex to unlock. - */ - void unlockVertex(mxCell vertex) { - lockedVertices.remove(vertex); - - final mxCellState state = getView().getState(vertex, true); - getView().updateLabel(state); - getView().updateLabelBounds(state); - getView().updateBoundingBox(state); - } - SwingWorker rebuild(ProgressIndicator progress, CommunicationsManager commsManager, CommunicationsFilter currentFilter) { return new RebuildWorker(progress, commsManager, currentFilter); } @@ -226,8 +219,8 @@ final class CommunicationsGraph extends mxGraph { void resetGraph() { clear(); getView().setScale(1); - pinnedAccountDevices.clear(); - lockedVertices.clear(); + pinnedAccountModel.clear(); + lockedVertexModel.clear(); } private mxCell getOrCreateVertex(AccountDeviceInstanceKey accountDeviceInstanceKey) { @@ -275,21 +268,6 @@ final class CommunicationsGraph extends mxGraph { return edge; } - /** - * Are there any accounts in this graph? If there are no pinned accounts the - * graph will be empty. - * - * @return True if this graph is empty. - */ - boolean isEmpty() { - return pinnedAccountDevices.isEmpty(); - } - - boolean isVertexLocked(mxCell vertex) { - return lockedVertices.contains(vertex); - - } - /** * SwingWorker that loads the accounts and edges for this graph according to * the pinned accounts and the current filters. @@ -317,7 +295,7 @@ final class CommunicationsGraph extends mxGraph { * set to keep track of accounts related to pinned accounts */ Set relatedAccounts = new HashSet<>(); - for (AccountDeviceInstanceKey adiKey : pinnedAccountDevices) { + for (AccountDeviceInstanceKey adiKey : pinnedAccountModel.getPinnedAccounts()) { if (isCancelled()) { break; } diff --git a/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java b/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java new file mode 100644 index 0000000000..fd84f6d31b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/LockedVertexModel.java @@ -0,0 +1,86 @@ +/* + * 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.eventbus.EventBus; +import com.mxgraph.model.mxCell; +import java.util.HashSet; +import java.util.Set; +import org.sleuthkit.autopsy.communications.visualization.EventHandler; + +class LockedVertexModel { + + void registerhandler(EventHandler handler) { + eventBus.register(handler); + } + + void unregisterhandler(EventHandler handler) { + eventBus.unregister(handler); + } + + private final EventBus eventBus = new EventBus(); + + /** + * Set of mxCells (vertices) that are 'locked'. Locked vertices are not + * affected by layout algorithms, but may be repositioned manually by the + * user. + */ + private final Set lockedVertices = new HashSet<>(); + + LockedVertexModel() { + } + + /** + * Lock the given vertex so that applying a layout algorithm doesn't move + * it. The user can still manually position the vertex. + * + * @param vertex The vertex to lock. + */ + void lockVertex(mxCell vertex) { + lockedVertices.add(vertex); + eventBus.post(new VertexLockEvent(vertex, true)); + + } + + /** + * Lock the given vertex so that applying a layout algorithm can move it. + * + * @param vertex The vertex to unlock. + */ + void unlockVertex(mxCell vertex) { + lockedVertices.remove(vertex); + eventBus.post(new VertexLockEvent(vertex, false)); + + } + + boolean isVertexLocked(mxCell vertex) { + return lockedVertices.contains(vertex); + + } + + void clear() { + lockedVertices.clear(); + } + + static class VertexLockEvent { + + private final mxCell vertex; + + public mxCell getVertex() { + return vertex; + } + + public boolean isVertexLocked() { + return locked; + } + private final boolean locked; + + VertexLockEvent(mxCell vertex, boolean locked) { + this.vertex = vertex; + this.locked = locked; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/PinnedAccountModel.java b/Core/src/org/sleuthkit/autopsy/communications/PinnedAccountModel.java new file mode 100644 index 0000000000..2afa5b8318 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/PinnedAccountModel.java @@ -0,0 +1,71 @@ +/* + * 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.HashSet; +import java.util.Set; + +class PinnedAccountModel { + + /** + * Set of AccountDeviceInstanceKeys that are 'Pinned' to this graph. Pinned + * accounts are shown regardless of filters, and accounts that are related + * to pinned accounts and pass the filters are show. Pinning accounts is the + * primary way to populate the graph. + */ + private final Set pinnedAccountDevices = new HashSet<>(); + private final CommunicationsGraph graph; + + PinnedAccountModel(CommunicationsGraph graph) { + this.graph = graph; + } + + boolean isAccountPinned(AccountDeviceInstanceKey account) { + return pinnedAccountDevices.contains(account); + } + + /** + * Unpin the given accounts from the graph. Pinned accounts will always be + * shown regardless of the filter state. Furthermore, accounts with + * relationships that pass the filters will also be shown. + * + * @param accountDeviceInstances The accounts to unpin. + */ + void unpinAccount(ImmutableSet accountDeviceInstances) { + pinnedAccountDevices.removeAll(accountDeviceInstances); + } + + /** + * Pin the given accounts to the graph. Pinned accounts will always be shown + * regardless of the filter state. Furthermore, accounts with relationships + * that pass the filters will also be shown. + * + * @param accountDeviceInstances The accounts to pin. + */ + void pinAccount(ImmutableSet accountDeviceInstances) { + pinnedAccountDevices.addAll(accountDeviceInstances); + } + + /** + * Are there any accounts in this graph? If there are no pinned accounts the + * graph will be empty. + * + * @return True if this graph is empty. + */ + boolean isEmpty() { + return pinnedAccountDevices.isEmpty(); + } + + void clear() { + pinnedAccountDevices.clear(); + } + + Iterable getPinnedAccounts() { + return ImmutableSet.copyOf(pinnedAccountDevices); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index 0d0991f840..12b66824e6 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -91,8 +91,9 @@ import org.sleuthkit.datamodel.TskCoreException; /** * 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. + * Tool. Hosts an JGraphX mxGraphComponent that implements the communications + * network visualization and a MessageBrowser for viewing details of + * communications. * * The Lookup provided by getLookup will be proxied by the lookup of the * CVTTopComponent when this tab is active allowing for context sensitive @@ -140,10 +141,14 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private SwingWorker worker; + private final PinnedAccountModel pinnedAccountModel; + private final LockedVertexModel lockedVertexModel; public VisualizationPanel() { initComponents(); graph = new CommunicationsGraph(); + pinnedAccountModel = graph.getPinnedAccountModel(); + lockedVertexModel = graph.getLockedVertexModel(); fastOrganicLayout = new mxFastOrganicLayoutImpl(graph); circleLayout = new mxCircleLayoutImpl(graph); @@ -193,22 +198,22 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider final JPopupMenu jPopupMenu = new JPopupMenu(); final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) cellAt.getValue(); - if (graph.isVertexLocked(cellAt)) { + if (lockedVertexModel.isVertexLocked(cellAt)) { jPopupMenu.add(new JMenuItem(new AbstractAction("UnLock " + cellAt.getId(), unlockIcon) { @Override public void actionPerformed(ActionEvent e) { - graph.unlockVertex(cellAt); + lockedVertexModel.unlockVertex(cellAt); } })); } else { jPopupMenu.add(new JMenuItem(new AbstractAction("Lock " + cellAt.getId(), lockIcon) { @Override public void actionPerformed(ActionEvent e) { - graph.lockVertex(cellAt); + lockedVertexModel.lockVertex(cellAt); } })); } - if (graph.isAccountPinned(adiKey)) { + if (pinnedAccountModel.isAccountPinned(adiKey)) { jPopupMenu.add(new JMenuItem(new AbstractAction("Unpin " + cellAt.getId(), unpinIcon) { @Override public void actionPerformed(ActionEvent e) { @@ -247,6 +252,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } @Override + public Lookup getLookup() { return proxyLookup; } @@ -254,7 +260,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider @Subscribe void handleUnPinEvent(CVTEvents.UnpinAccountsEvent pinEvent) { graph.getModel().beginUpdate(); - graph.unpinAccount(pinEvent.getAccountDeviceInstances()); + pinnedAccountModel.unpinAccount(pinEvent.getAccountDeviceInstances()); graph.clear(); rebuildGraph(); // Updates the display @@ -268,7 +274,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider if (pinEvent.isReplace()) { graph.resetGraph(); } - graph.pinAccount(pinEvent.getAccountDeviceInstances()); + pinnedAccountModel.pinAccount(pinEvent.getAccountDeviceInstances()); rebuildGraph(); // Updates the display graph.getModel().endUpdate(); @@ -289,7 +295,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private void rebuildGraph() { - if (graph.isEmpty()) { + if (pinnedAccountModel.isEmpty()) { borderLayoutPanel.remove(graphComponent); borderLayoutPanel.add(placeHolderPanel, BorderLayout.CENTER); repaint(); @@ -743,7 +749,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider @Override public boolean isVertexIgnored(Object vertex) { return super.isVertexIgnored(vertex) - || VisualizationPanel.this.graph.isVertexLocked((mxCell) vertex); + || lockedVertexModel.isVertexLocked((mxCell) vertex); } @Override @@ -766,7 +772,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider @Override public boolean isVertexIgnored(Object vertex) { return super.isVertexIgnored(vertex) - || VisualizationPanel.this.graph.isVertexLocked((mxCell) vertex); + || lockedVertexModel.isVertexLocked((mxCell) vertex); } @Override @@ -789,7 +795,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider @Override public boolean isVertexIgnored(Object vertex) { return super.isVertexIgnored(vertex) - || VisualizationPanel.this.graph.isVertexLocked((mxCell) vertex); + || lockedVertexModel.isVertexLocked((mxCell) vertex); } @Override @@ -811,7 +817,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider @Override public boolean isVertexIgnored(Object vertex) { return super.isVertexIgnored(vertex) - || VisualizationPanel.this.graph.isVertexLocked((mxCell) vertex); + || lockedVertexModel.isVertexLocked((mxCell) vertex); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/communications/visualization/EventHandler.java b/Core/src/org/sleuthkit/autopsy/communications/visualization/EventHandler.java new file mode 100644 index 0000000000..539da4be4a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/visualization/EventHandler.java @@ -0,0 +1,9 @@ +package org.sleuthkit.autopsy.communications.visualization; + +/** + * + */ +public interface EventHandler { + + void handle(T event); +}