Merge branch 'develop' into features/JAL-2316
authorkiramt <k.mourao@dundee.ac.uk>
Tue, 13 Dec 2016 11:10:05 +0000 (11:10 +0000)
committerkiramt <k.mourao@dundee.ac.uk>
Tue, 13 Dec 2016 11:10:05 +0000 (11:10 +0000)
Conflicts (resolved):
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/gui/Preferences.java

1  2 
help/html/webServices/urllinks.html
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/gui/DasSourceBrowser.java
src/jalview/gui/Desktop.java
src/jalview/gui/IdPanel.java
src/jalview/gui/Preferences.java
src/jalview/gui/WsPreferences.java
src/jalview/jbgui/GSequenceLink.java

@@@ -27,9 -27,7 +27,9 @@@
      <strong>Opening URLs from Jalview</strong><br> Both the applet
      and the desktop application are able to open URLs as 'popups' in
      your web browser. <br> Double-clicking on the ID of a sequence
 -    will open the first URL that can be generated from its sequence ID.
 +    will open the URL designated for 'popups' in the &quot;Connections&quot; tab of the <a
 +    href="../features/preferences.html">Jalview desktop
 +    preferences</a>.
      This is by default the EMBL-EBI site, but you can easily configure your own <a
        href="#urllinks">sequence URL links</a>.
    </p>
      href="../features/preferences.html">Jalview desktop
      preferences</a>, or specified as <a
      href="http://www.jalview.org/examples/appletParameters.html#parameters">applet
 -    parameters</a>. <br> By default the item &quot;EMBL-EBI Search&quot; is added
 -    to this link menu. This link will show a web page in your default
 -    browser with the selected sequence id as part of the URL.<br>
 -    In the preferences dialog box, click <strong>new</strong> to add a
 +    parameters</a>.</p>
 +    <p> 
 +    By default, the list of available links in the preferences dialog box 
 +    contains the item &quot;EMBL-EBI Search&quot;, 
 +    which is set as the URL which opens on double-clicking on a sequence ID, and as a 
 +    menu item in the Links menu. This link will show a web page in your default
 +    browser with the selected sequence id as part of the URL.
 +    <br>
 +    Also by default, the list of available links contains persistent URLs for many common 
 +    bioinformatics databases. These links are downloaded by Jalview from
 +    the <em>identifiers.org</em> website, and the names and URLs are not user editable.
 +    <br>
 +    The list of links is sortable, by clicking on the headers of the table. The list
 +    can be filtered using the free text search box below the table, or the 
 +    &quot;Custom Only&quot; button, which displays only user-defined links.
 +    </p> 
 +    <p>
 +    In the preferences dialog box, the links which appear in the Links menu 
 +    can be configured by selecting or deselecting links in the &quot;In Menu&quot; 
 +    column. The names of selected links will be displayed
 +    on new menu items under the &quot;Link&quot; menu when you right
 +    click on a sequence id.<br> 
 +    You can configure which link is used when double-clicking on a sequence
 +    by selecting or deselecting links in the &quot;On Click&quot; column. Exactly one
 +    link must be configured for double-clicking. Since the link uses the sequence id
 +    to construct the URL to open, the selected link must contain the 
 +    &quot;$SEQUENCE_ID$&quot; token (see below for details of the &quot;$SEQUENCE_ID$&quot;
 +    and other tokens).</p>
 +    <p>
 +    Additionally you can click <strong>new</strong> to add a
      new link, and <strong>edit</strong> to modify an existing link, or <strong>delete</strong>
 -    to remove it.<br> You can name the link, this will be displayed
 -    on a new menu item under the &quot;Link&quot; menu when you right
 -    click on a sequence id. <br> The URL string must contain a
 +    to remove it. Only URLs entered by the user (or the default EMBL-EBI link) may
 +    be edited or deleted. When adding or editing a link, the URL string must contain a
      token that can be replaced with a sequence ID or DB accession ID. The simplest token is
      &quot;$SEQUENCE_ID$&quot;, which will be replaced by the chosen
      sequence id when you click on it. 
      the sequence ID for the sequence (<em>since Jalview 2.10.1</em>).
    </p>
    <p>
+     <strong><a name="warning">Warning dialog about updating
+         your configured URL links</a></strong><br /> In the desktop
+     prior to Jalview 2.10.1, the only way to configure custom links for
+     a particular database cross-reference for a sequence was to give it
+     a name that
+     <em>exactly</em> matched the database source, and a regular
+     expression for filtering out any spurious matches generated when the
+     custom linked was tested against the Sequence's ID string. Since the
+     introduction of the $DB_ACCESSION$ token, however, $SEQUENCE_ID$
+     will not be used for database cross-reference accession strings, and
+     if you have custom links configured, Jalview will raise a warning
+     message so let you know that you may need to update your links to
+     use $DB_ACCESSION$.
+   </p>
+   <p>
      <strong>Regular Expression Substitution</strong><br> A url may
      contain a string of the form $SEQUENCE_ID=/<em>regular
      expression</em>/=$ or $DB_ACCESSION=/<em>regular expression</em>/=$. 
@@@ -125,6 -125,8 +125,8 @@@ action.change_font_tree_panel = Change 
  action.colour = Colour
  action.calculate = Calculate
  action.select_all = Select all
+ action.select_highlighted_columns = Select Highlighted Columns
+ tooltip.select_highlighted_columns = Press B to mark highlighted columns, Ctrl-(or Cmd)-B to toggle, and Alt-B to mark all but highlighted columns 
  action.deselect_all = Deselect all
  action.invert_selection = Invert selection
  action.using_jmol = Using Jmol
@@@ -137,8 -139,7 +139,8 @@@ action.view_flanking_regions = Show fla
  label.view_flanking_regions = Show sequence data either side of the subsequences involved in this alignment
  label.structures_manager = Structures Manager
  label.nickname = Nickname:
 -label.url = URL:
 +label.url = URL
 +label.url\: = URL:
  label.input_file_url = Enter URL or Input File
  label.select_feature = Select feature
  label.name = Name
@@@ -410,6 -411,7 +412,6 @@@ label.couldnt_import_as_vamsas_session 
  label.vamsas_document_import_failed = Vamsas Document Import Failed
  label.couldnt_locate = Couldn't locate {0}
  label.url_not_found = URL not found
 -label.no_link_selected = No link selected
  label.new_sequence_url_link = New sequence URL link
  label.cannot_edit_annotations_in_wrapped_view = Cannot edit annotations in wrapped view
  label.wrapped_view_no_edit = Wrapped view - no edit
@@@ -1272,18 -1274,4 +1274,19 @@@ label.SEQUENCE_ID_no_longer_used = $SEQ
  label.SEQUENCE_ID_for_DB_ACCESSION1 = Please review your URL links in the 'Connections' tab of the Preferences window:
  label.SEQUENCE_ID_for_DB_ACCESSION2 = URL links using '$SEQUENCE_ID$' for DB accessions now use '$DB_ACCESSION$'.
  label.do_not_display_again = Do not display this message again
 -label.output_seq_details = Output Sequence Details to list all database references
 +exception.url_cannot_have_miriam_id = {0} is a MIRIAM id and cannot be used as a custom url name
 +exception.url_cannot_have_duplicate_id = {0} cannot be used as a label for more than one line
 +label.filter = Filter text:
 +action.customfilter = Custom only
 +action.showall = Show All
 +label.insert = Insert:
 +action.seq_id = $SEQUENCE_ID$
 +action.db_acc = $DB_ACCESSION$
 +label.default = On Click
 +label.inmenu = In Menu
 +label.id = ID
 +label.urltooltip = Only one url, which must use a sequence id, can be selected for the 'On Click' option
 +label.edit_sequence_url_link = Edit sequence URL link
 +warn.name_cannot_be_duplicate = URL names must be unique and cannot be MIRIAM ids
- label.invalid_name = Invalid Name !
++label.invalid_name = Invalid Name !
++label.output_seq_details = Output Sequence Details to list all database references
@@@ -122,6 -122,8 +122,8 @@@ action.change_font_tree_panel = Cambia
  action.colour = Color
  action.calculate = Calcular
  action.select_all = Seleccionar Todo
+ action.select_highlighted_columns = Seleccionar columnas resaltadas
+ tooltip.select_highlighted_columns = Presione B para marcar las columnas resaltadas, Ctrl (o Cmd)-B para cambiarlas, y Alt-B para marcar todas menos las columnas resaltadas
  action.deselect_all = Deseleccionar Todo
  action.invert_selection = Invertir selección
  action.using_jmol = Usar Jmol
