mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-06 21:00:22 +00:00
Merge pull request #3664 from millmanorama/962-layout-progress
962 layout progress
This commit is contained in:
commit
b759ddcc4f
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.communications;
|
||||
|
||||
import java.util.Collection;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JMenuItem;
|
||||
import org.openide.util.Utilities;
|
||||
import org.openide.util.actions.Presenter;
|
||||
|
||||
/**
|
||||
* Base class for actions that act on the selected AccountDeviceInstanceKeys.
|
||||
* getPopupPresenter() provides a JMenuItem that works (i.e., has an icon) in
|
||||
* custom context menus and also in the Netbeans Explorer views.
|
||||
*/
|
||||
abstract class AbstractCVTAction extends AbstractAction implements Presenter.Popup {
|
||||
|
||||
/**
|
||||
* Get the selected accounts that will be acted upon.
|
||||
*
|
||||
* @return The selected accounts
|
||||
*/
|
||||
Collection<? extends AccountDeviceInstanceKey> getSelectedAccounts() {
|
||||
return Utilities.actionsGlobalContext().lookupAll(AccountDeviceInstanceKey.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JMenuItem getPopupPresenter() {
|
||||
JMenuItem presenter = new JMenuItem(this);
|
||||
presenter.setText(getActionDisplayName());
|
||||
presenter.setIcon(getIcon());
|
||||
return presenter;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name/text of the action as displayed in a menu.
|
||||
*
|
||||
* @return The diaplay name of this action
|
||||
*/
|
||||
abstract String getActionDisplayName();
|
||||
|
||||
/**
|
||||
* The icon to use for this icon.
|
||||
*
|
||||
* @return An ImageIcon used to represent this action.
|
||||
*/
|
||||
abstract ImageIcon getIcon();
|
||||
}
|
@ -18,17 +18,13 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.communications;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import org.openide.nodes.AbstractNode;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Sheet;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.Utilities;
|
||||
import org.openide.util.lookup.Lookups;
|
||||
import org.sleuthkit.autopsy.datamodel.NodeProperty;
|
||||
import org.sleuthkit.datamodel.Account;
|
||||
@ -106,54 +102,4 @@ final class AccountDeviceInstanceNode extends AbstractNode {
|
||||
actions.add(ResetAndPinAccountsAction.getInstance());
|
||||
return actions.toArray(new Action[actions.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Action that pins the selected AccountDeviceInstances to the
|
||||
* visualization.
|
||||
*/
|
||||
static private class PinAccountsAction extends AbstractAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final static PinAccountsAction instance = new PinAccountsAction();
|
||||
|
||||
private static PinAccountsAction getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private PinAccountsAction() {
|
||||
super("Add Account(s) to Visualization");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
Collection<? extends AccountDeviceInstanceKey> lookupAll =
|
||||
Utilities.actionsGlobalContext().lookupAll(AccountDeviceInstanceKey.class);
|
||||
CVTEvents.getCVTEventBus().post(new CVTEvents.PinAccountsEvent(lookupAll, false));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action that pins the selected AccountDeviceInstances to the
|
||||
* visualization.
|
||||
*/
|
||||
static private class ResetAndPinAccountsAction extends AbstractAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final static ResetAndPinAccountsAction instance = new ResetAndPinAccountsAction();
|
||||
|
||||
private static ResetAndPinAccountsAction getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private ResetAndPinAccountsAction() {
|
||||
super("Visualize Account(s)");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
Collection<? extends AccountDeviceInstanceKey> lookupAll =
|
||||
Utilities.actionsGlobalContext().lookupAll(AccountDeviceInstanceKey.class);
|
||||
CVTEvents.getCVTEventBus().post(new CVTEvents.PinAccountsEvent(lookupAll, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,9 +20,6 @@ CVTTopComponent.vizPanel.TabConstraints.tabTitle=Visualize
|
||||
CVTTopComponent.accountsBrowser.TabConstraints.tabTitle_1=Browse
|
||||
CVTTopComponent.browseVisualizeTabPane.AccessibleContext.accessibleName=Visualize
|
||||
CVTTopComponent.vizPanel.TabConstraints.tabTitle_1=Visualize
|
||||
VisualizationPanel.jButton6.text=Hierarchy
|
||||
VisualizationPanel.jButton7.text=Circle
|
||||
VisualizationPanel.jButton8.text=Organic
|
||||
VisualizationPanel.fitGraphButton.text=
|
||||
VisualizationPanel.jTextArea1.text=Right-click an account in the Browse Accounts table, and select 'Visualize' to begin.
|
||||
VisualizationPanel.jLabel1.text=Layouts:
|
||||
@ -36,11 +33,8 @@ VisualizationPanel.zoomInButton.toolTipText=Zoom in
|
||||
VisualizationPanel.zoomInButton.text=
|
||||
VisualizationPanel.zoomOutButton.toolTipText=Zoom out
|
||||
VisualizationPanel.zoomOutButton.text=
|
||||
# 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.
|
||||
VisualizationPanel.circleLayoutButton.text=Circle
|
||||
VisualizationPanel.organicLayoutButton.text=Organic
|
||||
VisualizationPanel.fastOrganicLayoutButton.text=Fast Organic
|
||||
VisualizationPanel.hierarchyLayoutButton.text=Hierarchy
|
||||
VisualizationPanel.hierarchyLayoutButton.text=Hierarchical
|
||||
VisualizationPanel.clearVizButton.text_1=Clear Viz.
|
||||
|
@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.communications;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import org.sleuthkit.datamodel.CommunicationsFilter;
|
||||
|
||||
/**
|
||||
@ -79,7 +78,7 @@ final class CVTEvents {
|
||||
return accountDeviceInstances;
|
||||
}
|
||||
|
||||
public UnpinAccountsEvent(Set<AccountDeviceInstanceKey> accountDeviceInstances) {
|
||||
UnpinAccountsEvent(Collection<? extends AccountDeviceInstanceKey> accountDeviceInstances) {
|
||||
this.accountDeviceInstances = ImmutableSet.copyOf(accountDeviceInstances);
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ import com.google.common.collect.MultimapBuilder;
|
||||
import com.mxgraph.model.mxCell;
|
||||
import com.mxgraph.model.mxICell;
|
||||
import com.mxgraph.util.mxConstants;
|
||||
import com.mxgraph.view.mxCellState;
|
||||
import com.mxgraph.view.mxGraph;
|
||||
import com.mxgraph.view.mxStylesheet;
|
||||
import java.io.InputStream;
|
||||
@ -84,17 +83,19 @@ final class CommunicationsGraph extends mxGraph {
|
||||
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<>();
|
||||
|
||||
/* 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 LockedVertexModel lockedVertexModel;
|
||||
|
||||
private final PinnedAccountModel pinnedAccountModel;
|
||||
|
||||
CommunicationsGraph() {
|
||||
CommunicationsGraph(PinnedAccountModel pinnedAccountModel, LockedVertexModel lockedVertexModel) {
|
||||
super(mxStylesheet);
|
||||
this.pinnedAccountModel = pinnedAccountModel;
|
||||
this.lockedVertexModel = lockedVertexModel;
|
||||
//set fixed properties of graph.
|
||||
setAutoSizeCells(true);
|
||||
setCellsCloneable(false);
|
||||
@ -113,21 +114,6 @@ final class CommunicationsGraph extends mxGraph {
|
||||
setKeepEdgesInBackground(true);
|
||||
setResetEdgesOnMove(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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -255,20 +241,20 @@ final class CommunicationsGraph extends mxGraph {
|
||||
*/
|
||||
private class RebuildWorker extends SwingWorker<Void, Void> {
|
||||
|
||||
private final ProgressIndicator progress;
|
||||
private final ProgressIndicator progressIndicator;
|
||||
private final CommunicationsManager commsManager;
|
||||
private final CommunicationsFilter currentFilter;
|
||||
|
||||
RebuildWorker(ProgressIndicator progress, CommunicationsManager commsManager, CommunicationsFilter currentFilter) {
|
||||
this.progress = progress;
|
||||
this.progressIndicator = progress;
|
||||
this.currentFilter = currentFilter;
|
||||
this.commsManager = commsManager;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground() throws Exception {
|
||||
progress.start("Loading accounts");
|
||||
protected Void doInBackground() {
|
||||
progressIndicator.start("Loading accounts");
|
||||
int progressCounter = 0;
|
||||
try {
|
||||
/**
|
||||
@ -290,7 +276,7 @@ final class CommunicationsGraph extends mxGraph {
|
||||
final AccountDeviceInstanceKey relatedADIKey = new AccountDeviceInstanceKey(relatedADI, currentFilter, adiRelationshipsCount);
|
||||
relatedAccounts.put(relatedADI, relatedADIKey); //store related accounts
|
||||
}
|
||||
progress.progress(++progressCounter);
|
||||
progressIndicator.progress(++progressCounter);
|
||||
}
|
||||
|
||||
Set<AccountDeviceInstance> accounts = relatedAccounts.keySet();
|
||||
@ -298,9 +284,9 @@ final class CommunicationsGraph extends mxGraph {
|
||||
Map<AccountPair, Long> relationshipCounts = commsManager.getRelationshipCountsPairwise(accounts, currentFilter);
|
||||
|
||||
int total = relationshipCounts.size();
|
||||
int k = 0;
|
||||
int progress = 0;
|
||||
String progressText = "";
|
||||
progress.switchToDeterminate("", 0, total);
|
||||
progressIndicator.switchToDeterminate("", 0, total);
|
||||
for (Map.Entry<AccountPair, Long> entry : relationshipCounts.entrySet()) {
|
||||
Long count = entry.getValue();
|
||||
AccountPair relationshipKey = entry.getKey();
|
||||
@ -312,11 +298,10 @@ final class CommunicationsGraph extends mxGraph {
|
||||
mxCell addEdge = addOrUpdateEdge(count, account1, account2);
|
||||
progressText = addEdge.getId();
|
||||
}
|
||||
progress.progress(progressText, k++);
|
||||
progressIndicator.progress(progressText, progress++);
|
||||
}
|
||||
} catch (TskCoreException tskCoreException) {
|
||||
logger.log(Level.SEVERE, "Error", tskCoreException);
|
||||
} finally {
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -332,7 +317,7 @@ final class CommunicationsGraph extends mxGraph {
|
||||
} catch (CancellationException ex) {
|
||||
logger.log(Level.INFO, "Graph visualization cancelled");
|
||||
} finally {
|
||||
progress.finish();
|
||||
progressIndicator.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sleuthkit.autopsy.communications;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public interface EventHandler<T> {
|
||||
|
||||
void handle(T event);
|
||||
}
|
@ -18,20 +18,19 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.communications;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import com.mxgraph.model.mxCell;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
class LockedVertexModel {
|
||||
|
||||
void registerhandler(EventHandler<VertexLockEvent> handler) {
|
||||
eventBus.register(handler);
|
||||
}
|
||||
|
||||
void unregisterhandler(EventHandler<VertexLockEvent> handler) {
|
||||
eventBus.unregister(handler);
|
||||
}
|
||||
/**
|
||||
* Model of which vertices in a graph are locked ( not moveable by layout
|
||||
* algorithms).
|
||||
*
|
||||
*/
|
||||
final class LockedVertexModel {
|
||||
|
||||
private final EventBus eventBus = new EventBus();
|
||||
|
||||
@ -42,30 +41,34 @@ class LockedVertexModel {
|
||||
*/
|
||||
private final Set<mxCell> lockedVertices = new HashSet<>();
|
||||
|
||||
LockedVertexModel() {
|
||||
void registerhandler(Object handler) {
|
||||
eventBus.register(handler);
|
||||
}
|
||||
|
||||
void unregisterhandler(Object handler) {
|
||||
eventBus.unregister(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock the given vertex so that applying a layout algorithm doesn't move
|
||||
* it. The user can still manually position the vertex.
|
||||
* Lock the given vertices so that applying a layout algorithm doesn't move
|
||||
* them. The user can still manually position the vertices.
|
||||
*
|
||||
* @param vertex The vertex to lock.
|
||||
*/
|
||||
void lockVertex(mxCell vertex) {
|
||||
lockedVertices.add(vertex);
|
||||
eventBus.post(new VertexLockEvent(vertex, true));
|
||||
|
||||
void lock(Collection<mxCell> vertices) {
|
||||
lockedVertices.addAll(vertices);
|
||||
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.
|
||||
*/
|
||||
void unlockVertex(mxCell vertex) {
|
||||
lockedVertices.remove(vertex);
|
||||
eventBus.post(new VertexLockEvent(vertex, false));
|
||||
|
||||
void unlock(Collection<mxCell> vertices) {
|
||||
lockedVertices.removeAll(vertices);
|
||||
eventBus.post(new VertexLockEvent(false, vertices));
|
||||
}
|
||||
|
||||
boolean isVertexLocked(mxCell vertex) {
|
||||
@ -77,21 +80,36 @@ class LockedVertexModel {
|
||||
lockedVertices.clear();
|
||||
}
|
||||
|
||||
static class VertexLockEvent {
|
||||
|
||||
private final mxCell vertex;
|
||||
|
||||
public mxCell getVertex() {
|
||||
return vertex;
|
||||
boolean isEmpty() {
|
||||
return lockedVertices.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isVertexLocked() {
|
||||
/**
|
||||
* Event that represents a change in the locked state of one or more
|
||||
* vertices.
|
||||
*/
|
||||
final static class VertexLockEvent {
|
||||
|
||||
private final boolean locked;
|
||||
private final Set<mxCell> vertices;
|
||||
|
||||
/**
|
||||
* @return The vertices whose locked state has changed.
|
||||
*/
|
||||
public Set<mxCell> getVertices() {
|
||||
return vertices;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if the vertices are locked, False if the vertices are
|
||||
* unlocked.
|
||||
*/
|
||||
public boolean isLocked() {
|
||||
return locked;
|
||||
}
|
||||
private final boolean locked;
|
||||
|
||||
VertexLockEvent(mxCell vertex, boolean locked) {
|
||||
this.vertex = vertex;
|
||||
VertexLockEvent(boolean locked, Collection< mxCell> vertices) {
|
||||
this.vertices = ImmutableSet.copyOf(vertices);
|
||||
this.locked = locked;
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,6 @@ import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
|
||||
import org.sleuthkit.autopsy.corecomponents.TableFilterNode;
|
||||
import org.sleuthkit.autopsy.directorytree.DataResultFilterNode;
|
||||
import org.sleuthkit.datamodel.AccountDeviceInstance;
|
||||
|
||||
/**
|
||||
* The right hand side of the CVT. Has a DataResultPanel to show a listing of
|
||||
@ -151,10 +150,10 @@ public final class MessageBrowser extends JPanel implements ExplorerManager.Prov
|
||||
//Use lookup here?
|
||||
final AccountDeviceInstanceNode adiNode = (AccountDeviceInstanceNode) selectedNodes[0];
|
||||
|
||||
final Set<AccountDeviceInstance> accountDeviceInstances = new HashSet<>();
|
||||
final Set<AccountDeviceInstanceKey> accountDeviceInstances = new HashSet<>();
|
||||
for (final Node n : selectedNodes) {
|
||||
//Use lookup here?
|
||||
accountDeviceInstances.add(((AccountDeviceInstanceNode) n).getAccountDeviceInstance());
|
||||
accountDeviceInstances.add(((AccountDeviceInstanceNode) n).getAccountDeviceInstanceKey());
|
||||
}
|
||||
return SelectionNode.createFromAccounts(accountDeviceInstances, adiNode.getFilter(), adiNode.getCommsManager());
|
||||
}
|
||||
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.communications;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import javax.swing.ImageIcon;
|
||||
import org.openide.util.ImageUtilities;
|
||||
import org.openide.util.NbBundle;
|
||||
|
||||
/**
|
||||
* Action that pins the AccountDevicesIntanceKeys in the ActionsGlobalContext to
|
||||
* the visualizaion
|
||||
*/
|
||||
@NbBundle.Messages({"PinAccountsAction.pluralText=Add Selected Accounts to Visualization",
|
||||
"PinAccountsAction.singularText=Add Selected Account to Visualization"})
|
||||
final class PinAccountsAction extends AbstractCVTAction {
|
||||
|
||||
static private final ImageIcon ICON = ImageUtilities.loadImageIcon(
|
||||
"/org/sleuthkit/autopsy/communications/images/marker--plus.png", false);
|
||||
private static final String SINGULAR_TEXT = Bundle.PinAccountsAction_singularText();
|
||||
private static final String PLURAL_TEXT = Bundle.PinAccountsAction_pluralText();
|
||||
|
||||
private static final PinAccountsAction instance = new PinAccountsAction();
|
||||
|
||||
static PinAccountsAction getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
CVTEvents.getCVTEventBus().post(new CVTEvents.PinAccountsEvent(getSelectedAccounts(), false));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getActionDisplayName() {
|
||||
return getSelectedAccounts().size() > 1 ? PLURAL_TEXT : SINGULAR_TEXT;
|
||||
}
|
||||
|
||||
@Override
|
||||
ImageIcon getIcon() {
|
||||
return ICON;
|
||||
}
|
||||
}
|
@ -19,22 +19,31 @@
|
||||
package org.sleuthkit.autopsy.communications;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Model of what accounts are pinned to a visualization.
|
||||
*/
|
||||
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
|
||||
* to pinned accounts and pass the filters are show. Pinning accounts is the
|
||||
* primary way to populate the graph.
|
||||
*/
|
||||
private final Set<AccountDeviceInstanceKey> pinnedAccountDevices = new HashSet<>();
|
||||
private final CommunicationsGraph graph;
|
||||
|
||||
PinnedAccountModel(CommunicationsGraph graph) {
|
||||
this.graph = graph;
|
||||
private final EventBus eventBus = new EventBus();
|
||||
|
||||
void registerhandler(Object handler) {
|
||||
eventBus.register(handler);
|
||||
}
|
||||
|
||||
void unregisterhandler(Object handler) {
|
||||
eventBus.unregister(handler);
|
||||
}
|
||||
|
||||
boolean isAccountPinned(AccountDeviceInstanceKey account) {
|
||||
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.communications;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import javax.swing.ImageIcon;
|
||||
import org.openide.util.ImageUtilities;
|
||||
import org.openide.util.NbBundle;
|
||||
|
||||
/**
|
||||
* Action that clears any pinned accounts and pins the AcountDevicesInstanceKeys
|
||||
* in the ActionsGlobalContext to the visualization.
|
||||
*/
|
||||
@NbBundle.Messages(value = {"ResetAndPinAccountsAction.singularText=Visualize Only Selected Account",
|
||||
"ResetAndPinAccountsAction.pluralText=Visualize Only Selected Accounts"})
|
||||
final class ResetAndPinAccountsAction extends AbstractCVTAction {
|
||||
|
||||
private static final ImageIcon ICON = ImageUtilities.loadImageIcon(
|
||||
"/org/sleuthkit/autopsy/communications/images/marker--pin.png", false);
|
||||
private static final String SINGULAR_TEXT = Bundle.ResetAndPinAccountsAction_singularText();
|
||||
private static final String PLURAL_TEXT = Bundle.ResetAndPinAccountsAction_pluralText();
|
||||
|
||||
private static final ResetAndPinAccountsAction instance = new ResetAndPinAccountsAction();
|
||||
|
||||
static ResetAndPinAccountsAction getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
CVTEvents.getCVTEventBus().post(new CVTEvents.PinAccountsEvent(getSelectedAccounts(), true));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getActionDisplayName() {
|
||||
return getSelectedAccounts().size() > 1 ? PLURAL_TEXT : SINGULAR_TEXT;
|
||||
}
|
||||
|
||||
@Override
|
||||
ImageIcon getIcon() {
|
||||
return ICON;
|
||||
}
|
||||
}
|
@ -24,10 +24,13 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import org.openide.nodes.AbstractNode;
|
||||
import org.openide.nodes.ChildFactory;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.util.Lookup;
|
||||
import org.openide.util.lookup.Lookups;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.AccountDeviceInstance;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
@ -44,23 +47,27 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
*/
|
||||
final class SelectionNode extends AbstractNode {
|
||||
|
||||
private SelectionNode(Children children) {
|
||||
super(children);
|
||||
private SelectionNode(Children children, Lookup lookup) {
|
||||
super(children, lookup);
|
||||
}
|
||||
|
||||
static SelectionNode createFromAccountsAndRelationships(
|
||||
Set<Content> edgeRelationshipArtifacts,
|
||||
Set<AccountDeviceInstance> accountDeviceInstances,
|
||||
Set<AccountDeviceInstanceKey> accountDeviceInstanceKeys,
|
||||
CommunicationsFilter filter,
|
||||
CommunicationsManager commsManager) {
|
||||
|
||||
Set<AccountDeviceInstance> accountDeviceInstances = accountDeviceInstanceKeys.stream()
|
||||
.map(AccountDeviceInstanceKey::getAccountDeviceInstance)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
SelectionNode node = new SelectionNode(Children.create(
|
||||
new RelationshipChildren(
|
||||
edgeRelationshipArtifacts,
|
||||
accountDeviceInstances,
|
||||
commsManager,
|
||||
filter),
|
||||
true));
|
||||
true), Lookups.fixed(accountDeviceInstanceKeys.toArray()));
|
||||
|
||||
//This is not good for internationalization!!!
|
||||
String name = "";
|
||||
@ -82,7 +89,7 @@ final class SelectionNode extends AbstractNode {
|
||||
}
|
||||
|
||||
static SelectionNode createFromAccounts(
|
||||
Set<AccountDeviceInstance> accountDeviceInstances,
|
||||
Set<AccountDeviceInstanceKey> accountDeviceInstances,
|
||||
CommunicationsFilter filter,
|
||||
CommunicationsManager commsManager) {
|
||||
|
||||
@ -122,9 +129,9 @@ final class SelectionNode extends AbstractNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node createNodeForKey(Content t) {
|
||||
if (t instanceof BlackboardArtifact) {
|
||||
return new RelationshipNode((BlackboardArtifact) t);
|
||||
protected Node createNodeForKey(Content content) {
|
||||
if (content instanceof BlackboardArtifact) {
|
||||
return new RelationshipNode((BlackboardArtifact) content);
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Cannot create a RelationshipNode for non BlackboardArtifact content.");
|
||||
}
|
||||
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.communications;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import javax.swing.ImageIcon;
|
||||
import org.openide.util.ImageUtilities;
|
||||
import org.openide.util.NbBundle;
|
||||
|
||||
/**
|
||||
* Action that unpins the AcccountDeviceInstanceKeys in the ActionsGlobalContext
|
||||
* form the visualization.
|
||||
*/
|
||||
@NbBundle.Messages({"UnpinAccountsAction.pluralText=Remove Selected Accounts",
|
||||
"UnpinAccountsAction.singularText=Remove Selected Account"})
|
||||
final class UnpinAccountsAction extends AbstractCVTAction {
|
||||
|
||||
static final private ImageIcon ICON = ImageUtilities.loadImageIcon(
|
||||
"/org/sleuthkit/autopsy/communications/images/marker--minus.png", false);
|
||||
private static final String SINGULAR_TEXT = Bundle.UnpinAccountsAction_singularText();
|
||||
private static final String PLURAL_TEXT = Bundle.UnpinAccountsAction_pluralText();
|
||||
|
||||
private static final UnpinAccountsAction instance = new UnpinAccountsAction();
|
||||
|
||||
static UnpinAccountsAction getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent event) {
|
||||
CVTEvents.getCVTEventBus().post(new CVTEvents.UnpinAccountsEvent(getSelectedAccounts()));
|
||||
}
|
||||
|
||||
@Override
|
||||
String getActionDisplayName() {
|
||||
return getSelectedAccounts().size() > 1 ? PLURAL_TEXT : SINGULAR_TEXT;
|
||||
}
|
||||
|
||||
@Override
|
||||
ImageIcon getIcon() {
|
||||
return ICON;
|
||||
}
|
||||
}
|
@ -22,14 +22,12 @@ import com.google.common.eventbus.Subscribe;
|
||||
import com.mxgraph.layout.hierarchical.mxHierarchicalLayout;
|
||||
import com.mxgraph.layout.mxCircleLayout;
|
||||
import com.mxgraph.layout.mxFastOrganicLayout;
|
||||
import com.mxgraph.layout.mxGraphLayout;
|
||||
import com.mxgraph.layout.mxIGraphLayout;
|
||||
import com.mxgraph.layout.mxOrganicLayout;
|
||||
import com.mxgraph.model.mxCell;
|
||||
import com.mxgraph.model.mxICell;
|
||||
import com.mxgraph.swing.handler.mxRubberband;
|
||||
import com.mxgraph.swing.mxGraphComponent;
|
||||
import com.mxgraph.swing.util.mxMorphing;
|
||||
import com.mxgraph.util.mxEvent;
|
||||
import com.mxgraph.util.mxEventObject;
|
||||
import com.mxgraph.util.mxEventSource;
|
||||
@ -37,7 +35,9 @@ import com.mxgraph.util.mxPoint;
|
||||
import com.mxgraph.util.mxRectangle;
|
||||
import com.mxgraph.util.mxUndoManager;
|
||||
import com.mxgraph.util.mxUndoableEdit;
|
||||
import com.mxgraph.view.mxCellState;
|
||||
import com.mxgraph.view.mxGraph;
|
||||
import com.mxgraph.view.mxGraphView;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Cursor;
|
||||
@ -53,17 +53,18 @@ import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyVetoException;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Arrays;
|
||||
import static java.util.Collections.singleton;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
@ -86,12 +87,11 @@ import org.openide.util.Lookup;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.ProxyLookup;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.progress.ModalDialogProgressIndicator;
|
||||
import org.sleuthkit.datamodel.AccountDeviceInstance;
|
||||
import org.sleuthkit.datamodel.CommunicationsFilter;
|
||||
import org.sleuthkit.datamodel.CommunicationsManager;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
@ -107,23 +107,17 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
* CVTTopComponent when this tab is active allowing for context sensitive
|
||||
* actions to work correctly.
|
||||
*/
|
||||
@NbBundle.Messages("VisualizationPanel.cancelButton.text=Cancel")
|
||||
final public class VisualizationPanel extends JPanel implements Lookup.Provider {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger logger = Logger.getLogger(VisualizationPanel.class.getName());
|
||||
private static final String BASE_IMAGE_PATH = "/org/sleuthkit/autopsy/communications/images";
|
||||
static final private ImageIcon pinIcon
|
||||
= new ImageIcon(VisualizationPanel.class.getResource(BASE_IMAGE_PATH + "/marker--pin.png"));
|
||||
static final private ImageIcon addPinIcon
|
||||
= new ImageIcon(VisualizationPanel.class.getResource(BASE_IMAGE_PATH + "/marker--plus.png"));
|
||||
static final private ImageIcon unpinIcon
|
||||
= new ImageIcon(VisualizationPanel.class.getResource(BASE_IMAGE_PATH + "/marker--minus.png"));
|
||||
static final private ImageIcon unlockIcon
|
||||
= new ImageIcon(VisualizationPanel.class.getResource(BASE_IMAGE_PATH + "/lock_large_unlocked.png"));
|
||||
static final private ImageIcon lockIcon
|
||||
= 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 final ExplorerManager vizEM = new ExplorerManager();
|
||||
@ -138,27 +132,20 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
private final CommunicationsGraph graph;
|
||||
|
||||
private final mxUndoManager undoManager = new mxUndoManager();
|
||||
private final mxRubberband rubberband;
|
||||
private final mxFastOrganicLayout fastOrganicLayout;
|
||||
private final mxCircleLayout circleLayout;
|
||||
private final mxOrganicLayout organicLayout;
|
||||
private final mxHierarchicalLayout hierarchyLayout;
|
||||
private final mxRubberband rubberband; //NOPMD We keep a referenec as insurance to prevent garbage collection
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
private SwingWorker<?, ?> worker;
|
||||
private final PinnedAccountModel pinnedAccountModel;
|
||||
private final LockedVertexModel lockedVertexModel;
|
||||
private final PinnedAccountModel pinnedAccountModel = new PinnedAccountModel();
|
||||
private final LockedVertexModel lockedVertexModel = new LockedVertexModel();
|
||||
|
||||
private final Map<mxGraphLayout, JButton> layoutButtons = new HashMap<>();
|
||||
private mxGraphLayout currentLayout;
|
||||
private final Map<NamedGraphLayout, JButton> layoutButtons = new HashMap<>();
|
||||
private NamedGraphLayout currentLayout;
|
||||
|
||||
public VisualizationPanel() {
|
||||
initComponents();
|
||||
graph = new CommunicationsGraph();
|
||||
|
||||
pinnedAccountModel = graph.getPinnedAccountModel();
|
||||
lockedVertexModel = graph.getLockedVertexModel();
|
||||
|
||||
graph = new CommunicationsGraph(pinnedAccountModel, lockedVertexModel);
|
||||
|
||||
graphComponent = new mxGraphComponent(graph);
|
||||
graphComponent.setAutoExtend(true);
|
||||
@ -172,41 +159,43 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
graphComponent.setBackground(Color.WHITE);
|
||||
borderLayoutPanel.add(graphComponent, BorderLayout.CENTER);
|
||||
|
||||
//install rubber band selection handler
|
||||
//install rubber band other handlers
|
||||
rubberband = new mxRubberband(graphComponent);
|
||||
|
||||
lockedVertexModel.registerhandler(this);
|
||||
|
||||
final mxEventSource.mxIEventListener scaleListener = (Object sender, mxEventObject evt)
|
||||
-> zoomLabel.setText(DecimalFormat.getPercentInstance().format(graph.getView().getScale()));
|
||||
graph.getView().addListener(mxEvent.SCALE, scaleListener);
|
||||
graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, scaleListener);
|
||||
|
||||
final GraphMouseListener graphMouseListener = new GraphMouseListener();
|
||||
|
||||
graphComponent.getGraphControl().addMouseWheelListener(graphMouseListener);
|
||||
graphComponent.getGraphControl().addMouseListener(graphMouseListener);
|
||||
|
||||
final MessageBrowser messageBrowser = new MessageBrowser(vizEM, gacEM);
|
||||
splitPane.setRightComponent(messageBrowser);
|
||||
proxyLookup = new ProxyLookup(
|
||||
messageBrowser.getLookup(),
|
||||
ExplorerUtils.createLookup(vizEM, getActionMap()));
|
||||
ExplorerUtils.createLookup(vizEM, getActionMap()),
|
||||
messageBrowser.getLookup()
|
||||
);
|
||||
|
||||
//feed selection to explorermanager
|
||||
graph.getSelectionModel().addListener(null, new SelectionListener());
|
||||
graph.getSelectionModel().addListener(mxEvent.CHANGE, 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);
|
||||
|
||||
fastOrganicLayout = new mxFastOrganicLayoutImpl(graph);
|
||||
circleLayout = new mxCircleLayoutImpl(graph);
|
||||
organicLayout = new mxOrganicLayoutImpl(graph);
|
||||
FastOrganicLayoutImpl fastOrganicLayout = new FastOrganicLayoutImpl(graph);
|
||||
CircleLayoutImpl circleLayout = new CircleLayoutImpl(graph);
|
||||
OrganicLayoutImpl organicLayout = new OrganicLayoutImpl(graph);
|
||||
organicLayout.setMaxIterations(10);
|
||||
hierarchyLayout = new mxHierarchicalLayoutImpl(graph);
|
||||
HierarchicalLayoutImpl hierarchyLayout = new HierarchicalLayoutImpl(graph);
|
||||
|
||||
//local method to configure layout buttons
|
||||
BiConsumer<JButton, mxGraphLayout> configure = (layoutButton, layout) -> {
|
||||
BiConsumer<JButton, NamedGraphLayout> configure = (layoutButton, layout) -> {
|
||||
layoutButtons.put(layout, layoutButton);
|
||||
layoutButton.addActionListener(event -> applyLayout(layout));
|
||||
};
|
||||
@ -216,7 +205,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
configure.accept(fastOrganicLayoutButton, fastOrganicLayout);
|
||||
configure.accept(hierarchyLayoutButton, hierarchyLayout);
|
||||
|
||||
applyLayout(circleLayout);
|
||||
applyLayout(fastOrganicLayout);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -225,24 +214,35 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
* @param layout the value of layout
|
||||
*/
|
||||
@Override
|
||||
|
||||
public Lookup getLookup() {
|
||||
return proxyLookup;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
void handleUnPinEvent(final CVTEvents.UnpinAccountsEvent pinEvent) {
|
||||
void handle(LockedVertexModel.VertexLockEvent event) {
|
||||
final Set<mxCell> vertices = event.getVertices();
|
||||
mxGraphView view = graph.getView();
|
||||
vertices.forEach(vertex -> {
|
||||
final mxCellState state = view.getState(vertex, true);
|
||||
view.updateLabel(state);
|
||||
view.updateLabelBounds(state);
|
||||
view.updateBoundingBox(state);
|
||||
graphComponent.redraw(state);
|
||||
});
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
void handle(final CVTEvents.UnpinAccountsEvent pinEvent) {
|
||||
graph.getModel().beginUpdate();
|
||||
pinnedAccountModel.unpinAccount(pinEvent.getAccountDeviceInstances());
|
||||
graph.clear();
|
||||
rebuildGraph();
|
||||
// Updates the display
|
||||
graph.getModel().endUpdate();
|
||||
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
void handlePinEvent(final CVTEvents.PinAccountsEvent pinEvent) {
|
||||
void handle(final CVTEvents.PinAccountsEvent pinEvent) {
|
||||
graph.getModel().beginUpdate();
|
||||
if (pinEvent.isReplace()) {
|
||||
graph.resetGraph();
|
||||
@ -251,19 +251,16 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
rebuildGraph();
|
||||
// Updates the display
|
||||
graph.getModel().endUpdate();
|
||||
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
void handleFilterEvent(final CVTEvents.FilterChangeEvent filterChangeEvent) {
|
||||
|
||||
void handle(final CVTEvents.FilterChangeEvent filterChangeEvent) {
|
||||
graph.getModel().beginUpdate();
|
||||
graph.clear();
|
||||
currentFilter = filterChangeEvent.getNewFilter();
|
||||
rebuildGraph();
|
||||
// Updates the display
|
||||
graph.getModel().endUpdate();
|
||||
|
||||
}
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
@ -288,15 +285,12 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
if (worker.isCancelled()) {
|
||||
graph.resetGraph();
|
||||
rebuildGraph();
|
||||
morph(organicLayout);
|
||||
} else {
|
||||
morph(fastOrganicLayout);
|
||||
}
|
||||
applyLayout(currentLayout);
|
||||
}
|
||||
});
|
||||
|
||||
worker.execute();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -313,7 +307,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
logger.log(Level.SEVERE, "Can't get CommunicationsManager when there is no case open.", ex);
|
||||
}
|
||||
|
||||
Case.addEventTypeSubscriber(EnumSet.of(CURRENT_CASE), evt -> {
|
||||
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), evt -> {
|
||||
graph.getModel().beginUpdate();
|
||||
try {
|
||||
graph.resetGraph();
|
||||
@ -330,15 +324,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
logger.log(Level.SEVERE, "Error getting CommunicationsManager for the current case.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNotify() {
|
||||
super.removeNotify();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
@ -563,17 +551,54 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
}//GEN-LAST:event_zoomOutButtonActionPerformed
|
||||
|
||||
/**
|
||||
* Apply the given layout. The given layout becomes the current layout. The
|
||||
* layout is computed in the background.
|
||||
*
|
||||
* @param layoutButton the value of layoutButton
|
||||
* @param layout the value of layout
|
||||
* @param layout The layout to apply.
|
||||
*/
|
||||
private void applyLayout(mxGraphLayout layout) {
|
||||
@NbBundle.Messages({"VisualizationPanel.computingLayout=Computing Layout",
|
||||
"# {0} - layout name",
|
||||
"VisualizationPanel.layoutFailWithLockedVertices.text={0} layout failed with locked vertices. Unlock some vertices or try a different layout.",
|
||||
"# {0} - layout name",
|
||||
"VisualizationPanel.layoutFail.text={0} layout failed. Try a different layout."})
|
||||
private void applyLayout(NamedGraphLayout layout) {
|
||||
currentLayout = layout;
|
||||
layoutButtons.forEach((layoutKey, button)
|
||||
-> button.setFont(button.getFont().deriveFont(layoutKey == layout ? Font.BOLD : Font.PLAIN)));
|
||||
morph(layout);
|
||||
|
||||
ModalDialogProgressIndicator progressIndicator = new ModalDialogProgressIndicator(windowAncestor, Bundle.VisualizationPanel_computingLayout());
|
||||
progressIndicator.start(Bundle.VisualizationPanel_computingLayout());
|
||||
|
||||
new SwingWorker<Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground() {
|
||||
graph.getModel().beginUpdate();
|
||||
try {
|
||||
layout.execute(graph.getDefaultParent());
|
||||
fitGraph();
|
||||
} finally {
|
||||
graph.getModel().endUpdate();
|
||||
progressIndicator.finish();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
try {
|
||||
get();
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
logger.log(Level.WARNING, "CVT graph layout failed.", ex);
|
||||
if (lockedVertexModel.isEmpty()) {
|
||||
MessageNotifyUtil.Message.error(Bundle.VisualizationPanel_layoutFail_text(layout.getDisplayName()));
|
||||
} else {
|
||||
MessageNotifyUtil.Message.error(Bundle.VisualizationPanel_layoutFailWithLockedVertices_text(layout.getDisplayName()));
|
||||
}
|
||||
undoManager.undo();
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void clearVizButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_clearVizButtonActionPerformed
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
@ -584,7 +609,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
// Updates the display
|
||||
graph.getModel().endUpdate();
|
||||
setCursor(Cursor.getDefaultCursor());
|
||||
|
||||
}//GEN-LAST:event_clearVizButtonActionPerformed
|
||||
|
||||
private void fitGraph() {
|
||||
@ -609,57 +633,11 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
|
||||
final Dimension size = graphComponent.getSize();
|
||||
final double widthFactor = size.getWidth() / boundsForCells.getWidth();
|
||||
final double heightFactor = size.getHeight() / boundsForCells.getHeight();
|
||||
|
||||
graphComponent.zoom(widthFactor);
|
||||
|
||||
graphComponent.zoom((heightFactor + widthFactor) / 2.0);
|
||||
}
|
||||
|
||||
private void morph(mxIGraphLayout layout) {
|
||||
// layout using morphing
|
||||
graph.getModel().beginUpdate();
|
||||
|
||||
CancelationListener cancelationListener = new CancelationListener();
|
||||
ModalDialogProgressIndicator progress = new ModalDialogProgressIndicator(windowAncestor, "Computing layout", new String[]{CANCEL}, CANCEL, cancelationListener);
|
||||
SwingWorker<Void, Void> morphWorker = new SwingWorker<Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground() {
|
||||
progress.start("Computing layout");
|
||||
layout.execute(graph.getDefaultParent());
|
||||
if (isCancelled()) {
|
||||
progress.finish();
|
||||
return null;
|
||||
}
|
||||
mxMorphing morph = new mxMorphing(graphComponent, 20, 1.2, 20) {
|
||||
@Override
|
||||
public void updateAnimation() {
|
||||
fireEvent(new mxEventObject(mxEvent.EXECUTE));
|
||||
super.updateAnimation(); //To change body of generated methods, choose Tools | Templates.
|
||||
}
|
||||
|
||||
};
|
||||
morph.addListener(mxEvent.EXECUTE, (Object sender, mxEventObject evt) -> {
|
||||
if (isCancelled()) {
|
||||
morph.stopAnimation();
|
||||
}
|
||||
});
|
||||
morph.addListener(mxEvent.DONE, (Object sender, mxEventObject event) -> {
|
||||
graph.getModel().endUpdate();
|
||||
if (isCancelled()) {
|
||||
undoManager.undo();
|
||||
} else {
|
||||
fitGraph();
|
||||
}
|
||||
progress.finish();
|
||||
});
|
||||
|
||||
morph.startAnimation();
|
||||
return null;
|
||||
|
||||
}
|
||||
};
|
||||
cancelationListener.configure(morphWorker, progress);
|
||||
morphWorker.execute();
|
||||
}
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private JPanel borderLayoutPanel;
|
||||
@ -683,6 +661,10 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
private JButton zoomOutButton;
|
||||
// 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 {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ -694,7 +676,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
if (selectionCells.length > 0) {
|
||||
mxICell[] selectedCells = Arrays.asList(selectionCells).toArray(new mxCell[selectionCells.length]);
|
||||
HashSet<Content> relationshipSources = new HashSet<>();
|
||||
HashSet<AccountDeviceInstance> adis = new HashSet<>();
|
||||
HashSet<AccountDeviceInstanceKey> adis = new HashSet<>();
|
||||
for (mxICell cell : selectedCells) {
|
||||
if (cell.isEdge()) {
|
||||
mxICell source = (mxICell) graph.getModel().getTerminal(cell, true);
|
||||
@ -711,7 +693,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
logger.log(Level.SEVERE, " Error getting relationsips....", tskCoreException);
|
||||
}
|
||||
} else if (cell.isVertex()) {
|
||||
adis.add(((AccountDeviceInstanceKey) cell.getValue()).getAccountDeviceInstance());
|
||||
adis.add((AccountDeviceInstanceKey) cell.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@ -727,9 +709,20 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
}
|
||||
}
|
||||
|
||||
final private class mxFastOrganicLayoutImpl extends mxFastOrganicLayout {
|
||||
/**
|
||||
* Extend mxIGraphLayout with a getDisplayName method,
|
||||
*/
|
||||
private interface NamedGraphLayout extends mxIGraphLayout {
|
||||
|
||||
mxFastOrganicLayoutImpl(mxGraph graph) {
|
||||
String getDisplayName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension of mxFastOrganicLayout that ignores locked vertices.
|
||||
*/
|
||||
final private class FastOrganicLayoutImpl extends mxFastOrganicLayout implements NamedGraphLayout {
|
||||
|
||||
FastOrganicLayoutImpl(mxGraph graph) {
|
||||
super(graph);
|
||||
}
|
||||
|
||||
@ -740,18 +733,26 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
}
|
||||
|
||||
@Override
|
||||
public mxRectangle setVertexLocation(Object vertex, double x, double y) {
|
||||
public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x, y are standard coordinate names
|
||||
if (isVertexIgnored(vertex)) {
|
||||
return getVertexBounds(vertex);
|
||||
} else {
|
||||
return super.setVertexLocation(vertex, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Fast Organic";
|
||||
}
|
||||
}
|
||||
|
||||
final private class mxCircleLayoutImpl extends mxCircleLayout {
|
||||
/**
|
||||
* Extension of mxCircleLayout that ignores locked vertices.
|
||||
*/
|
||||
final private class CircleLayoutImpl extends mxCircleLayout implements NamedGraphLayout {
|
||||
|
||||
mxCircleLayoutImpl(mxGraph graph) {
|
||||
CircleLayoutImpl(mxGraph graph) {
|
||||
super(graph);
|
||||
setResetEdges(true);
|
||||
}
|
||||
@ -763,18 +764,26 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
}
|
||||
|
||||
@Override
|
||||
public mxRectangle setVertexLocation(Object vertex, double x, double y) {
|
||||
public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x, y are standard coordinate names
|
||||
if (isVertexIgnored(vertex)) {
|
||||
return getVertexBounds(vertex);
|
||||
} else {
|
||||
return super.setVertexLocation(vertex, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Circle";
|
||||
}
|
||||
}
|
||||
|
||||
final private class mxOrganicLayoutImpl extends mxOrganicLayout {
|
||||
/**
|
||||
* Extension of mxOrganicLayout that ignores locked vertices.
|
||||
*/
|
||||
final private class OrganicLayoutImpl extends mxOrganicLayout implements NamedGraphLayout {
|
||||
|
||||
mxOrganicLayoutImpl(mxGraph graph) {
|
||||
OrganicLayoutImpl(mxGraph graph) {
|
||||
super(graph);
|
||||
setResetEdges(true);
|
||||
}
|
||||
@ -786,18 +795,26 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
}
|
||||
|
||||
@Override
|
||||
public mxRectangle setVertexLocation(Object vertex, double x, double y) {
|
||||
public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x, y are standard coordinate names
|
||||
if (isVertexIgnored(vertex)) {
|
||||
return getVertexBounds(vertex);
|
||||
} else {
|
||||
return super.setVertexLocation(vertex, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Organic";
|
||||
}
|
||||
}
|
||||
|
||||
final private class mxHierarchicalLayoutImpl extends mxHierarchicalLayout {
|
||||
/**
|
||||
* Extension of mxHierarchicalLayout that ignores locked vertices.
|
||||
*/
|
||||
final private class HierarchicalLayoutImpl extends mxHierarchicalLayout implements NamedGraphLayout {
|
||||
|
||||
mxHierarchicalLayoutImpl(mxGraph graph) {
|
||||
HierarchicalLayoutImpl(mxGraph graph) {
|
||||
super(graph);
|
||||
}
|
||||
|
||||
@ -808,15 +825,24 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
}
|
||||
|
||||
@Override
|
||||
public mxRectangle setVertexLocation(Object vertex, double x, double y) {
|
||||
public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x, y are standard coordinate names
|
||||
if (isVertexIgnored(vertex)) {
|
||||
return getVertexBounds(vertex);
|
||||
} else {
|
||||
return super.setVertexLocation(vertex, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Hierarchical";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener that closses the given ModalDialogProgressIndicator and cancels
|
||||
* the future.
|
||||
*/
|
||||
private class CancelationListener implements ActionListener {
|
||||
|
||||
private Future<?> cancellable;
|
||||
@ -833,9 +859,12 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
cancellable.cancel(true);
|
||||
progress.finish();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Mouse Adapter for the graphComponent. Handles wheel zooming and context
|
||||
* menus.
|
||||
*/
|
||||
private class GraphMouseListener extends MouseAdapter {
|
||||
|
||||
/**
|
||||
@ -846,9 +875,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
@Override
|
||||
public void mouseWheelMoved(final MouseWheelEvent event) {
|
||||
super.mouseWheelMoved(event);
|
||||
if (event.getPreciseWheelRotation() > 0) {
|
||||
if (event.getPreciseWheelRotation() < 0) {
|
||||
graphComponent.zoomIn();
|
||||
} else if (event.getPreciseWheelRotation() < 0) {
|
||||
} else if (event.getPreciseWheelRotation() > 0) {
|
||||
graphComponent.zoomOut();
|
||||
}
|
||||
}
|
||||
@ -867,45 +896,71 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
|
||||
final JPopupMenu jPopupMenu = new JPopupMenu();
|
||||
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)) {
|
||||
jPopupMenu.add(new JMenuItem(new AbstractAction("UnLock " + cellAt.getId(), unlockIcon) {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent event) {
|
||||
lockedVertexModel.unlockVertex(cellAt);
|
||||
}
|
||||
}));
|
||||
jPopupMenu.add(new JMenuItem(new UnlockAction(selectedVertices)));
|
||||
} else {
|
||||
jPopupMenu.add(new JMenuItem(new AbstractAction("Lock " + cellAt.getId(), lockIcon) {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent event) {
|
||||
lockedVertexModel.lockVertex(cellAt);
|
||||
}
|
||||
}));
|
||||
jPopupMenu.add(new JMenuItem(new LockAction(selectedVertices)));
|
||||
}
|
||||
if (pinnedAccountModel.isAccountPinned(adiKey)) {
|
||||
jPopupMenu.add(new JMenuItem(new AbstractAction("Unpin " + cellAt.getId(), unpinIcon) {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent event) {
|
||||
handleUnPinEvent(new CVTEvents.UnpinAccountsEvent(singleton((AccountDeviceInstanceKey) cellAt.getValue())));
|
||||
}
|
||||
}));
|
||||
jPopupMenu.add(UnpinAccountsAction.getInstance().getPopupPresenter());
|
||||
} else {
|
||||
jPopupMenu.add(new JMenuItem(new AbstractAction("Pin " + cellAt.getId(), addPinIcon) {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent event) {
|
||||
handlePinEvent(new CVTEvents.PinAccountsEvent(singleton((AccountDeviceInstanceKey) cellAt.getValue()), false));
|
||||
}
|
||||
}));
|
||||
jPopupMenu.add(new JMenuItem(new AbstractAction("Pin only " + cellAt.getId(), pinIcon) {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent event) {
|
||||
handlePinEvent(new CVTEvents.PinAccountsEvent(singleton((AccountDeviceInstanceKey) cellAt.getValue()), true));
|
||||
}
|
||||
}));
|
||||
jPopupMenu.add(PinAccountsAction.getInstance().getPopupPresenter());
|
||||
jPopupMenu.add(ResetAndPinAccountsAction.getInstance().getPopupPresenter());
|
||||
}
|
||||
jPopupMenu.show(graphComponent.getGraphControl(), event.getX(), event.getY());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action that un-locks the selected vertices.
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action that locks the selected vertices.
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user