Merge remote-tracking branch 'origin/develop' into features/JAL-2316
authorkiramt <k.mourao@dundee.ac.uk>
Thu, 26 Jan 2017 11:40:39 +0000 (11:40 +0000)
committerkiramt <k.mourao@dundee.ac.uk>
Thu, 26 Jan 2017 11:40:39 +0000 (11:40 +0000)
1  2 
resources/lang/Messages.properties
src/jalview/appletgui/APopupMenu.java
src/jalview/gui/Desktop.java
src/jalview/gui/Preferences.java

@@@ -139,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
@@@ -324,7 -323,7 +324,7 @@@ label.size = Size
  label.style = Style:
  label.calculating = Calculating....
  label.modify_conservation_visibility = Modify conservation visibility
- label.colour_residues_above_occurence = Colour residues above % occurence
+ label.colour_residues_above_occurrence = Colour residues above % occurrence
  label.set_this_label_text = set this label text
  label.sequences_from = Sequences from {0}
  label.successfully_loaded_file  = Successfully loaded file {0}
@@@ -412,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
@@@ -1274,21 -1274,4 +1274,21 @@@ 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
 +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.primary = Double Click
 +label.inmenu = In Menu
 +label.id = ID
 +label.database = Database
 +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 = User-defined URL names must be unique and cannot be MIRIAM ids
 +label.invalid_name = Invalid Name !
  label.output_seq_details = Output Sequence Details to list all database references
 +label.urllinks = Links
@@@ -36,7 -36,8 +36,8 @@@ import jalview.datamodel.SequenceGroup
  import jalview.datamodel.SequenceI;
  import jalview.io.AppletFormatAdapter;
  import jalview.io.DataSourceType;
- import jalview.io.FileFormat;
+ import jalview.io.FileFormatI;
+ import jalview.io.FileFormats;
  import jalview.io.SequenceAnnotationReport;
  import jalview.schemes.Blosum62ColourScheme;
  import jalview.schemes.BuriedColourScheme;
@@@ -92,8 -93,6 +93,6 @@@ public class APopupMenu extends java.aw
  
    protected MenuItem buriedColour = new MenuItem();
  
-   protected CheckboxMenuItem abovePIDColour = new CheckboxMenuItem();
    protected MenuItem userDefinedColour = new MenuItem();
  
    protected MenuItem PIDColour = new MenuItem();
  
    MenuItem noColourmenuItem = new MenuItem();
  
+   protected CheckboxMenuItem abovePIDColour = new CheckboxMenuItem();
+   MenuItem modifyPID = new MenuItem();
    protected CheckboxMenuItem conservationMenuItem = new CheckboxMenuItem();
  
+   MenuItem modifyConservation = new MenuItem();
    final AlignmentPanel ap;
  
    MenuItem unGroupMenuItem = new MenuItem();
    Menu menu1 = new Menu();
  
    public APopupMenu(AlignmentPanel apanel, final SequenceI seq,
 -          Vector<String> links)
 +          List<String> links)
    {
      // /////////////////////////////////////////////////////////
      // If this is activated from the sequence panel, the user may want to
        e.printStackTrace();
      }
  
-     for (String ff : FileFormat.getWritableFormats(true))
+     for (String ff : FileFormats.getInstance().getWritableFormats(true))
      {
        MenuItem item = new MenuItem(ff);
  
          if (sg.cs != null)
          {
            abovePIDColour.setState(sg.cs.getThreshold() > 0);
+           modifyPID.setEnabled(abovePIDColour.getState());
            conservationMenuItem.setState(sg.cs.conservationApplied());
+           modifyConservation.setEnabled(conservationMenuItem.getState());
          }
        }
      }
      {
        noColourmenuItem_actionPerformed();
      }
