JAL-3725 restrict mapped virtual feature location to mapped region
[jalview.git] / src / jalview / gui / PopupMenu.java
index 5f6c556..abe9835 100644 (file)
@@ -1,6 +1,6 @@
 /*
- * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
- * Copyright (C) 2014 The Jalview Authors
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
  * 
  * This file is part of Jalview.
  * 
  */
 package jalview.gui;
 
-import jalview.analysis.AAFrequency;
-import jalview.analysis.Conservation;
-import jalview.commands.ChangeCaseCommand;
-import jalview.commands.EditCommand;
-import jalview.datamodel.AlignmentAnnotation;
-import jalview.datamodel.Annotation;
-import jalview.datamodel.DBRefEntry;
-import jalview.datamodel.PDBEntry;
-import jalview.datamodel.Sequence;
-import jalview.datamodel.SequenceFeature;
-import jalview.datamodel.SequenceGroup;
-import jalview.datamodel.SequenceI;
-import jalview.io.FormatAdapter;
-import jalview.io.SequenceAnnotationReport;
-import jalview.renderer.AnnotationRenderer;
-import jalview.schemes.AnnotationColourGradient;
-import jalview.schemes.Blosum62ColourScheme;
-import jalview.schemes.BuriedColourScheme;
-import jalview.schemes.ClustalxColourScheme;
-import jalview.schemes.HelixColourScheme;
-import jalview.schemes.HydrophobicColourScheme;
-import jalview.schemes.NucleotideColourScheme;
-import jalview.schemes.PIDColourScheme;
-import jalview.schemes.PurinePyrimidineColourScheme;
-import jalview.schemes.ResidueProperties;
-import jalview.schemes.StrandColourScheme;
-import jalview.schemes.TaylorColourScheme;
-import jalview.schemes.TurnColourScheme;
-import jalview.schemes.UserColourScheme;
-import jalview.schemes.ZappoColourScheme;
-import jalview.util.GroupUrlLink;
-import jalview.util.GroupUrlLink.UrlStringTooLongException;
-import jalview.util.MessageManager;
-import jalview.util.UrlLink;
-
 import java.awt.Color;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
@@ -62,10 +27,14 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Hashtable;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.SortedMap;
+import java.util.TreeMap;
 import java.util.Vector;
 
 import javax.swing.ButtonGroup;
@@ -73,83 +42,91 @@ import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JColorChooser;
 import javax.swing.JMenu;
 import javax.swing.JMenuItem;
-import javax.swing.JOptionPane;
 import javax.swing.JPopupMenu;
 import javax.swing.JRadioButtonMenuItem;
 
+import jalview.analysis.AAFrequency;
+import jalview.analysis.AlignmentAnnotationUtils;
+import jalview.analysis.AlignmentUtils;
+import jalview.analysis.Conservation;
+import jalview.api.AlignViewportI;
+import jalview.bin.Cache;
+import jalview.commands.ChangeCaseCommand;
+import jalview.commands.EditCommand;
+import jalview.commands.EditCommand.Action;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.DBRefEntry;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.MappedFeatures;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+import jalview.gui.ColourMenuHelper.ColourChangeListener;
+import jalview.io.FileFormatI;
+import jalview.io.FileFormats;
+import jalview.io.FormatAdapter;
+import jalview.io.SequenceAnnotationReport;
+import jalview.schemes.Blosum62ColourScheme;
+import jalview.schemes.ColourSchemeI;
+import jalview.schemes.ColourSchemes;
+import jalview.schemes.PIDColourScheme;
+import jalview.schemes.ResidueColourScheme;
+import jalview.util.Comparison;
+import jalview.util.GroupUrlLink;
+import jalview.util.GroupUrlLink.UrlStringTooLongException;
+import jalview.util.MessageManager;
+import jalview.util.StringUtils;
+import jalview.util.UrlLink;
+import jalview.viewmodel.seqfeatures.FeatureRendererModel;
+
 /**
- * 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
+public class PopupMenu extends JPopupMenu implements ColourChangeListener
 {
-  private static final String ALL_ANNOTATIONS = "All";
-
-  private static final String COMMA = ",";
-
-  JMenu groupMenu = new JMenu();
-
-  JMenuItem groupName = new JMenuItem();
-
-  protected JRadioButtonMenuItem clustalColour = new JRadioButtonMenuItem();
-
-  protected JRadioButtonMenuItem zappoColour = new JRadioButtonMenuItem();
-
-  protected JRadioButtonMenuItem taylorColour = new JRadioButtonMenuItem();
+  /*
+   * maximum length of feature description to include in popup menu item text
+   */
+  private static final int FEATURE_DESC_MAX = 40;
 
-  protected JRadioButtonMenuItem hydrophobicityColour = new JRadioButtonMenuItem();
+  /*
+   * true for ID Panel menu, false for alignment panel menu
+   */
+  private final boolean forIdPanel;
 
-  protected JRadioButtonMenuItem helixColour = new JRadioButtonMenuItem();
+  private final AlignmentPanel ap;
 
-  protected JRadioButtonMenuItem strandColour = new JRadioButtonMenuItem();
+  /*
+   * the sequence under the cursor when clicked
+   * (additional sequences may be selected)
+   */
+  private final SequenceI sequence;
 
-  protected JRadioButtonMenuItem turnColour = new JRadioButtonMenuItem();
+  JMenu groupMenu = new JMenu();
 
-  protected JRadioButtonMenuItem buriedColour = new JRadioButtonMenuItem();
+  JMenuItem groupName = new JMenuItem();
 
   protected JCheckBoxMenuItem abovePIDColour = new JCheckBoxMenuItem();
 
-  protected JRadioButtonMenuItem userDefinedColour = new JRadioButtonMenuItem();
-
-  protected JRadioButtonMenuItem PIDColour = new JRadioButtonMenuItem();
-
-  protected JRadioButtonMenuItem BLOSUM62Colour = new JRadioButtonMenuItem();
-
-  protected JRadioButtonMenuItem purinePyrimidineColour = new JRadioButtonMenuItem();
-
-  protected JRadioButtonMenuItem RNAInteractionColour = new JRadioButtonMenuItem();
-
-  // protected JRadioButtonMenuItem covariationColour = new
-  // JRadioButtonMenuItem();
-
-  JRadioButtonMenuItem noColourmenuItem = new JRadioButtonMenuItem();
+  protected JMenuItem modifyPID = new JMenuItem();
 
   protected JCheckBoxMenuItem conservationMenuItem = new JCheckBoxMenuItem();
 
-  AlignmentPanel ap;
-
-  JMenu sequenceMenu = new JMenu();
-
-  JMenuItem sequenceName = new JMenuItem();
+  protected JRadioButtonMenuItem annotationColour;
 
-  JMenuItem sequenceDetails = new JMenuItem();
+  protected JMenuItem modifyConservation = new JMenuItem();
 
-  JMenuItem sequenceSelDetails = new JMenuItem();
-
-  JMenuItem chooseAnnotations = new JMenuItem();
+  JMenu sequenceMenu = new JMenu();
 
-  SequenceI sequence;
+  JMenuItem makeReferenceSeq = new JMenuItem();
 
   JMenuItem createGroupMenuItem = new JMenuItem();
 
   JMenuItem unGroupMenuItem = new JMenuItem();
 
-  JMenuItem outline = new JMenuItem();
-
-  JRadioButtonMenuItem nucleotideMenuItem = new JRadioButtonMenuItem();
-
   JMenu colourMenu = new JMenu();
 
   JCheckBoxMenuItem showBoxes = new JCheckBoxMenuItem();
@@ -162,111 +139,275 @@ public class PopupMenu extends JPopupMenu
 
   JMenu editMenu = new JMenu();
 
-  JMenuItem cut = new JMenuItem();
-
-  JMenuItem copy = new JMenuItem();
-
   JMenuItem upperCase = new JMenuItem();
 
   JMenuItem lowerCase = new JMenuItem();
 
   JMenuItem toggle = new JMenuItem();
 
-  JMenu pdbMenu = new JMenu();
+  JMenu outputMenu = new JMenu();
 
-  JMenuItem pdbFromFile = new JMenuItem();
+  JMenu seqShowAnnotationsMenu = new JMenu();
 
-  // JBPNote: Commented these out - Should add these services via the web
-  // services menu system.
-  // JMenuItem ContraFold = new JMenuItem();
+  JMenu seqHideAnnotationsMenu = new JMenu();
 
-  // JMenuItem RNAFold = new JMenuItem();
+  JMenuItem seqAddReferenceAnnotations = new JMenuItem(
+          MessageManager.getString("label.add_reference_annotations"));
 
-  JMenuItem enterPDB = new JMenuItem();
+  JMenu groupShowAnnotationsMenu = new JMenu();
 
-  JMenuItem discoverPDB = new JMenuItem();
+  JMenu groupHideAnnotationsMenu = new JMenu();
 
-  JMenu outputMenu = new JMenu();
+  JMenuItem groupAddReferenceAnnotations = new JMenuItem(
+          MessageManager.getString("label.add_reference_annotations"));
 
-  JMenu showAnnotationsMenu = new JMenu();
+  JMenuItem textColour = new JMenuItem();
 
-  JMenu hideAnnotationsMenu = new JMenu();
+  JMenu editGroupMenu = new JMenu();
 
-  JMenuItem addDatasequenceAnnotations = new JMenuItem();
+  JMenuItem chooseStructure = new JMenuItem();
 
-  JMenuItem sequenceFeature = new JMenuItem();
+  JMenu rnaStructureMenu = new JMenu();
 
-  JMenuItem textColour = 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
+   */
+  protected 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);
+    }
+  }
 
-  JMenu jMenu1 = new JMenu();
+  /**
+   * 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);
 
-  JMenu structureMenu = new JMenu();
+      ex.printStackTrace();
+    }
+  }
 
-  JMenu viewStructureMenu = new JMenu();
+  /**
+   * 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()
+        {
 
-  // JMenu colStructureMenu = new JMenu();
-  JMenuItem editSequence = new JMenuItem();
+          @Override
+          public void run()
+          {
+            try
+            {
+              showLink(urlgenerator.constructFrom(urlstub));
+            } catch (UrlStringTooLongException e2)
+            {
+            }
+          }
 
-  // JMenuItem annotationMenuItem = new JMenuItem();
+        }).start();
+      }
+    });
 
-  JMenu groupLinksMenu;
+    linkMenu.add(item);
+  }
 
   /**
-   * Creates a new PopupMenu object.
+   * Constructor for a PopupMenu for a click in the alignment panel (on a residue)
    * 
    * @param ap
-   *          DOCUMENT ME!
+   *              the panel in which the mouse is clicked
    * @param seq
-   *          DOCUMENT ME!
+   *              the sequence under the mouse
+   * @throws NullPointerException
+   *                                if seq is null
    */
-  public PopupMenu(final AlignmentPanel ap, Sequence seq, Vector links)
+  public PopupMenu(final AlignmentPanel ap, SequenceI seq, int column)
   {
-    this(ap, seq, links, null);
+    this(false, ap, seq, column, null);
   }
 
   /**
+   * Constructor for a PopupMenu for a click in the sequence id panel
    * 
-   * @param ap
+   * @param alignPanel
+   *                     the panel in which the mouse is clicked
    * @param seq
-   * @param links
+   *                     the sequence under the mouse click
    * @param groupLinks
+   *                     templates for sequence external links
+   * @throws NullPointerException
+   *                                if seq is null
    */
