View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2024.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.ui;
39  
40  import static ffx.utilities.FileUtils.copyInputStreamToTmpFile;
41  import static ffx.utilities.StringUtils.pdbForID;
42  import static java.lang.String.format;
43  import static java.lang.System.arraycopy;
44  
45  import ffx.crystal.Crystal;
46  import ffx.crystal.ReplicatesCrystal;
47  import ffx.potential.MolecularAssembly;
48  import ffx.potential.bonded.Atom;
49  import ffx.potential.bonded.Bond;
50  import ffx.potential.bonded.MSNode;
51  import ffx.potential.bonded.MSRoot;
52  import ffx.potential.bonded.RendererCache;
53  import ffx.potential.bonded.RotamerLibrary;
54  import ffx.potential.parameters.ForceField;
55  import ffx.potential.parsers.ARCFileFilter;
56  import ffx.potential.parsers.FFXFileFilter;
57  import ffx.potential.parsers.ForceFieldFileFilter;
58  import ffx.potential.parsers.ForceFieldFilter;
59  import ffx.potential.parsers.INTFileFilter;
60  import ffx.potential.parsers.INTFilter;
61  import ffx.potential.parsers.InducedFileFilter;
62  import ffx.potential.parsers.InducedFilter;
63  import ffx.potential.parsers.KeyFileFilter;
64  import ffx.potential.parsers.KeyFilter;
65  import ffx.potential.parsers.MergeFilter;
66  import ffx.potential.parsers.PDBFileFilter;
67  import ffx.potential.parsers.PDBFilter;
68  import ffx.potential.parsers.SystemFilter;
69  import ffx.potential.parsers.XYZFileFilter;
70  import ffx.potential.parsers.XYZFilter;
71  import ffx.ui.properties.FFXLocale;
72  import ffx.utilities.Keyword;
73  import ffx.utilities.Resources;
74  
75  import java.awt.BorderLayout;
76  import java.awt.Container;
77  import java.awt.Cursor;
78  import java.awt.Dimension;
79  import java.awt.Font;
80  import java.awt.Frame;
81  import java.awt.GraphicsConfiguration;
82  import java.awt.GraphicsEnvironment;
83  import java.awt.GridLayout;
84  import java.awt.Toolkit;
85  import java.awt.event.ActionEvent;
86  import java.awt.event.ActionListener;
87  import java.io.*;
88  import java.net.InetAddress;
89  import java.net.InetSocketAddress;
90  import java.net.MalformedURLException;
91  import java.net.URI;
92  import java.net.URISyntaxException;
93  import java.net.URL;
94  import java.time.Month;
95  import java.util.ArrayList;
96  import java.util.Arrays;
97  import java.util.Hashtable;
98  import java.util.List;
99  import java.util.logging.Level;
100 import java.util.logging.Logger;
101 import java.util.prefs.Preferences;
102 import javax.swing.BorderFactory;
103 import javax.swing.ImageIcon;
104 import javax.swing.JCheckBoxMenuItem;
105 import javax.swing.JDialog;
106 import javax.swing.JFileChooser;
107 import javax.swing.JFrame;
108 import javax.swing.JLabel;
109 import javax.swing.JOptionPane;
110 import javax.swing.JPanel;
111 import javax.swing.JScrollPane;
112 import javax.swing.JSplitPane;
113 import javax.swing.JTabbedPane;
114 import javax.swing.JTextArea;
115 import javax.swing.SwingUtilities;
116 import javax.swing.UIManager;
117 import javax.swing.UnsupportedLookAndFeelException;
118 import javax.swing.border.Border;
119 import javax.swing.border.EtchedBorder;
120 import javax.swing.event.ChangeEvent;
121 import javax.swing.event.ChangeListener;
122 import javax.swing.filechooser.FileSystemView;
123 
124 import org.apache.commons.configuration2.CompositeConfiguration;
125 import org.apache.commons.io.FileUtils;
126 import org.apache.commons.io.FilenameUtils;
127 import org.apache.commons.lang3.SystemUtils;
128 import org.jogamp.java3d.GraphicsConfigTemplate3D;
129 import org.jogamp.java3d.Transform3D;
130 import org.jogamp.java3d.TransformGroup;
131 import org.jogamp.vecmath.Vector3d;
132 
133 /**
134  * The MainPanel class is the main container for Force Field X, handles file input/output and is used
135  * to pass references among the various sub-Panels.
136  *
137  * @author Michael J. Schnieders
138  */
139 public final class MainPanel extends JPanel implements ActionListener, ChangeListener {
140 
141   @Serial
142   private static final long serialVersionUID = 1L;
143 
144   /**
145    * Constant <code>version="1.0.0-BETA"</code>
146    */
147   public static final String version = "1.0.0-BETA";
148   /**
149    * Constant <code>date="February 2024"</code>
150    */
151   public static final String date = "February 2024";
152   /**
153    * Constant
154    */
155   public static final String border =
156       " _________________________________________________________________________\n";
157   /**
158    * Constant
159    */
160   public static final String title = "        FORCE FIELD X - Polyglot Software for Molecular Biophysics \n";
161 
162   public static final String aboutString;
163   /**
164    * Constant <code>KEYWORDS=1</code>
165    */
166   static final int KEYWORDS = 1;
167   /**
168    * Constant <code>MODELING=2</code>
169    */
170   static final int MODELING = 2;
171   /**
172    * Constant <code>forceFieldFileFilter</code>
173    */
174   static final ForceFieldFileFilter forceFieldFileFilter = new ForceFieldFileFilter();
175   /**
176    * Constant <code>keyFileFilter</code>
177    */
178   static final KeyFileFilter keyFileFilter = new KeyFileFilter();
179 
180   private static final Logger logger = Logger.getLogger(MainPanel.class.getName());
181   /**
182    * Constant <code>GRAPHICS=0</code>
183    */
184   private static final int GRAPHICS = 0;
185   /**
186    * Constant <code>xyzFileFilter</code>
187    */
188   private static final XYZFileFilter xyzFileFilter = new XYZFileFilter();
189   /**
190    * Constant <code>arcFileFilter</code>
191    */
192   private static final ARCFileFilter arcFileFilter = new ARCFileFilter();
193   /**
194    * Constant <code>intFileFilter</code>
195    */
196   private static final INTFileFilter intFileFilter = new INTFileFilter();
197   /**
198    * Constant <code>indFileFilter</code>
199    */
200   private static final InducedFileFilter indFileFilter = new InducedFileFilter();
201   /**
202    * Constant <code>pdbFileFilter</code>
203    */
204   private static final PDBFileFilter pdbFileFilter = new PDBFileFilter();
205   /**
206    * Constant <code>ffxFileFilter</code>
207    */
208   private static final FFXFileFilter ffxFileFilter = new FFXFileFilter();
209 
210   private static final Preferences preferences = Preferences.userNodeForPackage(MainPanel.class);
211   /**
212    * Constant <code>classpath=""</code>
213    */
214   static String classpath;
215   /**
216    * Constant <code>ffxDir</code>
217    */
218   static File ffxDir;
219   /**
220    * Present working directory.
221    */
222   private static File pwd;
223   /**
224    * JFileChooser for choosing a file.
225    */
226   private static JFileChooser fileChooser = null;
227 
228   static {
229     var basedir = System.getProperty("basedir");
230     var mvnProps = new File(basedir + "/bin/build.properties");
231     var commitVersion = version + "-unknown";
232     var commitDate = date;
233     var commitSCM = "";
234     if (mvnProps.exists()) {
235       try (BufferedReader br = new BufferedReader(new FileReader(mvnProps))) {
236         var ffxVersion = "1.0.0-BETA";
237         var ffxVersionProp = "ffx.version=";
238         var gitCommitsCount = "";
239         var gitCommitsCountProp = "git.total.commit.count=";
240         var timestampProp = "timestamp=";
241         var gitRevisionProp = "git.commit.id.full=";
242         var line = br.readLine();
243         while (line != null) {
244           line = line.trim();
245           if (line.startsWith(ffxVersionProp)) {
246             ffxVersion = line.replaceFirst(ffxVersionProp, "");
247           } else if (line.startsWith(gitCommitsCountProp)) {
248             gitCommitsCount = line.replaceFirst(gitCommitsCountProp, "");
249           } else if (line.startsWith(timestampProp)) {
250             var timeStr = line.replaceFirst(timestampProp, "");
251             // Expected to be MM-dd-yyyy
252             var timeToks = timeStr.split("-");
253             try {
254               var year = timeToks[2];
255               int mon = Integer.parseInt(timeToks[0]);
256               Month month = Month.of(mon);
257               var mstr = month.toString();
258               commitDate = format("%c%s %s", mstr.charAt(0), mstr.substring(1).toLowerCase(), year);
259             } catch (Exception ex) {
260               commitDate = date;
261             }
262           } else if (line.startsWith(gitRevisionProp) && !line.contains("UNKNOWN_REVISION")) {
263             var scm = line.replaceFirst(gitRevisionProp, "");
264             commitSCM = format("        %s %s \n", "Git Revision ", scm);
265           }
266           line = br.readLine();
267         }
268         var sb = new StringBuilder(ffxVersion).append("-");
269         if (!gitCommitsCount.isEmpty()) {
270           sb.append(gitCommitsCount);
271         } else {
272           sb.append("unknown");
273         }
274         commitVersion = sb.toString();
275       } catch (Exception ex) {
276         //
277       }
278     }
279     aboutString = "        Version "
280         + commitVersion
281         + "  "
282         + commitDate
283         + " \n"
284         + commitSCM // Will contain its own spacing/newline, or be empty.
285         + " \n"
286         + """
287                 Copyright (c)  Michael J. Schnieders  2001-2024
288                 Portions Copyright (c):
289                   Timothy D. Fenn      2009-2024
290                   Jacob M. Litman      2015-2024
291                   Rae A. Corrigan      2019-2024
292                   Guowei Qi            2019-2024
293                   Mallory R. Tollefson 2019-2024
294                   Aaron J. Nessler     2021-2024
295                   Andrew C. Thiel      2021-2024
296         
297                 All Rights Reserved
298         
299                 Force Field X is distributed under the GPL v. 3 license
300                 with linking exception and is hosted by the Schnieders Lab
301                 at The University of Iowa.
302         
303                 User Manual:   https://ffx.biochem.uiowa.edu/manual.html
304                 Publications:  https://ffx.biochem.uiowa.edu/publications.html
305                 License:       https://ffx.biochem.uiowa.edu/licenses.html
306         """;
307 
308     try {
309       String ffxString = System.getProperty("ffx.dir", ".");
310       ffxDir = new File(ffxString);
311       classpath = System.getProperty("java.class.path");
312       pwd = MainPanel.getPWD();
313     } catch (Exception e) {
314       logger.severe(" FFX directory could not be found.\n" + e);
315     }
316   }
317 
318   /**
319    * Main FFX JFrame.
320    */
321   private final JFrame frame;
322   /**
323    * Number of File Opener Threads.
324    */
325   private int fileOpenerThreads = -1;
326   /**
327    * Root of the structural hierarchy.
328    */
329   private MSRoot dataRoot;
330   /**
331    * The structural hierarchy.
332    */
333   private Hierarchy hierarchy;
334   /**
335    * The FFX Main Menu.
336    */
337   private MainMenu mainMenu;
338   /**
339    * The Graphics Panel.
340    */
341   private GraphicsPanel graphicsPanel;
342   /**
343    * The Modeling Panel.
344    */
345   private ModelingPanel modelingPanel;
346   /**
347    * The Keyword Panel.
348    */
349   private KeywordPanel keywordPanel;
350   /**
351    * A reference to the Modeling Shell.
352    */
353   private ModelingShell modelingShell = null;
354   /**
355    * The Java3D Graphics Canvas.
356    */
357   private GraphicsCanvas graphicsCanvas;
358   /**
359    * The SplitPane holds the Hierarchy and JTabbedPane.
360    */
361   private JSplitPane splitPane;
362   /**
363    * The value fo the Split Pane Divider.
364    */
365   private int splitPaneDivider;
366   /**
367    * Status Label.
368    */
369   private JLabel statusLabel;
370   /**
371    * Filter to open a force field file.
372    */
373   private ForceFieldFilter forceFieldFilter;
374   /**
375    * The FFX Locale.
376    */
377   private FFXLocale locale = null;
378   /**
379    * The FFX About Dialog.
380    */
381   private JDialog aboutDialog = null;
382   /**
383    * The FFX About Text Area.
384    */
385   private JTextArea aboutTextArea = null;
386   /**
387    * Thread to open systems.
388    */
389   private Thread openThread = null;
390   /**
391    * The active system filter.
392    */
393   private SystemFilter activeFilter = null;
394   /**
395    * Flag to indicate oscillation.
396    */
397   private boolean oscillate = false;
398   /**
399    * Reference to a Simulation Loader.
400    */
401   private SimulationLoader simulation;
402   /**
403    * IP of the simulation.
404    */
405   private String ip = "";
406   /**
407    * Simulation port.
408    */
409   private int port = 2000;
410   /**
411    * InetAddress of the simulation.
412    */
413   private InetAddress address = null;
414   /**
415    * InetSocketAddress of the simulation.
416    */
417   private InetSocketAddress socketAddress = new InetSocketAddress(port);
418   /**
419    * Initialize all the sub-Panels and put them together
420    */
421   private boolean init = false;
422   /**
423    * Exit status to describe how FFX is terminating.
424    */
425   private ExitStatus exitType = ExitStatus.NORMAL;
426 
427   /**
428    * MainPanel Constructor
429    *
430    * @param f Application Frame
431    */
432   public MainPanel(JFrame f) {
433     frame = f;
434   }
435 
436   /**
437    * Constructor for MainPanel.
438    */
439   public MainPanel() {
440     frame = null;
441   }
442 
443   /**
444    * getPWD
445    *
446    * @return a {@link java.io.File} object.
447    */
448   static File getPWD() {
449     if (pwd == null) {
450       pwd =
451           new File(
452               System.getProperty(
453                   "user.dir",
454                   FileSystemView.getFileSystemView().getDefaultDirectory().getAbsolutePath()));
455     }
456     return pwd;
457   }
458 
459   /**
460    * JFileChooser
461    *
462    * @return a {@link javax.swing.JFileChooser} object.
463    */
464   static JFileChooser resetFileChooser() {
465     if (fileChooser == null) {
466       fileChooser = new JFileChooser();
467     }
468     fileChooser.resetChoosableFileFilters();
469     fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
470     fileChooser.setFileHidingEnabled(false);
471     fileChooser.setAcceptAllFileFilterUsed(true);
472     fileChooser.setCurrentDirectory(getPWD());
473     fileChooser.setMultiSelectionEnabled(false);
474     fileChooser.setSelectedFile(null);
475     return fileChooser;
476   }
477 
478   /**
479    * about
480    */
481   public void about() {
482     if (aboutDialog == null) {
483       aboutDialog = new JDialog(frame, "About... ", true);
484       URL ffxURL = getClass().getClassLoader().getResource("ffx/ui/icons/splash.png");
485       ImageIcon logoIcon = new ImageIcon(ffxURL);
486       JLabel logoLabel = new JLabel(logoIcon);
487       logoLabel.setSize(600, 600);
488       logoLabel.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
489       Container contentpane = aboutDialog.getContentPane();
490       contentpane.setLayout(new BorderLayout());
491       initAbout();
492       contentpane.add(aboutTextArea, BorderLayout.SOUTH);
493       contentpane.add(logoLabel, BorderLayout.CENTER);
494       aboutDialog.pack();
495       Dimension dim = getToolkit().getScreenSize();
496       Dimension ddim = aboutDialog.getSize();
497       aboutDialog.setLocation((dim.width - ddim.width) / 2, (dim.height - ddim.height) / 2);
498       aboutDialog.setResizable(false);
499     }
500     aboutDialog.setVisible(true);
501   }
502 
503   /**
504    * {@inheritDoc}
505    *
506    * <p>Handle most File, Selection, Trajectory, Simulation, Window and Help Menu Commands This
507    * should probably be partitioned between a few different handlers
508    */
509   @Override
510   public void actionPerformed(ActionEvent evt) {
511     String arg = evt.getActionCommand();
512     if (logger.isLoggable(Level.FINEST)) {
513       logger.finest(" Action: " + arg);
514     }
515     // File Commands
516     switch (arg) {
517       case "Open" -> open();
518       case "DownloadFromPDB" -> openFromPDB();
519       case "SaveAs" -> saveAsXYZ(null);
520       case "Close" -> close();
521       case "CloseAll" -> closeAll();
522       case "ChooseKeyFile" -> chooseKey();
523       case "ChooseLogFile" -> chooseLog();
524       case "LoadInducedData" -> openInduced();
525 
526       // Selection Commands
527       case "SelectAll" -> selectAll();
528       case "MergeSelections" -> merge();
529       case "HighlightSelections" -> highlightSelections(evt);
530 
531       // Trajectory
532       case "Play" -> play();
533       case "Stop" -> stop();
534       case "StepForward" -> stepForward();
535       case "StepBack" -> stepBack();
536       case "Reset" -> reset();
537       case "Oscillate" -> oscillate(evt);
538       case "Frame" -> frame();
539       case "Speed" -> speed();
540       case "Skip" -> skip();
541 
542       // Simulation
543       case "ConnectToLocalJob" -> connectToTINKER(null, null);
544       case "ConnectToRemoteJob" -> connect();
545       case "ReleaseJob" -> release();
546       case "SetPort" -> setPort();
547       case "SetRemoteJobAddress" -> setRemoteJobAddress();
548 
549       // Window
550       case "ShowToolBar" -> showToolBar(evt);
551       case "ShowTree" -> showTree(evt);
552       case "ShowGlobalAxes" -> showGlobalAxes(evt);
553       case "ResetPanes" -> resetPanes();
554       case "ResetConsole" -> resetShell();
555       case "OceanLookAndFeel" -> oceanLookAndFeel();
556       case "WindowsLookAndFeel", "MacOSXLookAndFeel", "MotifLookAndFeel" -> platformLookAndFeel();
557       case "ShrinkGraphicsWindow" -> resizePanes(20);
558       case "ExpandGraphicsWindow" -> resizePanes(-20);
559       case "About" -> about();
560 
561       // Others
562       case "GarbageCollect" -> Runtime.getRuntime().gc();
563       case "Exit" -> exit();
564       default -> {
565         try {
566           ClassLoader cl = MainPanel.class.getClassLoader();
567           URL url = cl.getResource(arg);
568           logger.info(url.toString());
569           File structureFile = new File(url.getFile());
570           logger.info(structureFile.toString());
571           String tempFile =
572               copyInputStreamToTmpFile(url.openStream(), "ffx", structureFile.getName(), "pdb");
573           open(tempFile);
574         } catch (Exception e) {
575           System.err.println("MainPanel - Menu command not found: " + arg);
576         }
577       }
578     }
579   }
580 
581   /**
582    * Detach the active FSystem's BranchGroup from the Scene and clear that FSystem's data
583    *
584    * @return a {@link java.lang.Thread} object.
585    */
586   public Thread close() {
587     FFXSystem m = hierarchy.getActive();
588     return close(m);
589   }
590 
591   /**
592    * close
593    *
594    * @param closedModel a {@link ffx.ui.FFXSystem} object.
595    * @return a {@link java.lang.Thread} object.
596    */
597   public Thread close(FFXSystem closedModel) {
598     if (closedModel.getParent() != null) {
599       Trajectory traj = closedModel.getTrajectory();
600       if (traj != null) {
601         traj.stop();
602       }
603       if (simulation != null && simulation.getFSystem() == closedModel) {
604         release();
605       }
606       hierarchy.removeTreeNode(closedModel);
607       closedModel.setView(RendererCache.ViewModel.DESTROY, null);
608       Thread thread = new Thread(new UIFileCloser(closedModel));
609       thread.start();
610       return thread;
611     }
612     return null;
613   }
614 
615   /**
616    * exit with current exit code (default: 0 (ExitStatus.NORMAL))
617    */
618   public void exit() {
619     exit(exitType);
620   }
621 
622   /**
623    * frame
624    */
625   public void frame() {
626     Trajectory trajectory = getTrajectory();
627     if (trajectory == null) {
628       return;
629     }
630     String frameNumber = "" + trajectory.getFrame();
631     frameNumber = JOptionPane.showInputDialog("Enter the Frame Number", frameNumber);
632     try {
633       int f = Integer.parseInt(frameNumber);
634       trajectory.setFrame(f);
635     } catch (NumberFormatException e) {
636       //
637     }
638   }
639 
640   /**
641    * Return the active SystemFilter.
642    *
643    * @return the active SystemFilter.
644    */
645   public SystemFilter getFilter() {
646     return activeFilter;
647   }
648 
649   /**
650    * Getter for the field <code>frame</code>.
651    *
652    * @return a {@link java.awt.Frame} object.
653    */
654   public Frame getFrame() {
655     return frame;
656   }
657 
658   /**
659    * Getter for the field <code>hierarchy</code>.
660    *
661    * @return a {@link ffx.ui.Hierarchy} object.
662    */
663   public Hierarchy getHierarchy() {
664     return hierarchy;
665   }
666 
667   /**
668    * Getter for the field <code>mainMenu</code>.
669    *
670    * @return a {@link ffx.ui.MainMenu} object.
671    */
672   public MainMenu getMainMenu() {
673     return mainMenu;
674   }
675 
676   /**
677    * Getter for the field <code>modelingShell</code>.
678    *
679    * @return a {@link ffx.ui.ModelingShell} object.
680    */
681   public ModelingShell getModelingShell() {
682     if (modelingShell == null) {
683       modelingShell = new ModelingShell(this);
684       modelingShell.run();
685     }
686     return modelingShell;
687   }
688 
689   /**
690    * initialize
691    */
692   public void initialize() {
693     if (init) {
694       return;
695     }
696     init = true;
697 
698     String dir =
699         System.getProperty(
700             "user.dir", FileSystemView.getFileSystemView().getDefaultDirectory().getAbsolutePath());
701     setCWD(new File(dir));
702     locale = new FFXLocale("en", "US");
703 
704     // Create the Root Node
705     dataRoot = new MSRoot();
706     Border bb = BorderFactory.createEtchedBorder(EtchedBorder.RAISED);
707     statusLabel = new JLabel("  ");
708     JLabel stepLabel = new JLabel("  ");
709     stepLabel.setHorizontalAlignment(JLabel.RIGHT);
710     JLabel energyLabel = new JLabel("  ");
711     energyLabel.setHorizontalAlignment(JLabel.RIGHT);
712     JPanel statusPanel = new JPanel(new GridLayout(1, 3));
713     statusPanel.setBorder(bb);
714     statusPanel.add(statusLabel);
715     statusPanel.add(stepLabel);
716     statusPanel.add(energyLabel);
717 
718     // Initialize various Panels
719     setLayout(new BorderLayout());
720     hierarchy = new Hierarchy(this);
721     hierarchy.setStatus(statusLabel, stepLabel, energyLabel);
722     keywordPanel = new KeywordPanel(this);
723     modelingPanel = new ModelingPanel(this);
724 
725     JPanel treePane = new JPanel(new BorderLayout());
726     JScrollPane scrollPane =
727         new JScrollPane(
728             hierarchy,
729             JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
730             JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
731     treePane.add(scrollPane, BorderLayout.CENTER);
732 
733     if (!GraphicsEnvironment.isHeadless()) {
734       GraphicsConfigTemplate3D template3D = new GraphicsConfigTemplate3D();
735       // template3D.setDoubleBuffer(GraphicsConfigTemplate.PREFERRED);
736       GraphicsConfiguration gc = null;
737       try {
738         gc =
739             GraphicsEnvironment.getLocalGraphicsEnvironment()
740                 .getDefaultScreenDevice()
741                 .getBestConfiguration(template3D);
742         // gc =
743         // GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
744       } catch (Exception e) {
745         logger.log(Level.SEVERE, " Exception encountered when trying to get the best GraphicsConfiguration", e);
746       }
747       try {
748         graphicsCanvas = new GraphicsCanvas(gc, this);
749       } catch (Exception e) {
750         logger.log(Level.SEVERE, " Exception encountered when trying to create the GraphicsCanvas", e);
751       }
752       graphicsPanel = new GraphicsPanel(graphicsCanvas, statusPanel);
753     }
754 
755     // Holds 3D Graphics, Keyword Editor, Modeling Commands and Log Panels
756     // JTabbedPane tabbedPane = new JTabbedPane();
757     // ClassLoader loader = getClass().getClassLoader();
758     // ImageIcon graphicsIcon = new ImageIcon(loader.getResource("ffx/ui/icons/monitor.png"));
759     // ImageIcon keywordIcon = new ImageIcon(loader.getResource("ffx/ui/icons/key.png"));
760     // ImageIcon modelingIcon = new ImageIcon(loader.getResource("ffx/ui/icons/cog.png"));
761     // tabbedPane.addTab(locale.getValue("Graphics"), graphicsIcon, graphicsPanel);
762     // tabbedPane.addTab(locale.getValue("KeywordEditor"), keywordIcon, keywordPanel);
763     // tabbedPane.addTab(locale.getValue("ModelingCommands"), modelingIcon, modelingPanel);
764     // tabbedPane.addChangeListener(this);
765     // splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, false, treePane, tabbedPane);
766 
767     splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, false, treePane, graphicsPanel);
768     splitPane.setResizeWeight(0.25);
769     splitPane.setOneTouchExpandable(true);
770     add(splitPane, BorderLayout.CENTER);
771 
772     if (!GraphicsEnvironment.isHeadless()) {
773       mainMenu = new MainMenu(this);
774       add(mainMenu.getToolBar(), BorderLayout.NORTH);
775       getModelingShell();
776       loadPrefs();
777     }
778   }
779 
780   /**
781    * merge
782    *
783    * @param nodesToMerge an array of {@link ffx.potential.bonded.MSNode} objects.
784    */
785   public void merge(MSNode[] nodesToMerge) {
786     ArrayList<MSNode> activeNodes = new ArrayList<>();
787     for (MSNode node : nodesToMerge) {
788       if (node != null) {
789         activeNodes.add(node);
790       }
791     }
792     if (activeNodes.size() > 1) {
793       merge(activeNodes);
794     }
795   }
796 
797   public Thread open(File file, String commandDescription) {
798     UIFileOpener opener = openInit(file, commandDescription);
799     openThread = new Thread(opener);
800     openThread.start();
801     setPanel(GRAPHICS);
802     return openThread;
803   }
804 
805   public Thread open(List<File> files, String commandDescription) {
806     UIFileOpener openFile = openInit(files, commandDescription);
807     openThread = new Thread(openFile);
808     openThread.start();
809     setPanel(GRAPHICS);
810     return openThread;
811   }
812 
813   /**
814    * open
815    *
816    * @param name a {@link java.lang.String} object.
817    * @return a {@link java.lang.Thread} object.
818    */
819   public Thread open(String name) {
820     File file = resolveName(name);
821     if (file == null) {
822       logger.log(Level.WARNING, "{0}: could not be found.", name);
823       return null;
824     }
825     return open(file, null);
826   }
827 
828   /**
829    * open
830    *
831    * @param names an array of {@link java.lang.String} objects.
832    * @return a {@link java.lang.Thread} object.
833    */
834   public Thread open(String[] names) {
835     if (names == null) {
836       return null;
837     }
838     List<File> files = new ArrayList<>();
839     // Resolve all file names.
840     for (String name : names) {
841       File file = resolveName(name);
842       if (file == null || !file.exists()) {
843         return null;
844       }
845       files.add(file);
846     }
847     return open(files, null);
848   }
849 
850   public synchronized MolecularAssembly[] openWaitUtils(String file) {
851     UIFileOpener opener = openFromUtils(file);
852     Thread thread = new Thread(opener);
853     while (thread.isAlive()) {
854       try {
855         wait(1);
856       } catch (InterruptedException e) {
857         String message = "Exception waiting for " + file + " to open.";
858         logger.log(Level.WARNING, message, e);
859         return null;
860       }
861     }
862 
863     MolecularAssembly[] systems = activeFilter.getMolecularAssemblyArray();
864     if (systems != null) {
865       int n = systems.length;
866       FFXSystem[] ffxSystems = new FFXSystem[n];
867       FFXSystem[] allSystems = getHierarchy().getSystems();
868       int total = allSystems.length;
869       System.arraycopy(allSystems, total - n, ffxSystems, 0, n);
870       return ffxSystems;
871     } else {
872       return null;
873     }
874   }
875 
876   /**
877    * reset
878    */
879   public void reset() {
880     Trajectory trajectory = getTrajectory();
881     if (trajectory == null) {
882       return;
883     }
884     trajectory.stop();
885     trajectory.rewind();
886   }
887 
888   public void saveKeywordFile(File file) {
889     keywordPanel.keySave(file);
890   }
891 
892   /**
893    * {@inheritDoc}
894    */
895   @Override
896   public void stateChanged(ChangeEvent evt) {
897     JTabbedPane jtp = (JTabbedPane) evt.getSource();
898     int index = jtp.getSelectedIndex();
899     if (index == 0) {
900       graphicsCanvas.selected();
901     } else if (index == 1) {
902       keywordPanel.selected();
903     } else if (index == 2) {
904       modelingPanel.selected();
905     }
906   }
907 
908   /**
909    * stop
910    */
911   public void stop() {
912     Trajectory trajectory = getTrajectory();
913     if (trajectory == null) {
914       return;
915     }
916     trajectory.stop();
917   }
918 
919   /**
920    * {@inheritDoc}
921    */
922   @Override
923   public String toString() {
924     return "Program Control";
925   }
926 
927   /**
928    * Prompt the user to select an alternate key file.
929    */
930   private void chooseKey() {
931     JFileChooser d = MainPanel.resetFileChooser();
932     d.setFileFilter(new KeyFileFilter());
933     d.setAcceptAllFileFilterUsed(false);
934     FFXSystem sys = getHierarchy().getActive();
935     if (sys != null) {
936       File newCWD = sys.getFile();
937       if (newCWD != null && newCWD.getParentFile() != null) {
938         d.setCurrentDirectory(newCWD.getParentFile());
939       }
940     } else {
941       return;
942     }
943     int result = d.showOpenDialog(this);
944     if (result == JFileChooser.APPROVE_OPTION) {
945       File f = d.getSelectedFile();
946       sys.setKeyFile(f);
947       sys.setKeywords(KeyFilter.open(f));
948       getKeywordPanel().loadActive(sys);
949     }
950   }
951 
952   /**
953    * Prompt the user to select an alternate log file
954    */
955   private void chooseLog() {
956     JFileChooser d = resetFileChooser();
957     FFXSystem sys = getHierarchy().getActive();
958     if (sys != null) {
959       File newCWD = sys.getFile();
960       if (newCWD != null && newCWD.getParentFile() != null) {
961         d.setCurrentDirectory(newCWD.getParentFile());
962       }
963     } else {
964       return;
965     }
966     d.setDialogTitle("Select a log file");
967     d.setAcceptAllFileFilterUsed(true);
968     int result = d.showOpenDialog(this);
969     if (result == JFileChooser.APPROVE_OPTION) {
970       File f = d.getSelectedFile();
971       if (f != null) {
972         sys.setLogFile(f);
973         setCWD(d.getCurrentDirectory());
974         // getModelingPanel().selected();
975       }
976     }
977   }
978 
979   /**
980    * closeWait
981    */
982   synchronized void closeWait() {
983     FFXSystem active = hierarchy.getActive();
984     if (active == null) {
985       logger.log(Level.INFO, " No active system to close.");
986       return;
987     }
988     Thread thread = close(active);
989     while (thread != null && thread.isAlive()) {
990       try {
991         wait(1);
992       } catch (InterruptedException e) {
993         String message = "Exception waiting for " + active + " to close.";
994         logger.log(Level.WARNING, message, e);
995       }
996     }
997   }
998 
999   /**
1000    * Close all open systems.
1001    */
1002   synchronized void closeAll() {
1003     while (hierarchy.getActive() != null) {
1004       closeWait();
1005     }
1006   }
1007 
1008   /**
1009    * Attempt to connect to a TINKER Simulation
1010    */
1011   private void connect() {
1012     if (simulation == null || simulation.isFinished()) {
1013       if (simulation != null) {
1014         simulation.release();
1015       }
1016       simulation = new SimulationLoader(null, null, this, socketAddress);
1017       simulation.connect();
1018       mainMenu.setConnect(false);
1019       setPanel(GRAPHICS);
1020     }
1021   }
1022 
1023   /**
1024    * connectToTINKER
1025    *
1026    * @param system         a {@link ffx.ui.FFXSystem} object.
1027    * @param modelingThread a {@link java.lang.Thread} object.
1028    */
1029   void connectToTINKER(FFXSystem system, Thread modelingThread) {
1030     if (simulation == null || simulation.isFinished()) {
1031       if (simulation != null) {
1032         simulation.release();
1033       }
1034       InetSocketAddress tempAddress;
1035       try {
1036         tempAddress = new InetSocketAddress(InetAddress.getLocalHost(), port);
1037       } catch (Exception e) {
1038         try {
1039           tempAddress = new InetSocketAddress(InetAddress.getByName(null), port);
1040         } catch (Exception ex) {
1041           System.err.println("Could not determine Local Host: " + ex);
1042           return;
1043         }
1044       }
1045       simulation = new SimulationLoader(system, modelingThread, this, tempAddress);
1046       if (modelingThread != null) {
1047         modelingThread.start();
1048       }
1049       simulation.connect();
1050       mainMenu.setConnect(false);
1051       setPanel(GRAPHICS);
1052     }
1053   }
1054 
1055   /**
1056    * createKeyFile
1057    *
1058    * @param system a {@link ffx.ui.FFXSystem} object.
1059    * @return a boolean.
1060    */
1061   boolean createKeyFile(FFXSystem system) {
1062     String message = "Please select a parameter file " + "and a TINKER Key file will be created.";
1063     String params = (String) JOptionPane.showInputDialog(this, message, "Parameter File",
1064         JOptionPane.QUESTION_MESSAGE, null, keywordPanel.getParamFiles(), null);
1065     if (params != null) {
1066       if (params.equalsIgnoreCase("Use an existing TINKER Key file")) {
1067         JFileChooser fc = resetFileChooser();
1068         fc.setDialogTitle("Choose a KEY File");
1069         fc.setCurrentDirectory(pwd);
1070         fc.setSelectedFile(null);
1071         fc.setFileFilter(keyFileFilter);
1072         int result = fc.showOpenDialog(this);
1073         if (result == JFileChooser.APPROVE_OPTION) {
1074           File keyfile = fc.getSelectedFile();
1075           if (keyfile.exists()) {
1076             Hashtable<String, Keyword> keywordHash = KeyFilter.open(keyfile);
1077             if (keywordHash != null) {
1078               system.setKeywords(keywordHash);
1079             } else {
1080               return false;
1081             }
1082             system.setKeyFile(keyfile);
1083             system.setForceField(null);
1084             return true;
1085           }
1086         }
1087       } else {
1088         File tempFile = system.getFile();
1089         if (tempFile.getParentFile().canWrite()) {
1090           String path = system.getFile().getParent() + File.separatorChar;
1091           String keyFileName = system.getName() + ".key";
1092           File keyfile = new File(path + keyFileName);
1093           try {
1094             FileWriter fw = new FileWriter(keyfile);
1095             BufferedWriter bw = new BufferedWriter(fw);
1096             bw.write("\n");
1097             bw.write("# Force Field Selection\n");
1098             String tempParm = keywordPanel.getParamPath(params);
1099             if (tempParm.indexOf(" ") > 0) {
1100               tempParm = "\"" + keywordPanel.getParamPath(params) + "\"";
1101             }
1102             bw.write("PARAMETERS        " + tempParm + "\n");
1103             bw.close();
1104             fw.close();
1105             Hashtable<String, Keyword> keywordHash = KeyFilter.open(keyfile);
1106             if (keywordHash != null) {
1107               system.setKeywords(keywordHash);
1108             } else {
1109               return false;
1110             }
1111             system.setKeyFile(keyfile);
1112             system.setForceField(null);
1113             return true;
1114           } catch (Exception e) {
1115             logger.warning("" + e);
1116             message = "There was an error creating " + keyfile.getAbsolutePath();
1117             JOptionPane.showMessageDialog(this, message);
1118           }
1119         } else {
1120           message =
1121               "Could not create a Key file because " + pwd.getAbsolutePath() + " is not writable";
1122           JOptionPane.showMessageDialog(this, message);
1123         }
1124       }
1125     }
1126     return false;
1127   }
1128 
1129   /**
1130    * exit with a target ExitStatus
1131    *
1132    * @param exitStatus How FFX has closed.
1133    */
1134   void exit(ExitStatus exitStatus) {
1135     // Package-private out of conservatism; may be safe to make public.
1136     savePrefs();
1137 
1138     Resources.logResources();
1139 
1140     System.exit(exitStatus.getExitCode());
1141   }
1142 
1143   /**
1144    * Set the current exit code.
1145    *
1146    * @param exitType Enumerated type for exit codes.
1147    */
1148   void setExitType(ExitStatus exitType) {
1149     // Package-private out of conservatism; may be safe to make public.
1150     this.exitType = exitType;
1151   }
1152 
1153   /**
1154    * Getter for the field <code>dataRoot</code>.
1155    *
1156    * @return a {@link ffx.potential.bonded.MSRoot} object.
1157    */
1158   MSRoot getDataRoot() {
1159     return dataRoot;
1160   }
1161 
1162   /**
1163    * getFFXLocale
1164    *
1165    * @return a {@link ffx.ui.properties.FFXLocale} object.
1166    */
1167   FFXLocale getFFXLocale() {
1168     return locale;
1169   }
1170 
1171   /**
1172    * getGraphics3D
1173    *
1174    * @return a {@link ffx.ui.GraphicsCanvas} object.
1175    */
1176   GraphicsCanvas getGraphics3D() {
1177     return graphicsCanvas;
1178   }
1179 
1180   /**
1181    * Getter for the field <code>keywordPanel</code>.
1182    *
1183    * @return a {@link ffx.ui.KeywordPanel} object.
1184    */
1185   KeywordPanel getKeywordPanel() {
1186     return keywordPanel;
1187   }
1188 
1189   ModelingPanel getModelingPanel() {
1190     return modelingPanel;
1191   }
1192 
1193   /**
1194    * getStatusBar
1195    *
1196    * @return a {@link javax.swing.JLabel} object.
1197    */
1198   JLabel getStatusBar() {
1199     return statusLabel;
1200   }
1201 
1202   /**
1203    * Get the Trajectory wrapper for the active system
1204    *
1205    * @return trajectory
1206    */
1207   private Trajectory getTrajectory() {
1208     FFXSystem system = hierarchy.getActive();
1209     if (system == null) {
1210       return null;
1211     }
1212     Trajectory trajectory = system.getTrajectory();
1213     if (trajectory != null) {
1214       return trajectory;
1215     }
1216     trajectory = new Trajectory(system, this);
1217     trajectory.setOscillate(oscillate);
1218     system.setTrajectory(trajectory);
1219     return trajectory;
1220   }
1221 
1222   /**
1223    * highlightSelections
1224    *
1225    * @param evt a {@link java.awt.event.ActionEvent} object.
1226    */
1227   private void highlightSelections(ActionEvent evt) {
1228     if (evt.getSource() instanceof JCheckBoxMenuItem jcb) {
1229       hierarchy.setHighlighting(jcb.isSelected());
1230     } else {
1231       boolean highlighting = RendererCache.highlightSelections;
1232       if (highlighting) {
1233         hierarchy.setHighlighting(false);
1234         mainMenu.setHighlighting(false);
1235       } else {
1236         hierarchy.setHighlighting(true);
1237         mainMenu.setHighlighting(true);
1238       }
1239     }
1240   }
1241 
1242   private void initAbout() {
1243     aboutTextArea = new JTextArea();
1244     Font font = Font.decode(Font.MONOSPACED);
1245     aboutTextArea.setFont(font);
1246     aboutTextArea.setText(aboutString);
1247     aboutTextArea.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
1248     aboutTextArea.setEditable(false);
1249   }
1250 
1251   /**
1252    * isOpening
1253    *
1254    * @return a boolean.
1255    */
1256   boolean isOpening() {
1257     return (openThread != null && openThread.isAlive());
1258   }
1259 
1260   /**
1261    * Load preferences from the user node
1262    */
1263   private void loadPrefs() {
1264     String c = MainPanel.class.getName();
1265     JFrame frame1 = (JFrame) SwingUtilities.getRoot(this);
1266     Toolkit toolkit = getToolkit();
1267     Dimension screenSize = toolkit.getScreenSize();
1268     int x = preferences.getInt(c + ".x", screenSize.width / 8);
1269     int y = preferences.getInt(c + ".y", screenSize.height / 8);
1270     int width = preferences.getInt(c + ".width", screenSize.width * 3 / 4);
1271     int height = preferences.getInt(c + ".height", screenSize.height * 3 / 4);
1272     if (width > screenSize.width * 0.4
1273         && width < screenSize.width * 0.8
1274         && height > screenSize.height * 0.4
1275         && height < screenSize.height * 0.8) {
1276       frame1.setSize(width, height);
1277     } else {
1278       frame1.setSize(screenSize.width * 4 / 5, screenSize.height * 4 / 5);
1279     }
1280     if (x > 0 && x < screenSize.width / 2 && y > 0 && y < screenSize.height / 2) {
1281       frame1.setLocation(x, y);
1282     } else {
1283       frame1.setLocation(screenSize.width / 8, screenSize.height / 8);
1284     }
1285     splitPaneDivider = preferences.getInt(c + ".divider", 200);
1286     if (splitPaneDivider < frame1.getWidth() * (1.0f / 4.0f)) {
1287       splitPaneDivider = (int) (frame1.getWidth() * (1.0f / 4.0f));
1288     }
1289     splitPane.setDividerLocation(splitPaneDivider);
1290     if (!preferences.getBoolean(c + ".system", true)) {
1291       mainMenu.setSystemShowing(false);
1292       splitPane.setDividerLocation(0);
1293     } else {
1294       mainMenu.setSystemShowing(true);
1295     }
1296     if (!preferences.getBoolean(c + ".menu", true)) {
1297       remove(mainMenu.getToolBar());
1298       mainMenu.setMenuShowing(false);
1299       validate();
1300     } else {
1301       mainMenu.setMenuShowing(true);
1302     }
1303     try {
1304       port = preferences.getInt(c + ".port", 2000);
1305       ip = preferences.get(c + ".ip", InetAddress.getLocalHost().getHostAddress());
1306       if (ip != null) {
1307         address = InetAddress.getByName(ip);
1308         socketAddress = new InetSocketAddress(address, port);
1309       } else {
1310         socketAddress = new InetSocketAddress(port);
1311       }
1312     } catch (Exception e) {
1313       logger.log(Level.WARNING, e.toString());
1314     }
1315     if (graphicsCanvas != null) {
1316       graphicsCanvas.loadPrefs();
1317     }
1318   }
1319 
1320   /**
1321    * merge
1322    */
1323   private void merge() {
1324     ArrayList<MSNode> activeNodes = hierarchy.getActiveNodes();
1325     if (activeNodes.size() >= 2) {
1326       merge(activeNodes);
1327     }
1328   }
1329 
1330   /**
1331    * Merge two or more selected FSystem Nodes into one FSystem node. There are a few gotchas that
1332    * need to be fixed
1333    *
1334    * @param nodesToMerge a {@link java.util.ArrayList} object.
1335    */
1336   private void merge(ArrayList<MSNode> nodesToMerge) {
1337     ArrayList<MSNode> activeNodes = new ArrayList<>();
1338     for (MSNode node : nodesToMerge) {
1339       if (node != null && !(node instanceof MSRoot)) {
1340         activeNodes.add(node);
1341       }
1342     }
1343     if (activeNodes.size() <= 1) {
1344       return;
1345     }
1346     // Set up a structure to hold the new system
1347     FFXSystem active = hierarchy.getActive();
1348     File file = SystemFilter.version(hierarchy.getActive().getFile());
1349     FFXSystem system = new FFXSystem(file, "Merge Result", active.getProperties());
1350     system.setKeyFile(active.getKeyFile());
1351     system.setKeywords(KeyFilter.open(active.getKeyFile()));
1352     // Fill arrays with the atoms and bonds from the systems to be combined
1353     ArrayList<Atom> mergedAtoms = new ArrayList<>();
1354     ArrayList<Bond> mergedBonds = new ArrayList<>();
1355     ArrayList<FFXSystem> systems = new ArrayList<>();
1356     TransformGroup parentTransformGroup;
1357     FFXSystem parentSystem;
1358     Transform3D parentTransform3D = new Transform3D();
1359     Vector3d parentPosition = new Vector3d();
1360     Vector3d atomPosition = new Vector3d();
1361     // TINKER Atom Numbers start at 1
1362     int atomNum = 1;
1363     Vector3d zero = new Vector3d(0.0, 0.0, 0.0);
1364     for (MSNode m : activeNodes) {
1365       parentSystem = m.getMSNode(FFXSystem.class);
1366       if (parentSystem == null) {
1367         return;
1368       }
1369       if (!systems.contains(parentSystem)) {
1370         graphicsCanvas.updateSceneWait(
1371             parentSystem, false, true, RendererCache.ViewModel.WIREFRAME, false, null);
1372         systems.add(parentSystem);
1373       }
1374       // Move each atom into the global frame by applying the System
1375       // Transform to
1376       // relative atomic position
1377       parentTransformGroup = parentSystem.getOriginToRot();
1378       parentTransformGroup.getTransform(parentTransform3D);
1379       parentTransform3D.get(parentPosition);
1380       parentTransform3D.setTranslation(zero);
1381       // parentTransform3D.setScale(1.0d);
1382       for (Atom atom : m.getAtomList()) {
1383         atom.removeFromParent();
1384         atom.setXyzIndex(atomNum++);
1385         mergedAtoms.add(atom);
1386         atom.getV3D(atomPosition);
1387         parentTransform3D.transform(atomPosition);
1388         atomPosition.add(parentPosition);
1389         atom.moveTo(atomPosition);
1390       }
1391       for (Bond bond : m.getBondList()) {
1392         bond.removeFromParent();
1393         mergedBonds.add(bond);
1394       }
1395     }
1396     for (FFXSystem sys : systems) {
1397       close(sys);
1398     }
1399     MergeFilter mergeFilter = new MergeFilter(system, mergedAtoms, mergedBonds);
1400     UIFileOpener fileOpener = new UIFileOpener(mergeFilter, this);
1401     if (fileOpenerThreads > 0) {
1402       fileOpener.setNThreads(fileOpenerThreads);
1403     }
1404     Thread thread = new Thread(fileOpener);
1405     thread.start();
1406   }
1407 
1408   /**
1409    * oceanLookAndFeel
1410    */
1411   private void oceanLookAndFeel() {
1412     try {
1413       UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
1414       SwingUtilities.updateComponentTreeUI(SwingUtilities.getRoot(this));
1415     } catch (Exception e) {
1416       //
1417     }
1418   }
1419 
1420   /**
1421    * Trys to convert a file picked from a JFileChooser
1422    */
1423   private Thread open() {
1424     if (openThread != null && openThread.isAlive()) {
1425       return null;
1426     }
1427     JFileChooser fc = resetFileChooser();
1428     fc.setDialogTitle("Choose FFX, PDB, XYZ or ARC");
1429     fc.addChoosableFileFilter(xyzFileFilter);
1430     fc.addChoosableFileFilter(pdbFileFilter);
1431     fc.addChoosableFileFilter(intFileFilter);
1432     fc.addChoosableFileFilter(arcFileFilter);
1433     fc.addChoosableFileFilter(ffxFileFilter);
1434     fc.setAcceptAllFileFilterUsed(true);
1435     int result = fc.showOpenDialog(this);
1436     if (result == JFileChooser.APPROVE_OPTION) {
1437       File file = fc.getSelectedFile();
1438       return open(file, null);
1439     }
1440     return null;
1441   }
1442 
1443   private UIFileOpener openFromUtils(File file, String commandDescription) {
1444     UIFileOpener opener = openInit(file, commandDescription);
1445     openThread = new Thread(opener);
1446     openThread.start();
1447     setPanel(GRAPHICS);
1448     return opener;
1449   }
1450 
1451   /**
1452    * Attempts to load the supplied file
1453    *
1454    * @param file               File to open
1455    * @param commandDescription Description of the command that created this file.
1456    * @return a {@link java.lang.Thread} object.
1457    */
1458   private UIFileOpener openInit(File file, String commandDescription) {
1459     if (file == null || !file.isFile() || !file.canRead()) {
1460       return null;
1461     }
1462     file = new File(FilenameUtils.normalize(file.getAbsolutePath()));
1463     // Set the Current Working Directory based on this file.
1464     setCWD(file.getParentFile());
1465 
1466     // Create the CompositeConfiguration properties.
1467     CompositeConfiguration properties = Keyword.loadProperties(file);
1468     // Create an FFXSystem for this file.
1469     FFXSystem newSystem = new FFXSystem(file, commandDescription, properties);
1470     // Create a Force Field.
1471     forceFieldFilter = new ForceFieldFilter(properties);
1472     ForceField forceField = forceFieldFilter.parse();
1473     String[] patches = properties.getStringArray("patch");
1474     for (String patch : patches) {
1475       logger.info(" Attempting to read force field patch from " + patch + ".");
1476       CompositeConfiguration patchConfiguration = new CompositeConfiguration();
1477       patchConfiguration.addProperty("parameters", patch);
1478       forceFieldFilter = new ForceFieldFilter(patchConfiguration);
1479       ForceField patchForceField = forceFieldFilter.parse();
1480       forceField.append(patchForceField);
1481       if (RotamerLibrary.addRotPatch(patch)) {
1482         logger.info(format(" Loaded rotamer definitions from patch %s.", patch));
1483       }
1484     }
1485     newSystem.setForceField(forceField);
1486     SystemFilter systemFilter;
1487 
1488     // Decide what parser to use.
1489     if (xyzFileFilter.acceptDeep(file)) {
1490       // Use the TINKER Cartesian Coordinate File Parser.
1491       systemFilter = new XYZFilter(file, newSystem, forceField, properties);
1492     } else if (intFileFilter.acceptDeep(file)) {
1493       // Use the TINKER Internal Coordinate File Parser.
1494       systemFilter = new INTFilter(file, newSystem, forceField, properties);
1495     } else {
1496       // Use the PDB File Parser.
1497       systemFilter = new PDBFilter(file, newSystem, forceField, properties);
1498     }
1499 
1500     setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
1501     activeFilter = systemFilter;
1502     UIFileOpener fileOpener = new UIFileOpener(systemFilter, this);
1503     if (fileOpenerThreads > 0) {
1504       fileOpener.setNThreads(fileOpenerThreads);
1505     }
1506     return fileOpener;
1507   }
1508 
1509   private UIFileOpener openFromUtils(List<File> files, String commandDescription) {
1510     UIFileOpener openFile = openInit(files, commandDescription);
1511     openThread = new Thread(openFile);
1512     openThread.start();
1513     setPanel(GRAPHICS);
1514     return openFile;
1515   }
1516 
1517   /**
1518    * Attempts to load the supplied file
1519    *
1520    * @param files              Files to open
1521    * @param commandDescription Description of the command that created this file.
1522    * @return a {@link java.lang.Thread} object.
1523    */
1524   private UIFileOpener openInit(List<File> files, String commandDescription) {
1525     if (files == null) {
1526       return null;
1527     }
1528     File file = new File(FilenameUtils.normalize(files.get(0).getAbsolutePath()));
1529     // Set the Current Working Directory based on this file.
1530     setCWD(file.getParentFile());
1531 
1532     // Create the CompositeConfiguration properties.
1533     CompositeConfiguration properties = Keyword.loadProperties(file);
1534     forceFieldFilter = new ForceFieldFilter(properties);
1535     ForceField forceField = forceFieldFilter.parse();
1536 
1537     // Create an FFXSystem for this file.
1538     FFXSystem newSystem = new FFXSystem(file, commandDescription, properties);
1539     String[] patches = properties.getStringArray("patch");
1540     for (String patch : patches) {
1541       logger.info(" Attempting to read force field patch from " + patch + ".");
1542       CompositeConfiguration patchConfiguration = new CompositeConfiguration();
1543       patchConfiguration.addProperty("parameters", patch);
1544       forceFieldFilter = new ForceFieldFilter(patchConfiguration);
1545       ForceField patchForceField = forceFieldFilter.parse();
1546       forceField.append(patchForceField);
1547       if (RotamerLibrary.addRotPatch(patch)) {
1548         logger.info(format(" Loaded rotamer definitions from patch %s.", patch));
1549       }
1550     }
1551     newSystem.setForceField(forceField);
1552     // Decide what parser to use.
1553     SystemFilter systemFilter;
1554     if (xyzFileFilter.acceptDeep(file)) {
1555       // Use the TINKER Cartesian Coordinate File Parser.
1556       systemFilter = new XYZFilter(files, newSystem, forceField, properties);
1557     } else if (intFileFilter.acceptDeep(file)) {
1558       // Use the TINKER Internal Coordinate File Parser.
1559       systemFilter = new INTFilter(files, newSystem, forceField, properties);
1560     } else {
1561       // Use the PDB File Parser.
1562       systemFilter = new PDBFilter(files, newSystem, forceField, properties);
1563     }
1564 
1565     setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
1566     activeFilter = systemFilter;
1567     // return new UIFileOpener(systemFilter, this);
1568     UIFileOpener fileOpener = new UIFileOpener(systemFilter, this);
1569     if (fileOpenerThreads > 0) {
1570       fileOpener.setNThreads(fileOpenerThreads);
1571     }
1572     return fileOpener;
1573   }
1574 
1575   /**
1576    * openWait
1577    *
1578    * @param file a {@link java.lang.String} object.
1579    * @return an array of {@link ffx.ui.FFXSystem} objects.
1580    */
1581   synchronized FFXSystem[] openWait(String file) {
1582     Thread thread = open(file);
1583     while (thread != null && thread.isAlive()) {
1584       try {
1585         wait(1);
1586       } catch (InterruptedException e) {
1587         String message = "Exception waiting for " + file + " to open.";
1588         logger.log(Level.WARNING, message, e);
1589         return null;
1590       }
1591     }
1592 
1593     MolecularAssembly[] systems = activeFilter.getMolecularAssemblyArray();
1594     if (systems != null) {
1595       int n = systems.length;
1596       FFXSystem[] ffxSystems = new FFXSystem[n];
1597       FFXSystem[] allSystems = getHierarchy().getSystems();
1598       int total = allSystems.length;
1599       arraycopy(allSystems, total - n, ffxSystems, 0, n);
1600       return ffxSystems;
1601     } else {
1602       return null;
1603     }
1604   }
1605 
1606   /**
1607    * openWait
1608    *
1609    * @param file     a {@link java.lang.String} object.
1610    * @param nThreads the number of threads.
1611    * @return an array of {@link ffx.ui.FFXSystem} objects.
1612    */
1613   synchronized FFXSystem[] openWait(String file, int nThreads) {
1614     fileOpenerThreads = nThreads;
1615     FFXSystem[] systs = openWait(file);
1616     fileOpenerThreads = -1;
1617     return systs;
1618   }
1619 
1620   /**
1621    * openWait
1622    *
1623    * @param files an array of {@link java.lang.String} objects.
1624    * @return an array of {@link ffx.ui.FFXSystem} objects.
1625    */
1626   synchronized FFXSystem[] openWait(String[] files) {
1627     Thread thread = open(files);
1628     while (thread != null && thread.isAlive()) {
1629       try {
1630         wait(1);
1631       } catch (InterruptedException e) {
1632         String message = "Exception waiting for " + files[0] + " to open.";
1633         logger.log(Level.WARNING, message, e);
1634         return null;
1635       }
1636     }
1637 
1638     MolecularAssembly[] systems = activeFilter.getMolecularAssemblyArray();
1639     if (systems != null) {
1640       int n = systems.length;
1641       FFXSystem[] ffxSystems = new FFXSystem[n];
1642       FFXSystem[] allSystems = getHierarchy().getSystems();
1643       int total = allSystems.length;
1644       arraycopy(allSystems, total - n, ffxSystems, 0, n);
1645       return ffxSystems;
1646     } else {
1647       return null;
1648     }
1649   }
1650 
1651   /**
1652    * openWait
1653    *
1654    * @param files    an array of {@link java.lang.String} objects.
1655    * @param nThreads the number of threads.
1656    * @return an array of {@link ffx.ui.FFXSystem} objects.
1657    */
1658   synchronized FFXSystem[] openWait(String[] files, int nThreads) {
1659     fileOpenerThreads = nThreads;
1660     FFXSystem[] systs = openWait(files);
1661     fileOpenerThreads = -1;
1662     return systs;
1663   }
1664 
1665   private UIFileOpener openFromUtils(String name) {
1666     File file = resolveName(name);
1667     if (file == null) {
1668       logger.log(Level.WARNING, "{0}: could not be found.", name);
1669       return null;
1670     }
1671     return openFromUtils(file, null);
1672   }
1673 
1674   private File resolveName(String name) {
1675     // Return null if name == null.
1676     if (name == null) {
1677       return null;
1678     }
1679     File file = new File(name);
1680     // If the file exists, return it.
1681     if (file.exists()) {
1682       return file;
1683     }
1684     // Check for a file in the CWD.
1685     file = new File(pwd + File.separator + name);
1686     if (file.exists()) {
1687       return file;
1688     }
1689     // Check for an HTTP address
1690     if (name.startsWith("http://")) {
1691       String fileName = FilenameUtils.getName(name);
1692       if (fileName == null) {
1693         return null;
1694       }
1695       return downloadURL(name);
1696     }
1697     // Check for a PDB ID.
1698     if (name.length() == 4) {
1699       String fileName = name + ".pdb";
1700       String path = getPWD().getAbsolutePath();
1701       File pdbFile = new File(path + File.separatorChar + fileName);
1702       if (!pdbFile.exists()) {
1703         String fromURL = pdbForID(name);
1704         return downloadURL(fromURL);
1705       } else {
1706         return pdbFile;
1707       }
1708     }
1709     return null;
1710   }
1711 
1712   private UIFileOpener openFromUtils(String[] names) {
1713     if (names == null) {
1714       return null;
1715     }
1716     List<File> files = new ArrayList<>();
1717     // Resolve all file names.
1718     for (String name : names) {
1719       File file = resolveName(name);
1720       if (file == null || !file.exists()) {
1721         return null;
1722       }
1723       files.add(file);
1724     }
1725     return openFromUtils(files, null);
1726   }
1727 
1728   /**
1729    * Opens a file from the PDB
1730    */
1731   private void openFromPDB() {
1732     if (openThread != null && openThread.isAlive()) {
1733       return;
1734     }
1735     String code = JOptionPane.showInputDialog("Enter the PDB Identifier (4 characters)", "");
1736     if (code == null) {
1737       return;
1738     }
1739     code = code.toLowerCase().trim();
1740     if (code.length() != 4) {
1741       return;
1742     }
1743     String fileName = code + ".pdb";
1744     String path = getPWD().getAbsolutePath();
1745     File pdbFile = new File(path + File.separatorChar + fileName);
1746     CompositeConfiguration properties = Keyword.loadProperties(pdbFile);
1747     forceFieldFilter = new ForceFieldFilter(properties);
1748     ForceField forceField = forceFieldFilter.parse();
1749     FFXSystem newSystem = new FFXSystem(pdbFile, "PDB", properties);
1750     newSystem.setForceField(forceField);
1751     if (!pdbFile.exists()) {
1752       String fromURL = pdbForID(code);
1753       pdbFile = downloadURL(fromURL);
1754       if (pdbFile == null || !pdbFile.exists()) {
1755         return;
1756       }
1757     } else {
1758       String message = format(" Reading the local copy of the PDB file %s.", pdbFile);
1759       logger.info(message);
1760     }
1761     PDBFilter pdbFilter = new PDBFilter(pdbFile, newSystem, forceField, properties);
1762     setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
1763     UIFileOpener openFile = new UIFileOpener(pdbFilter, this);
1764     if (fileOpenerThreads > 0) {
1765       openFile.setNThreads(fileOpenerThreads);
1766     }
1767     openThread = new Thread(openFile);
1768     openThread.start();
1769     setPanel(GRAPHICS);
1770   }
1771 
1772   private File downloadURL(String fromString) {
1773     // Check for null input.
1774     if (fromString == null) {
1775       return null;
1776     }
1777 
1778     // Convert the string to a URL instance.
1779     URL fromURL;
1780     try {
1781       URI uri = new URI(fromString);
1782       fromURL = uri.toURL();
1783     } catch (MalformedURLException | URISyntaxException e) {
1784       String message = format(" URL incorrectly formatted %s.", fromString);
1785       logger.log(Level.INFO, message, e);
1786       return null;
1787     }
1788 
1789     // Download the URL to a local file.
1790     logger.info(format(" Downloading %s", fromString));
1791     try {
1792       File toFile = new File(FilenameUtils.getName(fromURL.getPath()));
1793       FileUtils.copyURLToFile(fromURL, toFile, 1000, 1000);
1794       logger.info(format(" Saved to %s\n", toFile.getPath()));
1795       return toFile;
1796     } catch (IOException ex) {
1797       logger.log(Level.INFO, " Failed to read URL " + fromURL.getPath(), ex);
1798       return null;
1799     }
1800   }
1801 
1802   private void openInduced() {
1803     FFXSystem active = hierarchy.getActive();
1804     resetFileChooser();
1805     fileChooser.setCurrentDirectory(pwd);
1806     fileChooser.setSelectedFile(active.getFile());
1807     fileChooser.setDialogTitle("Choose Induced Dipole File");
1808     fileChooser.addChoosableFileFilter(indFileFilter);
1809     fileChooser.setAcceptAllFileFilterUsed(true);
1810     fileChooser.setFileFilter(indFileFilter);
1811     int result = fileChooser.showOpenDialog(this);
1812     if (result == JFileChooser.APPROVE_OPTION) {
1813       File f = fileChooser.getSelectedFile();
1814       InducedFilter indFilter = new InducedFilter(active, f);
1815       indFilter.read();
1816     }
1817   }
1818 
1819   /**
1820    * Attempt to convert a TINKER *.key file
1821    *
1822    * @param newSystem FFXSystem that needs an associated Key File
1823    * @param createKey flag to create a key file be created
1824    * @return Key file that was found, or null if nothing could be found
1825    */
1826   boolean openKey(FFXSystem newSystem, boolean createKey) {
1827     String keyFileName;
1828     String temp = newSystem.getFile().getName();
1829     int dot = temp.lastIndexOf(".");
1830     if (dot > 0) {
1831       keyFileName = temp.substring(0, dot) + ".key";
1832     } else {
1833       keyFileName = temp + ".key";
1834     }
1835     String path = newSystem.getFile().getParent() + File.separator;
1836     File keyfile = new File(path + keyFileName);
1837     if (keyfile.exists()) {
1838       Hashtable<String, Keyword> keywordHash = KeyFilter.open(keyfile);
1839       if (keywordHash != null) {
1840         newSystem.setKeywords(keywordHash);
1841       } else {
1842         return false;
1843       }
1844       newSystem.setKeyFile(keyfile);
1845       newSystem.setForceField(null);
1846       return true;
1847     }
1848     keyfile = new File(path + "tinker.key");
1849     if (keyfile.exists()) {
1850       logger.info("Using tinker.key: " + keyfile);
1851       Hashtable<String, Keyword> keywordHash = KeyFilter.open(keyfile);
1852       if (keywordHash != null) {
1853         newSystem.setKeywords(keywordHash);
1854       } else {
1855         return false;
1856       }
1857       newSystem.setKeyFile(keyfile);
1858       newSystem.setForceField(null);
1859       return true;
1860     }
1861     if (createKey) {
1862       return createKeyFile(newSystem);
1863     }
1864     return false;
1865   }
1866 
1867   /**
1868    * openOn
1869    *
1870    * @param f         a {@link java.io.File} object.
1871    * @param oldSystem a {@link ffx.ui.FFXSystem} object.
1872    * @param command   a {@link java.lang.String} object.
1873    */
1874   void openOn(File f, FFXSystem oldSystem, String command) {
1875     XYZFilter.readOnto(f, oldSystem);
1876     oldSystem.setCommandDescription(command);
1877     graphicsCanvas.updateScene(oldSystem, true, false, null, false, null);
1878     getHierarchy().updateStatus();
1879     getHierarchy().repaint();
1880   }
1881 
1882   /**
1883    * oscillate
1884    *
1885    * @param evt a {@link java.awt.event.ActionEvent} object.
1886    */
1887   private void oscillate(ActionEvent evt) {
1888     oscillate = ((JCheckBoxMenuItem) evt.getSource()).isSelected();
1889     FFXSystem[] systems = getHierarchy().getSystems();
1890 
1891     if (systems == null) {
1892       return;
1893     }
1894 
1895     for (FFXSystem system : systems) {
1896       Trajectory trajectory = system.getTrajectory();
1897       if (trajectory != null) {
1898         trajectory.setOscillate(oscillate);
1899       }
1900     }
1901   }
1902 
1903   /**
1904    * platformLookAndFeel
1905    */
1906   private void platformLookAndFeel() {
1907     try {
1908       if (SystemUtils.IS_OS_LINUX) {
1909         UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel");
1910       } else {
1911         UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
1912       }
1913       SwingUtilities.updateComponentTreeUI(SwingUtilities.getRoot(this));
1914     } catch (ClassNotFoundException
1915              | InstantiationException
1916              | IllegalAccessException
1917              | UnsupportedLookAndFeelException e) {
1918       logger.log(Level.WARNING, "Can''t set look and feel: {0}", e);
1919     }
1920   }
1921 
1922   /**
1923    * play
1924    */
1925   private void play() {
1926     Trajectory trajectory = getTrajectory();
1927     if (trajectory == null) {
1928       return;
1929     }
1930     trajectory.start();
1931   }
1932 
1933   /**
1934    * Close the connection to a running simulation
1935    */
1936   private void release() {
1937     if (simulation != null) {
1938       simulation.release();
1939       simulation = null;
1940       mainMenu.setConnect(true);
1941     }
1942   }
1943 
1944   /**
1945    * resetPanes
1946    */
1947   private void resetPanes() {
1948     resizePanes(0);
1949   }
1950 
1951   /**
1952    * resetShell
1953    */
1954   void resetShell() {
1955     if (!GraphicsEnvironment.isHeadless()) {
1956       modelingShell = getModelingShell();
1957       try {
1958         modelingShell.exit();
1959       } catch (NullPointerException e) {
1960         //
1961       } finally {
1962         modelingShell = null;
1963       }
1964       modelingShell = getModelingShell();
1965     }
1966   }
1967 
1968   /**
1969    * Set the split panes to their default proportions
1970    *
1971    * @param move a int.
1972    */
1973   private void resizePanes(int move) {
1974     if (move == 0) {
1975       splitPaneDivider = 0;
1976       mainMenu.setMenuShowing(false);
1977       mainMenu.toggleToolBarShowing();
1978       mainMenu.setSystemShowing(false);
1979       mainMenu.systemClick();
1980     } else {
1981       splitPane.setDividerLocation(splitPane.getDividerLocation() + move);
1982     }
1983   }
1984 
1985   /**
1986    * Save the currently selected FFXSystem to disk.
1987    *
1988    * @param file File to save the system to.
1989    * @since 1.0
1990    */
1991   void saveAsXYZ(File file) {
1992     FFXSystem system = hierarchy.getActive();
1993     if (system != null && !system.isClosing()) {
1994       File saveFile = file;
1995       if (saveFile == null) {
1996         resetFileChooser();
1997         fileChooser.setCurrentDirectory(pwd);
1998         fileChooser.setFileFilter(xyzFileFilter);
1999         fileChooser.setAcceptAllFileFilterUsed(false);
2000         int result = fileChooser.showSaveDialog(this);
2001         if (result == JFileChooser.APPROVE_OPTION) {
2002           saveFile = fileChooser.getSelectedFile();
2003           pwd = saveFile.getParentFile();
2004         }
2005       }
2006       if (saveFile != null) {
2007         SystemFilter filter = new XYZFilter(saveFile, system, null, null);
2008         if (filter.writeFile(saveFile, false)) {
2009           // Refresh Panels with the new System name
2010           hierarchy.setActive(system);
2011           activeFilter = filter;
2012         }
2013       }
2014     }
2015   }
2016 
2017   /**
2018    * Save the currently selected FFXSystem to disk.
2019    *
2020    * @param file File to save the system to.
2021    * @since 1.0
2022    */
2023   void saveAsP1(File file) {
2024     FFXSystem system = hierarchy.getActive();
2025     if (system != null && !system.isClosing()) {
2026       File saveFile = file;
2027       if (saveFile == null) {
2028         resetFileChooser();
2029         fileChooser.setCurrentDirectory(pwd);
2030         fileChooser.setFileFilter(xyzFileFilter);
2031         fileChooser.setAcceptAllFileFilterUsed(false);
2032         int result = fileChooser.showSaveDialog(this);
2033         if (result == JFileChooser.APPROVE_OPTION) {
2034           saveFile = fileChooser.getSelectedFile();
2035           pwd = saveFile.getParentFile();
2036         }
2037       }
2038       if (saveFile != null) {
2039         XYZFilter filter = new XYZFilter(saveFile, system, null, null);
2040         Crystal crystal = system.getCrystal().getUnitCell();
2041         if (crystal instanceof ReplicatesCrystal) {
2042           crystal = crystal.getUnitCell();
2043         }
2044         if (filter.writeFileAsP1(saveFile, false, crystal)) {
2045           // Refresh Panels with the new System name
2046           hierarchy.setActive(system);
2047         }
2048         activeFilter = filter;
2049       }
2050     }
2051   }
2052 
2053   /**
2054    * Save the currently selected FFXSystem to a PDB file.
2055    *
2056    * @param file File to save the system to.
2057    * @since 1.0
2058    */
2059   void saveAsPDB(File file) {
2060     saveAsPDB(file, true);
2061   }
2062 
2063   /**
2064    * Save the currently selected FFXSystem to a PDB file.
2065    *
2066    * @param file     File to save the system to.
2067    * @param writeEnd Flag to indicate this is the last snapshot.
2068    * @since 1.0
2069    */
2070   private void saveAsPDB(File file, boolean writeEnd) {
2071     saveAsPDB(file, writeEnd, false);
2072   }
2073 
2074   /**
2075    * Save the currently selected FFXSystem to a PDB file.
2076    *
2077    * @param file     File to save the system to.
2078    * @param writeEnd Flag to indicate this is the last snapshot.
2079    * @param append   Flag to indicate appending this snapshot.
2080    * @since 1.0
2081    */
2082   void saveAsPDB(File file, boolean writeEnd, boolean append) {
2083     FFXSystem system = hierarchy.getActive();
2084     if (system == null) {
2085       logger.log(Level.INFO, " No active system to save.");
2086       return;
2087     }
2088     if (system.isClosing()) {
2089       logger.log(Level.INFO, " {0} is being closed and can no longer be saved.", system);
2090       return;
2091     }
2092     File saveFile = file;
2093     if (saveFile == null) {
2094       resetFileChooser();
2095       fileChooser.setCurrentDirectory(pwd);
2096       fileChooser.setFileFilter(pdbFileFilter);
2097       fileChooser.setAcceptAllFileFilterUsed(false);
2098       int result = fileChooser.showSaveDialog(this);
2099       if (result == JFileChooser.APPROVE_OPTION) {
2100         saveFile = fileChooser.getSelectedFile();
2101         pwd = saveFile.getParentFile();
2102       }
2103     }
2104     if (saveFile == null) {
2105       logger.log(Level.INFO, " No filename is defined for {0}.", system);
2106       return;
2107     }
2108     PDBFilter pdbFilter = new PDBFilter(saveFile, system, null, null);
2109     if (pdbFilter.writeFile(saveFile, append, false, writeEnd)) {
2110       // Refresh Panels with the new System name
2111       hierarchy.setActive(system);
2112       activeFilter = pdbFilter;
2113     } else {
2114       logger.log(Level.INFO, " Save failed for: {0}", system);
2115     }
2116   }
2117 
2118   void savePDBasP1(File file) {
2119     FFXSystem system = hierarchy.getActive();
2120     if (system == null) {
2121       logger.log(Level.INFO, " No active system to save.");
2122       return;
2123     }
2124     if (system.isClosing()) {
2125       logger.log(Level.INFO, " {0} is being closed and can no longer be saved.", system);
2126       return;
2127     }
2128     File saveFile = file;
2129     if (saveFile == null) {
2130       resetFileChooser();
2131       fileChooser.setCurrentDirectory(pwd);
2132       fileChooser.setFileFilter(pdbFileFilter);
2133       fileChooser.setAcceptAllFileFilterUsed(false);
2134       int result = fileChooser.showSaveDialog(this);
2135       if (result == JFileChooser.APPROVE_OPTION) {
2136         saveFile = fileChooser.getSelectedFile();
2137         pwd = saveFile.getParentFile();
2138       }
2139     }
2140     if (saveFile == null) {
2141       logger.log(Level.INFO, " No filename is defined for {0}.", system);
2142       return;
2143     }
2144 
2145     PDBFilter pdbFilter = new PDBFilter(saveFile, system, null, null);
2146     pdbFilter.writeFileAsP1(saveFile);
2147   }
2148 
2149   /**
2150    * saveAsPDB
2151    *
2152    * @param activeSystems an array of {@link ffx.potential.MolecularAssembly} objects.
2153    * @param file          a {@link java.io.File} object.
2154    */
2155   void saveAsPDB(MolecularAssembly[] activeSystems, File file) {
2156     File saveFile = file;
2157     if (saveFile == null) {
2158       resetFileChooser();
2159       fileChooser.setCurrentDirectory(pwd);
2160       fileChooser.setFileFilter(pdbFileFilter);
2161       fileChooser.setAcceptAllFileFilterUsed(false);
2162       int result = fileChooser.showSaveDialog(this);
2163       if (result == JFileChooser.APPROVE_OPTION) {
2164         saveFile = fileChooser.getSelectedFile();
2165         pwd = saveFile.getParentFile();
2166       }
2167     }
2168     if (saveFile != null) {
2169       PDBFilter pdbFilter = new PDBFilter(saveFile, Arrays.asList(activeSystems), null, null);
2170       pdbFilter.writeFile(saveFile, false);
2171       activeFilter = pdbFilter;
2172     }
2173   }
2174 
2175   /**
2176    * Save preferences to the user node
2177    */
2178   private void savePrefs() {
2179     String c = MainPanel.class.getName();
2180     if (!GraphicsEnvironment.isHeadless()) {
2181       preferences.putInt(c + ".x", frame.getLocation().x);
2182       preferences.putInt(c + ".y", frame.getLocation().y);
2183       preferences.putInt(c + ".width", frame.getWidth());
2184       preferences.putInt(c + ".height", frame.getHeight());
2185       preferences.putBoolean(c + ".system", mainMenu.isSystemShowing());
2186       preferences.putInt(c + ".divider", splitPane.getDividerLocation());
2187       preferences.putBoolean(c + ".menu", mainMenu.isMenuShowing());
2188       preferences.putBoolean(c + ".axis", mainMenu.isAxisShowing());
2189     }
2190     if (ip == null) {
2191       ip = "";
2192     }
2193     if (address != null) {
2194       String s = address.getHostAddress();
2195       if (s != null) {
2196         preferences.put(c + ".ip", s);
2197       }
2198       preferences.putInt(c + ".port", socketAddress.getPort());
2199     }
2200     preferences.put(c + ".cwd", pwd.toString());
2201 
2202     if (modelingPanel != null) {
2203       modelingPanel.savePrefs();
2204     }
2205     if (keywordPanel != null) {
2206       keywordPanel.savePrefs();
2207     }
2208     if (graphicsCanvas != null) {
2209       graphicsCanvas.savePrefs();
2210     }
2211   }
2212 
2213   /**
2214    * selectAll
2215    */
2216   private void selectAll() {
2217     if (dataRoot.getChildCount() == 0) {
2218       return;
2219     }
2220     hierarchy.selectAll();
2221   }
2222 
2223   /**
2224    * setCWD
2225    *
2226    * @param file a {@link java.io.File} object.
2227    */
2228   void setCWD(File file) {
2229     if ((file == null) || (!file.exists())) {
2230       return;
2231     }
2232     pwd = file;
2233   }
2234 
2235   /**
2236    * setPanel
2237    *
2238    * @param panel a int.
2239    */
2240   void setPanel(int panel) {
2241     // tabbedPane.setSelectedIndex(panel);
2242   }
2243 
2244   /**
2245    * Setter for the field <code>port</code>.
2246    */
2247   private void setPort() {
2248     String s = "" + port;
2249     s = JOptionPane.showInputDialog("Enter a port number", s);
2250     if (s == null) {
2251       return;
2252     }
2253     int temp;
2254     try {
2255       temp = Integer.parseInt(s);
2256     } catch (NumberFormatException e) {
2257       return;
2258     }
2259     port = temp;
2260     socketAddress = new InetSocketAddress(address, port);
2261   }
2262 
2263   /**
2264    * setRemoteJobAddress
2265    */
2266   private void setRemoteJobAddress() {
2267     if (address == null) {
2268       try {
2269         address = InetAddress.getLocalHost();
2270       } catch (Exception e) {
2271         try {
2272           address = InetAddress.getByName(null);
2273         } catch (Exception ex) {
2274           return;
2275         }
2276       }
2277     }
2278     String s = address.getHostAddress();
2279     s = JOptionPane.showInputDialog("Enter an IP Address (XXX.XXX.XXX.XXX)", s);
2280     if (s == null) {
2281       return;
2282     }
2283     InetAddress newAddress;
2284     InetSocketAddress newSocketAddress;
2285     try {
2286       newAddress = InetAddress.getByName(s);
2287       newSocketAddress = new InetSocketAddress(newAddress, port);
2288     } catch (Exception e) {
2289       return;
2290     }
2291     address = newAddress;
2292     socketAddress = newSocketAddress;
2293   }
2294 
2295   /**
2296    * showGlobalAxes
2297    *
2298    * @param evt a {@link java.awt.event.ActionEvent} object.
2299    */
2300   private void showGlobalAxes(ActionEvent evt) {
2301     JCheckBoxMenuItem showAxesCheckBox = (JCheckBoxMenuItem) evt.getSource();
2302     graphicsCanvas.setAxisShowing(showAxesCheckBox.isSelected());
2303   }
2304 
2305   /**
2306    * showToolBar
2307    *
2308    * @param evt a {@link java.awt.event.ActionEvent} object.
2309    */
2310   private void showToolBar(ActionEvent evt) {
2311     JCheckBoxMenuItem toolBarCheckBox = (JCheckBoxMenuItem) evt.getSource();
2312     if (toolBarCheckBox.isSelected()) {
2313       add(mainMenu.getToolBar(), BorderLayout.NORTH);
2314       frame.validate();
2315     } else {
2316       remove(mainMenu.getToolBar());
2317       frame.validate();
2318     }
2319   }
2320 
2321   /**
2322    * showTree
2323    *
2324    * @param evt a {@link java.awt.event.ActionEvent} object.
2325    */
2326   private void showTree(ActionEvent evt) {
2327     JCheckBoxMenuItem treeCheckBox = (JCheckBoxMenuItem) evt.getSource();
2328     if (treeCheckBox.isSelected()) {
2329       if (splitPaneDivider < frame.getWidth() * (1.0f / 4.0f)) {
2330         splitPaneDivider = (int) (frame.getWidth() * (1.0f / 4.0f));
2331       }
2332       splitPane.setDividerLocation(splitPaneDivider);
2333     } else {
2334       splitPaneDivider = splitPane.getDividerLocation();
2335       splitPane.setDividerLocation(0.0);
2336     }
2337   }
2338 
2339   /**
2340    * skip
2341    */
2342   private void skip() {
2343     Trajectory trajectory = getTrajectory();
2344     if (trajectory == null) {
2345       return;
2346     }
2347     String skip = "" + trajectory.getSkip();
2348     skip = JOptionPane.showInputDialog("Enter the Number of Frames to Skip", skip);
2349     try {
2350       int f = Integer.parseInt(skip);
2351       trajectory.setSkip(f);
2352     } catch (NumberFormatException e) {
2353       //
2354     }
2355   }
2356 
2357   /**
2358    * speed
2359    */
2360   private void speed() {
2361     Trajectory trajectory = getTrajectory();
2362     if (trajectory == null) {
2363       return;
2364     }
2365     String rate = "" + trajectory.getRate();
2366     rate = JOptionPane.showInputDialog("Enter the Frame Rate (1-100)", rate);
2367     try {
2368       int f = Integer.parseInt(rate);
2369       trajectory.setRate(f);
2370     } catch (NumberFormatException e) {
2371       //
2372     }
2373   }
2374 
2375   /**
2376    * stepBack
2377    */
2378   private void stepBack() {
2379     Trajectory trajectory = getTrajectory();
2380     if (trajectory == null) {
2381       return;
2382     }
2383     trajectory.stop();
2384     trajectory.back();
2385   }
2386 
2387   /**
2388    * stepForward
2389    */
2390   private void stepForward() {
2391     Trajectory trajectory = getTrajectory();
2392     if (trajectory == null) {
2393       return;
2394     }
2395     trajectory.stop();
2396     trajectory.forward();
2397   }
2398 
2399   /**
2400    * Enumerates the exit status codes FFX may terminate with; 0 will indicate normal execution, 1-99
2401    * will indicate fatal errors, 100-199 non-fatal errors, and 200-254 other exit statuses.
2402    * Presently, only 0, 1, 3, 100, and 200 have been defined for FFX.
2403    *
2404    * <p>When adding to this enumeration, avoid the ranges 2, 64-78, 126-128, 130, 137, and 255 or
2405    * greater (see https://tldp.org/LDP/abs/html/exitcodes.html and the C/C++ standard
2406    * /usr/include/sysexits.h).
2407    */
2408   enum ExitStatus {
2409     // Normal termination.
2410     NORMAL(0),
2411     // Indicates some uncaught Exception, Error, or Throwable. As of now,
2412     // this enum value is unused, and we rely on the JVM automatically exiting
2413     // with a system code of 1 under these circumstances.
2414     EXCEPTION(1),
2415     // A call to logger.severe() resulted in program termination.
2416     SEVERE(3),
2417     // Algorithm did not complete properly (a minimization had a bad
2418     // interpolation, etc).
2419     ALGORITHM_FAILURE(100),
2420     // Some issue not listed here.
2421     OTHER(200);
2422 
2423     // This gets sent to System.exit().
2424     private final int exitCode;
2425 
2426     ExitStatus(int exitCode) {
2427       this.exitCode = exitCode;
2428     }
2429 
2430     /**
2431      * Gets the exit code associated with this exit status.
2432      *
2433      * @return JVM exit code.
2434      */
2435     int getExitCode() {
2436       return exitCode;
2437     }
2438   }
2439 }