j2sNative clean-up. Fixes problem with second try of sequence fetching
[jalview.git] / src / jalview / gui / PopupMenu.java
index b574ddd..3a6f9e4 100644 (file)
@@ -38,6 +38,7 @@ import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.gui.ColourMenuHelper.ColourChangeListener;
+import jalview.gui.JalviewColourChooser.ColourChooserListener;
 import jalview.io.FileFormatI;
 import jalview.io.FileFormats;
 import jalview.io.FormatAdapter;
@@ -46,11 +47,15 @@ import jalview.schemes.Blosum62ColourScheme;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ColourSchemes;
 import jalview.schemes.PIDColourScheme;
+import jalview.schemes.ResidueColourScheme;
 import jalview.util.GroupUrlLink;
 import jalview.util.GroupUrlLink.UrlStringTooLongException;
 import jalview.util.MessageManager;
+import jalview.util.Platform;
+import jalview.util.StringUtils;
 import jalview.util.UrlLink;
 
+import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
@@ -67,17 +72,20 @@ import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.Vector;
 
+import javax.swing.ButtonGroup;
 import javax.swing.JCheckBoxMenuItem;
-import javax.swing.JColorChooser;
+import javax.swing.JInternalFrame;
+import javax.swing.JLabel;
 import javax.swing.JMenu;
 import javax.swing.JMenuItem;
+import javax.swing.JPanel;
 import javax.swing.JPopupMenu;