@@@ -134,8 -136,7 +136,8 @@@ action.view_flanking_regions = Mostrar 
  label.view_flanking_regions = Mostrar los datos de la secuencia a ambos lados de las subsecuencias implicadas en este alineamiento
  label.structures_manager = Administrar estructuras
  label.nickname = Sobrenombre:
 -label.url = URL: 
 +label.url\: = URL:
 +label.url = URL 
  label.input_file_url = Introducir URL en el fichero de entrada
  label.select_feature = Seleccionar característica
  label.name = Nombre
@@@ -378,6 -379,7 +380,6 @@@ label.couldnt_import_as_vamsas_session 
  label.vamsas_document_import_failed =  Fallo en la importación del documento Vamsas
  label.couldnt_locate = No se pudo localizar {0}
  label.url_not_found = URL no encontrada
 -label.no_link_selected = Enlace no seleccionado
  label.new_sequence_url_link = Enlace a una nueva secuencia URL
  label.cannot_edit_annotations_in_wrapped_view = No se pueden editar anotaciones en vista envolvente
  label.wrapped_view_no_edit = Vista envolvente - no editar
@@@ -1273,18 -1275,4 +1275,19 @@@ label.SEQUENCE_ID_no_longer_used = $SEQ
  label.SEQUENCE_ID_for_DB_ACCESSION1 = Por favor, revise sus URLs en la pestaña 'Conexiones' de la ventana de Preferencias:
  label.SEQUENCE_ID_for_DB_ACCESSION2 = URL enlaza usando '$SEQUENCE_ID$' para accesiones DB ahora usar '$DB_ACCESSION$'.
  label.do_not_display_again = No mostrar este mensaje de nuevo
 -label.output_seq_details = Seleccionar Detalles de la secuencia para ver todas
 +exception.url_cannot_have_miriam_id = {0} is a MIRIAM id and cannot be used as a custom url name
 +exception.url_cannot_have_duplicate_id = {0} cannot be used as a label for more than one link
 +label.filter = Filter text:
 +action.customfilter = Custom only
 +action.showall = Show All
 +label.insert = Insert:
 +action.seq_id = $SEQUENCE_ID$
 +action.db_acc = $DB_ACCESSION$
 +label.default = On Click
 +label.inmenu = In Menu
 +label.id = ID
 +label.urltooltip = Only one url, which must use a sequence id, can be selected for the 'On Click' option
 +label.edit_sequence_url_link = Edit sequence URL link
 +warn.name_cannot_be_duplicate = URL names must be unique and cannot be MIRIAM ids
- label.invalid_name = Invalid Name !
++label.invalid_name = Invalid Name !
++label.output_seq_details = Seleccionar Detalles de la secuencia para ver todas
@@@ -453,18 -453,18 +453,18 @@@ public class DasSourceBrowser extends G
      pane12.add(nametf, BorderLayout.EAST);
      panel.add(pane12, BorderLayout.NORTH);
      pane12 = new JPanel(new BorderLayout());
 -    pane12.add(new JLabel(MessageManager.getString("label.url")),
 +    pane12.add(new JLabel(MessageManager.getString("label.url:")),
              BorderLayout.NORTH);
      pane12.add(seqs, BorderLayout.SOUTH);
      pane12.add(urltf, BorderLayout.EAST);
      panel.add(pane12, BorderLayout.SOUTH);
  
-     int reply = JOptionPane.showInternalConfirmDialog(Desktop.desktop,
+     int reply = JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
              panel,
              MessageManager.getString("label.enter_local_das_source"),
-             JOptionPane.OK_CANCEL_OPTION);
+             JvOptionPane.OK_CANCEL_OPTION);
  
-     if (reply != JOptionPane.OK_OPTION)
+     if (reply != JvOptionPane.OK_OPTION)
      {
        return;
      }
  
      if (!sourceRegistry.getSource(nickname).isLocal())
      {
-       JOptionPane
+       JvOptionPane
                .showInternalMessageDialog(
                        Desktop.desktop,
                        MessageManager
                                .getString("label.you_can_only_edit_or_remove_local_das_sources"),
                        MessageManager.getString("label.public_das_source"),
-                       JOptionPane.WARNING_MESSAGE);
+                       JvOptionPane.WARNING_MESSAGE);
        return;
      }
  
      Object[] options = { "Edit", "Remove", "Cancel" };
-     int choice = JOptionPane.showInternalOptionDialog(Desktop.desktop,
+     int choice = JvOptionPane.showInternalOptionDialog(Desktop.desktop,
              "Do you want to edit or remove " + nickname + "?",
              "Edit / Remove Local DAS Source",
-             JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE,
+             JvOptionPane.YES_NO_CANCEL_OPTION, JvOptionPane.QUESTION_MESSAGE,
              null, options, options[2]);
  
      switch (choice)
   */
  package jalview.gui;
  
 -import static jalview.util.UrlConstants.EMBLEBI_STRING;
  import static jalview.util.UrlConstants.SEQUENCE_ID;
  
  import jalview.api.AlignViewportI;
  import jalview.api.AlignmentViewPanel;
  import jalview.bin.Cache;
  import jalview.bin.Jalview;
+ import jalview.io.DataSourceType;
+ import jalview.io.FileFormat;
+ import jalview.io.FileFormatException;
+ import jalview.io.FileFormatI;
  import jalview.io.FileLoader;
- import jalview.io.FormatAdapter;
  import jalview.io.IdentifyFile;
  import jalview.io.JalviewFileChooser;
  import jalview.io.JalviewFileView;
  import jalview.jbgui.GSplitFrame;
  import jalview.jbgui.GStructureViewer;
  import jalview.structure.StructureSelectionManager;
 +import jalview.urls.IdOrgSettings;
  import jalview.util.ImageMaker;
  import jalview.util.MessageManager;
  import jalview.util.Platform;
 +import jalview.util.UrlConstants;
  import jalview.viewmodel.AlignmentViewport;
 +import jalview.ws.UrlDownloadClient;
  import jalview.ws.params.ParamManager;
  
  import java.awt.BorderLayout;
@@@ -76,7 -77,6 +79,7 @@@ import java.beans.PropertyChangeListene
  import java.io.BufferedInputStream;
  import java.io.File;
  import java.io.FileOutputStream;
 +import java.io.IOException;
  import java.net.URL;
  import java.util.ArrayList;
  import java.util.Hashtable;
@@@ -102,7 -102,6 +105,6 @@@ import javax.swing.JFrame
  import javax.swing.JInternalFrame;
  import javax.swing.JLabel;
  import javax.swing.JMenuItem;
- import javax.swing.JOptionPane;
  import javax.swing.JPanel;
  import javax.swing.JPopupMenu;
  import javax.swing.JProgressBar;
@@@ -392,8 -391,6 +394,8 @@@ public class Desktop extends jalview.jb
  
      showNews.setVisible(false);
  
 +    getIdentifiersOrgData();
 +
      checkURLLinks();
  
      this.addWindowListener(new WindowAdapter()
        }
      });
  
-     // displayed.
      // Thread off a new instance of the file chooser - this reduces the time it
      // takes to open it later on.
      new Thread(new Runnable()
        public void run()
        {
          Cache.log.debug("Filechooser init thread started.");
-         new JalviewFileChooser(
-                 jalview.bin.Cache.getProperty("LAST_DIRECTORY"),
-                 jalview.io.AppletFormatAdapter.READABLE_EXTENSIONS,
-                 jalview.io.AppletFormatAdapter.READABLE_FNAMES,
-                 jalview.bin.Cache.getProperty("DEFAULT_FILE_FORMAT"));
+         String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
+         JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
+                 fileFormat);
          Cache.log.debug("Filechooser init thread finished.");
        }
      }).start();
      });
    }
  
 +  public void getIdentifiersOrgData()
 +  {
 +    // Thread off the identifiers fetcher
 +    addDialogThread(new Runnable()
 +    {
 +      @Override
 +      public void run()
 +      {
 +        Cache.log.debug("Downloading data from identifiers.org");
 +        UrlDownloadClient client = new UrlDownloadClient();
 +        try
 +        {
 +          client.download(IdOrgSettings.getUrl(),
 +                  IdOrgSettings.getDownloadLocation());
 +        } catch (IOException e)
 +        {
 +          Cache.log.debug("Exception downloading identifiers.org data"
 +                  + e.getMessage());
 +        }
 +      }
 +    });
 +  }
 +
    @Override
    protected void showNews_actionPerformed(ActionEvent e)
    {
          String file = (String) contents
                  .getTransferData(DataFlavor.stringFlavor);
  
-         String format = new IdentifyFile().identify(file,
-                 FormatAdapter.PASTE);
+         FileFormatI format = new IdentifyFile().identify(file,
+                 DataSourceType.PASTE);
  
-         new FileLoader().LoadFile(file, FormatAdapter.PASTE, format);
+         new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
  
        }
      } catch (Exception ex)
        }
      });
  
