19 package org.sleuthkit.autopsy.communications;
21 import com.google.common.eventbus.Subscribe;
22 import com.mxgraph.layout.hierarchical.mxHierarchicalLayout;
23 import com.mxgraph.layout.mxCircleLayout;
24 import com.mxgraph.layout.mxFastOrganicLayout;
25 import com.mxgraph.layout.mxIGraphLayout;
26 import com.mxgraph.layout.mxOrganicLayout;
27 import com.mxgraph.model.mxCell;
28 import com.mxgraph.model.mxICell;
29 import com.mxgraph.swing.handler.mxRubberband;
30 import com.mxgraph.swing.mxGraphComponent;
31 import com.mxgraph.util.mxEvent;
32 import com.mxgraph.util.mxEventObject;
33 import com.mxgraph.util.mxEventSource;
34 import com.mxgraph.util.mxPoint;
35 import com.mxgraph.util.mxRectangle;
36 import com.mxgraph.util.mxUndoManager;
37 import com.mxgraph.util.mxUndoableEdit;
38 import com.mxgraph.view.mxCellState;
39 import com.mxgraph.view.mxGraph;
40 import com.mxgraph.view.mxGraphView;
41 import java.awt.BorderLayout;
42 import java.awt.Color;
43 import java.awt.Cursor;
44 import java.awt.Dimension;
46 import java.awt.Frame;
47 import java.awt.event.ActionEvent;
48 import java.awt.event.ActionListener;
49 import java.awt.event.MouseAdapter;
50 import java.awt.event.MouseEvent;
51 import java.awt.event.MouseWheelEvent;
52 import java.beans.PropertyChangeEvent;
53 import java.beans.PropertyVetoException;
54 import java.text.DecimalFormat;
55 import java.util.Arrays;
56 import java.util.EnumSet;
57 import java.util.HashMap;
58 import java.util.HashSet;
59 import java.util.List;
61 import java.util.concurrent.ExecutionException;
63 import java.util.concurrent.Future;
64 import java.util.function.BiConsumer;
65 import java.util.logging.Level;
66 import java.util.stream.Collectors;
67 import java.util.stream.Stream;
68 import javax.swing.AbstractAction;
69 import javax.swing.ImageIcon;
70 import javax.swing.JButton;
71 import javax.swing.JLabel;
72 import javax.swing.JMenuItem;
73 import javax.swing.JPanel;
74 import javax.swing.JPopupMenu;
75 import javax.swing.JSplitPane;
76 import javax.swing.JTextArea;
77 import javax.swing.JToolBar;
78 import javax.swing.SwingConstants;
79 import javax.swing.SwingUtilities;
80 import javax.swing.SwingWorker;
81 import org.jdesktop.layout.GroupLayout;
82 import org.jdesktop.layout.LayoutStyle;
83 import org.openide.explorer.ExplorerManager;
84 import org.openide.explorer.ExplorerUtils;
85 import org.openide.nodes.Node;
86 import org.openide.util.Lookup;
87 import org.openide.util.NbBundle;
88 import org.openide.util.lookup.ProxyLookup;
114 private static final String
BASE_IMAGE_PATH =
"/org/sleuthkit/autopsy/communications/images";
117 static final private ImageIcon
lockIcon
120 @NbBundle.Messages(
"VisualizationPanel.cancelButton.text=Cancel")
121 private static final String
CANCEL = Bundle.VisualizationPanel_cancelButton_text();
123 private final ExplorerManager
vizEM =
new ExplorerManager();
124 private final ExplorerManager
gacEM =
new ExplorerManager();
132 private final CommunicationsGraph
graph;
150 graphComponent =
new mxGraphComponent(graph);
151 graphComponent.setAutoExtend(
true);
152 graphComponent.setAutoScroll(
true);
153 graphComponent.setAutoscrolls(
true);
154 graphComponent.setConnectable(
false);
155 graphComponent.setDragEnabled(
false);
156 graphComponent.setKeepSelectionVisibleOnZoom(
true);
157 graphComponent.setOpaque(
true);
158 graphComponent.setToolTips(
true);
159 graphComponent.setBackground(Color.WHITE);
163 rubberband =
new mxRubberband(graphComponent);
167 final mxEventSource.mxIEventListener scaleListener = (Object sender, mxEventObject evt)
168 ->
zoomLabel.setText(DecimalFormat.getPercentInstance().format(graph.getView().getScale()));
169 graph.getView().addListener(mxEvent.SCALE, scaleListener);
170 graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, scaleListener);
173 graphComponent.getGraphControl().addMouseWheelListener(graphMouseListener);
174 graphComponent.getGraphControl().addMouseListener(graphMouseListener);
177 splitPane.setRightComponent(messageBrowser);
178 proxyLookup =
new ProxyLookup(
179 ExplorerUtils.createLookup(vizEM, getActionMap()),
185 final mxEventSource.mxIEventListener undoListener = (Object sender, mxEventObject evt)
186 -> undoManager.undoableEditHappened((mxUndoableEdit) evt.getProperty(
"edit"));
188 graph.getModel().addListener(mxEvent.UNDO, undoListener);
189 graph.getView().addListener(mxEvent.UNDO, undoListener);
194 organicLayout.setMaxIterations(10);
198 BiConsumer<JButton, NamedGraphLayout> configure = (layoutButton, layout) -> {
200 layoutButton.addActionListener(event ->
applyLayout(layout));
217 void handle(LockedVertexModel.VertexLockEvent event) {
218 final Set<mxCell> vertices =
event.getVertices();
219 mxGraphView view = graph.getView();
220 vertices.forEach(vertex -> {
221 final mxCellState state = view.getState(vertex,
true);
222 view.updateLabel(state);
223 view.updateLabelBounds(state);
224 view.updateBoundingBox(state);
225 graphComponent.redraw(state);
230 void handle(
final CVTEvents.UnpinAccountsEvent pinEvent) {
231 graph.getModel().beginUpdate();
236 graph.getModel().endUpdate();
240 void handle(
final CVTEvents.PinAccountsEvent pinEvent) {
241 graph.getModel().beginUpdate();
242 if (pinEvent.isReplace()) {
248 graph.getModel().endUpdate();
252 void handle(
final CVTEvents.FilterChangeEvent filterChangeEvent) {
253 graph.getModel().beginUpdate();
255 currentFilter = filterChangeEvent.getNewFilter();
258 graph.getModel().endUpdate();
261 @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
276 worker = graph.rebuild(progress, commsManager, currentFilter);
277 cancelationListener.configure(worker, progress);
278 worker.addPropertyChangeListener((
final PropertyChangeEvent evt) -> {
279 if (worker.isDone()) {
280 if (worker.isCancelled()) {
295 windowAncestor = (Frame) SwingUtilities.getAncestorOfClass(Frame.class,
this);
299 }
catch (TskCoreException ex) {
300 logger.log(Level.SEVERE,
"Error getting CommunicationsManager for the current case.", ex);
302 logger.log(Level.SEVERE,
"Can't get CommunicationsManager when there is no case open.", ex);
306 graph.getModel().beginUpdate();
310 graph.getModel().endUpdate();
312 if (evt.getNewValue() == null) {
315 Case currentCase = (
Case) evt.getNewValue();
318 }
catch (TskCoreException ex) {
319 logger.log(Level.SEVERE,
"Error getting CommunicationsManager for the current case.", ex);
330 @SuppressWarnings(
"unchecked")
354 setLayout(
new BorderLayout());
361 jTextArea1.setBackground(
new Color(240, 240, 240));
369 placeHolderPanelLayout.setHorizontalGroup(placeHolderPanelLayout.createParallelGroup(GroupLayout.LEADING)
370 .add(placeHolderPanelLayout.createSequentialGroup()
371 .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
372 .add(
jTextArea1, GroupLayout.PREFERRED_SIZE, 424, GroupLayout.PREFERRED_SIZE)
373 .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
375 placeHolderPanelLayout.setVerticalGroup(placeHolderPanelLayout.createParallelGroup(GroupLayout.LEADING)
376 .add(placeHolderPanelLayout.createSequentialGroup()
377 .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
378 .add(
jTextArea1, GroupLayout.PREFERRED_SIZE, 47, GroupLayout.PREFERRED_SIZE)
379 .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
406 jSeparator1.setOrientation(SwingConstants.VERTICAL);
408 zoomOutButton.setIcon(
new ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/communications/images/magnifier-zoom-out-red.png")));
412 zoomOutButton.setHorizontalTextPosition(SwingConstants.CENTER);
413 zoomOutButton.setVerticalTextPosition(SwingConstants.BOTTOM);
415 public void actionPerformed(ActionEvent evt) {
420 zoomInButton.setIcon(
new ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/communications/images/magnifier-zoom-in-green.png")));
424 zoomInButton.setHorizontalTextPosition(SwingConstants.CENTER);
425 zoomInButton.setVerticalTextPosition(SwingConstants.BOTTOM);
427 public void actionPerformed(ActionEvent evt) {
432 zoomActualButton.setIcon(
new ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/communications/images/magnifier-zoom-actual.png")));
439 public void actionPerformed(ActionEvent evt) {
444 fitZoomButton.setIcon(
new ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/communications/images/magnifier-zoom-fit.png")));
448 fitZoomButton.setHorizontalTextPosition(SwingConstants.CENTER);
449 fitZoomButton.setVerticalTextPosition(SwingConstants.BOTTOM);
451 public void actionPerformed(ActionEvent evt) {
460 clearVizButton.setIcon(
new ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/communications/images/broom.png")));
463 public void actionPerformed(ActionEvent evt) {
468 jSeparator2.setOrientation(SwingConstants.VERTICAL);
470 GroupLayout toolbarLayout =
new GroupLayout(
toolbar);
471 toolbar.setLayout(toolbarLayout);
472 toolbarLayout.setHorizontalGroup(toolbarLayout.createParallelGroup(GroupLayout.LEADING)
473 .add(toolbarLayout.createSequentialGroup()
477 .add(
jSeparator1, GroupLayout.PREFERRED_SIZE, 10, GroupLayout.PREFERRED_SIZE)
480 .addPreferredGap(LayoutStyle.RELATED)
482 .addPreferredGap(LayoutStyle.RELATED)
484 .addPreferredGap(LayoutStyle.RELATED)
486 .addPreferredGap(LayoutStyle.RELATED)
488 .addPreferredGap(LayoutStyle.RELATED)
489 .add(
jSeparator2, GroupLayout.PREFERRED_SIZE, 10, GroupLayout.PREFERRED_SIZE)
490 .addPreferredGap(LayoutStyle.RELATED)
492 .addPreferredGap(LayoutStyle.RELATED)
494 .addPreferredGap(LayoutStyle.RELATED)
495 .add(
zoomOutButton, GroupLayout.PREFERRED_SIZE, 32, GroupLayout.PREFERRED_SIZE)
496 .addPreferredGap(LayoutStyle.RELATED)
497 .add(
zoomInButton, GroupLayout.PREFERRED_SIZE, 32, GroupLayout.PREFERRED_SIZE)
498 .addPreferredGap(LayoutStyle.RELATED)
499 .add(
zoomActualButton, GroupLayout.PREFERRED_SIZE, 33, GroupLayout.PREFERRED_SIZE)
500 .addPreferredGap(LayoutStyle.RELATED)
501 .add(
fitZoomButton, GroupLayout.PREFERRED_SIZE, 32, GroupLayout.PREFERRED_SIZE)
502 .addContainerGap(12, Short.MAX_VALUE))
504 toolbarLayout.setVerticalGroup(toolbarLayout.createParallelGroup(GroupLayout.LEADING)
505 .add(toolbarLayout.createSequentialGroup()
507 .add(toolbarLayout.createParallelGroup(GroupLayout.CENTER)
508 .add(
jLabel1, GroupLayout.PREFERRED_SIZE, 25, GroupLayout.PREFERRED_SIZE)
513 .add(
jSeparator1, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
515 .add(
zoomInButton, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
516 .add(
zoomActualButton, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
517 .add(
fitZoomButton, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
521 .add(
jSeparator2, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
537 graphComponent.zoomActual();
541 graphComponent.zoomIn();
545 graphComponent.zoomOut();
554 @NbBundle.Messages({
"VisualizationPanel.computingLayout=Computing Layout",
555 "# {0} - layout name",
556 "VisualizationPanel.layoutFailWithLockedVertices.text={0} layout failed with locked vertices. Unlock some vertices or try a different layout.",
557 "# {0} - layout name",
558 "VisualizationPanel.layoutFail.text={0} layout failed. Try a different layout."})
562 -> button.setFont(button.getFont().deriveFont(layoutKey == layout ? Font.BOLD : Font.PLAIN)));
565 progressIndicator.
start(Bundle.VisualizationPanel_computingLayout());
567 new SwingWorker<Void, Void>() {
569 protected Void doInBackground() {
570 graph.getModel().beginUpdate();
572 layout.execute(graph.getDefaultParent());
575 graph.getModel().endUpdate();
576 progressIndicator.
finish();
582 protected void done() {
585 }
catch (InterruptedException | ExecutionException ex) {
586 logger.log(Level.WARNING,
"CVT graph layout failed.", ex);
599 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
600 graph.getModel().beginUpdate();
605 graph.getModel().endUpdate();
606 setCursor(Cursor.getDefaultCursor());
610 graphComponent.zoomTo(1,
true);
611 mxPoint translate = graph.getView().getTranslate();
612 if (translate == null || Double.isNaN(translate.getX()) || Double.isNaN(translate.getY())) {
613 translate =
new mxPoint();
616 mxRectangle boundsForCells = graph.getCellBounds(graph.getDefaultParent(),
true,
true,
true);
617 if (boundsForCells == null || Double.isNaN(boundsForCells.getWidth()) || Double.isNaN(boundsForCells.getHeight())) {
618 boundsForCells =
new mxRectangle(0, 0, 1, 1);
620 final mxPoint mxPoint =
new mxPoint(translate.getX() - boundsForCells.getX(), translate.getY() - boundsForCells.getY());
622 graph.cellsMoved(graph.getChildCells(graph.getDefaultParent()), mxPoint.getX(), mxPoint.getY(),
false,
false);
624 boundsForCells = graph.getCellBounds(graph.getDefaultParent(),
true,
true,
true);
625 if (boundsForCells == null || Double.isNaN(boundsForCells.getWidth()) || Double.isNaN(boundsForCells.getHeight())) {
626 boundsForCells =
new mxRectangle(0, 0, 1, 1);
629 final Dimension size = graphComponent.getSize();
630 final double widthFactor = size.getWidth() / boundsForCells.getWidth();
631 final double heightFactor = size.getHeight() / boundsForCells.getHeight();
633 graphComponent.zoom((heightFactor + widthFactor) / 2.0);
665 @SuppressWarnings(
"unchecked")
667 public void invoke(Object sender, mxEventObject evt) {
668 Object[] selectionCells = graph.getSelectionCells();
669 Node rootNode = Node.EMPTY;
670 Node[] selectedNodes =
new Node[0];
671 if (selectionCells.length > 0) {
672 mxICell[] selectedCells = Arrays.asList(selectionCells).toArray(
new mxCell[selectionCells.length]);
673 HashSet<Content> relationshipSources =
new HashSet<>();
674 HashSet<AccountDeviceInstanceKey> adis =
new HashSet<>();
675 for (mxICell cell : selectedCells) {
677 mxICell source = (mxICell) graph.getModel().getTerminal(cell,
true);
678 AccountDeviceInstanceKey account1 = (AccountDeviceInstanceKey) source.getValue();
679 mxICell target = (mxICell) graph.getModel().getTerminal(cell,
false);
680 AccountDeviceInstanceKey account2 = (AccountDeviceInstanceKey) target.getValue();
682 final List<Content> relationshipSources1 = commsManager.getRelationshipSources(
683 account1.getAccountDeviceInstance(),
684 account2.getAccountDeviceInstance(),
686 relationshipSources.addAll(relationshipSources1);
687 }
catch (TskCoreException tskCoreException) {
688 logger.log(Level.SEVERE,
" Error getting relationsips....", tskCoreException);
690 }
else if (cell.isVertex()) {
691 adis.add((AccountDeviceInstanceKey) cell.getValue());
695 rootNode = SelectionNode.createFromAccountsAndRelationships(relationshipSources, adis, currentFilter, commsManager);
696 selectedNodes =
new Node[]{rootNode};
698 vizEM.setRootContext(rootNode);
700 vizEM.setSelectedNodes(selectedNodes);
701 }
catch (PropertyVetoException ex) {
702 logger.log(Level.SEVERE,
"Selection vetoed.", ex);
726 return super.isVertexIgnored(vertex)
733 return getVertexBounds(vertex);
735 return super.setVertexLocation(vertex, x, y);
741 return "Fast Organic";
757 return super.isVertexIgnored(vertex)
764 return getVertexBounds(vertex);
766 return super.setVertexLocation(vertex, x, y);
788 return super.isVertexIgnored(vertex)
795 return getVertexBounds(vertex);
797 return super.setVertexLocation(vertex, x, y);
818 return super.isVertexIgnored(vertex)
825 return getVertexBounds(vertex);
827 return super.setVertexLocation(vertex, x, y);
833 return "Hierarchical";
854 cancellable.cancel(
true);
872 super.mouseWheelMoved(event);
873 if (event.getPreciseWheelRotation() < 0) {
874 graphComponent.zoomIn();
875 }
else if (event.getPreciseWheelRotation() > 0) {
876 graphComponent.zoomOut();
887 super.mouseClicked(event);
888 if (SwingUtilities.isRightMouseButton(event)) {
889 final mxCell cellAt = (mxCell) graphComponent.getCellAt(event.getX(),
event.getY());
890 if (cellAt != null && cellAt.isVertex()) {
891 final JPopupMenu jPopupMenu =
new JPopupMenu();
892 final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) cellAt.getValue();
894 Set<mxCell> selectedVertices
895 = Stream.of(graph.getSelectionModel().getCells())
896 .map(mxCell.class::cast)
897 .filter(mxCell::isVertex)
898 .collect(Collectors.toSet());
901 jPopupMenu.add(
new JMenuItem(
new UnlockAction(selectedVertices)));
903 jPopupMenu.add(
new JMenuItem(
new LockAction(selectedVertices)));
906 jPopupMenu.add(UnpinAccountsAction.getInstance().getPopupPresenter());
908 jPopupMenu.add(PinAccountsAction.getInstance().getPopupPresenter());
909 jPopupMenu.add(ResetAndPinAccountsAction.getInstance().getPopupPresenter());
911 jPopupMenu.show(graphComponent.getGraphControl(),
event.getX(),
event.getY());
921 "VisualizationPanel.unlockAction.singularText=Unlock Selected Account",
922 "VisualizationPanel.unlockAction.pluralText=Unlock Selected Accounts",})
928 super(selectedVertices.size() > 1 ? Bundle.VisualizationPanel_unlockAction_pluralText() : Bundle.VisualizationPanel_unlockAction_singularText(),
930 this.selectedVertices = selectedVertices;
944 "VisualizationPanel.lockAction.singularText=Lock Selected Account",
945 "VisualizationPanel.lockAction.pluralText=Lock Selected Accounts"})
951 super(selectedVertices.size() > 1 ? Bundle.VisualizationPanel_lockAction_pluralText() : Bundle.VisualizationPanel_lockAction_singularText(),
953 this.selectedVertices = selectedVertices;
void zoomOutButtonActionPerformed(ActionEvent evt)
final Set< mxCell > selectedVertices
boolean isVertexIgnored(Object vertex)
static final Logger logger
void invoke(Object sender, mxEventObject evt)
static final String BASE_IMAGE_PATH
void mouseClicked(final MouseEvent event)
static final ImageIcon unlockIcon
CommunicationsManager commsManager
static Case getOpenCase()
boolean isVertexIgnored(Object vertex)
void applyLayout(NamedGraphLayout layout)
void actionPerformed(final ActionEvent event)
final mxRubberband rubberband
final PinnedAccountModel pinnedAccountModel
NamedGraphLayout currentLayout
static final long serialVersionUID
JToolBar.Separator jSeparator2
static final ImageIcon lockIcon
ModalDialogProgressIndicator progress
mxRectangle setVertexLocation(Object vertex, double x, double y)
JButton hierarchyLayoutButton
synchronized void start(String message, int totalWorkUnits)
JButton organicLayoutButton
final ProxyLookup proxyLookup
synchronized void setCancelling(String cancellingMessage)
final CommunicationsGraph graph
void actionPerformed(final ActionEvent event)
boolean isVertexIgnored(Object vertex)
CommunicationsFilter currentFilter
final ExplorerManager vizEM
void fitZoomButtonActionPerformed(ActionEvent evt)
SleuthkitCase getSleuthkitCase()
void mouseWheelMoved(final MouseWheelEvent event)
JButton circleLayoutButton
final LockedVertexModel lockedVertexModel
final mxUndoManager undoManager
JToolBar.Separator jSeparator1
mxRectangle setVertexLocation(Object vertex, double x, double y)
boolean isVertexIgnored(Object vertex)
mxRectangle setVertexLocation(Object vertex, double x, double y)
final mxGraphComponent graphComponent
synchronized static Logger getLogger(String name)
void actionPerformed(ActionEvent event)
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
mxRectangle setVertexLocation(Object vertex, double x, double y)
final ExplorerManager gacEM
JButton fastOrganicLayoutButton
void zoomInButtonActionPerformed(ActionEvent evt)
static final String CANCEL
final Set< mxCell > selectedVertices
void zoomActualButtonActionPerformed(ActionEvent evt)
void clearVizButtonActionPerformed(ActionEvent evt)
final Map< NamedGraphLayout, JButton > layoutButtons
static void error(String message)
synchronized void finish()