+import javax.swing.JRadioButtonMenuItem;
+import javax.swing.JScrollPane;
 
 /**
- * DOCUMENT ME!
- * 
- * @author $author$
- * @version $Revision: 1.118 $
+ * The popup menu that is displayed on right-click on a sequence id, or in the
+ * sequence alignment.
  */
 public class PopupMenu extends JPopupMenu implements ColourChangeListener
 {
@@ -91,6 +99,8 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
 
   protected JCheckBoxMenuItem conservationMenuItem = new JCheckBoxMenuItem();
 
+  protected JRadioButtonMenuItem annotationColour;
+
   protected JMenuItem modifyConservation = new JMenuItem();
 
   AlignmentPanel ap;
@@ -172,6 +182,187 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
   JMenuItem hideInsertions = new JMenuItem();
 
   /**
+   * Constructs a menu with sub-menu items for any hyperlinks for the sequence
+   * and/or features provided. Hyperlinks may include a lookup by sequence id,
+   * or database cross-references, depending on which links are enabled in user
+   * preferences.
+   * 
+   * @param seq
+   * @param features
+   * @return
+   */
+  static JMenu buildLinkMenu(final SequenceI seq,
+          List<SequenceFeature> features)
+  {
+    JMenu linkMenu = new JMenu(MessageManager.getString("action.link"));
+
+    List<String> nlinks = null;
+    if (seq != null)
+    {
+      nlinks = Preferences.sequenceUrlLinks.getLinksForMenu();
+      UrlLink.sort(nlinks);
+    }
+    else
+    {
+      nlinks = new ArrayList<>();
+    }
+
+    if (features != null)
+    {
+      for (SequenceFeature sf : features)
+      {
+        if (sf.links != null)
+        {
+          for (String link : sf.links)
+          {
+            nlinks.add(link);
+          }
+        }
+      }
+    }
+
+    /*
+     * instantiate the hyperlinklink templates from sequence data;
+     * note the order of the templates is preserved in the map
+     */
+    Map<String, List<String>> linkset = new LinkedHashMap<>();
+    for (String link : nlinks)
+    {
+      UrlLink urlLink = null;
+      try
+      {
+        urlLink = new UrlLink(link);
+      } catch (Exception foo)
+      {
+        Cache.log.error("Exception for URLLink '" + link + "'", foo);
+        continue;
+      }
+
+      if (!urlLink.isValid())
+      {
+        Cache.log.error(urlLink.getInvalidMessage());
+        continue;
+      }
+
+      urlLink.createLinksFromSeq(seq, linkset);
+    }
+
+    /*
+     * construct menu items for the hyperlinks (still preserving
+     * the order of the sorted templates)
+     */
+    addUrlLinks(linkMenu, linkset.values());
+
+    return linkMenu;
+  }
+
+  /**
+   * A helper method that builds menu items from the given links, with action
+   * handlers to open the link URL, and adds them to the linkMenu. Each provided
+   * link should be a list whose second item is the menu text, and whose fourth
+   * item is the URL to open when the menu item is selected.
+   * 
+   * @param linkMenu
+   * @param linkset
+   */
+  static private void addUrlLinks(JMenu linkMenu,
+          Collection<List<String>> linkset)
+  {
+    for (List<String> linkstrset : linkset)
+    {
+      final String url = linkstrset.get(3);
+      JMenuItem item = new JMenuItem(linkstrset.get(1));
+      item.setToolTipText(MessageManager
+              .formatMessage("label.open_url_param", new Object[]
+              { url }));
+      item.addActionListener(new ActionListener()
+      {
+        @Override
+        public void actionPerformed(ActionEvent e)
+        {
+          new Thread(new Runnable()
+          {
+            @Override
+            public void run()
+            {
+              showLink(url);
+            }
+          }).start();
+        }
+      });
+      linkMenu.add(item);
+    }
+  }
+
+  /**
+   * Opens the provided url in the default web browser, or shows an error
+   * message if this fails
+   * 
+   * @param url
+   */
+  static void showLink(String url)
+  {
+    try
+    {
+      jalview.util.BrowserLauncher.openURL(url);
+    } catch (Exception ex)
+    {
+      JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+              MessageManager.getString("label.web_browser_not_found_unix"),
+              MessageManager.getString("label.web_browser_not_found"),
+              JvOptionPane.WARNING_MESSAGE);
+
+      ex.printStackTrace();
+    }
+  }
+
+  /**
+   * add a late bound groupURL item to the given linkMenu
+   * 
+   * @param linkMenu
+   * @param label
+   *          - menu label string
+   * @param urlgenerator
+   *          GroupURLLink used to generate URL
+   * @param urlstub
+   *          Object array returned from the makeUrlStubs function.
+   */
+  static void addshowLink(JMenu linkMenu, String label,
+          final GroupUrlLink urlgenerator, final Object[] urlstub)
+  {
+    JMenuItem item = new JMenuItem(label);
+    item.setToolTipText(MessageManager
+            .formatMessage("label.open_url_seqs_param", new Object[]
+            { urlgenerator.getUrl_prefix(),
+                urlgenerator.getNumberInvolved(urlstub) }));
+    // TODO: put in info about what is being sent.
+    item.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        new Thread(new Runnable()
+        {
+
+          @Override
+          public void run()
+          {
+            try
+            {
+              showLink(urlgenerator.constructFrom(urlstub));
+            } catch (UrlStringTooLongException e2)
+            {
+            }
+          }
+
+        }).start();
+      }
+    });
+
+    linkMenu.add(item);
+  }
+
+  /**
    * Creates a new PopupMenu object.
    * 
    * @param ap
@@ -400,9 +591,20 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
         }
       }
     }
-    // for the case when no sequences are even visible
+
+    /*
+     * offer 'Reveal All'
+     * - in the IdPanel (seq not null) if any sequence is hidden
+     * - in the IdPanel or SeqPanel if all sequences are hidden (seq is null)
+     */
     if (alignPanel.av.hasHiddenRows())
     {
+      boolean addOption = seq != null;
+      if (!addOption && alignPanel.av.getAlignment().getHeight() == 0)
+      {
+        addOption = true;
+      }
+      if (addOption)
       {
         menuItem = new JMenuItem(
                 MessageManager.getString("action.reveal_all"));
@@ -418,7 +620,6 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
             }
           }
         });
-
         add(menuItem);
       }
     }
