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