+     desktop.add(frame);
      windowMenu.add(menuItem);
  
-     desktop.add(frame);
      frame.toFront();
      try
      {
      // Java's Transferable for native dnd
      evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
      Transferable t = evt.getTransferable();
-     java.util.List<String> files = new ArrayList<String>();
-     java.util.List<String> protocols = new ArrayList<String>();
+     List<String> files = new ArrayList<String>();
+     List<DataSourceType> protocols = new ArrayList<DataSourceType>();
  
      try
      {
          for (int i = 0; i < files.size(); i++)
          {
            String file = files.get(i).toString();
-           String protocol = (protocols == null) ? FormatAdapter.FILE
-                   : (String) protocols.get(i);
-           String format = null;
+           DataSourceType protocol = (protocols == null) ? DataSourceType.FILE
+                   : protocols.get(i);
+           FileFormatI format = null;
  
            if (file.endsWith(".jar"))
            {
-             format = "Jalview";
+             format = FileFormat.Jalview;
  
            }
            else
    @Override
    public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
    {
-     JalviewFileChooser chooser = new JalviewFileChooser(
-             jalview.bin.Cache.getProperty("LAST_DIRECTORY"),
-             jalview.io.AppletFormatAdapter.READABLE_EXTENSIONS,
-             jalview.io.AppletFormatAdapter.READABLE_FNAMES,
-             jalview.bin.Cache.getProperty("DEFAULT_FILE_FORMAT"));
+     String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
+     JalviewFileChooser chooser = JalviewFileChooser.forRead(
+             Cache.getProperty("LAST_DIRECTORY"), fileFormat);
  
      chooser.setFileView(new JalviewFileView());
      chooser.setDialogTitle(MessageManager
      if (value == JalviewFileChooser.APPROVE_OPTION)
      {
        String choice = chooser.getSelectedFile().getPath();
-       jalview.bin.Cache.setProperty("LAST_DIRECTORY", chooser
+       Cache.setProperty("LAST_DIRECTORY", chooser
                .getSelectedFile().getParent());
  
-       String format = null;
-       if (chooser.getSelectedFormat() != null
-               && chooser.getSelectedFormat().equals("Jalview"))
+       FileFormatI format = null;
+       FileFormatI selectedFormat = chooser.getSelectedFormat();
+       if (FileFormat.Jalview.equals(selectedFormat))
        {
-         format = "Jalview";
+         format = FileFormat.Jalview;
        }
        else
        {
-         format = new IdentifyFile().identify(choice, FormatAdapter.FILE);
+         try
+         {
+           format = new IdentifyFile().identify(choice, DataSourceType.FILE);
+         } catch (FileFormatException e)
+         {
+           // format is null
+         }
        }
  
        if (viewport != null)
        {
-         new FileLoader().LoadFile(viewport, choice, FormatAdapter.FILE,
+         new FileLoader().LoadFile(viewport, choice, DataSourceType.FILE,
                  format);
        }
        else
        {
-         new FileLoader().LoadFile(choice, FormatAdapter.FILE, format);
+         new FileLoader().LoadFile(choice, DataSourceType.FILE, format);
        }
      }
    }
        }
      }
  
-     int reply = JOptionPane.showInternalConfirmDialog(desktop, panel,
+     int reply = JvOptionPane.showInternalConfirmDialog(desktop, panel,
              MessageManager.getString("label.input_alignment_from_url"),
-             JOptionPane.OK_CANCEL_OPTION);
+             JvOptionPane.OK_CANCEL_OPTION);
  
-     if (reply != JOptionPane.OK_OPTION)
+     if (reply != JvOptionPane.OK_OPTION)
      {
        return;
      }
      {
        if (viewport != null)
        {
-         new FileLoader().LoadFile(viewport, url, FormatAdapter.URL,
-                 "Jalview");
+         new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
+                 FileFormat.Jalview);
        }
        else
        {
-         new FileLoader().LoadFile(url, FormatAdapter.URL, "Jalview");
+         new FileLoader().LoadFile(url, DataSourceType.URL,
+                 FileFormat.Jalview);
        }
      }
      else
      {
-       String format = new IdentifyFile().identify(url, FormatAdapter.URL);
+       FileFormatI format = null;
+       try
+       {
+         format = new IdentifyFile().identify(url, DataSourceType.URL);
+       } catch (FileFormatException e)
+       {
+         // TODO revise error handling, distinguish between
+         // URL not found and response not valid
+       }
  
-       if (format.equals("URL NOT FOUND"))
+       if (format == null)
        {
-         JOptionPane.showInternalMessageDialog(Desktop.desktop,
+         JvOptionPane.showInternalMessageDialog(Desktop.desktop,
                  MessageManager.formatMessage("label.couldnt_locate",
                          new Object[] { url }), MessageManager
                          .getString("label.url_not_found"),
-                 JOptionPane.WARNING_MESSAGE);
+                 JvOptionPane.WARNING_MESSAGE);
  
          return;
        }
  
        if (viewport != null)
        {
-         new FileLoader().LoadFile(viewport, url, FormatAdapter.URL, format);
+         new FileLoader()
+                 .LoadFile(viewport, url, DataSourceType.URL, format);
        }
        else
        {
-         new FileLoader().LoadFile(url, FormatAdapter.URL, format);
+         new FileLoader().LoadFile(url, DataSourceType.URL, format);
        }
      }
    }
    public void aboutMenuItem_actionPerformed(ActionEvent e)
    {
      // StringBuffer message = getAboutMessage(false);
-     // JOptionPane.showInternalMessageDialog(Desktop.desktop,
+     // JvOptionPane.showInternalMessageDialog(Desktop.desktop,
      //
-     // message.toString(), "About Jalview", JOptionPane.INFORMATION_MESSAGE);
+     // message.toString(), "About Jalview", JvOptionPane.INFORMATION_MESSAGE);
      new Thread(new Runnable()
      {
        @Override
    public void saveState_actionPerformed(ActionEvent e)
    {
      JalviewFileChooser chooser = new JalviewFileChooser(
-             jalview.bin.Cache.getProperty("LAST_DIRECTORY"),
-             new String[] { "jvp" }, new String[] { "Jalview Project" },
-             "Jalview Project");
+             Cache.getProperty("LAST_DIRECTORY"), "jvp", "Jalview Project");
  
      chooser.setFileView(new JalviewFileView());
      chooser.setDialogTitle(MessageManager.getString("label.save_state"));
              Cache.log.error(
                      "Problems whilst trying to save to " + choice.getName(),
                      ex);
-             JOptionPane.showMessageDialog(me, MessageManager.formatMessage(
+             JvOptionPane.showMessageDialog(me, MessageManager.formatMessage(
                      "label.error_whilst_saving_current_state_to",
                      new Object[] { choice.getName() }), MessageManager
                      .getString("label.couldnt_save_project"),
-                     JOptionPane.WARNING_MESSAGE);
+                     JvOptionPane.WARNING_MESSAGE);
            }
            setProgressBar(null, choice.hashCode());
          }
    public void loadState_actionPerformed(ActionEvent e)
    {
      JalviewFileChooser chooser = new JalviewFileChooser(
-             jalview.bin.Cache.getProperty("LAST_DIRECTORY"), new String[] {
+             Cache.getProperty("LAST_DIRECTORY"), new String[] {
                  "jvp", "jar" }, new String[] { "Jalview Project",
                  "Jalview Project (old)" }, "Jalview Project");
      chooser.setFileView(new JalviewFileView());
        final File selectedFile = chooser.getSelectedFile();
        setProjectFile(selectedFile);
        final String choice = selectedFile.getAbsolutePath();
-       jalview.bin.Cache.setProperty("LAST_DIRECTORY",
+       Cache.setProperty("LAST_DIRECTORY",
                selectedFile.getParent());
        new Thread(new Runnable()
        {
            {
              Cache.log.error("Problems whilst loading project from "
                      + choice, ex);
-             JOptionPane.showMessageDialog(Desktop.desktop, MessageManager
+             JvOptionPane.showMessageDialog(Desktop.desktop, MessageManager
                      .formatMessage(
                              "label.error_whilst_loading_project_from",
                              new Object[] { choice }), MessageManager
                      .getString("label.couldnt_load_project"),
-                     JOptionPane.WARNING_MESSAGE);
+                     JvOptionPane.WARNING_MESSAGE);
            }
            setProgressBar(null, choice.hashCode());
          }
          String fle = chooser.getSelectedFile().toString();
          if (!vamsasImport(chooser.getSelectedFile()))
          {
-           JOptionPane
+           JvOptionPane
                    .showInternalMessageDialog(
                            Desktop.desktop,
                            MessageManager.formatMessage(
                                    new Object[] { fle }),
                            MessageManager
                                    .getString("label.vamsas_document_import_failed"),
-                           JOptionPane.ERROR_MESSAGE);
+                           JvOptionPane.ERROR_MESSAGE);
          }
        }
      }
      if (v_client != null)
      {
        JalviewFileChooser chooser = new JalviewFileChooser(
-               jalview.bin.Cache.getProperty("LAST_DIRECTORY"), new String[]
-               { "vdj" }, // TODO: VAMSAS DOCUMENT EXTENSION is VDJ
-               new String[] { "Vamsas Document" }, "Vamsas Document");
+               Cache.getProperty("LAST_DIRECTORY"), "vdj",// TODO: VAMSAS DOCUMENT EXTENSION is VDJ
+               "Vamsas Document");
  
        chooser.setFileView(new JalviewFileView());
        chooser.setDialogTitle(MessageManager
          JPanel progpanel = addProgressPanel(MessageManager.formatMessage(
                  "label.saving_vamsas_doc",
                  new Object[] { choice.getName() }));
-         jalview.bin.Cache.setProperty("LAST_DIRECTORY", choice.getParent());
+         Cache.setProperty("LAST_DIRECTORY", choice.getParent());
          String warnmsg = null;
          String warnttl = null;
          try
          removeProgressPanel(progpanel);
          if (warnmsg != null)
          {
-           JOptionPane.showInternalMessageDialog(Desktop.desktop,
+           JvOptionPane.showInternalMessageDialog(Desktop.desktop,
  
-           warnmsg, warnttl, JOptionPane.ERROR_MESSAGE);
+           warnmsg, warnttl, JvOptionPane.ERROR_MESSAGE);
          }
        }
      }
     */
    public void setVamsasUpdate(boolean b)
    {
-     jalview.bin.Cache.log.debug("Setting gui for Vamsas update "
+     Cache.log.debug("Setting gui for Vamsas update "
              + (b ? "in progress" : "finished"));
  
      if (vamUpdate != null)
          {
            // check what the actual links are - if it's just the default don't
            // bother with the warning
 -          Vector<String> links = Preferences.sequenceURLLinks;
 +          Vector<String> links = Preferences.sequenceUrlLinks
 +                  .getLinksForMenu();
  
            // only need to check links if there is one with a
            // SEQUENCE_ID which is not the default EMBL_EBI link
            while (li.hasNext())
            {
              String link = li.next();
 -            if (link.contains(SEQUENCE_ID) && !link.equals(EMBLEBI_STRING))
 +            if (link.contains(SEQUENCE_ID)
 +                    && !link.equals(UrlConstants.DEFAULT_STRING))
              {
                check = true;
                int barPos = link.indexOf("|");
            });
            msgPanel.add(jcb);
  
-           JOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
+           JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
                    MessageManager
                            .getString("label.SEQUENCE_ID_no_longer_used"),
-                   JOptionPane.WARNING_MESSAGE);
+                   JvOptionPane.WARNING_MESSAGE);
          }
        }
      });
      } catch (Exception ex)
      {
        jalview.bin.Cache.log.error("Groovy Shell Creation failed.", ex);
-       JOptionPane.showInternalMessageDialog(Desktop.desktop,
+       JvOptionPane.showInternalMessageDialog(Desktop.desktop,
  
        MessageManager.getString("label.couldnt_create_groovy_shell"),
                MessageManager.getString("label.groovy_support_failed"),
-               JOptionPane.ERROR_MESSAGE);
+               JvOptionPane.ERROR_MESSAGE);
      }
    }
  
                   * 
                   * jd.waitForInput();
                   */
