JAL-3490 pull up getViewAsVisibleContigs to base class and interface
[jalview.git] / src / jalview / viewmodel / AlignmentViewport.java
index 7054ed3..2c430db 100644 (file)
  */
 package jalview.viewmodel;
 
-import java.awt.Color;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.Deque;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
 import jalview.analysis.Conservation;
+import jalview.analysis.TreeModel;
 import jalview.api.AlignCalcManagerI;
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
@@ -44,25 +34,43 @@ import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentView;
 import jalview.datamodel.Annotation;
-import jalview.datamodel.CigarArray;
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.HiddenSequences;
+import jalview.datamodel.ProfilesI;
+import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceCollectionI;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
-import jalview.schemes.Blosum62ColourScheme;
+import jalview.renderer.ResidueShader;
+import jalview.renderer.ResidueShaderI;
 import jalview.schemes.ColourSchemeI;
-import jalview.schemes.PIDColourScheme;
-import jalview.schemes.ResidueProperties;
 import jalview.structure.CommandListener;
 import jalview.structure.StructureSelectionManager;
 import jalview.structure.VamsasSource;
+import jalview.util.Comparison;
+import jalview.util.MapList;
+import jalview.util.MappingUtils;
+import jalview.util.MessageManager;
 import jalview.viewmodel.styles.ViewStyle;
 import jalview.workers.AlignCalcManager;
 import jalview.workers.ComplementConsensusThread;
 import jalview.workers.ConsensusThread;
 import jalview.workers.StrucConsensusThread;
 
+import java.awt.Color;
+import java.beans.PropertyChangeSupport;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
 /**
  * base class holding visualization and analysis attributes and common logic for
  * an active alignment view displayed in the GUI
@@ -70,9 +78,11 @@ import jalview.workers.StrucConsensusThread;
  * @author jimp
  * 
  */
