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