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

Copyright © 2012-2022 Basis Technology. Generated on: Tue Aug 1 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.