-  public PopupMenu(final AlignmentPanel ap, final SequenceI seq,
-          Vector links, Vector groupLinks)
+  public PopupMenu(final AlignmentPanel alignPanel, final SequenceI seq,
+          List<String> groupLinks)
   {
-    // /////////////////////////////////////////////////////////
-    // If this is activated from the sequence panel, the user may want to
-    // edit or annotate a particular residue. Therefore display the residue menu
-    //
-    // If from the IDPanel, we must display the sequence menu
-    // ////////////////////////////////////////////////////////
-    this.ap = ap;
+    this(true, alignPanel, seq, -1, groupLinks);
+  }
+
+  /**
+   * Private constructor that constructs a popup menu for either sequence ID
+   * Panel, or alignment context
+   * 
+   * @param fromIdPanel
+   * @param alignPanel
+   * @param seq
+   * @param column
+   *                      aligned column position (0...)
+   * @param groupLinks
+   */
+  private PopupMenu(boolean fromIdPanel,
+          final AlignmentPanel alignPanel,
+          final SequenceI seq, final int column, List<String> groupLinks)
+  {
+    Objects.requireNonNull(seq);
+    this.forIdPanel = fromIdPanel;
+    this.ap = alignPanel;
     sequence = seq;
 
-    ButtonGroup colours = new ButtonGroup();
-    colours.add(noColourmenuItem);
-    colours.add(clustalColour);
-    colours.add(zappoColour);
-    colours.add(taylorColour);
-    colours.add(hydrophobicityColour);
-    colours.add(helixColour);
-    colours.add(strandColour);
-    colours.add(turnColour);
-    colours.add(buriedColour);
-    colours.add(abovePIDColour);
-    colours.add(userDefinedColour);
-    colours.add(PIDColour);
-    colours.add(BLOSUM62Colour);
-    colours.add(purinePyrimidineColour);
-    colours.add(RNAInteractionColour);
-    // colours.add(covariationColour);
-
-    for (int i = 0; i < jalview.io.FormatAdapter.WRITEABLE_FORMATS.length; i++)
-    {
-      JMenuItem item = new JMenuItem(
-              jalview.io.FormatAdapter.WRITEABLE_FORMATS[i]);
-
-      item.addActionListener(new java.awt.event.ActionListener()
+    for (String ff : FileFormats.getInstance().getWritableFormats(true))
+    {
+      JMenuItem item = new JMenuItem(ff);
+
+      item.addActionListener(new ActionListener()
       {
         @Override
         public void actionPerformed(ActionEvent e)
@@ -279,9 +420,28 @@ public class PopupMenu extends JPopupMenu
     }
 
     /*
-     * Build menus for annotation types that may be shown or hidden.
+     * Build menus for annotation types that may be shown or hidden, and for
+     * 'reference annotations' that may be added to the alignment. First for the
+     * currently selected sequence (if there is one):
+     */
+    final List<SequenceI> selectedSequence = (forIdPanel && seq != null
+            ? Arrays.asList(seq)
+            : Collections.<SequenceI> emptyList());
+    buildAnnotationTypesMenus(seqShowAnnotationsMenu,
+            seqHideAnnotationsMenu, selectedSequence);
+    configureReferenceAnnotationsMenu(seqAddReferenceAnnotations,
+            selectedSequence);
+
+    /*
+     * And repeat for the current selection group (if there is one):
      */
-    buildAnnotationTypesMenus();
+    final List<SequenceI> selectedGroup = (alignPanel.av.getSelectionGroup() == null
+            ? Collections.<SequenceI> emptyList()
+            : alignPanel.av.getSelectionGroup().getSequences());
+    buildAnnotationTypesMenus(groupShowAnnotationsMenu,
+            groupHideAnnotationsMenu, selectedGroup);
+    configureReferenceAnnotationsMenu(groupAddReferenceAnnotations,
+            selectedGroup);
 
     try
     {
@@ -291,135 +451,97 @@ public class PopupMenu extends JPopupMenu
       e.printStackTrace();
     }
 
-    JMenuItem menuItem;
-    if (seq != null)
+    if (forIdPanel)
     {
+      JMenuItem menuItem;
       sequenceMenu.setText(sequence.getName());
-
-      if (seq.getDatasetSequence().getPDBId() != null
-              && seq.getDatasetSequence().getPDBId().size() > 0)
+      if (seq == alignPanel.av.getAlignment().getSeqrep())
       {
-        java.util.Enumeration e = seq.getDatasetSequence().getPDBId()
-                .elements();
-
-        while (e.hasMoreElements())
-        {
-          final PDBEntry pdb = (PDBEntry) e.nextElement();
-
-          menuItem = new JMenuItem();
-          menuItem.setText(pdb.getId());
-          menuItem.addActionListener(new java.awt.event.ActionListener()
-          {
-            public void actionPerformed(ActionEvent e)
-            {
-              // TODO re JAL-860: optionally open dialog or provide a menu entry
-              // allowing user to open just one structure per sequence
-              // new AppJmol(pdb, ap.av.collateForPDB(new PDBEntry[]
-              // { pdb })[0], null, ap);
-              new StructureViewer(ap.getStructureSelectionManager())
-                      .viewStructures(pdb,
-                              ap.av.collateForPDB(new PDBEntry[]
-                              { pdb })[0], null, ap);
-            }
-          });
-          viewStructureMenu.add(menuItem);
-
-          /*
-           * menuItem = new JMenuItem(); menuItem.setText(pdb.getId());
-           * menuItem.addActionListener(new java.awt.event.ActionListener() {
-           * public void actionPerformed(ActionEvent e) {
-           * colourByStructure(pdb.getId()); } });
-           * colStructureMenu.add(menuItem);
-           */
-        }
+        makeReferenceSeq.setText(
+                MessageManager.getString("action.unmark_as_reference"));
       }
       else
       {
-        if (ap.av.getAlignment().isNucleotide() == false)
-        {
-          structureMenu.remove(viewStructureMenu);
-        }
-        // structureMenu.remove(colStructureMenu);
+        makeReferenceSeq.setText(
+                MessageManager.getString("action.set_as_reference"));
       }
 
-      if (ap.av.getAlignment().isNucleotide() == true)
+      if (!alignPanel.av.getAlignment().isNucleotide())
+      {
+        remove(rnaStructureMenu);
+      }
+      else
       {
-        AlignmentAnnotation[] aa = ap.av.getAlignment()
+        int origCount = rnaStructureMenu.getItemCount();
+        /*
+         * add menu items to 2D-render any alignment or sequence secondary
+         * structure annotation
+         */
+        AlignmentAnnotation[] aas = alignPanel.av.getAlignment()
                 .getAlignmentAnnotation();
-        for (int i = 0; i < aa.length; i++)
+        if (aas != null)
         {
-          if (aa[i].getRNAStruc() != null)
+          for (final AlignmentAnnotation aa : aas)
           {
-            final String rnastruc = aa[i].getRNAStruc();
-            final String structureLine = aa[i].label;
-            menuItem = new JMenuItem();
-            menuItem.setText(MessageManager.formatMessage(
-                    "label.2d_rna_structure_line", new String[]
-                    { structureLine }));
-            menuItem.addActionListener(new java.awt.event.ActionListener()
-
+            if (aa.isValidStruc() && aa.sequenceRef == null)
             {
-              @Override
-              public void actionPerformed(ActionEvent e)
+              /*
+               * valid alignment RNA secondary structure annotation
+               */
+              menuItem = new JMenuItem();
+              menuItem.setText(MessageManager.formatMessage(
+                      "label.2d_rna_structure_line", new Object[]
+                      { aa.label }));
+              menuItem.addActionListener(new ActionListener()
               {
-                // System.out.println("1:"+structureLine);
-                System.out.println("1:sname" + seq.getName());
-                System.out.println("2:seq" + seq);
-
-                // System.out.println("3:"+seq.getSequenceAsString());
-                System.out.println("3:strucseq" + rnastruc);
-                // System.out.println("4:struc"+seq.getRNA());
-                System.out.println("5:name" + seq.getName());
-                System.out.println("6:ap" + ap);
-                new AppVarna(structureLine, seq, seq.getSequenceAsString(),
-                        rnastruc, seq.getName(), ap);
-                // new AppVarna(seq.getName(),seq,rnastruc,seq.getRNA(),
-                // seq.getName(), ap);
-                System.out.println("end");
-              }
-            });
-            viewStructureMenu.add(menuItem);
+                @Override
+                public void actionPerformed(ActionEvent e)
+                {
+                  new AppVarna(seq, aa, alignPanel);
+                }
+              });
+              rnaStructureMenu.add(menuItem);
+            }
           }
         }
 
-        // SequenceFeatures[] test = seq.getSequenceFeatures();
-
         if (seq.getAnnotation() != null)
         {
-          AlignmentAnnotation seqAnno[] = seq.getAnnotation();
-          for (int i = 0; i < seqAnno.length; i++)
+          AlignmentAnnotation seqAnns[] = seq.getAnnotation();
+          for (final AlignmentAnnotation aa : seqAnns)
           {
-            if (seqAnno[i].getRNAStruc() != null)
+            if (aa.isValidStruc())
             {
-              final String rnastruc = seqAnno[i].getRNAStruc();
-
+              /*
+               * valid sequence RNA secondary structure annotation
+               */
               // TODO: make rnastrucF a bit more nice
               menuItem = new JMenuItem();
               menuItem.setText(MessageManager.formatMessage(
-                      "label.2d_rna_sequence_name", new String[]
+                      "label.2d_rna_sequence_name", new Object[]
                       { seq.getName() }));
-              menuItem.addActionListener(new java.awt.event.ActionListener()
+              menuItem.addActionListener(new ActionListener()
               {
                 @Override
                 public void actionPerformed(ActionEvent e)
                 {
                   // TODO: VARNA does'nt print gaps in the sequence
-
-                  new AppVarna(seq.getName() + " structure", seq, seq
-                          .getSequenceAsString(), rnastruc, seq.getName(),
-                          ap);
+                  new AppVarna(seq, aa, alignPanel);
                 }
               });
-              viewStructureMenu.add(menuItem);
+              rnaStructureMenu.add(menuItem);
             }
           }
         }
-
+        if (rnaStructureMenu.getItemCount() == origCount)
+        {
+          remove(rnaStructureMenu);
+        }
       }
 
       menuItem = new JMenuItem(
               MessageManager.getString("action.hide_sequences"));
-      menuItem.addActionListener(new java.awt.event.ActionListener()
+      menuItem.addActionListener(new ActionListener()
       {
         @Override
         public void actionPerformed(ActionEvent e)
@@ -429,13 +551,13 @@ public class PopupMenu extends JPopupMenu
       });
       add(menuItem);
 
-      if (ap.av.getSelectionGroup() != null
-              && ap.av.getSelectionGroup().getSize() > 1)
+      if (alignPanel.av.getSelectionGroup() != null
+              && alignPanel.av.getSelectionGroup().getSize() > 1)
       {
-        menuItem = new JMenuItem(MessageManager.formatMessage(
-                "label.represent_group_with", new String[]
+        menuItem = new JMenuItem(MessageManager
+                .formatMessage("label.represent_group_with", new Object[]
                 { seq.getName() }));
-        menuItem.addActionListener(new java.awt.event.ActionListener()
+        menuItem.addActionListener(new ActionListener()
         {
           @Override
           public void actionPerformed(ActionEvent e)
@@ -446,12 +568,12 @@ public class PopupMenu extends JPopupMenu
         sequenceMenu.add(menuItem);
       }
 
-      if (ap.av.hasHiddenRows())
+      if (alignPanel.av.hasHiddenRows())
       {
-        final int index = ap.av.getAlignment().findIndex(seq);
+        final int index = alignPanel.av.getAlignment().findIndex(seq);
 
-        if (ap.av.adjustForHiddenSeqs(index)
-                - ap.av.adjustForHiddenSeqs(index - 1) > 1)
+        if (alignPanel.av.adjustForHiddenSeqs(index)
+                - alignPanel.av.adjustForHiddenSeqs(index - 1) > 1)
         {
           menuItem = new JMenuItem(
                   MessageManager.getString("action.reveal_sequences"));
@@ -460,10 +582,10 @@ public class PopupMenu extends JPopupMenu
             @Override
             public void actionPerformed(ActionEvent e)
             {
-              ap.av.showSequence(index);
-              if (ap.overviewPanel != null)
+              alignPanel.av.showSequence(index);
+              if (alignPanel.overviewPanel != null)
               {
-                ap.overviewPanel.updateOverviewImage();
+                alignPanel.overviewPanel.updateOverviewImage();
               }
             }
           });
@@ -471,104 +593,66 @@ public class PopupMenu extends JPopupMenu
         }
       }
     }
-    // for the case when no sequences are even visible
-    if (ap.av.hasHiddenRows())
+
+    /*
+     * 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(
+        JMenuItem menuItem = new JMenuItem(
                 MessageManager.getString("action.reveal_all"));
         menuItem.addActionListener(new ActionListener()
         {
           @Override
           public void actionPerformed(ActionEvent e)
           {
-            ap.av.showAllHiddenSeqs();
-            if (ap.overviewPanel != null)
+            alignPanel.av.showAllHiddenSeqs();
+            if (alignPanel.overviewPanel != null)
             {
-              ap.overviewPanel.updateOverviewImage();
+              alignPanel.overviewPanel.updateOverviewImage();
             }
           }
         });
-
         add(menuItem);
       }
-
     }
 
-    SequenceGroup sg = ap.av.getSelectionGroup();
-    boolean isDefinedGroup = (sg != null) ? ap.av.getAlignment()
-            .getGroups().contains(sg) : false;
+    SequenceGroup sg = alignPanel.av.getSelectionGroup();
+    boolean isDefinedGroup = (sg != null)
+            ? alignPanel.av.getAlignment().getGroups().contains(sg)
+            : false;
 
     if (sg != null && sg.getSize() > 0)
     {
-      groupName.setText(MessageManager.formatMessage("label.name_param",
-              new String[]
-              { sg.getName() }));
       groupName.setText(MessageManager
               .getString("label.edit_name_and_description_current_group"));
 
-      if (sg.cs instanceof ZappoColourScheme)
-      {
-        zappoColour.setSelected(true);
-      }
-      else if (sg.cs instanceof TaylorColourScheme)
+      ColourMenuHelper.setColourSelected(colourMenu, sg.getColourScheme());
+
+      conservationMenuItem.setEnabled(!sg.isNucleotide());
+
+      if (sg.cs != null)
       {
-        taylorColour.setSelected(true);
-      }
-      else if (sg.cs instanceof PIDColourScheme)
-      {
-        PIDColour.setSelected(true);
-      }
-      else if (sg.cs instanceof Blosum62ColourScheme)
-      {
-        BLOSUM62Colour.setSelected(true);
-      }
-      else if (sg.cs instanceof UserColourScheme)
-      {
-        userDefinedColour.setSelected(true);
-      }
-      else if (sg.cs instanceof HydrophobicColourScheme)
-      {
-        hydrophobicityColour.setSelected(true);
-      }
-      else if (sg.cs instanceof HelixColourScheme)
-      {
-        helixColour.setSelected(true);
-      }
-      else if (sg.cs instanceof StrandColourScheme)
-      {
-        strandColour.setSelected(true);
-      }
-      else if (sg.cs instanceof TurnColourScheme)
-      {
-        turnColour.setSelected(true);
-      }
-      else if (sg.cs instanceof BuriedColourScheme)
-      {
-        buriedColour.setSelected(true);
-      }
-      else if (sg.cs instanceof ClustalxColourScheme)
-      {
-        clustalColour.setSelected(true);
-      }
-      else if (sg.cs instanceof PurinePyrimidineColourScheme)
-      {
-        purinePyrimidineColour.setSelected(true);
-      }
-
-      /*
-       * else if (sg.cs instanceof CovariationColourScheme) {
-       * covariationColour.setSelected(true); }
-       */
-      else
-      {
-        noColourmenuItem.setSelected(true);
-      }
-
-      if (sg.cs != null && sg.cs.conservationApplied())
-      {
-        conservationMenuItem.setSelected(true);
+        if (sg.cs.conservationApplied())
+        {
+          conservationMenuItem.setSelected(true);
+        }
+        if (sg.cs.getThreshold() > 0)
+        {
+          abovePIDColour.setSelected(true);
+        }
       }
+      modifyConservation.setEnabled(conservationMenuItem.isSelected());
+      modifyPID.setEnabled(abovePIDColour.isSelected());
       displayNonconserved.setSelected(sg.getShowNonconserved());
       showText.setSelected(sg.getDisplayText());
       showColourText.setSelected(sg.getColourText());
@@ -579,11 +663,12 @@ public class PopupMenu extends JPopupMenu
         buildGroupURLMenu(sg, groupLinks);
       }
       // Add a 'show all structures' for the current selection
-      Hashtable<String, PDBEntry> pdbe = new Hashtable<String, PDBEntry>(), reppdb = new Hashtable<String, PDBEntry>();
+      Hashtable<String, PDBEntry> pdbe = new Hashtable<>(), reppdb = new Hashtable<>();
+
       SequenceI sqass = null;
-      for (SequenceI sq : ap.av.getSequenceSelection())
+      for (SequenceI sq : alignPanel.av.getSequenceSelection())
       {
-        Vector<PDBEntry> pes = sq.getDatasetSequence().getPDBId();
+        Vector<PDBEntry> pes = sq.getDatasetSequence().getAllPDBEntries();
         if (pes != null && pes.size() > 0)
         {
           reppdb.put(pes.get(0).getId(), pes.get(0));
@@ -599,54 +684,10 @@ public class PopupMenu extends JPopupMenu
       }
       if (pdbe.size() > 0)
       {
-        final PDBEntry[] pe = pdbe.values().toArray(
-                new PDBEntry[pdbe.size()]), pr = reppdb.values().toArray(
-                new PDBEntry[reppdb.size()]);
+        final PDBEntry[] pe = pdbe.values()
+                .toArray(new PDBEntry[pdbe.size()]),
+                pr = reppdb.values().toArray(new PDBEntry[reppdb.size()]);
         final JMenuItem gpdbview, rpdbview;
-        if (pdbe.size() == 1)
-        {
-          structureMenu.add(gpdbview = new JMenuItem(MessageManager
-                  .formatMessage("label.view_structure_for", new String[]
-                  { sqass.getDisplayId(false) })));
-        }
-        else
-        {
-          structureMenu.add(gpdbview = new JMenuItem(MessageManager
-                  .formatMessage("label.view_all_structures", new String[]
-                  { new Integer(pdbe.size()).toString() })));
-        }
-        gpdbview.setToolTipText(MessageManager
-                .getString("label.open_new_jmol_view_with_all_structures_associated_current_selection_superimpose_using_alignment"));
-        gpdbview.addActionListener(new ActionListener()
-        {
-
-          @Override
-          public void actionPerformed(ActionEvent e)
-          {
-            new StructureViewer(ap.getStructureSelectionManager())
-                    .viewStructures(ap, pe, ap.av.collateForPDB(pe));
-          }
-        });
-        if (reppdb.size() > 1 && reppdb.size() < pdbe.size())
-        {
-          structureMenu.add(rpdbview = new JMenuItem(MessageManager
-                  .formatMessage(
-                          "label.view_all_representative_structures",
-                          new String[]
-                          { new Integer(reppdb.size()).toString() })));
-          rpdbview.setToolTipText(MessageManager
-                  .getString("label.open_new_jmol_view_with_all_representative_structures_associated_current_selection_superimpose_using_alignment"));
-          rpdbview.addActionListener(new ActionListener()
-          {
-
-            @Override
-            public void actionPerformed(ActionEvent e)
-            {
-              new StructureViewer(ap.getStructureSelectionManager())
-                      .viewStructures(ap, pr, ap.av.collateForPDB(pr));
-            }
-          });
-        }
       }
     }
     else
@@ -659,134 +700,234 @@ public class PopupMenu extends JPopupMenu
     {
       createGroupMenuItem.setVisible(true);
       unGroupMenuItem.setVisible(false);
-      jMenu1.setText(MessageManager.getString("action.edit_new_group"));
+      editGroupMenu.setText(MessageManager.getString("action.edit_new_group"));
     }
     else
     {
       createGroupMenuItem.setVisible(false);
       unGroupMenuItem.setVisible(true);
-      jMenu1.setText(MessageManager.getString("action.edit_group"));
+      editGroupMenu.setText(MessageManager.getString("action.edit_group"));
     }
 
-    if (seq == null)
+    if (!forIdPanel)
     {
       sequenceMenu.setVisible(false);
-      structureMenu.setVisible(false);
+      chooseStructure.setVisible(false);
+      rnaStructureMenu.setVisible(false);
+    }
+
+    addLinksAndFeatures(seq, column);
+  }
+
+  /**
+   * Adds
+   * <ul>
+   * <li>configured sequence database links (ID panel popup menu)</li>
+   * <li>non-positional feature links (ID panel popup menu)</li>
+   * <li>positional feature links (alignment panel popup menu)</li>
+   * <li>feature details links (alignment panel popup menu)</li>
+   * </ul>
+   * If this panel is also showed complementary (CDS/protein) features, then links
+   * to their feature details are also added.
+   * 
+   * @param seq
+   * @param column
+   */
+  void addLinksAndFeatures(final SequenceI seq, final int column)
+  {
+    List<SequenceFeature> features = null;
+    if (forIdPanel)
+    {
+      features = sequence.getFeatures().getNonPositionalFeatures();
+    }
+    else
+    {
+      features = ap.getFeatureRenderer().findFeaturesAtColumn(sequence,
+              column + 1);
+    }
+
+    addLinks(seq, features);
+
+    if (!forIdPanel)
+    {
+      addFeatureDetails(features, seq, column);
+    }
+  }
+
+  /**
+   * Add a menu item to show feature details for each sequence feature. Any
+   * linked 'virtual' features (CDS/protein) are also optionally found and
+   * included.
+   * 
+   * @param features
+   * @param seq
+   * @param column
+   */
+  protected void addFeatureDetails(List<SequenceFeature> features,
+          final SequenceI seq, final int column)
+  {
+    /*
+     * add features in CDS/protein complement at the corresponding
+     * position if configured to do so
+     */
+    MappedFeatures mf = null;
+    if (ap.av.isShowComplementFeatures())
+    {
+      if (!Comparison.isGap(sequence.getCharAt(column)))
+      {
+        AlignViewportI complement = ap.getAlignViewport()
+                .getCodingComplement();
+        AlignFrame af = Desktop.getAlignFrameFor(complement);
+        FeatureRendererModel fr2 = af.getFeatureRenderer();
+        int seqPos = sequence.findPosition(column);
+        mf = fr2.findComplementFeaturesAtResidue(sequence, seqPos);
+      }
     }
 
-    if (links != null && links.size() > 0)
+    if (features.isEmpty() && mf == null)
     {
+      /*
+       * no features to show at this position
+       */
+      return;
+    }
 
-      JMenu linkMenu = new JMenu(MessageManager.getString("action.link"));
-      Vector linkset = new Vector();
-      for (int i = 0; i < links.size(); i++)
+    JMenu details = new JMenu(
+            MessageManager.getString("label.feature_details"));
+    add(details);
+
+    String name = seq.getName();
+    for (final SequenceFeature sf : features)
+    {
+      addFeatureDetailsMenuItem(details, name, sf, null);
+    }
+
+    if (mf != null)
+    {
+      for (final SequenceFeature sf : mf.features)
       {
-        String link = links.elementAt(i).toString();
-        UrlLink urlLink = null;
-        try
-        {
-          urlLink = new UrlLink(link);
-        } catch (Exception foo)
-        {
-          jalview.bin.Cache.log.error("Exception for URLLink '" + link
-                  + "'", foo);
-          continue;
-        }
-        ;
-        if (!urlLink.isValid())
-        {
-          jalview.bin.Cache.log.error(urlLink.getInvalidMessage());
-          continue;
-        }
-        final String label = urlLink.getLabel();
-        if (seq != null && urlLink.isDynamic())
-        {
+        addFeatureDetailsMenuItem(details, name, sf, mf);
+      }
+    }
+  }
 
-          // collect matching db-refs
-          DBRefEntry[] dbr = jalview.util.DBRefUtils.selectRefs(
-                  seq.getDBRef(), new String[]
-                  { urlLink.getTarget() });
-          // collect id string too
-          String id = seq.getName();
-          String descr = seq.getDescription();
-          if (descr != null && descr.length() < 1)
-          {
-            descr = null;
-          }
+  /**
+   * A helper method to add one menu item whose action is to show details for
+   * one feature. The menu text includes feature description, but this may be
+   * truncated.
+   * 
+   * @param details
+   * @param seqName
+   * @param sf
+   * @param mf
+   */
+  void addFeatureDetailsMenuItem(JMenu details, final String seqName,
+          final SequenceFeature sf, MappedFeatures mf)
+  {
+    int start = sf.getBegin();
+    int end = sf.getEnd();
+    if (mf != null)
+    {
+      /*
+       * show local rather than linked feature coordinates
+       */
+      int[] beginRange = mf.getMappedPositions(start, start);
+      int[] endRange = mf.getMappedPositions(end, end);
+      if (beginRange == null || endRange == null)
+      {
+        // e.g. variant extending to stop codon so not mappable
+        return;
+      }
+      start = beginRange[0];
+      end = endRange[endRange.length - 1];
+      int[] localRange = mf.getMappedPositions(start, end);
+      if (localRange == null)
+      {
+        return;
+      }
+      start = localRange[0];
+      end = localRange[localRange.length - 1];
+    }
+    StringBuilder desc = new StringBuilder();
+    desc.append(sf.getType()).append(" ").append(String.valueOf(start));
+    if (start != end)
+    {
+      desc.append(sf.isContactFeature() ? ":" : "-");
+      desc.append(String.valueOf(end));
+    }
+    String description = sf.getDescription();
+    if (description != null)
+    {
+      desc.append(" ");
+      description = StringUtils.stripHtmlTags(description);
 
-          if (dbr != null)
-          {
-            for (int r = 0; r < dbr.length; r++)
-            {
-              if (id != null && dbr[r].getAccessionId().equals(id))
-              {
-                // suppress duplicate link creation for the bare sequence ID
-                // string with this link
-                id = null;
-              }
-              // create Bare ID link for this RUL
-              String[] urls = urlLink.makeUrls(dbr[r].getAccessionId(),
-                      true);
-              if (urls != null)
-              {
-                for (int u = 0; u < urls.length; u += 2)
-                {
-                  if (!linkset.contains(urls[u] + "|" + urls[u + 1]))
-                  {
-                    linkset.addElement(urls[u] + "|" + urls[u + 1]);
-                    addshowLink(linkMenu, label + "|" + urls[u],
-                            urls[u + 1]);
-                  }
-                }
-              }
-            }
-          }
-          if (id != null)
-          {
-            // create Bare ID link for this RUL
-            String[] urls = urlLink.makeUrls(id, true);
-            if (urls != null)
-            {
-              for (int u = 0; u < urls.length; u += 2)
-              {
-                if (!linkset.contains(urls[u] + "|" + urls[u + 1]))
-                {
-                  linkset.addElement(urls[u] + "|" + urls[u + 1]);
-                  addshowLink(linkMenu, label, urls[u + 1]);
-                }
-              }
-            }
-          }
-          // Create urls from description but only for URL links which are regex
-          // links
-          if (descr != null && urlLink.getRegexReplace() != null)
-          {
-            // create link for this URL from description where regex matches
-            String[] urls = urlLink.makeUrls(descr, true);
-            if (urls != null)
-            {
-              for (int u = 0; u < urls.length; u += 2)
-              {
-                if (!linkset.contains(urls[u] + "|" + urls[u + 1]))
-                {
-                  linkset.addElement(urls[u] + "|" + urls[u + 1]);
-                  addshowLink(linkMenu, label, urls[u + 1]);
-                }
-              }
-            }
-          }
-        }
-        else
-        {
-          if (!linkset.contains(label + "|" + urlLink.getUrl_prefix()))
-          {
-            linkset.addElement(label + "|" + urlLink.getUrl_prefix());
-            // Add a non-dynamic link
-            addshowLink(linkMenu, label, urlLink.getUrl_prefix());
-          }
-        }
+      /*
+       * truncate overlong descriptions unless they contain an href
+       * (as truncation could leave corrupted html)
+       */
+      boolean hasLink = description.indexOf("a href") > -1;
+      if (description.length() > FEATURE_DESC_MAX && !hasLink)
+      {
+        description = description.substring(0, FEATURE_DESC_MAX) + "...";
+      }
+      desc.append(description);
+    }
+    String featureGroup = sf.getFeatureGroup();
+    if (featureGroup != null)
+    {
+      desc.append(" (").append(featureGroup).append(")");
+    }
+    String htmlText = JvSwingUtils.wrapTooltip(true, desc.toString());
+    JMenuItem item = new JMenuItem(htmlText);
+    item.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        showFeatureDetails(sf, seqName, mf);
       }
-      if (sequence != null)
+    });
+    details.add(item);
+  }
+
+  /**
+   * Opens a panel showing a text report of feature details
+   * 
+   * @param sf
+   * @param seqName
+   * @param mf
+   */
+  protected void showFeatureDetails(SequenceFeature sf, String seqName,
+          MappedFeatures mf)
+  {
+    CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
+    // it appears Java's CSS does not support border-collapse :-(
+    cap.addStylesheetRule("table { border-collapse: collapse;}");
+    cap.addStylesheetRule("table, td, th {border: 1px solid black;}");
+    cap.setText(sf.getDetailsReport(seqName, mf));
+
+    Desktop.addInternalFrame(cap,
+            MessageManager.getString("label.feature_details"), 500, 500);
+  }
+
+  /**
+   * Adds a 'Link' menu item with a sub-menu item for each hyperlink provided.
+   * 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. If no links are found, the menu is not added.
+   * 
+   * @param seq
+   * @param features
+   */
+  void addLinks(final SequenceI seq, List<SequenceFeature> features)
+  {
+    JMenu linkMenu = buildLinkMenu(forIdPanel ? seq : null, features);
+
+    // only add link menu if it has entries
+    if (linkMenu.getItemCount() > 0)
+    {
+      if (forIdPanel)
       {
         sequenceMenu.add(linkMenu);
       }
@@ -798,9 +939,10 @@ public class PopupMenu extends JPopupMenu
   }
 
   /**
-   * Add annotation types to a 'Show annotations' or 'Hide annotations' menu.
+   * Add annotation types to 'Show annotations' and/or 'Hide annotations' menus.
    * "All" is added first, followed by a separator. Then add any annotation
-   * types associated with the current selection.
+   * types associated with the current selection. Separate menus are built for
+   * the selected sequence group (if any), and the selected sequence.
    * <p>
    * Some annotation rows are always rendered together - these can be identified
    * by a common graphGroup property > -1. Only one of each group will be marked
@@ -808,165 +950,83 @@ public class PopupMenu extends JPopupMenu
    * composite type name, e.g.
    * <p>
    * IUPredWS (Long), IUPredWS (Short)
+   * 
+   * @param seq
    */
-  protected void buildAnnotationTypesMenus()
+  protected void buildAnnotationTypesMenus(JMenu showMenu, JMenu hideMenu,
+          List<SequenceI> forSequences)
   {
-    showAnnotationsMenu.removeAll();
-    hideAnnotationsMenu.removeAll();
-    final List<String> all = Arrays.asList(ALL_ANNOTATIONS);
-    addAnnotationTypeToShowHide(showAnnotationsMenu, all, true, true);
-    addAnnotationTypeToShowHide(hideAnnotationsMenu, all, true, false);
-    showAnnotationsMenu.addSeparator();
-    hideAnnotationsMenu.addSeparator();
+    showMenu.removeAll();
+    hideMenu.removeAll();
+
+    final List<String> all = Arrays
+            .asList(new String[]
+            { MessageManager.getString("label.all") });
+    addAnnotationTypeToShowHide(showMenu, forSequences, "", all, true,
+            true);
+    addAnnotationTypeToShowHide(hideMenu, forSequences, "", all, true,
+            false);
+    showMenu.addSeparator();
+    hideMenu.addSeparator();
 
     final AlignmentAnnotation[] annotations = ap.getAlignment()
             .getAlignmentAnnotation();
-    BitSet visibleGraphGroups = PopupMenu
-            .getVisibleLineGraphGroups(annotations);
-
-    List<List<String>> shownTypes = new ArrayList<List<String>>();
-    List<List<String>> hiddenTypes = new ArrayList<List<String>>();
-    PopupMenu.getAnnotationTypesForShowHide(shownTypes, hiddenTypes,
-            visibleGraphGroups, annotations, ap.av.getSelectionGroup());
-
-    for (List<String> types : hiddenTypes)
-    {
-      addAnnotationTypeToShowHide(showAnnotationsMenu, types, false, true);
-    }
-
-    for (List<String> types : shownTypes)
-    {
-      addAnnotationTypeToShowHide(hideAnnotationsMenu, types, false, false);
-    }
-  }
-
-  /**
-   * Helper method to populate lists of annotation types for the Show/Hide
-   * Annotations menus. If sequenceGroup is not null, this is restricted to
-   * annotations which are associated with sequences in the selection group.
-   * <p/>
-   * If an annotation row is currently visible, its type (label) is added (once
-   * only per type), to the shownTypes list. If it is currently hidden, it is
-   * added to the hiddenTypesList.
-   * <p/>
-   * For rows that belong to a line graph group, so are always rendered
-   * together:
-   * <ul>
-   * <li>Treat all rows in the group as visible, if at least one of them is</li>
-   * <li>Build a comma-separated label with all the types that belong to the
-   * group</li>
-   * </ul>
-   * 
-   * @param shownTypes
-   * @param hiddenTypes
-   * @param visibleGraphGroups
-   * @param annotations
-   * @param sequenceGroup
-   */
-  public static void getAnnotationTypesForShowHide(
-          List<List<String>> shownTypes, List<List<String>> hiddenTypes,
-          BitSet visibleGraphGroups, AlignmentAnnotation[] annotations,
-          SequenceGroup sequenceGroup)
-  {
-    // lookup table, key = graph group, value = list of types in the group
-    Map<Integer, List<String>> groupLabels = new LinkedHashMap<Integer, List<String>>();
 
-    List<String> addedToShown = new ArrayList<String>();
-    List<String> addedToHidden = new ArrayList<String>();
+    /*
+     * Find shown/hidden annotations types, distinguished by source (calcId),
+     * and grouped by graphGroup. Using LinkedHashMap means we will retrieve in
+     * the insertion order, which is the order of the annotations on the
+     * alignment.
+     */
+    Map<String, List<List<String>>> shownTypes = new LinkedHashMap<>();
+    Map<String, List<List<String>>> hiddenTypes = new LinkedHashMap<>();
+    AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes, hiddenTypes,
+            AlignmentAnnotationUtils.asList(annotations), forSequences);
 
-    for (AlignmentAnnotation aa : annotations)
+    for (String calcId : hiddenTypes.keySet())
     {
-
-      if (sequenceGroup == null
-              || (aa.sequenceRef != null && sequenceGroup.getSequences()
-                      .contains(aa.sequenceRef)))
+      for (List<String> type : hiddenTypes.get(calcId))
       {
-        /*
-         * Build a 'composite label' for types in line graph groups.
-         */
-        final List<String> labelAsList = new ArrayList<String>();
-        labelAsList.add(aa.label);
-        if (aa.graph == AlignmentAnnotation.LINE_GRAPH
-                && aa.graphGroup > -1)
-        {
-          if (groupLabels.containsKey(aa.graphGroup))
-          {
-            if (!groupLabels.get(aa.graphGroup).contains(aa.label))
-            {
-              groupLabels.get(aa.graphGroup).add(aa.label);
-            }
-          }
-          else
-          {
-            groupLabels.put(aa.graphGroup, labelAsList);
-          }
-        }
-        else if (aa.visible && !addedToShown.contains(aa.label))
-        {
-          shownTypes.add(labelAsList);
-          addedToShown.add(aa.label);
-        }
-        else
-        {
-          if (!aa.visible && !addedToHidden.contains(aa.label))
-          {
-            hiddenTypes.add(labelAsList);
-            addedToHidden.add(aa.label);
-          }
-        }
+        addAnnotationTypeToShowHide(showMenu, forSequences, calcId, type,
+                false, true);
       }
     }
-    /*
-     * finally add the 'composite group labels' to the appropriate lists,
-     * depending on whether the group is identified as visible or hidden
-     */
-    for (int group : groupLabels.keySet())
+    // grey out 'show annotations' if none are hidden
+    showMenu.setEnabled(!hiddenTypes.isEmpty());
+
+    for (String calcId : shownTypes.keySet())
     {
-      final List<String> groupLabel = groupLabels.get(group);
-      if (visibleGraphGroups.get(group))
+      for (List<String> type : shownTypes.get(calcId))
       {
-        if (!shownTypes.contains(groupLabel))
-        {
-          shownTypes.add(groupLabel);
-        }
-      }
-      else if (!hiddenTypes.contains(groupLabel))
-      {
-        hiddenTypes.add(groupLabel);
+        addAnnotationTypeToShowHide(hideMenu, forSequences, calcId, type,
+                false, false);
       }
     }
+    // grey out 'hide annotations' if none are shown
+    hideMenu.setEnabled(!shownTypes.isEmpty());
   }
 
   /**
-   * Returns a BitSet (possibly empty) of those graphGroups for line graph
-   * annotations, which have at least one member annotation row marked visible.
-   * The logic is that only one row in the group is marked visible, but when it
-   * is drawn, so are all the other rows in the same group.
-   * <p/>
-   * This lookup set allows us to check whether rows marked not visible are in
-   * fact shown.
+   * Returns a list of sequences - either the current selection group (if there
+   * is one), else the specified single sequence.
    * 
-   * @see AnnotationRenderer#drawComponent
-   * @param annotations
+   * @param seq
    * @return
    */
-  public static BitSet getVisibleLineGraphGroups(
-          AlignmentAnnotation[] annotations)
+  protected List<SequenceI> getSequenceScope(SequenceI seq)
   {
-    // todo move to a utility class
-    BitSet result = new BitSet();
-    for (AlignmentAnnotation ann : annotations)
+    List<SequenceI> forSequences = null;
+    final SequenceGroup selectionGroup = ap.av.getSelectionGroup();
+    if (selectionGroup != null && selectionGroup.getSize() > 0)
     {
-      if (ann.graph == AlignmentAnnotation.LINE_GRAPH && ann.visible)
-      {
-        int gg = ann.graphGroup;
-        if (gg > -1)
-        {
-          result.set(gg);
-        }
-      }
+      forSequences = selectionGroup.getSequences();
+    }
+    else
+    {
+      forSequences = seq == null ? Collections.<SequenceI> emptyList()
+              : Arrays.asList(seq);
     }
-    return result;
+    return forSequences;
   }
 
   /**
@@ -975,6 +1035,9 @@ public class PopupMenu extends JPopupMenu
    * 
    * @param showOrHideMenu
    *          the menu to add to
+   * @param forSequences
+   *          the sequences whose annotations may be shown or hidden
+   * @param calcId
    * @param types
    *          the label to add
    * @param allTypes
@@ -984,77 +1047,50 @@ public class PopupMenu extends JPopupMenu
    *          type, else hide
    */
   protected void addAnnotationTypeToShowHide(JMenu showOrHideMenu,
-          final Collection<String> types, final boolean allTypes,
+          final List<SequenceI> forSequences, String calcId,
+          final List<String> types, final boolean allTypes,
           final boolean actionIsShow)
   {
     String label = types.toString(); // [a, b, c]
-    label = label.substring(1, label.length() - 1);
+    label = label.substring(1, label.length() - 1); // a, b, c
     final JMenuItem item = new JMenuItem(label);
-    item.addActionListener(new java.awt.event.ActionListener()
+    item.setToolTipText(calcId);
+    item.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        showHideAnnotation_actionPerformed(types, allTypes, actionIsShow);
+        AlignmentUtils.showOrHideSequenceAnnotations(ap.getAlignment(),
+                types, forSequences, allTypes, actionIsShow);
+        refresh();
       }
     });
     showOrHideMenu.add(item);
   }
 
-  /**
-   * Action on selecting a list of annotation type (or the 'all types' values)
-   * to show or hide for the selection.
-   * 
-   * @param types
-   * @param anyType
-   * @param doShow
-   */
-  protected void showHideAnnotation_actionPerformed(
-          Collection<String> types, boolean anyType, boolean doShow)
-  {
-    for (AlignmentAnnotation aa : ap.getAlignment()
-            .getAlignmentAnnotation())
-    {
-      if (anyType || types.contains(aa.label))
-      {
-        if ((aa.sequenceRef != null)
-                && ap.av.getSelectionGroup().getSequences()
-                        .contains(aa.sequenceRef))
-        {
-          aa.visible = doShow;
-        }
-      }
-    }
-    refresh();
-  }
-
-  private void buildGroupURLMenu(SequenceGroup sg, Vector groupLinks)
+  private void buildGroupURLMenu(SequenceGroup sg, List<String> groupLinks)
   {
 
     // TODO: usability: thread off the generation of group url content so root
     // menu appears asap
     // sequence only URLs
     // ID/regex match URLs
-    groupLinksMenu = new JMenu(
+    JMenu groupLinksMenu = new JMenu(
             MessageManager.getString("action.group_link"));
-    JMenu[] linkMenus = new JMenu[]
-    { null, new JMenu(MessageManager.getString("action.ids")),
+    // three types of url that might be created.
+    JMenu[] linkMenus = new JMenu[] { null,
+        new JMenu(MessageManager.getString("action.ids")),
         new JMenu(MessageManager.getString("action.sequences")),
-        new JMenu(MessageManager.getString("action.ids_sequences")) }; // three
-                                                                       // types
-                                                                       // of url
-                                                                       // that
-                                                                       // might
-                                                                       // be
-    // created.
+        new JMenu(MessageManager.getString("action.ids_sequences")) };
+
     SequenceI[] seqs = ap.av.getSelectionAsNewSequence();
     String[][] idandseqs = GroupUrlLink.formStrings(seqs);
-    Hashtable commonDbrefs = new Hashtable();
+    Hashtable<String, Object[]> commonDbrefs = new Hashtable<>();
     for (int sq = 0; sq < seqs.length; sq++)
     {
 
-      int start = seqs[sq].findPosition(sg.getStartRes()), end = seqs[sq]
-              .findPosition(sg.getEndRes());
+      int start = seqs[sq].findPosition(sg.getStartRes()),
+              end = seqs[sq].findPosition(sg.getEndRes());
       // just collect ids from dataset sequence
       // TODO: check if IDs collected from selecton group intersects with the
       // current selection, too
@@ -1063,18 +1099,17 @@ public class PopupMenu extends JPopupMenu
       {
         sqi = sqi.getDatasetSequence();
       }
-      DBRefEntry[] dbr = sqi.getDBRef();
+      DBRefEntry[] dbr = sqi.getDBRefs();
       if (dbr != null && dbr.length > 0)
       {
         for (int d = 0; d < dbr.length; d++)
         {
           String src = dbr[d].getSource(); // jalview.util.DBRefUtils.getCanonicalName(dbr[d].getSource()).toUpperCase();
-          Object[] sarray = (Object[]) commonDbrefs.get(src);
+          Object[] sarray = commonDbrefs.get(src);
           if (sarray == null)
           {
             sarray = new Object[2];
-            sarray[0] = new int[]
-            { 0 };
+            sarray[0] = new int[] { 0 };
             sarray[1] = new String[seqs.length];
 
             commonDbrefs.put(src, sarray);
@@ -1082,8 +1117,8 @@ public class PopupMenu extends JPopupMenu
 
           if (((String[]) sarray[1])[sq] == null)
           {
-            if (!dbr[d].hasMap()
-                    || (dbr[d].getMap().locateMappedRange(start, end) != null))
+            if (!dbr[d].hasMap() || (dbr[d].getMap()
+                    .locateMappedRange(start, end) != null))
             {
               ((String[]) sarray[1])[sq] = dbr[d].getAccessionId();
               ((int[]) sarray[0])[0]++;
@@ -1095,30 +1130,28 @@ public class PopupMenu extends JPopupMenu
     // now create group links for all distinct ID/sequence sets.
     boolean addMenu = false; // indicates if there are any group links to give
                              // to user
-    for (int i = 0; i < groupLinks.size(); i++)
+    for (String link : groupLinks)
     {
-      String link = groupLinks.elementAt(i).toString();
       GroupUrlLink urlLink = null;
       try
       {
         urlLink = new GroupUrlLink(link);
       } catch (Exception foo)
       {
-        jalview.bin.Cache.log.error("Exception for GroupURLLink '" + link
-                + "'", foo);
+        Cache.log.error("Exception for GroupURLLink '" + link + "'", foo);
         continue;
       }
       ;
       if (!urlLink.isValid())
       {
-        jalview.bin.Cache.log.error(urlLink.getInvalidMessage());
+        Cache.log.error(urlLink.getInvalidMessage());
         continue;
       }
       final String label = urlLink.getLabel();
       boolean usingNames = false;
       // Now see which parts of the group apply for this URL
       String ltarget = urlLink.getTarget(); // jalview.util.DBRefUtils.getCanonicalName(urlLink.getTarget());
-      Object[] idset = (Object[]) commonDbrefs.get(ltarget.toUpperCase());
+      Object[] idset = commonDbrefs.get(ltarget.toUpperCase());
       String[] seqstr, ids; // input to makeUrl
       if (idset != null)
       {
@@ -1155,13 +1188,12 @@ public class PopupMenu extends JPopupMenu
       if (urlset != null)
       {
         int type = urlLink.getGroupURLType() & 3;
-        // System.out.println(urlLink.getGroupURLType()
-        // +" "+((String[])urlset[3])[0]);
         // first two bits ofurlLink type bitfield are sequenceids and sequences
         // TODO: FUTURE: ensure the groupURL menu structure can be generalised
-        addshowLink(linkMenus[type], label
-                + (((type & 1) == 1) ? ("("
-                        + (usingNames ? "Names" : ltarget) + ")") : ""),
+        addshowLink(linkMenus[type],
+                label + (((type & 1) == 1)
+                        ? ("(" + (usingNames ? "Names" : ltarget) + ")")
+                        : ""),
                 urlLink, urlset);
         addMenu = true;
       }
@@ -1170,100 +1202,17 @@ public class PopupMenu extends JPopupMenu
     {
       groupLinksMenu = new JMenu(
               MessageManager.getString("action.group_link"));
-      for (int m = 0; m < linkMenus.length; m++)
-      {
-        if (linkMenus[m] != null
-                && linkMenus[m].getMenuComponentCount() > 0)
-        {
-          groupLinksMenu.add(linkMenus[m]);
-        }
-      }
-
-      groupMenu.add(groupLinksMenu);
-    }
-  }
-
-  /**
-   * 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 String[]
-            { url }));
-    item.addActionListener(new java.awt.event.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 java.awt.event.ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
+      for (int m = 0; m < linkMenus.length; m++)
       {
-        new Thread(new Runnable()
+        if (linkMenus[m] != null
+                && linkMenus[m].getMenuComponentCount() > 0)
         {
-
-          @Override
-          public void run()
-          {
-            try
-            {
-              showLink(urlgenerator.constructFrom(urlstub));
-            } catch (UrlStringTooLongException e)
-            {
-            }
-          }
-
-        }).start();
+          groupLinksMenu.add(linkMenus[m]);
+        }
       }
-    });
 
-    linkMenu.add(item);
+      groupMenu.add(groupLinksMenu);
+    }
   }
 
   /**
@@ -1274,10 +1223,9 @@ public class PopupMenu extends JPopupMenu
    */
   private void jbInit() throws Exception
   {
-    groupMenu.setText(MessageManager.getString("label.group"));
     groupMenu.setText(MessageManager.getString("label.selection"));
     groupName.setText(MessageManager.getString("label.name"));
-    groupName.addActionListener(new java.awt.event.ActionListener()
+    groupName.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1286,9 +1234,10 @@ public class PopupMenu extends JPopupMenu
       }
     });
     sequenceMenu.setText(MessageManager.getString("label.sequence"));
-    sequenceName.setText(MessageManager
-            .getString("label.edit_name_description"));
-    sequenceName.addActionListener(new java.awt.event.ActionListener()
+
+    JMenuItem sequenceName = new JMenuItem(
+            MessageManager.getString("label.edit_name_description"));
+    sequenceName.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1296,9 +1245,9 @@ public class PopupMenu extends JPopupMenu
         sequenceName_actionPerformed();
       }
     });
-    chooseAnnotations.setText(MessageManager
-            .getString("label.choose_annotations") + "...");
-    chooseAnnotations.addActionListener(new java.awt.event.ActionListener()
+    JMenuItem chooseAnnotations = new JMenuItem(
+            MessageManager.getString("action.choose_annotations"));
+    chooseAnnotations.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1306,31 +1255,30 @@ public class PopupMenu extends JPopupMenu
         chooseAnnotations_actionPerformed(e);
       }
     });
-    sequenceDetails.setText(MessageManager
-            .getString("label.sequence_details") + "...");
-    sequenceDetails.addActionListener(new java.awt.event.ActionListener()
+    JMenuItem sequenceDetails = new JMenuItem(
+            MessageManager.getString("label.sequence_details"));
+    sequenceDetails.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        sequenceDetails_actionPerformed();
+        createSequenceDetailsReport(new SequenceI[] { sequence });
       }
     });
-    sequenceSelDetails.setText(MessageManager
-            .getString("label.sequence_details") + "...");
-    sequenceSelDetails
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                sequenceSelectionDetails_actionPerformed();
-              }
-            });
-    PIDColour.setFocusPainted(false);
+    JMenuItem sequenceSelDetails = new JMenuItem(
+            MessageManager.getString("label.sequence_details"));
+    sequenceSelDetails.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        createSequenceDetailsReport(ap.av.getSequenceSelection());
+      }
+    });
+
     unGroupMenuItem
             .setText(MessageManager.getString("action.remove_group"));
