Merge branch 'develop' into trialMerge
[jalview.git] / src / jalview / gui / PopupMenu.java
index 2ef71cc..e78eaf2 100644 (file)
@@ -34,11 +34,11 @@ import jalview.datamodel.Annotation;
 import jalview.datamodel.DBRefEntry;
 import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
-import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.gui.ColourMenuHelper.ColourChangeListener;
+import jalview.gui.JalviewColourChooser.ColourChooserListener;
 import jalview.io.FileFormatI;
 import jalview.io.FileFormats;
 import jalview.io.FormatAdapter;
@@ -47,11 +47,15 @@ import jalview.schemes.Blosum62ColourScheme;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ColourSchemes;
 import jalview.schemes.PIDColourScheme;
+import jalview.schemes.ResidueColourScheme;
 import jalview.util.GroupUrlLink;
 import jalview.util.GroupUrlLink.UrlStringTooLongException;
 import jalview.util.MessageManager;
+import jalview.util.Platform;
+import jalview.util.StringUtils;
 import jalview.util.UrlLink;
 
+import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
@@ -68,11 +72,16 @@ import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.Vector;
 
+import javax.swing.ButtonGroup;
 import javax.swing.JCheckBoxMenuItem;
-import javax.swing.JColorChooser;
+import javax.swing.JInternalFrame;
+import javax.swing.JLabel;
 import javax.swing.JMenu;
 import javax.swing.JMenuItem;
+import javax.swing.JPanel;
 import javax.swing.JPopupMenu;
+import javax.swing.JRadioButtonMenuItem;
+import javax.swing.JScrollPane;
 
 /**
  * DOCUMENT ME!
@@ -92,6 +101,8 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
 
   protected JCheckBoxMenuItem conservationMenuItem = new JCheckBoxMenuItem();
 
+  protected JRadioButtonMenuItem annotationColour;
+
   protected JMenuItem modifyConservation = new JMenuItem();
 
   AlignmentPanel ap;
@@ -176,25 +187,31 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
    * Creates a new PopupMenu object.
    * 
    * @param ap
-   *          DOCUMENT ME!
    * @param seq
-   *          DOCUMENT ME!
+   * @param features
+   *          non-positional features (for seq not null), or positional features
+   *          at residue (for seq equal to null)
    */
-  public PopupMenu(final AlignmentPanel ap, Sequence seq,
-          List<String> links)
+  public PopupMenu(final AlignmentPanel ap, SequenceI seq,
+          List<SequenceFeature> features)
   {
-    this(ap, seq, links, null);
+    this(ap, seq, features, null);
   }
 
   /**
+   * Constructor
    * 
-   * @param ap
+   * @param alignPanel
    * @param seq
-   * @param links
+   *          the sequence under the cursor if in the Id panel, null if in the
+   *          sequence panel
+   * @param features
+   *          non-positional features if in the Id panel, features at the
+   *          clicked residue if in the sequence panel
    * @param groupLinks
    */
-  public PopupMenu(final AlignmentPanel ap, final SequenceI seq,
-          List<String> links, List<String> groupLinks)
+  public PopupMenu(final AlignmentPanel alignPanel, final SequenceI seq,
+          List<SequenceFeature> features, List<String> groupLinks)
   {
     // /////////////////////////////////////////////////////////
     // If this is activated from the sequence panel, the user may want to
@@ -202,7 +219,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
     //
     // If from the IDPanel, we must display the sequence menu
     // ////////////////////////////////////////////////////////
-    this.ap = ap;
+    this.ap = alignPanel;
     sequence = seq;
 
     for (String ff : FileFormats.getInstance().getWritableFormats(true))
@@ -237,9 +254,9 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
     /*
      * And repeat for the current selection group (if there is one):
      */
-    final List<SequenceI> selectedGroup = (ap.av.getSelectionGroup() == null
+    final List<SequenceI> selectedGroup = (alignPanel.av.getSelectionGroup() == null
             ? Collections.<SequenceI> emptyList()
-            : ap.av.getSelectionGroup().getSequences());
+            : alignPanel.av.getSelectionGroup().getSequences());
     buildAnnotationTypesMenus(groupShowAnnotationsMenu,
             groupHideAnnotationsMenu, selectedGroup);
     configureReferenceAnnotationsMenu(groupAddReferenceAnnotations,
@@ -257,7 +274,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
     if (seq != null)
     {
       sequenceMenu.setText(sequence.getName());
-      if (seq == ap.av.getAlignment().getSeqrep())
+      if (seq == alignPanel.av.getAlignment().getSeqrep())
       {
         makeReferenceSeq.setText(
                 MessageManager.getString("action.unmark_as_reference"));
@@ -268,7 +285,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
                 MessageManager.getString("action.set_as_reference"));
       }
 
-      if (!ap.av.getAlignment().isNucleotide())
+      if (!alignPanel.av.getAlignment().isNucleotide())
       {
         remove(rnaStructureMenu);
       }
@@ -279,7 +296,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
          * add menu items to 2D-render any alignment or sequence secondary
          * structure annotation
          */
-        AlignmentAnnotation[] aas = ap.av.getAlignment()
+        AlignmentAnnotation[] aas = alignPanel.av.getAlignment()
                 .getAlignmentAnnotation();
         if (aas != null)
         {
@@ -299,7 +316,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
                 @Override
                 public void actionPerformed(ActionEvent e)
                 {
-                  new AppVarna(seq, aa, ap);
+                  new AppVarna(seq, aa, alignPanel);
                 }
               });
               rnaStructureMenu.add(menuItem);
@@ -328,7 +345,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
                 public void actionPerformed(ActionEvent e)
                 {
                   // TODO: VARNA does'nt print gaps in the sequence
-                  new AppVarna(seq, aa, ap);
+                  new AppVarna(seq, aa, alignPanel);
                 }
               });
               rnaStructureMenu.add(menuItem);
