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

Copyright © 2012-2020 Basis Technology. Generated on: Wed Apr 8 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.