Merge remote-tracking branch 'origin/merge/JAL-845_JAL-1640' into
[jalview.git] / src / jalview / gui / AlignFrame.java
index 3180993..d613ea7 100644 (file)
@@ -23,13 +23,17 @@ package jalview.gui;
 import jalview.analysis.AAFrequency;
 import jalview.analysis.AlignmentSorter;
 import jalview.analysis.AlignmentUtils;
+import jalview.analysis.AlignmentUtils.MappingResult;
 import jalview.analysis.Conservation;
 import jalview.analysis.CrossRef;
-import jalview.analysis.NJTree;
+import jalview.analysis.Dna;
 import jalview.analysis.ParseProperties;
 import jalview.analysis.SequenceIdMatcher;
 import jalview.api.AlignViewControllerGuiI;
 import jalview.api.AlignViewControllerI;
+import jalview.api.AlignViewportI;
+import jalview.api.AlignmentViewPanel;
+import jalview.api.ViewStyleI;
 import jalview.api.analysis.ScoreModelI;
 import jalview.bin.Cache;
 import jalview.commands.CommandI;
@@ -52,8 +56,10 @@ import jalview.datamodel.SeqCigar;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
+import jalview.gui.ViewSelectionMenu.ViewSetProvider;
 import jalview.io.AlignmentProperties;
 import jalview.io.AnnotationFile;
+import jalview.io.BioJsHTMLOutput;
 import jalview.io.FeaturesFile;
 import jalview.io.FileLoader;
 import jalview.io.FormatAdapter;
@@ -83,7 +89,9 @@ 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.jws1.Discoverer;
 import jalview.ws.jws2.Jws2Discoverer;
 import jalview.ws.jws2.jabaws2.Jws2Instance;
@@ -105,6 +113,8 @@ import java.awt.dnd.DropTargetEvent;
 import java.awt.dnd.DropTargetListener;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
 import java.awt.event.KeyAdapter;
 import java.awt.event.KeyEvent;
 import java.awt.event.MouseAdapter;
@@ -115,9 +125,12 @@ import java.beans.PropertyChangeEvent;
 import java.io.File;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Deque;
 import java.util.Enumeration;
 import java.util.Hashtable;
 import java.util.List;
+import java.util.Set;
 import java.util.Vector;
 
 import javax.swing.JButton;
@@ -145,19 +158,20 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         IProgressIndicator, AlignViewControllerGuiI
 {
 
-  /** DOCUMENT ME!! */
   public static final int DEFAULT_WIDTH = 700;
 
-  /** DOCUMENT ME!! */
   public static final int DEFAULT_HEIGHT = 500;
 
+  /*
+   * The currently displayed panel (selected tabbed view if more than one)
+   */
   public AlignmentPanel alignPanel;
 
   AlignViewport viewport;
 
   public AlignViewControllerI avc;
 
-  Vector alignPanels = new Vector();
+  List<AlignmentPanel> alignPanels = new ArrayList<AlignmentPanel>();
 
   /**
    * Last format used to load or save alignments in this window
@@ -336,7 +350,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     buildSortByAnnotationScoresMenu();
     buildTreeMenu();
     
-    if (viewport.wrapAlignment)
+    if (viewport.getWrapAlignment())
     {
       wrapMenuItem_actionPerformed(null);
     }
@@ -348,6 +362,59 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
     addKeyListener();
 
+    final List<AlignmentPanel> selviews = new ArrayList<AlignmentPanel>();
+    final List<AlignmentPanel> origview = new ArrayList<AlignmentPanel>();
+    ViewSelectionMenu vsel = new ViewSelectionMenu("Transfer colours from",
+            new ViewSetProvider()
+            {
+
+              @Override
+              public AlignmentPanel[] getAllAlignmentPanels()
+              {
+                origview.clear();
+                origview.add(alignPanel);
+                return Desktop.getAlignmentPanels(null);
+              }
+            }, selviews, new ItemListener()
+            {
+
+              @Override
+              public void itemStateChanged(ItemEvent e)
+              {
+                if (origview.size() > 0)
+                {
+                  ViewStyleI vs = selviews.get(0).getAlignViewport()
+                          .getViewStyle();
+                  origview.get(0).getAlignViewport().setViewStyle(vs);
+                  AlignViewportI complement = origview.get(0)
+                          .getAlignViewport().getCodingComplement();
+                  if (complement != null)
+                  {
+                    AlignFrame af = Desktop.getAlignFrameFor(complement);
+                    if (complement.isNucleotide())
+                    {
+                      complement.setViewStyle(vs);
+                      vs.setCharWidth(vs.getCharWidth() / 3);
+                    }
+                    else
+                    {
+                      int rw = vs.getCharWidth();
+                      vs.setCharWidth(rw * 3);
+                      complement.setViewStyle(vs);
+                      vs.setCharWidth(rw);
+                    }
+                    af.alignPanel.updateLayout();
+                    af.setMenusForViewport();
+                  }
+                  origview.get(0).updateLayout();
+                  origview.get(0).setSelected(true);
+                  origview.get(0).alignFrame.setMenusForViewport();
+
+                }
+              }
+            });
+    formatMenu.add(vsel);
+
   }
 
   /**
@@ -366,6 +433,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     reload.setEnabled(true);
   }
 
+  /**
+   * Add a KeyListener with handlers for various KeyPressed and KeyReleased
+   * events
+   */
   void addKeyListener()
   {
     addKeyListener(new KeyAdapter()
@@ -539,7 +610,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           break;
         }
         case KeyEvent.VK_PAGE_UP:
-          if (viewport.wrapAlignment)
+          if (viewport.getWrapAlignment())
           {
             alignPanel.scrollUp(true);
           }
@@ -550,7 +621,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           }
           break;
         case KeyEvent.VK_PAGE_DOWN:
-          if (viewport.wrapAlignment)
+          if (viewport.getWrapAlignment())
           {
             alignPanel.scrollUp(false);
           }
@@ -594,7 +665,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     avc = new jalview.controller.AlignViewController(this, viewport,
             alignPanel);
 
-    alignPanels.addElement(ap);
+    alignPanels.add(ap);
 
     PaintRefresher.Register(ap, ap.av.getSequenceSetId());
 
@@ -637,7 +708,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     expandViews.setEnabled(true);
     gatherViews.setEnabled(true);
     tabbedPane.setVisible(true);
-    AlignmentPanel first = (AlignmentPanel) alignPanels.firstElement();
+    AlignmentPanel first = alignPanels.get(0);
     tabbedPane.addTab(first.av.viewName, first);
     this.getContentPane().add(tabbedPane, BorderLayout.CENTER);
   }