+     else if (source == modifyConservation)
+     {
+       conservationMenuItem_itemStateChanged();
+     }
+     else if (source == modifyPID)
+     {
+       abovePIDColour_itemStateChanged();
+     }
      else if (source == unGroupMenuItem)
      {
        unGroupMenuItem_actionPerformed();
      // now returns a full copy of sequence data
      // TODO consider using getSequenceSelection instead here
  
-     FileFormat fileFormat = FileFormat.valueOf(e.getActionCommand());
+     FileFormatI fileFormat = FileFormats.getInstance().forName(
+             e.getActionCommand());
      cap.setText(new AppletFormatAdapter().formatSequences(fileFormat,
              ap.av.getShowJVSuffix(), ap, true));
  
      nucleotideMenuItem.addActionListener(this);
      conservationMenuItem.addItemListener(this);
      abovePIDColour.addItemListener(this);
+     modifyPID.setLabel(MessageManager
+             .getString("label.modify_identity_threshold"));
+     modifyPID.addActionListener(this);
+     modifyConservation.setLabel(MessageManager
+             .getString("label.modify_conservation_threshold"));
+     modifyPID.setEnabled(abovePIDColour.getState());
+     modifyConservation.setEnabled(conservationMenuItem.getState());
+     modifyConservation.addActionListener(this);
      colourMenu.setLabel(MessageManager.getString("label.group_colour"));
      showBoxes.setLabel(MessageManager.getString("action.boxes"));
      showBoxes.setState(true);
      colourMenu.add(nucleotideMenuItem);
      colourMenu.add(userDefinedColour);
      colourMenu.addSeparator();
-     colourMenu.add(abovePIDColour);
      colourMenu.add(conservationMenuItem);
+     colourMenu.add(modifyConservation);
+     colourMenu.add(abovePIDColour);
+     colourMenu.add(modifyPID);
  
      noColourmenuItem.setLabel(MessageManager.getString("label.none"));
      noColourmenuItem.addActionListener(this);
      BLOSUM62Colour.setLabel("BLOSUM62");
      BLOSUM62Colour.addActionListener(this);
      conservationMenuItem.setLabel(MessageManager
-             .getString("label.conservation"));
+             .getString("action.by_conservation"));
  
      editMenu.add(copy);
      copy.addActionListener(this);
      else
      // remove PIDColouring
      {
+       SliderPanel.hidePIDSlider();
        sg.cs.setThreshold(0, ap.av.isIgnoreGapsConsensus());
      }
+     modifyPID.setEnabled(abovePIDColour.getState());
      refresh();
    }
  
    protected void userDefinedColour_actionPerformed()
      else
      // remove ConservationColouring
      {
+       SliderPanel.hideConservationSlider();
        sg.cs.setConservation(null);
      }
+     modifyConservation.setEnabled(conservationMenuItem.getState());
      refresh();
    }
  
@@@ -20,6 -20,7 +20,6 @@@
   */
  package jalview.gui;
  
 -import static jalview.util.UrlConstants.EMBLEBI_STRING;
  import static jalview.util.UrlConstants.SEQUENCE_ID;
  
  import jalview.api.AlignViewportI;
@@@ -30,6 -31,7 +30,7 @@@ import jalview.io.DataSourceType
  import jalview.io.FileFormat;
  import jalview.io.FileFormatException;
  import jalview.io.FileFormatI;
+ import jalview.io.FileFormats;
  import jalview.io.FileLoader;
  import jalview.io.IdentifyFile;
  import jalview.io.JalviewFileChooser;
@@@ -37,14 -39,11 +38,14 @@@ 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.params.ParamManager;
 +import jalview.ws.utils.UrlDownloadClient;
  
  import java.awt.BorderLayout;
  import java.awt.Color;