@@ -353,8 +370,8 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       });
       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 Object[]
@@ -370,12 +387,12 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
         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"));
@@ -384,10 +401,10 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
             @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();
               }
             }
           });
@@ -395,9 +412,20 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
         }
       }
     }
-    // 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(
                 MessageManager.getString("action.reveal_all"));
@@ -406,21 +434,20 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
           @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();
+    SequenceGroup sg = alignPanel.av.getSelectionGroup();
     boolean isDefinedGroup = (sg != null)
-            ? ap.av.getAlignment().getGroups().contains(sg)
+            ? alignPanel.av.getAlignment().getGroups().contains(sg)
             : false;
 
     if (sg != null && sg.getSize() > 0)
@@ -458,7 +485,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       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().getAllPDBEntries();
         if (pes != null && pes.size() > 0)
@@ -508,24 +535,153 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       rnaStructureMenu.setVisible(false);
     }
 
-    if (links != null && links.size() > 0)
+    addLinks(seq, features);
+
+    if (seq == null)
+    {
+      addFeatureDetails(features);
+    }
+  }
+
+  /**
+   * Add a link to show feature details for each sequence feature
+   * 
+   * @param features
+   */
+  protected void addFeatureDetails(List<SequenceFeature> features)
+  {
+    if (features == null || features.isEmpty())
+    {
+      return;
+    }
+    JMenu details = new JMenu(
+            MessageManager.getString("label.feature_details"));
+    add(details);
+
+    for (final SequenceFeature sf : features)
+    {
+      int start = sf.getBegin();
+      int end = sf.getEnd();
+      String desc = null;
+      if (start == end)
+      {
+        desc = String.format("%s %d", sf.getType(), start);
+      }
+      else
+      {
+        desc = String.format("%s %d-%d", sf.getType(), start, end);
+      }
+      String tooltip = desc;
+      String description = sf.getDescription();
+      if (description != null)
+      {
+        description = StringUtils.stripHtmlTags(description);
+        if (description.length() > 12)
+        {
+          desc = desc + " " + description.substring(0, 12) + "..";
+        }
+        else
+        {
+          desc = desc + " " + description;
+        }
+        tooltip = tooltip + " " + description;
+      }
+      if (sf.getFeatureGroup() != null)
+      {
+        tooltip = tooltip + (" (" + sf.getFeatureGroup() + ")");
+      }
+      JMenuItem item = new JMenuItem(desc);
+      item.setToolTipText(tooltip);
+      item.addActionListener(new ActionListener()
+      {
+        @Override
+        public void actionPerformed(ActionEvent e)
+        {
+          showFeatureDetails(sf);
+        }
+      });
+      details.add(item);
+    }
+  }
+
+  /**
+   * Opens a panel showing a text report of feature dteails
+   * 
+   * @param sf
+   */
+  protected void showFeatureDetails(SequenceFeature sf)
+  {
+    JInternalFrame details;
+    if (/** @j2sNative true || */ false)
+    {
+      details = new JInternalFrame();
+      JPanel panel = new JPanel(new BorderLayout());
+      panel.setOpaque(true);
+      panel.setBackground(Color.white);
+      // TODO JAL-3026 set style of table correctly for feature details
+      JLabel reprt = new JLabel(MessageManager.formatMessage("label.html_content",
+            new Object[]
+            { sf.getDetailsReport()}));
+      reprt.setBackground(Color.WHITE);
+      reprt.setOpaque(true);
+      panel.add(reprt,BorderLayout.CENTER);
+      details.setContentPane(panel);
+      details.pack();
+    }
+    else
     {
-      addFeatureLinks(seq, links);
+      CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
+      // it appears Java's CSS does not support border-collaps :-(
+      cap.addStylesheetRule("table { border-collapse: collapse;}");
+      cap.addStylesheetRule("table, td, th {border: 1px solid black;}");
+      cap.setText(sf.getDetailsReport());
+      details = cap;
     }
+    Desktop.addInternalFrame(details,
+            MessageManager.getString("label.feature_details"), 500, 500);
   }
 
   /**
    * 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.
    * 
    * @param seq
-   * @param links
+   * @param features
    */
