JAL-3878 Wire web services logic to UI elements.
[jalview.git] / src / jalview / gui / Desktop.java
index 5df48a2..0c3b553 100644 (file)
@@ -1,19 +1,19 @@
 /*
  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
  * Copyright (C) $$Year-Rel$$ The Jalview Authors
- * 
+ *
  * This file is part of Jalview.
- * 
+ *
  * Jalview is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License 
+ * modify it under the terms of the GNU General Public License
  * as published by the Free Software Foundation, either version 3
  * of the License, or (at your option) any later version.
- *  
- * Jalview is distributed in the hope that it will be useful, but 
- * WITHOUT ANY WARRANTY; without even the implied warranty 
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ *
+ * Jalview is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  * PURPOSE.  See the GNU General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU General Public License
  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
  * The Jalview Authors are detailed in the 'AUTHORS' file.
@@ -59,8 +59,11 @@ import java.util.Hashtable;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Vector;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
 import java.util.concurrent.Semaphore;
 
 import javax.swing.AbstractAction;
@@ -121,16 +124,16 @@ import jalview.util.BrowserLauncher;
 import jalview.util.ImageMaker.TYPE;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
-import jalview.util.ShortcutKeyMaskExWrapper;
 import jalview.util.UrlConstants;
 import jalview.viewmodel.AlignmentViewport;
+import jalview.ws.WSDiscovererI;
 import jalview.ws.params.ParamManager;
 import jalview.ws.utils.UrlDownloadClient;
 
 /**
  * Jalview Desktop
- * 
- * 
+ *
+ *
  * @author $author$
  * @version $Revision: 1.155 $
  */
@@ -163,6 +166,7 @@ public class Desktop extends GDesktop
 
   public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
 
+  @SuppressWarnings("deprecation")
   private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
 
   /**
@@ -176,6 +180,7 @@ public class Desktop extends GDesktop
    * @param listener
    * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
    */
+  @Deprecated
   public void addJalviewPropertyChangeListener(
           PropertyChangeListener listener)
   {
@@ -188,6 +193,7 @@ public class Desktop extends GDesktop
    * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
    *      java.beans.PropertyChangeListener)
    */
+  @Deprecated
   public void addJalviewPropertyChangeListener(String propertyName,
           PropertyChangeListener listener)
   {
@@ -200,6 +206,7 @@ public class Desktop extends GDesktop
    * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
    *      java.beans.PropertyChangeListener)
    */
+  @Deprecated
   public void removeJalviewPropertyChangeListener(String propertyName,
           PropertyChangeListener listener)
   {
@@ -219,7 +226,7 @@ public class Desktop extends GDesktop
    * Answers an 'application scope' singleton instance of this class. Separate
    * SwingJS 'applets' running in the same browser page will each have a
    * distinct instance of Desktop.
-   * 
+   *
    * @return
    */
   public static Desktop getInstance()
@@ -235,19 +242,19 @@ public class Desktop extends GDesktop
             .getStructureSelectionManager(getInstance());
   }
 
-  static int openFrameCount = 0;
+  int openFrameCount = 0;
 
-  static final int xOffset = 30;
+  final int xOffset = 30;
 
-  static final int yOffset = 30;
+  final int yOffset = 30;
 
-  public static jalview.ws.jws1.Discoverer discoverer;
+  public jalview.ws.jws1.Discoverer discoverer;
 
-  public static Object[] jalviewClipboard;
+  public Object[] jalviewClipboard;
 
-  public static boolean internalCopy = false;
+  public boolean internalCopy = false;
 
-  static int fileLoadingCount = 0;
+  int fileLoadingCount = 0;
 
   class MyDesktopManager implements DesktopManager
   {
@@ -465,10 +472,12 @@ public class Desktop extends GDesktop
         setBounds(xPos, yPos, 900, 650);
       }
 
