diff --git a/Core/src/org/sleuthkit/autopsy/communications/Vertex_Label_template.html b/Core/src/org/sleuthkit/autopsy/communications/Vertex_Label_template.html
index dbc3f85a2c..4e489c992a 100644
--- a/Core/src/org/sleuthkit/autopsy/communications/Vertex_Label_template.html
+++ b/Core/src/org/sleuthkit/autopsy/communications/Vertex_Label_template.html
@@ -1,10 +1,3 @@
-
- {{#pinned}}
-

- {{/pinned}}
- {{#locked}}
-

- {{/locked}}
-

- {{accountName}}
-
+
+ {{#pinned}}

{{/pinned}}{{#locked}}

{{/locked}}

{{accountName}}
+
\ No newline at end of file
diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java
index 4ac198f1c4..217bbb168b 100644
--- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java
@@ -154,7 +154,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
rubberband = new mxRubberband(graphComponent);
final mxEventSource.mxIEventListener scaleListener = (Object sender, mxEventObject evt) ->
- zoomLabel.setText(DecimalFormat.getPercentInstance().format(graph.getScale()));
+ zoomLabel.setText(DecimalFormat.getPercentInstance().format(graph.getView().getScale()));
graph.getView().addListener(mxEvent.SCALE, scaleListener);
graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, scaleListener);
@@ -164,9 +164,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
public void mouseWheelMoved(final MouseWheelEvent e) {
super.mouseWheelMoved(e);
if (e.getPreciseWheelRotation() > 0) {
- graphComponent.zoomTo(graph.getScale() / graphComponent.getZoomFactor(), true);
+ graphComponent.zoomIn();
} else if (e.getPreciseWheelRotation() < 0) {
- graphComponent.zoomTo(graph.getScale() * graphComponent.getZoomFactor(), true);
+ graphComponent.zoomOut();
}
}
});
@@ -291,7 +291,10 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
}
private void rebuildGraph() throws TskCoreException {
- if (graph.hasPinnedAccounts()) {
+ if (graph.isEmpty()) {
+ borderLayoutPanel.remove(graphComponent);
+ borderLayoutPanel.add(placeHolderPanel, BorderLayout.CENTER);
+ } else {
borderLayoutPanel.remove(placeHolderPanel);
borderLayoutPanel.add(graphComponent, BorderLayout.CENTER);
if (worker != null) {
@@ -303,15 +306,16 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (worker.isDone()) {
- applyOrganicLayout(10);
+ if (graph.getModel().getChildCount(graph.getDefaultParent()) < 64) {
+ applyOrganicLayout(10);
+ } else {
+ statusLabel.setText("Too many cells, layout aborted.");
+ }
}
}
});
worker.execute();
- } else {
- borderLayoutPanel.remove(graphComponent);
- borderLayoutPanel.add(placeHolderPanel, BorderLayout.CENTER);
}
}
@@ -665,22 +669,28 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
private void morph(mxIGraphLayout layout) {
// layout using morphing
graph.getModel().beginUpdate();
- try {
- progressBar.setVisible(true);
- progressBar.setIndeterminate(true);
- progressBar.setString("applying layout");
- layout.execute(graph.getDefaultParent());
- } finally {
- mxMorphing morph = new mxMorphing(graphComponent, 20, 1.2, 20);
- morph.addListener(mxEvent.DONE, (Object sender, mxEventObject event) -> {
- graph.getModel().endUpdate();
- fitGraph();
- progressBar.setVisible(false);
- progressBar.setValue(0);
- });
- morph.startAnimation();
- }
+ progressBar.setVisible(true);
+ progressBar.setIndeterminate(true);
+ progressBar.setString("applying layout");
+
+ new SwingWorker() {
+ @Override
+ protected Void doInBackground() throws Exception {
+ layout.execute(graph.getDefaultParent());
+ mxMorphing morph = new mxMorphing(graphComponent, 20, 1.2, 20);
+ morph.addListener(mxEvent.DONE, (Object sender, mxEventObject event) -> {
+ graph.getModel().endUpdate();
+ fitGraph();
+ progressBar.setVisible(false);
+ progressBar.setValue(0);
+ });
+
+ morph.startAnimation();
+ return null;
+ }
+ }.execute();
+
}
// Variables declaration - do not modify//GEN-BEGIN:variables
@@ -709,9 +719,8 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
final private class SelectionListener implements mxEventSource.mxIEventListener {
- @Override
-
@SuppressWarnings("unchecked")
+ @Override
public void invoke(Object sender, mxEventObject evt) {
Object[] selectionCells = graph.getSelectionCells();
Node rootNode = Node.EMPTY;
@@ -814,7 +823,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
final private class mxFastOrganicLayoutImpl extends mxFastOrganicLayout {
- private mxFastOrganicLayoutImpl(mxGraph graph) {
+ mxFastOrganicLayoutImpl(mxGraph graph) {
super(graph);
}
@@ -836,7 +845,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
final private class mxCircleLayoutImpl extends mxCircleLayout {
- private mxCircleLayoutImpl(mxGraph graph) {
+ mxCircleLayoutImpl(mxGraph graph) {
super(graph);
setResetEdges(true);
}
@@ -859,7 +868,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
final private class mxOrganicLayoutImpl extends mxOrganicLayout {
- private mxOrganicLayoutImpl(mxGraph graph) {
+ mxOrganicLayoutImpl(mxGraph graph) {
super(graph);
setResetEdges(true);
}
@@ -882,7 +891,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
final private class mxHierarchicalLayoutImpl extends mxHierarchicalLayout {
- private mxHierarchicalLayoutImpl(mxGraph graph) {
+ mxHierarchicalLayoutImpl(mxGraph graph) {
super(graph);
}
diff --git a/Core/src/org/sleuthkit/autopsy/communications/mxGraphImpl.java b/Core/src/org/sleuthkit/autopsy/communications/mxGraphImpl.java
index 6b9a2b9391..72210e37ff 100644
--- a/Core/src/org/sleuthkit/autopsy/communications/mxGraphImpl.java
+++ b/Core/src/org/sleuthkit/autopsy/communications/mxGraphImpl.java
@@ -106,33 +106,6 @@ final class mxGraphImpl extends mxGraph {
setKeepEdgesInBackground(true);
setResetEdgesOnMove(true);
setHtmlLabels(true);
-
-// new mxLayoutManager(graph) {
-// final private mxOrganicLayout layout;
-// private int counter;
-// {
-// this.layout = new mxOrganicLayout(graph);
-// this.layout.setMaxIterations(1);
-// }
-//
-// @Override
-// protected void executeLayout(mxIGraphLayout layout, Object parent) {
-// if (counter % 10 == 0)
-// {
-// super.executeLayout(layout, parent);
-//// fitGraph();
-// }
-// counter++;
-// }
-//
-// @Override
-// public mxIGraphLayout getLayout(Object parent) {
-// if (graph.getModel().getChildCount(parent) > 0) {
-// return layout;
-// }
-// return null;
-// }
-// };
}
void clear() {
@@ -156,10 +129,8 @@ final class mxGraphImpl extends mxGraph {
scopes.put("accountName", adiKey.getAccountDeviceInstance().getAccount().getTypeSpecificID());
scopes.put("size", Math.round(Math.log(adiKey.getMessageCount()) + 5));
-
- scopes.put("iconFileName", mxGraphImpl.class
- .getResource("/org/sleuthkit/autopsy/communications/images/"
- + Utils.getIconFileName(adiKey.getAccountDeviceInstance().getAccount().getAccountType())));
+ scopes.put("iconFileName", mxGraphImpl.class.getResource("/org/sleuthkit/autopsy/communications/images/"
+ + Utils.getIconFileName(adiKey.getAccountDeviceInstance().getAccount().getAccountType())));
scopes.put("pinned", pinnedAccountDevices.contains(adiKey));
scopes.put("MARKER_PIN_URL", MARKER_PIN_URL);
scopes.put("locked", lockedVertices.contains((mxCell) cell));
@@ -175,25 +146,69 @@ final class mxGraphImpl extends mxGraph {
@Override
public String getToolTipForCell(Object cell) {
- return ((mxICell) cell).getId();
+ final StringWriter stringWriter = new StringWriter();
+ HashMap scopes = new HashMap<>();
+
+ Object value = getModel().getValue(cell);
+ if (value instanceof AccountDeviceInstanceKey) {
+ final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) value;
+
+ scopes.put("accountName", adiKey.getAccountDeviceInstance().getAccount().getTypeSpecificID());
+ scopes.put("size", 12);// Math.round(Math.log(adiKey.getMessageCount()) + 5));
+ scopes.put("iconFileName", mxGraphImpl.class.getResource("/org/sleuthkit/autopsy/communications/images/"
+ + Utils.getIconFileName(adiKey.getAccountDeviceInstance().getAccount().getAccountType())));
+ scopes.put("pinned", pinnedAccountDevices.contains(adiKey));
+ scopes.put("MARKER_PIN_URL", MARKER_PIN_URL);
+ scopes.put("locked", lockedVertices.contains((mxCell) cell));
+ scopes.put("LOCK_URL", LOCK_URL);
+
+ labelMustache.execute(stringWriter, scopes);
+
+ return stringWriter.toString();
+ } else {
+ return ((mxICell) cell).getId();
+ }
}
+ /**
+ * 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);
-
- final mxCellState state = getView().getState(vertex, true);
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);
@@ -204,8 +219,7 @@ final class mxGraphImpl extends mxGraph {
}
SwingWorker, ?> rebuild(ProgressIndicator progress, CommunicationsManager commsManager, CommunicationsFilter currentFilter) {
-
- return new SwingWorkerImpl(progress, commsManager, currentFilter);
+ return new RebuildWorker(progress, commsManager, currentFilter);
}
void resetGraph() {
@@ -261,12 +275,14 @@ final class mxGraphImpl extends mxGraph {
return edge;
}
- boolean hasPinnedAccounts() {
- return pinnedAccountDevices.isEmpty() == false;
- }
-
- double getScale() {
- return getView().getScale();
+ /**
+ * 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) {
@@ -274,13 +290,17 @@ final class mxGraphImpl extends mxGraph {
}
- private class SwingWorkerImpl extends SwingWorker {
+ /**
+ * SwingWorker that loads the accounts and edges for this graph according to
+ * the pinned accounts and the current filters.
+ */
+ private class RebuildWorker extends SwingWorker {
private final ProgressIndicator progress;
private final CommunicationsManager commsManager;
private final CommunicationsFilter currentFilter;
- SwingWorkerImpl(ProgressIndicator progress, CommunicationsManager commsManager, CommunicationsFilter currentFilter) {
+ RebuildWorker(ProgressIndicator progress, CommunicationsManager commsManager, CommunicationsFilter currentFilter) {
this.progress = progress;
this.currentFilter = currentFilter;
this.commsManager = commsManager;