-    unGroupMenuItem.addActionListener(new java.awt.event.ActionListener()
+    unGroupMenuItem.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1338,38 +1286,27 @@ public class PopupMenu extends JPopupMenu
         unGroupMenuItem_actionPerformed();
       }
     });
-    createGroupMenuItem.setText(MessageManager
-            .getString("action.create_group"));
     createGroupMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                createGroupMenuItem_actionPerformed();
-              }
-            });
-
-    outline.setText(MessageManager.getString("action.border_colour"));
-    outline.addActionListener(new java.awt.event.ActionListener()
+            .setText(MessageManager.getString("action.create_group"));
+    createGroupMenuItem.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        outline_actionPerformed();
+        createGroupMenuItem_actionPerformed();
       }
     });
-    nucleotideMenuItem
-            .setText(MessageManager.getString("label.nucleotide"));
-    nucleotideMenuItem.addActionListener(new ActionListener()
+
+    JMenuItem outline = new JMenuItem(
+            MessageManager.getString("action.border_colour"));
+    outline.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        nucleotideMenuItem_actionPerformed();
+        outline_actionPerformed();
       }
     });
-    colourMenu.setText(MessageManager.getString("label.group_colour"));
     showBoxes.setText(MessageManager.getString("action.boxes"));
     showBoxes.setState(true);
     showBoxes.addActionListener(new ActionListener()
@@ -1399,8 +1336,8 @@ public class PopupMenu extends JPopupMenu
         showColourText_actionPerformed();
       }
     });
