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