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