-    displayNonconserved.setText(MessageManager
-            .getString("label.show_non_conversed"));
+    displayNonconserved
+            .setText(MessageManager.getString("label.show_non_conserved"));
     displayNonconserved.setState(true);
     displayNonconserved.addActionListener(new ActionListener()
     {
@@ -1411,7 +1348,7 @@ public class PopupMenu extends JPopupMenu
       }
     });
     editMenu.setText(MessageManager.getString("action.edit"));
-    cut.setText(MessageManager.getString("action.cut"));
+    JMenuItem cut = new JMenuItem(MessageManager.getString("action.cut"));
     cut.addActionListener(new ActionListener()
     {
       @Override
@@ -1429,7 +1366,7 @@ public class PopupMenu extends JPopupMenu
         changeCase(e);
       }
     });
-    copy.setText(MessageManager.getString("action.copy"));
+    JMenuItem copy = new JMenuItem(MessageManager.getString("action.copy"));
     copy.addActionListener(new ActionListener()
     {
       @Override
@@ -1456,82 +1393,18 @@ public class PopupMenu extends JPopupMenu
         changeCase(e);
       }
     });
-    pdbMenu.setText(MessageManager
-            .getString("label.associate_structure_with_sequence"));
-    pdbFromFile.setText(MessageManager.getString("label.from_file"));
-    pdbFromFile.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        pdbFromFile_actionPerformed();
-      }
-    });
-    // RNAFold.setText("From RNA Fold with predict2D");
-    // RNAFold.addActionListener(new ActionListener()
-    // {
-    // public void actionPerformed(ActionEvent e)
-    // {
-    // try {
-    // RNAFold_actionPerformed();
-    // } catch (Exception e1) {
-    // // TODO Auto-generated catch block
-    // e1.printStackTrace();
-    // }
-    // }
-    // });
-    // ContraFold.setText("From Contra Fold with predict2D");
-    // ContraFold.addActionListener(new ActionListener()
-    // {
-    // public void actionPerformed(ActionEvent e)
-    // {
-    // try {
-    // ContraFold_actionPerformed();
-    // } catch (Exception e1) {
-    // // TODO Auto-generated catch block
-    // e1.printStackTrace();
-    // }
-    // }
-    // });
-    enterPDB.setText(MessageManager.getString("label.enter_pdb_id"));
-    enterPDB.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        enterPDB_actionPerformed();
-      }
-    });
-    discoverPDB.setText(MessageManager.getString("label.discover_pdb_ids"));
-    discoverPDB.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        discoverPDB_actionPerformed();
-      }
-    });
-    outputMenu.setText(MessageManager.getString("label.out_to_textbox")
-            + "...");
-    showAnnotationsMenu.setText(MessageManager
-            .getString("label.show_annotations"));
-    hideAnnotationsMenu.setText(MessageManager
-            .getString("label.hide_annotations"));
-    final List<AlignmentAnnotation> referenceAnns = getDatasequenceAnnotationsNotOnAlignment(ap.av
-            .getSelectionGroup());
-    addDatasequenceAnnotations.setText(MessageManager
-            .getString("label.add_reference_annotations"));
-    addDatasequenceAnnotations.setEnabled(!referenceAnns.isEmpty());
-    addDatasequenceAnnotations.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        addReferenceAnnotations_actionPerformed(referenceAnns);
-      }
-    });
-    sequenceFeature.setText(MessageManager
-            .getString("label.create_sequence_feature"));
+    outputMenu.setText(
+            MessageManager.getString("label.out_to_textbox") + "...");
+    seqShowAnnotationsMenu
+            .setText(MessageManager.getString("label.show_annotations"));
+    seqHideAnnotationsMenu
+            .setText(MessageManager.getString("label.hide_annotations"));
+    groupShowAnnotationsMenu
+            .setText(MessageManager.getString("label.show_annotations"));
+    groupHideAnnotationsMenu
+            .setText(MessageManager.getString("label.hide_annotations"));
+    JMenuItem sequenceFeature = new JMenuItem(
+            MessageManager.getString("label.create_sequence_feature"));
     sequenceFeature.addActionListener(new ActionListener()
     {
       @Override
@@ -1540,22 +1413,29 @@ public class PopupMenu extends JPopupMenu
         sequenceFeature_actionPerformed();
       }
     });
