Merge commit 'ab43013b7e357b84b4abade0dba949668dfb2a0e' into develop
[jalview.git] / src / jalview / gui / PopupMenu.java
index 260bcc5..07b8d16 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2b1)
  * Copyright (C) 2014 The Jalview Authors
  * 
  * This file is part of Jalview.
 package jalview.gui;
 
 import jalview.analysis.AAFrequency;
+import jalview.analysis.AlignmentAnnotationUtils;
 import jalview.analysis.Conservation;
 import jalview.commands.ChangeCaseCommand;
 import jalview.commands.EditCommand;
+import jalview.commands.EditCommand.Action;
 import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Annotation;
+import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.DBRefEntry;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.Sequence;
@@ -34,7 +38,6 @@ 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;
@@ -60,12 +63,13 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 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.TreeMap;
 import java.util.Vector;
 
 import javax.swing.ButtonGroup;
@@ -137,6 +141,8 @@ public class PopupMenu extends JPopupMenu
   JMenuItem sequenceDetails = new JMenuItem();
 
   JMenuItem sequenceSelDetails = new JMenuItem();
+  
+  JMenuItem makeReferenceSeq = new JMenuItem();
 
   JMenuItem chooseAnnotations = new JMenuItem();
 
@@ -188,9 +194,17 @@ public class PopupMenu extends JPopupMenu
 
   JMenu outputMenu = new JMenu();
 
-  JMenu showAnnotationsMenu = new JMenu();
+  JMenu seqShowAnnotationsMenu = new JMenu();
 
-  JMenu hideAnnotationsMenu = new JMenu();
+  JMenu seqHideAnnotationsMenu = new JMenu();
+
+  JMenuItem seqAddReferenceAnnotations = new JMenuItem();
+
+  JMenu groupShowAnnotationsMenu = new JMenu();
+
+  JMenu groupHideAnnotationsMenu = new JMenu();
+
+  JMenuItem groupAddReferenceAnnotations = new JMenuItem();
 
   JMenuItem sequenceFeature = new JMenuItem();
 
@@ -209,6 +223,8 @@ public class PopupMenu extends JPopupMenu
 
   JMenu groupLinksMenu;
 
+  JMenuItem hideInsertions = new JMenuItem();
+
   /**
    * Creates a new PopupMenu object.
    * 
@@ -277,9 +293,27 @@ 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):
      */