@@@ -79,7 -78,6 +80,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;
@@@ -394,8 -392,6 +395,8 @@@ public class Desktop extends jalview.jb
  
      showNews.setVisible(false);
  
 +    getIdentifiersOrgData();
 +
      checkURLLinks();
  
      this.addWindowListener(new WindowAdapter()
      });
    }
  
 +  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)
    {
        Cache.setProperty("LAST_DIRECTORY", chooser
                .getSelectedFile().getParent());
  
-       FileFormatI format = null;
-       FileFormatI selectedFormat = chooser.getSelectedFormat();
-       if (FileFormat.Jalview.equals(selectedFormat))
-       {
-         format = FileFormat.Jalview;
-       }
-       else
+       FileFormatI format = chooser.getSelectedFormat();
+       /*
+        * Call IdentifyFile to verify the file contains what its extension implies.
+        * Skip this step for dynamically added file formats, because
+        * IdentifyFile does not know how to recognise them.
+        */
+       if (FileFormats.getInstance().isIdentifiable(format))
        {
          try
          {
            format = new IdentifyFile().identify(choice, DataSourceType.FILE);
          } catch (FileFormatException e)
          {
-           // format is null
+           // format = null; //??
          }
        }
  
    @Override
    public void saveState_actionPerformed(ActionEvent e)
    {
-     JalviewFileChooser chooser = new JalviewFileChooser(
-             Cache.getProperty("LAST_DIRECTORY"), "jvp", "Jalview Project");
+     JalviewFileChooser chooser = new JalviewFileChooser("jvp",
+             "Jalview Project");
  
      chooser.setFileView(new JalviewFileView());
      chooser.setDialogTitle(MessageManager.getString("label.save_state"));
    {
      if (v_client != null)
      {
-       JalviewFileChooser chooser = new JalviewFileChooser(
-               Cache.getProperty("LAST_DIRECTORY"), "vdj",// TODO: VAMSAS DOCUMENT EXTENSION is VDJ
+    // TODO: VAMSAS DOCUMENT EXTENSION is VDJ
+       JalviewFileChooser chooser = new JalviewFileChooser("vdj",
                "Vamsas Document");
  
        chooser.setFileView(new JalviewFileView());
          {
            // check what the actual links are - if it's just the default don't
            // bother with the warning
 -          Vector<String> links = Preferences.sequenceURLLinks;
 +          List<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("|");
   */
  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;
@@@ -30,18 -35,12 +30,18 @@@ 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;
@@@ -50,24 -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.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.getPrimaryColumn(),
 +            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())
 +            .getPrimaryColumn();
 +    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.getPrimaryUrlId());
 +
      Cache.applicationProperties.setProperty("USE_PROXY",
              Boolean.toString(useProxy.isSelected()));
  
        if (format != null)
        {
          Cache.applicationProperties.setProperty("DEFAULT_FILE_FORMAT",
-                 format.toString());
+                 format.getName());
        }
        startupFileTextfield.setText(chooser.getSelectedFile()
                .getAbsolutePath());
    @Override
    public void newLink_actionPerformed(ActionEvent e)
    {
 -
      GSequenceLink link = new GSequenceLink();
      boolean valid = false;
      while (!valid)
        {
          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());
 +    int nameCol = ((UrlLinkTableModel) linkUrlTable.getModel())
 +            .getNameColumn();
 +    int urlCol = ((UrlLinkTableModel) linkUrlTable.getModel())
 +            .getUrlColumn();
 +    String oldName = linkUrlTable.getValueAt(index, nameCol).toString();
 +    link.setName(oldName);
 +    link.setURL(linkUrlTable.getValueAt(index, urlCol).toString());
  
      boolean valid = false;
      while (!valid)
      {
 -
        if (JvOptionPane.showInternalConfirmDialog(Desktop.desktop, link,
 -              MessageManager.getString("label.new_sequence_url_link"),
 +              MessageManager.getString("label.edit_sequence_url_link"),
                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 ((oldName.equals(link.getName()))
 +                  || (((UrlLinkTableModel) linkUrlTable.getModel())
 +                          .isUniqueName(link.getName())))
 +          {
 +            linkUrlTable.setValueAt(link.getName(), index, nameCol);
 +            linkUrlTable.setValueAt(link.getURL(), index, urlCol);
 +            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)
    {
        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);
 +      }
 +    }
 +}
  }