-    textColour.setText(MessageManager.getString("label.text_colour"));
-    textColour.addActionListener(new ActionListener()
+    editGroupMenu.setText(MessageManager.getString("label.group"));
+    chooseStructure.setText(
+            MessageManager.getString("label.show_pdbstruct_dialog"));
+    chooseStructure.addActionListener(new ActionListener()
     {
       @Override
-      public void actionPerformed(ActionEvent e)
+      public void actionPerformed(ActionEvent actionEvent)
       {
-        textColour_actionPerformed();
+        SequenceI[] selectedSeqs = new SequenceI[] { sequence };
+        if (ap.av.getSelectionGroup() != null)
+        {
+          selectedSeqs = ap.av.getSequenceSelection();
+        }
+        new StructureChooser(selectedSeqs, sequence, ap);
       }
     });
-    jMenu1.setText(MessageManager.getString("label.group"));
-    structureMenu.setText(MessageManager.getString("label.structure"));
-    viewStructureMenu.setText(MessageManager
-            .getString("label.view_structure"));
+
+    rnaStructureMenu
+            .setText(MessageManager.getString("label.view_rna_structure"));
+
     // colStructureMenu.setText("Colour By Structure");
-    editSequence.setText(MessageManager.getString("label.edit_sequence")
-            + "...");
+    JMenuItem editSequence = new JMenuItem(
+            MessageManager.getString("label.edit_sequence") + "...");
     editSequence.addActionListener(new ActionListener()
     {
       @Override
@@ -1564,481 +1444,384 @@ public class PopupMenu extends JPopupMenu
         editSequence_actionPerformed(actionEvent);
       }
     });