-public abstract class AlignmentViewport implements AlignViewportI,
-        ViewStyleI, CommandListener, VamsasSource
+public abstract class AlignmentViewport
+        implements AlignViewportI, CommandListener, VamsasSource
 {
+  protected ViewportRanges ranges;
+
   protected ViewStyleI viewStyle = new ViewStyle();
 
   /**
@@ -83,14 +93,26 @@ public abstract class AlignmentViewport implements AlignViewportI,
 
   FeaturesDisplayedI featuresDisplayed = null;
 
-  protected Deque<CommandI> historyList = new ArrayDeque<CommandI>();
+  protected Deque<CommandI> historyList = new ArrayDeque<>();
 
-  protected Deque<CommandI> redoList = new ArrayDeque<CommandI>();
+  protected Deque<CommandI> redoList = new ArrayDeque<>();
+
+  /**
+   * alignment displayed in the viewport. Please use get/setter
+   */
+  protected AlignmentI alignment;
+
+  public AlignmentViewport(AlignmentI al)
+  {
+    setAlignment(al);
+    ranges = new ViewportRanges(al);
+  }
 
   /**
    * @param name
    * @see jalview.api.ViewStyleI#setFontName(java.lang.String)
    */
+  @Override
   public void setFontName(String name)
   {
     viewStyle.setFontName(name);
@@ -100,6 +122,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param style
    * @see jalview.api.ViewStyleI#setFontStyle(int)
    */
+  @Override
   public void setFontStyle(int style)
   {
     viewStyle.setFontStyle(style);
@@ -109,6 +132,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param size
    * @see jalview.api.ViewStyleI#setFontSize(int)
    */
+  @Override
   public void setFontSize(int size)
   {
     viewStyle.setFontSize(size);
@@ -118,6 +142,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getFontStyle()
    */
+  @Override
   public int getFontStyle()
   {
     return viewStyle.getFontStyle();
@@ -127,6 +152,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getFontName()
    */
+  @Override
   public String getFontName()
   {
     return viewStyle.getFontName();
@@ -136,6 +162,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getFontSize()
    */
+  @Override
   public int getFontSize()
   {
     return viewStyle.getFontSize();
@@ -145,6 +172,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param upperCasebold
    * @see jalview.api.ViewStyleI#setUpperCasebold(boolean)
    */
+  @Override
   public void setUpperCasebold(boolean upperCasebold)
   {
     viewStyle.setUpperCasebold(upperCasebold);
@@ -154,6 +182,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#isUpperCasebold()
    */
+  @Override
   public boolean isUpperCasebold()
   {
     return viewStyle.isUpperCasebold();
@@ -163,6 +192,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#isSeqNameItalics()
    */
+  @Override
   public boolean isSeqNameItalics()
   {
     return viewStyle.isSeqNameItalics();
@@ -172,6 +202,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param colourByReferenceSeq
    * @see jalview.api.ViewStyleI#setColourByReferenceSeq(boolean)
    */
+  @Override
   public void setColourByReferenceSeq(boolean colourByReferenceSeq)
   {
     viewStyle.setColourByReferenceSeq(colourByReferenceSeq);
@@ -181,6 +212,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param b
    * @see jalview.api.ViewStyleI#setColourAppliesToAllGroups(boolean)
    */
+  @Override
   public void setColourAppliesToAllGroups(boolean b)
   {
     viewStyle.setColourAppliesToAllGroups(b);
@@ -190,6 +222,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getColourAppliesToAllGroups()
    */
+  @Override
   public boolean getColourAppliesToAllGroups()
   {
     return viewStyle.getColourAppliesToAllGroups();
@@ -199,6 +232,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getAbovePIDThreshold()
    */
+  @Override
   public boolean getAbovePIDThreshold()
   {
     return viewStyle.getAbovePIDThreshold();
@@ -208,6 +242,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param inc
    * @see jalview.api.ViewStyleI#setIncrement(int)
    */
+  @Override
   public void setIncrement(int inc)
   {
     viewStyle.setIncrement(inc);
@@ -217,6 +252,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getIncrement()
    */
+  @Override
   public int getIncrement()
   {
     return viewStyle.getIncrement();
@@ -226,6 +262,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param b
    * @see jalview.api.ViewStyleI#setConservationSelected(boolean)
    */
+  @Override
   public void setConservationSelected(boolean b)
   {
     viewStyle.setConservationSelected(b);
@@ -235,6 +272,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param show
    * @see jalview.api.ViewStyleI#setShowHiddenMarkers(boolean)
    */
+  @Override
   public void setShowHiddenMarkers(boolean show)
   {
     viewStyle.setShowHiddenMarkers(show);
@@ -244,6 +282,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getShowHiddenMarkers()
    */
+  @Override
   public boolean getShowHiddenMarkers()
   {
     return viewStyle.getShowHiddenMarkers();
@@ -253,6 +292,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param b
    * @see jalview.api.ViewStyleI#setScaleRightWrapped(boolean)
    */
+  @Override
   public void setScaleRightWrapped(boolean b)
   {
     viewStyle.setScaleRightWrapped(b);
@@ -262,6 +302,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param b
    * @see jalview.api.ViewStyleI#setScaleLeftWrapped(boolean)
    */
+  @Override
   public void setScaleLeftWrapped(boolean b)
   {
     viewStyle.setScaleLeftWrapped(b);
@@ -271,6 +312,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param b
    * @see jalview.api.ViewStyleI#setScaleAboveWrapped(boolean)
    */
+  @Override
   public void setScaleAboveWrapped(boolean b)
   {
     viewStyle.setScaleAboveWrapped(b);
@@ -280,6 +322,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getScaleLeftWrapped()
    */
+  @Override
   public boolean getScaleLeftWrapped()
   {
     return viewStyle.getScaleLeftWrapped();
@@ -289,6 +332,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getScaleAboveWrapped()
    */
+  @Override
   public boolean getScaleAboveWrapped()
   {
     return viewStyle.getScaleAboveWrapped();
@@ -298,6 +342,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getScaleRightWrapped()
    */
+  @Override
   public boolean getScaleRightWrapped()
   {
     return viewStyle.getScaleRightWrapped();
@@ -307,6 +352,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param b
    * @see jalview.api.ViewStyleI#setAbovePIDThreshold(boolean)
    */
+  @Override
   public void setAbovePIDThreshold(boolean b)
   {
     viewStyle.setAbovePIDThreshold(b);
@@ -316,6 +362,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param thresh
    * @see jalview.api.ViewStyleI#setThreshold(int)
    */
+  @Override
   public void setThreshold(int thresh)
   {
     viewStyle.setThreshold(thresh);
@@ -325,6 +372,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getThreshold()
    */
+  @Override
   public int getThreshold()
   {
     return viewStyle.getThreshold();
@@ -334,6 +382,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getShowJVSuffix()
    */
+  @Override
   public boolean getShowJVSuffix()
   {
     return viewStyle.getShowJVSuffix();
@@ -343,6 +392,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param b
    * @see jalview.api.ViewStyleI#setShowJVSuffix(boolean)
    */
+  @Override
   public void setShowJVSuffix(boolean b)
   {
     viewStyle.setShowJVSuffix(b);
@@ -352,15 +402,18 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param state
    * @see jalview.api.ViewStyleI#setWrapAlignment(boolean)
    */
+  @Override
   public void setWrapAlignment(boolean state)
   {
     viewStyle.setWrapAlignment(state);
+    ranges.setWrappedMode(state);
   }
 
   /**
    * @param state
    * @see jalview.api.ViewStyleI#setShowText(boolean)
    */
+  @Override
   public void setShowText(boolean state)
   {
     viewStyle.setShowText(state);
@@ -370,6 +423,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param state
    * @see jalview.api.ViewStyleI#setRenderGaps(boolean)
    */
+  @Override
   public void setRenderGaps(boolean state)
   {
     viewStyle.setRenderGaps(state);
@@ -379,6 +433,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getColourText()
    */
+  @Override
   public boolean getColourText()
   {
     return viewStyle.getColourText();
@@ -388,6 +443,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param state
    * @see jalview.api.ViewStyleI#setColourText(boolean)
    */
+  @Override
   public void setColourText(boolean state)
   {
     viewStyle.setColourText(state);
@@ -397,6 +453,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getWrapAlignment()
    */
+  @Override
   public boolean getWrapAlignment()
   {
     return viewStyle.getWrapAlignment();
@@ -406,6 +463,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getShowText()
    */
+  @Override
   public boolean getShowText()
   {
     return viewStyle.getShowText();
@@ -415,6 +473,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getWrappedWidth()
    */
+  @Override
   public int getWrappedWidth()
   {
     return viewStyle.getWrappedWidth();
@@ -424,6 +483,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param w
    * @see jalview.api.ViewStyleI#setWrappedWidth(int)
    */
+  @Override
   public void setWrappedWidth(int w)
   {
     viewStyle.setWrappedWidth(w);
@@ -433,6 +493,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getCharHeight()
    */
+  @Override
   public int getCharHeight()
   {
     return viewStyle.getCharHeight();
@@ -442,6 +503,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param h
    * @see jalview.api.ViewStyleI#setCharHeight(int)
    */
+  @Override
   public void setCharHeight(int h)
   {
     viewStyle.setCharHeight(h);
@@ -451,6 +513,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getCharWidth()
    */
+  @Override
   public int getCharWidth()
   {
     return viewStyle.getCharWidth();
@@ -460,6 +523,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param w
    * @see jalview.api.ViewStyleI#setCharWidth(int)
    */
+  @Override
   public void setCharWidth(int w)
   {
     viewStyle.setCharWidth(w);
@@ -469,6 +533,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getShowBoxes()
    */
+  @Override
   public boolean getShowBoxes()
   {
     return viewStyle.getShowBoxes();
@@ -478,6 +543,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getShowUnconserved()
    */
+  @Override
   public boolean getShowUnconserved()
   {
     return viewStyle.getShowUnconserved();
@@ -487,6 +553,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param showunconserved
    * @see jalview.api.ViewStyleI#setShowUnconserved(boolean)
    */
+  @Override
   public void setShowUnconserved(boolean showunconserved)
   {
     viewStyle.setShowUnconserved(showunconserved);
@@ -496,25 +563,12 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param default1
    * @see jalview.api.ViewStyleI#setSeqNameItalics(boolean)
    */
+  @Override
   public void setSeqNameItalics(boolean default1)
   {
     viewStyle.setSeqNameItalics(default1);
   }
 
-  /**
-   * @param selected
-   * @see jalview.api.ViewStyleI#setShowSeqFeaturesHeight(boolean)
-   */
-  public void setShowSeqFeaturesHeight(boolean selected)
-  {
-    viewStyle.setShowSeqFeaturesHeight(selected);
-  }
-
-  /**
-   * alignment displayed in the viewport. Please use get/setter
-   */
-  protected AlignmentI alignment;
-
   @Override
   public AlignmentI getAlignment()
   {
@@ -545,7 +599,6 @@ public abstract class AlignmentViewport implements AlignViewportI,
     return isDataset;
   }
 
-
   private Map<SequenceI, SequenceCollectionI> hiddenRepSequences;
 
   protected ColumnSelection colSel = new ColumnSelection();
@@ -556,98 +609,88 @@ public abstract class AlignmentViewport implements AlignViewportI,
 
   protected boolean ignoreGapsInConsensusCalculation = false;
 
-  protected ColourSchemeI globalColourScheme = null;
-
+  protected ResidueShaderI residueShading = new ResidueShader();
 
   @Override
   public void setGlobalColourScheme(ColourSchemeI cs)
   {
     // TODO: logic refactored from AlignFrame changeColour -
-    // autorecalc stuff should be changed to rely on the worker system
+    // TODO: autorecalc stuff should be changed to rely on the worker system
     // check to see if we should implement a changeColour(cs) method rather than
-    // put th logic in here
+    // put the logic in here
     // - means that caller decides if they want to just modify state and defer
     // calculation till later or to do all calculations in thread.
     // via changecolour
-    globalColourScheme = cs;
-    boolean recalc = false;
+
+    /*
+     * only instantiate alignment colouring once, thereafter update it;
+     * this means that any conservation or PID threshold settings
+     * persist when the alignment colour scheme is changed
+     */
+    if (residueShading == null)
+    {
+      residueShading = new ResidueShader(viewStyle);
+    }
+    residueShading.setColourScheme(cs);
+
+    // TODO: do threshold and increment belong in ViewStyle or ResidueShader?
+    // ...problem: groups need these, but do not currently have a ViewStyle
+
     if (cs != null)
     {
-      cs.setConservationApplied(recalc = getConservationSelected());
-      if (getAbovePIDThreshold() || cs instanceof PIDColourScheme
-              || cs instanceof Blosum62ColourScheme)
-      {
-        recalc = true;
-        cs.setThreshold(viewStyle.getThreshold(),
-                ignoreGapsInConsensusCalculation);
-      }
-      else
-      {
-        cs.setThreshold(0, ignoreGapsInConsensusCalculation);
-      }
-      if (recalc)
+      if (getConservationSelected())
       {
-        cs.setConsensus(hconsensus);
-        cs.setConservation(hconservation);
+        residueShading.setConservation(hconservation);
       }
-      cs.alignmentChanged(alignment, hiddenRepSequences);
+      /*
+       * reset conservation flag in case just set to false if
+       * Conservation was null (calculation still in progress)
+       */
+      residueShading.setConservationApplied(getConservationSelected());
+      residueShading.alignmentChanged(alignment, hiddenRepSequences);
     }
+
+    /*
+     * if 'apply colour to all groups' is selected... do so
+     * (but don't transfer any colour threshold settings to groups)
+     */
     if (getColourAppliesToAllGroups())
     {
       for (SequenceGroup sg : getAlignment().getGroups())
       {
-        if (cs == null)
-        {
-          sg.cs = null;
-          continue;
-        }
-        sg.cs = cs.applyTo(sg, getHiddenRepSequences());
-        sg.setConsPercGaps(ConsPercGaps);
-        if (getAbovePIDThreshold() || cs instanceof PIDColourScheme
-                || cs instanceof Blosum62ColourScheme)
-        {
-          sg.cs.setThreshold(viewStyle.getThreshold(),
-                  isIgnoreGapsConsensus());
-          recalc = true;
-        }
-        else
-        {
-          sg.cs.setThreshold(0, isIgnoreGapsConsensus());
-        }
-
-        if (getConservationSelected())
+        /*
+         * retain any colour thresholds per group while
+         * changing choice of colour scheme (JAL-2386)
+         */
+        sg.setColourScheme(
+                cs == null ? null : cs.getInstance(this, sg));
+        if (cs != null)
         {
-          sg.cs.setConservationApplied(true);
-          recalc = true;
-        }
-        else
-        {
-          sg.cs.setConservation(null);
-          // sg.cs.setThreshold(0, getIgnoreGapsConsensus());
-        }
-        if (recalc)
-        {
-          sg.recalcConservation();
-        }
-        else
-        {
-          sg.cs.alignmentChanged(sg, hiddenRepSequences);
+          sg.getGroupColourScheme().alignmentChanged(sg,
+                  hiddenRepSequences);
         }
       }
     }
-
   }
 
   @Override
   public ColourSchemeI getGlobalColourScheme()
   {
-    return globalColourScheme;
+    return residueShading == null ? null : residueShading.getColourScheme();
+  }
+
+  @Override
+  public ResidueShaderI getResidueShading()
+  {
+    return residueShading;
   }
 
   protected AlignmentAnnotation consensus;
 
   protected AlignmentAnnotation complementConsensus;
 
+  protected AlignmentAnnotation gapcounts;
+
   protected AlignmentAnnotation strucConsensus;
 
   protected AlignmentAnnotation conservation;
@@ -661,7 +704,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
   /**
    * results of alignment consensus analysis for visible portion of view
    */
-  protected Hashtable[] hconsensus = null;
+  protected ProfilesI hconsensus = null;
 
   /**
    * results of cDNA complement consensus visible portion of view
@@ -695,7 +738,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
   }
 
   @Override
-  public void setSequenceConsensusHash(Hashtable[] hconsensus)
+  public void setSequenceConsensusHash(ProfilesI hconsensus)
   {
     this.hconsensus = hconsensus;
   }
@@ -707,7 +750,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
   }
 
   @Override
-  public Hashtable[] getSequenceConsensusHash()
+  public ProfilesI getSequenceConsensusHash()
   {
     return hconsensus;
   }
@@ -750,6 +793,12 @@ public abstract class AlignmentViewport implements AlignViewportI,
   }
 
   @Override
+  public AlignmentAnnotation getAlignmentGapAnnotation()
+  {
+    return gapcounts;
+  }
+
+  @Override
   public AlignmentAnnotation getComplementConsensusAnnotation()
   {
     return complementConsensus;
@@ -769,16 +818,17 @@ public abstract class AlignmentViewport implements AlignViewportI,
   public void updateConservation(final AlignmentViewPanel ap)
   {
     // see note in mantis : issue number 8585
-    if (alignment.isNucleotide() || conservation == null
+    if (alignment.isNucleotide()
+            || (conservation == null && quality == null)
             || !autoCalculateConsensus)
     {
       return;
     }
-    if (calculator
-            .getRegisteredWorkersOfClass(jalview.workers.ConservationThread.class) == null)
+    if (calculator.getRegisteredWorkersOfClass(
+            jalview.workers.ConservationThread.class) == null)
     {
-      calculator.registerWorker(new jalview.workers.ConservationThread(
-              this, ap));
+      calculator.registerWorker(
+              new jalview.workers.ConservationThread(this, ap));
     }
   }
 
@@ -792,22 +842,44 @@ public abstract class AlignmentViewport implements AlignViewportI,
     {
       return;
     }
-    if (calculator.getRegisteredWorkersOfClass(ConsensusThread.class) == null)
+    if (calculator
+            .getRegisteredWorkersOfClass(ConsensusThread.class) == null)
     {
       calculator.registerWorker(new ConsensusThread(this, ap));
     }
 
     /*
      * A separate thread to compute cDNA consensus for a protein alignment
+     * which has mapping to cDNA
      */
     final AlignmentI al = this.getAlignment();
     if (!al.isNucleotide() && al.getCodonFrames() != null
             && !al.getCodonFrames().isEmpty())
     {
-      if (calculator
-              .getRegisteredWorkersOfClass(ComplementConsensusThread.class) == null)
+      /*
+       * fudge - check first for protein-to-nucleotide mappings
+       * (we don't want to do this for protein-to-protein)
+       */
+      boolean doConsensus = false;
+      for (AlignedCodonFrame mapping : al.getCodonFrames())
+      {
+        // TODO hold mapping type e.g. dna-to-protein in AlignedCodonFrame?
+        MapList[] mapLists = mapping.getdnaToProt();
+        // mapLists can be empty if project load has not finished resolving seqs
+        if (mapLists.length > 0 && mapLists[0].getFromRatio() == 3)
+        {
+          doConsensus = true;
+          break;
+        }
+      }
+      if (doConsensus)
       {
-        calculator.registerWorker(new ComplementConsensusThread(this, ap));
+        if (calculator.getRegisteredWorkersOfClass(
+                ComplementConsensusThread.class) == null)
+        {
+          calculator
+                  .registerWorker(new ComplementConsensusThread(this, ap));
+        }
       }
     }
   }
@@ -827,7 +899,8 @@ public abstract class AlignmentViewport implements AlignViewportI,
     {
       return;
     }
-    if (calculator.getRegisteredWorkersOfClass(StrucConsensusThread.class) == null)
+    if (calculator.getRegisteredWorkersOfClass(
+            StrucConsensusThread.class) == null)
     {
       calculator.registerWorker(new StrucConsensusThread(this, ap));
     }
@@ -854,6 +927,41 @@ public abstract class AlignmentViewport implements AlignViewportI,
     return false;
   }
 
+  public void setAlignment(AlignmentI align)
+  {
+    this.alignment = align;
+  }
+
+  /**
+   * Clean up references when this viewport is closed
+   */
+  @Override
+  public void dispose()
+  {
+    /*
+     * defensively null out references to large objects in case
+     * this object is not garbage collected (as if!)
+     */
+    consensus = null;
+    complementConsensus = null;
+    strucConsensus = null;
+    conservation = null;
+    quality = null;
+    groupConsensus = null;
+    groupConservation = null;
+    hconsensus = null;
+    hconservation = null;
+    hcomplementConsensus = null;
+    gapcounts = null;
+    calculator = null;
+    residueShading = null; // may hold a reference to Consensus
+    changeSupport = null;
+    ranges = null;
+    currentTree = null;
+    selectionGroup = null;
+    setAlignment(null);
+  }
+
   @Override
   public boolean isClosed()
   {
@@ -984,7 +1092,6 @@ public abstract class AlignmentViewport implements AlignViewportI,
    */
   public boolean sortByTree = false;
 
-
   /**
    * 
    * 
@@ -997,7 +1104,8 @@ public abstract class AlignmentViewport implements AlignViewportI,
   }
 
   /**
-   * Set the selection group for this window.
+   * Set the selection group for this window. Also sets the current alignment as
+   * the context for the group, if it does not already have one.
    * 
    * @param sg
    *          - group holding references to sequences in this alignment view
@@ -1007,11 +1115,15 @@ public abstract class AlignmentViewport implements AlignViewportI,
   public void setSelectionGroup(SequenceGroup sg)
   {
     selectionGroup = sg;
+    if (sg != null && sg.getContext() == null)
+    {
+      sg.setContext(alignment);
+    }
   }
 
-  public void setHiddenColumns(ColumnSelection colsel)
+  public void setHiddenColumns(HiddenColumns hidden)
   {
-    this.colSel = colsel;
+    this.alignment.setHiddenColumns(hidden);
   }
 
   @Override
@@ -1028,6 +1140,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
     {
       updateHiddenColumns();
     }
+    isColSelChanged(true);
   }
 
   /**
@@ -1048,9 +1161,17 @@ public abstract class AlignmentViewport implements AlignViewportI,
   }
 
   @Override
+  public boolean hasSelectedColumns()
+  {
+    ColumnSelection columnSelection = getColumnSelection();
+    return columnSelection != null && columnSelection.hasSelectedColumns();
+  }
+
+  @Override
   public boolean hasHiddenColumns()
   {
-    return colSel != null && colSel.hasHiddenColumns();
+    return alignment.getHiddenColumns() != null
+            && alignment.getHiddenColumns().hasHiddenColumns();
   }
 
   public void updateHiddenColumns()
@@ -1060,12 +1181,10 @@ public abstract class AlignmentViewport implements AlignViewportI,
     // hasHiddenColumns = colSel.hasHiddenColumns();
   }
 
-  protected boolean hasHiddenRows = false;
-
   @Override
   public boolean hasHiddenRows()
   {
-    return hasHiddenRows;
+    return alignment.getHiddenSequences().getSize() > 0;
   }
 
   protected SequenceGroup selectionGroup;
@@ -1074,8 +1193,8 @@ public abstract class AlignmentViewport implements AlignViewportI,
   {
     if (sequenceSetID != null)
     {
-      System.err
-              .println("Warning - overwriting a sequenceSetId for a viewport!");
+      System.err.println(
+              "Warning - overwriting a sequenceSetId for a viewport!");
     }
     sequenceSetID = new String(newid);
   }
@@ -1113,9 +1232,9 @@ public abstract class AlignmentViewport implements AlignViewportI,
     if (ap != null)
     {
       updateConsensus(ap);
-      if (globalColourScheme != null)
+      if (residueShading != null)
       {
-        globalColourScheme.setThreshold(globalColourScheme.getThreshold(),
+        residueShading.setThreshold(residueShading.getThreshold(),
                 ignoreGapsInConsensusCalculation);
       }
     }
@@ -1158,8 +1277,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    */
   public boolean isColSelChanged(boolean b)
   {
-    int hc = (colSel == null || colSel.size() == 0) ? -1 : colSel
-            .hashCode();
+    int hc = (colSel == null || colSel.isEmpty()) ? -1 : colSel.hashCode();
     if (hc != -1 && hc != colselhash)
     {
       if (b)
@@ -1177,10 +1295,9 @@ public abstract class AlignmentViewport implements AlignViewportI,
     return ignoreGapsInConsensusCalculation;
   }
 
-  // / property change stuff
-
+  // property change stuff
   // JBPNote Prolly only need this in the applet version.
-  private final java.beans.PropertyChangeSupport changeSupport = new java.beans.PropertyChangeSupport(
+  private PropertyChangeSupport changeSupport = new PropertyChangeSupport(
           this);
 
   protected boolean showConservation = true;
@@ -1189,13 +1306,20 @@ public abstract class AlignmentViewport implements AlignViewportI,
 
   protected boolean showConsensus = true;
 
-  private Map<SequenceI, Color> sequenceColours = new HashMap<SequenceI, Color>();
+  protected boolean showOccupancy = true;
+
+  private Map<SequenceI, Color> sequenceColours = new HashMap<>();
 
   protected SequenceAnnotationOrder sortAnnotationsBy = null;
 
   protected boolean showAutocalculatedAbove;
 
   /**
+   * when set, view will scroll to show the highlighted position
+   */
+  private boolean followHighlight = true;
+
+  /**
    * Property change listener for changes in alignment
    * 
    * @param listener
@@ -1216,7 +1340,10 @@ public abstract class AlignmentViewport implements AlignViewportI,
   public void removePropertyChangeListener(
           java.beans.PropertyChangeListener listener)
   {
-    changeSupport.removePropertyChangeListener(listener);
+    if (changeSupport != null)
+    {
+      changeSupport.removePropertyChangeListener(listener);
+    }
   }
 
   /**
@@ -1239,42 +1366,47 @@ public abstract class AlignmentViewport implements AlignViewportI,
 
   public void hideSelectedColumns()
   {
-    if (colSel.size() < 1)
+    if (colSel.isEmpty())
     {
       return;
     }
 
-    colSel.hideSelectedColumns();
+    colSel.hideSelectedColumns(alignment);
     setSelectionGroup(null);
-
+    isColSelChanged(true);
   }
 
   public void hideColumns(int start, int end)
   {
     if (start == end)
     {
-      colSel.hideColumns(start);
+      colSel.hideSelectedColumns(start, alignment.getHiddenColumns());
     }
     else
     {
-      colSel.hideColumns(start, end);
+      alignment.getHiddenColumns().hideColumns(start, end);
     }
+    isColSelChanged(true);
   }
 
   public void showColumn(int col)
   {
-    colSel.revealHiddenColumns(col);
-
+    alignment.getHiddenColumns().revealHiddenColumns(col, colSel);
+    isColSelChanged(true);
   }
 
   public void showAllHiddenColumns()
   {
-    colSel.revealAllHiddenColumns();
+    alignment.getHiddenColumns().revealAllHiddenColumns(colSel);
+    isColSelChanged(true);
   }
 
   // common hide/show seq stuff
   public void showAllHiddenSeqs()
   {
+    int startSeq = ranges.getStartSeq();
+    int endSeq = ranges.getEndSeq();
+
     if (alignment.getHiddenSequences().getSize() > 0)
     {
       if (selectionGroup == null)
@@ -1282,17 +1414,18 @@ public abstract class AlignmentViewport implements AlignViewportI,
         selectionGroup = new SequenceGroup();
         selectionGroup.setEndRes(alignment.getWidth() - 1);
       }
-      List<SequenceI> tmp = alignment.getHiddenSequences().showAll(
-              hiddenRepSequences);
+      List<SequenceI> tmp = alignment.getHiddenSequences()
+              .showAll(hiddenRepSequences);
       for (SequenceI seq : tmp)
       {
         selectionGroup.addSequence(seq, false);
         setSequenceAnnotationsVisible(seq, true);
       }
 
-      hasHiddenRows = false;
       hiddenRepSequences = null;
 
+      ranges.setStartEndSeq(startSeq, endSeq + tmp.size());
+
       firePropertyChange("alignment", null, alignment.getSequences());
       // used to set hasHiddenRows/hiddenRepSequences here, after the property
       // changed event
@@ -1302,8 +1435,10 @@ public abstract class AlignmentViewport implements AlignViewportI,
 
   public void showSequence(int index)
   {
-    List<SequenceI> tmp = alignment.getHiddenSequences().showSequence(
-            index,
+    int startSeq = ranges.getStartSeq();
+    int endSeq = ranges.getEndSeq();
+
+    List<SequenceI> tmp = alignment.getHiddenSequences().showSequence(index,
             hiddenRepSequences);
     if (tmp.size() > 0)
     {
@@ -1318,12 +1453,9 @@ public abstract class AlignmentViewport implements AlignViewportI,
         selectionGroup.addSequence(seq, false);
         setSequenceAnnotationsVisible(seq, true);
       }
-      // JBPNote: refactor: only update flag if we modified visiblity (used to
-      // do this regardless)
-      if (alignment.getHiddenSequences().getSize() < 1)
-      {
-        hasHiddenRows = false;
-      }
+
+      ranges.setStartEndSeq(startSeq, endSeq + tmp.size());
+
       firePropertyChange("alignment", null, alignment.getSequences());
       sendSelection();
     }
@@ -1345,6 +1477,11 @@ public abstract class AlignmentViewport implements AlignViewportI,
 
   public void hideSequence(SequenceI[] seq)
   {
+    /*
+     * cache offset to first visible sequence
+     */
+    int startSeq = ranges.getStartSeq();
+
     if (seq != null)
     {
       for (int i = 0; i < seq.length; i++)
@@ -1352,12 +1489,45 @@ public abstract class AlignmentViewport implements AlignViewportI,
         alignment.getHiddenSequences().hideSequence(seq[i]);
         setSequenceAnnotationsVisible(seq[i], false);
       }
-      hasHiddenRows = true;
+      ranges.setStartSeq(startSeq);
       firePropertyChange("alignment", null, alignment.getSequences());
     }
   }
 
   /**
+   * Hides the specified sequence, or the sequences it represents
+   * 
+   * @param sequence
+   *          the sequence to hide, or keep as representative
+   * @param representGroup
+   *          if true, hide the current selection group except for the
+   *          representative sequence
+   */
+  public void hideSequences(SequenceI sequence, boolean representGroup)
+  {
+    if (selectionGroup == null || selectionGroup.getSize() < 1)
+    {
+      hideSequence(new SequenceI[] { sequence });
+      return;
+    }
+
+    if (representGroup)
+    {
+      hideRepSequences(sequence, selectionGroup);
+      setSelectionGroup(null);
+      return;
+    }
+
+    int gsize = selectionGroup.getSize();
+    SequenceI[] hseqs = selectionGroup.getSequences()
+            .toArray(new SequenceI[gsize]);
+
+    hideSequence(hseqs);
+    setSelectionGroup(null);
+    sendSelection();
+  }
+
+  /**
    * Set visibility for any annotations for the given sequence.
    * 
    * @param sequenceI
@@ -1365,11 +1535,15 @@ public abstract class AlignmentViewport implements AlignViewportI,
   protected void setSequenceAnnotationsVisible(SequenceI sequenceI,
           boolean visible)
   {
-    for (AlignmentAnnotation ann : alignment.getAlignmentAnnotation())
+    AlignmentAnnotation[] anns = alignment.getAlignmentAnnotation();
+    if (anns != null)
     {
-      if (ann.sequenceRef == sequenceI)
+      for (AlignmentAnnotation ann : anns)
       {
-        ann.visible = visible;
+        if (ann.sequenceRef == sequenceI)
+        {
+          ann.visible = visible;
+        }
       }
     }
   }
@@ -1384,7 +1558,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
 
     if (hiddenRepSequences == null)
     {
-      hiddenRepSequences = new Hashtable();
+      hiddenRepSequences = new Hashtable<>();
     }
 
     hiddenRepSequences.put(repSequence, sg);
@@ -1410,12 +1584,42 @@ public abstract class AlignmentViewport implements AlignViewportI,
 
   }
 
+  /**
+   * 
+   * @return null or the current reference sequence
+   */
+  public SequenceI getReferenceSeq()
+  {
+    return alignment.getSeqrep();
+  }
+
+  /**
+   * @param seq
+   * @return true iff seq is the reference for the alignment
+   */
+  public boolean isReferenceSeq(SequenceI seq)
+  {
+    return alignment.getSeqrep() == seq;
+  }
+
+  /**
+   * 
+   * @param seq
+   * @return true if there are sequences represented by this sequence that are
+   *         currently hidden
+   */
   public boolean isHiddenRepSequence(SequenceI seq)
   {
-    return alignment.getSeqrep()==seq || (hiddenRepSequences != null
+    return (hiddenRepSequences != null
             && hiddenRepSequences.containsKey(seq));
   }
 
+  /**
+   * 
+   * @param seq
+   * @return null or a sequence group containing the sequences that seq
+   *         represents
+   */
   public SequenceGroup getRepresentedSequences(SequenceI seq)
   {
     return (SequenceGroup) (hiddenRepSequences == null ? null
@@ -1425,17 +1629,17 @@ public abstract class AlignmentViewport implements AlignViewportI,
   @Override
   public int adjustForHiddenSeqs(int alignmentIndex)
   {
-    return alignment.getHiddenSequences().adjustForHiddenSeqs(
-            alignmentIndex);
+    return alignment.getHiddenSequences()
+            .adjustForHiddenSeqs(alignmentIndex);
   }
 
   @Override
   public void invertColumnSelection()
   {
-    colSel.invertColumnSelection(0, alignment.getWidth());
+    colSel.invertColumnSelection(0, alignment.getWidth(), alignment);
+    isColSelChanged(true);
   }
 
-
   @Override
   public SequenceI[] getSelectionAsNewSequence()
   {
@@ -1463,7 +1667,6 @@ public abstract class AlignmentViewport implements AlignViewportI,
     return sequences;
   }
 
-
   @Override
   public SequenceI[] getSequenceSelection()
   {
@@ -1479,16 +1682,6 @@ public abstract class AlignmentViewport implements AlignViewportI,
     return sequences;
   }
 
-
-  @Override
-  public CigarArray getViewAsCigars(
-          boolean selectedRegionOnly)
-  {
-    return new CigarArray(alignment, colSel,
-            (selectedRegionOnly ? selectionGroup : null));
-  }
-
-
   @Override
   public jalview.datamodel.AlignmentView getAlignmentView(
           boolean selectedOnly)
@@ -1496,20 +1689,27 @@ public abstract class AlignmentViewport implements AlignViewportI,
     return getAlignmentView(selectedOnly, false);
   }
 
-
   @Override
   public jalview.datamodel.AlignmentView getAlignmentView(
           boolean selectedOnly, boolean markGroups)
   {
-    return new AlignmentView(alignment, colSel, selectionGroup,
-            colSel != null && colSel.hasHiddenColumns(), selectedOnly,
-            markGroups);
+    return new AlignmentView(alignment, alignment.getHiddenColumns(),
+            selectionGroup,
+            alignment.getHiddenColumns() != null
+                    && alignment.getHiddenColumns().hasHiddenColumns(),
+            selectedOnly, markGroups);
   }
 
-
   @Override
   public String[] getViewAsString(boolean selectedRegionOnly)
   {
+    return getViewAsString(selectedRegionOnly, true);
+  }
+
+  @Override
+  public String[] getViewAsString(boolean selectedRegionOnly,
+          boolean exportHiddenSeqs)
+  {
     String[] selection = null;
     SequenceI[] seqs = null;
     int i, iSize;
@@ -1523,15 +1723,32 @@ public abstract class AlignmentViewport implements AlignViewportI,
     }
     else
     {
-      iSize = alignment.getHeight();
-      seqs = alignment.getSequencesArray();
-      end = alignment.getWidth();
+      if (hasHiddenRows() && exportHiddenSeqs)
+      {
+        AlignmentI fullAlignment = alignment.getHiddenSequences()
+                .getFullAlignment();
+        iSize = fullAlignment.getHeight();
+        seqs = fullAlignment.getSequencesArray();
+        end = fullAlignment.getWidth();
+      }
+      else
+      {
+        iSize = alignment.getHeight();
+        seqs = alignment.getSequencesArray();
+        end = alignment.getWidth();
+      }
     }
 
     selection = new String[iSize];
-    if (colSel != null && colSel.hasHiddenColumns())
+    if (alignment.getHiddenColumns() != null
+            && alignment.getHiddenColumns().hasHiddenColumns())
     {
-      selection = colSel.getVisibleSequenceStrings(start, end, seqs);
+      for (i = 0; i < iSize; i++)
+      {
+        Iterator<int[]> blocks = alignment.getHiddenColumns()
+                .getVisContigsIterator(start, end + 1, false);
+        selection[i] = seqs[i].getSequenceStringFromIterator(blocks);
+      }
     }
     else
     {
@@ -1544,24 +1761,24 @@ public abstract class AlignmentViewport implements AlignViewportI,
     return selection;
   }
 
-
   @Override
   public List<int[]> getVisibleRegionBoundaries(int min, int max)
   {
-    ArrayList<int[]> regions = new ArrayList<int[]>();
+    ArrayList<int[]> regions = new ArrayList<>();
     int start = min;
     int end = max;
 
     do
     {
-      if (colSel != null && colSel.hasHiddenColumns())
+      HiddenColumns hidden = alignment.getHiddenColumns();
+      if (hidden != null && hidden.hasHiddenColumns())
       {
         if (start == 0)
         {
-          start = colSel.adjustForHiddenColumns(start);
+          start = hidden.visibleToAbsoluteColumn(start);
         }
 
-        end = colSel.getHiddenBoundaryRight(start);
+        end = hidden.getNextHiddenBoundary(false, start);
         if (start == end)
         {
           end = max;
@@ -1572,13 +1789,12 @@ public abstract class AlignmentViewport implements AlignViewportI,
         }
       }
 
-      regions.add(new int[]
-      { start, end });
+      regions.add(new int[] { start, end });
 
-      if (colSel != null && colSel.hasHiddenColumns())
+      if (hidden != null && hidden.hasHiddenColumns())
       {
-        start = colSel.adjustForHiddenColumns(end);
-        start = colSel.getHiddenBoundaryLeft(start) + 1;
+        start = hidden.visibleToAbsoluteColumn(end);
+        start = hidden.getNextHiddenBoundary(true, start) + 1;
       }
     } while (end < max);
 
@@ -1588,20 +1804,25 @@ public abstract class AlignmentViewport implements AlignViewportI,
   }
 
   @Override
-  public List<AlignmentAnnotation> getVisibleAlignmentAnnotation(boolean selectedOnly)
+  public List<AlignmentAnnotation> getVisibleAlignmentAnnotation(
+          boolean selectedOnly)
   {
-    ArrayList<AlignmentAnnotation> ala = new ArrayList<AlignmentAnnotation>();
+    ArrayList<AlignmentAnnotation> ala = new ArrayList<>();
     AlignmentAnnotation[] aa;
-    if ((aa=alignment.getAlignmentAnnotation())!=null)
+    if ((aa = alignment.getAlignmentAnnotation()) != null)
     {
-      for (AlignmentAnnotation annot:aa)
+      for (AlignmentAnnotation annot : aa)
       {
         AlignmentAnnotation clone = new AlignmentAnnotation(annot);
-        if (selectedOnly && selectionGroup!=null)
+        if (selectedOnly && selectionGroup != null)
         {
-          colSel.makeVisibleAnnotation(selectionGroup.getStartRes(), selectionGroup.getEndRes(),clone);
-        } else {
-          colSel.makeVisibleAnnotation(clone);
+          clone.makeVisibleAnnotation(
+                  selectionGroup.getStartRes(), selectionGroup.getEndRes(),
+                  alignment.getHiddenColumns());
+        }
+        else
+        {
+          clone.makeVisibleAnnotation(alignment.getHiddenColumns());
         }
         ala.add(clone);
       }
@@ -1609,14 +1830,12 @@ public abstract class AlignmentViewport implements AlignViewportI,
     return ala;
   }
 
-
   @Override
   public boolean isPadGaps()
   {
     return padGaps;
   }
 
-
   @Override
   public void setPadGaps(boolean padGaps)
   {
@@ -1668,7 +1887,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
       selectionGroup.setEndRes(alWidth - 1);
     }
 
-    resetAllColourSchemes();
+    updateAllColourSchemes();
     calculator.restartWorkers();
     // alignment.adjustSequenceAnnotations();
   }
@@ -1676,19 +1895,19 @@ public abstract class AlignmentViewport implements AlignViewportI,
   /**
    * reset scope and do calculations for all applied colourschemes on alignment
    */
-  void resetAllColourSchemes()
+  void updateAllColourSchemes()
   {
-    ColourSchemeI cs = globalColourScheme;
-    if (cs != null)
+    ResidueShaderI rs = residueShading;
+    if (rs != null)
     {
-      cs.alignmentChanged(alignment, hiddenRepSequences);
+      rs.alignmentChanged(alignment, hiddenRepSequences);
 
-      cs.setConsensus(hconsensus);
-      if (cs.conservationApplied())
+      rs.setConsensus(hconsensus);
+      if (rs.conservationApplied())
       {
-        cs.setConservation(Conservation.calculateConservation("All",
-                ResidueProperties.propHash, 3, alignment.getSequences(), 0,
-                alignment.getWidth(), false, getConsPercGaps(), false));
+        rs.setConservation(Conservation.calculateConservation("All",
+                alignment.getSequences(), 0, alignment.getWidth(), false,
+                getConsPercGaps(), false));
       }
     }
 
@@ -1719,32 +1938,54 @@ public abstract class AlignmentViewport implements AlignViewportI,
       {
         initRNAStructure();
       }
-      consensus = new AlignmentAnnotation("Consensus", "PID",
+      consensus = new AlignmentAnnotation("Consensus",
+              MessageManager.getString("label.consensus_descr"),
               new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
       initConsensus(consensus);
+      initGapCounts();
 
       initComplementConsensus();
     }
   }
 
   /**
-   * If this is a protein alignment and there are mappings to cDNA, add the cDNA
-   * consensus annotation.
+   * If this is a protein alignment and there are mappings to cDNA, adds the
+   * cDNA consensus annotation and returns true, else returns false.
    */
-  protected void initComplementConsensus()
+  public boolean initComplementConsensus()
   {
     if (!alignment.isNucleotide())
     {
-      final Set<AlignedCodonFrame> codonMappings = alignment
+      final List<AlignedCodonFrame> codonMappings = alignment
               .getCodonFrames();
       if (codonMappings != null && !codonMappings.isEmpty())
       {
-        complementConsensus = new AlignmentAnnotation("cDNA Consensus",
-                "PID for cDNA", new Annotation[1], 0f, 100f,
-                AlignmentAnnotation.BAR_GRAPH);
-        initConsensus(complementConsensus);
+        boolean doConsensus = false;
+        for (AlignedCodonFrame mapping : codonMappings)
+        {
+          // TODO hold mapping type e.g. dna-to-protein in AlignedCodonFrame?
+          MapList[] mapLists = mapping.getdnaToProt();
+          // mapLists can be empty if project load has not finished resolving
+          // seqs
+          if (mapLists.length > 0 && mapLists[0].getFromRatio() == 3)
+          {
+            doConsensus = true;
+            break;
+          }
+        }
+        if (doConsensus)
+        {
+          complementConsensus = new AlignmentAnnotation("cDNA Consensus",
+                  MessageManager
+                          .getString("label.complement_consensus_descr"),
+                  new Annotation[1], 0f, 100f,
+                  AlignmentAnnotation.BAR_GRAPH);
+          initConsensus(complementConsensus);
+          return true;
+        }
       }
     }
+    return false;
   }
 
   private void initConsensus(AlignmentAnnotation aa)
@@ -1758,6 +1999,25 @@ public abstract class AlignmentViewport implements AlignViewportI,
     }
   }
 
+  // these should be extracted from the view model - style and settings for
+  // derived annotation
+  private void initGapCounts()
+  {
+    if (showOccupancy)
+    {
+      gapcounts = new AlignmentAnnotation("Occupancy",
+              MessageManager.getString("label.occupancy_descr"),
+              new Annotation[1], 0f, alignment.getHeight(),
+              AlignmentAnnotation.BAR_GRAPH);
+      gapcounts.hasText = true;
+      gapcounts.autoCalculated = true;
+      gapcounts.scaleColLabel = true;
+      gapcounts.graph = AlignmentAnnotation.BAR_GRAPH;
+
+      alignment.addAnnotation(gapcounts);
+    }
+  }
+
   private void initConservation()
   {
     if (showConservation)
@@ -1765,9 +2025,9 @@ public abstract class AlignmentViewport implements AlignViewportI,
       if (conservation == null)
       {
         conservation = new AlignmentAnnotation("Conservation",
-                "Conservation of total alignment less than "
-                        + getConsPercGaps() + "% gaps", new Annotation[1],
-                0f, 11f, AlignmentAnnotation.BAR_GRAPH);
+                MessageManager.formatMessage("label.conservation_descr",
+                        getConsPercGaps()),
+                new Annotation[1], 0f, 11f, AlignmentAnnotation.BAR_GRAPH);
         conservation.hasText = true;
         conservation.autoCalculated = true;
         alignment.addAnnotation(conservation);
@@ -1782,7 +2042,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
       if (quality == null)
       {
         quality = new AlignmentAnnotation("Quality",
-                "Alignment Quality based on Blosum62 scores",
+                MessageManager.getString("label.quality_descr"),
                 new Annotation[1], 0f, 11f, AlignmentAnnotation.BAR_GRAPH);
         quality.hasText = true;
         quality.autoCalculated = true;
@@ -1795,7 +2055,8 @@ public abstract class AlignmentViewport implements AlignViewportI,
   {
     if (alignment.hasRNAStructure() && strucConsensus == null)
     {
-      strucConsensus = new AlignmentAnnotation("StrucConsensus", "PID",
+      strucConsensus = new AlignmentAnnotation("StrucConsensus",
+              MessageManager.getString("label.strucconsensus_descr"),
               new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
       strucConsensus.hasText = true;
       strucConsensus.autoCalculated = true;
@@ -1900,7 +2161,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
     // intersect alignment annotation with alignment groups
 
     AlignmentAnnotation[] aan = alignment.getAlignmentAnnotation();
-    List<SequenceGroup> oldrfs = new ArrayList<SequenceGroup>();
+    List<SequenceGroup> oldrfs = new ArrayList<>();
     if (aan != null)
     {
       for (int an = 0; an < aan.length; an++)
@@ -1944,6 +2205,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
     }
     oldrfs.clear();
   }
+
   @Override
   public boolean isDisplayReferenceSeq()
   {
@@ -2052,7 +2314,8 @@ public abstract class AlignmentViewport implements AlignViewportI,
   @Override
   public boolean areFeaturesDisplayed()
   {
-    return featuresDisplayed != null && featuresDisplayed.getRegisterdFeaturesCount()>0;
+    return featuresDisplayed != null
+            && featuresDisplayed.getRegisteredFeaturesCount() > 0;
   }
 
   /**
@@ -2066,6 +2329,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
   {
     viewStyle.setShowSequenceFeatures(b);
   }
+
   @Override
   public boolean isShowSequenceFeatures()
   {
@@ -2075,7 +2339,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
   @Override
   public void setShowSequenceFeaturesHeight(boolean selected)
   {
-    viewStyle.setShowSeqFeaturesHeight(selected);
+    viewStyle.setShowSequenceFeaturesHeight(selected);
   }
 
   @Override
@@ -2084,8 +2348,6 @@ public abstract class AlignmentViewport implements AlignViewportI,
     return viewStyle.isShowSequenceFeaturesHeight();
   }
 
-
-
   @Override
   public void setShowAnnotation(boolean b)
   {
@@ -2126,6 +2388,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getTextColour()
    */
+  @Override
   public Color getTextColour()
   {
     return viewStyle.getTextColour();
@@ -2135,6 +2398,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getTextColour2()
    */
+  @Override
   public Color getTextColour2()
   {
     return viewStyle.getTextColour2();
@@ -2144,6 +2408,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getThresholdTextColour()
    */
+  @Override
   public int getThresholdTextColour()
   {
     return viewStyle.getThresholdTextColour();
@@ -2153,6 +2418,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#isConservationColourSelected()
    */
+  @Override
   public boolean isConservationColourSelected()
   {
     return viewStyle.isConservationColourSelected();
@@ -2162,6 +2428,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#isRenderGaps()
    */
+  @Override
   public boolean isRenderGaps()
   {
     return viewStyle.isRenderGaps();
@@ -2171,23 +2438,17 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#isShowColourText()
    */
+  @Override
   public boolean isShowColourText()
   {
     return viewStyle.isShowColourText();
   }
-  /**
-   * @return
-   * @see jalview.api.ViewStyleI#isShowSeqFeaturesHeight()
-   */
-  public boolean isShowSeqFeaturesHeight()
-  {
-    return viewStyle.isShowSeqFeaturesHeight();
-  }
 
   /**
    * @param conservationColourSelected
    * @see jalview.api.ViewStyleI#setConservationColourSelected(boolean)
    */
+  @Override
   public void setConservationColourSelected(
           boolean conservationColourSelected)
   {
@@ -2198,6 +2459,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param showColourText
    * @see jalview.api.ViewStyleI#setShowColourText(boolean)
    */
+  @Override
   public void setShowColourText(boolean showColourText)
   {
     viewStyle.setShowColourText(showColourText);
@@ -2207,6 +2469,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param textColour
    * @see jalview.api.ViewStyleI#setTextColour(java.awt.Color)
    */
+  @Override
   public void setTextColour(Color textColour)
   {
     viewStyle.setTextColour(textColour);
@@ -2216,6 +2479,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param thresholdTextColour
    * @see jalview.api.ViewStyleI#setThresholdTextColour(int)
    */
+  @Override
   public void setThresholdTextColour(int thresholdTextColour)
   {
     viewStyle.setThresholdTextColour(thresholdTextColour);
@@ -2225,6 +2489,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param textColour2
    * @see jalview.api.ViewStyleI#setTextColour2(java.awt.Color)
    */
+  @Override
   public void setTextColour2(Color textColour2)
   {
     viewStyle.setTextColour2(textColour2);
@@ -2240,6 +2505,11 @@ public abstract class AlignmentViewport implements AlignViewportI,
   public void setViewStyle(ViewStyleI settingsForView)
   {
     viewStyle = new ViewStyle(settingsForView);
+    if (residueShading != null)
+    {
+      residueShading.setConservationApplied(
+              settingsForView.isConservationColourSelected());
+    }
   }
 
   @Override
@@ -2252,6 +2522,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#getIdWidth()
    */
+  @Override
   public int getIdWidth()
   {
     return viewStyle.getIdWidth();
@@ -2261,6 +2532,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param i
    * @see jalview.api.ViewStyleI#setIdWidth(int)
    */
+  @Override
   public void setIdWidth(int i)
   {
     viewStyle.setIdWidth(i);
@@ -2270,6 +2542,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#isCentreColumnLabels()
    */
+  @Override
   public boolean isCentreColumnLabels()
   {
     return viewStyle.isCentreColumnLabels();
@@ -2279,6 +2552,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param centreColumnLabels
    * @see jalview.api.ViewStyleI#setCentreColumnLabels(boolean)
    */
+  @Override
   public void setCentreColumnLabels(boolean centreColumnLabels)
   {
     viewStyle.setCentreColumnLabels(centreColumnLabels);
@@ -2288,6 +2562,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param showdbrefs
    * @see jalview.api.ViewStyleI#setShowDBRefs(boolean)
    */
+  @Override
   public void setShowDBRefs(boolean showdbrefs)
   {
     viewStyle.setShowDBRefs(showdbrefs);
@@ -2297,6 +2572,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#isShowDBRefs()
    */
+  @Override
   public boolean isShowDBRefs()
   {
     return viewStyle.isShowDBRefs();
@@ -2306,6 +2582,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @return
    * @see jalview.api.ViewStyleI#isShowNPFeats()
    */
+  @Override
   public boolean isShowNPFeats()
   {
     return viewStyle.isShowNPFeats();
@@ -2315,6 +2592,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
    * @param shownpfeats
    * @see jalview.api.ViewStyleI#setShowNPFeats(boolean)
    */
+  @Override
   public void setShowNPFeats(boolean shownpfeats)
   {
     viewStyle.setShowNPFeats(shownpfeats);
@@ -2338,7 +2616,8 @@ public abstract class AlignmentViewport implements AlignViewportI,
 
   protected void broadcastCommand(CommandI command, boolean undo)
   {
-    getStructureSelectionManager().commandPerformed(command, undo, getVamsasSource());
+    getStructureSelectionManager().commandPerformed(command, undo,
+            getVamsasSource());
   }
 
   /**
@@ -2397,7 +2676,8 @@ public abstract class AlignmentViewport implements AlignViewportI,
     return sortAnnotationsBy;
   }
 
-  public void setSortAnnotationsBy(SequenceAnnotationOrder sortAnnotationsBy)
+  public void setSortAnnotationsBy(
+          SequenceAnnotationOrder sortAnnotationsBy)
   {
     this.sortAnnotationsBy = sortAnnotationsBy;
   }
@@ -2411,4 +2691,375 @@ public abstract class AlignmentViewport implements AlignViewportI,
   {
     this.showAutocalculatedAbove = showAutocalculatedAbove;
   }
+
+  @Override
+  public boolean isScaleProteinAsCdna()
+  {
+    return viewStyle.isScaleProteinAsCdna();
+  }
+
+  @Override
+  public void setScaleProteinAsCdna(boolean b)
+  {
+    viewStyle.setScaleProteinAsCdna(b);
+  }
+
+  @Override
+  public boolean isProteinFontAsCdna()
+  {
+    return viewStyle.isProteinFontAsCdna();
+  }
+
+  @Override
+  public void setProteinFontAsCdna(boolean b)
+  {
+    viewStyle.setProteinFontAsCdna(b);
+  }
+
+  @Override
+  public void setShowComplementFeatures(boolean b)
+  {
+    viewStyle.setShowComplementFeatures(b);
+  }
+
+  @Override
+  public boolean isShowComplementFeatures()
+  {
+    return viewStyle.isShowComplementFeatures();
+  }
+
+  @Override
+  public void setShowComplementFeaturesOnTop(boolean b)
+  {
+    viewStyle.setShowComplementFeaturesOnTop(b);
+  }
+
+  @Override
+  public boolean isShowComplementFeaturesOnTop()
+  {
+    return viewStyle.isShowComplementFeaturesOnTop();
+  }
+
+  /**
+   * @return true if view should scroll to show the highlighted region of a
+   *         sequence
+   * @return
+   */
+  @Override
+  public final boolean isFollowHighlight()
+  {
+    return followHighlight;
+  }
+
+  @Override
+  public final void setFollowHighlight(boolean b)
+  {
+    this.followHighlight = b;
+  }
+
+  @Override
+  public ViewportRanges getRanges()
+  {
+    return ranges;
+  }
+
+  /**
+   * Helper method to populate the SearchResults with the location in the
+   * complementary alignment to scroll to, in order to match this one.
+   * 
+   * @param sr
+   *          the SearchResults to add to
+   * @return the offset (below top of visible region) of the matched sequence
+   */
+  protected int findComplementScrollTarget(SearchResultsI sr)
+  {
+    final AlignViewportI complement = getCodingComplement();
+    if (complement == null || !complement.isFollowHighlight())
+    {
+      return 0;
+    }
+    boolean iAmProtein = !getAlignment().isNucleotide();
+    AlignmentI proteinAlignment = iAmProtein ? getAlignment()
+            : complement.getAlignment();
+    if (proteinAlignment == null)
+    {
+      return 0;
+    }
+    final List<AlignedCodonFrame> mappings = proteinAlignment
+            .getCodonFrames();
+
+    /*
+     * Heuristic: find the first mapped sequence (if any) with a non-gapped
+     * residue in the middle column of the visible region. Scroll the
+     * complementary alignment to line up the corresponding residue.
+     */
+    int seqOffset = 0;
+    SequenceI sequence = null;
+
+    /*
+     * locate 'middle' column (true middle if an odd number visible, left of
+     * middle if an even number visible)
+     */
+    int middleColumn = ranges.getStartRes()
+            + (ranges.getEndRes() - ranges.getStartRes()) / 2;
+    final HiddenSequences hiddenSequences = getAlignment()
+            .getHiddenSequences();
+
+    /*
+     * searching to the bottom of the alignment gives smoother scrolling across
+     * all gapped visible regions
+     */
+    int lastSeq = alignment.getHeight() - 1;
+    List<AlignedCodonFrame> seqMappings = null;
+    for (int seqNo = ranges
+            .getStartSeq(); seqNo <= lastSeq; seqNo++, seqOffset++)
+    {
+      sequence = getAlignment().getSequenceAt(seqNo);
+      if (hiddenSequences != null && hiddenSequences.isHidden(sequence))
+      {
+        continue;
+      }
+      if (Comparison.isGap(sequence.getCharAt(middleColumn)))
+      {
+        continue;
+      }
+      seqMappings = MappingUtils.findMappingsForSequenceAndOthers(sequence,
+              mappings,
+              getCodingComplement().getAlignment().getSequences());
+      if (!seqMappings.isEmpty())
+      {
+        break;
+      }
+    }
+
+    if (sequence == null || seqMappings == null || seqMappings.isEmpty())
+    {
+      /*
+       * No ungapped mapped sequence in middle column - do nothing
+       */
+      return 0;
+    }
+    MappingUtils.addSearchResults(sr, sequence,
+            sequence.findPosition(middleColumn), seqMappings);
+    return seqOffset;
+  }
+
+  /**
+   * synthesize a column selection if none exists so it covers the given
+   * selection group. if wholewidth is false, no column selection is made if the
+   * selection group covers the whole alignment width.
+   * 
+   * @param sg
+   * @param wholewidth
+   */
+  public void expandColSelection(SequenceGroup sg, boolean wholewidth)
+  {
+    int sgs, sge;
+    if (sg != null && (sgs = sg.getStartRes()) >= 0
+            && sg.getStartRes() <= (sge = sg.getEndRes())
+            && !this.hasSelectedColumns())
+    {
+      if (!wholewidth && alignment.getWidth() == (1 + sge - sgs))
+      {
+        // do nothing
+        return;
+      }
+      if (colSel == null)
+      {
+        colSel = new ColumnSelection();
+      }
+      for (int cspos = sg.getStartRes(); cspos <= sg.getEndRes(); cspos++)
+      {
+        colSel.addElement(cspos);
+      }
+    }
+  }
+
+  /**
+   * hold status of current selection group - defined on alignment or not.
+   */
+  private boolean selectionIsDefinedGroup = false;
+
+  @Override
+  public boolean isSelectionDefinedGroup()
+  {
+    if (selectionGroup == null)
+    {
+      return false;
+    }
+    if (isSelectionGroupChanged(true))
+    {
+      selectionIsDefinedGroup = false;
+      List<SequenceGroup> gps = alignment.getGroups();
+      if (gps == null || gps.size() == 0)
+      {
+        selectionIsDefinedGroup = false;
+      }
+      else
+      {
+        selectionIsDefinedGroup = gps.contains(selectionGroup);
+      }
+    }
+    return selectionGroup.isDefined() || selectionIsDefinedGroup;
+  }
+
+  /**
+   * null, or currently highlighted results on this view
+   */
+  private SearchResultsI searchResults = null;
+
+  protected TreeModel currentTree = null;
+
+  @Override
+  public boolean hasSearchResults()
+  {
+    return searchResults != null;
+  }
+
+  @Override
+  public void setSearchResults(SearchResultsI results)
+  {
+    searchResults = results;
+  }
+
+  @Override
+  public SearchResultsI getSearchResults()
+  {
+    return searchResults;
+  }
+
+  /**
+   * get the consensus sequence as displayed under the PID consensus annotation
+   * row.
+   * 
+   * @return consensus sequence as a new sequence object
+   */
+  public SequenceI getConsensusSeq()
+  {
+    if (consensus == null)
+    {
+      updateConsensus(null);
+    }
+    if (consensus == null)
+    {
+      return null;
+    }
+    StringBuffer seqs = new StringBuffer();
+    for (int i = 0; i < consensus.annotations.length; i++)
+    {
+      Annotation annotation = consensus.annotations[i];
+      if (annotation != null)
+      {
+        String description = annotation.description;
+        if (description != null && description.startsWith("["))
+        {
+          // consensus is a tie - just pick the first one
+          seqs.append(description.charAt(1));
+        }
+        else
+        {
+          seqs.append(annotation.displayCharacter);
+        }
+      }
+    }
+
+    SequenceI sq = new Sequence("Consensus", seqs.toString());
+    sq.setDescription("Percentage Identity Consensus "
+            + ((ignoreGapsInConsensusCalculation) ? " without gaps" : ""));
+    return sq;
+  }
+
+  @Override
+  public void setCurrentTree(TreeModel tree)
+  {
+    currentTree = tree;
+  }
+
+  @Override
+  public TreeModel getCurrentTree()
+  {
+    return currentTree;
+  }
+
+  /**
+   * flag set to indicate if structure views might be out of sync with sequences
+   * in the alignment
+   */
+
+  private boolean needToUpdateStructureViews = false;
+
+  @Override
+  public boolean isUpdateStructures()
+  {
+    return needToUpdateStructureViews;
+  }
+
+  @Override
+  public void setUpdateStructures(boolean update)
+  {
+    needToUpdateStructureViews = update;
+  }
+
+  @Override
+  public boolean needToUpdateStructureViews()
+  {
+    boolean update = needToUpdateStructureViews;
+    needToUpdateStructureViews = false;
+    return update;
+  }
+
+  @Override
+  public void addSequenceGroup(SequenceGroup sequenceGroup)
+  {
+    alignment.addGroup(sequenceGroup);
+
+    Color col = sequenceGroup.idColour;
+    if (col != null)
+    {
+      col = col.brighter();
+
+      for (SequenceI sq : sequenceGroup.getSequences())
+      {
+        setSequenceColour(sq, col);
+      }
+    }
+
+    if (codingComplement != null)
+    {
+      SequenceGroup mappedGroup = MappingUtils
+              .mapSequenceGroup(sequenceGroup, this, codingComplement);
+      if (mappedGroup.getSequences().size() > 0)
+      {
+        codingComplement.getAlignment().addGroup(mappedGroup);
+
+        if (col != null)
+        {
+          for (SequenceI seq : mappedGroup.getSequences())
+          {
+            codingComplement.setSequenceColour(seq, col);
+          }
+        }
+      }
+      // propagate the structure view update flag according to our own setting
+      codingComplement.setUpdateStructures(needToUpdateStructureViews);
+    }
+  }
+
+  @Override
+  public Iterator<int[]> getViewAsVisibleContigs(boolean selectedRegionOnly)
+  {
+    int start = 0;
+    int end = 0;
+    if (selectedRegionOnly && selectionGroup != null)
+    {
+      start = selectionGroup.getStartRes();
+      end = selectionGroup.getEndRes() + 1;
+    }
+    else
+    {
+      end = alignment.getWidth();
+    }
+    return (alignment.getHiddenColumns().getVisContigsIterator(start, end,
+            false));
+  }
 }