@@ -701,21 +772,102 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   public void setGUINucleotide(boolean nucleotide)
   {
     showTranslation.setVisible(nucleotide);
+    cdna.setVisible(!nucleotide);
     conservationMenuItem.setEnabled(!nucleotide);
     modifyConservation.setEnabled(!nucleotide);
     showGroupConservation.setEnabled(!nucleotide);
     rnahelicesColour.setEnabled(nucleotide);
     purinePyrimidineColour.setEnabled(nucleotide);
-    // Remember AlignFrame always starts as protein
-    // if (!nucleotide)
-    // {
-    // showTr
-    // calculateMenu.remove(calculateMenu.getItemCount() - 2);
-    // }
   }
 
   /**
-   * set up menus for the currently viewport. This may be called after any
+   * Builds codon mappings from this (protein) alignment to any compatible
+   * nucleotide alignments. Mappings are built between sequences with the same
+   * name and compatible lengths. Also makes the cDNA alignment a
+   * CommandListener for the protein alignment so that edits are mirrored.
+   */
+  @Override
+  protected void linkCdna_actionPerformed()
+  {
+    int linkedCount = 0;
+    int alreadyLinkedCount = 0;
+    final AlignmentI thisAlignment = this.alignPanel.getAlignment();
+
+    for (AlignFrame af : Desktop.getAlignFrames())
+    {
+      if (af.alignPanel != null)
+      {
+        final AlignmentI thatAlignment = af.alignPanel.getAlignment();
+        if (thatAlignment.isNucleotide())
+        {
+          MappingResult mapped = AlignmentUtils.mapProteinToCdna(
+                  thisAlignment, thatAlignment);
+          if (mapped == MappingResult.AlreadyMapped)
+          {
+            alreadyLinkedCount++;
+          }
+          else if (mapped == MappingResult.Mapped)
+          {
+            final StructureSelectionManager ssm = StructureSelectionManager
+                    .getStructureSelectionManager(Desktop.instance);
+            ssm.addMappings(thisAlignment.getCodonFrames());
+            // enable the next line to enable linked editing
+            // ssm.addCommandListener(af.getViewport());
+            linkedCount++;
+          }
+        }
+      }
+    }
+    String msg = "";
+    if (linkedCount == 0 && alreadyLinkedCount == 0)
+    {
+      msg = MessageManager.getString("label.no_cdna");
+    }
+    else if (linkedCount > 0)
+    {
+      msg = MessageManager.formatMessage("label.linked_cdna", linkedCount);
+    }
+    else
+    {
+      msg = MessageManager.formatMessage("label.cdna_all_linked",
+              alreadyLinkedCount);
+    }
+    setStatus(msg);
+  }
+
+  /**
+   * Align any linked cDNA to match the alignment of this (protein) alignment.
+   * Any mapped sequence regions will be realigned, unmapped sequences are not
+   * affected.
+   */
+  @Override
+  protected void alignCdna_actionPerformed()
+  {
+    int seqCount = 0;
+    int alignCount = 0;
+    final AlignmentI thisAlignment = this.alignPanel.getAlignment();
+    for (AlignFrame af : Desktop.getAlignFrames())
+    {
+      if (af.alignPanel != null)
+      {
+        final AlignmentI thatAlignment = af.alignPanel.getAlignment();
+        if (thatAlignment.isNucleotide())
+        {
+          int seqsAligned = thatAlignment.alignAs(thisAlignment);
+          seqCount += seqsAligned;
+          if (seqsAligned > 0)
+          {
+            af.alignPanel.alignmentChanged();
+            alignCount++;
+          }
+        }
+      }
+    }
+    setStatus(MessageManager.formatMessage("label.cdna_aligned", seqCount,
+            alignCount));
+  }
+  /**
+   * set up menus for the current viewport. This may be called after any
    * operation that affects the data in the current view (selection changed,
    * etc) to update the menus to reflect the new state.
    */
@@ -734,17 +886,17 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   void setMenusFromViewport(AlignViewport av)
   {
     padGapsMenuitem.setSelected(av.isPadGaps());
-    colourTextMenuItem.setSelected(av.showColourText);
+    colourTextMenuItem.setSelected(av.isShowColourText());
     abovePIDThreshold.setSelected(av.getAbovePIDThreshold());
     conservationMenuItem.setSelected(av.getConservationSelected());
     seqLimits.setSelected(av.getShowJVSuffix());
     idRightAlign.setSelected(av.isRightAlignIds());
-    centreColumnLabelsMenuItem.setState(av.centreColumnLabels);
-    renderGapsMenuItem.setSelected(av.renderGaps);
-    wrapMenuItem.setSelected(av.wrapAlignment);
-    scaleAbove.setVisible(av.wrapAlignment);
-    scaleLeft.setVisible(av.wrapAlignment);
-    scaleRight.setVisible(av.wrapAlignment);
+    centreColumnLabelsMenuItem.setState(av.isCentreColumnLabels());
+    renderGapsMenuItem.setSelected(av.isRenderGaps());
+    wrapMenuItem.setSelected(av.getWrapAlignment());
+    scaleAbove.setVisible(av.getWrapAlignment());
+    scaleLeft.setVisible(av.getWrapAlignment());
+    scaleRight.setVisible(av.getWrapAlignment());
     annotationPanelMenuItem.setState(av.isShowAnnotation());
     /*
      * Show/hide annotations only enabled if annotation panel is shown
@@ -753,8 +905,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     hideAllSeqAnnotations.setEnabled(annotationPanelMenuItem.getState());
     showAllAlAnnotations.setEnabled(annotationPanelMenuItem.getState());
     hideAllAlAnnotations.setEnabled(annotationPanelMenuItem.getState());
-    viewBoxesMenuItem.setSelected(av.showBoxes);
-    viewTextMenuItem.setSelected(av.showText);
+    viewBoxesMenuItem.setSelected(av.getShowBoxes());
+    viewTextMenuItem.setSelected(av.getShowText());
     showNonconservedMenuItem.setSelected(av.getShowUnconserved());
     showGroupConsensus.setSelected(av.isShowGroupConsensus());
     showGroupConservation.setSelected(av.isShowGroupConservation());
@@ -765,11 +917,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     setColourSelected(ColourSchemeProperty.getColourName(av
             .getGlobalColourScheme()));
 
-    showSeqFeatures.setSelected(av.showSequenceFeatures);
-    hiddenMarkers.setState(av.showHiddenMarkers);
+    showSeqFeatures.setSelected(av.isShowSequenceFeatures());
+    hiddenMarkers.setState(av.getShowHiddenMarkers());
     applyToAllGroups.setState(av.getColourAppliesToAllGroups());
-    showNpFeatsMenuitem.setSelected(av.isShowNpFeats());
-    showDbRefsMenuitem.setSelected(av.isShowDbRefs());
+    showNpFeatsMenuitem.setSelected(av.isShowNPFeats());
+    showDbRefsMenuitem.setSelected(av.isShowDBRefs());
     autoCalculate.setSelected(av.autoCalculateConsensus);
     sortByTree.setSelected(av.sortByTree);
     listenToViewSelections.setSelected(av.followSelection);
@@ -859,7 +1011,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         public void actionPerformed(ActionEvent e)
         {
           handler.cancelActivity(id);
-          us.setProgressBar(MessageManager.formatMessage("label.cancelled_params", new String[]{((JLabel) progressPanel.getComponent(0)).getText()}), id);
+          us.setProgressBar(MessageManager.formatMessage("label.cancelled_params", new Object[]{((JLabel) progressPanel.getComponent(0)).getText()}), id);
         }
       });
       progressPanel.add(cancel, BorderLayout.EAST);
@@ -1030,7 +1182,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     if (value == JalviewFileChooser.APPROVE_OPTION)
     {
       currentFileFormat = chooser.getSelectedFormat();
-      if (currentFileFormat == null)
+      while (currentFileFormat == null)
       {
         JOptionPane
                 .showInternalMessageDialog(
@@ -1040,8 +1192,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                         MessageManager
                                 .getString("label.file_format_not_specified"),
                         JOptionPane.WARNING_MESSAGE);
+        currentFileFormat = chooser.getSelectedFormat();
         value = chooser.showSaveDialog(this);
-        return;
+        if (value != JalviewFileChooser.APPROVE_OPTION)
+        {
+          return;
+        }
       }
 
       fileName = chooser.getSelectedFile().getPath();
@@ -1073,10 +1229,15 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                 .lastIndexOf(java.io.File.separatorChar) + 1);
       }
 
-      success = new Jalview2XML().SaveAlignment(this, file, shortName);
+      /*
+       * First save any linked Chimera session.
+       */
+      Desktop.instance.saveChimeraSessions(file);
+
+      success = new Jalview2XML().saveAlignment(this, file, shortName);
 
       statusBar.setText(MessageManager.formatMessage(
-              "label.successfully_saved_to_file_in_format", new String[]
+              "label.successfully_saved_to_file_in_format", new Object[]
               { fileName, format }));
 
     }