+    makeReferenceSeq.setText(
+            MessageManager.getString("label.mark_as_representative"));
+    makeReferenceSeq.addActionListener(new ActionListener()
+    {
+
+      @Override
+      public void actionPerformed(ActionEvent actionEvent)
+      {
+        makeReferenceSeq_actionPerformed(actionEvent);
+
+      }
+    });
 
-    /*
-     * annotationMenuItem.setText("By Annotation");
-     * annotationMenuItem.addActionListener(new ActionListener() { public void
-     * actionPerformed(ActionEvent actionEvent) {
-     * annotationMenuItem_actionPerformed(actionEvent); } });
-     */
     groupMenu.add(sequenceSelDetails);
     add(groupMenu);
     add(sequenceMenu);
-    this.add(structureMenu);
+    add(rnaStructureMenu);
+    add(chooseStructure);
+    if (forIdPanel)
+    {
+      JMenuItem hideInsertions = new JMenuItem(
+              MessageManager.getString("label.hide_insertions"));
+      hideInsertions.addActionListener(new ActionListener()
+      {
+
+        @Override
+        public void actionPerformed(ActionEvent e)
+        {
+          hideInsertions_actionPerformed(e);
+        }
+      });
+      add(hideInsertions);
+    }
+    // annotations configuration panel suppressed for now
     // groupMenu.add(chooseAnnotations);
-    groupMenu.add(showAnnotationsMenu);
-    groupMenu.add(hideAnnotationsMenu);
-    groupMenu.add(addDatasequenceAnnotations);
+
+    /*
+     * Add show/hide annotations to the Sequence menu, and to the Selection menu
+     * (if a selection group is in force).
+     */
+    sequenceMenu.add(seqShowAnnotationsMenu);
+    sequenceMenu.add(seqHideAnnotationsMenu);
+    sequenceMenu.add(seqAddReferenceAnnotations);
+    groupMenu.add(groupShowAnnotationsMenu);
+    groupMenu.add(groupHideAnnotationsMenu);
+    groupMenu.add(groupAddReferenceAnnotations);
     groupMenu.add(editMenu);
     groupMenu.add(outputMenu);
     groupMenu.add(sequenceFeature);
     groupMenu.add(createGroupMenuItem);
     groupMenu.add(unGroupMenuItem);
-    groupMenu.add(jMenu1);
+    groupMenu.add(editGroupMenu);
     sequenceMenu.add(sequenceName);
     sequenceMenu.add(sequenceDetails);
-    colourMenu.add(textColour);
-    colourMenu.add(noColourmenuItem);
-    colourMenu.add(clustalColour);
-    colourMenu.add(BLOSUM62Colour);
-    colourMenu.add(PIDColour);
-    colourMenu.add(zappoColour);
-    colourMenu.add(taylorColour);
-    colourMenu.add(hydrophobicityColour);
-    colourMenu.add(helixColour);
-    colourMenu.add(strandColour);
-    colourMenu.add(turnColour);
-    colourMenu.add(buriedColour);
-    colourMenu.add(nucleotideMenuItem);
-    if (ap.getAlignment().isNucleotide())
-    {
-      // JBPNote - commented since the colourscheme isn't functional
-      // colourMenu.add(RNAInteractionColour);
-      colourMenu.add(purinePyrimidineColour);
-    }
-    // colourMenu.add(covariationColour);
-    colourMenu.add(userDefinedColour);
-
-    if (jalview.gui.UserDefinedColours.getUserColourSchemes() != null)
-    {
-      java.util.Enumeration userColours = jalview.gui.UserDefinedColours
-              .getUserColourSchemes().keys();
+    sequenceMenu.add(makeReferenceSeq);
 
-      while (userColours.hasMoreElements())
-      {
-        JMenuItem item = new JMenuItem(userColours.nextElement().toString());
-        item.addActionListener(new ActionListener()
-        {
-          @Override
-          public void actionPerformed(ActionEvent evt)
-          {
-            userDefinedColour_actionPerformed(evt);
-          }
-        });
-        colourMenu.add(item);
-      }
-    }
+    initColourMenu();
+    buildColourMenu();
 
-    colourMenu.addSeparator();
-    colourMenu.add(abovePIDColour);
-    colourMenu.add(conservationMenuItem);
-    // colourMenu.add(annotationMenuItem);
     editMenu.add(copy);
     editMenu.add(cut);
     editMenu.add(editSequence);
     editMenu.add(upperCase);
     editMenu.add(lowerCase);
     editMenu.add(toggle);
-    pdbMenu.add(pdbFromFile);
-    // JBPNote: These shouldn't be added here - should appear in a generic
-    // 'apply web service to this sequence menu'
-    // pdbMenu.add(RNAFold);
-    // pdbMenu.add(ContraFold);
-    pdbMenu.add(enterPDB);
-    pdbMenu.add(discoverPDB);
-    jMenu1.add(groupName);
-    jMenu1.add(colourMenu);
-    jMenu1.add(showBoxes);
-    jMenu1.add(showText);
-    jMenu1.add(showColourText);
-    jMenu1.add(outline);
-    jMenu1.add(displayNonconserved);
-    structureMenu.add(pdbMenu);
-    structureMenu.add(viewStructureMenu);
-    // structureMenu.add(colStructureMenu);
-    noColourmenuItem.setText(MessageManager.getString("label.none"));
-    noColourmenuItem.addActionListener(new java.awt.event.ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        noColourmenuItem_actionPerformed();
-      }
-    });
+    editGroupMenu.add(groupName);
+    editGroupMenu.add(colourMenu);
+    editGroupMenu.add(showBoxes);
+    editGroupMenu.add(showText);
+    editGroupMenu.add(showColourText);
+    editGroupMenu.add(outline);
+    editGroupMenu.add(displayNonconserved);
+  }
 
-    clustalColour.setText(MessageManager
-            .getString("label.clustalx_colours"));
-    clustalColour.addActionListener(new java.awt.event.ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        clustalColour_actionPerformed();
-      }
-    });
-    zappoColour.setText(MessageManager.getString("label.zappo"));
-    zappoColour.addActionListener(new java.awt.event.ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        zappoColour_actionPerformed();
-      }
-    });
-    taylorColour.setText(MessageManager.getString("label.taylor"));
-    taylorColour.addActionListener(new java.awt.event.ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        taylorColour_actionPerformed();
-      }
-    });
-    hydrophobicityColour.setText(MessageManager
-            .getString("label.hydrophobicity"));
-    hydrophobicityColour
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                hydrophobicityColour_actionPerformed();
-              }
-            });
-    helixColour.setText(MessageManager.getString("label.helix_propensity"));
-    helixColour.addActionListener(new java.awt.event.ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        helixColour_actionPerformed();
-      }
-    });
-    strandColour.setText(MessageManager
-            .getString("label.strand_propensity"));
-    strandColour.addActionListener(new java.awt.event.ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        strandColour_actionPerformed();
-      }
-    });
-    turnColour.setText(MessageManager.getString("label.turn_propensity"));
-    turnColour.addActionListener(new java.awt.event.ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        turnColour_actionPerformed();
-      }
-    });
-    buriedColour.setText(MessageManager.getString("label.buried_index"));
-    buriedColour.addActionListener(new java.awt.event.ActionListener()
+  /**
+   * Constructs the entries for the colour menu
+   */
+  protected void initColourMenu()
+  {
+    colourMenu.setText(MessageManager.getString("label.group_colour"));
+    textColour.setText(MessageManager.getString("label.text_colour"));
+    textColour.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        buriedColour_actionPerformed();
+        textColour_actionPerformed();
       }
     });
-    abovePIDColour.setText(MessageManager
-            .getString("label.above_identity_percentage"));
-    abovePIDColour.addActionListener(new java.awt.event.ActionListener()
+
+    abovePIDColour.setText(
+            MessageManager.getString("label.above_identity_threshold"));
+    abovePIDColour.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        abovePIDColour_actionPerformed();
+        abovePIDColour_actionPerformed(abovePIDColour.isSelected());
       }
     });
-    userDefinedColour.setText(MessageManager
-            .getString("action.user_defined"));
-    userDefinedColour.addActionListener(new java.awt.event.ActionListener()
+
+    modifyPID.setText(
+            MessageManager.getString("label.modify_identity_threshold"));
+    modifyPID.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        userDefinedColour_actionPerformed(e);
+        modifyPID_actionPerformed();
       }
     });
-    PIDColour
-            .setText(MessageManager.getString("label.percentage_identity"));
-    PIDColour.addActionListener(new java.awt.event.ActionListener()
+
+    conservationMenuItem
+            .setText(MessageManager.getString("action.by_conservation"));
+    conservationMenuItem.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        PIDColour_actionPerformed();
+        conservationMenuItem_actionPerformed(
+                conservationMenuItem.isSelected());
       }
     });
-    BLOSUM62Colour.setText(MessageManager.getString("label.blosum62"));
-    BLOSUM62Colour.addActionListener(new java.awt.event.ActionListener()
+
+    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()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        BLOSUM62Colour_actionPerformed();
+        modifyConservation_actionPerformed();
       }
     });
-    purinePyrimidineColour.setText(MessageManager
-            .getString("label.purine_pyrimidine"));
-    purinePyrimidineColour
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                purinePyrimidineColour_actionPerformed();
-              }
-            });
-
-    /*
-     * covariationColour.addActionListener(new java.awt.event.ActionListener() {
-     * public void actionPerformed(ActionEvent e) {
-     * covariationColour_actionPerformed(); } });
-     */
-
-    conservationMenuItem.setText(MessageManager
-            .getString("label.conservation"));
-    conservationMenuItem
-            .addActionListener(new java.awt.event.ActionListener()
-            {
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                conservationMenuItem_actionPerformed();
-              }
-            });
-  }
-
-  /**
-   * Get a list of any annotations on the dataset sequences in the current
-   * selection group that are not also on the alignment.
-   * <p/>
-   * The criteria for 'on the alignment' is finding an annotation that matches
-   * on sequenceRef.datasetSequence, calcId and label.
-   * 
-   * @return
-   */
-  protected List<AlignmentAnnotation> getDatasequenceAnnotationsNotOnAlignment(
-          SequenceGroup sg)
-  {
-    List<AlignmentAnnotation> result = new ArrayList<AlignmentAnnotation>();
-    
-    for (SequenceI seq : sg.getSequences())
-    {
-      SequenceI dataset = seq.getDatasetSequence();
-      AlignmentAnnotation[] datasetAnnotations = dataset.getAnnotation();
-      if (datasetAnnotations != null)
-      {
-        for (AlignmentAnnotation dsann : datasetAnnotations)
-        {
-          /*
-           * If the alignment has no annotation that matches this one...
-           */
-          if (!ap.getAlignment()
-                  .findAnnotation(dataset, dsann.getCalcId(), dsann.label)
-                  .iterator().hasNext())
-          {
-            /*
-             * ...then add it to the result list
-             */
-            result.add(dsann);
-          }
-        }
-      }
-    }
-    return result;
   }
 
   /**
-   * Add any annotations on the sequence dataset to the alignment (that are not
-   * already copied to it).
+   * Builds the group colour sub-menu, including any user-defined colours which
+   * were loaded at startup or during the Jalview session
    */
-  protected void addReferenceAnnotations_actionPerformed(
-          List<AlignmentAnnotation> anns)
+  protected void buildColourMenu()
   {
-    for (AlignmentAnnotation ann : anns)
+    SequenceGroup sg = ap.av.getSelectionGroup();
+    if (sg == null)
     {
-      // todo: copy, add, adjust...
+      /*
+       * popup menu with no sequence group scope
+       */
+      return;
     }
-  }
+    colourMenu.removeAll();
+    colourMenu.add(textColour);
+    colourMenu.addSeparator();
 
-  protected void sequenceSelectionDetails_actionPerformed()
-  {
-    createSequenceDetailsReport(ap.av.getSequenceSelection());
-  }
+    ButtonGroup bg = ColourMenuHelper.addMenuItems(colourMenu, this, sg,
+            false);
+    bg.add(annotationColour);
+    colourMenu.add(annotationColour);
 
-  protected void sequenceDetails_actionPerformed()
-  {
-    createSequenceDetailsReport(new SequenceI[]
-    { sequence });
+    colourMenu.addSeparator();
+    colourMenu.add(conservationMenuItem);
+    colourMenu.add(modifyConservation);
+    colourMenu.add(abovePIDColour);
+    colourMenu.add(modifyPID);
   }
 
-  public void createSequenceDetailsReport(SequenceI[] sequences)
+  protected void modifyConservation_actionPerformed()
   {
-    CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
-    StringBuffer contents = new StringBuffer();
-    for (SequenceI seq : sequences)
+    SequenceGroup sg = getGroup();
+    if (sg.cs != null)
     {
-      contents.append("<p><h2>"
-              + MessageManager
-                      .formatMessage(
-                              "label.create_sequence_details_report_annotation_for",
-                              new String[]
-                              { seq.getDisplayId(true) }) + "</h2></p><p>");
-      new SequenceAnnotationReport(null)
-              .createSequenceAnnotationReport(
-                      contents,
-                      seq,
-                      true,
-                      true,
-                      false,
-                      (ap.seqPanel.seqCanvas.fr != null) ? ap.seqPanel.seqCanvas.fr.minmax
-                              : null);
-      contents.append("</p>");
+      SliderPanel.setConservationSlider(ap, sg.cs, sg.getName());
+      SliderPanel.showConservationSlider();
     }
-    cap.setText("<html>" + contents.toString() + "</html>");
-
-    Desktop.instance.addInternalFrame(cap, MessageManager.formatMessage(
-            "label.sequece_details_for",
-            (sequences.length == 1 ? new String[]
-            { sequences[0].getDisplayId(true) } : new String[]
-            { MessageManager.getString("label.selection") })), 500, 400);
-
   }
 
