use undoManager to implement better layout cancelation

This commit is contained in:
millmanorama 2018-02-14 17:02:51 +01:00
parent 535a1feee3
commit 54da8d3145
2 changed files with 59 additions and 41 deletions

View File

@ -52,25 +52,25 @@ import org.sleuthkit.datamodel.CommunicationsManager;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
final class mxGraphImpl extends mxGraph { final class CommunicationsGraph extends mxGraph {
private static final Logger logger = Logger.getLogger(CommunicationsGraph.class.getName());
private static final URL MARKER_PIN_URL = CommunicationsGraph.class.getResource("/org/sleuthkit/autopsy/communications/images/marker--pin.png");
private static final URL LOCK_URL = CommunicationsGraph.class.getResource("/org/sleuthkit/autopsy/communications/images/lock_large_locked.png");
private static final Logger logger = Logger.getLogger(mxGraphImpl.class.getName());
private static final URL MARKER_PIN_URL = mxGraphImpl.class.getResource("/org/sleuthkit/autopsy/communications/images/marker--pin.png");
private static final URL LOCK_URL = mxGraphImpl.class.getResource("/org/sleuthkit/autopsy/communications/images/lock_large_locked.png");
/** /**
* mustache.java template * mustache.java template
*/ */
private final static Mustache labelMustache; private final static Mustache labelMustache;
static { static {
// String labelTemplatePath = "/org/sleuthkit/autopsy/communications/Vertex_Label_template.html"; InputStream templateStream = CommunicationsGraph.class.getResourceAsStream("/org/sleuthkit/autopsy/communications/Vertex_Label_template.html");
InputStream templateStream = mxGraphImpl.class.getResourceAsStream("/org/sleuthkit/autopsy/communications/Vertex_Label_template.html");
labelMustache = new DefaultMustacheFactory().compile(new InputStreamReader(templateStream), "Vertex_Label"); labelMustache = new DefaultMustacheFactory().compile(new InputStreamReader(templateStream), "Vertex_Label");
} }
static final private mxStylesheet mxStylesheet = new mxStylesheet(); static final private mxStylesheet mxStylesheet = new mxStylesheet();
private final HashSet<AccountDeviceInstanceKey> pinnedAccountDevices = new HashSet<>(); private final Set<AccountDeviceInstanceKey> pinnedAccountDevices = new HashSet<>();
private final HashSet<mxCell> lockedVertices = new HashSet<>(); private final Set<mxCell> lockedVertices = new HashSet<>();
private final Map<String, mxCell> nodeMap = new HashMap<>(); private final Map<String, mxCell> nodeMap = new HashMap<>();
private final Multimap<Content, mxCell> edgeMap = MultimapBuilder.hashKeys().hashSetValues().build(); private final Multimap<Content, mxCell> edgeMap = MultimapBuilder.hashKeys().hashSetValues().build();
@ -88,7 +88,7 @@ final class mxGraphImpl extends mxGraph {
mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_STARTARROW, mxConstants.NONE); mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_STARTARROW, mxConstants.NONE);
} }
mxGraphImpl() { CommunicationsGraph() {
super(mxStylesheet); super(mxStylesheet);
setAutoSizeCells(true); setAutoSizeCells(true);
setCellsCloneable(false); setCellsCloneable(false);
@ -130,7 +130,7 @@ final class mxGraphImpl extends mxGraph {
scopes.put("accountName", adiKey.getAccountDeviceInstance().getAccount().getTypeSpecificID()); scopes.put("accountName", adiKey.getAccountDeviceInstance().getAccount().getTypeSpecificID());
scopes.put("size", Math.round(Math.log(adiKey.getMessageCount()) + 5)); scopes.put("size", Math.round(Math.log(adiKey.getMessageCount()) + 5));
scopes.put("iconFileName", mxGraphImpl.class.getResource("/org/sleuthkit/autopsy/communications/images/" scopes.put("iconFileName", CommunicationsGraph.class.getResource("/org/sleuthkit/autopsy/communications/images/"
+ Utils.getIconFileName(adiKey.getAccountDeviceInstance().getAccount().getAccountType()))); + Utils.getIconFileName(adiKey.getAccountDeviceInstance().getAccount().getAccountType())));
scopes.put("pinned", pinnedAccountDevices.contains(adiKey)); scopes.put("pinned", pinnedAccountDevices.contains(adiKey));
scopes.put("MARKER_PIN_URL", MARKER_PIN_URL); scopes.put("MARKER_PIN_URL", MARKER_PIN_URL);
@ -156,7 +156,7 @@ final class mxGraphImpl extends mxGraph {
scopes.put("accountName", adiKey.getAccountDeviceInstance().getAccount().getTypeSpecificID()); scopes.put("accountName", adiKey.getAccountDeviceInstance().getAccount().getTypeSpecificID());
scopes.put("size", 12);// Math.round(Math.log(adiKey.getMessageCount()) + 5)); scopes.put("size", 12);// Math.round(Math.log(adiKey.getMessageCount()) + 5));
scopes.put("iconFileName", mxGraphImpl.class.getResource("/org/sleuthkit/autopsy/communications/images/" scopes.put("iconFileName", CommunicationsGraph.class.getResource("/org/sleuthkit/autopsy/communications/images/"
+ Utils.getIconFileName(adiKey.getAccountDeviceInstance().getAccount().getAccountType()))); + Utils.getIconFileName(adiKey.getAccountDeviceInstance().getAccount().getAccountType())));
scopes.put("pinned", pinnedAccountDevices.contains(adiKey)); scopes.put("pinned", pinnedAccountDevices.contains(adiKey));
scopes.put("MARKER_PIN_URL", MARKER_PIN_URL); scopes.put("MARKER_PIN_URL", MARKER_PIN_URL);
@ -310,7 +310,7 @@ final class mxGraphImpl extends mxGraph {
@Override @Override
protected Void doInBackground() throws Exception { protected Void doInBackground() throws Exception {
progress.start("Loading accounts"); progress.start("Loading accounts");
progress.switchToDeterminate("Loading accounts", 0,pinnedAccountDevices.size()); // progress.switchToDeterminate("Loading accounts", 0,pinnedAccountDevices.size());
int i = 0; int i = 0;
try { try {
/** /**
@ -337,7 +337,7 @@ final class mxGraphImpl extends mxGraph {
//for each pair of related accounts add edges if they are related o each other. //for each pair of related accounts add edges if they are related o each other.
// this is O(n^2) in the number of related accounts!!! // this is O(n^2) in the number of related accounts!!!
List<AccountDeviceInstanceKey> relatedAccountsList = new ArrayList<>(relatedAccounts); List<AccountDeviceInstanceKey> relatedAccountsList = new ArrayList<>(relatedAccounts);
progress.switchToDeterminate("",0,relatedAccountsList.size()); progress.switchToDeterminate("", 0, relatedAccountsList.size());
for (i = 0; i < relatedAccountsList.size(); i++) { for (i = 0; i < relatedAccountsList.size(); i++) {
AccountDeviceInstanceKey adiKey1 = relatedAccountsList.get(i); AccountDeviceInstanceKey adiKey1 = relatedAccountsList.get(i);
for (int j = i; j < relatedAccountsList.size(); j++) { for (int j = i; j < relatedAccountsList.size(); j++) {
@ -360,7 +360,7 @@ final class mxGraphImpl extends mxGraph {
logger.log(Level.SEVERE, "Error", tskCoreException); logger.log(Level.SEVERE, "Error", tskCoreException);
} finally { } finally {
} }
return null; return null;
} }

View File

@ -34,6 +34,8 @@ import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource; import com.mxgraph.util.mxEventSource;
import com.mxgraph.util.mxPoint; import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle; import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxUndoManager;
import com.mxgraph.util.mxUndoableEdit;
import com.mxgraph.view.mxGraph; import com.mxgraph.view.mxGraph;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Color; import java.awt.Color;
@ -45,7 +47,6 @@ import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelEvent;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyVetoException; import java.beans.PropertyVetoException;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.Arrays; import java.util.Arrays;
@ -97,6 +98,7 @@ 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;
@ -113,17 +115,23 @@ 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"));
private static final String CANCEL = Bundle.VisualizationPanel_cancelButton_text();
private final ExplorerManager vizEM = new ExplorerManager(); private final ExplorerManager vizEM = new ExplorerManager();
private final ExplorerManager gacEM = new ExplorerManager(); private final ExplorerManager gacEM = new ExplorerManager();
private final ProxyLookup proxyLookup = new ProxyLookup( private final ProxyLookup proxyLookup = new ProxyLookup(
ExplorerUtils.createLookup(gacEM, getActionMap()), ExplorerUtils.createLookup(gacEM, getActionMap()),
ExplorerUtils.createLookup(vizEM, getActionMap())); ExplorerUtils.createLookup(vizEM, getActionMap()));
private final mxGraphComponent graphComponent; private Frame windowAncestor;
private final mxGraphImpl graph;
private CommunicationsManager commsManager; private CommunicationsManager commsManager;
private CommunicationsFilter currentFilter; private CommunicationsFilter currentFilter;
private final mxGraphComponent graphComponent;
private final CommunicationsGraph graph;
protected mxUndoManager undoManager = new mxUndoManager();
private final mxRubberband rubberband; private final mxRubberband rubberband;
private final mxFastOrganicLayout fastOrganicLayout; private final mxFastOrganicLayout fastOrganicLayout;
private final mxCircleLayout circleLayout; private final mxCircleLayout circleLayout;
@ -132,11 +140,10 @@ 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 Frame windowAncestor;
public VisualizationPanel() { public VisualizationPanel() {
initComponents(); initComponents();
graph = new mxGraphImpl(); graph = new CommunicationsGraph();
fastOrganicLayout = new mxFastOrganicLayoutImpl(graph); fastOrganicLayout = new mxFastOrganicLayoutImpl(graph);
circleLayout = new mxCircleLayoutImpl(graph); circleLayout = new mxCircleLayoutImpl(graph);
@ -232,6 +239,11 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
//feed selection to explorermanager //feed selection to explorermanager
graph.getSelectionModel().addListener(null, new SelectionListener()); graph.getSelectionModel().addListener(null, new SelectionListener());
final mxEventSource.mxIEventListener undoListener = (Object sender, mxEventObject evt) ->
undoManager.undoableEditHappened((mxUndoableEdit) evt.getProperty("edit"));
graph.getModel().addListener(mxEvent.UNDO, undoListener);
graph.getView().addListener(mxEvent.UNDO, undoListener);
} }
@Override @Override
@ -289,24 +301,21 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
} }
CancelationListener cancelationListener = new CancelationListener(); CancelationListener cancelationListener = new CancelationListener();
ModalDialogProgressIndicator modalDialogProgressIndicator = new ModalDialogProgressIndicator(windowAncestor, "Loading Visualization", new String[]{"Cancel"}, "Cancel", cancelationListener); ModalDialogProgressIndicator progress = new ModalDialogProgressIndicator(windowAncestor, "Loading Visualization", new String[]{CANCEL}, CANCEL, cancelationListener);
worker = graph.rebuild(modalDialogProgressIndicator, commsManager, currentFilter); worker = graph.rebuild(progress, commsManager, currentFilter);
cancelationListener.setCancellable(worker); cancelationListener.configure(worker, progress);
worker.addPropertyChangeListener(new PropertyChangeListener() { worker.addPropertyChangeListener((final PropertyChangeEvent evt) -> {
@Override if (worker.isDone()) {
public void propertyChange(final PropertyChangeEvent evt) { if (worker.isCancelled()) {
if (worker.isDone()) { graph.resetGraph();
if (worker.isCancelled()) { rebuildGraph();
graph.resetGraph(); } else if (graph.getModel().getChildCount(graph.getDefaultParent()) < 64) {
rebuildGraph(); applyOrganicLayout(10);
} else if (graph.getModel().getChildCount(graph.getDefaultParent()) < 64) { } else {
applyOrganicLayout(10); JOptionPane.showMessageDialog(VisualizationPanel.this,
} else { "Too many accounts, layout aborted.",
JOptionPane.showMessageDialog(VisualizationPanel.this, "Autopsy",
"Too many accounts, layout aborted.", JOptionPane.WARNING_MESSAGE);
"Autopsy",
JOptionPane.WARNING_MESSAGE);
}
} }
} }
}); });
@ -632,7 +641,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
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, "Computing layout", 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() throws Exception { protected Void doInBackground() throws Exception {
@ -657,15 +666,20 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
}); });
morph.addListener(mxEvent.DONE, (Object sender, mxEventObject event) -> { morph.addListener(mxEvent.DONE, (Object sender, mxEventObject event) -> {
graph.getModel().endUpdate(); graph.getModel().endUpdate();
fitGraph(); if (isCancelled()) {
undoManager.undo();
} else {
fitGraph();
}
progress.finish(); progress.finish();
}); });
morph.startAnimation(); morph.startAnimation();
return null; return null;
} }
}; };
cancelationListener.setCancellable(morphWorker); cancelationListener.configure(morphWorker, progress);
morphWorker.execute(); morphWorker.execute();
} }
@ -813,14 +827,18 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
private class CancelationListener implements ActionListener { private class CancelationListener implements ActionListener {
private Future<?> cancellable; private Future<?> cancellable;
private ModalDialogProgressIndicator progress;
void setCancellable(Future<?> cancellable) { void configure(Future<?> cancellable, ModalDialogProgressIndicator progress) {
this.cancellable = cancellable; this.cancellable = cancellable;
this.progress = progress;
} }
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
progress.setCancelling("Cancelling...");
cancellable.cancel(true); cancellable.cancel(true);
progress.finish();
} }
} }