@@ -1133,7 +1294,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           this.setTitle(file);
           statusBar.setText(MessageManager.formatMessage(
                   "label.successfully_saved_to_file_in_format",
-                  new String[]
+                  new Object[]
                   { fileName, format }));
         } catch (Exception ex)
         {
@@ -1146,7 +1307,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     if (!success)
     {
       JOptionPane.showInternalMessageDialog(this, MessageManager
-              .formatMessage("label.couldnt_save_file", new String[]
+              .formatMessage("label.couldnt_save_file", new Object[]
               { fileName }), MessageManager
               .getString("label.error_saving_file"),
               JOptionPane.WARNING_MESSAGE);
@@ -1208,7 +1369,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
               viewport.getAlignment(), omitHidden,
               viewport.getColumnSelection()));
       Desktop.addInternalFrame(cap, MessageManager.formatMessage(
-              "label.alignment_output_command", new String[]
+              "label.alignment_output_command", new Object[]
               { e.getActionCommand() }), 600, 500);
     } catch (OutOfMemoryError oom)
     {
@@ -1230,9 +1391,15 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     // new HTMLOutput(alignPanel,
     // alignPanel.getSeqPanel().seqCanvas.getSequenceRenderer(),
     // alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer());
-    new HtmlSvgOutput(alignPanel);
+    new HtmlSvgOutput(null, alignPanel);
   }
 
+  @Override
+  public void bioJSMenuItem_actionPerformed(ActionEvent e)
+  {
+    new BioJsHTMLOutput(alignPanel,
+            alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer());
+  }
   public void createImageMap(File file, String image)
   {
     alignPanel.makePNGImageMap(file, image);
@@ -1296,11 +1463,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   public void exportAnnotations_actionPerformed(ActionEvent e)
   {
-    new AnnotationExporter().exportAnnotations(alignPanel,
-            viewport.isShowAnnotation() ? viewport.getAlignment()
-                    .getAlignmentAnnotation() : null, viewport
-                    .getAlignment().getGroups(), ((Alignment) viewport
-                    .getAlignment()).alignmentProperties);
+    new AnnotationExporter().exportAnnotations(alignPanel);
   }
 
   @Override
@@ -1352,7 +1515,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
             // setClosed(true) is called
             for (int i = 0; i < alignPanels.size(); i++)
             {
-              AlignmentPanel ap = (AlignmentPanel) alignPanels.elementAt(i);
+              AlignmentPanel ap = alignPanels.get(i);
               ap.closePanel();
             }
           }
@@ -1374,22 +1537,17 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   }
 
   /**
-   * close alignPanel2 and shuffle tabs appropriately.
+   * Close the specified panel and close up tabs appropriately.
    * 
-   * @param alignPanel2
+   * @param panelToClose
    */