-  void addFeatureLinks(final SequenceI seq, List<String> links)
+  void addLinks(final SequenceI seq, List<SequenceFeature> features)
   {
     JMenu linkMenu = new JMenu(MessageManager.getString("action.link"));
+
+    List<String> nlinks = null;
+    if (seq != null)
+    {
+      nlinks = Preferences.sequenceUrlLinks.getLinksForMenu();
+    }
+    else
+    {
+      nlinks = new ArrayList<>();
+    }
+
+    if (features != null)
+    {
+      for (SequenceFeature sf : features)
+      {
+        if (sf.links != null)
+        {
+          for (String link : sf.links)
+          {
+            nlinks.add(link);
+          }
+        }
+      }
+    }
+
     Map<String, List<String>> linkset = new LinkedHashMap<>();
 
-    for (String link : links)
+    for (String link : nlinks)
     {
       UrlLink urlLink = null;
       try
@@ -548,25 +704,18 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
 
     addshowLinks(linkMenu, linkset.values());
 
-    // disable link menu if there are no valid entries
+    // only add link menu if it has entries
     if (linkMenu.getItemCount() > 0)
     {
-      linkMenu.setEnabled(true);
-    }
-    else
-    {
-      linkMenu.setEnabled(false);
-    }
-
-    if (sequence != null)
-    {
-      sequenceMenu.add(linkMenu);
-    }
-    else
-    {
-      add(linkMenu);
+      if (sequence != null)
+      {
+        sequenceMenu.add(linkMenu);
+      }
+      else
+      {
+        add(linkMenu);
+      }
     }
-
   }
 
   /**
@@ -730,12 +879,14 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       {
         sqi = sqi.getDatasetSequence();
       }
-      DBRefEntry[] dbr = sqi.getDBRefs();
-      if (dbr != null && dbr.length > 0)
+      List<DBRefEntry> dbr = sqi.getDBRefs();
+      int nd;
+      if (dbr != null && (nd = dbr.size()) > 0)
       {
-        for (int d = 0; d < dbr.length; d++)
+        for (int d = 0; d < nd; d++)
         {
-          String src = dbr[d].getSource(); // jalview.util.DBRefUtils.getCanonicalName(dbr[d].getSource()).toUpperCase();
+          DBRefEntry e = dbr.get(d);
+          String src = e.getSource(); // jalview.util.DBRefUtils.getCanonicalName(dbr[d].getSource()).toUpperCase();
           Object[] sarray = commonDbrefs.get(src);
           if (sarray == null)
           {
@@ -748,10 +899,10 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
 
           if (((String[]) sarray[1])[sq] == null)
           {
-            if (!dbr[d].hasMap() || (dbr[d].getMap()
+            if (!e.hasMap() || (e.getMap()
                     .locateMappedRange(start, end) != null))
             {
-              ((String[]) sarray[1])[sq] = dbr[d].getAccessionId();
+              ((String[]) sarray[1])[sq] = e.getAccessionId();
               ((int[]) sarray[0])[0]++;
             }
           }
@@ -1162,7 +1313,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       @Override
       public void actionPerformed(ActionEvent actionEvent)
       {
-        editSequence_actionPerformed(actionEvent);
+        editSequence_actionPerformed();
       }
     });
     makeReferenceSeq.setText(
@@ -1293,6 +1444,13 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       }
     });
 
+    annotationColour = new JRadioButtonMenuItem(
+            MessageManager.getString("action.by_annotation"));
+    annotationColour.setName(ResidueColourScheme.ANNOTATION_COLOUR);
+    annotationColour.setEnabled(false);
+    annotationColour.setToolTipText(
+            MessageManager.getString("label.by_annotation_tooltip"));
+
     modifyConservation.setText(MessageManager
             .getString("label.modify_conservation_threshold"));
     modifyConservation.addActionListener(new ActionListener()
@@ -1323,7 +1481,10 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
     colourMenu.add(textColour);
     colourMenu.addSeparator();
 
-    ColourMenuHelper.addMenuItems(colourMenu, this, sg, false);
+    ButtonGroup bg = ColourMenuHelper.addMenuItems(colourMenu, this, sg,
+            false);
+    bg.add(annotationColour);
+    colourMenu.add(annotationColour);
 
     colourMenu.addSeparator();
     colourMenu.add(conservationMenuItem);
@@ -1453,15 +1614,8 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
 
   protected void hideInsertions_actionPerformed(ActionEvent actionEvent)
   {
-
-    HiddenColumns hidden = new HiddenColumns();
-    BitSet inserts = new BitSet(), mask = new BitSet();
-
-    // set mask to preserve existing hidden columns outside selected group
-    if (ap.av.hasHiddenColumns())
-    {
-      ap.av.getAlignment().getHiddenColumns().markHiddenRegions(mask);
-    }
+    HiddenColumns hidden = ap.av.getAlignment().getHiddenColumns();
+    BitSet inserts = new BitSet();
 
     boolean markedPopup = false;
     // mark inserts in current selection
@@ -1469,10 +1623,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
     {
       // mark just the columns in the selection group to be hidden
       inserts.set(ap.av.getSelectionGroup().getStartRes(),
-              ap.av.getSelectionGroup().getEndRes() + 1);
-
-      // and clear that part of the mask
-      mask.andNot(inserts);
+              ap.av.getSelectionGroup().getEndRes() + 1); // TODO why +1?
 
       // now clear columns without gaps
       for (SequenceI sq : ap.av.getSelectionGroup().getSequences())
@@ -1483,29 +1634,18 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
         }
         inserts.and(sq.getInsertionsAsBits());
       }
-    }
-    else
-    {
-      // initially, mark all columns to be hidden
-      inserts.set(0, ap.av.getAlignment().getWidth());
-
-      // and clear out old hidden regions completely
-      mask.clear();
+      hidden.clearAndHideColumns(inserts, ap.av.getSelectionGroup().getStartRes(),
+              ap.av.getSelectionGroup().getEndRes());
     }
 
     // now mark for sequence under popup if we haven't already done it
-    if (!markedPopup && sequence != null)
+    else if (!markedPopup && sequence != null)
     {
-      inserts.and(sequence.getInsertionsAsBits());
-    }
-
-    // finally, preserve hidden regions outside selection
-    inserts.or(mask);
-
-    // and set hidden columns accordingly
-    hidden.hideMarkedBits(inserts);
+      inserts.or(sequence.getInsertionsAsBits());
 
-    ap.av.getAlignment().setHiddenColumns(hidden);
+      // and set hidden columns accordingly
+      hidden.hideColumns(inserts);
+    }
     refresh();
   }
 
@@ -1521,8 +1661,8 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
 
   public void createSequenceDetailsReport(SequenceI[] sequences)
   {
-    CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
     StringBuilder contents = new StringBuilder(128);
+    contents.append("<html><body>");
     for (SequenceI seq : sequences)
     {
       contents.append("<p><h2>" + MessageManager.formatMessage(
@@ -1530,15 +1670,33 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
               new Object[]
               { seq.getDisplayId(true) }) + "</h2></p><p>");
       new SequenceAnnotationReport(null).createSequenceAnnotationReport(
-              contents, seq, true, true,
-              (ap.getSeqPanel().seqCanvas.fr != null)
-                      ? ap.getSeqPanel().seqCanvas.fr.getMinMax()
-                      : null);
+              contents, seq, true, true, ap.getSeqPanel().seqCanvas.fr);
       contents.append("</p>");
     }
-    cap.setText("<html>" + contents.toString() + "</html>");
+    contents.append("</body></html>");
+    String report = contents.toString();
+    
+    JInternalFrame frame;
+    if (Platform.isJS())
+    {
+      JLabel textLabel = new JLabel();
+      textLabel.setText(report);
+      textLabel.setBackground(Color.WHITE);
+      JPanel pane = new JPanel(new BorderLayout());
+      ((JPanel) pane).setOpaque(true);
+      pane.setBackground(Color.WHITE);
+      ((JPanel) pane).add(textLabel, BorderLayout.NORTH);
+      frame = new JInternalFrame();
+      frame.getContentPane().add(new JScrollPane(pane));
+    }
+    else
+    {
+      CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
+      cap.setText(report);
+      frame = cap;
+    }
 
-    Desktop.addInternalFrame(cap,
+    Desktop.addInternalFrame(frame,
             MessageManager.formatMessage("label.sequence_details_for",
                     (sequences.length == 1 ? new Object[]
                     { sequences[0].getDisplayId(true) }
@@ -1546,7 +1704,6 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
                             { MessageManager
                                     .getString("label.selection") })),
             500, 400);
-
   }
 
   protected void showNonconserved_actionPerformed()
@@ -1665,30 +1822,27 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
+   * Shows a dialog where group name and description may be edited
    */
   protected void groupName_actionPerformed()
   {
-
     SequenceGroup sg = getGroup();
     EditNameDialog dialog = new EditNameDialog(sg.getName(),
             sg.getDescription(),
-            "       " + MessageManager.getString("label.group_name") + " ",
-            MessageManager.getString("label.group_description") + " ",
+            MessageManager.getString("label.group_name"),
+            MessageManager.getString("label.group_description"));
+    dialog.showDialog(ap.alignFrame,
             MessageManager.getString("label.edit_group_name_description"),
-            ap.alignFrame);
-
-    if (!dialog.accept)
-    {
-      return;
-    }
-
-    sg.setName(dialog.getName());
-    sg.setDescription(dialog.getDescription());
-    refresh();
+            new Runnable()
+            {
+              @Override
+              public void run()
+              {
+                sg.setName(dialog.getName());
+                sg.setDescription(dialog.getDescription());
+                refresh();
+              }
+            });
   }
 
   /**
@@ -1709,48 +1863,41 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
+   * Shows a dialog where sequence name and description may be edited
    */
   void sequenceName_actionPerformed()
   {
     EditNameDialog dialog = new EditNameDialog(sequence.getName(),
             sequence.getDescription(),
-            "       " + MessageManager.getString("label.sequence_name")
-                    + " ",
-            MessageManager.getString("label.sequence_description") + " ",
+            MessageManager.getString("label.sequence_name"),
+            MessageManager.getString("label.sequence_description"));
+    dialog.showDialog(ap.alignFrame,
             MessageManager.getString(
                     "label.edit_sequence_name_description"),
-            ap.alignFrame);
-
-    if (!dialog.accept)
-    {
-      return;
-    }
-
-    if (dialog.getName() != null)
-    {
-      if (dialog.getName().indexOf(" ") > -1)
-      {
-        JvOptionPane.showMessageDialog(ap,
-                MessageManager
-                        .getString("label.spaces_converted_to_backslashes"),
-                MessageManager
-                        .getString("label.no_spaces_allowed_sequence_name"),
-                JvOptionPane.WARNING_MESSAGE);
-      }
-
-      sequence.setName(dialog.getName().replace(' ', '_'));
-      ap.paintAlignment(false);
-    }
-
-    sequence.setDescription(dialog.getDescription());
-
-    ap.av.firePropertyChange("alignment", null,
-            ap.av.getAlignment().getSequences());
-
+            new Runnable()
+            {
+              @Override
+              public void run()
+              {
+                if (dialog.getName() != null)
+                {
+                  if (dialog.getName().indexOf(" ") > -1)
+                  {
+                    JvOptionPane.showMessageDialog(ap,
+                            MessageManager.getString(
+                                    "label.spaces_converted_to_underscores"),
+                            MessageManager.getString(
+                                    "label.no_spaces_allowed_sequence_name"),
+                            JvOptionPane.WARNING_MESSAGE);
+                  }
+                  sequence.setName(dialog.getName().replace(' ', '_'));
+                  ap.paintAlignment(false, false);
+                }
+                sequence.setDescription(dialog.getDescription());
+                ap.av.firePropertyChange("alignment", null,
+                        ap.av.getAlignment().getSequences());
+              }
+            });
   }
 
   /**
@@ -1775,24 +1922,23 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
+   * Offers a colour chooser and sets the selected colour as the group outline
    */
   protected void outline_actionPerformed()
   {
-    SequenceGroup sg = getGroup();
-    Color col = JColorChooser.showDialog(this,
-            MessageManager.getString("label.select_outline_colour"),
-            Color.BLUE);
-
-    if (col != null)
+    String title = MessageManager
+            .getString("label.select_outline_colour");
+    ColourChooserListener listener = new ColourChooserListener()
     {
-      sg.setOutlineColour(col);
-    }
-
-    refresh();
+      @Override
+      public void colourSelected(Color c)
+      {
+        getGroup().setOutlineColour(c);
+        refresh();
+      };
+    };
+    JalviewColourChooser.showColourChooser(Desktop.getDesktop(),
+            title, Color.BLUE, listener);
   }
 
   /**
@@ -1854,12 +2000,12 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
 
   public void copy_actionPerformed()
   {
-    ap.alignFrame.copy_actionPerformed(null);
+    ap.alignFrame.copy_actionPerformed();
   }
 
   public void cut_actionPerformed()
   {
-    ap.alignFrame.cut_actionPerformed(null);
+    ap.alignFrame.cut_actionPerformed();
   }
 
   void changeCase(ActionEvent e)
@@ -1946,8 +2092,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       if (start <= end)
       {
         seqs.add(sg.getSequenceAt(i).getDatasetSequence());
-        features.add(
-                new SequenceFeature(null, null, null, start, end, null));
+        features.add(new SequenceFeature(null, null, start, end, null));
       }
     }
 
@@ -1956,12 +2101,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
      */
     if (!seqs.isEmpty())
     {
-      if (ap.getSeqPanel().seqCanvas.getFeatureRenderer()
-              .amendFeatures(seqs, features, true, ap))
-      {
-        ap.alignFrame.setShowSeqFeatures(true);
-        ap.highlightSearchResults(null);
-      }
+      new FeatureEditor(ap, seqs, features, true).showDialog();
     }
   }
 