@@ -528,7 +729,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
    */
   protected void addFeatureDetails(List<SequenceFeature> features)
   {
-    if (features.isEmpty())
+    if (features == null || features.isEmpty())
     {
       return;
     }
@@ -549,11 +750,27 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       {
         desc = String.format("%s %d-%d", sf.getType(), start, end);
       }
+      String tooltip = desc;
+      String description = sf.getDescription();
+      if (description != null)
+      {
+        description = StringUtils.stripHtmlTags(description);
+        if (description.length() > 12)
+        {
+          desc = desc + " " + description.substring(0, 12) + "..";
+        }
+        else
+        {
+          desc = desc + " " + description;
+        }
+        tooltip = tooltip + " " + description;
+      }
       if (sf.getFeatureGroup() != null)
       {
-        desc = desc + " (" + sf.getFeatureGroup() + ")";
+        tooltip = tooltip + (" (" + sf.getFeatureGroup() + ")");
       }
       JMenuItem item = new JMenuItem(desc);
+      item.setToolTipText(tooltip);
       item.addActionListener(new ActionListener()
       {
         @Override
@@ -573,9 +790,38 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
    */
   protected void showFeatureDetails(SequenceFeature sf)
   {
-    CutAndPasteTransfer cap = new CutAndPasteTransfer();
-    cap.setText(sf.getDetailsReport());
-    Desktop.addInternalFrame(cap,
+    JInternalFrame details;
+    if (Platform.isJS())
+    {
+      details = new JInternalFrame();
+      JPanel panel = new JPanel(new BorderLayout());
+      panel.setOpaque(true);
+      panel.setBackground(Color.white);
+      // TODO JAL-3026 set style of table correctly for feature details
+      JLabel reprt = new JLabel(MessageManager
+              .formatMessage("label.html_content", new Object[]
+              { sf.getDetailsReport() }));
+      reprt.setBackground(Color.WHITE);
+      reprt.setOpaque(true);
+      panel.add(reprt, BorderLayout.CENTER);
+      details.setContentPane(panel);
+      details.pack();
+    }
+    else
+    /**
+     * Java only
+     * 
+     * @j2sNative
+     */
+    {
+      CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
+      // it appears Java's CSS does not support border-collaps :-(
+      cap.addStylesheetRule("table { border-collapse: collapse;}");
+      cap.addStylesheetRule("table, td, th {border: 1px solid black;}");
+      cap.setText(sf.getDetailsReport());
+      details = cap;
+    }
+    Desktop.addInternalFrame(details,
             MessageManager.getString("label.feature_details"), 500, 500);
   }
 
@@ -584,63 +830,14 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
    * When seq is not null, these are links for the sequence id, which may be to
    * external web sites for the sequence accession, and/or links embedded in
    * non-positional features. When seq is null, only links embedded in the
-   * provided features are added.
+   * provided features are added. If no links are found, the menu is not added.
    * 
    * @param seq
    * @param features
    */
   void addLinks(final SequenceI seq, List<SequenceFeature> features)
   {
-    JMenu linkMenu = new JMenu(MessageManager.getString("action.link"));
-
-    List<String> nlinks = null;
-    if (seq != null)
-    {
-      nlinks = Preferences.sequenceUrlLinks.getLinksForMenu();
-    }
-    else
-    {
-      nlinks = new ArrayList<>();
-    }
-
-    if (features != null)
-    {
-      for (SequenceFeature sf : features)
-      {
-        if (sf.links != null)
-        {
-          for (String link : sf.links)
-          {
-            nlinks.add(link);
-          }
-        }
-      }
-    }
-
-    Map<String, List<String>> linkset = new LinkedHashMap<>();
-
-    for (String link : nlinks)
-    {
-      UrlLink urlLink = null;
-      try
-      {
-        urlLink = new UrlLink(link);
-      } catch (Exception foo)
-      {
-        Cache.log.error("Exception for URLLink '" + link + "'", foo);
-        continue;
-      }
-
-      if (!urlLink.isValid())
-      {
-        Cache.log.error(urlLink.getInvalidMessage());
-        continue;
-      }
-
-      urlLink.createLinksFromSeq(seq, linkset);
-    }
-
-    addshowLinks(linkMenu, linkset.values());
+    JMenu linkMenu = buildLinkMenu(seq, features);
 
     // only add link menu if it has entries
     if (linkMenu.getItemCount() > 0)
@@ -817,12 +1014,14 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       {
         sqi = sqi.getDatasetSequence();
       }
-      DBRefEntry[] dbr = sqi.getDBRefs();
-      if (dbr != null && dbr.length > 0)
+      List<DBRefEntry> dbr = sqi.getDBRefs();
+      int nd;
+      if (dbr != null && (nd = dbr.size()) > 0)
       {
-        for (int d = 0; d < dbr.length; d++)
+        for (int d = 0; d < nd; d++)
         {
-          String src = dbr[d].getSource(); // jalview.util.DBRefUtils.getCanonicalName(dbr[d].getSource()).toUpperCase();
+          DBRefEntry e = dbr.get(d);
+          String src = e.getSource(); // jalview.util.DBRefUtils.getCanonicalName(dbr[d].getSource()).toUpperCase();
           Object[] sarray = commonDbrefs.get(src);
           if (sarray == null)
           {
@@ -835,10 +1034,10 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
 
           if (((String[]) sarray[1])[sq] == null)
           {
-            if (!dbr[d].hasMap() || (dbr[d].getMap()
+            if (!e.hasMap() || (e.getMap()
                     .locateMappedRange(start, end) != null))
             {
-              ((String[]) sarray[1])[sq] = dbr[d].getAccessionId();
+              ((String[]) sarray[1])[sq] = e.getAccessionId();
               ((int[]) sarray[0])[0]++;
             }
           }
@@ -933,98 +1132,6 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
     }
   }
 
-  private void addshowLinks(JMenu linkMenu,
-          Collection<List<String>> linkset)
-  {
-    for (List<String> linkstrset : linkset)
-    {
-      // split linkstr into label and url
-      addshowLink(linkMenu, linkstrset.get(1), linkstrset.get(3));
-    }
-  }
-
-  /**
-   * add a show URL menu item to the given linkMenu
-   * 
-   * @param linkMenu
-   * @param label
-   *          - menu label string
-   * @param url
-   *          - url to open
-   */
-  private void addshowLink(JMenu linkMenu, String label, final String url)
-  {
-    JMenuItem item = new JMenuItem(label);
-    item.setToolTipText(MessageManager.formatMessage("label.open_url_param",
-            new Object[]
-            { url }));
-    item.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        new Thread(new Runnable()
-        {
-
-          @Override
-          public void run()
-          {
-            showLink(url);
-          }
-
-        }).start();
-      }
-    });
-
-    linkMenu.add(item);
-  }
-
-  /**
-   * add a late bound groupURL item to the given linkMenu
-   * 
-   * @param linkMenu
-   * @param label
-   *          - menu label string
-   * @param urlgenerator
-   *          GroupURLLink used to generate URL
-   * @param urlstub
-   *          Object array returned from the makeUrlStubs function.
-   */
-  private void addshowLink(JMenu linkMenu, String label,
-          final GroupUrlLink urlgenerator, final Object[] urlstub)
-  {
-    JMenuItem item = new JMenuItem(label);
-    item.setToolTipText(MessageManager
-            .formatMessage("label.open_url_seqs_param", new Object[]
-            { urlgenerator.getUrl_prefix(),
-                urlgenerator.getNumberInvolved(urlstub) }));
-    // TODO: put in info about what is being sent.
-    item.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        new Thread(new Runnable()
-        {
-
-          @Override
-          public void run()
-          {
-            try
-            {
-              showLink(urlgenerator.constructFrom(urlstub));
-            } catch (UrlStringTooLongException e2)
-            {
-            }
-          }
-
-        }).start();
-      }
-    });
-
-    linkMenu.add(item);
-  }
-
   /**
    * DOCUMENT ME!
    * 
@@ -1249,7 +1356,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       @Override
       public void actionPerformed(ActionEvent actionEvent)
       {
-        editSequence_actionPerformed(actionEvent);
+        editSequence_actionPerformed();
       }
     });
     makeReferenceSeq.setText(
@@ -1380,6 +1487,13 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       }
     });
 
+    annotationColour = new JRadioButtonMenuItem(
+            MessageManager.getString("action.by_annotation"));
+    annotationColour.setName(ResidueColourScheme.ANNOTATION_COLOUR);
+    annotationColour.setEnabled(false);
+    annotationColour.setToolTipText(
+            MessageManager.getString("label.by_annotation_tooltip"));
+
     modifyConservation.setText(MessageManager
             .getString("label.modify_conservation_threshold"));
     modifyConservation.addActionListener(new ActionListener()
@@ -1410,7 +1524,10 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
     colourMenu.add(textColour);
     colourMenu.addSeparator();
 
-    ColourMenuHelper.addMenuItems(colourMenu, this, sg, false);
+    ButtonGroup bg = ColourMenuHelper.addMenuItems(colourMenu, this, sg,
+            false);
+    bg.add(annotationColour);
+    colourMenu.add(annotationColour);
 
     colourMenu.addSeparator();
     colourMenu.add(conservationMenuItem);
@@ -1540,15 +1657,8 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
 
   protected void hideInsertions_actionPerformed(ActionEvent actionEvent)
   {
-
-    HiddenColumns hidden = new HiddenColumns();
-    BitSet inserts = new BitSet(), mask = new BitSet();
-
-    // set mask to preserve existing hidden columns outside selected group
-    if (ap.av.hasHiddenColumns())
-    {
-      ap.av.getAlignment().getHiddenColumns().markHiddenRegions(mask);
-    }
+    HiddenColumns hidden = ap.av.getAlignment().getHiddenColumns();
+    BitSet inserts = new BitSet();
 
     boolean markedPopup = false;
     // mark inserts in current selection
@@ -1556,10 +1666,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
     {
       // mark just the columns in the selection group to be hidden
       inserts.set(ap.av.getSelectionGroup().getStartRes(),
-              ap.av.getSelectionGroup().getEndRes() + 1);
-
-      // and clear that part of the mask
-      mask.andNot(inserts);
+              ap.av.getSelectionGroup().getEndRes() + 1); // TODO why +1?
 
       // now clear columns without gaps
       for (SequenceI sq : ap.av.getSelectionGroup().getSequences())
@@ -1570,29 +1677,18 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
         }
         inserts.and(sq.getInsertionsAsBits());
       }
-    }
-    else
-    {
-      // initially, mark all columns to be hidden
-      inserts.set(0, ap.av.getAlignment().getWidth());
-
-      // and clear out old hidden regions completely
-      mask.clear();
+      hidden.clearAndHideColumns(inserts, ap.av.getSelectionGroup().getStartRes(),
+              ap.av.getSelectionGroup().getEndRes());
     }
 
     // now mark for sequence under popup if we haven't already done it
-    if (!markedPopup && sequence != null)
+    else if (!markedPopup && sequence != null)
     {
-      inserts.and(sequence.getInsertionsAsBits());
-    }
+      inserts.or(sequence.getInsertionsAsBits());
 
-    // finally, preserve hidden regions outside selection
-    inserts.or(mask);
-
-    // and set hidden columns accordingly
-    hidden.hideMarkedBits(inserts);
-
-    ap.av.getAlignment().setHiddenColumns(hidden);
+      // and set hidden columns accordingly
+      hidden.hideColumns(inserts);
+    }
     refresh();
   }
 
@@ -1608,8 +1704,8 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
 
   public void createSequenceDetailsReport(SequenceI[] sequences)
   {
-    CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
     StringBuilder contents = new StringBuilder(128);
+    contents.append("<html><body>");
     for (SequenceI seq : sequences)
     {
       contents.append("<p><h2>" + MessageManager.formatMessage(
@@ -1617,15 +1713,38 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
               new Object[]
               { seq.getDisplayId(true) }) + "</h2></p><p>");
       new SequenceAnnotationReport(null).createSequenceAnnotationReport(
-              contents, seq, true, true,
-              (ap.getSeqPanel().seqCanvas.fr != null)
-                      ? ap.getSeqPanel().seqCanvas.fr.getMinMax()
-                      : null);
+              contents, seq, true, true, ap.getSeqPanel().seqCanvas.fr);
       contents.append("</p>");
     }
-    cap.setText("<html>" + contents.toString() + "</html>");
+    contents.append("</body></html>");
+    String report = contents.toString();
+
+    JInternalFrame frame;
+    if (Platform.isJS())
+    {
+      JLabel textLabel = new JLabel();
+      textLabel.setText(report);
+      textLabel.setBackground(Color.WHITE);
+      JPanel pane = new JPanel(new BorderLayout());
+      pane.setOpaque(true);
+      pane.setBackground(Color.WHITE);
+      pane.add(textLabel, BorderLayout.NORTH);
+      frame = new JInternalFrame();
+      frame.getContentPane().add(new JScrollPane(pane));
+    }
+    else
+    /**
+     * Java only
+     * 
+     * @j2sNative
+     */
+    {
+      CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
+      cap.setText(report);
+      frame = cap;
+    }
 
-    Desktop.addInternalFrame(cap,
+    Desktop.addInternalFrame(frame,
             MessageManager.formatMessage("label.sequence_details_for",
                     (sequences.length == 1 ? new Object[]
                     { sequences[0].getDisplayId(true) }
@@ -1633,7 +1752,6 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
                             { MessageManager
                                     .getString("label.selection") })),
             500, 400);
-
   }
 
   protected void showNonconserved_actionPerformed()
@@ -1752,30 +1870,27 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
+   * Shows a dialog where group name and description may be edited
    */
   protected void groupName_actionPerformed()
   {
-
     SequenceGroup sg = getGroup();
     EditNameDialog dialog = new EditNameDialog(sg.getName(),
             sg.getDescription(),
-            "       " + MessageManager.getString("label.group_name") + " ",
-            MessageManager.getString("label.group_description") + " ",
+            MessageManager.getString("label.group_name"),
+            MessageManager.getString("label.group_description"));
+    dialog.showDialog(ap.alignFrame,
             MessageManager.getString("label.edit_group_name_description"),
-            ap.alignFrame);
-
-    if (!dialog.accept)
-    {
-      return;
-    }
-
-    sg.setName(dialog.getName());
-    sg.setDescription(dialog.getDescription());
-    refresh();
+            new Runnable()
+            {
+              @Override
+              public void run()
+              {
+                sg.setName(dialog.getName());
+                sg.setDescription(dialog.getDescription());
+                refresh();
+              }
+            });
   }
 
   /**
@@ -1796,48 +1911,41 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
+   * Shows a dialog where sequence name and description may be edited
    */
   void sequenceName_actionPerformed()
   {
     EditNameDialog dialog = new EditNameDialog(sequence.getName(),
             sequence.getDescription(),
-            "       " + MessageManager.getString("label.sequence_name")
-                    + " ",
-            MessageManager.getString("label.sequence_description") + " ",
+            MessageManager.getString("label.sequence_name"),
+            MessageManager.getString("label.sequence_description"));
+    dialog.showDialog(ap.alignFrame,
             MessageManager.getString(
                     "label.edit_sequence_name_description"),
-            ap.alignFrame);
-
-    if (!dialog.accept)
-    {
-      return;
-    }
-
-    if (dialog.getName() != null)
-    {
-      if (dialog.getName().indexOf(" ") > -1)
-      {
-        JvOptionPane.showMessageDialog(ap,
-                MessageManager
-                        .getString("label.spaces_converted_to_backslashes"),
-                MessageManager
-                        .getString("label.no_spaces_allowed_sequence_name"),
-                JvOptionPane.WARNING_MESSAGE);
-      }
-
-      sequence.setName(dialog.getName().replace(' ', '_'));
-      ap.paintAlignment(false);
-    }
-
-    sequence.setDescription(dialog.getDescription());
-
-    ap.av.firePropertyChange("alignment", null,
-            ap.av.getAlignment().getSequences());
-
+            new Runnable()
+            {
+              @Override
+              public void run()
+              {
+                if (dialog.getName() != null)
+                {
+                  if (dialog.getName().indexOf(" ") > -1)
+                  {
+                    JvOptionPane.showMessageDialog(ap,
+                            MessageManager.getString(
+                                    "label.spaces_converted_to_underscores"),
+                            MessageManager.getString(
+                                    "label.no_spaces_allowed_sequence_name"),
+                            JvOptionPane.WARNING_MESSAGE);
+                  }
+                  sequence.setName(dialog.getName().replace(' ', '_'));
+                  ap.paintAlignment(false, false);
+                }
+                sequence.setDescription(dialog.getDescription());
+                ap.av.firePropertyChange("alignment", null,
+                        ap.av.getAlignment().getSequences());
+              }
+            });
   }
 
   /**
@@ -1862,24 +1970,23 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
+   * Offers a colour chooser and sets the selected colour as the group outline
    */
   protected void outline_actionPerformed()
   {
-    SequenceGroup sg = getGroup();
-    Color col = JColorChooser.showDialog(this,
-            MessageManager.getString("label.select_outline_colour"),
-            Color.BLUE);
-
-    if (col != null)
+    String title = MessageManager
+            .getString("label.select_outline_colour");
+    ColourChooserListener listener = new ColourChooserListener()
     {
-      sg.setOutlineColour(col);
-    }
-
-    refresh();
+      @Override
+      public void colourSelected(Color c)
+      {
+        getGroup().setOutlineColour(c);
+        refresh();
+      };
+    };
+    JalviewColourChooser.showColourChooser(Desktop.getDesktop(),
+            title, Color.BLUE, listener);
   }
 
   /**
@@ -1918,22 +2025,6 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
     refresh();
   }
 
-  public void showLink(String url)
-  {
-    try
-    {
-      jalview.util.BrowserLauncher.openURL(url);
-    } catch (Exception ex)
-    {
-      JvOptionPane.showInternalMessageDialog(Desktop.desktop,
-              MessageManager.getString("label.web_browser_not_found_unix"),
-              MessageManager.getString("label.web_browser_not_found"),
-              JvOptionPane.WARNING_MESSAGE);
-
-      ex.printStackTrace();
-    }
-  }
-
   void hideSequences(boolean representGroup)
   {
     ap.av.hideSequences(sequence, representGroup);
@@ -1941,12 +2032,12 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
 
   public void copy_actionPerformed()
   {
-    ap.alignFrame.copy_actionPerformed(null);
+    ap.alignFrame.copy_actionPerformed();
   }
 
   public void cut_actionPerformed()
   {
-    ap.alignFrame.cut_actionPerformed(null);
+    ap.alignFrame.cut_actionPerformed();
   }
 
   void changeCase(ActionEvent e)
@@ -2042,13 +2133,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
      */
     if (!seqs.isEmpty())
     {
-      if (ap.getSeqPanel().seqCanvas.getFeatureRenderer()
-              .amendFeatures(seqs, features, true, ap))
-      {
-        ap.alignFrame.setShowSeqFeatures(true);
-        ap.av.setSearchResults(null); // clear highlighting
-        ap.repaint(); // draw new/amended features
-      }
+      new FeatureEditor(ap, seqs, features, true).showDialog();
     }
   }
 