-                 JOptionPane
+                 JvOptionPane
                          .showConfirmDialog(
                                  Desktop.desktop,
                                  new JLabel(
                                                  + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
                                                  + " Tools->Preferences dialog box to change them.</p></html>"),
                                  "Web Service Configuration Problem",
-                                 JOptionPane.DEFAULT_OPTION,
-                                 JOptionPane.ERROR_MESSAGE);
+                                 JvOptionPane.DEFAULT_OPTION,
+                                 JvOptionPane.ERROR_MESSAGE);
                  serviceChangedDialog = null;
  
                }
            jalview.util.BrowserLauncher.openURL(url);
          } catch (Exception ex)
          {
-           JOptionPane.showInternalMessageDialog(Desktop.desktop,
+           JvOptionPane.showInternalMessageDialog(Desktop.desktop,
                    MessageManager
                            .getString("label.web_browser_not_found_unix"),
                    MessageManager.getString("label.web_browser_not_found"),
-                   JOptionPane.WARNING_MESSAGE);
+                   JvOptionPane.WARNING_MESSAGE);
  
            ex.printStackTrace();
          }
    }
  
    public static void transferFromDropTarget(List<String> files,
-           List<String> protocols, DropTargetDropEvent evt, Transferable t)
+           List<DataSourceType> protocols, DropTargetDropEvent evt,
+           Transferable t)
            throws Exception
    {
  
                .getTransferData(DataFlavor.javaFileListFlavor))
        {
          files.add(((File) file).toString());
-         protocols.add(FormatAdapter.FILE);
+         protocols.add(DataSourceType.FILE);
        }
      }
      else
        {
          Cache.log.debug("Adding missing FILE protocol for "
                  + files.get(protocols.size()));
-         protocols.add(FormatAdapter.FILE);
+         protocols.add(DataSourceType.FILE);
        }
        for (java.util.StringTokenizer st = new java.util.StringTokenizer(
                data, "\r\n"); st.hasMoreTokens();)
          java.net.URI uri = new java.net.URI(s);
          if (uri.getScheme().toLowerCase().startsWith("http"))
          {
-           protocols.add(FormatAdapter.URL);
+           protocols.add(DataSourceType.URL);
            files.add(uri.toString());
          }
          else
          {
            // otherwise preserve old behaviour: catch all for file objects
            java.io.File file = new java.io.File(uri);
-           protocols.add(FormatAdapter.FILE);
+           protocols.add(DataSourceType.FILE);
            files.add(file.toString());
          }
        }
@@@ -27,6 -27,7 +27,6 @@@ import jalview.datamodel.SequenceI
  import jalview.io.SequenceAnnotationReport;
  import jalview.util.MessageManager;
  import jalview.util.Platform;
 -import jalview.util.UrlLink;
  import jalview.viewmodel.AlignmentViewport;
  
  import java.awt.BorderLayout;