-  protected void showNonconserved_actionPerformed()
+  protected void modifyPID_actionPerformed()
   {
-    getGroup().setShowNonconserved(displayNonconserved.isSelected());
-    refresh();
+    SequenceGroup sg = getGroup();
+    if (sg.cs != null)
+    {
+      // int threshold = SliderPanel.setPIDSliderSource(ap, sg.cs, getGroup()
+      // .getName());
+      // sg.cs.setThreshold(threshold, ap.av.isIgnoreGapsConsensus());
+      SliderPanel.setPIDSliderSource(ap, sg.cs, getGroup().getName());
+      SliderPanel.showPIDSlider();
+    }
   }
 
   /**
-   * call to refresh view after settings change
+   * Check for any annotations on the underlying dataset sequences (for the
+   * current selection group) which are not 'on the alignment'.If any are found,
+   * enable the option to add them to the alignment. The criteria for 'on the
+   * alignment' is finding an alignment annotation on the alignment, matched on
+   * calcId, label and sequenceRef.
+   * 
+   * A tooltip is also constructed that displays the source (calcId) and type
+   * (label) of the annotations that can be added.
+   * 
+   * @param menuItem
+   * @param forSequences
    */
-  void refresh()
+  protected void configureReferenceAnnotationsMenu(JMenuItem menuItem,
+          List<SequenceI> forSequences)
   {
-    ap.updateAnnotation();
-    ap.paintAlignment(true);
+    menuItem.setEnabled(false);
 
-    PaintRefresher.Refresh(this, ap.av.getSequenceSetId());
-  }
+    /*
+     * Temporary store to hold distinct calcId / type pairs for the tooltip.
+     * Using TreeMap means calcIds are shown in alphabetical order.
+     */
+    SortedMap<String, String> tipEntries = new TreeMap<>();
+    final Map<SequenceI, List<AlignmentAnnotation>> candidates = new LinkedHashMap<>();
+    AlignmentI al = this.ap.av.getAlignment();
+    AlignmentUtils.findAddableReferenceAnnotations(forSequences, tipEntries,
+            candidates, al);
+    if (!candidates.isEmpty())
+    {
+      StringBuilder tooltip = new StringBuilder(64);
+      tooltip.append(MessageManager.getString("label.add_annotations_for"));
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  protected void clustalColour_actionPerformed()
-  {
-    SequenceGroup sg = getGroup();
-    sg.cs = new ClustalxColourScheme(sg, ap.av.getHiddenRepSequences());
-    refresh();
+      /*
+       * Found annotations that could be added. Enable the menu item, and
+       * configure its tooltip and action.
+       */
+      menuItem.setEnabled(true);
+      for (String calcId : tipEntries.keySet())
+      {
+        tooltip.append("<br/>" + calcId + "/" + tipEntries.get(calcId));
+      }
+      String tooltipText = JvSwingUtils.wrapTooltip(true,
+              tooltip.toString());
+      menuItem.setToolTipText(tooltipText);
+
+      menuItem.addActionListener(new ActionListener()
+      {
+        @Override
+        public void actionPerformed(ActionEvent e)
+        {
+          addReferenceAnnotations_actionPerformed(candidates);
+        }
+      });
+    }
   }
 
   /**
-   * DOCUMENT ME!
+   * Add annotations to the sequences and to the alignment.
    * 
-   * @param e
-   *          DOCUMENT ME!
+   * @param candidates
+   *          a map whose keys are sequences on the alignment, and values a list
+   *          of annotations to add to each sequence
    */