-  public void closeView(AlignmentPanel alignPanel2)
+  public void closeView(AlignmentPanel panelToClose)
   {
     int index = tabbedPane.getSelectedIndex();
-    int closedindex = tabbedPane.indexOfComponent(alignPanel2);
-    alignPanels.removeElement(alignPanel2);
-    // Unnecessary
-    // if (viewport == alignPanel2.av)
-    // {
-    // viewport = null;
-    // }
-    alignPanel2.closePanel();
-    alignPanel2 = null;
+    int closedindex = tabbedPane.indexOfComponent(panelToClose);
+    alignPanels.remove(panelToClose);
+    panelToClose.closePanel();
+    panelToClose = null;
 
     tabbedPane.removeTabAt(closedindex);
     tabbedPane.validate();
@@ -1409,12 +1567,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   void updateEditMenuBar()
   {
 
-    if (viewport.historyList.size() > 0)
+    if (viewport.getHistoryList().size() > 0)
     {
       undoMenuItem.setEnabled(true);
-      CommandI command = viewport.historyList.peek();
+      CommandI command = viewport.getHistoryList().peek();
       undoMenuItem.setText(MessageManager.formatMessage(
-              "label.undo_command", new String[]
+              "label.undo_command", new Object[]
               { command.getDescription() }));
     }
     else
@@ -1423,13 +1581,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       undoMenuItem.setText(MessageManager.getString("action.undo"));
     }
 
-    if (viewport.redoList.size() > 0)
+    if (viewport.getRedoList().size() > 0)
     {
       redoMenuItem.setEnabled(true);
 
-      CommandI command = viewport.redoList.peek();
+      CommandI command = viewport.getRedoList().peek();
       redoMenuItem.setText(MessageManager.formatMessage(
-              "label.redo_command", new String[]
+              "label.redo_command", new Object[]
               { command.getDescription() }));
     }
     else
@@ -1443,8 +1601,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   {
     if (command.getSize() > 0)
     {
-      viewport.historyList.push(command);
-      viewport.redoList.clear();
+      viewport.addToHistoryList(command);
+      viewport.clearRedoList();
       updateEditMenuBar();
       viewport.updateHiddenColumns();
       // viewport.hasHiddenColumns = (viewport.getColumnSelection() != null
@@ -1462,11 +1620,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   {
     if (alignPanels != null)
     {
-      Enumeration e = alignPanels.elements();
       AlignmentI[] als = new AlignmentI[alignPanels.size()];
-      for (int i = 0; e.hasMoreElements(); i++)
+      int i = 0;
+      for (AlignmentPanel ap : alignPanels)
       {
-        als[i] = ((AlignmentPanel) e.nextElement()).av.getAlignment();
+        als[i++] = ap.av.getAlignment();
       }
       return als;
     }
@@ -1487,15 +1645,15 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   protected void undoMenuItem_actionPerformed(ActionEvent e)
   {
-    if (viewport.historyList.empty())
+    if (viewport.getHistoryList().isEmpty())
     {
       return;
     }
-    CommandI command = viewport.historyList.pop();
-    viewport.redoList.push(command);
+    CommandI command = viewport.getHistoryList().pop();
+    viewport.addToRedoList(command);
     command.undoCommand(getViewAlignments());
 
-    AlignViewport originalSource = getOriginatingSource(command);
+    AlignmentViewport originalSource = getOriginatingSource(command);
     updateEditMenuBar();
 
     if (originalSource != null)
@@ -1525,16 +1683,16 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   protected void redoMenuItem_actionPerformed(ActionEvent e)
   {
-    if (viewport.redoList.size() < 1)
+    if (viewport.getRedoList().size() < 1)
     {
       return;
     }
 
-    CommandI command = viewport.redoList.pop();
-    viewport.historyList.push(command);
+    CommandI command = viewport.getRedoList().pop();
+    viewport.addToHistoryList(command);
     command.doCommand(getViewAlignments());
 
-    AlignViewport originalSource = getOriginatingSource(command);
+    AlignmentViewport originalSource = getOriginatingSource(command);
     updateEditMenuBar();
 
     if (originalSource != null)
@@ -1556,9 +1714,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
   }
 
-  AlignViewport getOriginatingSource(CommandI command)
+  AlignmentViewport getOriginatingSource(CommandI command)
   {
-    AlignViewport originalSource = null;
+    AlignmentViewport originalSource = null;
     // For sequence removal and addition, we need to fire
     // the property change event FROM the viewport where the
     // original alignment was altered
@@ -1567,16 +1725,16 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     {
       EditCommand editCommand = (EditCommand) command;
       al = editCommand.getAlignment();
-      Vector comps = (Vector) PaintRefresher.components.get(viewport
+      List<Component> comps = PaintRefresher.components.get(viewport
               .getSequenceSetId());
 
-      for (int i = 0; i < comps.size(); i++)
+      for (Component comp : comps)
       {
-        if (comps.elementAt(i) instanceof AlignmentPanel)
+        if (comp instanceof AlignmentPanel)
         {
-          if (al == ((AlignmentPanel) comps.elementAt(i)).av.getAlignment())
+          if (al == ((AlignmentPanel) comp).av.getAlignment())
           {
-            originalSource = ((AlignmentPanel) comps.elementAt(i)).av;
+            originalSource = ((AlignmentPanel) comp).av;
             break;
           }
         }
@@ -1619,7 +1777,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
   synchronized void slideSequences(boolean right, int size)
   {
-    List<SequenceI> sg = new Vector();
+    List<SequenceI> sg = new ArrayList<SequenceI>();
     if (viewport.cursorMode)
     {
       sg.add(viewport.getAlignment().getSequenceAt(
@@ -1638,13 +1796,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       return;
     }
 
-    Vector invertGroup = new Vector();
+    List<SequenceI> invertGroup = new ArrayList<SequenceI>();
 
-    for (int i = 0; i < viewport.getAlignment().getHeight(); i++)
+    for (SequenceI seq : viewport.getAlignment().getSequences())
     {
-      if (!sg.contains(viewport.getAlignment().getSequenceAt(i)))
+      if (!sg.contains(seq))
       {
-        invertGroup.add(viewport.getAlignment().getSequenceAt(i));
+        invertGroup.add(seq);
       }
     }
 
@@ -1653,7 +1811,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     SequenceI[] seqs2 = new SequenceI[invertGroup.size()];
     for (int i = 0; i < invertGroup.size(); i++)
     {
-      seqs2[i] = (SequenceI) invertGroup.elementAt(i);
+      seqs2[i] = invertGroup.get(i);
     }
 
     SlideSequencesCommand ssc;
@@ -1701,11 +1859,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
 
     boolean appendHistoryItem = false;
-    if (viewport.historyList != null && viewport.historyList.size() > 0
-            && viewport.historyList.peek() instanceof SlideSequencesCommand)
+    Deque<CommandI> historyList = viewport.getHistoryList();
+    if (historyList != null
+            && historyList.size() > 0
+            && historyList.peek() instanceof SlideSequencesCommand)
     {
       appendHistoryItem = ssc
-              .appendSlideCommand((SlideSequencesCommand) viewport.historyList
+              .appendSlideCommand((SlideSequencesCommand) historyList
                       .peek());
     }
 
@@ -1762,20 +1922,17 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       return;
     }
 
-    Vector hiddenColumns = null;
+    ArrayList<int[]> hiddenColumns = null;
     if (viewport.hasHiddenColumns())
     {
-      hiddenColumns = new Vector();
+      hiddenColumns = new ArrayList<int[]>();
       int hiddenOffset = viewport.getSelectionGroup().getStartRes(), hiddenCutoff = viewport
               .getSelectionGroup().getEndRes();
-      for (int i = 0; i < viewport.getColumnSelection().getHiddenColumns()
-              .size(); i++)
+      for (int[] region : viewport.getColumnSelection().getHiddenColumns())
       {
-        int[] region = (int[]) viewport.getColumnSelection()
-                .getHiddenColumns().elementAt(i);
         if (region[0] >= hiddenOffset && region[1] <= hiddenCutoff)
         {
-          hiddenColumns.addElement(new int[]
+          hiddenColumns.add(new int[]
           { region[0] - hiddenOffset, region[1] - hiddenOffset });
         }
       }
@@ -1784,7 +1941,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     Desktop.jalviewClipboard = new Object[]
     { seqs, viewport.getAlignment().getDataset(), hiddenColumns };
     statusBar.setText(MessageManager.formatMessage(
-            "label.copied_sequences_to_clipboard", new String[]
+            "label.copied_sequences_to_clipboard", new Object[]
             { Integer.valueOf(seqs.length).toString() }));
   }
 
@@ -2082,7 +2239,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                 alignment.getSequences());
         if (alignPanels != null)
         {
-          for (AlignmentPanel ap : ((Vector<AlignmentPanel>) alignPanels))
+          for (AlignmentPanel ap : alignPanels)
           {
             ap.validateAnnotationDimensions(false);
           }
@@ -2102,10 +2259,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         if (Desktop.jalviewClipboard != null
                 && Desktop.jalviewClipboard[2] != null)
         {
-          Vector hc = (Vector) Desktop.jalviewClipboard[2];
-          for (int i = 0; i < hc.size(); i++)
+          List<int[]> hc = (List<int[]>) Desktop.jalviewClipboard[2];
+          for (int[] region : hc)
           {
-            int[] region = (int[]) hc.elementAt(i);
             af.viewport.hideColumns(region[0], region[1]);
           }
         }
@@ -2162,10 +2318,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       if (Desktop.jalviewClipboard != null
               && Desktop.jalviewClipboard[2] != null)
       {
-        Vector hc = (Vector) Desktop.jalviewClipboard[2];
-        for (int i = 0; i < hc.size(); i++)
+        List<int[]> hc = (List<int[]>) Desktop.jalviewClipboard[2];
+        for (int region[] : hc)
         {
-          int[] region = (int[]) hc.elementAt(i);
           af.viewport.hideColumns(region[0], region[1]);
         }
       }
@@ -2506,7 +2661,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     addHistoryItem(removeGapCols);
 
     statusBar.setText(MessageManager.formatMessage(
-            "label.removed_empty_columns", new String[]
+            "label.removed_empty_columns", new Object[]
             { Integer.valueOf(removeGapCols.getSize()).toString() }));
 
     // This is to maintain viewport position on first residue
@@ -2577,16 +2732,6 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
             .getSequences());
   }
 
-  // else
-  {
-    // if (justifySeqs>0)
-    {
-      // alignment.justify(justifySeqs!=RIGHT_JUSTIFY);
-    }
-  }
-
-  // }
-
   /**
    * DOCUMENT ME!
    * 
@@ -2599,74 +2744,80 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     new Finder();
   }
 
-  @Override
-  public void newView_actionPerformed(ActionEvent e)
-  {
-    newView(true);
-  }
-
   /**
-   * 
-   * @param copyAnnotation
-   *          if true then duplicate all annnotation, groups and settings
-   * @return new alignment panel, already displayed.
-   */
-  public AlignmentPanel newView(boolean copyAnnotation)
-  {
-    return newView(null, copyAnnotation);
-  }
-
-  /**
-   * 
-   * @param viewTitle
-   *          title of newly created view
-   * @return new alignment panel, already displayed.
+   * Create a new view of the current alignment.
    */
-  public AlignmentPanel newView(String viewTitle)
+  @Override
+  public void newView_actionPerformed(ActionEvent e)
   {
-    return newView(viewTitle, true);
+    newView(null, true);
   }
 
   /**
+   * Creates and shows a new view of the current alignment.
    * 
    * @param viewTitle
-   *          title of newly created view
+   *          title of newly created view; if null, one will be generated
    * @param copyAnnotation
    *          if true then duplicate all annnotation, groups and settings
    * @return new alignment panel, already displayed.
    */
   public AlignmentPanel newView(String viewTitle, boolean copyAnnotation)
   {
+    /*
+     * Create a new AlignmentPanel (with its own, new Viewport)
+     */
     AlignmentPanel newap = new Jalview2XML().copyAlignPanel(alignPanel,
             true);
     if (!copyAnnotation)
     {
-      // just remove all the current annotation except for the automatic stuff
+      /*
+       * remove all groups and annotation except for the automatic stuff
+       */
       newap.av.getAlignment().deleteAllGroups();
-      for (AlignmentAnnotation alan : newap.av.getAlignment()
-              .getAlignmentAnnotation())
-      {
-        if (!alan.autoCalculated)
-        {
-          newap.av.getAlignment().deleteAnnotation(alan);
-        }
-        ;
-      }
+      newap.av.getAlignment().deleteAllAnnotations(false);
     }
 
-    newap.av.gatherViewsHere = false;
+    newap.av.setGatherViewsHere(false);
 
     if (viewport.viewName == null)
     {
-      viewport.viewName = "Original";
+      viewport.viewName = MessageManager
+              .getString("label.view_name_original");
     }
 
-    newap.av.historyList = viewport.historyList;
-    newap.av.redoList = viewport.redoList;
+    /*
+     * Views share the same edits, undo and redo stacks, mappings.
+     */
+    newap.av.setHistoryList(viewport.getHistoryList());
+    newap.av.setRedoList(viewport.getRedoList());
+    newap.av.getAlignment().setCodonFrames(
+            viewport.getAlignment().getCodonFrames());
 
+    newap.av.viewName = getNewViewName(viewTitle);
+
+    addAlignmentPanel(newap, true);
+    newap.alignmentChanged();
+
+    if (alignPanels.size() == 2)
+    {
+      viewport.setGatherViewsHere(true);
+    }
+    tabbedPane.setSelectedIndex(tabbedPane.getTabCount() - 1);
+    return newap;
+  }
+
+  /**
+   * Make a new name for the view, ensuring it is unique within the current
+   * sequenceSetId. (This used to be essential for Jalview Project archives, but
+   * these now use viewId. Unique view names are still desirable for usability.)
+   * 
+   * @param viewTitle
+   * @return
+   */
+  protected String getNewViewName(String viewTitle)
+  {
     int index = Desktop.getViewCount(viewport.getSequenceSetId());
-    // make sure the new view has a unique name - this is essential for Jalview
-    // 2 archives
     boolean addFirstIndex = false;
     if (viewTitle == null || viewTitle.trim().length() == 0)
     {
@@ -2678,45 +2829,55 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       index = 1;// we count from 1 if given a specific name
     }
     String newViewName = viewTitle + ((addFirstIndex) ? " " + index : "");
-    Vector comps = (Vector) PaintRefresher.components.get(viewport
+
+    List<Component> comps = PaintRefresher.components.get(viewport
             .getSequenceSetId());
-    Vector existingNames = new Vector();
-    for (int i = 0; i < comps.size(); i++)
-    {
-      if (comps.elementAt(i) instanceof AlignmentPanel)
-      {
-        AlignmentPanel ap = (AlignmentPanel) comps.elementAt(i);
-        if (!existingNames.contains(ap.av.viewName))
-        {
-          existingNames.addElement(ap.av.viewName);
-        }
-      }
-    }
+
+    List<String> existingNames = getExistingViewNames(comps);
 
     while (existingNames.contains(newViewName))
     {
       newViewName = viewTitle + " " + (++index);
     }
+    return newViewName;
+  }
 
-    newap.av.viewName = newViewName;
-
-    addAlignmentPanel(newap, true);
-    newap.alignmentChanged();
-
-    if (alignPanels.size() == 2)
+  /**
+   * Returns a list of distinct view names found in the given list of
+   * components. View names are held on the viewport of an AlignmentPanel.
+   * 
+   * @param comps
+   * @return
+   */
+  protected List<String> getExistingViewNames(List<Component> comps)
+  {
+    List<String> existingNames = new ArrayList<String>();
+    for (Component comp : comps)
     {
-      viewport.gatherViewsHere = true;
+      if (comp instanceof AlignmentPanel)
+      {
+        AlignmentPanel ap = (AlignmentPanel) comp;
+        if (!existingNames.contains(ap.av.viewName))
+        {
+          existingNames.add(ap.av.viewName);
+        }
+      }
     }
-    tabbedPane.setSelectedIndex(tabbedPane.getTabCount() - 1);
-    return newap;
+    return existingNames;
   }
 
+  /**
+   * Explode tabbed views into separate windows.
+   */
   @Override
   public void expandViews_actionPerformed(ActionEvent e)
   {
     Desktop.instance.explodeViews(this);
   }
 
+  /**
+   * Gather views in separate windows back into a tabbed presentation.
+   */
   @Override
   public void gatherViews_actionPerformed(ActionEvent e)
   {
@@ -2761,7 +2922,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   public void centreColumnLabels_actionPerformed(ActionEvent e)
   {
-    viewport.centreColumnLabels = centreColumnLabelsMenuItem.getState();
+    viewport.setCentreColumnLabels(centreColumnLabelsMenuItem.getState());
     alignPanel.paintAlignment(true);
   }
 
@@ -2806,7 +2967,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     scaleLeft.setVisible(wrapMenuItem.isSelected());
     scaleRight.setVisible(wrapMenuItem.isSelected());
     viewport.setWrapAlignment(wrapMenuItem.isSelected());
-    alignPanel.setWrapAlignment(wrapMenuItem.isSelected());
+    alignPanel.updateLayout();
   }
 
   @Override
@@ -3083,7 +3244,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   {
     viewport.setShowSequenceFeaturesHeight(showSeqFeaturesHeight
             .isSelected());
-    if (viewport.getShowSequenceFeaturesHeight())
+    if (viewport.isShowSequenceFeaturesHeight())
     {
       // ensure we're actually displaying features
       viewport.setShowSequenceFeatures(true);
@@ -3110,11 +3271,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   {
     final boolean setVisible = annotationPanelMenuItem.isSelected();
     viewport.setShowAnnotation(setVisible);
-    alignPanel.setAnnotationVisible(setVisible);
     this.showAllSeqAnnotations.setEnabled(setVisible);
     this.hideAllSeqAnnotations.setEnabled(setVisible);
     this.showAllAlAnnotations.setEnabled(setVisible);
     this.hideAllAlAnnotations.setEnabled(setVisible);
+    alignPanel.updateLayout();
   }
 
   @Override
@@ -3125,13 +3286,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     StringBuffer contents = new AlignmentProperties(viewport.getAlignment())
             .formatAsHtml();
     editPane.setText(MessageManager.formatMessage("label.html_content",
-            new String[]
+            new Object[]
             { contents.toString() }));
     JInternalFrame frame = new JInternalFrame();
     frame.getContentPane().add(new JScrollPane(editPane));
 
-    Desktop.instance.addInternalFrame(frame, MessageManager.formatMessage(
-            "label.alignment_properties", new String[]
+    Desktop.addInternalFrame(frame, MessageManager.formatMessage(
+            "label.alignment_properties", new Object[]
             { getTitle() }), 500, 400);
   }
 
@@ -3153,7 +3314,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     OverviewPanel overview = new OverviewPanel(alignPanel);
     frame.setContentPane(overview);
     Desktop.addInternalFrame(frame, MessageManager.formatMessage(
-            "label.overview_params", new String[]
+            "label.overview_params", new Object[]
             { this.getTitle() }), frame.getWidth(), frame.getHeight());
     frame.pack();
     frame.setLayer(JLayeredPane.PALETTE_LAYER);
@@ -3316,6 +3477,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   }
 
   @Override
+  public void annotationColumn_actionPerformed(ActionEvent e)
+  {
+    new AnnotationColumnChooser(viewport, alignPanel);
+  }
+
+  @Override
   public void rnahelicesColour_actionPerformed(ActionEvent e)
   {
     new RNAHelicesColourChooser(viewport, alignPanel);
@@ -3350,11 +3517,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       {
         threshold = SliderPanel.setPIDSliderSource(alignPanel, cs,
                 "Background");
-        cs.setThreshold(threshold, viewport.getIgnoreGapsConsensus());
+        cs.setThreshold(threshold, viewport.isIgnoreGapsConsensus());
       }
       else
       {
-        cs.setThreshold(0, viewport.getIgnoreGapsConsensus());
+        cs.setThreshold(0, viewport.isIgnoreGapsConsensus());
       }
 
       if (viewport.getConservationSelected())
@@ -3417,7 +3584,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                 || cs instanceof PIDColourScheme
                 || cs instanceof Blosum62ColourScheme)
         {
-          sg.cs.setThreshold(threshold, viewport.getIgnoreGapsConsensus());
+          sg.cs.setThreshold(threshold, viewport.isIgnoreGapsConsensus());
 
           sg.cs.setConsensus(AAFrequency.calculate(
                   sg.getSequences(viewport.getHiddenRepSequences()),
@@ -3425,7 +3592,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         }
         else
         {
-          sg.cs.setThreshold(0, viewport.getIgnoreGapsConsensus());
+          sg.cs.setThreshold(0, viewport.isIgnoreGapsConsensus());
         }
 
         if (viewport.getConservationSelected())
@@ -3554,8 +3721,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   {
 
     Component[] menuItems = colourMenu.getMenuComponents();
-    int i, iSize = menuItems.length;
-    for (i = 0; i < iSize; i++)
+    int iSize = menuItems.length;
+    for (int i = 0; i < iSize; i++)
     {
       if (menuItems[i].getName() != null
               && menuItems[i].getName().equals("USER_DEFINED"))
@@ -3816,7 +3983,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   public void averageDistanceTreeMenuItem_actionPerformed(ActionEvent e)
   {
-    NewTreePanel("AV", "PID", "Average distance tree using PID");
+    newTreePanel("AV", "PID", "Average distance tree using PID");
   }
 
   /**
@@ -3828,7 +3995,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   public void neighbourTreeMenuItem_actionPerformed(ActionEvent e)
   {
-    NewTreePanel("NJ", "PID", "Neighbour joining tree using PID");
+    newTreePanel("NJ", "PID", "Neighbour joining tree using PID");
   }
 
   /**
@@ -3840,7 +4007,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   protected void njTreeBlosumMenuItem_actionPerformed(ActionEvent e)
   {
-    NewTreePanel("NJ", "BL", "Neighbour joining tree using BLOSUM62");
+    newTreePanel("NJ", "BL", "Neighbour joining tree using BLOSUM62");
   }
 
   /**
@@ -3852,7 +4019,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   protected void avTreeBlosumMenuItem_actionPerformed(ActionEvent e)
   {
-    NewTreePanel("AV", "BL", "Average distance tree using BLOSUM62");
+    newTreePanel("AV", "BL", "Average distance tree using BLOSUM62");
   }
 
   /**
@@ -3865,7 +4032,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param title
    *          DOCUMENT ME!
    */
-  void NewTreePanel(String type, String pwType, String title)
+  void newTreePanel(String type, String pwType, String title)
   {
     TreePanel tp;
 
@@ -3956,7 +4123,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   public void addSortByOrderMenuItem(String title,
           final AlignmentOrder order)
   {
-    final JMenuItem item = new JMenuItem(MessageManager.formatMessage("action.by_title_param", new String[]{title}));
+    final JMenuItem item = new JMenuItem(MessageManager.formatMessage("action.by_title_param", new Object[]{title}));
     sort.add(item);
     item.addActionListener(new java.awt.event.ActionListener()
     {
@@ -4080,7 +4247,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     {
       String treecalcnm = MessageManager.getString("label.tree_calc_"
               + type.toLowerCase());
-      for (final Object pwtype : ResidueProperties.scoreMatrices.keySet())
+      for (final String pwtype : ResidueProperties.scoreMatrices.keySet())
       {
         JMenuItem tm = new JMenuItem();
         ScoreModelI sm = ResidueProperties.scoreMatrices.get(pwtype);
@@ -4096,7 +4263,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
             @Override
             public void actionPerformed(ActionEvent e)
             {
-              NewTreePanel(type, (String) pwtype, title);
+              newTreePanel(type, pwtype, title);
             }
           });
           calculateTree.add(tm);
@@ -4106,21 +4273,18 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
     sortByTreeMenu.removeAll();
 
-    Vector comps = (Vector) PaintRefresher.components.get(viewport
+    List<Component> comps = PaintRefresher.components.get(viewport
             .getSequenceSetId());
-    Vector treePanels = new Vector();
-    int i, iSize = comps.size();
-    for (i = 0; i < iSize; i++)
+    List<TreePanel> treePanels = new ArrayList<TreePanel>();
+    for (Component comp : comps)
     {
-      if (comps.elementAt(i) instanceof TreePanel)
+      if (comp instanceof TreePanel)
       {
-        treePanels.add(comps.elementAt(i));
+        treePanels.add((TreePanel) comp);
       }
     }
 
-    iSize = treePanels.size();
-
-    if (iSize < 1)
+    if (treePanels.size() < 1)
     {
       sortByTreeMenu.setVisible(false);
       return;
@@ -4128,17 +4292,15 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
     sortByTreeMenu.setVisible(true);
 
-    for (i = 0; i < treePanels.size(); i++)
+    for (final TreePanel tp : treePanels)
     {
-      final TreePanel tp = (TreePanel) treePanels.elementAt(i);
       final JMenuItem item = new JMenuItem(tp.getTitle());
-      final NJTree tree = ((TreePanel) treePanels.elementAt(i)).getTree();
       item.addActionListener(new java.awt.event.ActionListener()
       {
         @Override
         public void actionPerformed(ActionEvent e)
         {
-          tp.sortByTree_actionPerformed(null);
+          tp.sortByTree_actionPerformed();
           addHistoryItem(tp.sortAlignmentIn(alignPanel));
 
         }
@@ -4188,28 +4350,17 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     else if (viewport.getSelectionGroup() != null
             && viewport.getSelectionGroup().getSize() == 1)
     {
-      int option = JOptionPane
-              .showConfirmDialog(
-this,
-                      "More than one sequece group selection is required for this Job, click \n'Cancel' to edit your selection or 'Ok' to submit the entire sequence.",
-                      "Invalid selection",
-                      JOptionPane.OK_CANCEL_OPTION);
+      int option = JOptionPane.showConfirmDialog(this,
+              MessageManager.getString("warn.oneseq_msainput_selection"),
+              MessageManager.getString("label.invalid_selection"),
+              JOptionPane.OK_CANCEL_OPTION);
       if (option == JOptionPane.OK_OPTION)
       {
         msa = viewport.getAlignmentView(false);
       }
-
     }
     else
     {
-      /*
-       * Vector seqs = viewport.getAlignment().getSequences();
-       * 
-       * if (seqs.size() > 1) { msa = new SequenceI[seqs.size()];
-       * 
-       * for (int i = 0; i < seqs.size(); i++) { msa[i] = (SequenceI)
-       * seqs.elementAt(i); } }
-       */
       msa = viewport.getAlignmentView(false);
     }
     return msa;
@@ -4389,7 +4540,6 @@ this,
       } catch (Exception e)
       {
       }
-      ;
     }
     final AlignFrame me = this;
     buildingMenu = true;
@@ -4544,14 +4694,11 @@ this,
                         .debug("Exception during web service menu building process.",
                                 e);
               }
-              ;
             }
           });
         } catch (Exception e)
         {
         }
-        ;
-
         buildingMenu = false;
       }
     }).start();
@@ -4697,7 +4844,7 @@ this,
       public void run()
       {
         final long sttime = System.currentTimeMillis();
-        ths.setProgressBar(MessageManager.formatMessage("status.searching_for_sequences_from", new String[]{fsrc}), sttime);
+        ths.setProgressBar(MessageManager.formatMessage("status.searching_for_sequences_from", new Object[]{fsrc}), sttime);
         try
         {
           Alignment ds = ths.getViewport().getAlignment().getDataset(); // update
@@ -4721,12 +4868,11 @@ this,
               sprods[s].updatePDBIds();
             }
             Alignment al = new Alignment(sprods);
-            AlignedCodonFrame[] cf = prods.getCodonFrames();
+            Set<AlignedCodonFrame> cf = prods.getCodonFrames();
             al.setDataset(ds);
-            for (int s = 0; cf != null && s < cf.length; s++)
+            for (AlignedCodonFrame acf : cf)
             {
-              al.addCodonFrame(cf[s]);
-              cf[s] = null;
+              al.addCodonFrame(acf);
             }
             AlignFrame naf = new AlignFrame(al, DEFAULT_WIDTH,
                     DEFAULT_HEIGHT);
@@ -4753,7 +4899,7 @@ this,
           jalview.bin.Cache.log.error("Error when finding crossreferences",
                   e);
         }
-        ths.setProgressBar(MessageManager.formatMessage("status.finished_searching_for_sequences_from", new String[]{fsrc}),
+        ths.setProgressBar(MessageManager.formatMessage("status.finished_searching_for_sequences_from", new Object[]{fsrc}),
                 sttime);
       }
 
@@ -4779,92 +4925,49 @@ this,
     }
   }
 
-  @Override
-  public void showProducts_actionPerformed(ActionEvent e)
-  {
-    // /////////////////////////////
-    // Collect Data to be translated/transferred
-
-    SequenceI[] selection = viewport.getSequenceSelection();
-    AlignmentI al = null;
-    try
-    {
-      al = jalview.analysis.Dna.CdnaTranslate(selection, viewport
-              .getViewAsVisibleContigs(true), viewport.getGapCharacter(),
-              viewport.getAlignment().getDataset());
-    } catch (Exception ex)
-    {
-      al = null;
-      jalview.bin.Cache.log.debug("Exception during translation.", ex);
-    }
-    if (al == null)
-    {
-      JOptionPane
-              .showMessageDialog(
-                      Desktop.desktop,
-                      MessageManager
-                              .getString("label.select_at_least_three_bases_in_at_least_one_sequence_to_cDNA_translation"),
-                      MessageManager.getString("label.translation_failed"),
-                      JOptionPane.WARNING_MESSAGE);
-    }
-    else
-    {
-      AlignFrame af = new AlignFrame(al, DEFAULT_WIDTH, DEFAULT_HEIGHT);
-      Desktop.addInternalFrame(af, MessageManager.formatMessage(
-              "label.translation_of_params", new String[]
-              { this.getTitle() }), DEFAULT_WIDTH, DEFAULT_HEIGHT);
-    }
-  }
-
+  /**
+   * Construct and display a new frame containing the translation of this
+   * frame's cDNA sequences to their aligned protein (amino acid) equivalents.
+   */
   @Override
   public void showTranslation_actionPerformed(ActionEvent e)
   {
-    // /////////////////////////////
-    // Collect Data to be translated/transferred
-
-    SequenceI[] selection = viewport.getSequenceSelection();
-    String[] seqstring = viewport.getViewAsString(true);
     AlignmentI al = null;
     try
     {
-      al = jalview.analysis.Dna.CdnaTranslate(selection, seqstring,
-              viewport.getViewAsVisibleContigs(true), viewport
-                      .getGapCharacter(), viewport.getAlignment()
-                      .getAlignmentAnnotation(), viewport.getAlignment()
-                      .getWidth(), viewport.getAlignment().getDataset());
+      Dna dna = new Dna(viewport, viewport.getViewAsVisibleContigs(true));
+
+      al = dna.translateCdna();
     } catch (Exception ex)
     {
-      al = null;
       jalview.bin.Cache.log.error(
               "Exception during translation. Please report this !", ex);
-      JOptionPane
-              .showMessageDialog(
-                      Desktop.desktop,
-                      MessageManager
-                              .getString("label.error_when_translating_sequences_submit_bug_report"),
-                      MessageManager
-                              .getString("label.implementation_error")
-                              + MessageManager
-                                      .getString("translation_failed"),
-                      JOptionPane.ERROR_MESSAGE);
+      final String msg = MessageManager
+              .getString("label.error_when_translating_sequences_submit_bug_report");
+      final String title = MessageManager
+              .getString("label.implementation_error")
+              + MessageManager.getString("translation_failed");
+      JOptionPane.showMessageDialog(Desktop.desktop, msg, title,
+              JOptionPane.ERROR_MESSAGE);
       return;
     }
-    if (al == null)
+    if (al == null || al.getHeight() == 0)
     {
-      JOptionPane
-              .showMessageDialog(
-                      Desktop.desktop,
-                      MessageManager
-                              .getString("label.select_at_least_three_bases_in_at_least_one_sequence_to_cDNA_translation"),
-                      MessageManager.getString("label.translation_failed"),
-                      JOptionPane.WARNING_MESSAGE);
+      final String msg = MessageManager
+              .getString("label.select_at_least_three_bases_in_at_least_one_sequence_to_cDNA_translation");
+      final String title = MessageManager
+              .getString("label.translation_failed");
+      JOptionPane.showMessageDialog(Desktop.desktop, msg, title,
+              JOptionPane.WARNING_MESSAGE);
     }
     else
     {
       AlignFrame af = new AlignFrame(al, DEFAULT_WIDTH, DEFAULT_HEIGHT);
       Desktop.addInternalFrame(af, MessageManager.formatMessage(
-              "label.translation_of_params", new String[]
+              "label.translation_of_params", new Object[]
               { this.getTitle() }), DEFAULT_WIDTH, DEFAULT_HEIGHT);
+      // enable next line for linked editing
+      // viewport.getStructureSelectionManager().addCommandListener(viewport);
     }
   }
 
@@ -4884,7 +4987,7 @@ this,
     {
       featuresFile = new FeaturesFile(file, type).parse(viewport
               .getAlignment().getDataset(), alignPanel.getSeqPanel().seqCanvas
-              .getFeatureRenderer().featureColours, false,
+              .getFeatureRenderer().getFeatureColours(), false,
               jalview.bin.Cache.getDefault("RELAXEDSEQIDMATCHING", false));
     } catch (Exception ex)
     {
@@ -4893,7 +4996,7 @@ this,
 
     if (featuresFile)
     {
-      viewport.showSequenceFeatures = true;
+      viewport.setShowSequenceFeatures(true);
       showSeqFeatures.setSelected(true);
       if (alignPanel.getSeqPanel().seqCanvas.fr != null)
       {
@@ -5060,7 +5163,7 @@ this,
                                   MessageManager
                                           .formatMessage(
                                                   "label.automatically_associate_pdb_files_with_sequences_same_name",
-                                                  new String[]
+                                                  new Object[]
                                                   { Integer.valueOf(
                                                           filesmatched
                                                                   .size())
@@ -5103,7 +5206,7 @@ this,
                                   "<html>"+MessageManager
                                           .formatMessage(
                                                   "label.ignore_unmatched_dropped_files_info",
-                                                  new String[]
+                                                  new Object[]
                                                   { Integer.valueOf(
                                                           filesnotmatched
                                                                   .size())
@@ -5150,7 +5253,7 @@ this,
       // try to parse as annotation.
       boolean isAnnotation = (format == null || format
               .equalsIgnoreCase("PFAM")) ? new AnnotationFile()
-              .readAnnotationFile(viewport.getAlignment(), file, protocol)
+              .annotateAlignmentView(viewport, file, protocol)
               : false;
 
       if (!isAnnotation)
@@ -5211,7 +5314,8 @@ this,
           {
             jalview.io.JPredFile predictions = new jalview.io.JPredFile(
                     file, protocol);
-            new JnetAnnotationMaker().add_annotation(predictions,
+            new JnetAnnotationMaker();
+            JnetAnnotationMaker.add_annotation(predictions,
                     viewport.getAlignment(), 0, false);
             isAnnotation = true;
           }
@@ -5283,31 +5387,55 @@ this,
     }
   }
 
+  /**
+   * Method invoked by the ChangeListener on the tabbed pane, in other words
+   * when a different tabbed pane is selected by the user or programmatically.
+   */
   @Override
   public void tabSelectionChanged(int index)
   {
     if (index > -1)
     {
-      alignPanel = (AlignmentPanel) alignPanels.elementAt(index);
+      alignPanel = alignPanels.get(index);
       viewport = alignPanel.av;
       avc.setViewportAndAlignmentPanel(viewport, alignPanel);
       setMenusFromViewport(viewport);
     }
+
+    /*
+     * If there is a frame linked to this one in a SplitPane, switch it to the
+     * same view tab index. No infinite recursion of calls should happen, since
+     * tabSelectionChanged() should not get invoked on setting the selected
+     * index to an unchanged value. Guard against setting an invalid index
+     * before the new view peer tab has been created.
+     */
+    final AlignViewportI peer = viewport.getCodingComplement();
+    if (peer != null)
+    {
+      AlignFrame linkedAlignFrame = ((AlignViewport) peer).getAlignPanel().alignFrame;
+      if (linkedAlignFrame.tabbedPane.getTabCount() > index)
+      {
+        linkedAlignFrame.tabbedPane.setSelectedIndex(index);
+      }
+    }
   }
 
+  /**
+   * On right mouse click on view tab, prompt for and set new view name.
+   */
   @Override
   public void tabbedPane_mousePressed(MouseEvent e)
   {
     if (SwingUtilities.isRightMouseButton(e))
     {
-      String reply = JOptionPane.showInternalInputDialog(this,
-              MessageManager.getString("label.enter_view_name"),
-              MessageManager.getString("label.enter_view_name"),
+      String msg = MessageManager.getString("label.enter_view_name");
+      String reply = JOptionPane.showInternalInputDialog(this, msg, msg,
               JOptionPane.QUESTION_MESSAGE);
 
       if (reply != null)
       {
         viewport.viewName = reply;
+        // TODO warn if reply is in getExistingViewNames()?
         tabbedPane.setTitleAt(tabbedPane.getSelectedIndex(), reply);
       }
     }
@@ -5347,7 +5475,7 @@ this,
   @Override
   protected void showDbRefs_actionPerformed(ActionEvent e)
   {
-    viewport.setShowDbRefs(showDbRefsMenuitem.isSelected());
+    viewport.setShowDBRefs(showDbRefsMenuitem.isSelected());
   }
 
   /*
@@ -5359,7 +5487,7 @@ this,
   @Override
   protected void showNpFeats_actionPerformed(ActionEvent e)
   {
-    viewport.setShowNpFeats(showNpFeatsMenuitem.isSelected());
+    viewport.setShowNPFeats(showNpFeatsMenuitem.isSelected());
   }
 
   /**
@@ -5368,7 +5496,7 @@ this,
    * 
    * @param av
    */
-  public boolean closeView(AlignViewport av)
+  public boolean closeView(AlignViewportI av)
   {
     if (viewport == av)
     {
@@ -5514,7 +5642,7 @@ this,
                   }
 
                 });
-                fetchr.setToolTipText(JvSwingUtils.wrapTooltip(true, MessageManager.formatMessage("label.fetch_retrieve_from", new String[]{src.getDbName()})));
+                fetchr.setToolTipText(JvSwingUtils.wrapTooltip(true, MessageManager.formatMessage("label.fetch_retrieve_from", new Object[]{src.getDbName()})));
                 dfetch.add(fetchr);
                 comp++;
               }
@@ -5525,7 +5653,7 @@ this,
                 // fetch all entry
                 DbSourceProxy src = otherdb.get(0);
                 fetchr = new JMenuItem(MessageManager.formatMessage(
-                        "label.fetch_all_param", new String[]
+                        "label.fetch_all_param", new Object[]
                         { src.getDbSource() }));
                 fetchr.addActionListener(new ActionListener()
                 {
@@ -5547,11 +5675,11 @@ this,
                   }
                 });
 
-                fetchr.setToolTipText(JvSwingUtils.wrapTooltip(true, MessageManager.formatMessage("label.fetch_retrieve_from_all_sources", new String[]{Integer.valueOf(otherdb.size()).toString(), src.getDbSource(), src.getDbName()})));
+                fetchr.setToolTipText(JvSwingUtils.wrapTooltip(true, MessageManager.formatMessage("label.fetch_retrieve_from_all_sources", new Object[]{Integer.valueOf(otherdb.size()).toString(), src.getDbSource(), src.getDbName()})));
                 dfetch.add(fetchr);
                 comp++;
                 // and then build the rest of the individual menus
-                ifetch = new JMenu(MessageManager.formatMessage("label.source_from_db_source", new String[]{src.getDbSource()}));
+                ifetch = new JMenu(MessageManager.formatMessage("label.source_from_db_source", new Object[]{src.getDbSource()}));
                 icomp = 0;
                 String imname = null;
                 int i = 0;
@@ -5564,7 +5692,7 @@ this,
                           0, 10) + "..." : dbname;
                   if (imname == null)
                   {
-                    imname = MessageManager.formatMessage("label.from_msname", new String[]{sname});
+                    imname = MessageManager.formatMessage("label.from_msname", new Object[]{sname});
                   }
                   fetchr = new JMenuItem(msname);
                   final DbSourceProxy[] dassrc =
@@ -5591,7 +5719,7 @@ this,
 
                   });
                   fetchr.setToolTipText("<html>"
-                          + MessageManager.formatMessage("label.fetch_retrieve_from", new String[]{dbname}));
+                          + MessageManager.formatMessage("label.fetch_retrieve_from", new Object[]{dbname}));
                   ifetch.add(fetchr);
                   ++i;
                   if (++icomp >= mcomp || i == (otherdb.size()))
@@ -5755,6 +5883,16 @@ this,
       alignPanel.paintAlignment(true);
     }
   }
+  public void clearAlignmentSeqRep()
+  {
+    // TODO refactor alignmentseqrep to controller
+    if (viewport.getAlignment().hasSeqrep()) {
+      viewport.getAlignment().setSeqrep(null);
+      PaintRefresher.Refresh(this, viewport.getSequenceSetId());
+      alignPanel.updateAnnotation();
+      alignPanel.paintAlignment(true);
+    }
+  }
 
   @Override
   protected void createGroup_actionPerformed(ActionEvent e)
@@ -5787,7 +5925,8 @@ this,
       throw new Error(MessageManager.getString("error.implementation_error_cannot_show_view_alignment_frame"));
     }
     if (tabbedPane != null
-            & alignPanels.indexOf(alignmentPanel) != tabbedPane
+            && tabbedPane.getTabCount() > 0
+            && alignPanels.indexOf(alignmentPanel) != tabbedPane
                     .getSelectedIndex())
     {
       tabbedPane.setSelectedIndex(alignPanels.indexOf(alignmentPanel));
@@ -5832,6 +5971,64 @@ this,
             .setShowAutocalculatedAbove(isShowAutoCalculatedAbove());
     alignPanel.paintAlignment(true);
   }
+
+  /**
+   * 
+   * @return alignment panels in this alignment frame
+   */
+  public List<? extends AlignmentViewPanel> getAlignPanels()
+  {
+    return alignPanels == null ? Arrays.asList(alignPanel)
+            : alignPanels;
+  }
+
+  /**
+   * Open a new alignment window, with the cDNA associated with this (protein)
+   * alignment, aligned as is the protein.
+   */
+  @Override
+  protected void viewAsCdna_actionPerformed()
+  {
+    final AlignmentI alignment = getViewport().getAlignment();
+    Set<AlignedCodonFrame> mappings = alignment.getCodonFrames();
+    if (mappings == null)
+    {
+      return;
+    }
+    List<SequenceI> cdnaSeqs = new ArrayList<SequenceI>();
+    for (SequenceI aaSeq : alignment.getSequences()) {
+      for (AlignedCodonFrame acf : mappings) {
+        SequenceI dnaSeq = acf.getDnaForAaSeq(aaSeq.getDatasetSequence());
+        if (dnaSeq != null)
+        {
+          /*
+           * There is a cDNA mapping for this protein sequence - add to new
+           * alignment. It will share the same dataset sequence as other mapped
+           * cDNA (no new mappings need to be created).
+           */
+          final Sequence newSeq = new Sequence(dnaSeq);
+          newSeq.setDatasetSequence(dnaSeq);
+          cdnaSeqs.add(newSeq);
+        }
+      }
+    }
+    if (cdnaSeqs.size() == 0)
+    {
+      // show a warning dialog no mapped cDNA
+      return;
+    }
+    AlignmentI cdna = new Alignment(cdnaSeqs.toArray(new SequenceI[cdnaSeqs
+            .size()]));
+    AlignFrame alignFrame = new AlignFrame(cdna, AlignFrame.DEFAULT_WIDTH,
+            AlignFrame.DEFAULT_HEIGHT);
+    cdna.alignAs(alignment);
+    String newtitle = "cDNA " + MessageManager.getString("label.for") + " "
+            + this.title;
+    Desktop.addInternalFrame(alignFrame, newtitle,
+            AlignFrame.DEFAULT_WIDTH,
+            AlignFrame.DEFAULT_HEIGHT);
+
+  }
 }
 
 class PrintThread extends Thread