JAL-3253 preliminary static fixes for JavaScript part 3 of 3
[jalview.git] / src / jalview / gui / Desktop.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import jalview.api.AlignViewportI;
24 import jalview.api.AlignmentViewPanel;
25 import jalview.bin.Cache;
26 import jalview.bin.Jalview;
27 import jalview.gui.ImageExporter.ImageWriterI;
28 import jalview.io.BackupFiles;
29 import jalview.io.DataSourceType;
30 import jalview.io.FileFormat;
31 import jalview.io.FileFormatException;
32 import jalview.io.FileFormatI;
33 import jalview.io.FileFormats;
34 import jalview.io.FileLoader;
35 import jalview.io.FormatAdapter;
36 import jalview.io.IdentifyFile;
37 import jalview.io.JalviewFileChooser;
38 import jalview.io.JalviewFileView;
39 import jalview.jbgui.GSplitFrame;
40 import jalview.jbgui.GStructureViewer;
41 import jalview.project.Jalview2XML;
42 import jalview.structure.StructureSelectionManager;
43 import jalview.urls.IdOrgSettings;
44 import jalview.util.BrowserLauncher;
45 import jalview.util.ImageMaker.TYPE;
46 import jalview.util.MessageManager;
47 import jalview.util.Platform;
48 import jalview.util.UrlConstants;
49 import jalview.viewmodel.AlignmentViewport;
50 import jalview.ws.params.ParamManager;
51 import jalview.ws.utils.UrlDownloadClient;
52
53 import java.awt.BorderLayout;
54 import java.awt.Color;
55 import java.awt.Dimension;
56 import java.awt.FontMetrics;
57 import java.awt.Graphics;
58 import java.awt.GridLayout;
59 import java.awt.Point;
60 import java.awt.Rectangle;
61 import java.awt.Toolkit;
62 import java.awt.Window;
63 import java.awt.datatransfer.Clipboard;
64 import java.awt.datatransfer.ClipboardOwner;
65 import java.awt.datatransfer.DataFlavor;
66 import java.awt.datatransfer.Transferable;
67 import java.awt.dnd.DnDConstants;
68 import java.awt.dnd.DropTargetDragEvent;
69 import java.awt.dnd.DropTargetDropEvent;
70 import java.awt.dnd.DropTargetEvent;
71 import java.awt.dnd.DropTargetListener;
72 import java.awt.event.ActionEvent;
73 import java.awt.event.ActionListener;
74 import java.awt.event.InputEvent;
75 import java.awt.event.KeyEvent;
76 import java.awt.event.MouseAdapter;
77 import java.awt.event.MouseEvent;
78 import java.awt.event.WindowAdapter;
79 import java.awt.event.WindowEvent;
80 import java.beans.PropertyChangeEvent;
81 import java.beans.PropertyChangeListener;
82 import java.io.BufferedInputStream;
83 import java.io.File;
84 import java.io.FileOutputStream;
85 import java.io.IOException;
86 import java.net.URL;
87 import java.util.ArrayList;
88 import java.util.Hashtable;
89 import java.util.List;
90 import java.util.ListIterator;
91 import java.util.Vector;
92 import java.util.concurrent.ExecutorService;
93 import java.util.concurrent.Executors;
94 import java.util.concurrent.Semaphore;
95
96 import javax.swing.AbstractAction;
97 import javax.swing.Action;
98 import javax.swing.ActionMap;
99 import javax.swing.Box;
100 import javax.swing.BoxLayout;
101 import javax.swing.DefaultDesktopManager;
102 import javax.swing.DesktopManager;
103 import javax.swing.InputMap;
104 import javax.swing.JButton;
105 import javax.swing.JCheckBox;
106 import javax.swing.JComboBox;
107 import javax.swing.JComponent;
108 import javax.swing.JDesktopPane;
109 import javax.swing.JFrame;
110 import javax.swing.JInternalFrame;
111 import javax.swing.JLabel;
112 import javax.swing.JMenuItem;
113 import javax.swing.JPanel;
114 import javax.swing.JPopupMenu;
115 import javax.swing.JProgressBar;
116 import javax.swing.JTextField;
117 import javax.swing.KeyStroke;
118 import javax.swing.SwingUtilities;
119 import javax.swing.event.HyperlinkEvent;
120 import javax.swing.event.HyperlinkEvent.EventType;
121 import javax.swing.event.InternalFrameAdapter;
122 import javax.swing.event.InternalFrameEvent;
123 import javax.swing.event.MenuEvent;
124 import javax.swing.event.MenuListener;
125
126 import org.stackoverflowusers.file.WindowsShortcut;
127
128 /**
129  * Jalview Desktop
130  * 
131  * 
132  * @author $author$
133  * @version $Revision: 1.155 $
134  */
135 @SuppressWarnings("serial")
136 public class Desktop extends jalview.jbgui.GDesktop
137         implements DropTargetListener, ClipboardOwner, IProgressIndicator,
138         jalview.api.StructureSelectionManagerProvider
139 {
140   private final static int DEFAULT_MIN_WIDTH = 300;
141
142   private final static int DEFAULT_MIN_HEIGHT = 250;
143
144   private final static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
145
146   private final static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
147
148   private final static String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
149
150   private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
151
152   /**
153    * news reader - null if it was never started.
154    */
155   BlogReader jvnews = null;
156
157   private File projectFile;
158
159   /**
160    * @param listener
161    * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
162    */
163   public void addJalviewPropertyChangeListener(
164           PropertyChangeListener listener)
165   {
166     changeSupport.addJalviewPropertyChangeListener(listener);
167   }
168
169   /**
170    * @param propertyName
171    * @param listener
172    * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
173    *      java.beans.PropertyChangeListener)
174    */
175   public void addJalviewPropertyChangeListener(String propertyName,
176           PropertyChangeListener listener)
177   {
178     changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
179   }
180
181   /**
182    * @param propertyName
183    * @param listener
184    * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
185    *      java.beans.PropertyChangeListener)
186    */
187   public void removeJalviewPropertyChangeListener(String propertyName,
188           PropertyChangeListener listener)
189   {
190     changeSupport.removeJalviewPropertyChangeListener(propertyName,
191             listener);
192   }
193
194   /**
195    * Singleton Desktop instance only in Java;
196    */
197   private static Desktop instance;
198
199   public static Desktop getInstance()
200   {
201     Desktop d;
202     @SuppressWarnings("unused")
203     ThreadGroup g = Thread.currentThread().getThreadGroup();
204     /**
205      * @j2sNative d = g._jalviewDesktopInstance;
206      */
207     {
208       d = instance;
209     }
210     return d;
211   }
212
213   private static void setInstance(Desktop d)
214   {
215     @SuppressWarnings("unused")
216     ThreadGroup g = Thread.currentThread().getThreadGroup();
217     /**
218      * @j2sNative g._jalviewDesktopInstance = d;
219      */
220     {
221       instance = d;
222     }
223   }
224
225   private MyDesktopPane desktopPane;
226
227   public static MyDesktopPane getDesktopPane()
228   {
229     return getInstance().desktopPane;
230   }
231
232   private void setDesktopPane(MyDesktopPane pane)
233   {
234     getInstance().desktopPane = pane;
235   }
236
237   static int openFrameCount = 0;
238
239   static final int xOffset = 30;
240
241   static final int yOffset = 30;
242
243   public jalview.ws.jws1.Discoverer discoverer;
244
245   public Object[] jalviewClipboard;
246
247   public boolean internalCopy = false;
248
249   private static int fileLoadingCount = 0;
250
251   public JInternalFrame conservationSlider, PIDSlider;
252
253   class MyDesktopManager implements DesktopManager
254   {
255
256     private DesktopManager delegate;
257
258     public MyDesktopManager(DesktopManager delegate)
259     {
260       this.delegate = delegate;
261     }
262
263     @Override
264     public void activateFrame(JInternalFrame f)
265     {
266       try
267       {
268         delegate.activateFrame(f);
269       } catch (NullPointerException npe)
270       {
271         Point p = getMousePosition();
272         getInstance().showPasteMenu(p.x, p.y);
273       }
274     }
275
276     @Override
277     public void beginDraggingFrame(JComponent f)
278     {
279       delegate.beginDraggingFrame(f);
280     }
281
282     @Override
283     public void beginResizingFrame(JComponent f, int direction)
284     {
285       delegate.beginResizingFrame(f, direction);
286     }
287
288     @Override
289     public void closeFrame(JInternalFrame f)
290     {
291       delegate.closeFrame(f);
292     }
293
294     @Override
295     public void deactivateFrame(JInternalFrame f)
296     {
297       delegate.deactivateFrame(f);
298     }
299
300     @Override
301     public void deiconifyFrame(JInternalFrame f)
302     {
303       delegate.deiconifyFrame(f);
304     }
305
306     @Override
307     public void dragFrame(JComponent f, int newX, int newY)
308     {
309       if (newY < 0)
310       {
311         newY = 0;
312       }
313       delegate.dragFrame(f, newX, newY);
314     }
315
316     @Override
317     public void endDraggingFrame(JComponent f)
318     {
319       delegate.endDraggingFrame(f);
320       getDesktopPane().repaint();
321     }
322
323     @Override
324     public void endResizingFrame(JComponent f)
325     {
326       delegate.endResizingFrame(f);
327       getDesktopPane().repaint();
328     }
329
330     @Override
331     public void iconifyFrame(JInternalFrame f)
332     {
333       delegate.iconifyFrame(f);
334     }
335
336     @Override
337     public void maximizeFrame(JInternalFrame f)
338     {
339       delegate.maximizeFrame(f);
340     }
341
342     @Override
343     public void minimizeFrame(JInternalFrame f)
344     {
345       delegate.minimizeFrame(f);
346     }
347
348     @Override
349     public void openFrame(JInternalFrame f)
350     {
351       delegate.openFrame(f);
352     }
353
354     @Override
355     public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
356             int newHeight)
357     {
358       if (newY < 0)
359       {
360         newY = 0;
361       }
362       delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
363     }
364
365     @Override
366     public void setBoundsForFrame(JComponent f, int newX, int newY,
367             int newWidth, int newHeight)
368     {
369       delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
370     }
371
372     // All other methods, simply delegate
373
374   }
375
376   /**
377    * Creates a new Desktop object.
378    */
379   public Desktop()
380   {
381     super();
382     /**
383      * A note to implementors. It is ESSENTIAL that any activities that might
384      * block are spawned off as threads rather than waited for during this
385      * constructor.
386      */
387     setInstance(this);
388     if (!Platform.isJS())
389     {
390       doVamsasClientCheck();
391     }
392
393     doConfigureStructurePrefs();
394     setTitle("Jalview " + jalview.bin.Cache.getProperty("VERSION"));
395     setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
396     boolean selmemusage = jalview.bin.Cache.getDefault("SHOW_MEMUSAGE",
397             false);
398     boolean showjconsole = jalview.bin.Cache.getDefault("SHOW_JAVA_CONSOLE",
399             false);
400     setDesktopPane(new MyDesktopPane(selmemusage));
401
402     showMemusage.setSelected(selmemusage);
403     getDesktopPane().setBackground(Color.white);
404     getContentPane().setLayout(new BorderLayout());
405     // alternate config - have scrollbars - see notes in JAL-153
406     // JScrollPane sp = new JScrollPane();
407     // sp.getViewport().setView(desktop);
408     // getContentPane().add(sp, BorderLayout.CENTER);
409
410     // BH 2018 - just an experiment to try unclipped JInternalFrames.
411     if (Platform.isJS())
412     {
413       getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
414     }
415
416     getContentPane().add(getDesktopPane(), BorderLayout.CENTER);
417     getDesktopPane().setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
418
419     // This line prevents Windows Look&Feel resizing all new windows to maximum
420     // if previous window was maximised
421     getDesktopPane().setDesktopManager(new MyDesktopManager(
422             (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
423                     : Platform.isAMacAndNotJS()
424                             ? new AquaInternalFrameManager(
425                                     getDesktopPane().getDesktopManager())
426                             : getDesktopPane().getDesktopManager())));
427
428     Rectangle dims = getLastKnownDimensions("");
429     if (dims != null)
430     {
431       setBounds(dims);
432     }
433     else
434     {
435       Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
436       int xPos = Math.max(5, (screenSize.width - 900) / 2);
437       int yPos = Math.max(5, (screenSize.height - 650) / 2);
438       setBounds(xPos, yPos, 900, 650);
439     }
440
441     if (!Platform.isJS())
442     /**
443      * Java only
444      * 
445      * @j2sIgnore
446      */
447     {
448
449       jconsole = new Console(this, showjconsole);
450       // add essential build information
451       jconsole.setHeader("Jalview Version: "
452               + jalview.bin.Cache.getProperty("VERSION") + "\n"
453               + "Jalview Installation: "
454               + jalview.bin.Cache.getDefault("INSTALLATION", "unknown")
455               + "\n" + "Build Date: "
456               + jalview.bin.Cache.getDefault("BUILD_DATE", "unknown") + "\n"
457               + "Java version: " + System.getProperty("java.version") + "\n"
458               + System.getProperty("os.arch") + " "
459               + System.getProperty("os.name") + " "
460               + System.getProperty("os.version"));
461
462       showConsole(showjconsole);
463
464       showNews.setVisible(false);
465
466       experimentalFeatures.setSelected(showExperimental());
467
468       getIdentifiersOrgData();
469
470       checkURLLinks();
471
472       // Spawn a thread that shows the splashscreen
473
474       SwingUtilities.invokeLater(new Runnable()
475       {
476         @Override
477         public void run()
478         {
479           new SplashScreen();
480         }
481       });
482
483       // Thread off a new instance of the file chooser - this reduces the time
484       // it
485       // takes to open it later on.
486       new Thread(new Runnable()
487       {
488         @Override
489         public void run()
490         {
491           Cache.log.debug("Filechooser init thread started.");
492           String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
493           JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
494                   fileFormat);
495           Cache.log.debug("Filechooser init thread finished.");
496         }
497       }).start();
498       // Add the service change listener
499       changeSupport.addJalviewPropertyChangeListener("services",
500               new PropertyChangeListener()
501               {
502
503                 @Override
504                 public void propertyChange(PropertyChangeEvent evt)
505                 {
506                   Cache.log.debug("Firing service changed event for "
507                           + evt.getNewValue());
508                   JalviewServicesChanged(evt);
509                 }
510
511               });
512
513     }
514
515     this.setDropTarget(new java.awt.dnd.DropTarget(getDesktopPane(), this));
516
517     this.addWindowListener(new WindowAdapter()
518     {
519       @Override
520       public void windowClosing(WindowEvent evt)
521       {
522         quit();
523       }
524     });
525
526     MouseAdapter ma;
527     this.addMouseListener(ma = new MouseAdapter()
528     {
529       @Override
530       public void mousePressed(MouseEvent evt)
531       {
532         if (evt.isPopupTrigger()) // Mac
533         {
534           showPasteMenu(evt.getX(), evt.getY());
535         }
536       }
537
538       @Override
539       public void mouseReleased(MouseEvent evt)
540       {
541         if (evt.isPopupTrigger()) // Windows
542         {
543           showPasteMenu(evt.getX(), evt.getY());
544         }
545       }
546     });
547     getDesktopPane().addMouseListener(ma);
548
549   }
550
551   /**
552    * Answers true if user preferences to enable experimental features is True
553    * (on), else false
554    * 
555    * @return
556    */
557   public boolean showExperimental()
558   {
559     String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
560             Boolean.FALSE.toString());
561     return Boolean.valueOf(experimental).booleanValue();
562   }
563
564   public void doConfigureStructurePrefs()
565   {
566     // configure services
567     StructureSelectionManager ssm = StructureSelectionManager
568             .getStructureSelectionManager(this);
569     if (jalview.bin.Cache.getDefault(Preferences.ADD_SS_ANN, true))
570     {
571       ssm.setAddTempFacAnnot(jalview.bin.Cache
572               .getDefault(Preferences.ADD_TEMPFACT_ANN, true));
573       ssm.setProcessSecondaryStructure(jalview.bin.Cache
574               .getDefault(Preferences.STRUCT_FROM_PDB, true));
575       ssm.setSecStructServices(
576               jalview.bin.Cache.getDefault(Preferences.USE_RNAVIEW, true));
577     }
578     else
579     {
580       ssm.setAddTempFacAnnot(false);
581       ssm.setProcessSecondaryStructure(false);
582       ssm.setSecStructServices(false);
583     }
584   }
585
586   public void checkForNews()
587   {
588     final Desktop me = this;
589     // Thread off the news reader, in case there are connection problems.
590     new Thread(new Runnable()
591     {
592       @Override
593       public void run()
594       {
595         Cache.log.debug("Starting news thread.");
596         jvnews = new BlogReader(me);
597         showNews.setVisible(true);
598         Cache.log.debug("Completed news thread.");
599       }
600     }).start();
601   }
602
603   public void getIdentifiersOrgData()
604   {
605     // Thread off the identifiers fetcher
606     new Thread(new Runnable()
607     {
608       @Override
609       public void run()
610       {
611         Cache.log.debug("Downloading data from identifiers.org");
612         // UrlDownloadClient client = new UrlDownloadClient();
613         try
614         {
615           UrlDownloadClient.download(IdOrgSettings.getUrl(),
616                   IdOrgSettings.getDownloadLocation());
617         } catch (IOException e)
618         {
619           Cache.log.debug("Exception downloading identifiers.org data"
620                   + e.getMessage());
621         }
622       }
623     }).start();
624     ;
625   }
626
627   @Override
628   protected void showNews_actionPerformed(ActionEvent e)
629   {
630     showNews(showNews.isSelected());
631   }
632
633   protected void showNews(boolean visible)
634   {
635     Cache.log.debug((visible ? "Showing" : "Hiding") + " news.");
636     showNews.setSelected(visible);
637     if (visible && !jvnews.isVisible())
638     {
639       new Thread(new Runnable()
640       {
641         @Override
642         public void run()
643         {
644           long now = System.currentTimeMillis();
645           Desktop.getInstance().setProgressBar(
646                   MessageManager.getString("status.refreshing_news"), now);
647           jvnews.refreshNews();
648           Desktop.getInstance().setProgressBar(null, now);
649           jvnews.showNews();
650         }
651       }).start();
652     }
653   }
654
655   /**
656    * recover the last known dimensions for a jalview window
657    * 
658    * @param windowName
659    *          - empty string is desktop, all other windows have unique prefix
660    * @return null or last known dimensions scaled to current geometry (if last
661    *         window geom was known)
662    */
663   Rectangle getLastKnownDimensions(String windowName)
664   {
665     // TODO: lock aspect ratio for scaling desktop Bug #0058199
666     Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
667     String x = jalview.bin.Cache.getProperty(windowName + "SCREEN_X");
668     String y = jalview.bin.Cache.getProperty(windowName + "SCREEN_Y");
669     String width = jalview.bin.Cache
670             .getProperty(windowName + "SCREEN_WIDTH");
671     String height = jalview.bin.Cache
672             .getProperty(windowName + "SCREEN_HEIGHT");
673     if ((x != null) && (y != null) && (width != null) && (height != null))
674     {
675       int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
676               iw = Integer.parseInt(width), ih = Integer.parseInt(height);
677       if (jalview.bin.Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
678       {
679         // attempt #1 - try to cope with change in screen geometry - this
680         // version doesn't preserve original jv aspect ratio.
681         // take ratio of current screen size vs original screen size.
682         double sw = ((1f * screenSize.width) / (1f * Integer.parseInt(
683                 jalview.bin.Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
684         double sh = ((1f * screenSize.height) / (1f * Integer.parseInt(
685                 jalview.bin.Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
686         // rescale the bounds depending upon the current screen geometry.
687         ix = (int) (ix * sw);
688         iw = (int) (iw * sw);
689         iy = (int) (iy * sh);
690         ih = (int) (ih * sh);
691         while (ix >= screenSize.width)
692         {
693           jalview.bin.Cache.log.debug(
694                   "Window geometry location recall error: shifting horizontal to within screenbounds.");
695           ix -= screenSize.width;
696         }
697         while (iy >= screenSize.height)
698         {
699           jalview.bin.Cache.log.debug(
700                   "Window geometry location recall error: shifting vertical to within screenbounds.");
701           iy -= screenSize.height;
702         }
703         jalview.bin.Cache.log.debug(
704                 "Got last known dimensions for " + windowName + ": x:" + ix
705                         + " y:" + iy + " width:" + iw + " height:" + ih);
706       }
707       // return dimensions for new instance
708       return new Rectangle(ix, iy, iw, ih);
709     }
710     return null;
711   }
712
713   private void doVamsasClientCheck()
714   {
715     if (Cache.vamsasJarsPresent())
716     {
717       setupVamsasDisconnectedGui();
718       VamsasMenu.setVisible(true);
719       final Desktop us = this;
720       VamsasMenu.addMenuListener(new MenuListener()
721       {
722         // this listener remembers when the menu was first selected, and
723         // doesn't rebuild the session list until it has been cleared and
724         // reselected again.
725         boolean refresh = true;
726
727         @Override
728         public void menuCanceled(MenuEvent e)
729         {
730           refresh = true;
731         }
732
733         @Override
734         public void menuDeselected(MenuEvent e)
735         {
736           refresh = true;
737         }
738
739         @Override
740         public void menuSelected(MenuEvent e)
741         {
742           if (refresh)
743           {
744             us.buildVamsasStMenu();
745             refresh = false;
746           }
747         }
748       });
749       vamsasStart.setVisible(true);
750     }
751   }
752
753   protected void showPasteMenu(int x, int y)
754   {
755     JPopupMenu popup = new JPopupMenu();
756     JMenuItem item = new JMenuItem(
757             MessageManager.getString("label.paste_new_window"));
758     item.addActionListener(new ActionListener()
759     {
760       @Override
761       public void actionPerformed(ActionEvent evt)
762       {
763         paste();
764       }
765     });
766
767     popup.add(item);
768     popup.show(this, x, y);
769   }
770
771   public void paste()
772   {
773     try
774     {
775       Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
776       Transferable contents = c.getContents(this);
777
778       if (contents != null)
779       {
780         String file = (String) contents
781                 .getTransferData(DataFlavor.stringFlavor);
782
783         FileFormatI format = new IdentifyFile().identify(file,
784                 DataSourceType.PASTE);
785
786         new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
787
788       }
789     } catch (Exception ex)
790     {
791       System.out.println(
792               "Unable to paste alignment from system clipboard:\n" + ex);
793     }
794   }
795
796   /**
797    * Adds and opens the given frame to the desktop
798    * 
799    * @param frame
800    *          Frame to show
801    * @param title
802    *          Visible Title
803    * @param w
804    *          width
805    * @param h
806    *          height
807    */
808   public static synchronized void addInternalFrame(
809           final JInternalFrame frame, String title, int w, int h)
810   {
811     addInternalFrame(frame, title, true, w, h, true, false);
812   }
813
814   /**
815    * Add an internal frame to the Jalview desktop
816    * 
817    * @param frame
818    *          Frame to show
819    * @param title
820    *          Visible Title
821    * @param makeVisible
822    *          When true, display frame immediately, otherwise, caller must call
823    *          setVisible themselves.
824    * @param w
825    *          width
826    * @param h
827    *          height
828    */
829   public static synchronized void addInternalFrame(
830           final JInternalFrame frame, String title, boolean makeVisible,
831           int w, int h)
832   {
833     addInternalFrame(frame, title, makeVisible, w, h, true, false);
834   }
835
836   /**
837    * Add an internal frame to the Jalview desktop and make it visible
838    * 
839    * @param frame
840    *          Frame to show
841    * @param title
842    *          Visible Title
843    * @param w
844    *          width
845    * @param h
846    *          height
847    * @param resizable
848    *          Allow resize
849    */
850   public static synchronized void addInternalFrame(
851           final JInternalFrame frame, String title, int w, int h,
852           boolean resizable)
853   {
854     addInternalFrame(frame, title, true, w, h, resizable, false);
855   }
856
857   /**
858    * Add an internal frame to the Jalview desktop
859    * 
860    * @param frame
861    *          Frame to show
862    * @param title
863    *          Visible Title
864    * @param makeVisible
865    *          When true, display frame immediately, otherwise, caller must call
866    *          setVisible themselves.
867    * @param w
868    *          width
869    * @param h
870    *          height
871    * @param resizable
872    *          Allow resize
873    * @param ignoreMinSize
874    *          Do not set the default minimum size for frame
875    */
876   public static synchronized void addInternalFrame(
877           final JInternalFrame frame, String title, boolean makeVisible,
878           int w, int h, boolean resizable, boolean ignoreMinSize)
879   {
880
881     // TODO: allow callers to determine X and Y position of frame (eg. via
882     // bounds object).
883     // TODO: consider fixing method to update entries in the window submenu with
884     // the current window title
885
886     frame.setTitle(title);
887     if (frame.getWidth() < 1 || frame.getHeight() < 1)
888     {
889       frame.setSize(w, h);
890     }
891     // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
892     // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
893     // IF JALVIEW IS RUNNING HEADLESS
894     // ///////////////////////////////////////////////
895     if (getInstance() == null || Jalview.isHeadlessMode())
896     {
897       return;
898     }
899
900     openFrameCount++;
901
902     if (!ignoreMinSize)
903     {
904       frame.setMinimumSize(
905               new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
906
907       // Set default dimension for Alignment Frame window.
908       // The Alignment Frame window could be added from a number of places,
909       // hence,
910       // I did this here in order not to miss out on any Alignment frame.
911       if (frame instanceof AlignFrame)
912       {
913         frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
914                 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
915       }
916     }
917
918     frame.setVisible(makeVisible);
919     frame.setClosable(true);
920     frame.setResizable(resizable);
921     frame.setMaximizable(resizable);
922     frame.setIconifiable(resizable);
923     frame.setOpaque(Platform.isJS());
924
925     if (frame.getX() < 1 && frame.getY() < 1)
926     {
927       frame.setLocation(xOffset * openFrameCount,
928               yOffset * ((openFrameCount - 1) % 10) + yOffset);
929     }
930
931     /*
932      * add an entry for the new frame in the Window menu 
933      * (and remove it when the frame is closed)
934      */
935     JMenuItem menuItem = new JMenuItem(title);
936     frame.addInternalFrameListener(new InternalFrameAdapter()
937     {
938       @Override
939       public void internalFrameActivated(InternalFrameEvent evt)
940       {
941         JInternalFrame itf = getDesktopPane().getSelectedFrame();
942         if (itf != null)
943         {
944           if (itf instanceof AlignFrame)
945           {
946             Jalview.setCurrentAlignFrame((AlignFrame) itf);
947           }
948           itf.requestFocus();
949         }
950       }
951
952       @Override
953       public void internalFrameClosed(InternalFrameEvent evt)
954       {
955         PaintRefresher.RemoveComponent(frame);
956
957         /*
958          * defensive check to prevent frames being
959          * added half off the window
960          */
961         if (openFrameCount > 0)
962         {
963           openFrameCount--;
964         }
965
966         /*
967          * ensure no reference to alignFrame retained by menu item listener
968          */
969         if (menuItem.getActionListeners().length > 0)
970         {
971           menuItem.removeActionListener(menuItem.getActionListeners()[0]);
972         }
973         getInstance().windowMenu.remove(menuItem);
974       };
975     });
976
977     menuItem.addActionListener(new ActionListener()
978     {
979       @Override
980       public void actionPerformed(ActionEvent e)
981       {
982         try
983         {
984           frame.setSelected(true);
985           frame.setIcon(false);
986         } catch (java.beans.PropertyVetoException ex)
987         {
988           // System.err.println(ex.toString());
989         }
990       }
991     });
992
993     setKeyBindings(frame);
994
995     getDesktopPane().add(frame);
996
997     getInstance().windowMenu.add(menuItem);
998
999     frame.toFront();
1000     try
1001     {
1002       frame.setSelected(true);
1003       frame.requestFocus();
1004     } catch (java.beans.PropertyVetoException ve)
1005     {
1006     } catch (java.lang.ClassCastException cex)
1007     {
1008       Cache.log.warn(
1009               "Squashed a possible GUI implementation error. If you can recreate this, please look at http://issues.jalview.org/browse/JAL-869",
1010               cex);
1011     }
1012   }
1013
1014   /**
1015    * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close the
1016    * window
1017    * 
1018    * @param frame
1019    */
1020   private static void setKeyBindings(JInternalFrame frame)
1021   {
1022     final Action closeAction = new AbstractAction()
1023     {
1024       @Override
1025       public void actionPerformed(ActionEvent e)
1026       {
1027         frame.dispose();
1028       }
1029     };
1030
1031     /*
1032      * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1033      */
1034     KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1035             InputEvent.CTRL_DOWN_MASK);
1036     KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1037             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
1038
1039     InputMap inputMap = frame
1040             .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1041     String ctrlW = ctrlWKey.toString();
1042     inputMap.put(ctrlWKey, ctrlW);
1043     inputMap.put(cmdWKey, ctrlW);
1044
1045     ActionMap actionMap = frame.getActionMap();
1046     actionMap.put(ctrlW, closeAction);
1047   }
1048
1049   @Override
1050   public void lostOwnership(Clipboard clipboard, Transferable contents)
1051   {
1052     if (!internalCopy)
1053     {
1054       Desktop.getInstance().jalviewClipboard = null;
1055     }
1056
1057     internalCopy = false;
1058   }
1059
1060   @Override
1061   public void dragEnter(DropTargetDragEvent evt)
1062   {
1063   }
1064
1065   @Override
1066   public void dragExit(DropTargetEvent evt)
1067   {
1068   }
1069
1070   @Override
1071   public void dragOver(DropTargetDragEvent evt)
1072   {
1073   }
1074
1075   @Override
1076   public void dropActionChanged(DropTargetDragEvent evt)
1077   {
1078   }
1079
1080   /**
1081    * DOCUMENT ME!
1082    * 
1083    * @param evt
1084    *          DOCUMENT ME!
1085    */
1086   @Override
1087   public void drop(DropTargetDropEvent evt)
1088   {
1089     boolean success = true;
1090     // JAL-1552 - acceptDrop required before getTransferable call for
1091     // Java's Transferable for native dnd
1092     evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1093     Transferable t = evt.getTransferable();
1094     List<Object> files = new ArrayList<>();
1095     List<DataSourceType> protocols = new ArrayList<>();
1096
1097     try
1098     {
1099       Desktop.transferFromDropTarget(files, protocols, evt, t);
1100     } catch (Exception e)
1101     {
1102       e.printStackTrace();
1103       success = false;
1104     }
1105
1106     if (files != null)
1107     {
1108       try
1109       {
1110         for (int i = 0; i < files.size(); i++)
1111         {
1112           // BH 2018 File or String
1113           Object file = files.get(i);
1114           String fileName = file.toString();
1115           DataSourceType protocol = (protocols == null)
1116                   ? DataSourceType.FILE
1117                   : protocols.get(i);
1118           FileFormatI format = null;
1119
1120           if (fileName.endsWith(".jar"))
1121           {
1122             format = FileFormat.Jalview;
1123
1124           }
1125           else
1126           {
1127             format = new IdentifyFile().identify(file, protocol);
1128           }
1129           if (file instanceof File)
1130           {
1131             Platform.cacheFileData((File) file);
1132           }
1133           new FileLoader().LoadFile(null, file, protocol, format);
1134
1135         }
1136       } catch (Exception ex)
1137       {
1138         success = false;
1139       }
1140     }
1141     evt.dropComplete(success); // need this to ensure input focus is properly
1142                                // transfered to any new windows created
1143   }
1144
1145   /**
1146    * DOCUMENT ME!
1147    * 
1148    * @param e
1149    *          DOCUMENT ME!
1150    */
1151   @Override
1152   public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1153   {
1154     String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1155     JalviewFileChooser chooser = JalviewFileChooser
1156             .forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat, true);
1157
1158     chooser.setFileView(new JalviewFileView());
1159     chooser.setDialogTitle(
1160             MessageManager.getString("label.open_local_file"));
1161     chooser.setToolTipText(MessageManager.getString("action.open"));
1162
1163     chooser.setResponseHandler(0, new Runnable()
1164     {
1165       @Override
1166       public void run()
1167       {
1168         File selectedFile = chooser.getSelectedFile();
1169         Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1170
1171         FileFormatI format = chooser.getSelectedFormat();
1172
1173         /*
1174          * Call IdentifyFile to verify the file contains what its extension implies.
1175          * Skip this step for dynamically added file formats, because
1176          * IdentifyFile does not know how to recognise them.
1177          */
1178         if (FileFormats.getInstance().isIdentifiable(format))
1179         {
1180           try
1181           {
1182             format = new IdentifyFile().identify(selectedFile,
1183                     DataSourceType.FILE);
1184           } catch (FileFormatException e)
1185           {
1186             // format = null; //??
1187           }
1188         }
1189
1190         new FileLoader().LoadFile(viewport, selectedFile,
1191                 DataSourceType.FILE, format);
1192       }
1193     });
1194     chooser.showOpenDialog(this);
1195   }
1196
1197   /**
1198    * Shows a dialog for input of a URL at which to retrieve alignment data
1199    * 
1200    * @param viewport
1201    */
1202   @Override
1203   public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1204   {
1205     // This construct allows us to have a wider textfield
1206     // for viewing
1207     JLabel label = new JLabel(
1208             MessageManager.getString("label.input_file_url"));
1209
1210     JPanel panel = new JPanel(new GridLayout(2, 1));
1211     panel.add(label);
1212
1213     /*
1214      * the URL to fetch is
1215      * Java: an editable combobox with history
1216      * JS: (pending JAL-3038) a plain text field
1217      */
1218     JComponent history;
1219     String urlBase = "http://www.";
1220     if (Platform.isJS())
1221     {
1222       history = new JTextField(urlBase, 35);
1223     }
1224     else
1225     /**
1226      * Java only
1227      * 
1228      * @j2sIgnore
1229      */
1230     {
1231       JComboBox<String> asCombo = new JComboBox<>();
1232       asCombo.setPreferredSize(new Dimension(400, 20));
1233       asCombo.setEditable(true);
1234       asCombo.addItem(urlBase);
1235       String historyItems = Cache.getProperty("RECENT_URL");
1236       if (historyItems != null)
1237       {
1238         for (String token : historyItems.split("\\t"))
1239         {
1240           asCombo.addItem(token);
1241         }
1242       }
1243       history = asCombo;
1244     }
1245     panel.add(history);
1246
1247     Object[] options = new Object[] { MessageManager.getString("action.ok"),
1248         MessageManager.getString("action.cancel") };
1249     Runnable action = new Runnable()
1250     {
1251       @Override
1252       public void run()
1253       {
1254         @SuppressWarnings("unchecked")
1255         String url = (history instanceof JTextField
1256                 ? ((JTextField) history).getText()
1257                 : ((JComboBox<String>) history).getSelectedItem()
1258                         .toString());
1259
1260         if (url.toLowerCase().endsWith(".jar"))
1261         {
1262           if (viewport != null)
1263           {
1264             new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1265                     FileFormat.Jalview);
1266           }
1267           else
1268           {
1269             new FileLoader().LoadFile(url, DataSourceType.URL,
1270                     FileFormat.Jalview);
1271           }
1272         }
1273         else
1274         {
1275           FileFormatI format = null;
1276           try
1277           {
1278             format = new IdentifyFile().identify(url, DataSourceType.URL);
1279           } catch (FileFormatException e)
1280           {
1281             // TODO revise error handling, distinguish between
1282             // URL not found and response not valid
1283           }
1284
1285           if (format == null)
1286           {
1287             String msg = MessageManager
1288                     .formatMessage("label.couldnt_locate", url);
1289             JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
1290                     msg,
1291                     MessageManager.getString("label.url_not_found"),
1292                     JvOptionPane.WARNING_MESSAGE);
1293
1294             return;
1295           }
1296
1297           if (viewport != null)
1298           {
1299             new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1300                     format);
1301           }
1302           else
1303           {
1304             new FileLoader().LoadFile(url, DataSourceType.URL, format);
1305           }
1306         }
1307       }
1308     };
1309     String dialogOption = MessageManager
1310             .getString("label.input_alignment_from_url");
1311     JvOptionPane.newOptionDialog(getDesktopPane())
1312             .setResponseHandler(0, action)
1313             .showInternalDialog(panel, dialogOption,
1314                     JvOptionPane.YES_NO_CANCEL_OPTION,
1315                     JvOptionPane.PLAIN_MESSAGE, null, options,
1316                     MessageManager.getString("action.ok"));
1317   }
1318
1319   /**
1320    * Opens the CutAndPaste window for the user to paste an alignment in to
1321    * 
1322    * @param viewPanel
1323    *          - if not null, the pasted alignment is added to the current
1324    *          alignment; if null, to a new alignment window
1325    */
1326   @Override
1327   public void inputTextboxMenuItem_actionPerformed(
1328           AlignmentViewPanel viewPanel)
1329   {
1330     CutAndPasteTransfer cap = new CutAndPasteTransfer();
1331     cap.setForInput(viewPanel);
1332     Desktop.addInternalFrame(cap,
1333             MessageManager.getString("label.cut_paste_alignmen_file"), true,
1334             600, 500);
1335   }
1336
1337   /*
1338    * Exit the program
1339    */
1340   @Override
1341   public void quit()
1342   {
1343     Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1344     jalview.bin.Cache.setProperty("SCREENGEOMETRY_WIDTH",
1345             screen.width + "");
1346     jalview.bin.Cache.setProperty("SCREENGEOMETRY_HEIGHT",
1347             screen.height + "");
1348     storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1349             getWidth(), getHeight()));
1350
1351     if (jconsole != null)
1352     {
1353       storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1354       jconsole.stopConsole();
1355     }
1356     if (jvnews != null)
1357     {
1358       storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1359
1360     }
1361     if (dialogExecutor != null)
1362     {
1363       dialogExecutor.shutdownNow();
1364     }
1365     closeAll_actionPerformed(null);
1366
1367     if (groovyConsole != null)
1368     {
1369       // suppress a possible repeat prompt to save script
1370       groovyConsole.setDirty(false);
1371       groovyConsole.exit();
1372     }
1373     System.exit(0);
1374   }
1375
1376   private void storeLastKnownDimensions(String string, Rectangle jc)
1377   {
1378     jalview.bin.Cache.log.debug("Storing last known dimensions for "
1379             + string + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1380             + " height:" + jc.height);
1381
1382     jalview.bin.Cache.setProperty(string + "SCREEN_X", jc.x + "");
1383     jalview.bin.Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1384     jalview.bin.Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1385     jalview.bin.Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1386   }
1387
1388   /**
1389    * DOCUMENT ME!
1390    * 
1391    * @param e
1392    *          DOCUMENT ME!
1393    */
1394   @Override
1395   public void aboutMenuItem_actionPerformed(ActionEvent e)
1396   {
1397     // StringBuffer message = getAboutMessage(false);
1398     // JvOptionPane.showInternalMessageDialog(Desktop.getDesktop(),
1399     //
1400     // message.toString(), "About Jalview", JvOptionPane.INFORMATION_MESSAGE);
1401     new Thread(new Runnable()
1402     {
1403       @Override
1404       public void run()
1405       {
1406         new SplashScreen(true);
1407       }
1408     }).start();
1409   }
1410
1411   public StringBuffer getAboutMessage(boolean shortv)
1412   {
1413     StringBuffer message = new StringBuffer();
1414     message.append("<html>");
1415     if (shortv)
1416     {
1417       message.append("<h1><strong>Version: "
1418               + jalview.bin.Cache.getProperty("VERSION")
1419               + "</strong></h1>");
1420       message.append("<strong>Last Updated: <em>"
1421               + jalview.bin.Cache.getDefault("BUILD_DATE", "unknown")
1422               + "</em></strong>");
1423
1424     }
1425     else
1426     {
1427
1428       message.append("<strong>Version "
1429               + jalview.bin.Cache.getProperty("VERSION")
1430               + "; last updated: "
1431               + jalview.bin.Cache.getDefault("BUILD_DATE", "unknown"));
1432     }
1433
1434     if (jalview.bin.Cache.getDefault("LATEST_VERSION", "Checking")
1435             .equals("Checking"))
1436     {
1437       message.append("<br>...Checking latest version...</br>");
1438     }
1439     else if (!jalview.bin.Cache.getDefault("LATEST_VERSION", "Checking")
1440             .equals(jalview.bin.Cache.getProperty("VERSION")))
1441     {
1442       boolean red = false;
1443       if (jalview.bin.Cache.getProperty("VERSION").toLowerCase()
1444               .indexOf("automated build") == -1)
1445       {
1446         red = true;
1447         // Displayed when code version and jnlp version do not match and code
1448         // version is not a development build
1449         message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1450       }
1451
1452       message.append("<br>!! Version "
1453               + jalview.bin.Cache.getDefault("LATEST_VERSION",
1454                       "..Checking..")
1455               + " is available for download from "
1456               + jalview.bin.Cache.getDefault("www.jalview.org",
1457                       "http://www.jalview.org")
1458               + " !!");
1459       if (red)
1460       {
1461         message.append("</div>");
1462       }
1463     }
1464     message.append("<br>Authors:  " + jalview.bin.Cache.getDefault(
1465             "AUTHORFNAMES",
1466             "The Jalview Authors (See AUTHORS file for current list)")
1467             + "<br><br>Development managed by The Barton Group, University of Dundee, Scotland, UK.<br>"
1468             + "<br><br>For help, see the FAQ at <a href=\"http://www.jalview.org/faq\">www.jalview.org/faq</a> and/or join the jalview-discuss@jalview.org mailing list"
1469             + "<br><br>If  you use Jalview, please cite:"
1470             + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
1471             + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
1472             + "<br>Bioinformatics doi: 10.1093/bioinformatics/btp033"
1473             + "</html>");
1474     return message;
1475   }
1476
1477   /**
1478    * Action on requesting Help documentation
1479    */
1480   @Override
1481   public void documentationMenuItem_actionPerformed()
1482   {
1483     try
1484     {
1485       if (Platform.isJS())
1486       {
1487         BrowserLauncher.openURL("http://www.jalview.org/help.html");
1488       }
1489       else
1490       /**
1491        * Java only
1492        * 
1493        * @j2sIgnore
1494        */
1495       {
1496         Help.showHelpWindow();
1497       }
1498     } catch (Exception ex)
1499     {
1500       System.err.println("Error opening help: " + ex.getMessage());
1501     }
1502   }
1503
1504   @Override
1505   public void closeAll_actionPerformed(ActionEvent e)
1506   {
1507     // TODO show a progress bar while closing?
1508     JInternalFrame[] frames = getDesktopPane().getAllFrames();
1509     for (int i = 0; i < frames.length; i++)
1510     {
1511       try
1512       {
1513         frames[i].setClosed(true);
1514       } catch (java.beans.PropertyVetoException ex)
1515       {
1516       }
1517     }
1518     Jalview.setCurrentAlignFrame(null);
1519     System.out.println("ALL CLOSED");
1520     if (v_client != null)
1521     {
1522       // TODO clear binding to vamsas document objects on close_all
1523     }
1524
1525     /*
1526      * reset state of singleton objects as appropriate (clear down session state
1527      * when all windows are closed)
1528      */
1529     StructureSelectionManager ssm = StructureSelectionManager
1530             .getStructureSelectionManager(this);
1531     if (ssm != null)
1532     {
1533       ssm.resetAll();
1534     }
1535   }
1536
1537   @Override
1538   public void raiseRelated_actionPerformed(ActionEvent e)
1539   {
1540     reorderAssociatedWindows(false, false);
1541   }
1542
1543   @Override
1544   public void minimizeAssociated_actionPerformed(ActionEvent e)
1545   {
1546     reorderAssociatedWindows(true, false);
1547   }
1548
1549   void closeAssociatedWindows()
1550   {
1551     reorderAssociatedWindows(false, true);
1552   }
1553
1554   /*
1555    * (non-Javadoc)
1556    * 
1557    * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1558    * ActionEvent)
1559    */
1560   @Override
1561   protected void garbageCollect_actionPerformed(ActionEvent e)
1562   {
1563     // We simply collect the garbage
1564     jalview.bin.Cache.log.debug("Collecting garbage...");
1565     System.gc();
1566     jalview.bin.Cache.log.debug("Finished garbage collection.");
1567   }
1568
1569   /*
1570    * (non-Javadoc)
1571    * 
1572    * @see
1573    * jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.ActionEvent
1574    * )
1575    */
1576   @Override
1577   protected void showMemusage_actionPerformed(ActionEvent e)
1578   {
1579     getDesktopPane().showMemoryUsage(showMemusage.isSelected());
1580   }
1581
1582   /*
1583    * (non-Javadoc)
1584    * 
1585    * @see
1586    * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1587    * )
1588    */
1589   @Override
1590   protected void showConsole_actionPerformed(ActionEvent e)
1591   {
1592     showConsole(showConsole.isSelected());
1593   }
1594
1595   Console jconsole = null;
1596
1597   /**
1598    * control whether the java console is visible or not
1599    * 
1600    * @param selected
1601    */
1602   void showConsole(boolean selected)
1603   {
1604     // TODO: decide if we should update properties file
1605     if (jconsole != null) // BH 2018
1606     {
1607       showConsole.setSelected(selected);
1608       Cache.setProperty("SHOW_JAVA_CONSOLE",
1609               Boolean.valueOf(selected).toString());
1610       jconsole.setVisible(selected);
1611     }
1612   }
1613
1614   void reorderAssociatedWindows(boolean minimize, boolean close)
1615   {
1616     JInternalFrame[] frames = getDesktopPane().getAllFrames();
1617     if (frames == null || frames.length < 1)
1618     {
1619       return;
1620     }
1621
1622     AlignmentViewport source = null, target = null;
1623     if (frames[0] instanceof AlignFrame)
1624     {
1625       source = ((AlignFrame) frames[0]).getCurrentView();
1626     }
1627     else if (frames[0] instanceof TreePanel)
1628     {
1629       source = ((TreePanel) frames[0]).getViewPort();
1630     }
1631     else if (frames[0] instanceof PCAPanel)
1632     {
1633       source = ((PCAPanel) frames[0]).av;
1634     }
1635     else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1636     {
1637       source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1638     }
1639
1640     if (source != null)
1641     {
1642       for (int i = 0; i < frames.length; i++)
1643       {
1644         target = null;
1645         if (frames[i] == null)
1646         {
1647           continue;
1648         }
1649         if (frames[i] instanceof AlignFrame)
1650         {
1651           target = ((AlignFrame) frames[i]).getCurrentView();
1652         }
1653         else if (frames[i] instanceof TreePanel)
1654         {
1655           target = ((TreePanel) frames[i]).getViewPort();
1656         }
1657         else if (frames[i] instanceof PCAPanel)
1658         {
1659           target = ((PCAPanel) frames[i]).av;
1660         }
1661         else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1662         {
1663           target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1664         }
1665
1666         if (source == target)
1667         {
1668           try
1669           {
1670             if (close)
1671             {
1672               frames[i].setClosed(true);
1673             }
1674             else
1675             {
1676               frames[i].setIcon(minimize);
1677               if (!minimize)
1678               {
1679                 frames[i].toFront();
1680               }
1681             }
1682
1683           } catch (java.beans.PropertyVetoException ex)
1684           {
1685           }
1686         }
1687       }
1688     }
1689   }
1690
1691   /**
1692    * DOCUMENT ME!
1693    * 
1694    * @param e
1695    *          DOCUMENT ME!
1696    */
1697   @Override
1698   protected void preferences_actionPerformed(ActionEvent e)
1699   {
1700     new Preferences();
1701   }
1702
1703   /**
1704    * Prompts the user to choose a file and then saves the Jalview state as a
1705    * Jalview project file
1706    */
1707   @Override
1708   public void saveState_actionPerformed()
1709   {
1710     saveState_actionPerformed(false);
1711   }
1712
1713   public void saveState_actionPerformed(boolean saveAs)
1714   {
1715     java.io.File projectFile = getProjectFile();
1716     // autoSave indicates we already have a file and don't need to ask
1717     boolean autoSave = projectFile != null && !saveAs
1718             && BackupFiles.getEnabled();
1719
1720     // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1721     // saveAs="+saveAs+", Backups
1722     // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1723
1724     boolean approveSave = false;
1725     if (!autoSave)
1726     {
1727       JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1728               "Jalview Project");
1729
1730       chooser.setFileView(new JalviewFileView());
1731       chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1732
1733       int value = chooser.showSaveDialog(this);
1734
1735       if (value == JalviewFileChooser.APPROVE_OPTION)
1736       {
1737         projectFile = chooser.getSelectedFile();
1738         setProjectFile(projectFile);
1739         approveSave = true;
1740       }
1741     }
1742
1743     if (approveSave || autoSave)
1744     {
1745       final Desktop me = this;
1746       final java.io.File chosenFile = projectFile;
1747       new Thread(new Runnable()
1748       {
1749         @Override
1750         public void run()
1751         {
1752           // TODO: refactor to Jalview desktop session controller action.
1753           setProgressBar(MessageManager.formatMessage(
1754                   "label.saving_jalview_project", new Object[]
1755                   { chosenFile.getName() }), chosenFile.hashCode());
1756           jalview.bin.Cache.setProperty("LAST_DIRECTORY",
1757                   chosenFile.getParent());
1758           // TODO catch and handle errors for savestate
1759           // TODO prevent user from messing with the Desktop whilst we're saving
1760           try
1761           {
1762                 boolean doBackup = BackupFiles.getEnabled();
1763             BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile) : null;
1764
1765             new Jalview2XML().saveState(doBackup ? backupfiles.getTempFile() : chosenFile);
1766
1767             if (doBackup)
1768             {
1769               backupfiles.setWriteSuccess(true);
1770               backupfiles.rollBackupsAndRenameTempFile();
1771             }
1772           } catch (OutOfMemoryError oom)
1773           {
1774             new OOMWarning("Whilst saving current state to "
1775                     + chosenFile.getName(), oom);
1776           } catch (Exception ex)
1777           {
1778             Cache.log.error("Problems whilst trying to save to "
1779                     + chosenFile.getName(), ex);
1780             JvOptionPane.showMessageDialog(me,
1781                     MessageManager.formatMessage(
1782                             "label.error_whilst_saving_current_state_to",
1783                             new Object[]
1784                             { chosenFile.getName() }),
1785                     MessageManager.getString("label.couldnt_save_project"),
1786                     JvOptionPane.WARNING_MESSAGE);
1787           }
1788           setProgressBar(null, chosenFile.hashCode());
1789         }
1790       }).start();
1791       }
1792   }
1793
1794   @Override
1795   public void saveAsState_actionPerformed(ActionEvent e)
1796   {
1797     saveState_actionPerformed(true);
1798   }
1799
1800   protected void setProjectFile(File choice)
1801   {
1802     this.projectFile = choice;
1803   }
1804
1805   public File getProjectFile()
1806   {
1807     return this.projectFile;
1808   }
1809
1810   /**
1811    * Shows a file chooser dialog and tries to read in the selected file as a
1812    * Jalview project
1813    */
1814   @Override
1815   public void loadState_actionPerformed()
1816   {
1817     final String[] suffix = new String[] { "jvp", "jar" };
1818     final String[] desc = new String[] { "Jalview Project",
1819         "Jalview Project (old)" };
1820     JalviewFileChooser chooser = new JalviewFileChooser(
1821             Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1822             "Jalview Project", true, true); // last two booleans: allFiles,
1823                                             // allowBackupFiles
1824     chooser.setFileView(new JalviewFileView());
1825     chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1826     chooser.setResponseHandler(0, new Runnable()
1827     {
1828       @Override
1829       public void run()
1830       {
1831         File selectedFile = chooser.getSelectedFile();
1832         setProjectFile(selectedFile);
1833         String choice = selectedFile.getAbsolutePath();
1834         Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1835         new Thread(new Runnable()
1836         {
1837           @Override
1838           public void run()
1839           {
1840                 try 
1841             {
1842               new Jalview2XML().loadJalviewAlign(choice);
1843             } catch (OutOfMemoryError oom)
1844                 {
1845                   new OOMWarning("Whilst loading project from " + choice, oom);
1846                 } catch (Exception ex)
1847                 {
1848                   Cache.log.error(
1849                           "Problems whilst loading project from " + choice, ex);
1850               JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
1851                           MessageManager.formatMessage(
1852                                   "label.error_whilst_loading_project_from",
1853                                 new Object[]
1854                                     { choice }),
1855                           MessageManager.getString("label.couldnt_load_project"),
1856                           JvOptionPane.WARNING_MESSAGE);
1857                 }
1858           }
1859         }).start();
1860       }
1861     });
1862     
1863     chooser.showOpenDialog(this);
1864   }
1865
1866   @Override
1867   public void inputSequence_actionPerformed(ActionEvent e)
1868   {
1869     new SequenceFetcher(this);
1870   }
1871
1872   JPanel progressPanel;
1873
1874   ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1875
1876   public void startLoading(final Object fileName)
1877   {
1878     if (fileLoadingCount == 0)
1879     {
1880       fileLoadingPanels.add(addProgressPanel(MessageManager
1881               .formatMessage("label.loading_file", new Object[]
1882               { fileName })));
1883     }
1884     fileLoadingCount++;
1885   }
1886
1887   private JPanel addProgressPanel(String string)
1888   {
1889     if (progressPanel == null)
1890     {
1891       progressPanel = new JPanel(new GridLayout(1, 1));
1892       totalProgressCount = 0;
1893       getInstance().getContentPane().add(progressPanel, BorderLayout.SOUTH);
1894     }
1895     JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1896     JProgressBar progressBar = new JProgressBar();
1897     progressBar.setIndeterminate(true);
1898
1899     thisprogress.add(new JLabel(string), BorderLayout.WEST);
1900
1901     thisprogress.add(progressBar, BorderLayout.CENTER);
1902     progressPanel.add(thisprogress);
1903     ((GridLayout) progressPanel.getLayout()).setRows(
1904             ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1905     ++totalProgressCount;
1906     getInstance().validate();
1907     return thisprogress;
1908   }
1909
1910   int totalProgressCount = 0;
1911
1912   private void removeProgressPanel(JPanel progbar)
1913   {
1914     if (progressPanel != null)
1915     {
1916       synchronized (progressPanel)
1917       {
1918         progressPanel.remove(progbar);
1919         GridLayout gl = (GridLayout) progressPanel.getLayout();
1920         gl.setRows(gl.getRows() - 1);
1921         if (--totalProgressCount < 1)
1922         {
1923           this.getContentPane().remove(progressPanel);
1924           progressPanel = null;
1925         }
1926       }
1927     }
1928     validate();
1929   }
1930
1931   public void stopLoading()
1932   {
1933     fileLoadingCount--;
1934     if (fileLoadingCount < 1)
1935     {
1936       while (fileLoadingPanels.size() > 0)
1937       {
1938         removeProgressPanel(fileLoadingPanels.remove(0));
1939       }
1940       fileLoadingPanels.clear();
1941       fileLoadingCount = 0;
1942     }
1943     validate();
1944   }
1945
1946   public static int getViewCount(String alignmentId)
1947   {
1948     AlignmentViewport[] aps = getViewports(alignmentId);
1949     return (aps == null) ? 0 : aps.length;
1950   }
1951
1952   /**
1953    * 
1954    * @param alignmentId
1955    *          - if null, all sets are returned
1956    * @return all AlignmentPanels concerning the alignmentId sequence set
1957    */
1958   public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1959   {
1960     if (Desktop.getDesktopPane() == null)
1961     {
1962       // no frames created and in headless mode
1963       // TODO: verify that frames are recoverable when in headless mode
1964       return null;
1965     }
1966     List<AlignmentPanel> aps = new ArrayList<>();
1967     AlignFrame[] frames = getAlignFrames();
1968     if (frames == null)
1969     {
1970       return null;
1971     }
1972     for (AlignFrame af : frames)
1973     {
1974       for (AlignmentPanel ap : af.alignPanels)
1975       {
1976         if (alignmentId == null
1977                 || alignmentId.equals(ap.av.getSequenceSetId()))
1978         {
1979           aps.add(ap);
1980         }
1981       }
1982     }
1983     if (aps.size() == 0)
1984     {
1985       return null;
1986     }
1987     AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
1988     return vap;
1989   }
1990
1991   /**
1992    * get all the viewports on an alignment.
1993    * 
1994    * @param sequenceSetId
1995    *          unique alignment id (may be null - all viewports returned in that
1996    *          case)
1997    * @return all viewports on the alignment bound to sequenceSetId
1998    */
1999   public static AlignmentViewport[] getViewports(String sequenceSetId)
2000   {
2001     List<AlignmentViewport> viewp = new ArrayList<>();
2002     if (getDesktopPane() != null)
2003     {
2004       AlignFrame[] frames = Desktop.getAlignFrames();
2005
2006       for (AlignFrame afr : frames)
2007       {
2008         if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2009                 .equals(sequenceSetId))
2010         {
2011           if (afr.alignPanels != null)
2012           {
2013             for (AlignmentPanel ap : afr.alignPanels)
2014             {
2015               if (sequenceSetId == null
2016                       || sequenceSetId.equals(ap.av.getSequenceSetId()))
2017               {
2018                 viewp.add(ap.av);
2019               }
2020             }
2021           }
2022           else
2023           {
2024             viewp.add(afr.getViewport());
2025           }
2026         }
2027       }
2028       if (viewp.size() > 0)
2029       {
2030         return viewp.toArray(new AlignmentViewport[viewp.size()]);
2031       }
2032     }
2033     return null;
2034   }
2035
2036   /**
2037    * Explode the views in the given frame into separate AlignFrame
2038    * 
2039    * @param af
2040    */
2041   public static void explodeViews(AlignFrame af)
2042   {
2043     int size = af.alignPanels.size();
2044     if (size < 2)
2045     {
2046       return;
2047     }
2048
2049     for (int i = 0; i < size; i++)
2050     {
2051       AlignmentPanel ap = af.alignPanels.get(i);
2052       AlignFrame newaf = new AlignFrame(ap);
2053
2054       /*
2055        * Restore the view's last exploded frame geometry if known. Multiple
2056        * views from one exploded frame share and restore the same (frame)
2057        * position and size.
2058        */
2059       Rectangle geometry = ap.av.getExplodedGeometry();
2060       if (geometry != null)
2061       {
2062         newaf.setBounds(geometry);
2063       }
2064
2065       ap.av.setGatherViewsHere(false);
2066
2067       addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2068               AlignFrame.DEFAULT_HEIGHT);
2069     }
2070
2071     af.alignPanels.clear();
2072     af.closeMenuItem_actionPerformed(true);
2073
2074   }
2075
2076   /**
2077    * Gather expanded views (separate AlignFrame's) with the same sequence set
2078    * identifier back in to this frame as additional views, and close the expanded
2079    * views. Note the expanded frames may themselves have multiple views. We take
2080    * the lot.
2081    * 
2082    * @param source
2083    */
2084   public void gatherViews(AlignFrame source)
2085   {
2086     source.viewport.setGatherViewsHere(true);
2087     source.viewport.setExplodedGeometry(source.getBounds());
2088     JInternalFrame[] frames = getDesktopPane().getAllFrames();
2089     String viewId = source.viewport.getSequenceSetId();
2090
2091     for (int t = 0; t < frames.length; t++)
2092     {
2093       if (frames[t] instanceof AlignFrame && frames[t] != source)
2094       {
2095         AlignFrame af = (AlignFrame) frames[t];
2096         boolean gatherThis = false;
2097         for (int a = 0; a < af.alignPanels.size(); a++)
2098         {
2099           AlignmentPanel ap = af.alignPanels.get(a);
2100           if (viewId.equals(ap.av.getSequenceSetId()))
2101           {
2102             gatherThis = true;
2103             ap.av.setGatherViewsHere(false);
2104             ap.av.setExplodedGeometry(af.getBounds());
2105             source.addAlignmentPanel(ap, false);
2106           }
2107         }
2108
2109         if (gatherThis)
2110         {
2111           af.alignPanels.clear();
2112           af.closeMenuItem_actionPerformed(true);
2113         }
2114       }
2115     }
2116
2117   }
2118
2119   jalview.gui.VamsasApplication v_client = null;
2120
2121   @Override
2122   public void vamsasImport_actionPerformed(ActionEvent e)
2123   {
2124     // TODO: JAL-3048 not needed for Jalview-JS
2125
2126     if (v_client == null)
2127     {
2128       // Load and try to start a session.
2129       JalviewFileChooser chooser = new JalviewFileChooser(
2130               jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
2131
2132       chooser.setFileView(new JalviewFileView());
2133       chooser.setDialogTitle(
2134               MessageManager.getString("label.open_saved_vamsas_session"));
2135       chooser.setToolTipText(MessageManager.getString(
2136               "label.select_vamsas_session_opened_as_new_vamsas_session"));
2137
2138       int value = chooser.showOpenDialog(this);
2139
2140       if (value == JalviewFileChooser.APPROVE_OPTION)
2141       {
2142         String fle = chooser.getSelectedFile().toString();
2143         if (!vamsasImport(chooser.getSelectedFile()))
2144         {
2145           JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
2146                   MessageManager.formatMessage(
2147                           "label.couldnt_import_as_vamsas_session",
2148                           new Object[]
2149                           { fle }),
2150                   MessageManager
2151                           .getString("label.vamsas_document_import_failed"),
2152                   JvOptionPane.ERROR_MESSAGE);
2153         }
2154       }
2155     }
2156     else
2157     {
2158       jalview.bin.Cache.log.error(
2159               "Implementation error - load session from a running session is not supported.");
2160     }
2161   }
2162
2163   /**
2164    * import file into a new vamsas session (uses jalview.gui.VamsasApplication)
2165    * 
2166    * @param file
2167    * @return true if import was a success and a session was started.
2168    */
2169   public boolean vamsasImport(URL url)
2170   {
2171     // TODO: create progress bar
2172     if (v_client != null)
2173     {
2174
2175       jalview.bin.Cache.log.error(
2176               "Implementation error - load session from a running session is not supported.");
2177       return false;
2178     }
2179
2180     try
2181     {
2182       // copy the URL content to a temporary local file
2183       // TODO: be a bit cleverer here with nio (?!)
2184       File file = File.createTempFile("vdocfromurl", ".vdj");
2185       FileOutputStream fos = new FileOutputStream(file);
2186       BufferedInputStream bis = new BufferedInputStream(url.openStream());
2187       byte[] buffer = new byte[2048];
2188       int ln;
2189       while ((ln = bis.read(buffer)) > -1)
2190       {
2191         fos.write(buffer, 0, ln);
2192       }
2193       bis.close();
2194       fos.close();
2195       v_client = new jalview.gui.VamsasApplication(this, file,
2196               url.toExternalForm());
2197     } catch (Exception ex)
2198     {
2199       jalview.bin.Cache.log.error(
2200               "Failed to create new vamsas session from contents of URL "
2201                       + url,
2202               ex);
2203       return false;
2204     }
2205     setupVamsasConnectedGui();
2206     v_client.initial_update(); // TODO: thread ?
2207     return v_client.inSession();
2208   }
2209
2210   /**
2211    * import file into a new vamsas session (uses jalview.gui.VamsasApplication)
2212    * 
2213    * @param file
2214    * @return true if import was a success and a session was started.
2215    */
2216   public boolean vamsasImport(File file)
2217   {
2218     if (v_client != null)
2219     {
2220
2221       jalview.bin.Cache.log.error(
2222               "Implementation error - load session from a running session is not supported.");
2223       return false;
2224     }
2225
2226     setProgressBar(MessageManager.formatMessage(
2227             "status.importing_vamsas_session_from", new Object[]
2228             { file.getName() }), file.hashCode());
2229     try
2230     {
2231       v_client = new jalview.gui.VamsasApplication(this, file, null);
2232     } catch (Exception ex)
2233     {
2234       setProgressBar(MessageManager.formatMessage(
2235               "status.importing_vamsas_session_from", new Object[]
2236               { file.getName() }), file.hashCode());
2237       jalview.bin.Cache.log.error(
2238               "New vamsas session from existing session file failed:", ex);
2239       return false;
2240     }
2241     setupVamsasConnectedGui();
2242     v_client.initial_update(); // TODO: thread ?
2243     setProgressBar(MessageManager.formatMessage(
2244             "status.importing_vamsas_session_from", new Object[]
2245             { file.getName() }), file.hashCode());
2246     return v_client.inSession();
2247   }
2248
2249   public boolean joinVamsasSession(String mysesid)
2250   {
2251     if (v_client != null)
2252     {
2253       throw new Error(MessageManager
2254               .getString("error.try_join_vamsas_session_another"));
2255     }
2256     if (mysesid == null)
2257     {
2258       throw new Error(
2259               MessageManager.getString("error.invalid_vamsas_session_id"));
2260     }
2261     v_client = new VamsasApplication(this, mysesid);
2262     setupVamsasConnectedGui();
2263     v_client.initial_update();
2264     return (v_client.inSession());
2265   }
2266
2267   @Override
2268   public void vamsasStart_actionPerformed(ActionEvent e)
2269   {
2270     if (v_client == null)
2271     {
2272       // Start a session.
2273       // we just start a default session for moment.
2274       /*
2275        * JalviewFileChooser chooser = new JalviewFileChooser(jalview.bin.Cache.
2276        * getProperty("LAST_DIRECTORY"));
2277        * 
2278        * chooser.setFileView(new JalviewFileView());
2279        * chooser.setDialogTitle("Load Vamsas file");
2280        * chooser.setToolTipText("Import");
2281        * 
2282        * int value = chooser.showOpenDialog(this);
2283        * 
2284        * if (value == JalviewFileChooser.APPROVE_OPTION) { v_client = new
2285        * jalview.gui.VamsasApplication(this, chooser.getSelectedFile());
2286        */
2287       v_client = new VamsasApplication(this);
2288       setupVamsasConnectedGui();
2289       v_client.initial_update(); // TODO: thread ?
2290     }
2291     else
2292     {
2293       // store current data in session.
2294       v_client.push_update(); // TODO: thread
2295     }
2296   }
2297
2298   protected void setupVamsasConnectedGui()
2299   {
2300     vamsasStart.setText(MessageManager.getString("label.session_update"));
2301     vamsasSave.setVisible(true);
2302     vamsasStop.setVisible(true);
2303     vamsasImport.setVisible(false); // Document import to existing session is
2304     // not possible for vamsas-client-1.0.
2305   }
2306
2307   protected void setupVamsasDisconnectedGui()
2308   {
2309     vamsasSave.setVisible(false);
2310     vamsasStop.setVisible(false);
2311     vamsasImport.setVisible(true);
2312     vamsasStart
2313             .setText(MessageManager.getString("label.new_vamsas_session"));
2314   }
2315
2316   @Override
2317   public void vamsasStop_actionPerformed(ActionEvent e)
2318   {
2319     if (v_client != null)
2320     {
2321       v_client.end_session();
2322       v_client = null;
2323       setupVamsasDisconnectedGui();
2324     }
2325   }
2326
2327   protected void buildVamsasStMenu()
2328   {
2329     if (v_client == null)
2330     {
2331       String[] sess = null;
2332       try
2333       {
2334         sess = VamsasApplication.getSessionList();
2335       } catch (Exception e)
2336       {
2337         jalview.bin.Cache.log.warn("Problem getting current sessions list.",
2338                 e);
2339         sess = null;
2340       }
2341       if (sess != null)
2342       {
2343         jalview.bin.Cache.log.debug(
2344                 "Got current sessions list: " + sess.length + " entries.");
2345         VamsasStMenu.removeAll();
2346         for (int i = 0; i < sess.length; i++)
2347         {
2348           JMenuItem sessit = new JMenuItem();
2349           sessit.setText(sess[i]);
2350           sessit.setToolTipText(MessageManager
2351                   .formatMessage("label.connect_to_session", new Object[]
2352                   { sess[i] }));
2353           final Desktop dsktp = this;
2354           final String mysesid = sess[i];
2355           sessit.addActionListener(new ActionListener()
2356           {
2357
2358             @Override
2359             public void actionPerformed(ActionEvent e)
2360             {
2361               if (dsktp.v_client == null)
2362               {
2363                 Thread rthr = new Thread(new Runnable()
2364                 {
2365
2366                   @Override
2367                   public void run()
2368                   {
2369                     dsktp.v_client = new VamsasApplication(dsktp, mysesid);
2370                     dsktp.setupVamsasConnectedGui();
2371                     dsktp.v_client.initial_update();
2372                   }
2373
2374                 });
2375                 rthr.start();
2376               }
2377             };
2378           });
2379           VamsasStMenu.add(sessit);
2380         }
2381         // don't show an empty menu.
2382         VamsasStMenu.setVisible(sess.length > 0);
2383
2384       }
2385       else
2386       {
2387         jalview.bin.Cache.log.debug("No current vamsas sessions.");
2388         VamsasStMenu.removeAll();
2389         VamsasStMenu.setVisible(false);
2390       }
2391     }
2392     else
2393     {
2394       // Not interested in the content. Just hide ourselves.
2395       VamsasStMenu.setVisible(false);
2396     }
2397   }
2398
2399   @Override
2400   public void vamsasSave_actionPerformed(ActionEvent e)
2401   {
2402     // TODO: JAL-3048 not needed for Jalview-JS
2403
2404     if (v_client != null)
2405     {
2406       // TODO: VAMSAS DOCUMENT EXTENSION is VDJ
2407       JalviewFileChooser chooser = new JalviewFileChooser("vdj",
2408               "Vamsas Document");
2409
2410       chooser.setFileView(new JalviewFileView());
2411       chooser.setDialogTitle(MessageManager
2412               .getString("label.save_vamsas_document_archive"));
2413
2414       int value = chooser.showSaveDialog(this);
2415
2416       if (value == JalviewFileChooser.APPROVE_OPTION)
2417       {
2418         java.io.File choice = chooser.getSelectedFile();
2419         JPanel progpanel = addProgressPanel(MessageManager
2420                 .formatMessage("label.saving_vamsas_doc", new Object[]
2421                 { choice.getName() }));
2422         Cache.setProperty("LAST_DIRECTORY", choice.getParent());
2423         String warnmsg = null;
2424         String warnttl = null;
2425         try
2426         {
2427           v_client.vclient.storeDocument(choice);
2428         } catch (Error ex)
2429         {
2430           warnttl = "Serious Problem saving Vamsas Document";
2431           warnmsg = ex.toString();
2432           jalview.bin.Cache.log
2433                   .error("Error Whilst saving document to " + choice, ex);
2434
2435         } catch (Exception ex)
2436         {
2437           warnttl = "Problem saving Vamsas Document.";
2438           warnmsg = ex.toString();
2439           jalview.bin.Cache.log.warn(
2440                   "Exception Whilst saving document to " + choice, ex);
2441
2442         }
2443         removeProgressPanel(progpanel);
2444         if (warnmsg != null)
2445         {
2446           JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
2447
2448                   warnmsg, warnttl, JvOptionPane.ERROR_MESSAGE);
2449         }
2450       }
2451     }
2452   }
2453
2454   JPanel vamUpdate = null;
2455
2456   /**
2457    * hide vamsas user gui bits when a vamsas document event is being handled.
2458    * 
2459    * @param b
2460    *          true to hide gui, false to reveal gui
2461    */
2462   public void setVamsasUpdate(boolean b)
2463   {
2464     Cache.log.debug("Setting gui for Vamsas update "
2465             + (b ? "in progress" : "finished"));
2466
2467     if (vamUpdate != null)
2468     {
2469       this.removeProgressPanel(vamUpdate);
2470     }
2471     if (b)
2472     {
2473       vamUpdate = this.addProgressPanel(
2474               MessageManager.getString("label.updating_vamsas_session"));
2475     }
2476     vamsasStart.setVisible(!b);
2477     vamsasStop.setVisible(!b);
2478     vamsasSave.setVisible(!b);
2479   }
2480
2481   public JInternalFrame[] getAllFrames()
2482   {
2483     return getDesktopPane().getAllFrames();
2484   }
2485
2486   /**
2487    * Checks the given url to see if it gives a response indicating that the user
2488    * should be informed of a new questionnaire.
2489    * 
2490    * @param url
2491    */
2492   public void checkForQuestionnaire(String url)
2493   {
2494     UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2495     // javax.swing.SwingUtilities.invokeLater(jvq);
2496     new Thread(jvq).start();
2497   }
2498
2499   public void checkURLLinks()
2500   {
2501     // Thread off the URL link checker
2502     addDialogThread(new Runnable()
2503     {
2504       @Override
2505       public void run()
2506       {
2507         if (Cache.getDefault("CHECKURLLINKS", true))
2508         {
2509           // check what the actual links are - if it's just the default don't
2510           // bother with the warning
2511           List<String> links = Preferences.sequenceUrlLinks
2512                   .getLinksForMenu();
2513
2514           // only need to check links if there is one with a
2515           // SEQUENCE_ID which is not the default EMBL_EBI link
2516           ListIterator<String> li = links.listIterator();
2517           boolean check = false;
2518           List<JLabel> urls = new ArrayList<>();
2519           while (li.hasNext())
2520           {
2521             String link = li.next();
2522             if (link.contains(UrlConstants.SEQUENCE_ID)
2523                     && !UrlConstants.isDefaultString(link))
2524             {
2525               check = true;
2526               int barPos = link.indexOf("|");
2527               String urlMsg = barPos == -1 ? link
2528                       : link.substring(0, barPos) + ": "
2529                               + link.substring(barPos + 1);
2530               urls.add(new JLabel(urlMsg));
2531             }
2532           }
2533           if (!check)
2534           {
2535             return;
2536           }
2537
2538           // ask user to check in case URL links use old style tokens
2539           // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2540           JPanel msgPanel = new JPanel();
2541           msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2542           msgPanel.add(Box.createVerticalGlue());
2543           JLabel msg = new JLabel(MessageManager
2544                   .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2545           JLabel msg2 = new JLabel(MessageManager
2546                   .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2547           msgPanel.add(msg);
2548           for (JLabel url : urls)
2549           {
2550             msgPanel.add(url);
2551           }
2552           msgPanel.add(msg2);
2553
2554           final JCheckBox jcb = new JCheckBox(
2555                   MessageManager.getString("label.do_not_display_again"));
2556           jcb.addActionListener(new ActionListener()
2557           {
2558             @Override
2559             public void actionPerformed(ActionEvent e)
2560             {
2561               // update Cache settings for "don't show this again"
2562               boolean showWarningAgain = !jcb.isSelected();
2563               Cache.setProperty("CHECKURLLINKS",
2564                       Boolean.valueOf(showWarningAgain).toString());
2565             }
2566           });
2567           msgPanel.add(jcb);
2568
2569           JvOptionPane.showMessageDialog(Desktop.getDesktopPane(), msgPanel,
2570                   MessageManager
2571                           .getString("label.SEQUENCE_ID_no_longer_used"),
2572                   JvOptionPane.WARNING_MESSAGE);
2573         }
2574       }
2575     });
2576   }
2577
2578   /**
2579    * Proxy class for JDesktopPane which optionally displays the current memory
2580    * usage and highlights the desktop area with a red bar if free memory runs low.
2581    * 
2582    * @author AMW
2583    */
2584   public class MyDesktopPane extends JDesktopPane
2585           implements Runnable
2586   {
2587     private static final float ONE_MB = 1048576f;
2588
2589     boolean showMemoryUsage = false;
2590
2591     Runtime runtime;
2592
2593     java.text.NumberFormat df;
2594
2595     float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2596             percentUsage;
2597
2598     public MyDesktopPane(boolean showMemoryUsage)
2599     {
2600       showMemoryUsage(showMemoryUsage);
2601     }
2602
2603     public void showMemoryUsage(boolean showMemory)
2604     {
2605       this.showMemoryUsage = showMemory;
2606       if (showMemory)
2607       {
2608         Thread worker = new Thread(this);
2609         worker.start();
2610       }
2611       repaint();
2612     }
2613
2614     public boolean isShowMemoryUsage()
2615     {
2616       return showMemoryUsage;
2617     }
2618
2619     @Override
2620     public void run()
2621     {
2622       df = java.text.NumberFormat.getNumberInstance();
2623       df.setMaximumFractionDigits(2);
2624       runtime = Runtime.getRuntime();
2625
2626       while (showMemoryUsage)
2627       {
2628         try
2629         {
2630           maxMemory = runtime.maxMemory() / ONE_MB;
2631           allocatedMemory = runtime.totalMemory() / ONE_MB;
2632           freeMemory = runtime.freeMemory() / ONE_MB;
2633           totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2634
2635           percentUsage = (totalFreeMemory / maxMemory) * 100;
2636
2637           // if (percentUsage < 20)
2638           {
2639             // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2640             // Color.red);
2641             // instance.set.setBorder(border1);
2642           }
2643           repaint();
2644           // sleep after showing usage
2645           Thread.sleep(3000);
2646         } catch (Exception ex)
2647         {
2648           ex.printStackTrace();
2649         }
2650       }
2651     }
2652
2653     @Override
2654     public void paintComponent(Graphics g)
2655     {
2656       if (showMemoryUsage && g != null && df != null)
2657       {
2658         if (percentUsage < 20)
2659         {
2660           g.setColor(Color.red);
2661         }
2662         FontMetrics fm = g.getFontMetrics();
2663         if (fm != null)
2664         {
2665           g.drawString(MessageManager.formatMessage("label.memory_stats",
2666                   new Object[]
2667                   { df.format(totalFreeMemory), df.format(maxMemory),
2668                       df.format(percentUsage) }),
2669                   10, getHeight() - fm.getHeight());
2670         }
2671       }
2672     }
2673   }
2674
2675   /**
2676    * Accessor method to quickly get all the AlignmentFrames loaded.
2677    * 
2678    * @return an array of AlignFrame, or null if none found
2679    */
2680   public static AlignFrame[] getAlignFrames()
2681   {
2682     if (Jalview.isHeadlessMode())
2683     {
2684       // Desktop.getDesktop() is null in headless mode
2685       return new AlignFrame[] { Jalview.getCurrentAlignFrame() };
2686     }
2687
2688     JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
2689
2690     if (frames == null)
2691     {
2692       return null;
2693     }
2694     List<AlignFrame> avp = new ArrayList<>();
2695     // REVERSE ORDER
2696     for (int i = frames.length - 1; i > -1; i--)
2697     {
2698       if (frames[i] instanceof AlignFrame)
2699       {
2700         avp.add((AlignFrame) frames[i]);
2701       }
2702       else if (frames[i] instanceof SplitFrame)
2703       {
2704         /*
2705          * Also check for a split frame containing an AlignFrame
2706          */
2707         GSplitFrame sf = (GSplitFrame) frames[i];
2708         if (sf.getTopFrame() instanceof AlignFrame)
2709         {
2710           avp.add((AlignFrame) sf.getTopFrame());
2711         }
2712         if (sf.getBottomFrame() instanceof AlignFrame)
2713         {
2714           avp.add((AlignFrame) sf.getBottomFrame());
2715         }
2716       }
2717     }
2718     if (avp.size() == 0)
2719     {
2720       return null;
2721     }
2722     AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2723     return afs;
2724   }
2725
2726   /**
2727    * Returns an array of any AppJmol frames in the Desktop (or null if none).
2728    * 
2729    * @return
2730    */
2731   public GStructureViewer[] getJmols()
2732   {
2733     JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
2734
2735     if (frames == null)
2736     {
2737       return null;
2738     }
2739     List<GStructureViewer> avp = new ArrayList<>();
2740     // REVERSE ORDER
2741     for (int i = frames.length - 1; i > -1; i--)
2742     {
2743       if (frames[i] instanceof AppJmol)
2744       {
2745         GStructureViewer af = (GStructureViewer) frames[i];
2746         avp.add(af);
2747       }
2748     }
2749     if (avp.size() == 0)
2750     {
2751       return null;
2752     }
2753     GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2754     return afs;
2755   }
2756
2757   /**
2758    * Add Groovy Support to Jalview
2759    */
2760   @Override
2761   public void groovyShell_actionPerformed()
2762   {
2763     try
2764     {
2765       openGroovyConsole();
2766     } catch (Exception ex)
2767     {
2768       jalview.bin.Cache.log.error("Groovy Shell Creation failed.", ex);
2769       JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
2770
2771               MessageManager.getString("label.couldnt_create_groovy_shell"),
2772               MessageManager.getString("label.groovy_support_failed"),
2773               JvOptionPane.ERROR_MESSAGE);
2774     }
2775   }
2776
2777   /**
2778    * Open the Groovy console
2779    */
2780   private void openGroovyConsole()
2781   {
2782     if (groovyConsole == null)
2783     {
2784       groovyConsole = new groovy.ui.Console();
2785       groovyConsole.setVariable("Jalview", this);
2786       groovyConsole.run();
2787
2788       /*
2789        * We allow only one console at a time, so that AlignFrame menu option
2790        * 'Calculate | Run Groovy script' is unambiguous.
2791        * Disable 'Groovy Console', and enable 'Run script', when the console is 
2792        * opened, and the reverse when it is closed
2793        */
2794       Window window = (Window) groovyConsole.getFrame();
2795       window.addWindowListener(new WindowAdapter()
2796       {
2797         @Override
2798         public void windowClosed(WindowEvent e)
2799         {
2800           /*
2801            * rebind CMD-Q from Groovy Console to Jalview Quit
2802            */
2803           addQuitHandler();
2804           enableExecuteGroovy(false);
2805         }
2806       });
2807     }
2808
2809     /*
2810      * show Groovy console window (after close and reopen)
2811      */
2812     ((Window) groovyConsole.getFrame()).setVisible(true);
2813
2814     /*
2815      * if we got this far, enable 'Run Groovy' in AlignFrame menus
2816      * and disable opening a second console
2817      */
2818     enableExecuteGroovy(true);
2819   }
2820
2821   /**
2822    * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this binding
2823    * when opened
2824    */
2825   protected void addQuitHandler()
2826   {
2827     getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
2828             .put(KeyStroke.getKeyStroke(KeyEvent.VK_Q,
2829                     Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
2830                     "Quit");
2831     getRootPane().getActionMap().put("Quit", new AbstractAction()
2832     {
2833       @Override
2834       public void actionPerformed(ActionEvent e)
2835       {
2836         quit();
2837       }
2838     });
2839   }
2840
2841   /**
2842    * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2843    * 
2844    * @param enabled
2845    *          true if Groovy console is open
2846    */
2847   public void enableExecuteGroovy(boolean enabled)
2848   {
2849     /*
2850      * disable opening a second Groovy console
2851      * (or re-enable when the console is closed)
2852      */
2853     groovyShell.setEnabled(!enabled);
2854
2855     AlignFrame[] alignFrames = getAlignFrames();
2856     if (alignFrames != null)
2857     {
2858       for (AlignFrame af : alignFrames)
2859       {
2860         af.setGroovyEnabled(enabled);
2861       }
2862     }
2863   }
2864
2865   /**
2866    * Progress bars managed by the IProgressIndicator method.
2867    */
2868   private Hashtable<Long, JPanel> progressBars;
2869
2870   private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2871
2872   /*
2873    * (non-Javadoc)
2874    * 
2875    * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2876    */
2877   @Override
2878   public void setProgressBar(String message, long id)
2879   {
2880             Platform.timeCheck("Desktop " + message, Platform.TIME_MARK);     
2881
2882     if (progressBars == null)
2883     {
2884       progressBars = new Hashtable<>();
2885       progressBarHandlers = new Hashtable<>();
2886     }
2887
2888     if (progressBars.get(new Long(id)) != null)
2889     {
2890       JPanel panel = progressBars.remove(new Long(id));
2891       if (progressBarHandlers.contains(new Long(id)))
2892       {
2893         progressBarHandlers.remove(new Long(id));
2894       }
2895       removeProgressPanel(panel);
2896     }
2897     else
2898     {
2899       progressBars.put(new Long(id), addProgressPanel(message));
2900     }
2901   }
2902
2903   /*
2904    * (non-Javadoc)
2905    * 
2906    * @see jalview.gui.IProgressIndicator#registerHandler(long,
2907    * jalview.gui.IProgressIndicatorHandler)
2908    */
2909   @Override
2910   public void registerHandler(final long id,
2911           final IProgressIndicatorHandler handler)
2912   {
2913     if (progressBarHandlers == null
2914             || !progressBars.containsKey(new Long(id)))
2915     {
2916       throw new Error(MessageManager.getString(
2917               "error.call_setprogressbar_before_registering_handler"));
2918     }
2919     progressBarHandlers.put(new Long(id), handler);
2920     final JPanel progressPanel = progressBars.get(new Long(id));
2921     if (handler.canCancel())
2922     {
2923       JButton cancel = new JButton(
2924               MessageManager.getString("action.cancel"));
2925       final IProgressIndicator us = this;
2926       cancel.addActionListener(new ActionListener()
2927       {
2928
2929         @Override
2930         public void actionPerformed(ActionEvent e)
2931         {
2932           handler.cancelActivity(id);
2933           us.setProgressBar(MessageManager
2934                   .formatMessage("label.cancelled_params", new Object[]
2935                   { ((JLabel) progressPanel.getComponent(0)).getText() }),
2936                   id);
2937         }
2938       });
2939       progressPanel.add(cancel, BorderLayout.EAST);
2940     }
2941   }
2942
2943   /**
2944    * 
2945    * @return true if any progress bars are still active
2946    */
2947   @Override
2948   public boolean operationInProgress()
2949   {
2950     if (progressBars != null && progressBars.size() > 0)
2951     {
2952       return true;
2953     }
2954     return false;
2955   }
2956
2957   /**
2958    * This will return the first AlignFrame holding the given viewport instance. It
2959    * will break if there are more than one AlignFrames viewing a particular av.
2960    * 
2961    * @param viewport
2962    * @return alignFrame for viewport
2963    */
2964   public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2965   {
2966     if (getDesktopPane() != null)
2967     {
2968       AlignmentPanel[] aps = getAlignmentPanels(
2969               viewport.getSequenceSetId());
2970       for (int panel = 0; aps != null && panel < aps.length; panel++)
2971       {
2972         if (aps[panel] != null && aps[panel].av == viewport)
2973         {
2974           return aps[panel].alignFrame;
2975         }
2976       }
2977     }
2978     return null;
2979   }
2980
2981   public VamsasApplication getVamsasApplication()
2982   {
2983     return v_client;
2984
2985   }
2986
2987   /**
2988    * flag set if jalview GUI is being operated programmatically
2989    */
2990   private boolean inBatchMode = false;
2991
2992   /**
2993    * check if jalview GUI is being operated programmatically
2994    * 
2995    * @return inBatchMode
2996    */
2997   public boolean isInBatchMode()
2998   {
2999     return inBatchMode;
3000   }
3001
3002   /**
3003    * set flag if jalview GUI is being operated programmatically
3004    * 
3005    * @param inBatchMode
3006    */
3007   public void setInBatchMode(boolean inBatchMode)
3008   {
3009     this.inBatchMode = inBatchMode;
3010   }
3011
3012   public void startServiceDiscovery()
3013   {
3014     startServiceDiscovery(false);
3015   }
3016
3017   public void startServiceDiscovery(boolean blocking)
3018   {
3019     boolean alive = true;
3020     Thread t0 = null, t1 = null, t2 = null;
3021     // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
3022     if (true)
3023     {
3024       // todo: changesupport handlers need to be transferred
3025       if (discoverer == null)
3026       {
3027         discoverer = new jalview.ws.jws1.Discoverer();
3028         // register PCS handler for getDesktop().
3029         discoverer.addPropertyChangeListener(changeSupport);
3030       }
3031       // JAL-940 - disabled JWS1 service configuration - always start discoverer
3032       // until we phase out completely
3033       (t0 = new Thread(discoverer)).start();
3034     }
3035
3036     if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
3037     {
3038       t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
3039               .startDiscoverer(changeSupport);
3040     }
3041     Thread t3 = null;
3042     {
3043       // TODO: do rest service discovery
3044     }
3045     if (blocking)
3046     {
3047       while (alive)
3048       {
3049         try
3050         {
3051           Thread.sleep(15);
3052         } catch (Exception e)
3053         {
3054         }
3055         alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
3056                 || (t3 != null && t3.isAlive())
3057                 || (t0 != null && t0.isAlive());
3058       }
3059     }
3060   }
3061
3062   /**
3063    * called to check if the service discovery process completed successfully.
3064    * 
3065    * @param evt
3066    */
3067   protected void JalviewServicesChanged(PropertyChangeEvent evt)
3068   {
3069     if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
3070     {
3071       final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
3072               .getErrorMessages();
3073       if (ermsg != null)
3074       {
3075         if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
3076         {
3077           if (serviceChangedDialog == null)
3078           {
3079             // only run if we aren't already displaying one of these.
3080             addDialogThread(serviceChangedDialog = new Runnable()
3081             {
3082               @Override
3083               public void run()
3084               {
3085
3086                 /*
3087                  * JalviewDialog jd =new JalviewDialog() {
3088                  * 
3089                  * @Override protected void cancelPressed() { // TODO
3090                  * Auto-generated method stub
3091                  * 
3092                  * }@Override protected void okPressed() { // TODO
3093                  * Auto-generated method stub
3094                  * 
3095                  * }@Override protected void raiseClosed() { // TODO
3096                  * Auto-generated method stub
3097                  * 
3098                  * } }; jd.initDialogFrame(new
3099                  * JLabel("<html><table width=\"450\"><tr><td>" + ermsg +
3100                  * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
3101                  * + " or mis-configured HTTP proxy settings.<br/>" +
3102                  * "Check the <em>Connections</em> and <em>Web services</em> tab of the"
3103                  * +
3104                  * " Tools->Preferences dialog box to change them.</td></tr></table></html>"
3105                  * ), true, true, "Web Service Configuration Problem", 450,
3106                  * 400);
3107                  * 
3108                  * jd.waitForInput();
3109                  */
3110                 JvOptionPane.showConfirmDialog(Desktop.getDesktopPane(),
3111                         new JLabel("<html><table width=\"450\"><tr><td>"
3112                                 + ermsg + "</td></tr></table>"
3113                                 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
3114                                 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
3115                                 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
3116                                 + " Tools->Preferences dialog box to change them.</p></html>"),
3117                         "Web Service Configuration Problem",
3118                         JvOptionPane.DEFAULT_OPTION,
3119                         JvOptionPane.ERROR_MESSAGE);
3120                 serviceChangedDialog = null;
3121
3122               }
3123             });
3124           }
3125         }
3126         else
3127         {
3128           Cache.log.error(
3129                   "Errors reported by JABA discovery service. Check web services preferences.\n"
3130                           + ermsg);
3131         }
3132       }
3133     }
3134   }
3135
3136   Runnable serviceChangedDialog = null;
3137
3138   /**
3139    * start a thread to open a URL in the configured browser. Pops up a warning
3140    * dialog to the user if there is an exception when calling out to the browser
3141    * to open the URL.
3142    * 
3143    * @param url
3144    */
3145   public static void showUrl(final String url)
3146   {
3147     showUrl(url, Desktop.getInstance());
3148   }
3149
3150   /**
3151    * Like showUrl but allows progress handler to be specified
3152    * 
3153    * @param url
3154    * @param progress
3155    *          (null) or object implementing IProgressIndicator
3156    */
3157   public static void showUrl(final String url,
3158           final IProgressIndicator progress)
3159   {
3160     new Thread(new Runnable()
3161     {
3162       @Override
3163       public void run()
3164       {
3165         try
3166         {
3167           if (progress != null)
3168           {
3169             progress.setProgressBar(MessageManager
3170                     .formatMessage("status.opening_params", new Object[]
3171                     { url }), this.hashCode());
3172           }
3173           BrowserLauncher.openURL(url);
3174         } catch (Exception ex)
3175         {
3176           JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
3177                   MessageManager
3178                           .getString("label.web_browser_not_found_unix"),
3179                   MessageManager.getString("label.web_browser_not_found"),
3180                   JvOptionPane.WARNING_MESSAGE);
3181
3182           ex.printStackTrace();
3183         }
3184         if (progress != null)
3185         {
3186           progress.setProgressBar(null, this.hashCode());
3187         }
3188       }
3189     }).start();
3190   }
3191
3192   private WsParamSetManager wsparamManager = null;
3193
3194   public static ParamManager getUserParameterStore()
3195   {
3196     Desktop d = getInstance();
3197     if (d.wsparamManager == null)
3198     {
3199       d.wsparamManager = new WsParamSetManager();
3200     }
3201     return d.wsparamManager;
3202   }
3203
3204   /**
3205    * static hyperlink handler proxy method for use by Jalview's internal windows
3206    * 
3207    * @param e
3208    */
3209   public static void hyperlinkUpdate(HyperlinkEvent e)
3210   {
3211     if (e.getEventType() == EventType.ACTIVATED)
3212     {
3213       String url = null;
3214       try
3215       {
3216         url = e.getURL().toString();
3217         Desktop.showUrl(url);
3218       } catch (Exception x)
3219       {
3220         if (url != null)
3221         {
3222           if (Cache.log != null)
3223           {
3224             Cache.log.error("Couldn't handle string " + url + " as a URL.");
3225           }
3226           else
3227           {
3228             System.err.println(
3229                     "Couldn't handle string " + url + " as a URL.");
3230           }
3231         }
3232         // ignore any exceptions due to dud links.
3233       }
3234
3235     }
3236   }
3237
3238   /**
3239    * single thread that handles display of dialogs to user.
3240    */
3241   ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
3242
3243   /**
3244    * flag indicating if dialogExecutor should try to acquire a permit
3245    */
3246   volatile boolean dialogPause = true;
3247
3248   /**
3249    * pause the queue
3250    */
3251   java.util.concurrent.Semaphore block = new Semaphore(0);
3252
3253   private groovy.ui.Console groovyConsole;
3254
3255   public StructureViewer lastTargetedView;
3256
3257   /**
3258    * add another dialog thread to the queue
3259    * 
3260    * @param prompter
3261    */
3262   public void addDialogThread(final Runnable prompter)
3263   {
3264     dialogExecutor.submit(new Runnable()
3265     {
3266       @Override
3267       public void run()
3268       {
3269         if (dialogPause)
3270         {
3271           try
3272           {
3273             block.acquire();
3274           } catch (InterruptedException x)
3275           {
3276           }
3277           ;
3278         }
3279         if (getInstance() == null)
3280         {
3281           return;
3282         }
3283         try
3284         {
3285           SwingUtilities.invokeAndWait(prompter);
3286         } catch (Exception q)
3287         {
3288           Cache.log.warn("Unexpected Exception in dialog thread.", q);
3289         }
3290       }
3291     });
3292   }
3293
3294   public void startDialogQueue()
3295   {
3296     // set the flag so we don't pause waiting for another permit and semaphore
3297     // the current task to begin
3298     dialogPause = false;
3299     block.release();
3300   }
3301
3302   /**
3303    * Outputs an image of the desktop to file in EPS format, after prompting the
3304    * user for choice of Text or Lineart character rendering (unless a preference
3305    * has been set). The file name is generated as
3306    * 
3307    * <pre>
3308    * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3309    * </pre>
3310    */
3311   @Override
3312   protected void snapShotWindow_actionPerformed(ActionEvent e)
3313   {
3314     // currently the menu option to do this is not shown
3315     invalidate();
3316
3317     int width = getWidth();
3318     int height = getHeight();
3319     File of = new File(
3320             "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3321     ImageWriterI writer = new ImageWriterI()
3322     {
3323       @Override
3324       public void exportImage(Graphics g) throws Exception
3325       {
3326         paintAll(g);
3327         Cache.log.info("Successfully written snapshot to file "
3328                 + of.getAbsolutePath());
3329       }
3330     };
3331     String title = "View of desktop";
3332     ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3333             title);
3334     exporter.doExport(of, this, width, height, title);
3335   }
3336
3337   /**
3338    * Explode the views in the given SplitFrame into separate SplitFrame windows.
3339    * This respects (remembers) any previous 'exploded geometry' i.e. the size and
3340    * location last time the view was expanded (if any). However it does not
3341    * remember the split pane divider location - this is set to match the
3342    * 'exploding' frame.
3343    * 
3344    * @param sf
3345    */
3346   public void explodeViews(SplitFrame sf)
3347   {
3348     AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3349     AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3350     List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3351             .getAlignPanels();
3352     List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3353             .getAlignPanels();
3354     int viewCount = topPanels.size();
3355     if (viewCount < 2)
3356     {
3357       return;
3358     }
3359
3360     /*
3361      * Processing in reverse order works, forwards order leaves the first panels
3362      * not visible. I don't know why!
3363      */
3364     for (int i = viewCount - 1; i >= 0; i--)
3365     {
3366       /*
3367        * Make new top and bottom frames. These take over the respective
3368        * AlignmentPanel objects, including their AlignmentViewports, so the
3369        * cdna/protein relationships between the viewports is carried over to the
3370        * new split frames.
3371        * 
3372        * explodedGeometry holds the (x, y) position of the previously exploded
3373        * SplitFrame, and the (width, height) of the AlignFrame component
3374        */
3375       AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3376       AlignFrame newTopFrame = new AlignFrame(topPanel);
3377       newTopFrame.setSize(oldTopFrame.getSize());
3378       newTopFrame.setVisible(true);
3379       Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3380               .getExplodedGeometry();
3381       if (geometry != null)
3382       {
3383         newTopFrame.setSize(geometry.getSize());
3384       }
3385
3386       AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3387       AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3388       newBottomFrame.setSize(oldBottomFrame.getSize());
3389       newBottomFrame.setVisible(true);
3390       geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3391               .getExplodedGeometry();
3392       if (geometry != null)
3393       {
3394         newBottomFrame.setSize(geometry.getSize());
3395       }
3396
3397       topPanel.av.setGatherViewsHere(false);
3398       bottomPanel.av.setGatherViewsHere(false);
3399       JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3400               newBottomFrame);
3401       if (geometry != null)
3402       {
3403         splitFrame.setLocation(geometry.getLocation());
3404       }
3405       Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3406     }
3407
3408     /*
3409      * Clear references to the panels (now relocated in the new SplitFrames)
3410      * before closing the old SplitFrame.
3411      */
3412     topPanels.clear();
3413     bottomPanels.clear();
3414     sf.close();
3415   }
3416
3417   /**
3418    * Gather expanded split frames, sharing the same pairs of sequence set ids,
3419    * back into the given SplitFrame as additional views. Note that the gathered
3420    * frames may themselves have multiple views.
3421    * 
3422    * @param source
3423    */
3424   public void gatherViews(GSplitFrame source)
3425   {
3426     /*
3427      * special handling of explodedGeometry for a view within a SplitFrame: - it
3428      * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3429      * height) of the AlignFrame component
3430      */
3431     AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3432     AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3433     myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3434             source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3435     myBottomFrame.viewport
3436             .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3437                     myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3438     myTopFrame.viewport.setGatherViewsHere(true);
3439     myBottomFrame.viewport.setGatherViewsHere(true);
3440     String topViewId = myTopFrame.viewport.getSequenceSetId();
3441     String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3442
3443     JInternalFrame[] frames = getDesktopPane().getAllFrames();
3444     for (JInternalFrame frame : frames)
3445     {
3446       if (frame instanceof SplitFrame && frame != source)
3447       {
3448         SplitFrame sf = (SplitFrame) frame;
3449         AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3450         AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3451         boolean gatherThis = false;
3452         for (int a = 0; a < topFrame.alignPanels.size(); a++)
3453         {
3454           AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3455           AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3456           if (topViewId.equals(topPanel.av.getSequenceSetId())
3457                   && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3458           {
3459             gatherThis = true;
3460             topPanel.av.setGatherViewsHere(false);
3461             bottomPanel.av.setGatherViewsHere(false);
3462             topPanel.av.setExplodedGeometry(
3463                     new Rectangle(sf.getLocation(), topFrame.getSize()));
3464             bottomPanel.av.setExplodedGeometry(
3465                     new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3466             myTopFrame.addAlignmentPanel(topPanel, false);
3467             myBottomFrame.addAlignmentPanel(bottomPanel, false);
3468           }
3469         }
3470
3471         if (gatherThis)
3472         {
3473           topFrame.getAlignPanels().clear();
3474           bottomFrame.getAlignPanels().clear();
3475           sf.close();
3476         }
3477       }
3478     }
3479
3480     /*
3481      * The dust settles...give focus to the tab we did this from.
3482      */
3483     myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3484   }
3485
3486   public static groovy.ui.Console getGroovyConsole()
3487   {
3488     return getInstance().groovyConsole;
3489   }
3490
3491   /**
3492    * handles the payload of a drag and drop event.
3493    * 
3494    * TODO refactor to desktop utilities class
3495    * 
3496    * @param files
3497    *          - Data source strings extracted from the drop event
3498    * @param protocols
3499    *          - protocol for each data source extracted from the drop event
3500    * @param evt
3501    *          - the drop event
3502    * @param t
3503    *          - the payload from the drop event
3504    * @throws Exception
3505    */
3506   @SuppressWarnings("unchecked")
3507   public static void transferFromDropTarget(List<Object> files,
3508           List<DataSourceType> protocols, DropTargetDropEvent evt,
3509           Transferable t) throws Exception
3510   {
3511
3512     // BH 2018 changed List<String> to List<Object> to allow for File from SwingJS
3513
3514     // DataFlavor[] flavors = t.getTransferDataFlavors();
3515     // for (int i = 0; i < flavors.length; i++) {
3516     // if (flavors[i].isFlavorJavaFileListType()) {
3517     // evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
3518     // List<File> list = (List<File>) t.getTransferData(flavors[i]);
3519     // for (int j = 0; j < list.size(); j++) {
3520     // File file = (File) list.get(j);
3521     // byte[] data = getDroppedFileBytes(file);
3522     // fileName.setText(file.getName() + " - " + data.length + " " +
3523     // evt.getLocation());
3524     // JTextArea target = (JTextArea) ((DropTarget) evt.getSource()).getComponent();
3525     // target.setText(new String(data));
3526     // }
3527     // dtde.dropComplete(true);
3528     // return;
3529     // }
3530     //
3531
3532     DataFlavor uriListFlavor = new DataFlavor(
3533             "text/uri-list;class=java.lang.String"), urlFlavour = null;
3534     try
3535     {
3536       urlFlavour = new DataFlavor(
3537               "application/x-java-url; class=java.net.URL");
3538     } catch (ClassNotFoundException cfe)
3539     {
3540       Cache.log.debug("Couldn't instantiate the URL dataflavor.", cfe);
3541     }
3542
3543     if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3544     {
3545
3546       try
3547       {
3548         java.net.URL url = (URL) t.getTransferData(urlFlavour);
3549         // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3550         // means url may be null.
3551         if (url != null)
3552         {
3553           protocols.add(DataSourceType.URL);
3554           files.add(url.toString());
3555           Cache.log.debug("Drop handled as URL dataflavor "
3556                   + files.get(files.size() - 1));
3557           return;
3558         }
3559         else
3560         {
3561           if (Platform.isAMacAndNotJS())
3562           {
3563             System.err.println(
3564                     "Please ignore plist error - occurs due to problem with java 8 on OSX");
3565           }
3566           ;
3567         }
3568       } catch (Throwable ex)
3569       {
3570         Cache.log.debug("URL drop handler failed.", ex);
3571       }
3572     }
3573     if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3574     {
3575       // Works on Windows and MacOSX
3576       Cache.log.debug("Drop handled as javaFileListFlavor");
3577       for (Object file : (List<Object>) t
3578               .getTransferData(DataFlavor.javaFileListFlavor))
3579       {
3580         files.add(file);
3581         protocols.add(DataSourceType.FILE);
3582       }
3583     }
3584     else
3585     {
3586       // Unix like behaviour
3587       boolean added = false;
3588       String data = null;
3589       if (t.isDataFlavorSupported(uriListFlavor))
3590       {
3591         Cache.log.debug("Drop handled as uriListFlavor");
3592         // This is used by Unix drag system
3593         data = (String) t.getTransferData(uriListFlavor);
3594       }
3595       if (data == null)
3596       {
3597         // fallback to text: workaround - on OSX where there's a JVM bug
3598         Cache.log.debug("standard URIListFlavor failed. Trying text");
3599         // try text fallback
3600         DataFlavor textDf = new DataFlavor(
3601                 "text/plain;class=java.lang.String");
3602         if (t.isDataFlavorSupported(textDf))
3603         {
3604           data = (String) t.getTransferData(textDf);
3605         }
3606
3607         Cache.log.debug("Plain text drop content returned "
3608                 + (data == null ? "Null - failed" : data));
3609
3610       }
3611       if (data != null)
3612       {
3613         while (protocols.size() < files.size())
3614         {
3615           Cache.log.debug("Adding missing FILE protocol for "
3616                   + files.get(protocols.size()));
3617           protocols.add(DataSourceType.FILE);
3618         }
3619         for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3620                 data, "\r\n"); st.hasMoreTokens();)
3621         {
3622           added = true;
3623           String s = st.nextToken();
3624           if (s.startsWith("#"))
3625           {
3626             // the line is a comment (as per the RFC 2483)
3627             continue;
3628           }
3629           java.net.URI uri = new java.net.URI(s);
3630           if (uri.getScheme().toLowerCase().startsWith("http"))
3631           {
3632             protocols.add(DataSourceType.URL);
3633             files.add(uri.toString());
3634           }
3635           else
3636           {
3637             // otherwise preserve old behaviour: catch all for file objects
3638             java.io.File file = new java.io.File(uri);
3639             protocols.add(DataSourceType.FILE);
3640             files.add(file.toString());
3641           }
3642         }
3643       }
3644
3645       if (Cache.log.isDebugEnabled())
3646       {
3647         if (data == null || !added)
3648         {
3649
3650           if (t.getTransferDataFlavors() != null
3651                   && t.getTransferDataFlavors().length > 0)
3652           {
3653             Cache.log.debug(
3654                     "Couldn't resolve drop data. Here are the supported flavors:");
3655             for (DataFlavor fl : t.getTransferDataFlavors())
3656             {
3657               Cache.log.debug(
3658                       "Supported transfer dataflavor: " + fl.toString());
3659               Object df = t.getTransferData(fl);
3660               if (df != null)
3661               {
3662                 Cache.log.debug("Retrieves: " + df);
3663               }
3664               else
3665               {
3666                 Cache.log.debug("Retrieved nothing");
3667               }
3668             }
3669           }
3670           else
3671           {
3672             Cache.log.debug("Couldn't resolve dataflavor for drop: "
3673                     + t.toString());
3674           }
3675         }
3676       }
3677     }
3678     if (Platform.isWindowsAndNotJS())
3679     {
3680       Cache.log.debug("Scanning dropped content for Windows Link Files");
3681
3682       // resolve any .lnk files in the file drop
3683       for (int f = 0; f < files.size(); f++)
3684       {
3685         String source = files.get(f).toString().toLowerCase();
3686         if (protocols.get(f).equals(DataSourceType.FILE)
3687                 && (source.endsWith(".lnk") || source.endsWith(".url")
3688                         || source.endsWith(".site")))
3689         {
3690           try
3691           {
3692             Object obj = files.get(f);
3693             File lf = (obj instanceof File ? (File) obj
3694                     : new File((String) obj));
3695             // process link file to get a URL
3696             Cache.log.debug("Found potential link file: " + lf);
3697             WindowsShortcut wscfile = new WindowsShortcut(lf);
3698             String fullname = wscfile.getRealFilename();
3699             protocols.set(f, FormatAdapter.checkProtocol(fullname));
3700             files.set(f, fullname);
3701             Cache.log.debug("Parsed real filename " + fullname
3702                     + " to extract protocol: " + protocols.get(f));
3703           } catch (Exception ex)
3704           {
3705             Cache.log.error(
3706                     "Couldn't parse " + files.get(f) + " as a link file.",
3707                     ex);
3708           }
3709         }
3710       }
3711     }
3712   }
3713
3714   /**
3715    * Sets the Preferences property for experimental features to True or False
3716    * depending on the state of the controlling menu item
3717    */
3718   @Override
3719   protected void showExperimental_actionPerformed(boolean selected)
3720   {
3721     Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3722   }
3723
3724   /**
3725    * Answers a (possibly empty) list of any structure viewer frames (currently for
3726    * either Jmol or Chimera) which are currently open. This may optionally be
3727    * restricted to viewers of a specified class, or viewers linked to a specified
3728    * alignment panel.
3729    * 
3730    * @param apanel
3731    *          if not null, only return viewers linked to this panel
3732    * @param structureViewerClass
3733    *          if not null, only return viewers of this class
3734    * @return
3735    */
3736   public List<StructureViewerBase> getStructureViewers(
3737           AlignmentPanel apanel,
3738           Class<? extends StructureViewerBase> structureViewerClass)
3739   {
3740     List<StructureViewerBase> result = new ArrayList<>();
3741     JInternalFrame[] frames = Desktop.getInstance().getAllFrames();
3742
3743     for (JInternalFrame frame : frames)
3744     {
3745       if (frame instanceof StructureViewerBase)
3746       {
3747         if (structureViewerClass == null
3748                 || structureViewerClass.isInstance(frame))
3749         {
3750           if (apanel == null
3751                   || ((StructureViewerBase) frame).isLinkedWith(apanel))
3752           {
3753             result.add((StructureViewerBase) frame);
3754           }
3755         }
3756       }
3757     }
3758     return result;
3759   }
3760
3761 }