multiselect for locking. Not updating the icon properly...

This commit is contained in:
millmanorama 2018-04-07 14:56:15 +02:00
parent 1882690b72
commit f3161c7af6
4 changed files with 124 additions and 73 deletions

View File

@ -38,6 +38,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.logging.Level; import java.util.logging.Level;
import javax.swing.SwingWorker; import javax.swing.SwingWorker;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
@ -84,17 +85,19 @@ final class CommunicationsGraph extends mxGraph {
mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_STARTARROW, mxConstants.NONE); mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_STARTARROW, mxConstants.NONE);
} }
/* Map from type specific account identifier to mxCell(vertex). */ /** Map from type specific account identifier to mxCell(vertex). */
private final Map<String, mxCell> nodeMap = new HashMap<>(); private final Map<String, mxCell> nodeMap = new HashMap<>();
/* Map from relationship source (Content) to mxCell (edge). */ /** Map from relationship source (Content) to mxCell (edge). */
private final Multimap<Content, mxCell> edgeMap = MultimapBuilder.hashKeys().hashSetValues().build(); private final Multimap<Content, mxCell> edgeMap = MultimapBuilder.hashKeys().hashSetValues().build();
private final LockedVertexModel lockedVertexModel; private final LockedVertexModel lockedVertexModel;
private final PinnedAccountModel pinnedAccountModel; private final PinnedAccountModel pinnedAccountModel;
CommunicationsGraph() { CommunicationsGraph(PinnedAccountModel pinnedAccountModel, LockedVertexModel lockedVertexModel) {
super(mxStylesheet); super(mxStylesheet);
this.pinnedAccountModel =pinnedAccountModel;
this.lockedVertexModel = lockedVertexModel;
//set fixed properties of graph. //set fixed properties of graph.
setAutoSizeCells(true); setAutoSizeCells(true);
setCellsCloneable(false); setCellsCloneable(false);
@ -113,21 +116,6 @@ final class CommunicationsGraph extends mxGraph {
setKeepEdgesInBackground(true); setKeepEdgesInBackground(true);
setResetEdgesOnMove(true); setResetEdgesOnMove(true);
setHtmlLabels(true); setHtmlLabels(true);
lockedVertexModel = new LockedVertexModel();
lockedVertexModel.registerhandler((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);
} }
/** /**
@ -299,7 +287,7 @@ final class CommunicationsGraph extends mxGraph {
int total = relationshipCounts.size(); int total = relationshipCounts.size();
int k = 0; int k = 0;
String progressText = ""; String progressText = "";
progress.switchToDeterminate("", 0, total); progress.switchToDeterminate("", 0, total);
for (Map.Entry<AccountPair, Long> entry : relationshipCounts.entrySet()) { for (Map.Entry<AccountPair, Long> entry : relationshipCounts.entrySet()) {
Long count = entry.getValue(); Long count = entry.getValue();
@ -308,7 +296,7 @@ final class CommunicationsGraph extends mxGraph {
AccountDeviceInstanceKey account2 = relatedAccounts.get(relationshipKey.getSecond()); AccountDeviceInstanceKey account2 = relatedAccounts.get(relationshipKey.getSecond());
if (pinnedAccountModel.isAccountPinned(account1) if (pinnedAccountModel.isAccountPinned(account1)
|| pinnedAccountModel.isAccountPinned(account2)) { || pinnedAccountModel.isAccountPinned(account2)) {
mxCell addEdge = addOrUpdateEdge(count, account1, account2); mxCell addEdge = addOrUpdateEdge(count, account1, account2);
progressText = addEdge.getId(); progressText = addEdge.getId();
} }

View File

@ -18,12 +18,22 @@
*/ */
package org.sleuthkit.autopsy.communications; package org.sleuthkit.autopsy.communications;
import com.google.common.collect.ImmutableSet;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
import com.mxgraph.model.mxCell; import com.mxgraph.model.mxCell;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
class LockedVertexModel { /**
* Model of which vertices in a graph are locked ( not moveable by layout
* algorithms).
*
*/
final class LockedVertexModel {
void registerhandler(EventHandler<VertexLockEvent> handler) { void registerhandler(EventHandler<VertexLockEvent> handler) {
eventBus.register(handler); eventBus.register(handler);
@ -42,30 +52,26 @@ class LockedVertexModel {
*/ */
private final Set<mxCell> lockedVertices = new HashSet<>(); private final Set<mxCell> lockedVertices = new HashSet<>();
LockedVertexModel() {
}
/** /**
* Lock the given vertex so that applying a layout algorithm doesn't move * Lock the given vertices so that applying a layout algorithm doesn't move
* it. The user can still manually position the vertex. * them. The user can still manually position the vertices.
* *
* @param vertex The vertex to lock. * @param vertex The vertex to lock.
*/ */
void lockVertex(mxCell vertex) { void lock(Collection<mxCell> vertices) {
lockedVertices.add(vertex); lockedVertices.addAll(vertices);
eventBus.post(new VertexLockEvent(vertex, true)); eventBus.post(new VertexLockEvent(true, vertices));
} }
/** /**
* Lock the given vertex so that applying a layout algorithm can move it. * Unlock the given vertices so that applying a layout algorithm can move
* them.
* *
* @param vertex The vertex to unlock. * @param vertex The vertex to unlock.
*/ */
void unlockVertex(mxCell vertex) { void unlock(Collection<mxCell> vertices) {
lockedVertices.remove(vertex); lockedVertices.removeAll(vertices);
eventBus.post(new VertexLockEvent(vertex, false)); eventBus.post(new VertexLockEvent(false, vertices));
} }
boolean isVertexLocked(mxCell vertex) { boolean isVertexLocked(mxCell vertex) {
@ -79,10 +85,10 @@ class LockedVertexModel {
static class VertexLockEvent { static class VertexLockEvent {
private final mxCell vertex; private final Set<mxCell> vertices;
public mxCell getVertex() { public Set<mxCell> getVertices() {
return vertex; return vertices;
} }
public boolean isVertexLocked() { public boolean isVertexLocked() {
@ -90,8 +96,8 @@ class LockedVertexModel {
} }
private final boolean locked; private final boolean locked;
VertexLockEvent(mxCell vertex, boolean locked) { VertexLockEvent(boolean locked, Collection< mxCell> vertices) {
this.vertex = vertex; this.vertices = ImmutableSet.copyOf(vertices);
this.locked = locked; this.locked = locked;
} }
} }

View File

@ -25,17 +25,12 @@ import java.util.Set;
class PinnedAccountModel { class PinnedAccountModel {
/** /**
* Set of AccountDeviceInstanceKeys that are 'Pinned' to this graph. Pinned * Set of AccountDeviceInstanceKeys that are 'Pinned' to the graph. Pinned
* accounts are shown regardless of filters, and accounts that are related * accounts are shown regardless of filters, and accounts that are related
* to pinned accounts and pass the filters are show. Pinning accounts is the * to pinned accounts and pass the filters are show. Pinning accounts is the
* primary way to populate the graph. * primary way to populate the graph.
*/ */
private final Set<AccountDeviceInstanceKey> pinnedAccountDevices = new HashSet<>(); private final Set<AccountDeviceInstanceKey> pinnedAccountDevices = new HashSet<>();
private final CommunicationsGraph graph;
PinnedAccountModel(CommunicationsGraph graph) {
this.graph = graph;
}
boolean isAccountPinned(AccountDeviceInstanceKey account) { boolean isAccountPinned(AccountDeviceInstanceKey account) {
return pinnedAccountDevices.contains(account); return pinnedAccountDevices.contains(account);

View File

@ -36,7 +36,9 @@ import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle; import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxUndoManager; import com.mxgraph.util.mxUndoManager;
import com.mxgraph.util.mxUndoableEdit; import com.mxgraph.util.mxUndoableEdit;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph; import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxGraphView;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Color; import java.awt.Color;
import java.awt.Cursor; import java.awt.Cursor;
@ -54,8 +56,11 @@ import java.util.Arrays;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import javax.swing.JButton; import javax.swing.JButton;
@ -83,7 +88,6 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.progress.ModalDialogProgressIndicator; import org.sleuthkit.autopsy.progress.ModalDialogProgressIndicator;
import org.sleuthkit.datamodel.AccountDeviceInstance;
import org.sleuthkit.datamodel.CommunicationsFilter; import org.sleuthkit.datamodel.CommunicationsFilter;
import org.sleuthkit.datamodel.CommunicationsManager; import org.sleuthkit.datamodel.CommunicationsManager;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
@ -99,7 +103,6 @@ import org.sleuthkit.datamodel.TskCoreException;
* CVTTopComponent when this tab is active allowing for context sensitive * CVTTopComponent when this tab is active allowing for context sensitive
* actions to work correctly. * actions to work correctly.
*/ */
@NbBundle.Messages("VisualizationPanel.cancelButton.text=Cancel")
final public class VisualizationPanel extends JPanel implements Lookup.Provider { final public class VisualizationPanel extends JPanel implements Lookup.Provider {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -110,6 +113,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
static final private ImageIcon lockIcon static final private ImageIcon lockIcon
= new ImageIcon(VisualizationPanel.class.getResource(BASE_IMAGE_PATH + "/lock_large_locked.png")); = new ImageIcon(VisualizationPanel.class.getResource(BASE_IMAGE_PATH + "/lock_large_locked.png"));
@NbBundle.Messages("VisualizationPanel.cancelButton.text=Cancel")
private static final String CANCEL = Bundle.VisualizationPanel_cancelButton_text(); private static final String CANCEL = Bundle.VisualizationPanel_cancelButton_text();
private final ExplorerManager vizEM = new ExplorerManager(); private final ExplorerManager vizEM = new ExplorerManager();
@ -124,7 +128,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
private final CommunicationsGraph graph; private final CommunicationsGraph graph;
private final mxUndoManager undoManager = new mxUndoManager(); private final mxUndoManager undoManager = new mxUndoManager();
private final mxRubberband rubberband; private final mxRubberband rubberband; //NOPMD We keep a referenec as insurance to prevent garbage collection
private final mxFastOrganicLayout fastOrganicLayout; private final mxFastOrganicLayout fastOrganicLayout;
private final mxCircleLayout circleLayout; private final mxCircleLayout circleLayout;
@ -133,14 +137,13 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
@ThreadConfined(type = ThreadConfined.ThreadType.AWT) @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
private SwingWorker<?, ?> worker; private SwingWorker<?, ?> worker;
private final PinnedAccountModel pinnedAccountModel; private final PinnedAccountModel pinnedAccountModel = new PinnedAccountModel();
private final LockedVertexModel lockedVertexModel; private final LockedVertexModel lockedVertexModel = new LockedVertexModel();
public VisualizationPanel() { public VisualizationPanel() {
initComponents(); initComponents();
graph = new CommunicationsGraph();
pinnedAccountModel = graph.getPinnedAccountModel(); graph = new CommunicationsGraph(pinnedAccountModel, lockedVertexModel);
lockedVertexModel = graph.getLockedVertexModel();
fastOrganicLayout = new mxFastOrganicLayoutImpl(graph); fastOrganicLayout = new mxFastOrganicLayoutImpl(graph);
circleLayout = new mxCircleLayoutImpl(graph); circleLayout = new mxCircleLayoutImpl(graph);
@ -159,9 +162,26 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
graphComponent.setBackground(Color.WHITE); graphComponent.setBackground(Color.WHITE);
borderLayoutPanel.add(graphComponent, BorderLayout.CENTER); borderLayoutPanel.add(graphComponent, BorderLayout.CENTER);
//install rubber band selection handler //install rubber band other handlers
rubberband = new mxRubberband(graphComponent); rubberband = new mxRubberband(graphComponent);
lockedVertexModel.registerhandler((LockedVertexModel.VertexLockEvent event) -> {
final Set<mxCell> vertices = event.getVertices();
mxGraphView view = graph.getView();
if (event.isVertexLocked()) {
vertices.forEach(vertex -> view.clear(vertex, true, true));
view.validate();
} else {
vertices.forEach(vertex -> {
final mxCellState state = view.getState(vertex, true);
view.updateLabel(state);
view.updateLabelBounds(state);
view.updateBoundingBox(state);
});
}
graphComponent.getGraphControl().repaint();
});
final mxEventSource.mxIEventListener scaleListener = (Object sender, mxEventObject evt) final mxEventSource.mxIEventListener scaleListener = (Object sender, mxEventObject evt)
-> zoomLabel.setText(DecimalFormat.getPercentInstance().format(graph.getView().getScale())); -> zoomLabel.setText(DecimalFormat.getPercentInstance().format(graph.getView().getScale()));
graph.getView().addListener(mxEvent.SCALE, scaleListener); graph.getView().addListener(mxEvent.SCALE, scaleListener);
@ -199,20 +219,16 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
final JPopupMenu jPopupMenu = new JPopupMenu(); final JPopupMenu jPopupMenu = new JPopupMenu();
final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) cellAt.getValue(); final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) cellAt.getValue();
Set<mxCell> selectedVertices
= Stream.of(graph.getSelectionModel().getCells())
.map(mxCell.class::cast)
.filter(mxCell::isVertex)
.collect(Collectors.toSet());
if (lockedVertexModel.isVertexLocked(cellAt)) { if (lockedVertexModel.isVertexLocked(cellAt)) {
jPopupMenu.add(new JMenuItem(new AbstractAction("UnLock", unlockIcon) { jPopupMenu.add(new JMenuItem(new UnlockAction(selectedVertices)));
@Override
public void actionPerformed(final ActionEvent event) {
lockedVertexModel.unlockVertex(cellAt);
}
}));
} else { } else {
jPopupMenu.add(new JMenuItem(new AbstractAction("Lock", lockIcon) { jPopupMenu.add(new JMenuItem(new LockAction(selectedVertices)));
@Override
public void actionPerformed(final ActionEvent event) {
lockedVertexModel.lockVertex(cellAt);
}
}));
} }
if (pinnedAccountModel.isAccountPinned(adiKey)) { if (pinnedAccountModel.isAccountPinned(adiKey)) {
jPopupMenu.add(UnpinAccountsAction.getInstance().getPopupPresenter()); jPopupMenu.add(UnpinAccountsAction.getInstance().getPopupPresenter());
@ -224,6 +240,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
} }
} }
} }
}); });
final MessageBrowser messageBrowser = new MessageBrowser(vizEM, gacEM); final MessageBrowser messageBrowser = new MessageBrowser(vizEM, gacEM);
@ -650,16 +667,18 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
} }
@NbBundle.Messages({"Visualization.computingLayout=Computing Layout"})
private void morph(mxIGraphLayout layout) { private void morph(mxIGraphLayout layout) {
// layout using morphing // layout using morphing
graph.getModel().beginUpdate(); graph.getModel().beginUpdate();
CancelationListener cancelationListener = new CancelationListener(); CancelationListener cancelationListener = new CancelationListener();
ModalDialogProgressIndicator progress = new ModalDialogProgressIndicator(windowAncestor, "Computing layout", new String[]{CANCEL}, CANCEL, cancelationListener); ModalDialogProgressIndicator progress = new ModalDialogProgressIndicator(windowAncestor,
Bundle.Visualization_computingLayout(), new String[]{CANCEL}, CANCEL, cancelationListener);
SwingWorker<Void, Void> morphWorker = new SwingWorker<Void, Void>() { SwingWorker<Void, Void> morphWorker = new SwingWorker<Void, Void>() {
@Override @Override
protected Void doInBackground() { protected Void doInBackground() {
progress.start("Computing layout"); progress.start(Bundle.Visualization_computingLayout());
layout.execute(graph.getDefaultParent()); layout.execute(graph.getDefaultParent());
if (isCancelled()) { if (isCancelled()) {
progress.finish(); progress.finish();
@ -719,6 +738,10 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
private JButton zoomOutButton; private JButton zoomOutButton;
// End of variables declaration//GEN-END:variables // End of variables declaration//GEN-END:variables
/**
* Listens to graph selection model and updates ExplorerManager to reflect
* changes in selection.
*/
final private class SelectionListener implements mxEventSource.mxIEventListener { final private class SelectionListener implements mxEventSource.mxIEventListener {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -776,7 +799,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
} }
@Override @Override
public mxRectangle setVertexLocation(Object vertex, double x, double y) { public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x,y variable names are standard
if (isVertexIgnored(vertex)) { if (isVertexIgnored(vertex)) {
return getVertexBounds(vertex); return getVertexBounds(vertex);
} else { } else {
@ -799,7 +822,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
} }
@Override @Override
public mxRectangle setVertexLocation(Object vertex, double x, double y) { public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x,y variable names are standard
if (isVertexIgnored(vertex)) { if (isVertexIgnored(vertex)) {
return getVertexBounds(vertex); return getVertexBounds(vertex);
} else { } else {
@ -822,7 +845,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
} }
@Override @Override
public mxRectangle setVertexLocation(Object vertex, double x, double y) { public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x,y variable names are standard
if (isVertexIgnored(vertex)) { if (isVertexIgnored(vertex)) {
return getVertexBounds(vertex); return getVertexBounds(vertex);
} else { } else {
@ -844,7 +867,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
} }
@Override @Override
public mxRectangle setVertexLocation(Object vertex, double x, double y) { public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x,y variable names are standard
if (isVertexIgnored(vertex)) { if (isVertexIgnored(vertex)) {
return getVertexBounds(vertex); return getVertexBounds(vertex);
} else { } else {
@ -870,4 +893,43 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
progress.finish(); progress.finish();
} }
} }
@NbBundle.Messages({
"VisualizationPanel.unlockAction.singularText=Unlock Selected Account",
"VisualizationPanel.unlockAction.pluralText=Unlock Selected Accounts",})
private final class UnlockAction extends AbstractAction {
private final Set<mxCell> selectedVertices;
UnlockAction(Set<mxCell> selectedVertices) {
super(selectedVertices.size() > 1 ? Bundle.VisualizationPanel_unlockAction_pluralText() : Bundle.VisualizationPanel_unlockAction_singularText(),
unlockIcon);
this.selectedVertices = selectedVertices;
}
@Override
public void actionPerformed(final ActionEvent event) {
lockedVertexModel.unlock(selectedVertices);
}
}
@NbBundle.Messages({
"VisualizationPanel.lockAction.singularText=Lock Selected Account",
"VisualizationPanel.lockAction.pluralText=Lock Selected Accounts"})
private final class LockAction extends AbstractAction {
private final Set<mxCell> selectedVertices;
LockAction(Set<mxCell> selectedVertices) {
super(selectedVertices.size() > 1 ? Bundle.VisualizationPanel_lockAction_pluralText() : Bundle.VisualizationPanel_lockAction_singularText(),
lockIcon);
this.selectedVertices = selectedVertices;
}
@Override
public void actionPerformed(final ActionEvent event) {
lockedVertexModel.lock(selectedVertices);
}
}
} }