+      getIdentifiersOrgData();
+
       if (!Platform.isJS())
       /**
        * Java only
-       * 
+       *
        * @j2sIgnore
        */
       {
@@ -480,51 +489,53 @@ public class Desktop extends GDesktop
 
         experimentalFeatures.setSelected(showExperimental());
 
-        getIdentifiersOrgData();
-
-        checkURLLinks();
+        if (Jalview.isInteractive())
+        {
+          // disabled for SeqCanvasTest
+          checkURLLinks();
 
-        // Spawn a thread that shows the splashscreen
+          // Spawn a thread that shows the splashscreen
 
-        SwingUtilities.invokeLater(new Runnable()
-        {
-          @Override
-          public void run()
+          SwingUtilities.invokeLater(new Runnable()
           {
-            new SplashScreen(true);
-          }
-        });
+            @Override
+            public void run()
+            {
+              new SplashScreen(true);
+            }
+          });
 
-        // Thread off a new instance of the file chooser - this reduces the time
-        // it
-        // takes to open it later on.
-        new Thread(new Runnable()
-        {
-          @Override
-          public void run()
+          // Thread off a new instance of the file chooser - this reduces the
+          // time
+          // it
+          // takes to open it later on.
+          new Thread(new Runnable()
           {
-            Cache.log.debug("Filechooser init thread started.");
-            String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
-            JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
-                    fileFormat);
-            Cache.log.debug("Filechooser init thread finished.");
-          }
-        }).start();
-        // Add the service change listener
-        changeSupport.addJalviewPropertyChangeListener("services",
-                new PropertyChangeListener()
-                {
-
-                  @Override
-                  public void propertyChange(PropertyChangeEvent evt)
+            @Override
+            public void run()
+            {
+              Cache.log.debug("Filechooser init thread started.");
+              String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
+              JalviewFileChooser.forRead(
+                      Cache.getProperty("LAST_DIRECTORY"), fileFormat);
+              Cache.log.debug("Filechooser init thread finished.");
+            }
+          }).start();
+          // Add the service change listener
+          changeSupport.addJalviewPropertyChangeListener("services",
+                  new PropertyChangeListener()
                   {
-                    Cache.log.debug("Firing service changed event for "
-                            + evt.getNewValue());
-                    JalviewServicesChanged(evt);
-                  }
-                });
-      }
 
