Autopsy  4.17.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
VisualizationPanel.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2017-2018 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.communications;
20 
21 import com.google.common.eventbus.Subscribe;
22 import com.mxgraph.layout.hierarchical.mxHierarchicalLayout;
23 import com.mxgraph.layout.mxCircleLayout;
24 import com.mxgraph.layout.mxFastOrganicLayout;
25 import com.mxgraph.layout.mxIGraphLayout;
26 import com.mxgraph.layout.mxOrganicLayout;
27 import com.mxgraph.model.mxCell;
28 import com.mxgraph.model.mxICell;
29 import com.mxgraph.swing.handler.mxRubberband;
30 import com.mxgraph.swing.mxGraphComponent;
31 import com.mxgraph.util.mxCellRenderer;
32 import com.mxgraph.util.mxEvent;
33 import com.mxgraph.util.mxEventObject;
34 import com.mxgraph.util.mxEventSource;
35 import com.mxgraph.util.mxPoint;
36 import com.mxgraph.util.mxRectangle;
37 import com.mxgraph.util.mxUndoManager;
38 import com.mxgraph.util.mxUndoableEdit;
39 import com.mxgraph.view.mxCellState;
40 import com.mxgraph.view.mxGraph;
41 import com.mxgraph.view.mxGraphView;
42 import java.awt.BorderLayout;
43 import java.awt.Color;
44 import java.awt.Desktop;
45 import java.awt.Dimension;
46 import java.awt.Font;
47 import java.awt.Frame;
48 import java.awt.Graphics;
49 import java.awt.GridBagConstraints;
50 import java.awt.GridBagLayout;
51 import java.awt.GridLayout;
52 import java.awt.Insets;
53 import java.awt.event.ActionEvent;
54 import java.awt.event.ActionListener;
55 import java.awt.event.MouseAdapter;
56 import java.awt.event.MouseEvent;
57 import java.awt.event.MouseWheelEvent;
58 import java.awt.image.BufferedImage;
59 import java.beans.PropertyChangeEvent;
60 import java.io.IOException;
61 import java.nio.file.Files;
62 import java.nio.file.Path;
63 import java.nio.file.Paths;
64 import java.text.DecimalFormat;
65 import java.text.SimpleDateFormat;
66 import java.util.Arrays;
67 import java.util.Date;
68 import java.util.EnumSet;
69 import java.util.HashMap;
70 import java.util.HashSet;
71 import java.util.Map;
72 import java.util.Set;
73 import java.util.concurrent.ExecutionException;
74 import java.util.concurrent.Future;
75 import java.util.function.BiConsumer;
76 import java.util.logging.Level;
77 import java.util.stream.Collectors;
78 import java.util.stream.Stream;
79 import javafx.application.Platform;
80 import javafx.embed.swing.JFXPanel;
81 import javafx.scene.Scene;
82 import javafx.scene.layout.Pane;
83 import javax.swing.AbstractAction;
84 import javax.swing.ImageIcon;
85 import javax.swing.JButton;
86 import javax.swing.JLabel;
87 import javax.swing.JMenuItem;
88 import javax.swing.JOptionPane;
89 import javax.swing.JPanel;
90 import javax.swing.JPopupMenu;
91 import javax.swing.JTextField;
92 import javax.swing.JTextPane;
93 import javax.swing.JToolBar;
94 import javax.swing.SwingConstants;
95 import javax.swing.SwingUtilities;
96 import javax.swing.SwingWorker;
97 import org.apache.commons.lang3.StringUtils;
98 import org.controlsfx.control.Notifications;
99 import org.openide.util.NbBundle;
100 import org.openide.windows.WindowManager;
110 import org.sleuthkit.datamodel.AccountDeviceInstance;
111 import org.sleuthkit.datamodel.CommunicationsFilter;
112 import org.sleuthkit.datamodel.CommunicationsManager;
113 import org.sleuthkit.datamodel.TskCoreException;
125 @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
126 final public class VisualizationPanel extends JPanel {
127 
128  private static final long serialVersionUID = 1L;
129  private static final Logger logger = Logger.getLogger(VisualizationPanel.class.getName());
130  private static final String BASE_IMAGE_PATH = "/org/sleuthkit/autopsy/communications/images";
131  static final private ImageIcon unlockIcon
132  = new ImageIcon(VisualizationPanel.class.getResource(BASE_IMAGE_PATH + "/lock_large_unlocked.png"));
133  static final private ImageIcon lockIcon
134  = new ImageIcon(VisualizationPanel.class.getResource(BASE_IMAGE_PATH + "/lock_large_locked.png"));
135 
136  @NbBundle.Messages("VisualizationPanel.cancelButton.text=Cancel")
137  private static final String CANCEL = Bundle.VisualizationPanel_cancelButton_text();
138 
139  private Frame windowAncestor;
140 
141  private CommunicationsManager commsManager;
142  private CommunicationsFilter currentFilter;
143 
144  private final mxGraphComponent graphComponent;
145  private final CommunicationsGraph graph;
146 
147  private final mxUndoManager undoManager = new mxUndoManager();
148  private final mxRubberband rubberband; //NOPMD We keep a referenec as insurance to prevent garbage collection
149 
151  private SwingWorker<?, ?> worker;
152  private final PinnedAccountModel pinnedAccountModel = new PinnedAccountModel();
153  private final LockedVertexModel lockedVertexModel = new LockedVertexModel();
154 
155  private final Map<NamedGraphLayout, JButton> layoutButtons = new HashMap<>();
156  private NamedGraphLayout currentLayout;
157 
158  private final RelationshipBrowser relationshipBrowser;
159 
160  private final StateManager stateManager;
161 
162  @NbBundle.Messages("VisalizationPanel.paintingError=Problem painting visualization.")
163  public VisualizationPanel(RelationshipBrowser relationshipBrowser) {
164  this.relationshipBrowser = relationshipBrowser;
165  initComponents();
166  //initialize invisible JFXPanel that is used to show JFXNotifications over this window.
167  notificationsJFXPanel.setScene(new Scene(new Pane()));
168 
169  graph = new CommunicationsGraph(pinnedAccountModel, lockedVertexModel);
170 
171  /*
172  * custom implementation of mxGraphComponent that uses... a custom
173  * implementation of mxGraphControl ... that overrides paint so we can
174  * catch the NPEs we are getting and deal with them. For now that means
175  * just ignoring them.
176  */
177  graphComponent = new mxGraphComponent(graph) {
178  @Override
179  protected mxGraphComponent.mxGraphControl createGraphControl() {
180 
181  return new mxGraphControl() {
182 
183  @Override
184  public void paint(Graphics graphics) {
185  try {
186  super.paint(graphics);
187  } catch (NullPointerException ex) { //NOPMD
188  /* We can't find the underlying cause of the NPE in
189  * jgraphx, but it doesn't seem to cause any
190  * noticeable problems, so we are just logging it
191  * and moving on.
192  */
193  logger.log(Level.WARNING, "There was a NPE while painting the VisualizationPanel", ex);
194  }
195  }
196 
197  };
198  }
199  };
200  graphComponent.setAutoExtend(true);
201  graphComponent.setAutoScroll(true);
202  graphComponent.setAutoscrolls(true);
203  graphComponent.setConnectable(false);
204  graphComponent.setDragEnabled(false);
205  graphComponent.setKeepSelectionVisibleOnZoom(true);
206  graphComponent.setOpaque(true);
207  graphComponent.setToolTips(true);
208  graphComponent.setBackground(Color.WHITE);
209  borderLayoutPanel.add(graphComponent, BorderLayout.CENTER);
210 
211  //install rubber band other handlers
212  rubberband = new mxRubberband(graphComponent);
213 
214  lockedVertexModel.registerhandler(this);
215 
216  final mxEventSource.mxIEventListener scaleListener = (Object sender, mxEventObject evt)
217  -> zoomPercentLabel.setText(DecimalFormat.getPercentInstance().format(graph.getView().getScale()));
218  graph.getView().addListener(mxEvent.SCALE, scaleListener);
219  graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, scaleListener);
220 
221  final GraphMouseListener graphMouseListener = new GraphMouseListener();
222  graphComponent.getGraphControl().addMouseWheelListener(graphMouseListener);
223  graphComponent.getGraphControl().addMouseListener(graphMouseListener);
224 
225  //feed selection to explorermanager
226  graph.getSelectionModel().addListener(mxEvent.CHANGE, new SelectionListener());
227  final mxEventSource.mxIEventListener undoListener = (Object sender, mxEventObject evt)
228  -> undoManager.undoableEditHappened((mxUndoableEdit) evt.getProperty("edit"));
229 
230  graph.getModel().addListener(mxEvent.UNDO, undoListener);
231  graph.getView().addListener(mxEvent.UNDO, undoListener);
232 
233  FastOrganicLayoutImpl fastOrganicLayout = new FastOrganicLayoutImpl(graph);
234 
235  //local method to configure layout buttons
236  BiConsumer<JButton, NamedGraphLayout> configure = (layoutButton, layout) -> {
237  layoutButtons.put(layout, layoutButton);
238  layoutButton.addActionListener(event -> applyLayout(layout));
239  };
240  //configure layout buttons.
241  configure.accept(fastOrganicLayoutButton, fastOrganicLayout);
242 
243  applyLayout(fastOrganicLayout);
244 
245  stateManager = new StateManager(pinnedAccountModel);
246 
247  setStateButtonsEnabled();
248 
249  toolbar.setLayout(new WrapLayout());
250  }
251 
252  @Subscribe
253  void handle(LockedVertexModel.VertexLockEvent event) {
254  final Set<mxCell> vertices = event.getVertices();
255  mxGraphView view = graph.getView();
256  vertices.forEach(vertex -> {
257  final mxCellState state = view.getState(vertex, true);
258  view.updateLabel(state);
259  view.updateLabelBounds(state);
260  view.updateBoundingBox(state);
261  graphComponent.redraw(state);
262  });
263  }
264 
265  @Subscribe
266  void handle(final CVTEvents.UnpinAccountsEvent pinEvent) {
267  graph.getModel().beginUpdate();
268  pinnedAccountModel.unpinAccount(pinEvent.getAccountDeviceInstances());
269  graph.clear();
270  rebuildGraph();
271  // Updates the display
272  graph.getModel().endUpdate();
273 
274  setStateButtonsEnabled();
275  }
276 
277  @Subscribe
278  void handle(final CVTEvents.PinAccountsEvent pinEvent) {
279  graph.getModel().beginUpdate();
280  if (pinEvent.isReplace()) {
281  graph.resetGraph();
282  }
283  pinnedAccountModel.pinAccount(pinEvent.getAccountDeviceInstances());
284  rebuildGraph();
285  // Updates the display
286  graph.getModel().endUpdate();
287 
288  setStateButtonsEnabled();
289  }
290 
291  @Subscribe
292  void handle(final CVTEvents.FilterChangeEvent filterChangeEvent) {
293  graph.getModel().beginUpdate();
294  graph.clear();
295  currentFilter = filterChangeEvent.getNewFilter();
296  rebuildGraph();
297  // Updates the display
298  graph.getModel().endUpdate();
299 
300  setStateButtonsEnabled();
301  }
302 
303  @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
304  private void rebuildGraph() {
305  if (pinnedAccountModel.isEmpty()) {
306  borderLayoutPanel.remove(graphComponent);
307  borderLayoutPanel.add(placeHolderPanel, BorderLayout.CENTER);
308  repaint();
309  } else {
310  borderLayoutPanel.remove(placeHolderPanel);
311  borderLayoutPanel.add(graphComponent, BorderLayout.CENTER);
312  if (worker != null) {
313  worker.cancel(true);
314  }
315 
316  final CancelationListener cancelationListener = new CancelationListener();
317  final ModalDialogProgressIndicator progress = new ModalDialogProgressIndicator(windowAncestor, "Loading Visualization", new String[]{CANCEL}, CANCEL, cancelationListener);
318  worker = graph.rebuild(progress, commsManager, currentFilter);
319  cancelationListener.configure(worker, progress);
320  worker.addPropertyChangeListener((final PropertyChangeEvent evt) -> {
321  if (worker.isDone()) {
322  if (worker.isCancelled()) {
323  graph.resetGraph();
324  rebuildGraph();
325  }
326  applyLayout(currentLayout);
327  }
328  });
329 
330  worker.execute();
331  }
332  }
333 
334  @Override
335  public void addNotify() {
336  super.addNotify();
337  windowAncestor = (Frame) SwingUtilities.getAncestorOfClass(Frame.class, this);
338 
339  try {
340  commsManager = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager();
341  } catch (TskCoreException ex) {
342  logger.log(Level.SEVERE, "Error getting CommunicationsManager for the current case.", ex); //NON-NLS
343  } catch (NoCurrentCaseException ex) {
344  logger.log(Level.SEVERE, "Can't get CommunicationsManager when there is no case open.", ex); //NON-NLS
345  }
346 
347  Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), evt -> {
348  graph.getModel().beginUpdate();
349  try {
350  graph.resetGraph();
351  } finally {
352  graph.getModel().endUpdate();
353  }
354  if (evt.getNewValue() == null) {
355  commsManager = null;
356  } else {
357  Case currentCase = (Case) evt.getNewValue();
358  try {
359  commsManager = currentCase.getSleuthkitCase().getCommunicationsManager();
360  } catch (TskCoreException ex) {
361  logger.log(Level.SEVERE, "Error getting CommunicationsManager for the current case.", ex); //NON-NLS
362  }
363  }
364  });
365  }
366 
372  @SuppressWarnings("unchecked")
373  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
374  private void initComponents() {
375  GridBagConstraints gridBagConstraints;
376 
377  borderLayoutPanel = new JPanel();
378  placeHolderPanel = new JPanel();
379  jTextPane1 = new JTextPane();
380  notificationsJFXPanel = new JFXPanel();
381  toolbar = new JToolBar();
382  backButton = new JButton();
383  forwardButton = new JButton();
384  jSeparator3 = new JToolBar.Separator();
385  clearVizButton = new JButton();
386  fastOrganicLayoutButton = new JButton();
387  jSeparator2 = new JToolBar.Separator();
388  zoomLabel = new JLabel();
389  zoomPercentLabel = new JLabel();
390  zoomOutButton = new JButton();
391  fitZoomButton = new JButton();
392  zoomActualButton = new JButton();
393  zoomInButton = new JButton();
394  jSeparator1 = new JToolBar.Separator();
395  snapshotButton = new JButton();
396 
397  setLayout(new BorderLayout());
398 
399  borderLayoutPanel.setLayout(new BorderLayout());
400 
401  placeHolderPanel.setLayout(new GridBagLayout());
402 
403  jTextPane1.setEditable(false);
404  jTextPane1.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jTextPane1.text")); // NOI18N
405  jTextPane1.setOpaque(false);
406  gridBagConstraints = new GridBagConstraints();
407  gridBagConstraints.anchor = GridBagConstraints.NORTH;
408  gridBagConstraints.weighty = 1.0;
409  gridBagConstraints.insets = new Insets(50, 0, 0, 0);
410  placeHolderPanel.add(jTextPane1, gridBagConstraints);
411 
412  borderLayoutPanel.add(placeHolderPanel, BorderLayout.CENTER);
413  borderLayoutPanel.add(notificationsJFXPanel, BorderLayout.PAGE_END);
414 
415  add(borderLayoutPanel, BorderLayout.CENTER);
416 
417  toolbar.setRollover(true);
418 
419  backButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/resultset_previous.png"))); // NOI18N
420  backButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.backButton.text_1")); // NOI18N
421  backButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.backButton.toolTipText")); // NOI18N
422  backButton.setFocusable(false);
423  backButton.setHorizontalTextPosition(SwingConstants.CENTER);
424  backButton.setVerticalTextPosition(SwingConstants.BOTTOM);
425  backButton.addActionListener(new ActionListener() {
426  public void actionPerformed(ActionEvent evt) {
427  backButtonActionPerformed(evt);
428  }
429  });
430  toolbar.add(backButton);
431 
432  forwardButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/resultset_next.png"))); // NOI18N
433  forwardButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.forwardButton.text")); // NOI18N
434  forwardButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.forwardButton.toolTipText")); // NOI18N
435  forwardButton.setFocusable(false);
436  forwardButton.setHorizontalTextPosition(SwingConstants.CENTER);
437  forwardButton.setVerticalTextPosition(SwingConstants.BOTTOM);
438  forwardButton.addActionListener(new ActionListener() {
439  public void actionPerformed(ActionEvent evt) {
440  forwardButtonActionPerformed(evt);
441  }
442  });
443  toolbar.add(forwardButton);
444  toolbar.add(jSeparator3);
445 
446  clearVizButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/broom.png"))); // NOI18N
447  clearVizButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.clearVizButton.text_1")); // NOI18N
448  clearVizButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.clearVizButton.toolTipText")); // NOI18N
449  clearVizButton.setActionCommand(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.clearVizButton.actionCommand")); // NOI18N
450  clearVizButton.setFocusable(false);
451  clearVizButton.setHorizontalTextPosition(SwingConstants.CENTER);
452  clearVizButton.setVerticalTextPosition(SwingConstants.BOTTOM);
453  clearVizButton.addActionListener(new ActionListener() {
454  public void actionPerformed(ActionEvent evt) {
455  clearVizButtonActionPerformed(evt);
456  }
457  });
458  toolbar.add(clearVizButton);
459 
460  fastOrganicLayoutButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/arrow-circle-double-135.png"))); // NOI18N
461  fastOrganicLayoutButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fastOrganicLayoutButton.text")); // NOI18N
462  fastOrganicLayoutButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fastOrganicLayoutButton.toolTipText")); // NOI18N
463  fastOrganicLayoutButton.setFocusable(false);
464  fastOrganicLayoutButton.setHorizontalTextPosition(SwingConstants.CENTER);
465  fastOrganicLayoutButton.setVerticalTextPosition(SwingConstants.BOTTOM);
466  fastOrganicLayoutButton.addActionListener(new ActionListener() {
467  public void actionPerformed(ActionEvent evt) {
468  fastOrganicLayoutButtonActionPerformed(evt);
469  }
470  });
471  toolbar.add(fastOrganicLayoutButton);
472  toolbar.add(jSeparator2);
473 
474  zoomLabel.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomLabel.text")); // NOI18N
475  toolbar.add(zoomLabel);
476 
477  zoomPercentLabel.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomPercentLabel.text")); // NOI18N
478  toolbar.add(zoomPercentLabel);
479 
480  zoomOutButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-out-red.png"))); // NOI18N
481  zoomOutButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomOutButton.text")); // NOI18N
482  zoomOutButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomOutButton.toolTipText")); // NOI18N
483  zoomOutButton.setFocusable(false);
484  zoomOutButton.setHorizontalTextPosition(SwingConstants.CENTER);
485  zoomOutButton.setVerticalTextPosition(SwingConstants.BOTTOM);
486  zoomOutButton.addActionListener(new ActionListener() {
487  public void actionPerformed(ActionEvent evt) {
488  zoomOutButtonActionPerformed(evt);
489  }
490  });
491  toolbar.add(zoomOutButton);
492 
493  fitZoomButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-fit.png"))); // NOI18N
494  fitZoomButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fitZoomButton.text")); // NOI18N
495  fitZoomButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fitZoomButton.toolTipText")); // NOI18N
496  fitZoomButton.setFocusable(false);
497  fitZoomButton.setHorizontalTextPosition(SwingConstants.CENTER);
498  fitZoomButton.setVerticalTextPosition(SwingConstants.BOTTOM);
499  fitZoomButton.addActionListener(new ActionListener() {
500  public void actionPerformed(ActionEvent evt) {
501  fitZoomButtonActionPerformed(evt);
502  }
503  });
504  toolbar.add(fitZoomButton);
505 
506  zoomActualButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-actual.png"))); // NOI18N
507  zoomActualButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomActualButton.text")); // NOI18N
508  zoomActualButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomActualButton.toolTipText")); // NOI18N
509  zoomActualButton.setFocusable(false);
510  zoomActualButton.setHorizontalTextPosition(SwingConstants.CENTER);
511  zoomActualButton.setVerticalTextPosition(SwingConstants.BOTTOM);
512  zoomActualButton.addActionListener(new ActionListener() {
513  public void actionPerformed(ActionEvent evt) {
514  zoomActualButtonActionPerformed(evt);
515  }
516  });
517  toolbar.add(zoomActualButton);
518 
519  zoomInButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-in-green.png"))); // NOI18N
520  zoomInButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomInButton.text")); // NOI18N
521  zoomInButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomInButton.toolTipText")); // NOI18N
522  zoomInButton.setFocusable(false);
523  zoomInButton.setHorizontalTextPosition(SwingConstants.CENTER);
524  zoomInButton.setVerticalTextPosition(SwingConstants.BOTTOM);
525  zoomInButton.addActionListener(new ActionListener() {
526  public void actionPerformed(ActionEvent evt) {
527  zoomInButtonActionPerformed(evt);
528  }
529  });
530  toolbar.add(zoomInButton);
531  toolbar.add(jSeparator1);
532 
533  snapshotButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/report/images/image.png"))); // NOI18N
534  snapshotButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapshotButton.text_1")); // NOI18N
535  snapshotButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapshotButton.toolTipText")); // NOI18N
536  snapshotButton.setFocusable(false);
537  snapshotButton.setHorizontalTextPosition(SwingConstants.CENTER);
538  snapshotButton.setVerticalTextPosition(SwingConstants.BOTTOM);
539  snapshotButton.addActionListener(new ActionListener() {
540  public void actionPerformed(ActionEvent evt) {
541  snapshotButtonActionPerformed(evt);
542  }
543  });
544  toolbar.add(snapshotButton);
545 
546  add(toolbar, BorderLayout.NORTH);
547  }// </editor-fold>//GEN-END:initComponents
548 
549  private void fitZoomButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_fitZoomButtonActionPerformed
550  fitGraph();
551  }//GEN-LAST:event_fitZoomButtonActionPerformed
552 
553  private void zoomActualButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_zoomActualButtonActionPerformed
554  graphComponent.zoomActual();
555  CVTEvents.getCVTEventBus().post(new CVTEvents.ScaleChangeEvent(graph.getView().getScale()));
556  }//GEN-LAST:event_zoomActualButtonActionPerformed
557 
558  private void zoomInButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_zoomInButtonActionPerformed
559  graphComponent.zoomIn();
560  CVTEvents.getCVTEventBus().post(new CVTEvents.ScaleChangeEvent(graph.getView().getScale()));
561  }//GEN-LAST:event_zoomInButtonActionPerformed
562 
563  private void zoomOutButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_zoomOutButtonActionPerformed
564  graphComponent.zoomOut();
565  CVTEvents.getCVTEventBus().post(new CVTEvents.ScaleChangeEvent(graph.getView().getScale()));
566  }//GEN-LAST:event_zoomOutButtonActionPerformed
567 
574  @NbBundle.Messages({"VisualizationPanel.computingLayout=Computing Layout",
575  "# {0} - layout name",
576  "VisualizationPanel.layoutFailWithLockedVertices.text={0} layout failed with locked vertices. Unlock some vertices or try a different layout.",
577  "# {0} - layout name",
578  "VisualizationPanel.layoutFail.text={0} layout failed. Try a different layout."})
579  private void applyLayout(NamedGraphLayout layout) {
580  currentLayout = layout;
581  layoutButtons.forEach((layoutKey, button)
582  -> button.setFont(button.getFont().deriveFont(layoutKey == layout ? Font.BOLD : Font.PLAIN)));
583 
584  ModalDialogProgressIndicator progressIndicator = new ModalDialogProgressIndicator(windowAncestor, Bundle.VisualizationPanel_computingLayout());
585  progressIndicator.start(Bundle.VisualizationPanel_computingLayout());
586 
587  new SwingWorker<Void, Void>() {
588  @Override
589  protected Void doInBackground() {
590  graph.getModel().beginUpdate();
591  try {
592  layout.execute(graph.getDefaultParent());
593  fitGraph();
594  } finally {
595  graph.getModel().endUpdate();
596  progressIndicator.finish();
597  }
598  return null;
599  }
600 
601  @Override
602  protected void done() {
603  try {
604  get();
605  } catch (InterruptedException | ExecutionException ex) {
606  logger.log(Level.WARNING, "CVT graph layout failed.", ex);
607  }
608  }
609  }.execute();
610  }
611 
612  private void clearVizButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_clearVizButtonActionPerformed
613  CVTEvents.getCVTEventBus().post(new CVTEvents.UnpinAccountsEvent(pinnedAccountModel.getPinnedAccounts()));
614  }//GEN-LAST:event_clearVizButtonActionPerformed
615 
616  private void forwardButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_forwardButtonActionPerformed
617  handleStateChange(stateManager.advance());
618  }//GEN-LAST:event_forwardButtonActionPerformed
619 
620  private void backButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_backButtonActionPerformed
621  handleStateChange(stateManager.retreat());
622  }//GEN-LAST:event_backButtonActionPerformed
623 
629  private void handleStateChange(StateManager.CommunicationsState newState ){
630  if(newState == null) {
631  return;
632  }
633 
634  // If the zoom was changed, only change the zoom.
635  if(newState.isZoomChange()) {
636  graph.getView().setScale(newState.getZoomValue());
637  return;
638  }
639 
640  // This will cause the FilterPane to update its controls
641  CVTEvents.getCVTEventBus().post(new CVTEvents.StateChangeEvent(newState));
642  setStateButtonsEnabled();
643 
644  graph.getModel().beginUpdate();
645  graph.resetGraph();
646 
647  if(newState.getPinnedList() != null) {
648  pinnedAccountModel.pinAccount(newState.getPinnedList());
649  } else {
650  pinnedAccountModel.clear();
651  }
652 
653  currentFilter = newState.getCommunicationsFilter();
654 
655  rebuildGraph();
656  // Updates the display
657  graph.getModel().endUpdate();
658 
659  fitGraph();
660 
661  }
662 
663  private void setStateButtonsEnabled() {
664  backButton.setEnabled(stateManager.canRetreat());
665  forwardButton.setEnabled(stateManager.canAdvance());
666  }
667 
668  @NbBundle.Messages({
669  "VisualizationPanel_snapshot_report_failure=Snapshot report not created. An error occurred during creation."
670  })
671  private void snapshotButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_snapshotButtonActionPerformed
672  try {
673  handleSnapshotEvent();
674  } catch (NoCurrentCaseException | IOException ex) {
675  logger.log(Level.SEVERE, "Unable to create communications snapsot report", ex); //NON-NLS
676 
677  Platform.runLater(()
678  -> Notifications.create().owner(notificationsJFXPanel.getScene().getWindow())
679  .text(Bundle.VisualizationPanel_snapshot_report_failure())
680  .showWarning());
681  } catch( TskCoreException ex) {
682  logger.log(Level.WARNING, "Unable to add report to currenct case", ex); //NON-NLS
683  }
684  }//GEN-LAST:event_snapshotButtonActionPerformed
685 
686  private void fastOrganicLayoutButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_fastOrganicLayoutButtonActionPerformed
687  // TODO add your handling code here:
688  }//GEN-LAST:event_fastOrganicLayoutButtonActionPerformed
689 
690  private void fitGraph() {
691  graphComponent.zoomTo(1, true);
692  mxPoint translate = graph.getView().getTranslate();
693  if (translate == null || Double.isNaN(translate.getX()) || Double.isNaN(translate.getY())) {
694  translate = new mxPoint();
695  }
696 
697  mxRectangle boundsForCells = graph.getCellBounds(graph.getDefaultParent(), true, true, true);
698  if (boundsForCells == null || Double.isNaN(boundsForCells.getWidth()) || Double.isNaN(boundsForCells.getHeight())) {
699  boundsForCells = new mxRectangle(0, 0, 1, 1);
700  }
701  final mxPoint mxPoint = new mxPoint(translate.getX() - boundsForCells.getX(), translate.getY() - boundsForCells.getY());
702 
703  graph.cellsMoved(graph.getChildCells(graph.getDefaultParent()), mxPoint.getX(), mxPoint.getY(), false, false);
704 
705  boundsForCells = graph.getCellBounds(graph.getDefaultParent(), true, true, true);
706  if (boundsForCells == null || Double.isNaN(boundsForCells.getWidth()) || Double.isNaN(boundsForCells.getHeight())) {
707  boundsForCells = new mxRectangle(0, 0, 1, 1);
708  }
709 
710  final Dimension size = graphComponent.getSize();
711  final double widthFactor = size.getWidth() / boundsForCells.getWidth();
712  final double heightFactor = size.getHeight() / boundsForCells.getHeight();
713 
714  graphComponent.zoom((heightFactor + widthFactor) / 2.0);
715  }
716 
723  @NbBundle.Messages({
724  "VisualizationPanel_action_dialogs_title=Communications",
725  "VisualizationPanel_module_name=Communications",
726  "VisualizationPanel_action_name_text=Snapshot Report",
727  "VisualizationPane_fileName_prompt=Enter name for the Communications Snapshot Report:",
728  "VisualizationPane_reportName=Communications Snapshot",
729  "# {0} - default name",
730  "VisualizationPane_accept_defaultName=Report name was empty. Press OK to accept default report name: {0}",
731  "VisualizationPane_blank_report_title=Blank Report Name",
732  "# {0} - report name",
733  "VisualizationPane_overrite_exiting=Overwrite existing report?\n{0}"
734  })
735  private void handleSnapshotEvent() throws NoCurrentCaseException, IOException, TskCoreException {
736  Case currentCase = Case.getCurrentCaseThrows();
737  Date generationDate = new Date();
738 
739  final String defaultReportName = FileUtil.escapeFileName(currentCase.getDisplayName() + " " + new SimpleDateFormat("MMddyyyyHHmmss").format(generationDate)); //NON_NLS
740 
741  final JTextField text = new JTextField(50);
742  final JPanel panel = new JPanel(new GridLayout(2, 1));
743  panel.add(new JLabel(Bundle.VisualizationPane_fileName_prompt()));
744  panel.add(text);
745 
746  text.setText(defaultReportName);
747 
748  int result = JOptionPane.showConfirmDialog(graphComponent, panel,
749  Bundle.VisualizationPanel_action_dialogs_title(), JOptionPane.OK_CANCEL_OPTION);
750 
751  if (result == JOptionPane.OK_OPTION) {
752  String enteredReportName = text.getText();
753 
754  if(enteredReportName.trim().isEmpty()){
755  result = JOptionPane.showConfirmDialog(graphComponent, Bundle.VisualizationPane_accept_defaultName(defaultReportName), Bundle.VisualizationPane_blank_report_title(), JOptionPane.OK_CANCEL_OPTION);
756  if(result != JOptionPane.OK_OPTION) {
757  return;
758  }
759  }
760 
761  String reportName = StringUtils.defaultIfBlank(enteredReportName, defaultReportName);
762  Path reportPath = Paths.get(currentCase.getReportDirectory(), reportName);
763  if (Files.exists(reportPath)) {
764  result = JOptionPane.showConfirmDialog(graphComponent, Bundle.VisualizationPane_overrite_exiting(reportName),
765  Bundle.VisualizationPanel_action_dialogs_title(), JOptionPane.OK_CANCEL_OPTION);
766 
767  if (result == JOptionPane.OK_OPTION) {
768  FileUtil.deleteFileDir(reportPath.toFile());
769  createReport(currentCase, reportName);
770  }
771  } else {
772  createReport(currentCase, reportName);
773  currentCase.addReport(reportPath.toString(), Bundle.VisualizationPanel_module_name(), reportName);
774 
775  }
776  }
777  }
778 
787  @NbBundle.Messages({
788  "VisualizationPane_DisplayName=Open Report",
789  "VisualizationPane_NoAssociatedEditorMessage=There is no associated editor for reports of this type or the associated application failed to launch.",
790  "VisualizationPane_MessageBoxTitle=Open Report Failure",
791  "VisualizationPane_NoOpenInEditorSupportMessage=This platform (operating system) does not support opening a file in an editor this way.",
792  "VisualizationPane_MissingReportFileMessage=The report file no longer exists.",
793  "VisualizationPane_ReportFileOpenPermissionDeniedMessage=Permission to open the report file was denied.",
794  "# {0} - report path",
795  "VisualizationPane_Report_Success=Report Successfully create at:\n{0}",
796  "VisualizationPane_Report_OK_Button=OK",
797  "VisualizationPane_Open_Report=Open Report",})
798  private void createReport(Case currentCase, String reportName) throws IOException {
799 
800  // Create the report.
801  Path reportFolderPath = Paths.get(currentCase.getReportDirectory(), reportName, Bundle.VisualizationPane_reportName()); //NON_NLS
802  BufferedImage image = mxCellRenderer.createBufferedImage(graph, null, graph.getView().getScale(), Color.WHITE, true, null);
803  Path reportPath = new CommSnapShotReportWriter(currentCase, reportFolderPath, reportName, new Date(), image, currentFilter).writeReport();
804 
805  // Report success to the user and offer to open the report.
806  String message = Bundle.VisualizationPane_Report_Success(reportPath.toAbsolutePath());
807  String[] buttons = {Bundle.VisualizationPane_Open_Report(), Bundle.VisualizationPane_Report_OK_Button()};
808 
809  int result = JOptionPane.showOptionDialog(graphComponent, message,
810  Bundle.VisualizationPanel_action_dialogs_title(),
811  JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE,
812  null, buttons, buttons[1]);
813  if (result == JOptionPane.YES_NO_OPTION) {
814  try {
815  Desktop.getDesktop().open(reportPath.toFile());
816  } catch (IOException ex) {
817  JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
818  Bundle.VisualizationPane_NoAssociatedEditorMessage(),
819  Bundle.VisualizationPane_MessageBoxTitle(),
820  JOptionPane.ERROR_MESSAGE);
821  } catch (UnsupportedOperationException ex) {
822  JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
823  Bundle.VisualizationPane_NoOpenInEditorSupportMessage(),
824  Bundle.VisualizationPane_MessageBoxTitle(),
825  JOptionPane.ERROR_MESSAGE);
826  } catch (IllegalArgumentException ex) {
827  JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
828  Bundle.VisualizationPane_MissingReportFileMessage(),
829  Bundle.VisualizationPane_MessageBoxTitle(),
830  JOptionPane.ERROR_MESSAGE);
831  } catch (SecurityException ex) {
832  JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
833  Bundle.VisualizationPane_ReportFileOpenPermissionDeniedMessage(),
834  Bundle.VisualizationPane_MessageBoxTitle(),
835  JOptionPane.ERROR_MESSAGE);
836  }
837  }
838  }
839 
840  // Variables declaration - do not modify//GEN-BEGIN:variables
841  private JButton backButton;
842  private JPanel borderLayoutPanel;
843  private JButton clearVizButton;
844  private JButton fastOrganicLayoutButton;
845  private JButton fitZoomButton;
846  private JButton forwardButton;
847  private JToolBar.Separator jSeparator1;
848  private JToolBar.Separator jSeparator2;
849  private JToolBar.Separator jSeparator3;
850  private JTextPane jTextPane1;
851  private JFXPanel notificationsJFXPanel;
852  private JPanel placeHolderPanel;
853  private JButton snapshotButton;
854  private JToolBar toolbar;
855  private JButton zoomActualButton;
856  private JButton zoomInButton;
857  private JLabel zoomLabel;
858  private JButton zoomOutButton;
859  private JLabel zoomPercentLabel;
860  // End of variables declaration//GEN-END:variables
861 
866  final private class SelectionListener implements mxEventSource.mxIEventListener {
867 
868  @SuppressWarnings("unchecked")
869  @Override
870  public void invoke(Object sender, mxEventObject evt) {
871  Object[] selectionCells = graph.getSelectionCells();
872  if (selectionCells.length > 0) {
873  mxICell[] selectedCells = Arrays.asList(selectionCells).toArray(new mxCell[selectionCells.length]);
874  HashSet<AccountDeviceInstance> selectedNodes = new HashSet<>();
875  HashSet<SelectionInfo.GraphEdge> selectedEdges = new HashSet<>();
876  for (mxICell cell : selectedCells) {
877  if (cell.isEdge()) {
878  mxICell source = (mxICell) graph.getModel().getTerminal(cell, true);
879  mxICell target = (mxICell) graph.getModel().getTerminal(cell, false);
880 
881  selectedEdges.add(new SelectionInfo.GraphEdge(((AccountDeviceInstanceKey) source.getValue()).getAccountDeviceInstance(),
882  ((AccountDeviceInstanceKey) target.getValue()).getAccountDeviceInstance()));
883 
884  } else if (cell.isVertex()) {
885  selectedNodes.add(((AccountDeviceInstanceKey) cell.getValue()).getAccountDeviceInstance());
886  }
887  }
888 
889  relationshipBrowser.setSelectionInfo(new SelectionInfo(selectedNodes, selectedEdges, currentFilter));
890  } else {
891  relationshipBrowser.setSelectionInfo(new SelectionInfo(new HashSet<>(), new HashSet<>(), currentFilter));
892  }
893  }
894  }
895 
899  private interface NamedGraphLayout extends mxIGraphLayout {
900 
901  String getDisplayName();
902  }
903 
907  final private class FastOrganicLayoutImpl extends mxFastOrganicLayout implements NamedGraphLayout {
908 
909  FastOrganicLayoutImpl(mxGraph graph) {
910  super(graph);
911  }
912 
913  @Override
914  public boolean isVertexIgnored(Object vertex) {
915  return super.isVertexIgnored(vertex)
916  || lockedVertexModel.isVertexLocked((mxCell) vertex);
917  }
918 
919  @Override
920  public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x, y are standard coordinate names
921  if (isVertexIgnored(vertex)) {
922  return getVertexBounds(vertex);
923  } else {
924  return super.setVertexLocation(vertex, x, y);
925  }
926  }
927 
928  @Override
929  public String getDisplayName() {
930  return "Fast Organic";
931  }
932  }
933 
937  final private class CircleLayoutImpl extends mxCircleLayout implements NamedGraphLayout {
938 
939  CircleLayoutImpl(mxGraph graph) {
940  super(graph);
941  setResetEdges(true);
942  }
943 
944  @Override
945  public boolean isVertexIgnored(Object vertex) {
946  return super.isVertexIgnored(vertex)
947  || lockedVertexModel.isVertexLocked((mxCell) vertex);
948  }
949 
950  @Override
951  public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x, y are standard coordinate names
952  if (isVertexIgnored(vertex)) {
953  return getVertexBounds(vertex);
954  } else {
955  return super.setVertexLocation(vertex, x, y);
956  }
957  }
958 
959  @Override
960  public String getDisplayName() {
961  return "Circle";
962  }
963  }
964 
968  final private class OrganicLayoutImpl extends mxOrganicLayout implements NamedGraphLayout {
969 
970  OrganicLayoutImpl(mxGraph graph) {
971  super(graph);
972  setResetEdges(true);
973  }
974 
975  @Override
976  public boolean isVertexIgnored(Object vertex) {
977  return super.isVertexIgnored(vertex)
978  || lockedVertexModel.isVertexLocked((mxCell) vertex);
979  }
980 
981  @Override
982  public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x, y are standard coordinate names
983  if (isVertexIgnored(vertex)) {
984  return getVertexBounds(vertex);
985  } else {
986  return super.setVertexLocation(vertex, x, y);
987  }
988  }
989 
990  @Override
991  public String getDisplayName() {
992  return "Organic";
993  }
994  }
995 
999  final private class HierarchicalLayoutImpl extends mxHierarchicalLayout implements NamedGraphLayout {
1000 
1001  HierarchicalLayoutImpl(mxGraph graph) {
1002  super(graph);
1003  }
1004 
1005  @Override
1006  public boolean isVertexIgnored(Object vertex) {
1007  return super.isVertexIgnored(vertex)
1008  || lockedVertexModel.isVertexLocked((mxCell) vertex);
1009  }
1010 
1011  @Override
1012  public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x, y are standard coordinate names
1013  if (isVertexIgnored(vertex)) {
1014  return getVertexBounds(vertex);
1015  } else {
1016  return super.setVertexLocation(vertex, x, y);
1017  }
1018  }
1019 
1020  @Override
1021  public String getDisplayName() {
1022  return "Hierarchical";
1023  }
1024  }
1025 
1030  private class CancelationListener implements ActionListener {
1031 
1032  private Future<?> cancellable;
1034 
1035  void configure(Future<?> cancellable, ModalDialogProgressIndicator progress) {
1036  this.cancellable = cancellable;
1037  this.progress = progress;
1038  }
1039 
1040  @Override
1041  public void actionPerformed(ActionEvent event) {
1042  progress.setCancelling("Cancelling...");
1043  cancellable.cancel(true);
1044  progress.finish();
1045  }
1046  }
1047 
1052  private class GraphMouseListener extends MouseAdapter {
1053 
1059  @Override
1060  public void mouseWheelMoved(final MouseWheelEvent event) {
1061  super.mouseWheelMoved(event);
1062  if (event.getPreciseWheelRotation() < 0) {
1063  graphComponent.zoomIn();
1064  } else if (event.getPreciseWheelRotation() > 0) {
1065  graphComponent.zoomOut();
1066  }
1067 
1068  CVTEvents.getCVTEventBus().post(new CVTEvents.ScaleChangeEvent(graph.getView().getScale()));
1069  }
1070 
1076  @Override
1077  public void mouseClicked(final MouseEvent event) {
1078  super.mouseClicked(event);
1079  if (SwingUtilities.isRightMouseButton(event)) {
1080  final mxCell cellAt = (mxCell) graphComponent.getCellAt(event.getX(), event.getY());
1081  if (cellAt != null && cellAt.isVertex()) {
1082  final JPopupMenu jPopupMenu = new JPopupMenu();
1083  final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) cellAt.getValue();
1084 
1085  Set<mxCell> selectedVertices
1086  = Stream.of(graph.getSelectionModel().getCells())
1087  .map(mxCell.class::cast)
1088  .filter(mxCell::isVertex)
1089  .collect(Collectors.toSet());
1090 
1091  if (lockedVertexModel.isVertexLocked(cellAt)) {
1092  jPopupMenu.add(new JMenuItem(new UnlockAction(selectedVertices)));
1093  } else {
1094  jPopupMenu.add(new JMenuItem(new LockAction(selectedVertices)));
1095  }
1096  if (pinnedAccountModel.isAccountPinned(adiKey.getAccountDeviceInstance())) {
1097  jPopupMenu.add(UnpinAccountsAction.getInstance().getPopupPresenter());
1098  } else {
1099  jPopupMenu.add(PinAccountsAction.getInstance().getPopupPresenter());
1100  jPopupMenu.add(ResetAndPinAccountsAction.getInstance().getPopupPresenter());
1101  }
1102  jPopupMenu.show(graphComponent.getGraphControl(), event.getX(), event.getY());
1103  }
1104  }
1105  }
1106  }
1107 
1111  @NbBundle.Messages({
1112  "VisualizationPanel.unlockAction.singularText=Unlock Selected Account",
1113  "VisualizationPanel.unlockAction.pluralText=Unlock Selected Accounts",})
1114  private final class UnlockAction extends AbstractAction {
1115 
1116  private final Set<mxCell> selectedVertices;
1117 
1118  UnlockAction(Set<mxCell> selectedVertices) {
1119  super(selectedVertices.size() > 1 ? Bundle.VisualizationPanel_unlockAction_pluralText() : Bundle.VisualizationPanel_unlockAction_singularText(),
1120  unlockIcon);
1121  this.selectedVertices = selectedVertices;
1122  }
1123 
1124  @Override
1125 
1126  public void actionPerformed(final ActionEvent event) {
1127  lockedVertexModel.unlock(selectedVertices);
1128  }
1129  }
1130 
1134  @NbBundle.Messages({
1135  "VisualizationPanel.lockAction.singularText=Lock Selected Account",
1136  "VisualizationPanel.lockAction.pluralText=Lock Selected Accounts"})
1137  private final class LockAction extends AbstractAction {
1138 
1139  private final Set<mxCell> selectedVertices;
1140 
1141  LockAction(Set<mxCell> selectedVertices) {
1142  super(selectedVertices.size() > 1 ? Bundle.VisualizationPanel_lockAction_pluralText() : Bundle.VisualizationPanel_lockAction_singularText(),
1143  lockIcon);
1144  this.selectedVertices = selectedVertices;
1145  }
1146 
1147  @Override
1148  public void actionPerformed(final ActionEvent event) {
1149  lockedVertexModel.lock(selectedVertices);
1150  }
1151  }
1152 }
void createReport(Case currentCase, String reportName)
void handleStateChange(StateManager.CommunicationsState newState)
static boolean deleteFileDir(File path)
Definition: FileUtil.java:87
void addReport(String localPath, String srcModuleName, String reportName)
Definition: Case.java:1654
synchronized void start(String message, int totalWorkUnits)
static String escapeFileName(String fileName)
Definition: FileUtil.java:169
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:491

Copyright © 2012-2021 Basis Technology. Generated on: Tue Jan 19 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.