@@ -1993,7 +2133,12 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
 
   }
 
-  public void editSequence_actionPerformed(ActionEvent actionEvent)
+  /**
+   * Shows a dialog where sequence characters may be edited. Any changes are
+   * applied, and added as an available 'Undo' item in the edit commands
+   * history.
+   */
+  public void editSequence_actionPerformed()
   {
     SequenceGroup sg = ap.av.getSelectionGroup();
 
@@ -2007,24 +2152,28 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       EditNameDialog dialog = new EditNameDialog(
               sequence.getSequenceAsString(sg.getStartRes(),
                       sg.getEndRes() + 1),
-              null, MessageManager.getString("label.edit_sequence"), null,
+              null, MessageManager.getString("label.edit_sequence"), null);
+      dialog.showDialog(ap.alignFrame,
               MessageManager.getString("label.edit_sequence"),
-              ap.alignFrame);
-
-      if (dialog.accept)
-      {
-        EditCommand editCommand = new EditCommand(
-                MessageManager.getString("label.edit_sequences"),
-                Action.REPLACE,
-                dialog.getName().replace(' ', ap.av.getGapCharacter()),
-                sg.getSequencesAsArray(ap.av.getHiddenRepSequences()),
-                sg.getStartRes(), sg.getEndRes() + 1, ap.av.getAlignment());
-
-        ap.alignFrame.addHistoryItem(editCommand);
-
-        ap.av.firePropertyChange("alignment", null,
-                ap.av.getAlignment().getSequences());
-      }
+              new Runnable()
+              {
+                @Override
+                public void run()
+                {
+                  EditCommand editCommand = new EditCommand(
+                          MessageManager.getString("label.edit_sequences"),
+                          Action.REPLACE,
+                          dialog.getName().replace(' ',
+                                  ap.av.getGapCharacter()),
+                          sg.getSequencesAsArray(
+                                  ap.av.getHiddenRepSequences()),
+                          sg.getStartRes(), sg.getEndRes() + 1,
+                          ap.av.getAlignment());
+                  ap.alignFrame.addHistoryItem(editCommand);
+                  ap.av.firePropertyChange("alignment", null,
+                          ap.av.getAlignment().getSequences());
+                }
+              });
     }
   }
 
@@ -2042,7 +2191,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
      * switch to the chosen colour scheme (or null for None)
      */
     ColourSchemeI colourScheme = ColourSchemes.getInstance()
-            .getColourScheme(colourSchemeName, sg,
+            .getColourScheme(colourSchemeName, ap.av, sg,
                     ap.av.getHiddenRepSequences());
     sg.setColourScheme(colourScheme);
     if (colourScheme instanceof Blosum62ColourScheme