+                    @Override
+                    public void propertyChange(PropertyChangeEvent evt)
+                    {
+                      Cache.log.debug("Firing service changed event for "
+                              + evt.getNewValue());
+                      JalviewServicesChanged(evt);
+                    }
+                  });
+        }
+      }
       this.setDropTarget(new java.awt.dnd.DropTarget(desktopPane, this));
 
       this.addWindowListener(new WindowAdapter()
@@ -568,7 +579,7 @@ public class Desktop extends GDesktop
   /**
    * Answers true if user preferences to enable experimental features is True
    * (on), else false
-   * 
+   *
    * @return
    */
   public boolean showExperimental()
@@ -658,8 +669,8 @@ public class Desktop extends GDesktop
         public void run()
         {
           long now = System.currentTimeMillis();
-          setProgressBar(
-                  MessageManager.getString("status.refreshing_news"), now);
+          setProgressBar(MessageManager.getString("status.refreshing_news"),
+                  now);
           jvnews.refreshNews();
           setProgressBar(null, now);
           jvnews.showNews();
@@ -670,7 +681,7 @@ public class Desktop extends GDesktop
 
   /**
    * recover the last known dimensions for a jalview window
-   * 
+   *
    * @param windowName
    *          - empty string is desktop, all other windows have unique prefix
    * @return null or last known dimensions scaled to current geometry (if last
@@ -767,70 +778,83 @@ public class Desktop extends GDesktop
     }
   }
 
-  /**
-   * Adds and opens the given frame to the desktop
-   * 
-   * @param frame
-   *          Frame to show
-   * @param title
-   *          Visible Title
-   * @param w
-   *          width
-   * @param h
-   *          height
-   */
-  public static synchronized void addInternalFrame(
-          final JInternalFrame frame, String title, int w, int h)
-  {
-    addInternalFrame(frame, title, true, w, h, true, false);
-  }
+//  /**
+//   * Add an internal frame to the Jalview desktop that is allowed to be resized,
+//   * has a minimum size of 300px and might or might not be visible
+//   *
+//   * @param frame
+//   *          Frame to show
+//   * @param title
+//   *          Visible Title
+//   * @param makeVisible
+//   *          When true, display frame immediately, otherwise, caller must call
+//   *          setVisible themselves.
+//   * @param w
+//   *          width
+//   * @param h
+//   *          height
+//   */
+//  @Deprecated
+//  public static synchronized void addInternalFrame(
+//          final JInternalFrame frame, String title, boolean makeVisible,
+//          int w, int h)
+//  {
+//    // textbox, web services, sequenceFetcher, featureSettings
+//    getInstance().addFrame(frame, title, makeVisible, w, h,
+//            FRAME_ALLOW_RESIZE, FRAME_SET_MIN_SIZE_300);
+//  }
+//
+//  /**
+//   * Add an internal frame to the Jalview desktop that is visible, has a minimum
+//   * size of 300px, and may or may not be resizable
+//   *
+//   * @param frame
+//   *          Frame to show
+//   * @param title
+//   *          Visible Title
+//   * @param w
+//   *          width
+//   * @param h
+//   *          height
+//   * @param resizable
+//   *          Allow resize
+//   */
+//  @Deprecated
+//  public static synchronized void addInternalFrame(
+//          final JInternalFrame frame, String title, int w, int h,
+//          boolean resizable)
+//  {
+//    // annotation, font, calculation, user-defined colors
+//    getInstance().addFrame(frame, title, FRAME_MAKE_VISIBLE, w, h,
+//            resizable, FRAME_SET_MIN_SIZE_300);
+//  }
 
   /**
-   * Add an internal frame to the Jalview desktop
-   * 
+   * Adds and opens the given frame to the desktop that is visible, allowed to
+   * resize, and has a 300px minimum width.
+   *
    * @param frame
    *          Frame to show
    * @param title
    *          Visible Title
-   * @param makeVisible
-   *          When true, display frame immediately, otherwise, caller must call
-   *          setVisible themselves.
    * @param w
    *          width
    * @param h
    *          height
    */
   public static synchronized void addInternalFrame(
-          final JInternalFrame frame, String title, boolean makeVisible,
-          int w, int h)
+          final JInternalFrame frame, String title, int w, int h)
   {
-    addInternalFrame(frame, title, makeVisible, w, h, true, false);
-  }
+    // 58 classes
 
-  /**
-   * Add an internal frame to the Jalview desktop and make it visible
-   * 
-   * @param frame
-   *          Frame to show
-   * @param title
-   *          Visible Title
-   * @param w
-   *          width
-   * @param h
-   *          height
-   * @param resizable
-   *          Allow resize
-   */
-  public static synchronized void addInternalFrame(
-          final JInternalFrame frame, String title, int w, int h,
-          boolean resizable)
-  {
-    addInternalFrame(frame, title, true, w, h, resizable, false);
+    addInternalFrame(frame, title, Desktop.FRAME_MAKE_VISIBLE, w, h,
+            FRAME_ALLOW_RESIZE, FRAME_SET_MIN_SIZE_300);
   }
 
   /**
-   * Add an internal frame to the Jalview desktop
-   * 
+   * Add an internal frame to the Jalview desktop that may optionally be
+   * visible, resizable, and allowed to be any size
+   *
    * @param frame
    *          Frame to show
    * @param title
@@ -851,8 +875,7 @@ public class Desktop extends GDesktop
           final JInternalFrame frame, String title, boolean makeVisible,
           int w, int h, boolean resizable, boolean ignoreMinSize)
   {
-
-
+    // 15 classes call this method directly.
 
     // TODO: allow callers to determine X and Y position of frame (eg. via
     // bounds object).
@@ -864,22 +887,39 @@ public class Desktop extends GDesktop
     {
       frame.setSize(w, h);
     }
-    // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
-    // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
-    // IF JALVIEW IS RUNNING HEADLESS
-    // ///////////////////////////////////////////////
-    if (Jalview.isHeadlessMode())
-    {
-      return;
-    }
+    if (getInstance() != null)
+      getInstance().addFrame(frame, makeVisible, resizable,
+            ignoreMinSize);
+  }
+
+  // These can now by put into a single int flag, if desired:
+
+  public final static boolean FRAME_MAKE_VISIBLE = true;
+
+  public final static boolean FRAME_NOT_VISIBLE = false;
+
+  public final static boolean FRAME_ALLOW_RESIZE = true;
+
+  public final static boolean FRAME_NOT_RESIZABLE = false;
+
+  public final static boolean FRAME_ALLOW_ANY_SIZE = true;
+
+  public final static boolean FRAME_SET_MIN_SIZE_300 = false;
+
+  private void addFrame(JInternalFrame frame,
+          boolean makeVisible, boolean resizable,
+          boolean ignoreMinSize)
+  {
 
     openFrameCount++;
 
+    boolean isEmbedded = (Platform.getEmbeddedAttribute(frame, "id") != null);
+    boolean hasEmbeddedSize = (Platform.getDimIfEmbedded(frame, -1, -1) != null);
+    // Web page embedding allows us to ignore minimum size
+    ignoreMinSize |= hasEmbeddedSize;
+
     if (!ignoreMinSize)
     {
-      frame.setMinimumSize(
-              new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
-
       // Set default dimension for Alignment Frame window.
       // The Alignment Frame window could be added from a number of places,
       // hence,
@@ -888,6 +928,10 @@ public class Desktop extends GDesktop
       {
         frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
                 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
+      } else {
+        frame.setMinimumSize(
+                new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
+
       }
     }
 
@@ -897,7 +941,6 @@ public class Desktop extends GDesktop
     frame.setMaximizable(resizable);
     frame.setIconifiable(resizable);
     frame.setOpaque(Platform.isJS());
-    boolean isEmbedded = (Platform.getDimIfEmbedded(frame, -1, -1) != null);
     if (!isEmbedded && frame.getX() < 1 && frame.getY() < 1)
     {
       frame.setLocation(xOffset * openFrameCount,
@@ -905,17 +948,17 @@ public class Desktop extends GDesktop
     }
 
     /*
-     * add an entry for the new frame in the Window menu 
+     * add an entry for the new frame in the Window menu
      * (and remove it when the frame is closed)
      */
-    final JMenuItem menuItem = new JMenuItem(title);
+    final JMenuItem menuItem = new JMenuItem(frame.getTitle());
     frame.addInternalFrameListener(new InternalFrameAdapter()
     {
       @Override
       public void internalFrameActivated(InternalFrameEvent evt)
       {
         JInternalFrame itf = getDesktopPane().getSelectedFrame();
-       if (itf != null)
+        if (itf != null)
         {
           if (itf instanceof AlignFrame)
           {
@@ -990,12 +1033,11 @@ public class Desktop extends GDesktop
   /**
    * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
    * the window
-   * 
+   *
    * @param frame
    */
   private static void setKeyBindings(JInternalFrame frame)
   {
-    @SuppressWarnings("serial")
     final Action closeAction = new AbstractAction()
     {
       @Override
@@ -1011,7 +1053,7 @@ public class Desktop extends GDesktop
     KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
             InputEvent.CTRL_DOWN_MASK);
     KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
-            ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
+            Platform.SHORTCUT_KEY_MASK);
 
     InputMap inputMap = frame
             .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
@@ -1056,7 +1098,7 @@ public class Desktop extends GDesktop
 
   /**
    * DOCUMENT ME!
-   * 
+   *
    * @param evt
    *          DOCUMENT ME!
    */
@@ -1121,7 +1163,7 @@ public class Desktop extends GDesktop
 
   /**
    * DOCUMENT ME!
-   * 
+   *
    * @param e
    *          DOCUMENT ME!
    */
@@ -1174,7 +1216,7 @@ public class Desktop extends GDesktop
 
   /**
    * Shows a dialog for input of a URL at which to retrieve alignment data
-   * 
+   *
    * @param viewport
    */
   @Override
@@ -1202,7 +1244,7 @@ public class Desktop extends GDesktop
     else
     /**
      * Java only
-     * 
+     *
      * @j2sIgnore
      */
     {
@@ -1294,7 +1336,7 @@ public class Desktop extends GDesktop
 
   /**
    * Opens the CutAndPaste window for the user to paste an alignment in to
-   * 
+   *
    * @param viewPanel
    *          - if not null, the pasted alignment is added to the current
    *          alignment; if null, to a new alignment window
@@ -1306,8 +1348,9 @@ public class Desktop extends GDesktop
     CutAndPasteTransfer cap = new CutAndPasteTransfer();
     cap.setForInput(viewPanel);
     addInternalFrame(cap,
-            MessageManager.getString("label.cut_paste_alignmen_file"), true,
-            600, 500);
+            MessageManager.getString("label.cut_paste_alignmen_file"),
+            FRAME_MAKE_VISIBLE, 600, 500, FRAME_ALLOW_RESIZE,
+            FRAME_SET_MIN_SIZE_300);
   }
 
   /*
@@ -1361,7 +1404,7 @@ public class Desktop extends GDesktop
 
   /**
    * DOCUMENT ME!
-   * 
+   *
    * @param e
    *          DOCUMENT ME!
    */
@@ -1382,7 +1425,7 @@ public class Desktop extends GDesktop
    * Returns the html text for the About screen, including any available version
    * number, build details, author details and citation reference, but without
    * the enclosing {@code html} tags
-   * 
+   *
    * @return
    */
   public String getAboutMessage()
@@ -1446,7 +1489,7 @@ public class Desktop extends GDesktop
       else
       /**
        * Java only
-       * 
+       *
        * @j2sIgnore
        */
       {
@@ -1506,7 +1549,7 @@ public class Desktop extends GDesktop
 
   /*
    * (non-Javadoc)
-   * 
+   *
    * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
    * ActionEvent)
    */
@@ -1521,7 +1564,7 @@ public class Desktop extends GDesktop
 
   /*
    * (non-Javadoc)
-   * 
+   *
    * @see
    * jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.ActionEvent
    * )
@@ -1534,7 +1577,7 @@ public class Desktop extends GDesktop
 
   /*
    * (non-Javadoc)
-   * 
+   *
    * @see
    * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
    * )
@@ -1549,7 +1592,7 @@ public class Desktop extends GDesktop
 
   /**
    * control whether the java console is visible or not
-   * 
+   *
    * @param selected
    */
   void showConsole(boolean selected)
@@ -1572,7 +1615,8 @@ public class Desktop extends GDesktop
       return;
     }
 
-    AlignmentViewport source = null, target = null;
+    AlignViewportI source = null;
+    AlignViewportI target = null;
     if (frames[0] instanceof AlignFrame)
     {
       source = ((AlignFrame) frames[0]).getCurrentView();
@@ -1643,7 +1687,7 @@ public class Desktop extends GDesktop
 
   /**
    * DOCUMENT ME!
-   * 
+   *
    * @param e
    *          DOCUMENT ME!
    */
@@ -1803,7 +1847,7 @@ public class Desktop extends GDesktop
             {
               Cache.log.error(
                       "Problems whilst loading project from " + choice, ex);
-              JvOptionPane.showMessageDialog( getDesktopPane(),
+              JvOptionPane.showMessageDialog(getDesktopPane(),
                       MessageManager.formatMessage(
                               "label.error_whilst_loading_project_from",
                               new Object[]
@@ -1907,7 +1951,7 @@ public class Desktop extends GDesktop
   }
 
   /**
-   * 
+   *
    * @param alignmentId
    *          - if null, all sets are returned
    * @return all AlignmentPanels concerning the alignmentId sequence set
@@ -1947,7 +1991,7 @@ public class Desktop extends GDesktop
 
   /**
    * get all the viewports on an alignment.
-   * 
+   *
    * @param sequenceSetId
    *          unique alignment id (may be null - all viewports returned in that
    *          case)
@@ -1956,7 +2000,7 @@ public class Desktop extends GDesktop
   public static AlignmentViewport[] getViewports(String sequenceSetId)
   {
     List<AlignmentViewport> viewp = new ArrayList<>();
-    if ( getDesktopPane() != null)
+    if (getDesktopPane() != null)
     {
       AlignFrame[] frames = getAlignFrames();
 
@@ -1992,7 +2036,7 @@ public class Desktop extends GDesktop
 
   /**
    * Explode the views in the given frame into separate AlignFrame
-   * 
+   *
    * @param af
    */
   public static void explodeViews(AlignFrame af)
@@ -2060,7 +2104,7 @@ public class Desktop extends GDesktop
    * identifier back in to this frame as additional views, and close the
    * expanded views. Note the expanded frames may themselves have multiple
    * views. We take the lot.
-   * 
+   *
    * @param source
    */
   public void gatherViews(AlignFrame source)
@@ -2125,7 +2169,7 @@ public class Desktop extends GDesktop
   /**
    * Checks the given url to see if it gives a response indicating that the user
    * should be informed of a new questionnaire.
-   * 
+   *
    * @param url
    */
   public void checkForQuestionnaire(String url)
@@ -2218,7 +2262,7 @@ public class Desktop extends GDesktop
    * Proxy class for JDesktopPane which optionally displays the current memory
    * usage and highlights the desktop area with a red bar if free memory runs
    * low.
-   * 
+   *
    * @author AMW
    */
   public class MyDesktopPane extends JDesktopPane implements Runnable
@@ -2313,17 +2357,17 @@ public class Desktop extends GDesktop
 
   /**
    * Accessor method to quickly get all the AlignmentFrames loaded.
-   * 
+   *
    * @return an array of AlignFrame, or null if none found
    */
   public static AlignFrame[] getAlignFrames()
   {
     if (Jalview.isHeadlessMode())
     {
-      return new AlignFrame[] { Jalview.currentAlignFrame };
+      return new AlignFrame[] { Jalview.getInstance().currentAlignFrame };
     }
 
-    JInternalFrame[] frames =  getDesktopPane().getAllFrames();
+    JInternalFrame[] frames = getDesktopPane().getAllFrames();
 
     if (frames == null)
     {
@@ -2363,7 +2407,7 @@ public class Desktop extends GDesktop
 
   /**
    * Returns an array of any AppJmol frames in the Desktop (or null if none).
-   * 
+   *
    * @return
    */
   public GStructureViewer[] getJmols()
@@ -2426,7 +2470,7 @@ public class Desktop extends GDesktop
       /*
        * We allow only one console at a time, so that AlignFrame menu option
        * 'Calculate | Run Groovy script' is unambiguous.
-       * Disable 'Groovy Console', and enable 'Run script', when the console is 
+       * Disable 'Groovy Console', and enable 'Run script', when the console is
        * opened, and the reverse when it is closed
        */
       Window window = (Window) groovyConsole.getFrame();
@@ -2462,12 +2506,10 @@ public class Desktop extends GDesktop
    */
   protected void addQuitHandler()
   {
-    getRootPane()
-            .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
-                    KeyStroke
-                            .getKeyStroke(KeyEvent.VK_Q,
-                                    jalview.util.ShortcutKeyMaskExWrapper
-                                            .getMenuShortcutKeyMaskEx()),
+
+    getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
+            .put(KeyStroke.getKeyStroke(KeyEvent.VK_Q,
+                    Platform.SHORTCUT_KEY_MASK),
                     "Quit");
     getRootPane().getActionMap().put("Quit", new AbstractAction()
     {
@@ -2481,7 +2523,7 @@ public class Desktop extends GDesktop
 
   /**
    * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
-   * 
+   *
    * @param enabled
    *          true if Groovy console is open
    */
@@ -2512,7 +2554,7 @@ public class Desktop extends GDesktop
 
   /*
    * (non-Javadoc)
-   * 
+   *
    * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
    */
   @Override
@@ -2541,9 +2583,16 @@ public class Desktop extends GDesktop
     }
   }
 
+  @Override
+  public void removeProgressBar(long id)
+  {
+    //TODO
+    throw new UnsupportedOperationException("not implemented");
+  }
+
   /*
    * (non-Javadoc)
-   * 
+   *
    * @see jalview.gui.IProgressIndicator#registerHandler(long,
    * jalview.gui.IProgressIndicatorHandler)
    */
@@ -2582,7 +2631,7 @@ public class Desktop extends GDesktop
   }
 
   /**
-   * 
+   *
    * @return true if any progress bars are still active
    */
   @Override
@@ -2599,13 +2648,13 @@ public class Desktop extends GDesktop
    * This will return the first AlignFrame holding the given viewport instance.
    * It will break if there are more than one AlignFrames viewing a particular
    * av.
-   * 
+   *
    * @param viewport
    * @return alignFrame for viewport
    */
   public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
   {
-    if ( getDesktopPane() != null)
+    if (getDesktopPane() != null)
     {
       AlignmentPanel[] aps = getAlignmentPanels(
               viewport.getSequenceSetId());
@@ -2634,7 +2683,7 @@ public class Desktop extends GDesktop
 
   /**
    * check if jalview GUI is being operated programmatically
-   * 
+   *
    * @return inBatchMode
    */
   public boolean isInBatchMode()
@@ -2644,7 +2693,7 @@ public class Desktop extends GDesktop
 
   /**
    * set flag if jalview GUI is being operated programmatically
-   * 
+   *
    * @param inBatchMode
    */
   public void setInBatchMode(boolean inBatchMode)
@@ -2659,60 +2708,64 @@ public class Desktop extends GDesktop
 
   public void startServiceDiscovery(boolean blocking)
   {
-    boolean alive = true;
-    Thread t0 = null, t1 = null, t2 = null;
+    System.out.println("Starting service discovery");
+    var tasks = new ArrayList<Future<?>>();
     // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
-    if (true)
+
+    System.out.println("loading services");
+
+    /** @j2sIgnore */
     {
       // todo: changesupport handlers need to be transferred
       if (discoverer == null)
       {
-        discoverer = new jalview.ws.jws1.Discoverer();
+        discoverer = jalview.ws.jws1.Discoverer.getInstance();
         // register PCS handler for desktop.
         discoverer.addPropertyChangeListener(changeSupport);
       }
       // JAL-940 - disabled JWS1 service configuration - always start discoverer
       // until we phase out completely
-      (t0 = new Thread(discoverer)).start();
+      var f = new FutureTask<Void>(discoverer, null);
+      new Thread(f).start();
+      tasks.add(f);
     }
 
     if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
     {
-      t2 = jalview.ws.jws2.Jws2Discoverer.getInstance()
-              .startDiscoverer(changeSupport);
+      tasks.add(jalview.ws.jws2.Jws2Discoverer.getInstance().startDiscoverer());
     }
-    Thread t3 = null;
+    if (Cache.getDefault("SHOW_SLIVKA_SERVICES", true))
     {
-      // TODO: do rest service discovery
+      tasks.add(jalview.ws2.slivka.SlivkaWSDiscoverer.getInstance().startDiscoverer());
     }
     if (blocking)
     {
-      while (alive)
-      {
+      for (Future<?> task : tasks) {
         try
         {
-          Thread.sleep(15);
+          // block until all discovery tasks are done
+          task.get();
         } catch (Exception e)
         {
+          e.printStackTrace();
         }
-        alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
-                || (t3 != null && t3.isAlive())
-                || (t0 != null && t0.isAlive());
       }
     }
   }
 
   /**
    * called to check if the service discovery process completed successfully.
-   * 
+   *
    * @param evt
    */
   protected void JalviewServicesChanged(PropertyChangeEvent evt)
   {
     if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
     {
-      final String ermsg = jalview.ws.jws2.Jws2Discoverer.getInstance()
-              .getErrorMessages();
+      final WSDiscovererI discoverer = jalview.ws.jws2.Jws2Discoverer
+          .getInstance();
+      final String ermsg = discoverer.getErrorMessages();
+      // CONFLICT:ALT:?     final String ermsg = jalview.ws.jws2.Jws2Discoverer.getInstance()
       if (ermsg != null)
       {
         if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
@@ -2728,16 +2781,16 @@ public class Desktop extends GDesktop
 
                 /*
                  * JalviewDialog jd =new JalviewDialog() {
-                 * 
+                 *
                  * @Override protected void cancelPressed() { // TODO
                  * Auto-generated method stub
-                 * 
+                 *
                  * }@Override protected void okPressed() { // TODO
                  * Auto-generated method stub
-                 * 
+                 *
                  * }@Override protected void raiseClosed() { // TODO
                  * Auto-generated method stub
-                 * 
+                 *
                  * } }; jd.initDialogFrame(new
                  * JLabel("<html><table width=\"450\"><tr><td>" + ermsg +
                  * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
@@ -2747,7 +2800,7 @@ public class Desktop extends GDesktop
                  * " Tools->Preferences dialog box to change them.</td></tr></table></html>"
                  * ), true, true, "Web Service Configuration Problem", 450,
                  * 400);
-                 * 
+                 *
                  * jd.waitForInput();
                  */
                 JvOptionPane.showConfirmDialog(desktopPane,
@@ -2782,7 +2835,7 @@ public class Desktop extends GDesktop
    * start a thread to open a URL in the configured browser. Pops up a warning
    * dialog to the user if there is an exception when calling out to the browser
    * to open the URL.
-   * 
+   *
    * @param url
    */
   public static void showUrl(final String url)
@@ -2792,7 +2845,7 @@ public class Desktop extends GDesktop
 
   /**
    * Like showUrl but allows progress handler to be specified
-   * 
+   *
    * @param url
    * @param progress
    *          (null) or object implementing IProgressIndicator
@@ -2816,7 +2869,7 @@ public class Desktop extends GDesktop
           jalview.util.BrowserLauncher.openURL(url);
         } catch (Exception ex)
         {
-          JvOptionPane.showInternalMessageDialog( getDesktopPane(),
+          JvOptionPane.showInternalMessageDialog(getDesktopPane(),
                   MessageManager
                           .getString("label.web_browser_not_found_unix"),
                   MessageManager.getString("label.web_browser_not_found"),
@@ -2845,7 +2898,7 @@ public class Desktop extends GDesktop
 
   /**
    * static hyperlink handler proxy method for use by Jalview's internal windows
-   * 
+   *
    * @param e
    */
   public static void hyperlinkUpdate(HyperlinkEvent e)
@@ -2896,7 +2949,7 @@ public class Desktop extends GDesktop
 
   /**
    * add another dialog thread to the queue
-   * 
+   *
    * @param prompter
    */
   public void addDialogThread(final Runnable prompter)
@@ -2919,7 +2972,7 @@ public class Desktop extends GDesktop
         {
           return;
         }
-       try
+        try
         {
           SwingUtilities.invokeAndWait(prompter);
         } catch (Exception q)
@@ -2942,7 +2995,7 @@ public class Desktop extends GDesktop
    * Outputs an image of the desktop to file in EPS format, after prompting the
    * user for choice of Text or Lineart character rendering (unless a preference
    * has been set). The file name is generated as
-   * 
+   *
    * <pre>
    * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
    * </pre>
@@ -2979,7 +3032,7 @@ public class Desktop extends GDesktop
    * and location last time the view was expanded (if any). However it does not
    * remember the split pane divider location - this is set to match the
    * 'exploding' frame.
-   * 
+   *
    * @param sf
    */
   public void explodeViews(SplitFrame sf)
@@ -3007,7 +3060,7 @@ public class Desktop extends GDesktop
        * AlignmentPanel objects, including their AlignmentViewports, so the
        * cdna/protein relationships between the viewports is carried over to the
        * new split frames.
-       * 
+       *
        * explodedGeometry holds the (x, y) position of the previously exploded
        * SplitFrame, and the (width, height) of the AlignFrame component
        */
@@ -3057,7 +3110,7 @@ public class Desktop extends GDesktop
    * Gather expanded split frames, sharing the same pairs of sequence set ids,
    * back into the given SplitFrame as additional views. Note that the gathered
    * frames may themselves have multiple views.
-   * 
+   *
    * @param source
    */
   public void gatherViews(GSplitFrame source)
@@ -3129,9 +3182,9 @@ public class Desktop extends GDesktop
 
   /**
    * handles the payload of a drag and drop event.
-   * 
+   *
    * TODO refactor to desktop utilities class
-   * 
+   *
    * @param files
    *          - Data source strings extracted from the drop event
    * @param protocols
@@ -3142,6 +3195,7 @@ public class Desktop extends GDesktop
    *          - the payload from the drop event
    * @throws Exception
    */
+  @SuppressWarnings("unchecked")
   public static void transferFromDropTarget(List<Object> files,
           List<DataSourceType> protocols, DropTargetDropEvent evt,
           Transferable t) throws Exception
@@ -3213,7 +3267,7 @@ public class Desktop extends GDesktop
     {
       // Works on Windows and MacOSX
       Cache.log.debug("Drop handled as javaFileListFlavor");
-      for (Object file : (List) t
+      for (File file : (List<File>) t
               .getTransferData(DataFlavor.javaFileListFlavor))
       {
         files.add(file);
@@ -3365,7 +3419,7 @@ public class Desktop extends GDesktop
    * for either Jmol or Chimera) which are currently open. This may optionally
    * be restricted to viewers of a specified class, or viewers linked to a
    * specified alignment panel.
-   * 
+   *
    * @param apanel
    *          if not null, only return viewers linked to this panel
    * @param structureViewerClass