-    buildAnnotationTypesMenus();
+    final List<SequenceI> selectedSequence = (seq == null ? Collections
+            .<SequenceI> emptyList() : Arrays.asList(seq));
+    buildAnnotationTypesMenus(seqShowAnnotationsMenu,
+            seqHideAnnotationsMenu, selectedSequence);
+    configureReferenceAnnotationsMenu(seqAddReferenceAnnotations,
+            selectedSequence);
+
+    /*
+     * And repeat for the current selection group (if there is one):
+     */
+    final List<SequenceI> selectedGroup = (ap.av.getSelectionGroup() == null ? Collections
+            .<SequenceI> emptyList() : ap.av.getSelectionGroup()
+            .getSequences());
+    buildAnnotationTypesMenus(groupShowAnnotationsMenu,
+            groupHideAnnotationsMenu, selectedGroup);
+    configureReferenceAnnotationsMenu(groupAddReferenceAnnotations,
+            selectedGroup);
 
     try
     {
@@ -293,6 +327,12 @@ public class PopupMenu extends JPopupMenu
     if (seq != null)
     {
       sequenceMenu.setText(sequence.getName());
+      if (seq == ap.av.getAlignment().getSeqrep())
+      {
+        makeReferenceSeq.setText("Unmark representative");
+      } else {
+        makeReferenceSeq.setText("Mark as representative");
+      }
 
       if (seq.getDatasetSequence().getPDBId() != null
               && seq.getDatasetSequence().getPDBId().size() > 0)
@@ -306,8 +346,9 @@ public class PopupMenu extends JPopupMenu
 
           menuItem = new JMenuItem();
           menuItem.setText(pdb.getId());
-          menuItem.addActionListener(new java.awt.event.ActionListener()
+          menuItem.addActionListener(new ActionListener()
           {
+            @Override
             public void actionPerformed(ActionEvent e)
             {
               // TODO re JAL-860: optionally open dialog or provide a menu entry
@@ -339,36 +380,34 @@ public class PopupMenu extends JPopupMenu
         }
         // structureMenu.remove(colStructureMenu);
       }
-
       if (ap.av.getAlignment().isNucleotide() == true)
       {
         AlignmentAnnotation[] aa = ap.av.getAlignment()
                 .getAlignmentAnnotation();
-        for (int i = 0; i < aa.length; i++)
+        for (int i = 0; aa != null && i < aa.length; i++)
         {
-          if (aa[i].getRNAStruc() != null)
+          if (aa[i].isValidStruc() && aa[i].sequenceRef == null)
           {
             final String rnastruc = aa[i].getRNAStruc();
-            final String structureLine = aa[i].label;
+            final String structureLine = aa[i].label + " (alignment)";
             menuItem = new JMenuItem();
             menuItem.setText(MessageManager.formatMessage(
                     "label.2d_rna_structure_line", new String[]
                     { structureLine }));
             menuItem.addActionListener(new java.awt.event.ActionListener()
-
             {
               @Override
               public void actionPerformed(ActionEvent e)
               {
-                // 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);
+                // // 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(),
@@ -387,7 +426,7 @@ public class PopupMenu extends JPopupMenu
           AlignmentAnnotation seqAnno[] = seq.getAnnotation();
           for (int i = 0; i < seqAnno.length; i++)
           {
-            if (seqAnno[i].getRNAStruc() != null)
+            if (seqAnno[i].isValidStruc())
             {
               final String rnastruc = seqAnno[i].getRNAStruc();
 
@@ -412,7 +451,6 @@ public class PopupMenu extends JPopupMenu
             }
           }
         }
-
       }
 
       menuItem = new JMenuItem(
@@ -796,11 +834,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. The second parameter controls
-   * whether we include only currently visible annotation types (for the Hide
-   * menu), or only currently hidden annotation types (for the Show menu).
+   * 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,168 +845,82 @@ 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();
+    showMenu.removeAll();
+    hideMenu.removeAll();
+
     final List<String> all = Arrays.asList(ALL_ANNOTATIONS);
-    addAnnotationTypeToShowHide(showAnnotationsMenu,
-            all, true,
- true);
-    addAnnotationTypeToShowHide(hideAnnotationsMenu, all, true,
+    addAnnotationTypeToShowHide(showMenu, forSequences, "", all, true, true);
+    addAnnotationTypeToShowHide(hideMenu, forSequences, "", all, true,
             false);
-    showAnnotationsMenu.addSeparator();
-    hideAnnotationsMenu.addSeparator();
+    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<String, List<List<String>>>();
+    Map<String, List<List<String>>> hiddenTypes = new LinkedHashMap<String, List<List<String>>>();
+    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;
   }
 
   /**
@@ -978,6 +929,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
@@ -987,18 +941,21 @@ 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);
     final JMenuItem item = new JMenuItem(label);
+    item.setToolTipText(calcId);
     item.addActionListener(new java.awt.event.ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        showHideAnnotation_actionPerformed(types, allTypes, actionIsShow);
+        showHideAnnotation_actionPerformed(types, forSequences, allTypes,
+                actionIsShow);
       }
     });
     showOrHideMenu.add(item);
@@ -1006,14 +963,15 @@ public class PopupMenu extends JPopupMenu
 
   /**
    * Action on selecting a list of annotation type (or the 'all types' values)
-   * to show or hide for the selection.
+   * to show or hide for the specified sequences.
    * 
    * @param types
+   * @param forSequences
    * @param anyType
    * @param doShow
    */
   protected void showHideAnnotation_actionPerformed(
-          Collection<String> types,
+          Collection<String> types, List<SequenceI> forSequences,
           boolean anyType, boolean doShow)
   {
     for (AlignmentAnnotation aa : ap.getAlignment()
@@ -1022,8 +980,7 @@ public class PopupMenu extends JPopupMenu
       if (anyType || types.contains(aa.label))
       {
         if ((aa.sequenceRef != null)
-                && ap.av.getSelectionGroup().getSequences()
-                        .contains(aa.sequenceRef))
+                && forSequences.contains(aa.sequenceRef))
         {
           aa.visible = doShow;
         }
@@ -1517,9 +1474,13 @@ public class PopupMenu extends JPopupMenu
     });
     outputMenu.setText(MessageManager.getString("label.out_to_textbox")
             + "...");
-    showAnnotationsMenu.setText(MessageManager
+    seqShowAnnotationsMenu.setText(MessageManager
+            .getString("label.show_annotations"));
+    seqHideAnnotationsMenu.setText(MessageManager
+            .getString("label.hide_annotations"));
+    groupShowAnnotationsMenu.setText(MessageManager
             .getString("label.show_annotations"));
-    hideAnnotationsMenu.setText(MessageManager
+    groupHideAnnotationsMenu.setText(MessageManager
             .getString("label.hide_annotations"));
     sequenceFeature.setText(MessageManager
             .getString("label.create_sequence_feature"));
@@ -1555,7 +1516,28 @@ 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);
+        
+      }
+    });
+    hideInsertions.setText(MessageManager.getString("label.hide_insertions"));
+    hideInsertions.addActionListener(new ActionListener()
+    {
 
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        hideInsertions_actionPerformed(e);
+      }
+    });
     /*
      * annotationMenuItem.setText("By Annotation");
      * annotationMenuItem.addActionListener(new ActionListener() { public void
@@ -1565,10 +1547,24 @@ public class PopupMenu extends JPopupMenu
     groupMenu.add(sequenceSelDetails);
     add(groupMenu);
     add(sequenceMenu);
-    this.add(structureMenu);
+    add(structureMenu);
+    if (sequence!=null)
+    {
+      add(hideInsertions);
+    }
+    // annotations configuration panel suppressed for now
     // groupMenu.add(chooseAnnotations);
-    groupMenu.add(showAnnotationsMenu);
-    groupMenu.add(hideAnnotationsMenu);
+
+    /*
+     * 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);
@@ -1577,6 +1573,7 @@ public class PopupMenu extends JPopupMenu
     groupMenu.add(jMenu1);
     sequenceMenu.add(sequenceName);
     sequenceMenu.add(sequenceDetails);
+    sequenceMenu.add(makeReferenceSeq);
     colourMenu.add(textColour);
     colourMenu.add(noColourmenuItem);
     colourMenu.add(clustalColour);
@@ -1802,6 +1799,192 @@ public class PopupMenu extends JPopupMenu
             });
   }
 
+  /**
+   * 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
+   */
+  protected void configureReferenceAnnotationsMenu(
+          JMenuItem menuItem, List<SequenceI> forSequences)
+  {
+    menuItem.setText(MessageManager
+            .getString("label.add_reference_annotations"));
+    menuItem.setEnabled(false);
+    if (forSequences == null)
+    {
+      return;
+    }
+
+    /*
+     * Temporary store to hold distinct calcId / type pairs for the tooltip.
+     * Using TreeMap means calcIds are shown in alphabetical order.
+     */
+    Map<String, String> tipEntries = new TreeMap<String, String>();
+    StringBuilder tooltip = new StringBuilder(64);
+    tooltip.append(MessageManager.getString("label.add_annotations_for"));
+
+    /*
+     * For each sequence selected in the alignment, make a list of any
+     * annotations on the underlying dataset sequence which are not already on
+     * the alignment.
+     * 
+     * Build a map of { alignmentSequence, <List of annotations to add> }
+     */
+    AlignmentI al = this.ap.av.getAlignment();
+    final Map<SequenceI, List<AlignmentAnnotation>> candidates = new LinkedHashMap<SequenceI, List<AlignmentAnnotation>>();
+    for (SequenceI seq : forSequences)
+    {
+      SequenceI dataset = seq.getDatasetSequence();
+      if (dataset == null)
+      {
+        continue;
+      }
+      AlignmentAnnotation[] datasetAnnotations = dataset.getAnnotation();
+      if (datasetAnnotations == null)
+      {
+        continue;
+      }
+      final List<AlignmentAnnotation> result = new ArrayList<AlignmentAnnotation>();
+      for (AlignmentAnnotation dsann : datasetAnnotations)
+      {
+        /*
+         * Find matching annotations on the alignment.
+         */
+        final Iterable<AlignmentAnnotation> matchedAlignmentAnnotations = al
+                .findAnnotations(seq, dsann.getCalcId(),
+                        dsann.label);
+        if (!matchedAlignmentAnnotations.iterator().hasNext())
+        {
+          result.add(dsann);
+          tipEntries.put(dsann.getCalcId(), dsann.label);
+        }
+      }
+      /*
+       * Save any addable annotations for this sequence
+       */
+      if (!result.isEmpty())
+      {
+        candidates.put(seq, result);
+      }
+    }
+    if (!candidates.isEmpty())
+    {
+      /*
+       * 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);
+        }
+      });
+    }
+  }
+
+  /**
+   * Add annotations to the sequences and to the alignment.
+   * 
+   * @param candidates
+   *          a map whose keys are sequences on the alignment, and values a list
+   *          of annotations to add to each sequence
+   */
+  protected void addReferenceAnnotations_actionPerformed(
+          Map<SequenceI, List<AlignmentAnnotation>> candidates)
+  {
+    /*
+     * Add annotations at the top of the annotation, in the same order as their
+     * related sequences.
+     */
+    for (SequenceI seq : candidates.keySet())
+    {
+      for (AlignmentAnnotation ann : candidates.get(seq))
+      {
+        AlignmentAnnotation copyAnn = new AlignmentAnnotation(ann);
+        int startRes = 0;
+        int endRes = ann.annotations.length;
+        final SequenceGroup selectionGroup = this.ap.av.getSelectionGroup();
+        if (selectionGroup != null)
+        {
+          startRes = selectionGroup.getStartRes();
+          endRes = selectionGroup.getEndRes();
+        }
+        copyAnn.restrict(startRes, endRes);
+
+        /*
+         * Add to the sequence (sets copyAnn.datasetSequence), unless the
+         * original annotation is already on the sequence.
+         */
+        if (!seq.hasAnnotation(ann))
+        {
+          seq.addAlignmentAnnotation(copyAnn);
+        }
+        // adjust for gaps
+        copyAnn.adjustForAlignment();
+        // add to the alignment and set visible
+        this.ap.getAlignment().addAnnotation(copyAnn);
+        copyAnn.visible = true;
+      }
+    }
+    refresh();
+  }
+
+  protected void makeReferenceSeq_actionPerformed(ActionEvent actionEvent)
+  {
+    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();
+  }
+
+  protected void hideInsertions_actionPerformed(ActionEvent actionEvent)
+  {
+    if (sequence != null)
+    {
+      ColumnSelection cs = ap.av.getColumnSelection();
+      if (cs == null)
+      {
+        cs = new ColumnSelection();
+      }
+      cs.hideInsertionsFor(sequence);
+      ap.av.setColumnSelection(cs);
+    }
+    refresh();
+  }
   protected void sequenceSelectionDetails_actionPerformed()
   {
     createSequenceDetailsReport(ap.av.getSequenceSelection());
@@ -1832,7 +2015,9 @@ public class PopupMenu extends JPopupMenu
                       true,
                       true,
                       false,
-                      (ap.seqPanel.seqCanvas.fr != null) ? ap.seqPanel.seqCanvas.fr.minmax
+                      (ap.getSeqPanel().seqCanvas.fr != null) ? ap
+                              .getSeqPanel().seqCanvas.fr
+                              .getMinMax()
                               : null);
       contents.append("</p>");
     }
@@ -2466,7 +2651,8 @@ public class PopupMenu extends JPopupMenu
       String choice = chooser.getSelectedFile().getPath();
       jalview.bin.Cache.setProperty("LAST_DIRECTORY", choice);
       new AssociatePdbFileWithSeq().associatePdbWithSeq(choice,
-              jalview.io.AppletFormatAdapter.FILE, sequence, true);
+              jalview.io.AppletFormatAdapter.FILE, sequence, true,
+              Desktop.instance);
     }
 
   }
@@ -2548,7 +2734,7 @@ public class PopupMenu extends JPopupMenu
     System.arraycopy(features, 0, tfeatures, 0, rsize);
     features = tfeatures;
     seqs = rseqs;
-    if (ap.seqPanel.seqCanvas.getFeatureRenderer().amendFeatures(seqs,
+    if (ap.getSeqPanel().seqCanvas.getFeatureRenderer().amendFeatures(seqs,
             features, true, ap))
     {
       ap.alignFrame.setShowSeqFeatures(true);
@@ -2591,7 +2777,9 @@ public class PopupMenu extends JPopupMenu
     if (sg != null)
     {
       if (sequence == null)
+      {
         sequence = sg.getSequenceAt(0);
+      }
 
       EditNameDialog dialog = new EditNameDialog(
               sequence.getSequenceAsString(sg.getStartRes(),
@@ -2604,7 +2792,7 @@ public class PopupMenu extends JPopupMenu
       {
         EditCommand editCommand = new EditCommand(
                 MessageManager.getString("label.edit_sequences"),
-                EditCommand.REPLACE, dialog.getName().replace(' ',
+                Action.REPLACE, dialog.getName().replace(' ',
                         ap.av.getGapCharacter()),
                 sg.getSequencesAsArray(ap.av.getHiddenRepSequences()),
                 sg.getStartRes(), sg.getEndRes() + 1, ap.av.getAlignment());