JAL-2172 test and code updates for readonly selection list
[jalview.git] / src / jalview / gui / AlignFrame.java
index 21190bd..49bd138 100644 (file)
@@ -32,7 +32,6 @@ import jalview.api.AlignViewControllerI;
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureSettingsControllerI;
-import jalview.api.FeatureSettingsModelI;
 import jalview.api.SplitContainerI;
 import jalview.api.ViewStyleI;
 import jalview.api.analysis.ScoreModelI;
@@ -54,7 +53,6 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentOrder;
 import jalview.datamodel.AlignmentView;
 import jalview.datamodel.ColumnSelection;
-import jalview.datamodel.DBRefSource;
 import jalview.datamodel.HiddenSequences;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SeqCigar;
@@ -93,12 +91,10 @@ import jalview.schemes.TaylorColourScheme;
 import jalview.schemes.TurnColourScheme;
 import jalview.schemes.UserColourScheme;
 import jalview.schemes.ZappoColourScheme;
-import jalview.structure.StructureSelectionManager;
 import jalview.util.MessageManager;
 import jalview.viewmodel.AlignmentViewport;
 import jalview.ws.DBRefFetcher;
 import jalview.ws.DBRefFetcher.FetchFinishedListenerI;
-import jalview.ws.SequenceFetcher;
 import jalview.ws.jws1.Discoverer;
 import jalview.ws.jws2.Jws2Discoverer;
 import jalview.ws.jws2.jabaws2.Jws2Instance;
@@ -112,13 +108,14 @@ import java.awt.datatransfer.Clipboard;
 import java.awt.datatransfer.DataFlavor;
 import java.awt.datatransfer.StringSelection;
 import java.awt.datatransfer.Transferable;
-import java.awt.dnd.DnDConstants;
 import java.awt.dnd.DropTargetDragEvent;
 import java.awt.dnd.DropTargetDropEvent;
 import java.awt.dnd.DropTargetEvent;
 import java.awt.dnd.DropTargetListener;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
 import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
 import java.awt.event.KeyAdapter;
@@ -466,6 +463,14 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     {
       formatMenu.add(vsel);
     }
+    addFocusListener(new FocusAdapter()
+    {
+      @Override
+      public void focusGained(FocusEvent e)
+      {
+        Jalview.setCurrentAlignFrame(AlignFrame.this);
+      }
+    });
 
   }
 
@@ -839,8 +844,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     showGroupConservation.setEnabled(!nucleotide);
     rnahelicesColour.setEnabled(nucleotide);
     purinePyrimidineColour.setEnabled(nucleotide);
-    showComplementMenuItem.setText(MessageManager
-            .getString(nucleotide ? "label.protein" : "label.nucleotide"));
+    showComplementMenuItem.setText(nucleotide ? MessageManager
+            .getString("label.protein") : MessageManager
+            .getString("label.nucleotide"));
     setColourSelected(jalview.bin.Cache.getDefault(
             nucleotide ? Preferences.DEFAULT_COLOUR_NUC
                     : Preferences.DEFAULT_COLOUR_PROT, "None"));
@@ -911,10 +917,21 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
             .setSelected(av.getGlobalColourScheme() instanceof jalview.schemes.RNAHelicesColour);
 
     showProducts.setEnabled(canShowProducts());
+    setGroovyEnabled(Desktop.getGroovyConsole() != null);
 
     updateEditMenuBar();
   }
 
