JAL-3026 JLabel for SequenceFeature details in jalview-js
[jalview.git] / src / jalview / gui / PopupMenu.java
index 3e2eba9..2bcd229 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;
@@ -50,8 +50,11 @@ import jalview.schemes.PIDColourScheme;
 import jalview.util.GroupUrlLink;
 import jalview.util.GroupUrlLink.UrlStringTooLongException;
 import jalview.util.MessageManager;
+import jalview.util.StringUtils;
 import jalview.util.UrlLink;
+import jalview.util.dialogrunner.RunResponse;
 
+import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
@@ -69,10 +72,13 @@ import java.util.TreeMap;
 import java.util.Vector;
 
 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.JTextArea;
 
 /**
  * DOCUMENT ME!
@@ -176,25 +182,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 +214,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 +249,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 +269,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 +280,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 +291,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 +311,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 +340,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 +365,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 +382,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 +396,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();
               }
             }
           });
@@ -396,7 +408,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       }
     }
     // for the case when no sequences are even visible
-    if (ap.av.hasHiddenRows())
+    if (alignPanel.av.hasHiddenRows())
     {
       {
         menuItem = new JMenuItem(
@@ -406,10 +418,10 @@ 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();
             }
           }
         });
@@ -418,9 +430,9 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       }
     }
 
-    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)
@@ -455,10 +467,10 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
         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().getAllPDBEntries();
         if (pes != null && pes.size() > 0)
@@ -508,24 +520,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"));
-    Map<String, List<String>> linkset = new LinkedHashMap<String, List<String>>();
 
-    for (String link : links)
+    List<String> nlinks = null;
+    if (seq != null)
+    {
+      nlinks = Preferences.sequenceUrlLinks.getLinksForMenu();
+    }
+    else
+    {
+      nlinks = new ArrayList<>();
+    }
+
+    if (features != null)
+    {
+      for (SequenceFeature sf : features)
+      {
+        if (sf.links != null)
+        {
+          for (String link : sf.links)
+          {
+            nlinks.add(link);
+          }
+        }
+      }
+    }
+
+    Map<String, List<String>> linkset = new LinkedHashMap<>();
+
+    for (String link : nlinks)
     {
       UrlLink urlLink = null;
       try
@@ -548,25 +689,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);
+      }
     }
-
   }
 
   /**
@@ -609,8 +743,8 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
      * 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>>>();
+    Map<String, List<List<String>>> shownTypes = new LinkedHashMap<>();
+    Map<String, List<List<String>>> hiddenTypes = new LinkedHashMap<>();
     AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes, hiddenTypes,
             AlignmentAnnotationUtils.asList(annotations), forSequences);
 
@@ -716,7 +850,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
 
     SequenceI[] seqs = ap.av.getSelectionAsNewSequence();
     String[][] idandseqs = GroupUrlLink.formStrings(seqs);
-    Hashtable<String, Object[]> commonDbrefs = new Hashtable<String, Object[]>();
+    Hashtable<String, Object[]> commonDbrefs = new Hashtable<>();
     for (int sq = 0; sq < seqs.length; sq++)
     {
 
@@ -1162,7 +1296,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       @Override
       public void actionPerformed(ActionEvent actionEvent)
       {
-        editSequence_actionPerformed(actionEvent);
+        editSequence_actionPerformed();
       }
     });
     makeReferenceSeq.setText(
@@ -1377,8 +1511,8 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
      * 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<String, String>();
-    final Map<SequenceI, List<AlignmentAnnotation>> candidates = new LinkedHashMap<SequenceI, List<AlignmentAnnotation>>();
+    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);
@@ -1453,15 +1587,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 +1596,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 +1607,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();
   }
 
@@ -1530,10 +1643,7 @@ 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>");
@@ -1561,7 +1671,9 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
   void refresh()
   {
     ap.updateAnnotation();
-    ap.paintAlignment(true);
+    // removed paintAlignment(true) here:
+    // updateAnnotation calls paintAlignment already, so don't need to call
+    // again
 
     PaintRefresher.Refresh(this, ap.av.getSequenceSetId());
   }
@@ -1663,30 +1775,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 RunResponse(JvOptionPane.OK_OPTION)
+            {
+              @Override
+              public void run()
+              {
+                sg.setName(dialog.getName());
+                sg.setDescription(dialog.getDescription());
+                refresh();
+              }
+            });
   }
 
   /**
@@ -1707,48 +1816,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 RunResponse(JvOptionPane.OK_OPTION)
+            {
+              @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());
+              }
+            });
   }
 
   /**
@@ -1773,24 +1875,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);
   }
 
   /**
@@ -1929,8 +2030,8 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       return;
     }
 
-    List<SequenceI> seqs = new ArrayList<SequenceI>();
-    List<SequenceFeature> features = new ArrayList<SequenceFeature>();
+    List<SequenceI> seqs = new ArrayList<>();
+    List<SequenceFeature> features = new ArrayList<>();
 
     /*
      * assemble dataset sequences, and template new sequence features,
@@ -1944,8 +2045,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));
       }
     }
 
@@ -1954,12 +2054,15 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
      */
     if (!seqs.isEmpty())
     {
-      if (ap.getSeqPanel().seqCanvas.getFeatureRenderer()
-              .amendFeatures(seqs, features, true, ap))
+      new FeatureEditor(ap).createFeatures(seqs, features, new Runnable()
       {
-        ap.alignFrame.setShowSeqFeatures(true);
-        ap.highlightSearchResults(null);
-      }
+        public void run()
+        {
+          ap.alignFrame.setShowSeqFeatures(true);
+          ap.av.setSearchResults(null); // clear highlighting
+          ap.repaint(); // draw new/amended features
+        }
+      });
     }
   }
 
@@ -1991,7 +2094,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();
 
@@ -2005,24 +2113,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 RunResponse(JvOptionPane.OK_OPTION)
+              {
+                @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());
+                }
+              });
     }
   }