@@@ -108,8 -109,8 +108,8 @@@ public class IdPanel extends JPanel imp
      if (seq > -1 && seq < av.getAlignment().getHeight())
      {
        SequenceI sequence = av.getAlignment().getSequenceAt(seq);
-       StringBuffer tip = new StringBuffer(64);
-       seqAnnotReport.createSequenceAnnotationReport(tip, sequence,
+       StringBuilder tip = new StringBuilder(64);
+       seqAnnotReport.createTooltipAnnotationReport(tip, sequence,
                av.isShowDBRefs(), av.isShowNPFeats(),
                sp.seqCanvas.fr.getMinMax());
        setToolTipText(JvSwingUtils.wrapTooltip(true,
        return;
      }
  
 -    Vector links = Preferences.sequenceURLLinks;
 -    if (links == null || links.size() < 1)
 -    {
 -      return;
 -    }
 -
      int seq = alignPanel.getSeqPanel().findSeq(e);
 -    String url = null;
 -    int i = 0;
      String id = av.getAlignment().getSequenceAt(seq).getName();
 -    while (url == null && i < links.size())
 -    {
 -      // DEFAULT LINK IS FIRST IN THE LINK LIST
 -      // BUT IF ITS A REGEX AND DOES NOT MATCH THE NEXT ONE WILL BE TRIED
 -      url = links.elementAt(i++).toString();
 -      jalview.util.UrlLink urlLink = null;
 -      try
 -      {
 -        urlLink = new UrlLink(url);
 -      } catch (Exception foo)
 -      {
 -        jalview.bin.Cache.log.error("Exception for URLLink '" + url + "'",
 -                foo);
 -        url = null;
 -        continue;
 -      }
 +    String url = Preferences.sequenceUrlLinks.getDefaultUrl(id);
  
 -      if (urlLink.usesDBAccession())
 -      {
 -        // this URL requires an accession id, not the name of a sequence
 -        url = null;
 -        continue;
 -      }
 -
 -      if (!urlLink.isValid())
 -      {
 -        jalview.bin.Cache.log.error(urlLink.getInvalidMessage());
 -        url = null;
 -        continue;
 -      }
 -
 -      String urls[] = urlLink.makeUrls(id, true);
 -      if (urls == null || urls[0] == null || urls[0].length() < 4)
 -      {
 -        url = null;
 -        continue;
 -      }
 -      // just take first URL made from regex
 -      url = urls[1];
 -    }
      try
      {
        jalview.util.BrowserLauncher.openURL(url);
      } catch (Exception ex)
      {
-       JOptionPane.showInternalMessageDialog(Desktop.desktop,
+       JvOptionPane.showInternalMessageDialog(Desktop.desktop,
                MessageManager.getString("label.web_browser_not_found_unix"),
                MessageManager.getString("label.web_browser_not_found"),
-               JOptionPane.WARNING_MESSAGE);
+               JvOptionPane.WARNING_MESSAGE);
        ex.printStackTrace();
      }
    }
      Sequence sq = (Sequence) av.getAlignment().getSequenceAt(seq2);
      // build a new links menu based on the current links + any non-positional
      // features
 -    Vector<String> nlinks = new Vector<String>(Preferences.sequenceURLLinks);
 +    Vector<String> nlinks = Preferences.sequenceUrlLinks.getLinksForMenu();
      SequenceFeature sfs[] = sq == null ? null : sq.getSequenceFeatures();
      if (sfs != null)
      {
   */
  package jalview.gui;
  
 -import static jalview.util.UrlConstants.DB_ACCESSION;
 -import static jalview.util.UrlConstants.EMBLEBI_STRING;
 -import static jalview.util.UrlConstants.SEQUENCE_ID;
 -import static jalview.util.UrlConstants.SRS_STRING;
 -
  import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
  import jalview.bin.Cache;
  import jalview.gui.Help.HelpId;
  import jalview.gui.StructureViewer.ViewerType;
+ import jalview.io.FileFormatI;
  import jalview.io.JalviewFileChooser;
  import jalview.io.JalviewFileView;
  import jalview.jbgui.GPreferences;
  import jalview.jbgui.GSequenceLink;
  import jalview.schemes.ColourSchemeProperty;
 +import jalview.urls.UrlLinkTableModel;
 +import jalview.urls.api.UrlProviderFactoryI;
 +import jalview.urls.api.UrlProviderI;
 +import jalview.urls.desktop.DesktopUrlProviderFactory;
  import jalview.util.MessageManager;
  import jalview.util.Platform;
 +import jalview.util.UrlConstants;
  import jalview.ws.sifts.SiftsSettings;
  
  import java.awt.BorderLayout;
  import java.awt.Color;
 +import java.awt.Component;
  import java.awt.Dimension;
  import java.awt.Font;
  import java.awt.event.ActionEvent;
@@@ -49,25 -49,14 +50,24 @@@ import java.awt.event.MouseEvent
  import java.io.File;
  import java.util.ArrayList;
  import java.util.List;
 -import java.util.StringTokenizer;
 -import java.util.Vector;
  
  import javax.help.HelpSetException;
  import javax.swing.JColorChooser;
  import javax.swing.JFileChooser;
  import javax.swing.JInternalFrame;
- import javax.swing.JOptionPane;
  import javax.swing.JPanel;
 +import javax.swing.ListSelectionModel;
 +import javax.swing.RowFilter;
 +import javax.swing.RowSorter;
 +import javax.swing.SortOrder;
 +import javax.swing.event.DocumentEvent;
 +import javax.swing.event.DocumentListener;
 +import javax.swing.event.ListSelectionEvent;
 +import javax.swing.event.ListSelectionListener;
 +import javax.swing.table.TableCellRenderer;
 +import javax.swing.table.TableColumn;
 +import javax.swing.table.TableModel;
 +import javax.swing.table.TableRowSorter;
  
  import ext.edu.ucsf.rbvi.strucviz2.StructureManager;
  
@@@ -113,9 -102,7 +113,9 @@@ public class Preferences extends GPrefe
     * Holds name and link separated with | character. Sequence ID must be
     * $SEQUENCE_ID$ or $SEQUENCE_ID=/.possible | chars ./=$
     */
 -  public static Vector<String> sequenceURLLinks;
 +  public static UrlProviderI sequenceUrlLinks;
 +
 +  public static UrlLinkTableModel dataModel;
  
    /**
     * Holds name and link separated with | character. Sequence IDS and Sequences
    public static List<String> groupURLLinks;
    static
    {
 -    String string = Cache.getDefault("SEQUENCE_LINKS", EMBLEBI_STRING);
 -    sequenceURLLinks = new Vector<String>();
 -
 -    try
 +    // get links selected to be in the menu (SEQUENCE_LINKS)
 +    // and links entered by the user but not selected (STORED_LINKS)
 +    String inMenuString = Cache.getDefault("SEQUENCE_LINKS", "");
 +    String notInMenuString = Cache.getDefault("STORED_LINKS", "");
 +    String defaultUrl = Cache.getDefault("DEFAULT_URL",
 +            UrlConstants.DEFAULT_LABEL);
 +
 +    // if both links lists are empty, add the DEFAULT_URL link
 +    // otherwise we assume the default link is in one of the lists
 +    if (inMenuString.isEmpty() && notInMenuString.isEmpty())
      {
 -      StringTokenizer st = new StringTokenizer(string, "|");
 -      while (st.hasMoreElements())
 -      {
 -        String name = st.nextToken();
 -        String url = st.nextToken();
 -        // check for '|' within a regex
 -        int rxstart = url.indexOf("$" + DB_ACCESSION + "$");
 -        if (rxstart == -1)
 -        {
 -          rxstart = url.indexOf("$" + SEQUENCE_ID + "$");
 -        }
 -        while (rxstart == -1 && url.indexOf("/=$") == -1)
 -        {
 -          url = url + "|" + st.nextToken();
 -        }
 -        sequenceURLLinks.addElement(name + "|" + url);
 -      }
 -    } catch (Exception ex)
 -    {
 -      System.out.println(ex + "\nError parsing sequence links");
 -    }
 -    {
 -      // upgrade old SRS link
 -      int srsPos = sequenceURLLinks.indexOf(SRS_STRING);
 -      if (srsPos > -1)
 -      {
 -        sequenceURLLinks.setElementAt(EMBLEBI_STRING, srsPos);
 -      }
 +      inMenuString = UrlConstants.DEFAULT_STRING;
      }
 +    UrlProviderFactoryI factory = new DesktopUrlProviderFactory(defaultUrl,
 +            inMenuString, notInMenuString);
 +    sequenceUrlLinks = factory.createUrlProvider();
 +    dataModel = new UrlLinkTableModel(sequenceUrlLinks);
  
      /**
       * TODO: reformulate groupURL encoding so two or more can be stored in the
      groupURLLinks = new ArrayList<String>();
    }
  
 -  Vector<String> nameLinks, urlLinks;
 -
    JInternalFrame frame;
  
    DasSourceBrowser dasSource;
      /*
       * Set Connections tab defaults
       */
 -    nameLinks = new Vector<String>();
 -    urlLinks = new Vector<String>();
 -    for (int i = 0; i < sequenceURLLinks.size(); i++)
 +
 +    // set up sorting
 +    linkUrlTable.setModel(dataModel);
 +    final TableRowSorter<TableModel> sorter = new TableRowSorter<>(
 +            linkUrlTable.getModel());
 +    linkUrlTable.setRowSorter(sorter);
 +    List<RowSorter.SortKey> sortKeys = new ArrayList<>();
 +
 +    UrlLinkTableModel m = (UrlLinkTableModel) linkUrlTable.getModel();
 +    sortKeys.add(new RowSorter.SortKey(m.getDefaultColumn(),
 +            SortOrder.DESCENDING));
 +    sortKeys.add(new RowSorter.SortKey(m.getSelectedColumn(),
 +            SortOrder.DESCENDING));
 +    sortKeys.add(new RowSorter.SortKey(m.getNameColumn(),
 +            SortOrder.ASCENDING));
 +
 +    sorter.setSortKeys(sortKeys);
 +    sorter.sort();
 +    
 +    // set up filtering
 +    ActionListener onReset;
 +    onReset = new ActionListener()
      {
 -      String link = sequenceURLLinks.elementAt(i).toString();
 -      nameLinks.addElement(link.substring(0, link.indexOf("|")));
 -      urlLinks.addElement(link.substring(link.indexOf("|") + 1));
 -    }
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        filterTB.setText("");
 +        sorter.setRowFilter(RowFilter.regexFilter(""));
 +      }
 +
 +    };
 +    doReset.addActionListener(onReset);
 +
 +    // filter to display only custom urls
 +    final RowFilter<TableModel, Object> customUrlFilter = new RowFilter<TableModel, Object>()
 +    {
 +      @Override
 +      public boolean include(
 +              Entry<? extends TableModel, ? extends Object> entry)
 +      {
 +        return ((UrlLinkTableModel) entry.getModel()).isUserEntry(entry);
 +      }
 +    };
 +
 +    final TableRowSorter<TableModel> customSorter = new TableRowSorter<>(
 +            linkUrlTable.getModel());
 +    customSorter.setRowFilter(customUrlFilter);
 +
 +    ActionListener onCustomOnly;
 +    onCustomOnly = new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        filterTB.setText("");
 +        sorter.setRowFilter(customUrlFilter);
 +      }
 +    };
 +    userOnly.addActionListener(onCustomOnly);
 +
 +    filterTB.getDocument().addDocumentListener(new DocumentListener()
 +    {
 +      String caseInsensitiveFlag = "(?i)";
  
 -    updateLinkData();
 +      @Override
 +      public void changedUpdate(DocumentEvent e)
 +      {
 +        sorter.setRowFilter(RowFilter.regexFilter(caseInsensitiveFlag
 +                + filterTB.getText()));
 +      }
 +
 +      @Override
 +      public void removeUpdate(DocumentEvent e)
 +      {
 +        sorter.setRowFilter(RowFilter.regexFilter(caseInsensitiveFlag
 +                + filterTB.getText()));
 +      }
 +
 +      @Override
 +      public void insertUpdate(DocumentEvent e)
 +      {
 +        sorter.setRowFilter(RowFilter.regexFilter(caseInsensitiveFlag
 +                + filterTB.getText()));
 +      }
 +    });
 +
 +    // set up list selection functionality
 +    linkUrlTable.getSelectionModel().addListSelectionListener(
 +            new UrlListSelectionHandler());
 +
 +    // set up radio buttons
 +    int onClickCol = ((UrlLinkTableModel) linkUrlTable.getModel())
 +            .getDefaultColumn();
 +    String onClickName = linkUrlTable.getColumnName(onClickCol);
 +    linkUrlTable.getColumn(onClickName).setCellRenderer(
 +               new RadioButtonRenderer());
 +    linkUrlTable.getColumn(onClickName)
 +            .setCellEditor(new RadioButtonEditor());
 +
 +    // get boolean columns and resize those to min possible
 +    for (int column = 0; column < linkUrlTable.getColumnCount(); column++)
 +    {
 +      if (linkUrlTable.getModel().getColumnClass(column)
 +              .equals(Boolean.class))
 +      {
 +        TableColumn tableColumn = linkUrlTable.getColumnModel().getColumn(
 +                column);
 +        int preferredWidth = tableColumn.getMinWidth();
 +
 +        TableCellRenderer cellRenderer = linkUrlTable.getCellRenderer(0,
 +                column);
 +        Component c = linkUrlTable.prepareRenderer(cellRenderer, 0, column);
 +        int cwidth = c.getPreferredSize().width
 +                + linkUrlTable.getIntercellSpacing().width;
 +        preferredWidth = Math.max(preferredWidth, cwidth);
 +
 +        tableColumn.setPreferredWidth(preferredWidth);
 +      }
 +    }
  
      useProxy.setSelected(Cache.getDefault("USE_PROXY", false));
 +    useProxy_actionPerformed(); // make sure useProxy is correctly initialised
      proxyServerTB.setEnabled(useProxy.isSelected());
      proxyPortTB.setEnabled(useProxy.isSelected());
      proxyServerTB.setText(Cache.getDefault("PROXY_SERVER", ""));
  
      jalview.util.BrowserLauncher.resetBrowser();
  
 -    if (nameLinks.size() > 0)
 +    // save user-defined and selected links
 +    String menuLinks = sequenceUrlLinks.writeUrlsAsString(true);
 +    if (menuLinks.isEmpty())
 +    {
 +      Cache.applicationProperties.remove("SEQUENCE_LINKS");
 +    }
 +    else
      {
 -      StringBuffer links = new StringBuffer();
 -      sequenceURLLinks = new Vector<String>();
 -      for (int i = 0; i < nameLinks.size(); i++)
 -      {
 -        sequenceURLLinks.addElement(nameLinks.elementAt(i) + "|"
 -                + urlLinks.elementAt(i));
 -        links.append(sequenceURLLinks.elementAt(i).toString());
 -        links.append("|");
 -      }
 -      // remove last "|"
 -      links.setLength(links.length() - 1);
        Cache.applicationProperties.setProperty("SEQUENCE_LINKS",
 -              links.toString());
 +              menuLinks.toString());
 +    }
 +
 +    String nonMenuLinks = sequenceUrlLinks.writeUrlsAsString(false);
 +    if (nonMenuLinks.isEmpty())
 +    {
 +      Cache.applicationProperties.remove("STORED_LINKS");
      }
      else
      {
 -      Cache.applicationProperties.remove("SEQUENCE_LINKS");
 -      sequenceURLLinks.clear();
 +      Cache.applicationProperties.setProperty("STORED_LINKS",
 +              nonMenuLinks.toString());
      }
  
 +    Cache.applicationProperties.setProperty("DEFAULT_URL",
 +            sequenceUrlLinks.getDefaultUrlId());
 +
      Cache.applicationProperties.setProperty("USE_PROXY",
              Boolean.toString(useProxy.isSelected()));
  
    @Override
    public void startupFileTextfield_mouseClicked()
    {
-     JalviewFileChooser chooser = new JalviewFileChooser(
-             jalview.bin.Cache.getProperty("LAST_DIRECTORY"), new String[] {
-                 "fa, fasta, fastq", "aln", "pfam", "msf", "pir", "blc",
-                 "jar" }, new String[] { "Fasta", "Clustal", "PFAM", "MSF",
-                 "PIR", "BLC", "Jalview" },
-             jalview.bin.Cache.getProperty("DEFAULT_FILE_FORMAT"));
+     String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
+     JalviewFileChooser chooser = JalviewFileChooser.forRead(
+             Cache.getProperty("LAST_DIRECTORY"), fileFormat);
      chooser.setFileView(new JalviewFileView());
      chooser.setDialogTitle(MessageManager
              .getString("label.select_startup_file"));
  
      if (value == JalviewFileChooser.APPROVE_OPTION)
      {
-       jalview.bin.Cache.applicationProperties.setProperty(
-               "DEFAULT_FILE_FORMAT", chooser.getSelectedFormat());
+       FileFormatI format = chooser.getSelectedFormat();
+       if (format != null)
+       {
+         Cache.applicationProperties.setProperty("DEFAULT_FILE_FORMAT",
+                 format.toString());
+       }
        startupFileTextfield.setText(chooser.getSelectedFile()
                .getAbsolutePath());
      }
    @Override
    public void newLink_actionPerformed(ActionEvent e)
    {
 -
      GSequenceLink link = new GSequenceLink();
      boolean valid = false;
      while (!valid)
      {
-       if (JOptionPane.showInternalConfirmDialog(Desktop.desktop, link,
+       if (JvOptionPane.showInternalConfirmDialog(Desktop.desktop, link,
                MessageManager.getString("label.new_sequence_url_link"),
-               JOptionPane.OK_CANCEL_OPTION, -1, null) == JOptionPane.OK_OPTION)
+               JvOptionPane.OK_CANCEL_OPTION, -1, null) == JvOptionPane.OK_OPTION)
        {
          if (link.checkValid())
          {
 -          nameLinks.addElement(link.getName());
 -          urlLinks.addElement(link.getURL());
 -          updateLinkData();
 -          valid = true;
 +          if (((UrlLinkTableModel) linkUrlTable.getModel())
 +                  .isUniqueName(link.getName()))
 +          {
 +            ((UrlLinkTableModel) linkUrlTable.getModel()).insertRow(
 +                    link.getName(), link.getURL());
 +            valid = true;
 +          }
 +          else
 +          {
 +            link.notifyDuplicate();
 +            continue;
 +          }
          }
        }
        else
    {
      GSequenceLink link = new GSequenceLink();
  
 -    int index = linkNameList.getSelectedIndex();
 +    int index = linkUrlTable.getSelectedRow();
      if (index == -1)
      {
 -      JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 -              MessageManager.getString("label.no_link_selected"),
 -              MessageManager.getString("label.no_link_selected"),
 -              JvOptionPane.WARNING_MESSAGE);
 +      // button no longer enabled if row is not selected
 +      Cache.log.debug("Edit with no row selected in linkUrlTable");
        return;
      }
  
 -    link.setName(nameLinks.elementAt(index).toString());
 -    link.setURL(urlLinks.elementAt(index).toString());
 +    link.setName(linkUrlTable.getValueAt(index, 0).toString());
 +    link.setURL(linkUrlTable.getValueAt(index, 1).toString());
  
      boolean valid = false;
      while (!valid)
      {
-       if (JOptionPane.showInternalConfirmDialog(Desktop.desktop, link,
 -
+       if (JvOptionPane.showInternalConfirmDialog(Desktop.desktop, link,
 -              MessageManager.getString("label.new_sequence_url_link"),
 +              MessageManager.getString("label.edit_sequence_url_link"),
-               JOptionPane.OK_CANCEL_OPTION, -1, null) == JOptionPane.OK_OPTION)
+               JvOptionPane.OK_CANCEL_OPTION, -1, null) == JvOptionPane.OK_OPTION)
        {
          if (link.checkValid())
          {
 -          nameLinks.setElementAt(link.getName(), index);
 -          urlLinks.setElementAt(link.getURL(), index);
 -          updateLinkData();
 -          valid = true;
 +          if (((UrlLinkTableModel) linkUrlTable.getModel())
 +                  .isUniqueName(link.getName()))
 +          {
 +            linkUrlTable.setValueAt(link.getName(), index, 0);
 +            linkUrlTable.setValueAt(link.getURL(), index, 1);
 +            valid = true;
 +          }
 +          else
 +          {
 +            link.notifyDuplicate();
 +            continue;
 +          }
          }
        }
 -
        else
        {
          break;
    @Override
    public void deleteLink_actionPerformed(ActionEvent e)
    {
 -    int index = linkNameList.getSelectedIndex();
 +    int index = linkUrlTable.getSelectedRow();
 +    int modelIndex = -1;
      if (index == -1)
      {
 -      JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 -              MessageManager.getString("label.no_link_selected"),
 -              MessageManager.getString("label.no_link_selected"),
 -              JvOptionPane.WARNING_MESSAGE);
 +      // button no longer enabled if row is not selected
 +      Cache.log.debug("Delete with no row selected in linkUrlTable");
        return;
      }
 -    nameLinks.removeElementAt(index);
 -    urlLinks.removeElementAt(index);
 -    updateLinkData();
 -  }
 +    else
 +    {
 +      modelIndex = linkUrlTable.convertRowIndexToModel(index);
 +    }
  
 -  void updateLinkData()
 -  {
 -    linkNameList.setListData(nameLinks);
 -    linkURLList.setListData(urlLinks);
 +    // make sure we use the model index to delete, and not the table index
 +    ((UrlLinkTableModel) linkUrlTable.getModel()).removeRow(modelIndex);
    }
  
 +
    @Override
    public void defaultBrowser_mouseClicked(MouseEvent e)
    {
        }
      } catch (NumberFormatException x)
      {
-       JOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
+       JvOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
                .getString("warn.user_defined_width_requirements"),
                MessageManager.getString("label.invalid_id_column_width"),
-               JOptionPane.WARNING_MESSAGE);
+               JvOptionPane.WARNING_MESSAGE);
        userIdWidth.setText("");
      }
    }
        File f = new File(chimeraPath.getText());
        if (!f.canExecute())
        {
-         JOptionPane.showInternalMessageDialog(Desktop.desktop,
+         JvOptionPane.showInternalMessageDialog(Desktop.desktop,
                  MessageManager.getString("label.invalid_chimera_path"),
                  MessageManager.getString("label.invalid_name"),
-                 JOptionPane.ERROR_MESSAGE);
+                 JvOptionPane.ERROR_MESSAGE);
          return false;
        }
      }
      if (!found)
      {
        String[] options = { "OK", "Help" };
-       int showHelp = JOptionPane.showInternalOptionDialog(
+       int showHelp = JvOptionPane.showInternalOptionDialog(
                Desktop.desktop,
                JvSwingUtils.wrapTooltip(true,
                        MessageManager.getString("label.chimera_missing")),
-               "", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE,
+               "", JvOptionPane.YES_NO_OPTION, JvOptionPane.WARNING_MESSAGE,
                null, options, options[0]);
-       if (showHelp == JOptionPane.NO_OPTION)
+       if (showHelp == JvOptionPane.NO_OPTION)
        {
          try
          {
        return name.hashCode() + code.hashCode();
      }
    }
 +  
 +  private class UrlListSelectionHandler implements ListSelectionListener
 +  {
 +
 +    @Override
 +    public void valueChanged(ListSelectionEvent e)
 +    {
 +      ListSelectionModel lsm = (ListSelectionModel) e.getSource();
 +
 +      int index = lsm.getMinSelectionIndex();
 +      if (index == -1)
 +      {
 +        // no selection, so disable delete/edit buttons
 +        editLink.setEnabled(false);
 +        deleteLink.setEnabled(false);
 +        return;
 +      }
 +      int modelIndex = linkUrlTable.convertRowIndexToModel(index);
 +
 +      // enable/disable edit and delete link buttons
 +      if (((UrlLinkTableModel) linkUrlTable.getModel())
 +              .isRowDeletable(modelIndex))
 +      {
 +        deleteLink.setEnabled(true);
 +      }
 +      else
 +      {
 +        deleteLink.setEnabled(false);
 +      }
 +
 +      if (((UrlLinkTableModel) linkUrlTable.getModel())
 +              .isRowEditable(modelIndex))
 +      {
 +        editLink.setEnabled(true);
 +      }
 +      else
 +      {
 +        editLink.setEnabled(false);
 +      }
 +    }
 +}
  }
@@@ -454,16 -454,16 +454,16 @@@ public class WsPreferences extends GWsP
      JTextField urltf = new JTextField(url, 40);
      JPanel panel = new JPanel(new BorderLayout());
      JPanel pane12 = new JPanel(new BorderLayout());
 -    pane12.add(new JLabel(MessageManager.getString("label.url")),
 +    pane12.add(new JLabel(MessageManager.getString("label.url:")),
              BorderLayout.CENTER);
      pane12.add(urltf, BorderLayout.EAST);
      panel.add(pane12, BorderLayout.NORTH);
      boolean valid = false;
-     int resp = JOptionPane.CANCEL_OPTION;
+     int resp = JvOptionPane.CANCEL_OPTION;
      while (!valid
-             && (resp = JOptionPane.showInternalConfirmDialog(
+             && (resp = JvOptionPane.showInternalConfirmDialog(
                      Desktop.desktop, panel, title,
-                     JOptionPane.OK_CANCEL_OPTION)) == JOptionPane.OK_OPTION)
+                     JvOptionPane.OK_CANCEL_OPTION)) == JvOptionPane.OK_OPTION)
      {
        try
        {
        } catch (Exception e)
        {
          valid = false;
-         JOptionPane.showInternalMessageDialog(Desktop.desktop,
+         JvOptionPane.showInternalMessageDialog(Desktop.desktop,
                  MessageManager.getString("label.invalid_url"));
        }
      }
-     if (valid && resp == JOptionPane.OK_OPTION)
+     if (valid && resp == JvOptionPane.OK_OPTION)
      {
-       int validate = JOptionPane.showInternalConfirmDialog(Desktop.desktop,
+       int validate = JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
                MessageManager.getString("info.validate_jabaws_server"),
                MessageManager.getString("label.test_server"),
-               JOptionPane.YES_NO_OPTION);
+               JvOptionPane.YES_NO_OPTION);
  
-       if (validate == JOptionPane.OK_OPTION)
+       if (validate == JvOptionPane.OK_OPTION)
        {
          if (Jws2Discoverer.testServiceUrl(foo))
          {
          }
          else
          {
-           int opt = JOptionPane
+           int opt = JvOptionPane
                    .showInternalOptionDialog(
                            Desktop.desktop,
                            "The Server  '"
                                    + foo.toString()
                                    + "' failed validation,\ndo you want to add it anyway? ",
                            "Server Validation Failed",
-                           JOptionPane.YES_NO_OPTION,
-                           JOptionPane.INFORMATION_MESSAGE, null, null, null);
-           if (opt == JOptionPane.YES_OPTION)
+                           JvOptionPane.YES_NO_OPTION,
+                           JvOptionPane.INFORMATION_MESSAGE, null, null, null);
+           if (opt == JvOptionPane.YES_OPTION)
            {
              return foo.toString();
            }
            else
            {
-             JOptionPane
+             JvOptionPane
                      .showInternalMessageDialog(
                              Desktop.desktop,
                              MessageManager
      new Thread(new Runnable()
      {
  
 +      @Override
        public void run()
        {
          // force a refresh.
        new Thread(new Runnable()
        {
  
 +        @Override
          public void run()
          {
            progressBar.setVisible(true);
        new Thread(new Runnable()
        {
  
 +        @Override
          public void run()
          {
            long ct = System.currentTimeMillis();
      new Thread(new Runnable()
      {
  
 +      @Override
        public void run()
        {
          updateWsMenuConfig(false);
@@@ -20,6 -20,7 +20,7 @@@
   */
  package jalview.jbgui;
  
+ import jalview.gui.JvOptionPane;
  import jalview.gui.JvSwingUtils;
  import jalview.util.MessageManager;
  import jalview.util.UrlLink;
@@@ -28,49 -29,19 +29,48 @@@ import java.awt.Font
  import java.awt.GridBagConstraints;
  import java.awt.GridBagLayout;
  import java.awt.Insets;
 -import java.awt.Panel;
  import java.awt.Rectangle;
 +import java.awt.event.ActionEvent;
 +import java.awt.event.ActionListener;
  import java.awt.event.KeyAdapter;
  import java.awt.event.KeyEvent;
  
  import javax.swing.BorderFactory;
 +import javax.swing.JButton;
  import javax.swing.JLabel;
- import javax.swing.JOptionPane;
  import javax.swing.JPanel;
  import javax.swing.JTextField;
  import javax.swing.SwingConstants;
  
 -public class GSequenceLink extends Panel
 +public class GSequenceLink extends JPanel
  {
 +
 +  JTextField nameTB = new JTextField();
 +
 +  JTextField urlTB = new JTextField();
 +
 +  JButton insertSeq = new JButton();
 +
 +  JButton insertDBAcc = new JButton();
 +
 +  JLabel insert = new JLabel();
 +
 +  JLabel jLabel1 = new JLabel();
 +
 +  JLabel jLabel2 = new JLabel();
 +
 +  JLabel jLabel3 = new JLabel();
 +
 +  JLabel jLabel4 = new JLabel();
 +
 +  JLabel jLabel5 = new JLabel();
 +
 +  JLabel jLabel6 = new JLabel();
 +
 +  JPanel jPanel1 = new JPanel();
 +
 +  GridBagLayout gridBagLayout1 = new GridBagLayout();
 +
    public GSequenceLink()
    {
      try
          urlTB_keyTyped(e);
        }
      });
 +
 +    insertSeq.setLocation(77, 75);
 +    insertSeq.setSize(141, 24);
 +    insertSeq.setText(MessageManager.getString("action.seq_id"));
 +    insertSeq.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        insertSeq_action(e);
 +      }
 +    });
 +
 +    insertDBAcc.setLocation(210, 75);
 +    insertDBAcc.setSize(141, 24);
 +    insertDBAcc.setText(MessageManager.getString("action.db_acc"));
 +    insertDBAcc.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        insertDBAcc_action(e);
 +      }
 +    });
 +
 +    insert.setText(MessageManager.getString("label.insert"));
 +    insert.setFont(JvSwingUtils.getLabelFont());
 +    insert.setHorizontalAlignment(SwingConstants.RIGHT);
 +    insert.setBounds(17, 78, 58, 16);
 +
      jLabel1.setFont(JvSwingUtils.getLabelFont());
      jLabel1.setHorizontalAlignment(SwingConstants.TRAILING);
      jLabel1.setText(MessageManager.getString("label.link_name"));
      jLabel1.setBounds(new Rectangle(4, 10, 71, 24));
      jLabel2.setFont(JvSwingUtils.getLabelFont());
      jLabel2.setHorizontalAlignment(SwingConstants.TRAILING);
 -    jLabel2.setText(MessageManager.getString("label.url"));
 +    jLabel2.setText(MessageManager.getString("label.url:"));
      jLabel2.setBounds(new Rectangle(17, 37, 54, 27));
      jLabel3.setFont(new java.awt.Font("Verdana", Font.ITALIC, 11));
      jLabel3.setText(MessageManager.getString("label.use_sequence_id_1"));
 -    jLabel3.setBounds(new Rectangle(21, 72, 351, 15));
 +    jLabel3.setBounds(new Rectangle(21, 102, 351, 15));
      jLabel4.setFont(new java.awt.Font("Verdana", Font.ITALIC, 11));
      jLabel4.setText(MessageManager.getString("label.use_sequence_id_2"));
 -    jLabel4.setBounds(new Rectangle(21, 88, 351, 15));
 +    jLabel4.setBounds(new Rectangle(21, 118, 351, 15));
      jLabel5.setFont(new java.awt.Font("Verdana", Font.ITALIC, 11));
      jLabel5.setText(MessageManager.getString("label.use_sequence_id_3"));
 -    jLabel5.setBounds(new Rectangle(21, 106, 351, 15));
 +    jLabel5.setBounds(new Rectangle(21, 136, 351, 15));
  
      String lastLabel = MessageManager.getString("label.use_sequence_id_4");
      if (lastLabel.length() > 0)
        // e.g. Spanish version has longer text
        jLabel6.setFont(new java.awt.Font("Verdana", Font.ITALIC, 11));
        jLabel6.setText(lastLabel);
 -      jLabel6.setBounds(new Rectangle(21, 122, 351, 15));
 +      jLabel6.setBounds(new Rectangle(21, 152, 351, 15));
      }
  
      jPanel1.setBorder(BorderFactory.createEtchedBorder());
      jPanel1.add(jLabel1);
      jPanel1.add(nameTB);
      jPanel1.add(urlTB);
 +    jPanel1.add(insertSeq);
 +    jPanel1.add(insertDBAcc);
 +    jPanel1.add(insert);
      jPanel1.add(jLabel2);
      jPanel1.add(jLabel3);
      jPanel1.add(jLabel4);
      jPanel1.add(jLabel5);
  
 -    int height = 130;
 +    int height = 160;
      if (lastLabel.length() > 0)
      {
        jPanel1.add(jLabel6);
 -      height = 146;
 +      height = 176;
      }
  
      this.add(jPanel1, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0,
        return true;
      }
  
-     JOptionPane.showInternalMessageDialog(jalview.gui.Desktop.desktop,
+     JvOptionPane.showInternalMessageDialog(jalview.gui.Desktop.desktop,
              MessageManager.getString("warn.url_must_contain"),
              MessageManager.getString("label.invalid_url"),
-             JOptionPane.WARNING_MESSAGE);
+             JvOptionPane.WARNING_MESSAGE);
      return false;
    }
  
 -  JTextField nameTB = new JTextField();
 -
 -  JTextField urlTB = new JTextField();
 -
 -  JLabel jLabel1 = new JLabel();
 -
 -  JLabel jLabel2 = new JLabel();
 -
 -  JLabel jLabel3 = new JLabel();
 -
 -  JLabel jLabel4 = new JLabel();
 -
 -  JLabel jLabel5 = new JLabel();
 -
 -  JLabel jLabel6 = new JLabel();
 -
 -  JPanel jPanel1 = new JPanel();
 -
 -  GridBagLayout gridBagLayout1 = new GridBagLayout();
 +  public void notifyDuplicate()
 +  {
-     JOptionPane.showInternalMessageDialog(jalview.gui.Desktop.desktop,
++    JvOptionPane.showInternalMessageDialog(jalview.gui.Desktop.desktop,
 +            MessageManager.getString("warn.name_cannot_be_duplicate"),
 +            MessageManager.getString("label.invalid_name"),
-             JOptionPane.WARNING_MESSAGE);
++            JvOptionPane.WARNING_MESSAGE);
 +  }
  
    public void nameTB_keyTyped(KeyEvent e)
    {
      // }
  
    }
 +
 +  public void insertSeq_action(ActionEvent e)
 +  {
 +    insertIntoUrl(insertSeq.getText());
 +  }
 +
 +  public void insertDBAcc_action(ActionEvent e)
 +  {
 +    insertIntoUrl(insertDBAcc.getText());
 +  }
 +
 +  private void insertIntoUrl(String insertion)
 +  {
 +    int pos = urlTB.getCaretPosition();
 +    String text = urlTB.getText();
 +    String newText = text.substring(0, pos) + insertion
 +            + text.substring(pos);
 +    urlTB.setText(newText);
 +  }
  }