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