@@ -2080,7 +2165,12 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
 
   }
 
-  public void editSequence_actionPerformed(ActionEvent actionEvent)
+  /**
+   * Shows a dialog where sequence characters may be edited. Any changes are
+   * applied, and added as an available 'Undo' item in the edit commands
+   * history.
+   */
+  public void editSequence_actionPerformed()
   {
     SequenceGroup sg = ap.av.getSelectionGroup();
 
@@ -2094,24 +2184,28 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       EditNameDialog dialog = new EditNameDialog(
               sequence.getSequenceAsString(sg.getStartRes(),
                       sg.getEndRes() + 1),
-              null, MessageManager.getString("label.edit_sequence"), null,
+              null, MessageManager.getString("label.edit_sequence"), null);
+      dialog.showDialog(ap.alignFrame,
               MessageManager.getString("label.edit_sequence"),
-              ap.alignFrame);
-
-      if (dialog.accept)
-      {
-        EditCommand editCommand = new EditCommand(
-                MessageManager.getString("label.edit_sequences"),
-                Action.REPLACE,
-                dialog.getName().replace(' ', ap.av.getGapCharacter()),
-                sg.getSequencesAsArray(ap.av.getHiddenRepSequences()),
-                sg.getStartRes(), sg.getEndRes() + 1, ap.av.getAlignment());
-
-        ap.alignFrame.addHistoryItem(editCommand);
-
-        ap.av.firePropertyChange("alignment", null,
-                ap.av.getAlignment().getSequences());
-      }
+              new Runnable()
+              {
+                @Override
+                public void run()
+                {
+                  EditCommand editCommand = new EditCommand(
+                          MessageManager.getString("label.edit_sequences"),
+                          Action.REPLACE,
+                          dialog.getName().replace(' ',
+                                  ap.av.getGapCharacter()),
+                          sg.getSequencesAsArray(
+                                  ap.av.getHiddenRepSequences()),
+                          sg.getStartRes(), sg.getEndRes() + 1,
+                          ap.av.getAlignment());
+                  ap.alignFrame.addHistoryItem(editCommand);
+                  ap.av.firePropertyChange("alignment", null,
+                          ap.av.getAlignment().getSequences());
+                }
+              });
     }
   }
 
@@ -2129,7 +2223,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
      * switch to the chosen colour scheme (or null for None)
      */
     ColourSchemeI colourScheme = ColourSchemes.getInstance()
-            .getColourScheme(colourSchemeName, sg,
+            .getColourScheme(colourSchemeName, ap.av, sg,
                     ap.av.getHiddenRepSequences());
     sg.setColourScheme(colourScheme);
     if (colourScheme instanceof Blosum62ColourScheme