+  /**
+   * Set the enabled state of the 'Run Groovy' option in the Calculate menu
+   * 
+   * @param b
+   */
+  public void setGroovyEnabled(boolean b)
+  {
+    runGroovy.setEnabled(b);
+  }
+
   private IProgressIndicator progressBar;
 
   /*
@@ -1276,13 +1293,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     AlignmentI alignmentToExport = null;
     AlignExportSettingI settings = exportSettings;
     String[] omitHidden = null;
-    int[] alignmentStartEnd = new int[2];
 
     HiddenSequences hiddenSeqs = viewport.getAlignment()
             .getHiddenSequences();
 
     alignmentToExport = viewport.getAlignment();
-    alignmentStartEnd = new int[] { 0, alignmentToExport.getWidth() - 1 };
 
     boolean hasHiddenSeqs = hiddenSeqs.getSize() > 0;
     if (settings == null)
@@ -1294,9 +1309,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
     if (viewport.hasHiddenColumns() && !settings.isExportHiddenColumns())
     {
-      omitHidden = viewport.getViewAsString(false);
+      omitHidden = viewport.getViewAsString(false,
+              settings.isExportHiddenSequences());
     }
 
+    int[] alignmentStartEnd = new int[2];
     if (hasHiddenSeqs && settings.isExportHiddenSequences())
     {
       alignmentToExport = hiddenSeqs.getFullAlignment();
@@ -1304,64 +1321,15 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     else
     {
       alignmentToExport = viewport.getAlignment();
-      alignmentStartEnd = getStartEnd(alignmentStartEnd, viewport
-              .getColumnSelection().getHiddenColumns());
     }
+    alignmentStartEnd = alignmentToExport
+            .getVisibleStartAndEndIndex(viewport.getColumnSelection()
+                    .getHiddenColumns());
     AlignmentExportData ed = new AlignmentExportData(alignmentToExport,
             omitHidden, alignmentStartEnd, settings);
     return ed;
   }
 
-  public static int[] getStartEnd(int[] aligmentStartEnd,
-          List<int[]> hiddenCols)
-  {
-    int startPos = aligmentStartEnd[0];
-    int endPos = aligmentStartEnd[1];
-
-    int[] lowestRange = new int[] { -1, -1 };
-    int[] higestRange = new int[] { -1, -1 };
-
-    for (int[] hiddenCol : hiddenCols)
-    {
-      lowestRange = (hiddenCol[0] <= startPos) ? hiddenCol : lowestRange;
-      higestRange = (hiddenCol[1] >= endPos) ? hiddenCol : higestRange;
-    }
-
-    if (lowestRange[0] == -1 && lowestRange[1] == -1)
-    {
-      startPos = aligmentStartEnd[0];
-    }
-    else
-    {
-      startPos = lowestRange[1] + 1;
-    }
-
-    if (higestRange[0] == -1 && higestRange[1] == -1)
-    {
-      endPos = aligmentStartEnd[1];
-    }
-    else
-    {
-      endPos = higestRange[0] - 1;
-    }
-
-    // System.out.println("Export range : " + startPos + " - " + endPos);
-    return new int[] { startPos, endPos };
-  }
-
-  public static void main(String[] args)
-  {
-    ArrayList<int[]> hiddenCols = new ArrayList<int[]>();
-    hiddenCols.add(new int[] { 0, 0 });
-    hiddenCols.add(new int[] { 6, 9 });
-    hiddenCols.add(new int[] { 11, 12 });
-    hiddenCols.add(new int[] { 33, 33 });
-    hiddenCols.add(new int[] { 50, 50 });
-
-    int[] x = getStartEnd(new int[] { 0, 50 }, hiddenCols);
-    // System.out.println("Export range : " + x[0] + " - " + x[1]);
-  }
-
   /**
    * DOCUMENT ME!
    * 
@@ -2868,7 +2836,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   public void expandViews_actionPerformed(ActionEvent e)
   {
-    Desktop.instance.explodeViews(this);
+    Desktop.explodeViews(this);
   }
 
   /**
@@ -3009,9 +2977,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       // Hide everything by the current selection - this is a hack - we do the
       // invert and then hide
       // first check that there will be visible columns after the invert.
-      if ((viewport.getColumnSelection() != null
-              && viewport.getColumnSelection().getSelected() != null && viewport
-              .getColumnSelection().getSelected().size() > 0)
+      if (viewport.hasSelectedColumns()
               || (sg != null && sg.getSize() > 0 && sg.getStartRes() <= sg
                       .getEndRes()))
       {
@@ -3039,8 +3005,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         hideSelSequences_actionPerformed(null);
         hide = true;
       }
-      else if (!(toggleCols && viewport.getColumnSelection().getSelected()
-              .size() > 0))
+      else if (!(toggleCols && viewport.hasSelectedColumns()))
       {
         showAllSeqs_actionPerformed(null);
       }
@@ -3048,7 +3013,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
     if (toggleCols)
     {
-      if (viewport.getColumnSelection().getSelected().size() > 0)
+      if (viewport.hasSelectedColumns())
       {
         hideSelColumns_actionPerformed(null);
         if (!toggleSeqs)
@@ -3247,30 +3212,6 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   }
 
   /**
-   * Set or clear 'Show Sequence Features'
-   * 
-   * @param evt
-   *          DOCUMENT ME!
-   */
-  @Override
-  public void showSeqFeaturesHeight_actionPerformed(ActionEvent evt)
-  {
-    viewport.setShowSequenceFeaturesHeight(showSeqFeaturesHeight
-            .isSelected());
-    if (viewport.isShowSequenceFeaturesHeight())
-    {
-      // ensure we're actually displaying features
-      viewport.setShowSequenceFeatures(true);
-      showSeqFeatures.setSelected(true);
-    }
-    alignPanel.paintAlignment(true);
-    if (alignPanel.getOverviewPanel() != null)
-    {
-      alignPanel.getOverviewPanel().updateOverviewImage();
-    }
-  }
-
-  /**
    * Action on toggle of the 'Show annotations' menu item. This shows or hides
    * the annotations panel as a whole.
    * 
@@ -3676,35 +3617,51 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           @Override
           public void mousePressed(MouseEvent evt)
           {
-            if (evt.isControlDown()
-                    || SwingUtilities.isRightMouseButton(evt))
+            if (evt.isPopupTrigger()) // Mac
             {
-              radioItem.removeActionListener(radioItem.getActionListeners()[0]);
+              offerRemoval(radioItem);
+            }
+          }
 
-              int option = JOptionPane.showInternalConfirmDialog(
-                      jalview.gui.Desktop.desktop,
-                      MessageManager
-                              .getString("label.remove_from_default_list"),
-                      MessageManager
-                              .getString("label.remove_user_defined_colour"),
-                      JOptionPane.YES_NO_OPTION);
-              if (option == JOptionPane.YES_OPTION)
-              {
-                jalview.gui.UserDefinedColours
-                        .removeColourFromDefaults(radioItem.getText());
-                colourMenu.remove(radioItem);
-              }
-              else
+          @Override
+          public void mouseReleased(MouseEvent evt)
+          {
+            if (evt.isPopupTrigger()) // Windows
+            {
+              offerRemoval(radioItem);
+            }
+          }
+
+          /**
+           * @param radioItem
+           */
+          void offerRemoval(final JRadioButtonMenuItem radioItem)
+          {
+            radioItem.removeActionListener(radioItem.getActionListeners()[0]);
+
+            int option = JOptionPane.showInternalConfirmDialog(
+                    jalview.gui.Desktop.desktop,
+                    MessageManager
+                            .getString("label.remove_from_default_list"),
+                    MessageManager
+                            .getString("label.remove_user_defined_colour"),
+                    JOptionPane.YES_NO_OPTION);
+            if (option == JOptionPane.YES_OPTION)
+            {
+              jalview.gui.UserDefinedColours
+                      .removeColourFromDefaults(radioItem.getText());
+              colourMenu.remove(radioItem);
+            }
+            else
+            {
+              radioItem.addActionListener(new ActionListener()
               {
-                radioItem.addActionListener(new ActionListener()
+                @Override
+                public void actionPerformed(ActionEvent evt)
                 {
-                  @Override
-                  public void actionPerformed(ActionEvent evt)
-                  {
-                    userDefinedColour_actionPerformed(evt);
-                  }
-                });
-              }
+                  userDefinedColour_actionPerformed(evt);
+                }
+              });
             }
           }
         });
