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