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