@@ -4181,7 +4138,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       {
         JMenuItem tm = new JMenuItem();
         ScoreModelI sm = ResidueProperties.scoreMatrices.get(pwtype);
-        if (sm.isProtein() == !viewport.getAlignment().isNucleotide())
+        if (sm.isDNA() == viewport.getAlignment().isNucleotide()
+                || sm.isProtein() == !viewport.getAlignment()
+                        .isNucleotide())
         {
           String smn = MessageManager.getStringOrReturn(
                   "label.score_model_", sm.getName());
@@ -4494,22 +4453,18 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           // object broker mechanism.
           final Vector<JMenu> wsmenu = new Vector<JMenu>();
           final IProgressIndicator af = me;
+
+          /*
+           * do not i18n these strings - they are hard-coded in class
+           * compbio.data.msa.Category, Jws2Discoverer.isRecalculable() and
+           * SequenceAnnotationWSClient.initSequenceAnnotationWSClient()
+           */
           final JMenu msawsmenu = new JMenu("Alignment");
           final JMenu secstrmenu = new JMenu(
                   "Secondary Structure Prediction");
           final JMenu seqsrchmenu = new JMenu("Sequence Database Search");
           final JMenu analymenu = new JMenu("Analysis");
           final JMenu dismenu = new JMenu("Protein Disorder");
-          // final JMenu msawsmenu = new
-          // JMenu(MessageManager.getString("label.alignment"));
-          // final JMenu secstrmenu = new
-          // JMenu(MessageManager.getString("label.secondary_structure_prediction"));
-          // final JMenu seqsrchmenu = new
-          // JMenu(MessageManager.getString("label.sequence_database_search"));
-          // final JMenu analymenu = new
-          // JMenu(MessageManager.getString("label.analysis"));
-          // final JMenu dismenu = new
-          // JMenu(MessageManager.getString("label.protein_disorder"));
           // JAL-940 - only show secondary structure prediction services from
           // the legacy server
           if (// Cache.getDefault("SHOW_JWS1_SERVICES", true)
@@ -4669,38 +4624,45 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   }
 
   /**
-   * Searches selected sequences for xRef products and builds the Show
-   * Cross-References menu (formerly called Show Products)
+   * Searches the alignment sequences for xRefs and builds the Show
+   * Cross-References menu (formerly called Show Products), with database
+   * sources for which cross-references are found (protein sources for a
+   * nucleotide alignment and vice versa)
    * 
-   * @return true if Show Cross-references menu should be enabled.
+   * @return true if Show Cross-references menu should be enabled
    */
   public boolean canShowProducts()
   {
-    SequenceI[] selection = viewport.getSequenceSelection();
+    SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
     AlignmentI dataset = viewport.getAlignment().getDataset();
+
+    showProducts.removeAll();
+    final boolean dna = viewport.getAlignment().isNucleotide();
+
+    if (seqs == null || seqs.length == 0)
+    {
+      // nothing to see here.
+      return false;
+    }
+
     boolean showp = false;
     try
     {
-      showProducts.removeAll();
-      final boolean dna = viewport.getAlignment().isNucleotide();
-      String[] ptypes = (selection == null || selection.length == 0) ? null
-              : CrossRef.findSequenceXrefTypes(dna, selection, dataset);
+      List<String> ptypes = new CrossRef(seqs, dataset)
+              .findXrefSourcesForSequences(dna);
 
-      for (int t = 0; ptypes != null && t < ptypes.length; t++)
+      for (final String source : ptypes)
       {
         showp = true;
         final AlignFrame af = this;
-        final String source = ptypes[t];
-        JMenuItem xtype = new JMenuItem(ptypes[t]);
+        JMenuItem xtype = new JMenuItem(source);
         xtype.addActionListener(new ActionListener()
         {
-
           @Override
           public void actionPerformed(ActionEvent e)
           {
             showProductsFor(af.viewport.getSequenceSelection(), dna, source);
           }
-
         });
         showProducts.add(xtype);
       }
@@ -4708,7 +4670,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       showProducts.setEnabled(showp);
     } catch (Exception e)
     {
-      jalview.bin.Cache.log
+      Cache.log
               .warn("canShowProducts threw an exception - please report to help@jalview.org",
                       e);
       return false;
@@ -4716,228 +4678,22 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     return showp;
   }
 
-  protected void showProductsFor(final SequenceI[] sel, final boolean dna,
-          final String source)
+  /**
+   * Finds and displays cross-references for the selected sequences (protein
+   * products for nucleotide sequences, dna coding sequences for peptides).
+   * 
+   * @param sel
+   *          the sequences to show cross-references for
+   * @param dna
+   *          true if from a nucleotide alignment (so showing proteins)
+   * @param source
+   *          the database to show cross-references for
+   */
+  protected void showProductsFor(final SequenceI[] sel,
+          final boolean _odna, final String source)
   {
-    Runnable foo = new Runnable()
-    {
-
-      @Override
-      public void run()
-      {
-        final long sttime = System.currentTimeMillis();
-        AlignFrame.this.setProgressBar(MessageManager.formatMessage(
-                "status.searching_for_sequences_from",
-                new Object[] { source }), sttime);
-        try
-        {
-          AlignmentI alignment = AlignFrame.this.getViewport()
-                  .getAlignment();
-          AlignmentI xrefs = CrossRef.findXrefSequences(sel, dna, source,
-                  alignment);
-          if (xrefs != null)
-          {
-            /*
-             * get display scheme (if any) to apply to features
-             */
-            FeatureSettingsModelI featureColourScheme = new SequenceFetcher()
-                    .getFeatureColourScheme(source);
-
-            AlignmentI al = makeCrossReferencesAlignment(
-                    alignment.getDataset(), xrefs);
-
-            AlignFrame newFrame = new AlignFrame(al, DEFAULT_WIDTH,
-                    DEFAULT_HEIGHT);
-            String newtitle = String.format("%s %s %s",
-                    MessageManager.getString(dna ? "label.proteins"
-                            : "label.nucleotides"), MessageManager
-                            .getString("label.for"), getTitle());
-            newFrame.setTitle(newtitle);
-
-            if (!Cache.getDefault(Preferences.ENABLE_SPLIT_FRAME, true))
-            {
-              /*
-               * split frame display is turned off in preferences file
-               */
-              Desktop.addInternalFrame(newFrame, newtitle, DEFAULT_WIDTH,
-                      DEFAULT_HEIGHT);
-              return; // via finally clause
-            }
-
-            /*
-             * Make a copy of this alignment (sharing the same dataset
-             * sequences). If we are DNA, drop introns and update mappings
-             */
-            AlignmentI copyAlignment = null;
-            final SequenceI[] sequenceSelection = AlignFrame.this.viewport
-                    .getSequenceSelection();
-            List<AlignedCodonFrame> cf = xrefs.getCodonFrames();
-            boolean copyAlignmentIsAligned = false;
-            if (dna)
-            {
-              copyAlignment = AlignmentUtils.makeCdsAlignment(
-                      sequenceSelection, cf, alignment);
-              if (copyAlignment.getHeight() == 0)
-              {
-                System.err.println("Failed to make CDS alignment");
-              }
-              al.getCodonFrames().clear();
-              al.getCodonFrames().addAll(copyAlignment.getCodonFrames());
-
-              /*
-               * pending getting Embl transcripts to 'align', 
-               * we are only doing this for Ensembl
-               */
-              // TODO proper criteria for 'can align as cdna'
-              if (DBRefSource.ENSEMBL.equalsIgnoreCase(source)
-                      || AlignmentUtils.looksLikeEnsembl(alignment))
-              {
-                copyAlignment.alignAs(alignment);
-                copyAlignmentIsAligned = true;
-              }
-            }
-            else
-            {
-              copyAlignment = AlignmentUtils.makeCopyAlignment(
-                      sequenceSelection, xrefs.getSequencesArray());
-              copyAlignment.getCodonFrames().addAll(cf);
-            }
-            copyAlignment.setGapCharacter(AlignFrame.this.viewport
-                    .getGapCharacter());
-
-            StructureSelectionManager ssm = StructureSelectionManager
-                    .getStructureSelectionManager(Desktop.instance);
-            ssm.registerMappings(cf);
-
-            if (copyAlignment.getHeight() <= 0)
-            {
-              System.err.println("No Sequences generated for xRef type "
-                      + source);
-              return;
-            }
-            /*
-             * align protein to dna
-             */
-            if (dna && copyAlignmentIsAligned)
-            {
-              al.alignAs(copyAlignment);
-            }
-            else
-            {
-              /*
-               * align cdna to protein - currently only if 
-               * fetching and aligning Ensembl transcripts!
-               */
-              if (DBRefSource.ENSEMBL.equalsIgnoreCase(source))
-              {
-                copyAlignment.alignAs(al);
-              }
-            }
-
-            AlignFrame copyThis = new AlignFrame(copyAlignment,
-                    AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
-            copyThis.setTitle(AlignFrame.this.getTitle());
-
-            boolean showSequenceFeatures = viewport
-                    .isShowSequenceFeatures();
-            newFrame.setShowSeqFeatures(showSequenceFeatures);
-            copyThis.setShowSeqFeatures(showSequenceFeatures);
-            FeatureRenderer myFeatureStyling = alignPanel.getSeqPanel().seqCanvas
-                    .getFeatureRenderer();
-
-            /*
-             * copy feature rendering settings to split frame
-             */
-            newFrame.alignPanel.getSeqPanel().seqCanvas
-                    .getFeatureRenderer()
-                    .transferSettings(myFeatureStyling);
-            copyThis.alignPanel.getSeqPanel().seqCanvas
-                    .getFeatureRenderer()
-                    .transferSettings(myFeatureStyling);
-
-            /*
-             * apply 'database source' feature configuration
-             * if any was found
-             */
-            // TODO is this the feature colouring for the original
-            // alignment or the fetched xrefs? either could be Ensembl
-            newFrame.getViewport().applyFeaturesStyle(featureColourScheme);
-            copyThis.getViewport().applyFeaturesStyle(featureColourScheme);
-
-            SplitFrame sf = new SplitFrame(dna ? copyThis : newFrame,
-                    dna ? newFrame : copyThis);
-            newFrame.setVisible(true);
-            copyThis.setVisible(true);
-            String linkedTitle = MessageManager
-                    .getString("label.linked_view_title");
-            Desktop.addInternalFrame(sf, linkedTitle, -1, -1);
-            sf.adjustDivider();
-          }
-        } catch (Exception e)
-        {
-          Cache.log.error(
-                  "Exception when finding crossreferences", e);
-        } catch (OutOfMemoryError e)
-        {
-          new OOMWarning("whilst fetching crossreferences", e);
-        } catch (Throwable e)
-        {
-          Cache.log.error("Error when finding crossreferences",
-                  e);
-        } finally
-        {
-          AlignFrame.this.setProgressBar(MessageManager.formatMessage(
-                  "status.finished_searching_for_sequences_from",
-                  new Object[] { source }), sttime);
-        }
-      }
-
-      /**
-       * Makes an alignment containing the given sequences. If this is of the
-       * same type as the given dataset (nucleotide/protein), then the new
-       * alignment shares the same dataset, and its dataset sequences are added
-       * to it. Otherwise a new dataset sequence is created for the
-       * cross-references.
-       * 
-       * @param dataset
-       * @param seqs
-       * @return
-       */
-      protected AlignmentI makeCrossReferencesAlignment(AlignmentI dataset,
-              AlignmentI seqs)
-      {
-        boolean sameType = dataset.isNucleotide() == seqs.isNucleotide();
-
-        SequenceI[] sprods = new SequenceI[seqs.getHeight()];
-        for (int s = 0; s < sprods.length; s++)
-        {
-          sprods[s] = (seqs.getSequenceAt(s)).deriveSequence();
-          if (sameType)
-          {
-            if (dataset.getSequences() == null
-                    || !dataset.getSequences().contains(
-                            sprods[s].getDatasetSequence()))
-            {
-              dataset.addSequence(sprods[s].getDatasetSequence());
-            }
-          }
-          sprods[s].updatePDBIds();
-        }
-        Alignment al = new Alignment(sprods);
-        if (sameType)
-        {
-          al.setDataset((Alignment) dataset);
-        }
-        else
-        {
-          al.createDatasetAlignment();
-        }
-        return al;
-      }
-
-    };
-    Thread frunner = new Thread(foo);
-    frunner.start();
+    new Thread(CrossRefAction.showProductsFor(sel, _odna, source, this))
+            .start();
   }
 
   /**
@@ -4961,7 +4717,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
               .getString("label.error_when_translating_sequences_submit_bug_report");
       final String errorTitle = MessageManager
               .getString("label.implementation_error")
-              + MessageManager.getString("translation_failed");
+              + MessageManager.getString("label.translation_failed");
       JOptionPane.showMessageDialog(Desktop.desktop, msg, errorTitle,
               JOptionPane.ERROR_MESSAGE);
       return;
@@ -5060,49 +4816,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   public void drop(DropTargetDropEvent evt)
   {
     Transferable t = evt.getTransferable();
-    java.util.List files = null;
+    java.util.List<String> files = new ArrayList<String>(), protocols = new ArrayList<String>();
 
     try
     {
-      DataFlavor uriListFlavor = new DataFlavor(
-              "text/uri-list;class=java.lang.String");
-      if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
-      {
-        // Works on Windows and MacOSX
-        evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
-        files = (java.util.List) t
-                .getTransferData(DataFlavor.javaFileListFlavor);
-      }
-      else if (t.isDataFlavorSupported(uriListFlavor))
-      {
-        // This is used by Unix drag system
-        evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
-        String data = (String) t.getTransferData(uriListFlavor);
-        files = new java.util.ArrayList(1);
-        for (java.util.StringTokenizer st = new java.util.StringTokenizer(
-                data, "\r\n"); st.hasMoreTokens();)
-        {
-          String s = st.nextToken();
-          if (s.startsWith("#"))
-          {
-            // the line is a comment (as per the RFC 2483)
-            continue;
-          }
-
-          java.net.URI uri = new java.net.URI(s);
-          // check to see if we can handle this kind of URI
-          if (uri.getScheme().toLowerCase().startsWith("http"))
-          {
-            files.add(uri.toString());
-          }
-          else
-          {
-            // otherwise preserve old behaviour: catch all for file objects
-            java.io.File file = new java.io.File(uri);
-            files.add(file.toString());
-          }
-        }
-      }
+      Desktop.transferFromDropTarget(files, protocols, evt, t);
     } catch (Exception e)
     {
       e.printStackTrace();
@@ -5428,7 +5146,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   public void tabbedPane_mousePressed(MouseEvent e)
   {
-    if (SwingUtilities.isRightMouseButton(e))
+    if (e.isPopupTrigger())
     {
       String msg = MessageManager.getString("label.enter_view_name");
       String reply = JOptionPane.showInternalInputDialog(this, msg, msg,
@@ -5577,8 +5295,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                 AlignFrame.this.setMenusForViewport();
               }
             });
-            dbRefFetcher
-                    .fetchDBRefs(false);
+            dbRefFetcher.fetchDBRefs(false);
           }
         }).start();
 
@@ -6019,8 +5736,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   protected void setAnnotationsVisibility(boolean visible,
           boolean forSequences, boolean forAlignment)
   {
-    for (AlignmentAnnotation aa : alignPanel.getAlignment()
-            .getAlignmentAnnotation())
+    AlignmentAnnotation[] anns = alignPanel.getAlignment()
+            .getAlignmentAnnotation();
+    if (anns == null)
+    {
+      return;
+    }
+    for (AlignmentAnnotation aa : anns)
     {
       /*
        * don't display non-positional annotations on an alignment
@@ -6136,15 +5858,75 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     try
     {
       Dna dna = new Dna(viewport, viewport.getViewAsVisibleContigs(true));
-
       al = dna.reverseCdna(complement);
       viewport.addAlignment(al, "");
+      addHistoryItem(new EditCommand(
+              MessageManager.getString("label.add_sequences"),
+              Action.PASTE, al.getSequencesArray(), 0, al.getWidth(),
+              viewport.getAlignment()));
     } catch (Exception ex)
     {
       System.err.println(ex.getMessage());
       return;
     }
   }
+
+  /**
+   * Try to run a script in the Groovy console, having first ensured that this
+   * AlignFrame is set as currentAlignFrame in Desktop, to allow the script to
+   * be targeted at this alignment.
+   */
+  @Override
+  protected void runGroovy_actionPerformed()
+  {
+    Jalview.setCurrentAlignFrame(this);
+    groovy.ui.Console console = Desktop.getGroovyConsole();
+    if (console != null)
+    {
+      try
+      {
+        console.runScript();
+      } catch (Exception ex)
+      {
+        System.err.println((ex.toString()));
+        JOptionPane
+                .showInternalMessageDialog(Desktop.desktop, MessageManager
+                        .getString("label.couldnt_run_groovy_script"),
+                        MessageManager
+                                .getString("label.groovy_support_failed"),
+                        JOptionPane.ERROR_MESSAGE);
+      }
+    }
+    else
+    {
+      System.err.println("Can't run Groovy script as console not found");
+    }
+  }
+
+  /**
+   * Hides columns containing (or not containing) a specified feature, provided
+   * that would not leave all columns hidden
+   * 
+   * @param featureType
+   * @param columnsContaining
+   * @return
+   */
+  public boolean hideFeatureColumns(String featureType,
+          boolean columnsContaining)
+  {
+    boolean notForHiding = avc.markColumnsContainingFeatures(
+            columnsContaining, false, false, featureType);
+    if (notForHiding)
+    {
+      if (avc.markColumnsContainingFeatures(!columnsContaining, false,
+              false, featureType))
+      {
+        getViewport().hideSelectedColumns();
+        return true;
+      }
+    }
+    return false;
+  }
 }
 
 class PrintThread extends Thread