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