-  protected void zappoColour_actionPerformed()
+  protected void addReferenceAnnotations_actionPerformed(
+          Map<SequenceI, List<AlignmentAnnotation>> candidates)
   {
-    getGroup().cs = new ZappoColourScheme();
+    final SequenceGroup selectionGroup = this.ap.av.getSelectionGroup();
+    final AlignmentI alignment = this.ap.getAlignment();
+    AlignmentUtils.addReferenceAnnotations(candidates, alignment,
+            selectionGroup);
     refresh();
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  protected void taylorColour_actionPerformed()
+  protected void makeReferenceSeq_actionPerformed(ActionEvent actionEvent)
   {
-    getGroup().cs = new TaylorColourScheme();
+    if (!ap.av.getAlignment().hasSeqrep())
+    {
+      // initialise the display flags so the user sees something happen
+      ap.av.setDisplayReferenceSeq(true);
+      ap.av.setColourByReferenceSeq(true);
+      ap.av.getAlignment().setSeqrep(sequence);
+    }
+    else
+    {
+      if (ap.av.getAlignment().getSeqrep() == sequence)
+      {
+        ap.av.getAlignment().setSeqrep(null);
+      }
+      else
+      {
+        ap.av.getAlignment().setSeqrep(sequence);
+      }
+    }
     refresh();
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  protected void hydrophobicityColour_actionPerformed()
+  protected void hideInsertions_actionPerformed(ActionEvent actionEvent)
   {
-    getGroup().cs = new HydrophobicColourScheme();
-    refresh();
-  }
+    HiddenColumns hidden = ap.av.getAlignment().getHiddenColumns();
+    BitSet inserts = new BitSet();
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  protected void helixColour_actionPerformed()
-  {
-    getGroup().cs = new HelixColourScheme();
+    boolean markedPopup = false;
+    // mark inserts in current selection
+    if (ap.av.getSelectionGroup() != null)
+    {
+      // mark just the columns in the selection group to be hidden
+      inserts.set(ap.av.getSelectionGroup().getStartRes(),
+              ap.av.getSelectionGroup().getEndRes() + 1); // TODO why +1?
+
+      // now clear columns without gaps
+      for (SequenceI sq : ap.av.getSelectionGroup().getSequences())
+      {
+        if (sq == sequence)
+        {
+          markedPopup = true;
+        }
+        inserts.and(sq.getInsertionsAsBits());
+      }
+      hidden.clearAndHideColumns(inserts, ap.av.getSelectionGroup().getStartRes(),
+              ap.av.getSelectionGroup().getEndRes());
+    }
+
+    // now mark for sequence under popup if we haven't already done it
+    else if (!markedPopup && sequence != null)
+    {
+      inserts.or(sequence.getInsertionsAsBits());
+
+      // and set hidden columns accordingly
+      hidden.hideColumns(inserts);
+    }
     refresh();
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  protected void strandColour_actionPerformed()
+  protected void sequenceSelectionDetails_actionPerformed()
   {
-    getGroup().cs = new StrandColourScheme();
-    refresh();
+    createSequenceDetailsReport(ap.av.getSequenceSelection());
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  protected void turnColour_actionPerformed()
+  public void createSequenceDetailsReport(SequenceI[] sequences)
   {
-    getGroup().cs = new TurnColourScheme();
-    refresh();
+    CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
+    StringBuilder contents = new StringBuilder(128);
+    for (SequenceI seq : sequences)
+    {
+      contents.append("<p><h2>" + MessageManager.formatMessage(
+              "label.create_sequence_details_report_annotation_for",
+              new Object[]
+              { seq.getDisplayId(true) }) + "</h2></p><p>");
+      new SequenceAnnotationReport(false).createSequenceAnnotationReport(
+              contents, seq, true, true, ap.getSeqPanel().seqCanvas.fr);
+      contents.append("</p>");
+    }
+    cap.setText("<html>" + contents.toString() + "</html>");
+
+    Desktop.addInternalFrame(cap,
+            MessageManager.formatMessage("label.sequence_details_for",
+                    (sequences.length == 1 ? new Object[]
+                    { sequences[0].getDisplayId(true) }
+                            : new Object[]
+                            { MessageManager
+                                    .getString("label.selection") })),
+            500, 400);
+
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  protected void buriedColour_actionPerformed()
+  protected void showNonconserved_actionPerformed()
   {
-    getGroup().cs = new BuriedColourScheme();
+    getGroup().setShowNonconserved(displayNonconserved.isSelected());
     refresh();
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
+   * call to refresh view after settings change
    */
-  public void nucleotideMenuItem_actionPerformed()
+  void refresh()
   {
-    getGroup().cs = new NucleotideColourScheme();
-    refresh();
-  }
+    ap.updateAnnotation();
+    // removed paintAlignment(true) here:
+    // updateAnnotation calls paintAlignment already, so don't need to call
+    // again
 
-  protected void purinePyrimidineColour_actionPerformed()
-  {
-    getGroup().cs = new PurinePyrimidineColourScheme();
-    refresh();
+    PaintRefresher.Refresh(this, ap.av.getSequenceSetId());
   }
 
   /*
@@ -2048,10 +1831,12 @@ public class PopupMenu extends JPopupMenu
   /**
    * DOCUMENT ME!
    * 
+   * @param selected
+   * 
    * @param e
    *          DOCUMENT ME!
    */
-  protected void abovePIDColour_actionPerformed()
+  public void abovePIDColour_actionPerformed(boolean selected)
   {
     SequenceGroup sg = getGroup();
     if (sg.cs == null)
@@ -2059,49 +1844,27 @@ public class PopupMenu extends JPopupMenu
       return;
     }
 
-    if (abovePIDColour.isSelected())
+    if (selected)
     {
       sg.cs.setConsensus(AAFrequency.calculate(
               sg.getSequences(ap.av.getHiddenRepSequences()),
               sg.getStartRes(), sg.getEndRes() + 1));
 
-      int threshold = SliderPanel.setPIDSliderSource(ap, sg.cs, getGroup()
-              .getName());
+      int threshold = SliderPanel.setPIDSliderSource(ap,
+              sg.getGroupColourScheme(), getGroup().getName());
 
-      sg.cs.setThreshold(threshold, ap.av.getIgnoreGapsConsensus());
+      sg.cs.setThreshold(threshold, ap.av.isIgnoreGapsConsensus());
 
       SliderPanel.showPIDSlider();
     }
     else
     // remove PIDColouring
     {
-      sg.cs.setThreshold(0, ap.av.getIgnoreGapsConsensus());
-    }
-
-    refresh();
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  protected void userDefinedColour_actionPerformed(ActionEvent e)
-  {
-    SequenceGroup sg = getGroup();
-
-    if (e.getSource().equals(userDefinedColour))
-    {
-      new UserDefinedColours(ap, sg);
+      sg.cs.setThreshold(0, ap.av.isIgnoreGapsConsensus());
+      SliderPanel.hidePIDSlider();
     }
-    else
-    {
-      UserColourScheme udc = (UserColourScheme) UserDefinedColours
-              .getUserColourSchemes().get(e.getActionCommand());
+    modifyPID.setEnabled(selected);
 
-      sg.cs = udc;
-    }
     refresh();
   }
 
@@ -2123,54 +1886,7 @@ public class PopupMenu extends JPopupMenu
    * @param e
    *          DOCUMENT ME!
    */
-  protected void PIDColour_actionPerformed()
-  {
-    SequenceGroup sg = getGroup();
-    sg.cs = new PIDColourScheme();
-    sg.cs.setConsensus(AAFrequency.calculate(
-            sg.getSequences(ap.av.getHiddenRepSequences()),
-            sg.getStartRes(), sg.getEndRes() + 1));
-    refresh();
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  protected void BLOSUM62Colour_actionPerformed()
-  {
-    SequenceGroup sg = getGroup();
-
-    sg.cs = new Blosum62ColourScheme();
-
-    sg.cs.setConsensus(AAFrequency.calculate(
-            sg.getSequences(ap.av.getHiddenRepSequences()),
-            sg.getStartRes(), sg.getEndRes() + 1));
-
-    refresh();
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  protected void noColourmenuItem_actionPerformed()
-  {
-    getGroup().cs = null;
-    refresh();
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  protected void conservationMenuItem_actionPerformed()
+  public void conservationMenuItem_actionPerformed(boolean selected)
   {
     SequenceGroup sg = getGroup();
     if (sg.cs == null)
@@ -2178,45 +1894,28 @@ public class PopupMenu extends JPopupMenu
       return;
     }
 
-    if (conservationMenuItem.isSelected())
+    if (selected)
     {
       // JBPNote: Conservation name shouldn't be i18n translated
       Conservation c = new Conservation("Group",
-              ResidueProperties.propHash, 3, sg.getSequences(ap.av
-                      .getHiddenRepSequences()), sg.getStartRes(),
-              sg.getEndRes() + 1);
+              sg.getSequences(ap.av.getHiddenRepSequences()),
+              sg.getStartRes(), sg.getEndRes() + 1);
 
       c.calculate();
       c.verdict(false, ap.av.getConsPercGaps());
-
       sg.cs.setConservation(c);
 
-      SliderPanel.setConservationSlider(ap, sg.cs, sg.getName());
+      SliderPanel.setConservationSlider(ap, sg.getGroupColourScheme(),
+              sg.getName());
       SliderPanel.showConservationSlider();
     }
     else
     // remove ConservationColouring
     {
       sg.cs.setConservation(null);
+      SliderPanel.hideConservationSlider();
     }
-
-    refresh();
-  }
-
-  public void annotationMenuItem_actionPerformed(ActionEvent actionEvent)
-  {
-    SequenceGroup sg = getGroup();
-    if (sg == null)
-    {
-      return;
-    }
-
-    AnnotationColourGradient acg = new AnnotationColourGradient(
-            sequence.getAnnotation()[0], null,
-            AnnotationColourGradient.NO_THRESHOLD);
-
-    acg.setPredefinedColours(true);
-    sg.cs = acg;
+    modifyConservation.setEnabled(selected);
 
     refresh();
   }
@@ -2232,8 +1931,8 @@ public class PopupMenu extends JPopupMenu
 
     SequenceGroup sg = getGroup();
     EditNameDialog dialog = new EditNameDialog(sg.getName(),
-            sg.getDescription(), "       "
-                    + MessageManager.getString("label.group_name") + " ",
+            sg.getDescription(),
+            "       " + MessageManager.getString("label.group_name") + " ",
             MessageManager.getString("label.group_description") + " ",
             MessageManager.getString("label.edit_group_name_description"),
             ap.alignFrame);
@@ -2266,10 +1965,9 @@ public class PopupMenu extends JPopupMenu
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
+   * Shows a dialog where the sequence name and description may be edited. If a
+   * name containing spaces is entered, these are converted to underscores, with a
+   * warning message.
    */
   void sequenceName_actionPerformed()
   {
@@ -2278,8 +1976,8 @@ public class PopupMenu extends JPopupMenu
             "       " + MessageManager.getString("label.sequence_name")
                     + " ",
             MessageManager.getString("label.sequence_description") + " ",
-            MessageManager
-                    .getString("label.edit_sequence_name_description"),
+            MessageManager.getString(
+                    "label.edit_sequence_name_description"),
             ap.alignFrame);
 
     if (!dialog.accept)
@@ -2287,28 +1985,28 @@ public class PopupMenu extends JPopupMenu
       return;
     }
 
-    if (dialog.getName() != null)
+    String name = dialog.getName();
+    if (name != null)
     {
-      if (dialog.getName().indexOf(" ") > -1)
+      if (name.indexOf(" ") > -1)
       {
-        JOptionPane
-                .showMessageDialog(
-                        ap,
-                        MessageManager
-                                .getString("label.spaces_converted_to_backslashes"),
-                        MessageManager
-                                .getString("label.no_spaces_allowed_sequence_name"),
-                        JOptionPane.WARNING_MESSAGE);
+        JvOptionPane.showMessageDialog(ap,
+                MessageManager
+                        .getString("label.spaces_converted_to_backslashes"),
+                MessageManager
+                        .getString("label.no_spaces_allowed_sequence_name"),
+                JvOptionPane.WARNING_MESSAGE);
+        name = name.replace(' ', '_');
       }
 
-      sequence.setName(dialog.getName().replace(' ', '_'));
-      ap.paintAlignment(false);
+      sequence.setName(name);
+      ap.paintAlignment(false, false);
     }
 
     sequence.setDescription(dialog.getDescription());
 
-    ap.av.firePropertyChange("alignment", null, ap.av.getAlignment()
-            .getSequences());
+    ap.av.firePropertyChange("alignment", null,
+            ap.av.getAlignment().getSequences());
 
   }
 
@@ -2390,55 +2088,9 @@ public class PopupMenu extends JPopupMenu
     refresh();
   }
 
-  public void showLink(String url)
-  {
-    try
-    {
-      jalview.util.BrowserLauncher.openURL(url);
-    } catch (Exception ex)
-    {
-      JOptionPane.showInternalMessageDialog(Desktop.desktop,
-              MessageManager.getString("label.web_browser_not_found_unix"),
-              MessageManager.getString("label.web_browser_not_found"),
-              JOptionPane.WARNING_MESSAGE);
-
-      ex.printStackTrace();
-    }
-  }
-
   void hideSequences(boolean representGroup)
   {
-    SequenceGroup sg = ap.av.getSelectionGroup();
-    if (sg == null || sg.getSize() < 1)
-    {
-      ap.av.hideSequence(new SequenceI[]
-      { sequence });
-      return;
-    }
-
-    ap.av.setSelectionGroup(null);
-
-    if (representGroup)
-    {
-      ap.av.hideRepSequences(sequence, sg);
-
-      return;
-    }
-
-    int gsize = sg.getSize();
-    SequenceI[] hseqs;
-
-    hseqs = new SequenceI[gsize];
-
-    int index = 0;
-    for (int i = 0; i < gsize; i++)
-    {
-      hseqs[index++] = sg.getSequenceAt(i);
-    }
-
-    ap.av.hideSequence(hseqs);
-    // refresh(); TODO: ? needed ?
-    ap.av.sendSelection();
+    ap.av.hideSequences(sequence, representGroup);
   }
 
   public void copy_actionPerformed()
@@ -2458,8 +2110,8 @@ public class PopupMenu extends JPopupMenu
 
     if (sg != null)
     {
-      int[][] startEnd = ap.av.getVisibleRegionBoundaries(sg.getStartRes(),
-              sg.getEndRes() + 1);
+      List<int[]> startEnd = ap.av.getVisibleRegionBoundaries(
+              sg.getStartRes(), sg.getEndRes() + 1);
 
       String description;
       int caseChange;
@@ -2486,8 +2138,8 @@ public class PopupMenu extends JPopupMenu
 
       ap.alignFrame.addHistoryItem(caseCommand);
 
-      ap.av.firePropertyChange("alignment", null, ap.av.getAlignment()
-              .getSequences());
+      ap.av.firePropertyChange("alignment", null,
+              ap.av.getAlignment().getSequences());
 
     }
   }
@@ -2496,8 +2148,8 @@ public class PopupMenu extends JPopupMenu
   {
     CutAndPasteTransfer cap = new CutAndPasteTransfer();
     cap.setForInput(null);
-    Desktop.addInternalFrame(cap, MessageManager.formatMessage(
-            "label.alignment_output_command", new String[]
+    Desktop.addInternalFrame(cap, MessageManager
+            .formatMessage("label.alignment_output_command", new Object[]
             { e.getActionCommand() }), 600, 500);
 
     String[] omitHidden = null;
@@ -2506,79 +2158,10 @@ public class PopupMenu extends JPopupMenu
     // or we simply trust the user wants
     // wysiwig behaviour
 
-    cap.setText(new FormatAdapter().formatSequences(e.getActionCommand(),
-            ap.av, true));
-  }
-
-  public void pdbFromFile_actionPerformed()
-  {
-    jalview.io.JalviewFileChooser chooser = new jalview.io.JalviewFileChooser(
-            jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
-    chooser.setFileView(new jalview.io.JalviewFileView());
-    chooser.setDialogTitle(MessageManager.formatMessage(
-            "label.select_pdb_file_for", new String[]
-            { sequence.getDisplayId(false) }));
-    chooser.setToolTipText(MessageManager.formatMessage(
-            "label.load_pdb_file_associate_with_sequence", new String[]
-            { sequence.getDisplayId(false) }));
-
-    int value = chooser.showOpenDialog(null);
-
-    if (value == jalview.io.JalviewFileChooser.APPROVE_OPTION)
-    {
-      String choice = chooser.getSelectedFile().getPath();
-      jalview.bin.Cache.setProperty("LAST_DIRECTORY", choice);
-      new AssociatePdbFileWithSeq().associatePdbWithSeq(choice,
-              jalview.io.AppletFormatAdapter.FILE, sequence, true);
-    }
-
-  }
-
-  // JBNote: commented out - these won't be instantiated here...!
-  // public void RNAFold_actionPerformed() throws Exception
-  // {
-  // Predict2D P2D = new Predict2D();
-  // P2D.getStructure2DFromRNAFold("toto");
-  // }
-  //
-  // public void ContraFold_actionPerformed() throws Exception
-  // {
-  // Predict2D P2D = new Predict2D();
-  // P2D.getStructure2DFromContraFold("toto");
-  // }
-  public void enterPDB_actionPerformed()
-  {
-    String id = JOptionPane.showInternalInputDialog(Desktop.desktop,
-            MessageManager.getString("label.enter_pdb_id"),
-            MessageManager.getString("label.enter_pdb_id"),
-            JOptionPane.QUESTION_MESSAGE);
-
-    if (id != null && id.length() > 0)
-    {
-      PDBEntry entry = new PDBEntry();
-      entry.setId(id.toUpperCase());
-      sequence.getDatasetSequence().addPDBId(entry);
-    }
-  }
-
-  public void discoverPDB_actionPerformed()
-  {
-
-    final SequenceI[] sequences = ((ap.av.getSelectionGroup() == null) ? new SequenceI[]
-    { sequence }
-            : ap.av.getSequenceSelection());
-    Thread discpdb = new Thread(new Runnable()
-    {
-      @Override
-      public void run()
-      {
-
-        new jalview.ws.DBRefFetcher(sequences, ap.alignFrame)
-                .fetchDBRefs(false);
-      }
-
-    });
-    discpdb.start();
+    FileFormatI fileFormat = FileFormats.getInstance()
+            .forName(e.getActionCommand());
+    cap.setText(
+            new FormatAdapter(ap).formatSequences(fileFormat, ap, true));
   }
 
   public void sequenceFeature_actionPerformed()
@@ -2589,33 +2172,37 @@ public class PopupMenu extends JPopupMenu
       return;
     }
 
-    int rsize = 0, gSize = sg.getSize();
-    SequenceI[] rseqs, seqs = new SequenceI[gSize];
-    SequenceFeature[] tfeatures, features = new SequenceFeature[gSize];
+    List<SequenceI> seqs = new ArrayList<>();
+    List<SequenceFeature> features = new ArrayList<>();
 
+    /*
+     * assemble dataset sequences, and template new sequence features,
+     * for the amend features dialog
+     */
+    int gSize = sg.getSize();
     for (int i = 0; i < gSize; i++)
     {
       int start = sg.getSequenceAt(i).findPosition(sg.getStartRes());
       int end = sg.findEndRes(sg.getSequenceAt(i));
       if (start <= end)
       {
-        seqs[rsize] = sg.getSequenceAt(i).getDatasetSequence();
-        features[rsize] = new SequenceFeature(null, null, null, start, end,
-                "Jalview");
-        rsize++;
+        seqs.add(sg.getSequenceAt(i).getDatasetSequence());
+        features.add(new SequenceFeature(null, null, start, end, null));
       }
     }
-    rseqs = new SequenceI[rsize];
-    tfeatures = new SequenceFeature[rsize];
-    System.arraycopy(seqs, 0, rseqs, 0, rsize);
-    System.arraycopy(features, 0, tfeatures, 0, rsize);
-    features = tfeatures;
-    seqs = rseqs;
-    if (ap.seqPanel.seqCanvas.getFeatureRenderer().amendFeatures(seqs,
-            features, true, ap))
-    {
-      ap.alignFrame.setShowSeqFeatures(true);
-      ap.highlightSearchResults(null);
+
+    /*
+     * an entirely gapped region will generate empty lists of sequence / features
+     */
+    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
+      }
     }
   }
 
@@ -2628,38 +2215,22 @@ public class PopupMenu extends JPopupMenu
     }
   }
 
-  public void colourByStructure(String pdbid)
-  {
-    Annotation[] anots = ap.av.getStructureSelectionManager()
-            .colourSequenceFromStructure(sequence, pdbid);
-
-    AlignmentAnnotation an = new AlignmentAnnotation("Structure",
-            "Coloured by " + pdbid, anots);
-
-    ap.av.getAlignment().addAnnotation(an);
-    an.createSequenceMapping(sequence, 0, true);
-    // an.adjustForAlignment();
-    ap.av.getAlignment().setAnnotationIndex(an, 0);
-
-    ap.adjustAnnotationHeight();
-
-    sequence.addAlignmentAnnotation(an);
-
-  }
-
   public void editSequence_actionPerformed(ActionEvent actionEvent)
   {
     SequenceGroup sg = ap.av.getSelectionGroup();
 
+    SequenceI seq = sequence;
     if (sg != null)
     {
-      if (sequence == null)
-        sequence = sg.getSequenceAt(0);
+      if (seq == null)
+      {
+        seq = sg.getSequenceAt(0);
+      }
 
       EditNameDialog dialog = new EditNameDialog(
-              sequence.getSequenceAsString(sg.getStartRes(),
-                      sg.getEndRes() + 1), null,
-              MessageManager.getString("label.edit_sequence"), null,
+              seq.getSequenceAsString(sg.getStartRes(),
+                      sg.getEndRes() + 1),
+              null, MessageManager.getString("label.edit_sequence"), null,
               MessageManager.getString("label.edit_sequence"),
               ap.alignFrame);
 
@@ -2667,17 +2238,45 @@ public class PopupMenu extends JPopupMenu
       {
         EditCommand editCommand = new EditCommand(
                 MessageManager.getString("label.edit_sequences"),
-                EditCommand.REPLACE, dialog.getName().replace(' ',
-                        ap.av.getGapCharacter()),
+                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());
+        ap.av.firePropertyChange("alignment", null,
+                ap.av.getAlignment().getSequences());
       }
     }
   }
 
+  /**
+   * Action on user selecting an item from the colour menu (that does not have
+   * its bespoke action handler)
+   * 
+   * @return
+   */
+  @Override
+  public void changeColour_actionPerformed(String colourSchemeName)
+  {
+    SequenceGroup sg = getGroup();
+    /*
+     * switch to the chosen colour scheme (or null for None)
+     */
+    ColourSchemeI colourScheme = ColourSchemes.getInstance()
+            .getColourScheme(colourSchemeName, ap.av, sg,
+                    ap.av.getHiddenRepSequences());
+    sg.setColourScheme(colourScheme);
+    if (colourScheme instanceof Blosum62ColourScheme
+            || colourScheme instanceof PIDColourScheme)
+    {
+      sg.cs.setConsensus(AAFrequency.calculate(
+              sg.getSequences(ap.av.getHiddenRepSequences()),
+              sg.getStartRes(), sg.getEndRes() + 1));
+    }
+
+    refresh();
+  }
+
 }