Merge branch 'develop' into features/mchmmer
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 20 Feb 2018 14:32:36 +0000 (14:32 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 20 Feb 2018 14:32:36 +0000 (14:32 +0000)
Conflicts:
resources/lang/Messages.properties
src/jalview/api/AlignViewportI.java
src/jalview/datamodel/AlignmentAnnotation.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceI.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/Preferences.java
src/jalview/jbgui/GAlignFrame.java
src/jalview/jbgui/GPreferences.java
src/jalview/schemes/ResidueProperties.java
src/jalview/viewmodel/AlignmentViewport.java

28 files changed:
1  2 
resources/lang/Messages.properties
src/jalview/analysis/AAFrequency.java
src/jalview/analysis/SeqsetUtils.java
src/jalview/api/AlignViewportI.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/AlignViewport.java
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/AlignmentAnnotation.java
src/jalview/datamodel/AlignmentI.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceGroup.java
src/jalview/datamodel/SequenceI.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/Preferences.java
src/jalview/io/AlignFile.java
src/jalview/io/FileLoader.java
src/jalview/io/IdentifyFile.java
src/jalview/io/StockholmFile.java
src/jalview/jbgui/GAlignFrame.java
src/jalview/jbgui/GPreferences.java
src/jalview/renderer/AnnotationRenderer.java
src/jalview/renderer/ResidueShader.java
src/jalview/schemes/ResidueProperties.java
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/workers/InformationThread.java

@@@ -11,7 -11,6 +11,7 @@@ action.paste = Past
  action.show_html_source = Show HTML Source
  action.print = Print...
  action.web_service = Web Service
 +action.hmmer = HMMER
  action.cancel_job = Cancel Job
  action.start_job = Start Job
  action.revert = Revert
@@@ -243,7 -242,6 +243,6 @@@ label.documentation = Documentatio
  label.about = About...
  label.show_sequence_limits = Show Sequence Limits
  action.feature_settings = Feature Settings...
- label.feature_settings = Feature Settings
  label.all_columns = All Columns
  label.all_sequences = All Sequences
  label.selected_columns = Selected Columns 
@@@ -275,6 -273,7 +274,7 @@@ label.chimera_missing = Chimera structu
  label.chimera_failed = Error opening Chimera - is it installed?\nCheck path in Preferences, Structure
  label.min_colour = Minimum Colour
  label.max_colour = Maximum Colour
+ label.no_colour = No Colour
  label.use_original_colours = Use Original Colours
  label.threshold_minmax = Threshold is min/max
  label.represent_group_with = Represent Group with {0}
@@@ -282,9 -281,9 +282,9 @@@ label.selection = Selectio
  label.group_colour = Group Colour
  label.sequence = Sequence
  label.view_pdb_structure = View PDB Structure
- label.min = Min:
- label.max = Max:
- label.colour_by_label = Colour by label
+ label.min_value = Min value
+ label.max_value = Max value
+ label.no_value = No value
  label.new_feature = New Feature
  label.match_case = Match Case
  label.view_alignment_editor = View in alignment editor
@@@ -369,6 -368,8 +369,8 @@@ label.optimise_order = Optimise Orde
  label.seq_sort_by_score = Sequence sort by Score
  label.load_colours = Load Colours
  label.save_colours = Save Colours
+ label.load_colours_tooltip = Load feature colours and filters from file
+ label.save_colours_tooltip = Save feature colours and filters to file
  label.fetch_das_features = Fetch DAS Features
  label.selected_database_to_fetch_from = Selected {0} database {1} to fetch from {2} 
  label.database_param = Database: {0}
@@@ -491,6 -492,10 +493,10 @@@ label.settings_for_type = Settings for 
  label.view_full_application = View in Full Application
  label.load_associated_tree = Load Associated Tree...
  label.load_features_annotations = Load Features/Annotations...
+ label.load_vcf = Load SNP variants from plain text or indexed VCF data
+ label.load_vcf_file = Load VCF File
+ label.searching_vcf = Loading VCF variants...
+ label.added_vcf = Added {0} VCF variants to {1} sequence(s)
  label.export_features = Export Features...
  label.export_annotations = Export Annotations...
  label.to_upper_case = To Upper Case
@@@ -529,7 -534,6 +535,6 @@@ label.threshold_feature_above_threshol
  label.threshold_feature_below_threshold = Below Threshold
  label.adjust_threshold = Adjust threshold
  label.toggle_absolute_relative_display_threshold = Toggle between absolute and relative display threshold.
- label.display_features_same_type_different_label_using_different_colour = Display features of the same type with a different label using a different colour. (e.g. domain features)
  label.select_colour_minimum_value = Select Colour for Minimum Value
  label.select_colour_maximum_value = Select Colour for Maximum Value
  label.open_url_param = Open URL {0}
@@@ -674,7 -678,8 +679,8 @@@ label.2d_rna_structure_line = 2D RNA {0
  label.2d_rna_sequence_name = 2D RNA - {0}
  label.edit_name_and_description_current_group = Edit name and description of current group
  label.from_file = From File
- label.enter_pdb_id = Enter PDB Id (or pdbid:chaincode)
+ label.enter_pdb_id = Enter PDB Id
+ label.enter_pdb_id_tip = Enter PDB Id (or pdbid:chaincode)
  label.text_colour = Text Colour...
  label.structure = Structure
  label.show_pdbstruct_dialog = 3D Structure Data...
@@@ -780,7 -785,7 +786,7 @@@ label.pairwise_aligned_sequences = Pair
  label.original_data_for_params = Original Data for {0}
  label.points_for_params = Points for {0}
  label.transformed_points_for_params = Transformed points for {0}
- label.graduated_color_for_params = Graduated Feature Colour for {0}
+ label.variable_color_for = Variable Feature Colour for {0}
  label.select_background_colour = Select Background Colour
  label.invalid_font = Invalid Font
  label.separate_multiple_accession_ids = Enter one or more accession IDs separated by a semi-colon ";"
@@@ -867,7 -872,7 +873,7 @@@ label.msa_service_is_unknown = The Mult
  label.service_called_is_not_seq_search_service = The Service called \n{0}\nis not a \nSequence Search Service\!
  label.seq_search_service_is_unknown = The Sequence Search Service named {0} is unknown
  label.feature_type = Feature Type
- label.display = Display
+ label.show = Show
  label.service_url = Service URL
  label.copied_sequences = Copied sequences
  label.cut_sequences = Cut Sequences
@@@ -914,7 -919,6 +920,6 @@@ label.as_percentage = As Percentag
  error.not_implemented = Not implemented
  error.no_such_method_as_clone1_for = No such method as clone1 for {0}
  error.null_from_clone1 = Null from clone1!
- error.implementation_error_sortbyfeature = Implementation Error - sortByFeature method must be one of FEATURE_SCORE, FEATURE_LABEL or FEATURE_DENSITY.
  error.not_yet_implemented = Not yet implemented
  error.unknown_type_dna_or_pep = Unknown Type {0} - dna or pep are the only allowed values.
  error.implementation_error_dont_know_threshold_annotationcolourgradient = Implementation error: don't know about threshold setting for current AnnotationColourGradient.
@@@ -1143,9 -1147,6 +1148,9 @@@ status.loading_cached_pdb_entries = Loa
  status.searching_for_pdb_structures = Searching for PDB Structures
  status.opening_file_for = opening file for
  status.colouring_chimera = Colouring Chimera
 +status.running_hmmbuild = Building Hidden Markov Model
 +status.running_hmmalign = Creating alignment with Hidden Markov Model
 +status.running_hmmsearch = Searching for matching sequences
  label.font_doesnt_have_letters_defined = Font doesn't have letters defined\nso cannot be used\nwith alignment data
  label.font_too_small = Font size is too small
  label.error_loading_file_params = Error loading file {0}
@@@ -1300,7 -1301,6 +1305,6 @@@ label.database = Databas
  label.urltooltip = Only one url, which must use a sequence id, can be selected for the 'On Click' option
  label.edit_sequence_url_link = Edit sequence URL link
  warn.name_cannot_be_duplicate = User-defined URL names must be unique and cannot be MIRIAM ids
- label.invalid_name = Invalid Name !
  label.output_seq_details = Output Sequence Details to list all database references
  label.urllinks = Links
  label.default_cache_size = Default Cache Size
@@@ -1315,65 -1315,55 +1319,117 @@@ label.occupancy_descr = Number of align
  label.show_experimental = Enable experimental features
  label.show_experimental_tip = Enable any new and currently 'experimental' features (see Latest Release Notes for details)
  label.warning_hidden = Warning: {0} {1} is currently hidden
+ label.overview_settings = Overview settings
+ label.ov_legacy_gap = Use legacy gap colouring (gaps are white)
+ label.gap_colour = Gap colour:
+ label.ov_show_hide_default = Show hidden regions when opening overview
+ label.hidden_colour = Hidden colour:
+ label.select_gap_colour = Select gap colour
+ label.select_hidden_colour = Select hidden colour
+ label.overview = Overview
+ label.reset_to_defaults = Reset to defaults
+ label.oview_calc = Recalculating overview...
+ label.feature_details = Feature details
+ label.matchCondition_contains = Contains
+ label.matchCondition_notcontains = Does not contain
+ label.matchCondition_matches = Matches
+ label.matchCondition_notmatches = Does not match
+ label.matchCondition_present = Is present
+ label.matchCondition_notpresent = Is not present
+ label.matchCondition_eq = =
+ label.matchCondition_ne = not =
+ label.matchCondition_lt = <
+ label.matchCondition_le = <=
+ label.matchCondition_gt = >
+ label.matchCondition_ge = >=
+ label.numeric_required = The value should be numeric
+ label.filter = Filter
+ label.filters = Filters
+ label.join_conditions = Join conditions with
+ label.score = Score
+ label.colour_by_label = Colour by label
+ label.variable_colour = Variable colour...
+ label.select_colour = Select colour
+ option.enable_disable_autosearch = When ticked, search is performed automatically
+ option.autosearch = Autosearch
+ label.retrieve_ids = Retrieve IDs
+ label.display_settings_for = Display settings for {0} features
+ label.simple = Simple
+ label.simple_colour = Simple Colour
+ label.colour_by_text = Colour by text
+ label.graduated_colour = Graduated Colour
+ label.by_text_of = By text of
+ label.by_range_of = By range of
+ label.filters_tooltip = Click to set or amend filters
+ label.or = Or
+ label.and = And
+ label.sequence_feature_colours = Sequence Feature Colours
+ label.best_quality = Best Quality
+ label.best_resolution = Best Resolution
+ label.most_protein_chain = Most Protein Chain
+ label.most_bound_molecules = Most Bound Molecules
+ label.most_polymer_residues = Most Polymer Residues
+ label.cached_structures = Cached Structures
+ label.free_text_search = Free Text Search
 +label.hmmalign = hmmalign
 +label.hmmbuild = hmmbuild
 +label.hmmbuild_group = Build HMM from Selected Group
 +label.group_hmmbuild = Build HMM from Group
 +label.hmmsearch = hmmsearch
 +label.hmmer_location = HMMER Binaries Installation Location
 +warn.null_hmm = Please ensure the alignment contains a hidden Markov model.
 +label.ignore_below_background_frequency = Ignore Below Background Frequency
 +label.information_description = Information content, measured in bits
 +warn.no_selected_hmm = Please select a hidden Markov model sequence.
 +label.select_hmm = Select HMM
 +warn.no_sequence_data = No sequence data found.
 +warn.empty_grp_or_alignment = An empty group or alignment was found.
 +label.no_sequences_found = No matching sequences, or an error occurred.
 +label.hmmer = HMMER
 +label.trim_termini = Trim Non-Matching Termini
 +label.trim_termini_desc = If true, non-matching regions on either end of the resulting alignment are removed.
 +label.no_of_sequences = Sequences Returned
 +label.freq_alignment = Use Alignment Background Frequencies
 +label.freq_uniprot = Use Uniprot Background Frequencies
 +label.hmmalign_label = hmmalign Options
 +label.hmmsearch_label = hmmsearch Options
 +label.hmmbuild_not_found = The hmmbuild binary was not found
 +label.hmmalign_not_found = The hmmalign binary was not found
 +label.hmmsearch_not_found = The hmmsearch binary was not found
 +warn.hmm_command_failed = hmm command not found
 +label.invalid_folder = Invalid Folder
 +label.folder_not_exists = HMMER binaries not found. \n Please enter the path to the HMMER binaries (if installed).
 +label.hmmer_installed = HMMER installed
 +label.hmmer_no_sequences_found = No sequences found
 +label.number_of_results = Number of Results to Return
 +label.auto_align_seqs = Automatically Align Fetched Sequences
 +label.use_accessions = Return Accessions
 +label.seq_e_value = Sequence E-value Cutoff
 +label.seq_score = Sequence Score Threshold
 +label.dom_e_value = Domain E-value Cutoff
 +label.dom_score = Domain Score Threshold
 +label.number_of_results_desc = The maximum number of results that hmmsearch will return
 +label.auto_align_seqs_desc = If true, all fetched sequences will be aligned to the hidden Markov model with which the search was performed
 +label.use_accessions_desc = If true, the accession number of each sequence is returned, rather than that sequences name
 +label.seq_e_value_desc = The E-value cutoff for returned sequences
 +label.seq_score_desc = The score threshold for returned sequences
 +label.dom_e_value_desc = The E-value cutoff for returned domains
 +label.dom_score_desc = The score threshold for returned domains
 +label.not_enough_sequences = There are not enough sequences to run {0}
 +label.add_database = Add Database
 +label.this_alignment = This alignment
 +warn.file_not_exists = File does not exist
 +warn.invalid_format = This is not a valid database file format. The current supported formats are Fasta, Stockholm and Pfam.
 +label.database_for_hmmsearch = The database hmmsearch will search through
 +label.use_reference = Use Reference Annotation
 +label.use_reference_desc = If true, hmmbuild will keep all columns defined as a reference position by the reference annotation
 +label.hmm_name = HMM Name
 +label.hmm_name_desc = The name given to the HMM.
 +warn.no_reference_annotation = No reference annotation found.
 +label.hmmbuild_for = Build HMM for
 +label.hmmbuild_for_desc = Build an HMM for the selected sequence groups.
 +label.alignment = Alignment
 +label.groups_and_alignment = All groups and alignment
 +label.groups = All groups
 +label.selected_group = Selected group
 +label.use_info_for_height = Use Information Content as Letter Height
@@@ -24,7 -24,6 +24,7 @@@ import jalview.datamodel.AlignedCodonFr
  import jalview.datamodel.AlignmentAnnotation;
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.Annotation;
 +import jalview.datamodel.HiddenMarkovModel;
  import jalview.datamodel.Profile;
  import jalview.datamodel.ProfileI;
  import jalview.datamodel.Profiles;
@@@ -33,7 -32,6 +33,7 @@@ import jalview.datamodel.ResidueCount
  import jalview.datamodel.ResidueCount.SymbolCounts;
  import jalview.datamodel.SequenceI;
  import jalview.ext.android.SparseIntArray;
 +import jalview.schemes.ResidueProperties;
  import jalview.util.Comparison;
  import jalview.util.Format;
  import jalview.util.MappingUtils;
@@@ -57,12 -55,6 +57,12 @@@ public class AAFrequenc
  {
    public static final String PROFILE = "P";
  
 +  private static final String AMINO = "amino";
 +
 +  private static final String DNA = "DNA";
 +
 +  private static final String RNA = "RNA";
 +
    /*
     * Quick look-up of String value of char 'A' to 'Z'
     */
      }
    }
  
 +
 +
    /**
     * Calculate the consensus symbol(s) for each column in the given range.
     * 
                    "WARNING: Consensus skipping null sequence - possible race condition.");
            continue;
          }
-         char[] seq = sequences[row].getSequence();
-         if (seq.length > column)
+         if (sequences[row].getLength() > column)
          {
-           char c = seq[column];
+           char c = sequences[row].getCharAt(column);
            residueCounts.add(c);
            if (Comparison.isNucleotide(c))
            {
    }
  
    /**
 +   * Returns the full set of profiles for a hidden Markov model. The underlying
 +   * data is the raw probabilities of a residue being emitted at each node,
 +   * however the profiles returned by this function contain the percentage
 +   * chance of a residue emission.
 +   * 
 +   * @param hmm
 +   * @param width
 +   *          The width of the Profile array (Profiles) to be returned.
 +   * @param start
 +   *          The alignment column on which the first profile is based.
 +   * @param end
 +   *          The alignment column on which the last profile is based.
 +   * @param saveFullProfile
 +   *          Flag for saving the counts for each profile
 +   * @param removeBelowBackground
 +   *          Flag for removing any characters with a match emission probability
 +   *          less than its background frequency
 +   * @return
 +   */
 +  public static ProfilesI calculateHMMProfiles(final HiddenMarkovModel hmm,
 +          int width, int start, int end, boolean saveFullProfile,
 +          boolean removeBelowBackground, boolean infoLetterHeight)
 +  {
 +    ProfileI[] result = new ProfileI[width];
 +    int symbolCount = hmm.getNumberOfSymbols();
 +    for (int column = start; column < end; column++)
 +    {
 +      ResidueCount counts = new ResidueCount();
 +      for (char symbol : hmm.getSymbols())
 +      {
 +        int value = getAnalogueCount(hmm, column, symbol,
 +                removeBelowBackground, infoLetterHeight);
 +        counts.put(symbol, value);
 +      }
 +      int maxCount = counts.getModalCount();
 +      String maxResidue = counts.getResiduesForCount(maxCount);
 +      int gapCount = counts.getGapCount();
 +      ProfileI profile = new Profile(symbolCount, gapCount, maxCount,
 +              maxResidue);
 +
 +      if (saveFullProfile)
 +      {
 +        profile.setCounts(counts);
 +      }
 +
 +      result[column] = profile;
 +    }
 +    return new Profiles(result);
 +  }
 +
 +  /**
     * Make an estimate of the profile size we are going to compute i.e. how many
     * different characters may be present in it. Overestimating has a cost of
     * using more memory than necessary. Underestimating has a cost of needing to
    }
  
    /**
 +   * Derive the information annotations to be added to the alignment for
 +   * display. This does not recompute the raw data, but may be called on a
 +   * change in display options, such as 'ignore below background frequency',
 +   * which may in turn result in a change in the derived values.
 +   * 
 +   * @param information
 +   *          the annotation row to add annotations to
 +   * @param profiles
 +   *          the source information data
 +   * @param startCol
 +   *          start column (inclusive)
 +   * @param endCol
 +   *          end column (exclusive)
 +   * @param ignoreGaps
 +   *          if true, normalise residue percentages 
 +   * @param showSequenceLogo
 +   *          if true include all information symbols, else just show modal
 +   *          residue
 +   * @param nseq
 +   *          number of sequences
 +   */
 +  public static float completeInformation(AlignmentAnnotation information,
 +          ProfilesI profiles, int startCol, int endCol, long nseq,
 +          Float currentMax)
 +  {
 +    // long now = System.currentTimeMillis();
 +    if (information == null || information.annotations == null
 +            || information.annotations.length < endCol)
 +    {
 +      /*
 +       * called with a bad alignment annotation row 
 +       * wait for it to be initialised properly
 +       */
 +      return 0;
 +    }
 +
 +    Float max = 0f;
 +
 +    for (int i = startCol; i < endCol; i++)
 +    {
 +      ProfileI profile = profiles.get(i);
 +      if (profile == null)
 +      {
 +        /*
 +         * happens if sequences calculated over were 
 +         * shorter than alignment width
 +         */
 +        information.annotations[i] = null;
 +        return 0;
 +      }
 +
 +      HiddenMarkovModel hmm;
 +      
 +      SequenceI hmmSeq = information.sequenceRef;
 +      
 +      hmm = hmmSeq.getHMM();
 +      
 +      Float value = getInformationContent(i, hmm);
 +
 +      if (value > max)
 +      {
 +        max = value;
 +      }
 +
 +      String description = value + " bits";
 +      information.annotations[i] = new Annotation(
 +              Character.toString(Character
 +                      .toUpperCase(hmm.getConsensusAtAlignColumn(i))),
 +              description, ' ', value);
 +    }
 +    if (max > currentMax)
 +    {
 +      information.graphMax = max;
 +      return max;
 +    }
 +    else
 +    {
 +      information.graphMax = currentMax;
 +      return currentMax;
 +    }
 +  }
 +
 +  /**
     * Derive the gap count annotation row.
     * 
     * @param gaprow
      return result;
    }
  
 +
    /**
     * Extract a sorted extract of cDNA codon profile data. The returned array
     * contains
      for (int col = 0; col < cols; col++)
      {
        // todo would prefer a Java bean for consensus data
 -      Hashtable<String, int[]> columnHash = new Hashtable<String, int[]>();
 +      Hashtable<String, int[]> columnHash = new Hashtable<>();
        // #seqs, #ungapped seqs, counts indexed by (codon encoded + 1)
        int[] codonCounts = new int[66];
        codonCounts[0] = alignment.getSequences().size();
      }
      return scale;
    }
 +
 +  /**
 +   * Returns the information content at a specified column.
 +   * 
 +   * @param column
 +   *          Index of the column, starting from 0.
 +   * @return
 +   */
 +  public static float getInformationContent(int column,
 +          HiddenMarkovModel hmm)
 +  {
 +    float informationContent = 0f;
 +
 +    for (char symbol : hmm.getSymbols())
 +    {
 +      float freq = 0f;
 +      freq = ResidueProperties.backgroundFrequencies
 +              .get(hmm.getAlphabetType()).get(symbol);
 +      Double hmmProb = hmm.getMatchEmissionProbability(column, symbol);
 +      float prob = hmmProb.floatValue();
 +      informationContent += prob * (Math.log(prob / freq) / Math.log(2));
 +
 +    }
 +
 +    return informationContent;
 +  }
 +
 +  /**
 +   * Produces a HMM profile for a column in an alignment
 +   * 
 +   * @param aa
 +   *          Alignment annotation for which the profile is being calculated.
 +   * @param column
 +   *          Column in the alignment the profile is being made for.
 +   * @param removeBelowBackground
 +   *          Boolean indicating whether to ignore residues with probabilities
 +   *          less than their background frequencies.
 +   * @return
 +   */
 +  public static int[] extractHMMProfile(HiddenMarkovModel hmm, int column,
 +          boolean removeBelowBackground, boolean infoHeight)
 +  {
 +
 +    if (hmm != null)
 +    {
 +      int size = hmm.getNumberOfSymbols();
 +      char symbols[] = new char[size];
 +      int values[] = new int[size];
 +      List<Character> charList = hmm.getSymbols();
 +      Integer totalCount = 0;
 +
 +      for (int i = 0; i < size; i++)
 +      {
 +        char symbol = charList.get(i);
 +        symbols[i] = symbol;
 +        int value = getAnalogueCount(hmm, column, symbol,
 +                removeBelowBackground, infoHeight);
 +        values[i] = value;
 +        totalCount += value;
 +      }
 +
 +      QuickSort.sort(values, symbols);
 +
 +      int[] profile = new int[3 + size * 2];
 +
 +      profile[0] = AlignmentAnnotation.SEQUENCE_PROFILE;
 +      profile[1] = size;
 +      profile[2] = 100;
 +
 +      if (totalCount != 0)
 +      {
 +        int arrayPos = 3;
 +        for (int k = size - 1; k >= 0; k--)
 +        {
 +          Float percentage;
 +          Integer value = values[k];
 +          if (removeBelowBackground)
 +          {
 +            percentage = (value.floatValue() / totalCount.floatValue())
 +                    * 100;
 +          }
 +          else
 +          {
 +            percentage = value.floatValue() / 100f;
 +          }
 +          int intPercent = Math.round(percentage);
 +          profile[arrayPos] = symbols[k];
 +          profile[arrayPos + 1] = intPercent;
 +          arrayPos += 2;
 +        }
 +      }
 +      return profile;
 +    }
 +    return null;
 +  }
 +
 +  /**
 +   * Converts the emission probability of a residue at a column in the alignment
 +   * to a 'count' to allow for processing by the annotation renderer.
 +   * 
 +   * @param hmm
 +   * @param column
 +   * @param removeBelowBackground
 +   *          When true, this method returns 0 for any symbols with a match
 +   *          emission probability less than the background frequency.
 +   * @param symbol
 +   * @return
 +   */
 +  static int getAnalogueCount(HiddenMarkovModel hmm, int column,
 +          char symbol, boolean removeBelowBackground, boolean infoHeight)
 +  {
 +    Double value;
 +
 +    value = hmm.getMatchEmissionProbability(column, symbol);
 +    double freq;
 +
 +    freq = ResidueProperties.backgroundFrequencies
 +            .get(hmm.getAlphabetType()).get(symbol);
 +    if (value < freq && removeBelowBackground)
 +    {
 +      return 0;
 +    }
 +
 +    if (infoHeight)
 +    {
 +      value = value * (Math.log(value / freq) / Math.log(2));
 +    }
 +
 +    value = value * 10000;
 +    return Math.round(value.floatValue());
 +  }
  }
@@@ -20,7 -20,6 +20,7 @@@
   */
  package jalview.analysis;
  
 +import jalview.datamodel.HiddenMarkovModel;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.Sequence;
  import jalview.datamodel.SequenceFeature;
@@@ -28,6 -27,7 +28,7 @@@ import jalview.datamodel.SequenceI
  
  import java.util.Enumeration;
  import java.util.Hashtable;
+ import java.util.List;
  import java.util.Vector;
  
  public class SeqsetUtils
@@@ -35,7 -35,7 +36,7 @@@
  
    /**
     * Store essential properties of a sequence in a hashtable for later recovery
 -   * Keys are Name, Start, End, SeqFeatures, PdbId
 +   * Keys are Name, Start, End, SeqFeatures, PdbId, HMM
     * 
     * @param seq
     *          SequenceI
      {
        sqinfo.put("Description", seq.getDescription());
      }
-     Vector sfeat = new Vector();
-     jalview.datamodel.SequenceFeature[] sfarray = seq.getSequenceFeatures();
-     if (sfarray != null && sfarray.length > 0)
-     {
-       for (int i = 0; i < sfarray.length; i++)
-       {
-         sfeat.addElement(sfarray[i]);
-       }
-     }
+     Vector<SequenceFeature> sfeat = new Vector<SequenceFeature>();
+     List<SequenceFeature> sfs = seq.getFeatures().getAllFeatures();
+     sfeat.addAll(sfs);
      if (seq.getDatasetSequence() == null)
      {
        sqinfo.put("SeqFeatures", sfeat);
                (seq.getDatasetSequence() != null) ? seq.getDatasetSequence()
                        : new Sequence("THISISAPLACEHOLDER", ""));
      }
 +    if (seq.isHMMConsensusSequence())
 +    {
 +      sqinfo.put("HMM", seq.getHMM());
 +    }
      return sqinfo;
    }
  
      String oldname = (String) sqinfo.get("Name");
      Integer start = (Integer) sqinfo.get("Start");
      Integer end = (Integer) sqinfo.get("End");
-     Vector sfeatures = (Vector) sqinfo.get("SeqFeatures");
+     Vector<SequenceFeature> sfeatures = (Vector<SequenceFeature>) sqinfo
+             .get("SeqFeatures");
      Vector<PDBEntry> pdbid = (Vector<PDBEntry>) sqinfo.get("PdbId");
      String description = (String) sqinfo.get("Description");
      Sequence seqds = (Sequence) sqinfo.get("datasetSequence");
 +    HiddenMarkovModel hmm = (HiddenMarkovModel) sqinfo.get("HMM");
      if (oldname == null)
      {
        namePresent = false;
        sq.setEnd(end.intValue());
      }
  
-     if ((sfeatures != null) && (sfeatures.size() > 0))
+     if (sfeatures != null && !sfeatures.isEmpty())
      {
-       SequenceFeature[] sfarray = new SequenceFeature[sfeatures.size()];
-       for (int is = 0, isize = sfeatures.size(); is < isize; is++)
-       {
-         sfarray[is] = (SequenceFeature) sfeatures.elementAt(is);
-       }
-       sq.setSequenceFeatures(sfarray);
+       sq.setSequenceFeatures(sfeatures);
      }
      if (description != null)
      {
        sq.setDatasetSequence(seqds);
      }
  
 +    if (hmm != null)
 +    {
 +      sq.setHMM(new HiddenMarkovModel(hmm));
 +      sq.setIsHMMConsensusSequence(true);
 +    }
      return namePresent;
    }
  
  package jalview.api;
  
  import jalview.analysis.Conservation;
+ import jalview.analysis.TreeModel;
  import jalview.datamodel.AlignmentAnnotation;
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.AlignmentView;
- import jalview.datamodel.CigarArray;
  import jalview.datamodel.ColumnSelection;
  import jalview.datamodel.ProfilesI;
  import jalview.datamodel.SearchResultsI;
@@@ -54,7 -54,7 +54,7 @@@ public interface AlignViewportI extend
     * 
     * @return
     */
--  public ViewportRanges getRanges();
++  ViewportRanges getRanges();
  
    /**
     * calculate the height for visible annotation, revalidating bounds where
@@@ -62,7 -62,7 +62,7 @@@
     * 
     * @return total height of annotation
     */
--  public int calcPanelHeight();
++  int calcPanelHeight();
  
    /**
     * Answers true if the viewport has at least one column selected
  
    boolean isNormaliseSequenceLogo();
  
 +  boolean isShowInformationHistogram();
 +
 +  boolean isShowHMMSequenceLogo();
 +
 +  boolean isNormaliseHMMSequenceLogo();
 +
    ColourSchemeI getGlobalColourScheme();
  
    /**
  
    boolean isIgnoreGapsConsensus();
  
 +  boolean isIgnoreBelowBackground();
 +
    boolean isCalculationInProgress(AlignmentAnnotation alignmentAnnotation);
  
    AlignmentAnnotation getAlignmentQualityAnnot();
    void clearSequenceColours();
  
    /**
-    * This method returns the visible alignment as text, as seen on the GUI, ie
-    * if columns are hidden they will not be returned in the result. Use this for
-    * calculating trees, PCA, redundancy etc on views which contain hidden
-    * columns.
-    * 
-    * @return String[]
-    */
-   CigarArray getViewAsCigars(boolean selectedRegionOnly);
-   /**
     * return a compact representation of the current alignment selection to pass
     * to an analysis function
     * 
     * 
     * @return a copy of this view's current display settings
     */
--  public ViewStyleI getViewStyle();
++  ViewStyleI getViewStyle();
  
    /**
     * update the view's display settings with the given style set
     * 
     * @param settingsForView
     */
--  public void setViewStyle(ViewStyleI settingsForView);
++  void setViewStyle(ViewStyleI settingsForView);
  
    /**
     * Returns a viewport which holds the cDna for this (protein), or vice versa,
     */
    void setFollowHighlight(boolean b);
  
--  public void applyFeaturesStyle(FeatureSettingsModelI featureSettings);
++  void applyFeaturesStyle(FeatureSettingsModelI featureSettings);
  
    /**
     * check if current selection group is defined on the view, or is simply a
    @Override
    void setProteinFontAsCdna(boolean b);
  
 -  public abstract TreeModel getCurrentTree();
 +  void setSequenceInformationHashes(List<ProfilesI> info);
 +
 +  List<ProfilesI> getSequenceInformationHashes();
 +
 +  ProfilesI getSequenceInformationHash(int index);
 +
 +  List<AlignmentAnnotation> getInformationAnnotations();
 +
 +  AlignmentAnnotation getInformationAnnotation(int index);
 +
 +  void setSequenceInformationHash(ProfilesI info, int index);
 +
 +  /**
 +   * Initiates the information annotation for all uninitiated sequences.
 +   */
 +  void initInformation();
 +
 +  /**
 +   * Updates all information annotations.
 +   * 
 +   * @param ap
 +   */
 +  void updateInformation(AlignmentViewPanel ap);
 +
 +  boolean isInfoLetterHeight();
 +
++  abstract TreeModel getCurrentTree();
 -  public abstract void setCurrentTree(TreeModel tree);
++  abstract void setCurrentTree(TreeModel tree);
  }
@@@ -234,7 -234,6 +234,7 @@@ public class AlignFrame extends Embmenu
              alignPanel);
      viewport.updateConservation(alignPanel);
      viewport.updateConsensus(alignPanel);
 +    viewport.updateInformation(alignPanel);
  
      displayNonconservedMenuItem.setState(viewport.getShowUnconserved());
      followMouseOverFlag.setState(viewport.isFollowHighlight());
      createAlignFrameWindow(embedded);
      validate();
      alignPanel.adjustAnnotationHeight();
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, true);
    }
  
    public AlignViewport getAlignViewport()
        {
          viewport.featureSettings.refreshTable();
        }
-       alignPanel.paintAlignment(true);
+       alignPanel.paintAlignment(true, true);
        statusBar.setText(MessageManager
                .getString("label.successfully_added_features_alignment"));
      }
        break;
  
      }
-     alignPanel.paintAlignment(true);
+     // TODO: repaint flags set only if the keystroke warrants it
+     alignPanel.paintAlignment(true, true);
    }
  
    /**
      {
        applyAutoAnnotationSettings_actionPerformed();
      }
-     alignPanel.paintAlignment(true);
+     // TODO: repaint flags set only if warranted
+     alignPanel.paintAlignment(true, true);
    }
  
    /**
      else if (source == invertColSel)
      {
        viewport.invertColumnSelection();
-       alignPanel.paintAlignment(true);
+       alignPanel.paintAlignment(false, false);
        viewport.sendSelection();
      }
      else if (source == remove2LeftMenuItem)
      else if (source == showColumns)
      {
        viewport.showAllHiddenColumns();
-       alignPanel.paintAlignment(true);
+       alignPanel.paintAlignment(true, true);
        viewport.sendSelection();
      }
      else if (source == showSeqs)
      {
        viewport.showAllHiddenSeqs();
-       alignPanel.paintAlignment(true);
+       alignPanel.paintAlignment(true, true);
        // uncomment if we want to slave sequence selections in split frame
        // viewport.sendSelection();
      }
      else if (source == hideColumns)
      {
        viewport.hideSelectedColumns();
-       alignPanel.paintAlignment(true);
+       alignPanel.paintAlignment(true, true);
        viewport.sendSelection();
      }
      else if (source == hideSequences
              && viewport.getSelectionGroup() != null)
      {
        viewport.hideAllSelectedSeqs();
-       alignPanel.paintAlignment(true);
+       alignPanel.paintAlignment(true, true);
        // uncomment if we want to slave sequence selections in split frame
        // viewport.sendSelection();
      }
      else if (source == hideAllButSelection)
      {
        toggleHiddenRegions(false, false);
-       alignPanel.paintAlignment(true);
+       alignPanel.paintAlignment(true, true);
        viewport.sendSelection();
      }
      else if (source == hideAllSelection)
        viewport.expandColSelection(sg, false);
        viewport.hideAllSelectedSeqs();
        viewport.hideSelectedColumns();
-       alignPanel.paintAlignment(true);
+       alignPanel.paintAlignment(true, true);
        viewport.sendSelection();
      }
      else if (source == showAllHidden)
      {
        viewport.showAllHiddenColumns();
        viewport.showAllHiddenSeqs();
-       alignPanel.paintAlignment(true);
+       alignPanel.paintAlignment(true, true);
        viewport.sendSelection();
      }
      else if (source == showGroupConsensus)
      return null;
    }
  
+   private List<String> getDisplayedFeatureGroups()
+   {
+     if (alignPanel.getFeatureRenderer() != null
+             && viewport.getFeaturesDisplayed() != null)
+     {
+       return alignPanel.getFeatureRenderer().getDisplayedFeatureGroups();
+     }
+     return null;
+   }
    public String outputFeatures(boolean displayTextbox, String format)
    {
      String features;
      {
        features = formatter.printJalviewFormat(
                viewport.getAlignment().getSequencesArray(),
-               getDisplayedFeatureCols());
+               getDisplayedFeatureCols(), null, getDisplayedFeatureGroups(),
+               true);
      }
      else
      {
-       features = formatter.printGffFormat(
-               viewport.getAlignment().getSequencesArray(),
-               getDisplayedFeatureCols());
+       features = formatter.printGffFormat(viewport.getAlignment()
+               .getSequencesArray(), getDisplayedFeatureCols(),
+               getDisplayedFeatureGroups(), true);
      }
  
      if (displayTextbox)
      {
        System.exit(0);
      }
-     else
+     viewport = null;
+     if (alignPanel != null && alignPanel.overviewPanel != null)
      {
+       alignPanel.overviewPanel.dispose();
      }
-     viewport = null;
      alignPanel = null;
      this.dispose();
    }
      {
        EditCommand editCommand = (EditCommand) command;
        al = editCommand.getAlignment();
-       Vector comps = (Vector) PaintRefresher.components
+       Vector comps = PaintRefresher.components
                .get(viewport.getSequenceSetId());
        for (int i = 0; i < comps.size(); i++)
        {
      }
      viewport.getAlignment().moveSelectedSequencesByOne(sg,
              up ? null : viewport.getHiddenRepSequences(), up);
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, false);
  
      /*
       * Also move cDNA/protein complement sequences
                viewport, complement);
        complement.getAlignment().moveSelectedSequencesByOne(mappedSelection,
                up ? null : complement.getHiddenRepSequences(), up);
-       getSplitFrame().getComplement(this).alignPanel.paintAlignment(true);
+       getSplitFrame().getComplement(this).alignPanel.paintAlignment(true,
+               false);
      }
    }
  
      {
        PaintRefresher.Refresh(this, viewport.getSequenceSetId());
        alignPanel.updateAnnotation();
-       alignPanel.paintAlignment(true);
+       alignPanel.paintAlignment(true, true);
      }
    }
  
      // JAL-2034 - should delegate to
      // alignPanel to decide if overview needs
      // updating.
-     alignPanel.paintAlignment(false);
+     alignPanel.paintAlignment(false, false);
      PaintRefresher.Refresh(alignPanel, viewport.getSequenceSetId());
      viewport.sendSelection();
    }
      // JAL-2034 - should delegate to
      // alignPanel to decide if overview needs
      // updating.
-     alignPanel.paintAlignment(false);
+     alignPanel.paintAlignment(false, false);
      PaintRefresher.Refresh(alignPanel, viewport.getSequenceSetId());
      viewport.sendSelection();
    }
    public void invertColSel_actionPerformed()
    {
      viewport.invertColumnSelection();
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, false);
      PaintRefresher.Refresh(alignPanel, viewport.getSequenceSetId());
      viewport.sendSelection();
    }
      PaintRefresher.Register(newaf.alignPanel.seqPanel.seqCanvas,
              newaf.alignPanel.av.getSequenceSetId());
  
-     Vector comps = (Vector) PaintRefresher.components
+     Vector comps = PaintRefresher.components
              .get(viewport.getSequenceSetId());
      int viewSize = -1;
      for (int i = 0; i < comps.size(); i++)
    {
      viewport.setShowJVSuffix(seqLimits.getState());
      alignPanel.fontChanged();
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, false);
    }
  
    protected void colourTextMenuItem_actionPerformed()
    {
      viewport.setColourText(colourTextMenuItem.getState());
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(false, false);
    }
  
    protected void displayNonconservedMenuItem_actionPerformed()
    {
      viewport.setShowUnconserved(displayNonconservedMenuItem.getState());
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(false, false);
    }
  
    protected void wrapMenuItem_actionPerformed()
      scaleAbove.setEnabled(wrapMenuItem.getState());
      scaleLeft.setEnabled(wrapMenuItem.getState());
      scaleRight.setEnabled(wrapMenuItem.getState());
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, false);
    }
  
    public void overviewMenuItem_actionPerformed()
    {
      viewport.setGlobalColourScheme(cs);
  
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, true);
    }
  
    protected void modifyPID_actionPerformed()
  
      addHistoryItem(new OrderCommand("Pairwise Sort", oldOrder,
              viewport.getAlignment()));
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, false);
    }
  
    public void sortIDMenuItem_actionPerformed()
      AlignmentSorter.sortByID(viewport.getAlignment());
      addHistoryItem(
              new OrderCommand("ID Sort", oldOrder, viewport.getAlignment()));
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, false);
    }
  
    public void sortLengthMenuItem_actionPerformed()
      AlignmentSorter.sortByLength(viewport.getAlignment());
      addHistoryItem(new OrderCommand("Length Sort", oldOrder,
              viewport.getAlignment()));
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, false);
    }
  
    public void sortGroupMenuItem_actionPerformed()
      AlignmentSorter.sortByGroup(viewport.getAlignment());
      addHistoryItem(new OrderCommand("Group Sort", oldOrder,
              viewport.getAlignment()));
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, false);
  
    }
  
            current.insertCharAt(Width - 1, viewport.getGapCharacter());
          }
        }
-       alignPanel.paintAlignment(true);
+       alignPanel.paintAlignment(false, false);
      }
  
      if ((viewport.getSelectionGroup() != null
            current.insertCharAt(Width - 1, viewport.getGapCharacter());
          }
        }
-       alignPanel.paintAlignment(true);
+       alignPanel.paintAlignment(false, false);
  
      }
  
      addHistoryItem(new OrderCommand(MessageManager
              .formatMessage("label.order_by_params", new String[]
              { title }), oldOrder, viewport.getAlignment()));
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, false);
    }
  
    /**
        addHistoryItem(new OrderCommand(undoname, oldOrder,
                viewport.getAlignment()));
      }
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, false);
      return true;
    }
  
      {
        // register the association(s) and quit, don't create any windows.
        if (StructureSelectionManager.getStructureSelectionManager(applet)
-               .setMapping(seqs, chains, pdb.getFile(), protocol) == null)
+               .setMapping(seqs, chains, pdb.getFile(), protocol, null) == null)
        {
          System.err.println("Failed to map " + pdb.getFile() + " ("
                  + protocol + ") to any sequences");
@@@ -20,7 -20,6 +20,6 @@@
   */
  package jalview.appletgui;
  
- import jalview.analysis.TreeModel;
  import jalview.api.AlignViewportI;
  import jalview.api.FeatureSettingsModelI;
  import jalview.bin.JalviewLite;
@@@ -54,23 -53,12 +53,12 @@@ public class AlignViewport extends Alig
  
    boolean validCharWidth = true;
  
-   TreeModel currentTree = null;
    public jalview.bin.JalviewLite applet;
  
    boolean MAC = false;
  
    private AnnotationColumnChooser annotationColumnSelectionState;
  
-   @Override
-   public void finalize()
-   {
-     applet = null;
-     quality = null;
-     alignment = null;
-     colSel = null;
-   }
    public AlignViewport(AlignmentI al, JalviewLite applet)
    {
      super(al);
      ranges.setEndSeq(height / getCharHeight());
    }
  
-   public void setCurrentTree(TreeModel tree)
-   {
-     currentTree = tree;
-   }
-   public TreeModel getCurrentTree()
-   {
-     return currentTree;
-   }
    boolean centreColumnLabels;
  
    public boolean getCentreColumnLabels()
      // TODO implement for applet
    }
  
 +  @Override
 +  public boolean isNormaliseHMMSequenceLogo()
 +  {
 +    return normaliseHMMSequenceLogo;
 +  }
 +
 +
 +
  }
@@@ -28,6 -28,7 +28,7 @@@ import jalview.util.LinkedIdentityHashS
  import jalview.util.MessageManager;
  
  import java.util.ArrayList;
+ import java.util.Arrays;
  import java.util.Collections;
  import java.util.Enumeration;
  import java.util.HashSet;
@@@ -287,32 -288,6 +288,32 @@@ public class Alignment implements Align
    }
  
    /**
 +   * Inserts a sequence at a point in the alignment.
 +   * 
 +   * @param i
 +   *          the index of the position the sequence is to be inserted in.
 +   */
 +  @Override
 +  public void insertSequenceAt(int i, SequenceI snew)
 +  {
 +    synchronized (sequences)
 +    {
 +      if (sequences.size() > i)
 +      {
 +        sequences.add(i, snew);
 +        return;
 +
 +      }
 +      else
 +      {
 +        sequences.add(snew);
 +        hiddenSequences.adjustHeightSequenceAdded();
 +      }
 +      return;
 +    }
 +  }
 +
 +  /**
     * DOCUMENT ME!
     * 
     * @return DOCUMENT ME!
    {
      // TODO JAL-1270 needs test coverage
      // currently tested for use in jalview.gui.SequenceFetcher
-     boolean samegap = toappend.getGapCharacter() == getGapCharacter();
      char oldc = toappend.getGapCharacter();
+     boolean samegap = oldc == getGapCharacter();
      boolean hashidden = toappend.getHiddenSequences() != null
              && toappend.getHiddenSequences().hiddenSequences != null;
      // get all sequences including any hidden ones
          {
            if (!samegap)
            {
-             char[] oldseq = addedsq.getSequence();
-             for (int c = 0; c < oldseq.length; c++)
-             {
-               if (oldseq[c] == oldc)
-               {
-                 oldseq[c] = gapCharacter;
-               }
-             }
+             addedsq.replace(oldc, gapCharacter);
            }
            toappendsq.add(addedsq);
          }
    @Override
    public Iterable<AlignmentAnnotation> findAnnotation(String calcId)
    {
      AlignmentAnnotation[] alignmentAnnotation = getAlignmentAnnotation();
      if (alignmentAnnotation != null)
      {
-       for (AlignmentAnnotation a : alignmentAnnotation)
-       {
-         if (a.getCalcId() == calcId || (a.getCalcId() != null
-                 && calcId != null && a.getCalcId().equals(calcId)))
-         {
-           aa.add(a);
-         }
-       }
+       return AlignmentAnnotation.findAnnotation(
+               Arrays.asList(getAlignmentAnnotation()), calcId);
      }
-     return aa;
+     return Arrays.asList(new AlignmentAnnotation[] {});
    }
  
    @Override
    public Iterable<AlignmentAnnotation> findAnnotations(SequenceI seq,
            String calcId, String label)
    {
-     ArrayList<AlignmentAnnotation> aa = new ArrayList<>();
-     for (AlignmentAnnotation ann : getAlignmentAnnotation())
-     {
-       if ((calcId == null || (ann.getCalcId() != null
-               && ann.getCalcId().equals(calcId)))
-               && (seq == null || (ann.sequenceRef != null
-                       && ann.sequenceRef == seq))
-               && (label == null
-                       || (ann.label != null && ann.label.equals(label))))
-       {
-         aa.add(ann);
-       }
-     }
-     return aa;
+     return AlignmentAnnotation.findAnnotations(
+             Arrays.asList(getAlignmentAnnotation()), seq, calcId, label);
    }
  
    @Override
    {
      hiddenCols = cols;
    }
 +
 +  /**
 +   * Returns all HMM consensus sequences. This will not return real sequences
 +   * with HMMs. If remove is set to true, the consensus sequences will be
 +   * removed from the alignment.
 +   */
 +  @Override // TODO make this more efficient.
 +  public List<SequenceI> getHMMConsensusSequences(boolean remove)
 +  {
 +    List<SequenceI> seqs = new ArrayList<>();
 +    int position = 0;
 +    int seqsRemoved = 0;
 +    boolean endReached = false;
 +
 +    while (!endReached)
 +    {
 +      SequenceI seq = sequences.get(position);
 +      if (seq.isHMMConsensusSequence())
 +      {
 +        if (remove)
 +        {
 +          sequences.remove(position);
 +          seqsRemoved++;
 +          seq.setPreviousPosition(seqsRemoved + position - 1);
 +        }
 +        else
 +        {
 +          position++;
 +        }
 +        seqs.add(seq);
 +      }
 +      else
 +      {
 +        position++;
 +      }
 +
 +      if (position >= sequences.size())
 +      {
 +        endReached = true;
 +      }
 +    }
 +    return seqs;
 +  }
  }
@@@ -24,6 -24,7 +24,7 @@@ import jalview.analysis.Rna
  import jalview.analysis.SecStrConsensus.SimpleBP;
  import jalview.analysis.WUSSParseException;
  
+ import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.HashMap;
@@@ -96,14 -97,13 +97,13 @@@ public class AlignmentAnnotatio
     * Updates the _rnasecstr field Determines the positions that base pair and
     * the positions of helices based on secondary structure from a Stockholm file
     * 
-    * @param RNAannot
+    * @param rnaAnnotation
     */
-   private void _updateRnaSecStr(CharSequence RNAannot)
+   private void _updateRnaSecStr(CharSequence rnaAnnotation)
    {
      try
      {
-       bps = Rna.getModeleBP(RNAannot);
-       _rnasecstr = Rna.getBasePairs(bps);
+       _rnasecstr = Rna.getHelixMap(rnaAnnotation);
        invalidrnastruc = -1;
      } catch (WUSSParseException px)
      {
      {
        return;
      }
-     Rna.HelixMap(_rnasecstr);
-     // setRNAStruc(RNAannot);
  
      if (_rnasecstr != null && _rnasecstr.length > 0)
      {
  
    private boolean isrna;
  
-   /*
-    * (non-Javadoc)
-    * 
-    * @see java.lang.Object#finalize()
-    */
-   @Override
-   protected void finalize() throws Throwable
-   {
-     sequenceRef = null;
-     groupRef = null;
-     super.finalize();
-   }
    public static int getGraphValueFromString(String string)
    {
      if (string.equalsIgnoreCase("BAR_GRAPH"))
      }
    }
  
-   // JBPNote: what does this do ?
-   public void ConcenStru(CharSequence RNAannot) throws WUSSParseException
-   {
-     bps = Rna.getModeleBP(RNAannot);
-   }
    /**
     * Creates a new AlignmentAnnotation object.
     * 
      this.calcId = annotation.calcId;
      if (annotation.properties != null)
      {
 -      properties = new HashMap<String, String>();
 +      properties = new HashMap<>();
        for (Map.Entry<String, String> val : annotation.properties.entrySet())
        {
          properties.put(val.getKey(), val.getValue());
        if (annotation.sequenceMapping != null)
        {
          Integer p = null;
 -        sequenceMapping = new HashMap<Integer, Annotation>();
 +        sequenceMapping = new HashMap<>();
          Iterator<Integer> pos = annotation.sequenceMapping.keySet()
                  .iterator();
          while (pos.hasNext())
        int epos = sequenceRef.findPosition(endRes);
        if (sequenceMapping != null)
        {
 -        Map<Integer, Annotation> newmapping = new HashMap<Integer, Annotation>();
 +        Map<Integer, Annotation> newmapping = new HashMap<>();
          Iterator<Integer> e = sequenceMapping.keySet().iterator();
          while (e.hasNext())
          {
      {
        return;
      }
 -    sequenceMapping = new HashMap<Integer, Annotation>();
 +    sequenceMapping = new HashMap<>();
  
      int seqPos;
  
    /**
     * properties associated with the calcId
     */
 -  protected Map<String, String> properties = new HashMap<String, String>();
 +  protected Map<String, String> properties = new HashMap<>();
  
    /**
     * base colour for line graphs. If null, will be set automatically by
              : false;
  
      // TODO build a better annotation element map and get rid of annotations[]
 -    Map<Integer, Annotation> mapForsq = new HashMap<Integer, Annotation>();
 +    Map<Integer, Annotation> mapForsq = new HashMap<>();
      if (sequenceMapping != null)
      {
        if (sp2sq != null)
      if (mapping != null)
      {
        Map<Integer, Annotation> old = sequenceMapping;
 -      Map<Integer, Annotation> remap = new HashMap<Integer, Annotation>();
 +      Map<Integer, Annotation> remap = new HashMap<>();
        int index = -1;
        for (int mp[] : mapping.values())
        {
    {
      if (properties == null)
      {
 -      properties = new HashMap<String, String>();
 +      properties = new HashMap<>();
      }
      properties.put(property, value);
    }
      return graphMin < graphMax;
    }
  
+   public static Iterable<AlignmentAnnotation> findAnnotations(
+           Iterable<AlignmentAnnotation> list, SequenceI seq, String calcId,
+           String label)
+   {
 -
 -    ArrayList<AlignmentAnnotation> aa = new ArrayList<>();
++    List<AlignmentAnnotation> aa = new ArrayList<>();
+     for (AlignmentAnnotation ann : list)
+     {
+       if ((calcId == null || (ann.getCalcId() != null
+               && ann.getCalcId().equals(calcId)))
+               && (seq == null || (ann.sequenceRef != null
+                       && ann.sequenceRef == seq))
+               && (label == null
+                       || (ann.label != null && ann.label.equals(label))))
+       {
+         aa.add(ann);
+       }
+     }
+     return aa;
+   }
+   /**
+    * Answer true if any annotation matches the calcId passed in (if not null).
+    * 
+    * @param list
+    *          annotation to search
+    * @param calcId
+    * @return
+    */
+   public static boolean hasAnnotation(List<AlignmentAnnotation> list,
+           String calcId)
+   {
+     if (calcId != null && !"".equals(calcId))
+     {
+       for (AlignmentAnnotation a : list)
+       {
+         if (a.getCalcId() == calcId)
+         {
+           return true;
+         }
+       }
+     }
+     return false;
+   }
+   public static Iterable<AlignmentAnnotation> findAnnotation(
+           List<AlignmentAnnotation> list, String calcId)
+   {
 -
+     List<AlignmentAnnotation> aa = new ArrayList<>();
+     if (calcId == null)
+     {
+       return aa;
+     }
+     for (AlignmentAnnotation a : list)
+     {
+       if (a.getCalcId() == calcId || (a.getCalcId() != null
+               && calcId != null && a.getCalcId().equals(calcId)))
+       {
+         aa.add(a);
+       }
+     }
+     return aa;
+   }
  }
@@@ -5,16 -5,16 +5,16 @@@
   * This file is part of Jalview.
   * 
   * Jalview is free software: you can redistribute it and/or
-  * modify it under the terms of the GNU General License 
+  * modify it under the terms of the GNU General Public License 
   * as published by the Free Software Foundation, either version 3
   * of the License, or (at your option) any later version.
   *  
   * Jalview is distributed in the hope that it will be useful, but 
   * WITHOUT ANY WARRANTY; without even the implied warranty 
   * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
-  * PURPOSE.  See the GNU General License for more details.
+  * PURPOSE.  See the GNU General Public License for more details.
   * 
-  * You should have received a copy of the GNU General License
+  * You should have received a copy of the GNU General Public License
   * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
   * The Jalview Authors are detailed in the 'AUTHORS' file.
   */
@@@ -582,16 -582,4 +582,16 @@@ public interface AlignmentI extends Ann
  
    public void setHiddenColumns(HiddenColumns cols);
  
 +  /**
 +   * Insert a sequence at a position in an alignment
 +   * 
 +   * @param i
 +   *          The idnex of the position.
 +   * @param snew
 +   *          The new sequence.
 +   */
 +  void insertSequenceAt(int i, SequenceI snew);
 +
 +  
 +
  }
@@@ -22,6 -22,8 +22,8 @@@ package jalview.datamodel
  
  import jalview.analysis.AlignSeq;
  import jalview.api.DBRefEntryI;
+ import jalview.datamodel.features.SequenceFeatures;
+ import jalview.datamodel.features.SequenceFeaturesI;
  import jalview.util.Comparison;
  import jalview.util.DBRefUtils;
  import jalview.util.MapList;
@@@ -33,6 -35,7 +35,7 @@@ import java.util.BitSet
  import java.util.Collections;
  import java.util.Enumeration;
  import java.util.List;
+ import java.util.ListIterator;
  import java.util.Vector;
  
  import fr.orsay.lri.varna.models.rna.RNA;
@@@ -52,20 -55,12 +55,20 @@@ public class Sequence extends ASequenc
  
    private char[] sequence;
  
 +  int previousPosition;
 +
    String description;
  
    int start;
  
    int end;
  
 +  boolean hasInfo;
 +
 +  HiddenMarkovModel hmm;
 +
 +  boolean isHMMConsensusSequence = false;
 +
    Vector<PDBEntry> pdbIds;
  
    String vamsasId;
     */
    Vector<AlignmentAnnotation> annotation;
  
-   /**
-    * The index of the sequence in a MSA
+   private SequenceFeaturesI sequenceFeatureStore;
+   /*
+    * A cursor holding the approximate current view position to the sequence,
+    * as determined by findIndex or findPosition or findPositions.
+    * Using a cursor as a hint allows these methods to be more performant for
+    * large sequences.
     */
-   int index = -1;
+   private SequenceCursor cursor;
  
-   /**
-    * array of sequence features - may not be null for a valid sequence object
+   /*
+    * A number that should be incremented whenever the sequence is edited.
+    * If the value matches the cursor token, then we can trust the cursor,
+    * if not then it should be recomputed. 
     */
-   public SequenceFeature[] sequenceFeatures;
+   private int changeCount;
  
    /**
     * Creates a new Sequence object.
     */
    public Sequence(String name, String sequence, int start, int end)
    {
+     this();
      initSeqAndName(name, sequence.toCharArray(), start, end);
    }
  
    public Sequence(String name, char[] sequence, int start, int end)
    {
+     this();
      initSeqAndName(name, sequence, start, end);
    }
  
      checkValidRange();
    }
  
-   com.stevesoft.pat.Regex limitrx = new com.stevesoft.pat.Regex(
-           "[/][0-9]{1,}[-][0-9]{1,}$");
-   com.stevesoft.pat.Regex endrx = new com.stevesoft.pat.Regex("[0-9]{1,}$");
+   /**
+    * If 'name' ends in /i-j, where i >= j > 0 are integers, extracts i and j as
+    * start and end respectively and removes the suffix from the name
+    */
    void parseId()
    {
      if (name == null)
                "POSSIBLE IMPLEMENTATION ERROR: null sequence name passed to constructor.");
        name = "";
      }
-     // Does sequence have the /start-end signature?
-     if (limitrx.search(name))
+     int slashPos = name.lastIndexOf('/');
+     if (slashPos > -1 && slashPos < name.length() - 1)
      {
-       name = limitrx.left();
-       endrx.search(limitrx.stringMatched());
-       setStart(Integer.parseInt(limitrx.stringMatched().substring(1,
-               endrx.matchedFrom() - 1)));
-       setEnd(Integer.parseInt(endrx.stringMatched()));
+       String suffix = name.substring(slashPos + 1);
+       String[] range = suffix.split("-");
+       if (range.length == 2)
+       {
+         try
+         {
+           int from = Integer.valueOf(range[0]);
+           int to = Integer.valueOf(range[1]);
+           if (from > 0 && to >= from)
+           {
+             name = name.substring(0, slashPos);
+             setStart(from);
+             setEnd(to);
+             checkValidRange();
+           }
+         } catch (NumberFormatException e)
+         {
+           // leave name unchanged if suffix is invalid
+         }
+       }
      }
    }
  
+   /**
+    * Ensures that 'end' is not before the end of the sequence, that is,
+    * (end-start+1) is at least as long as the count of ungapped positions. Note
+    * that end is permitted to be beyond the end of the sequence data.
+    */
    void checkValidRange()
    {
      // Note: JAL-774 :
        int endRes = 0;
        for (int j = 0; j < sequence.length; j++)
        {
-         if (!jalview.util.Comparison.isGap(sequence[j]))
+         if (!Comparison.isGap(sequence[j]))
          {
            endRes++;
          }
    }
  
    /**
+    * default constructor
+    */
+   private Sequence()
+   {
+     sequenceFeatureStore = new SequenceFeatures();
+   }
+   /**
     * Creates a new Sequence object.
     * 
     * @param name
     */
    public Sequence(SequenceI seq, AlignmentAnnotation[] alAnnotation)
    {
+     this();
      initSeqFrom(seq, alAnnotation);
    }
  
    /**
    protected void initSeqFrom(SequenceI seq,
            AlignmentAnnotation[] alAnnotation)
    {
-     {
-       char[] oseq = seq.getSequence();
-       initSeqAndName(seq.getName(), Arrays.copyOf(oseq, oseq.length),
-               seq.getStart(), seq.getEnd());
-     }
+     char[] oseq = seq.getSequence(); // returns a copy of the array
+     initSeqAndName(seq.getName(), oseq, seq.getStart(), seq.getEnd());
      description = seq.getDescription();
      if (seq != datasetSequence)
      {
        setDatasetSequence(seq.getDatasetSequence());
      }
-     if (datasetSequence == null && seq.getDBRefs() != null)
+     
+     /*
+      * only copy DBRefs and seqfeatures if we really are a dataset sequence
+      */
+     if (datasetSequence == null)
      {
-       // only copy DBRefs and seqfeatures if we really are a dataset sequence
-       DBRefEntry[] dbr = seq.getDBRefs();
-       for (int i = 0; i < dbr.length; i++)
+       if (seq.getDBRefs() != null)
        {
-         addDBRef(new DBRefEntry(dbr[i]));
-       }
-       if (seq.getSequenceFeatures() != null)
-       {
-         SequenceFeature[] sf = seq.getSequenceFeatures();
-         for (int i = 0; i < sf.length; i++)
+         DBRefEntry[] dbr = seq.getDBRefs();
+         for (int i = 0; i < dbr.length; i++)
          {
-           addSequenceFeature(new SequenceFeature(sf[i]));
+           addDBRef(new DBRefEntry(dbr[i]));
          }
        }
+       /*
+        * make copies of any sequence features
+        */
+       for (SequenceFeature sf : seq.getSequenceFeatures())
+       {
+         addSequenceFeature(new SequenceFeature(sf));
+       }
      }
      if (seq.getAnnotation() != null)
      {
        AlignmentAnnotation[] sqann = seq.getAnnotation();
          this.addPDBId(new PDBEntry(pdb));
        }
      }
 +    if (seq.isHMMConsensusSequence())
 +    {
 +      this.isHMMConsensusSequence = true;
 +    }
 +    if (seq.getHMM() != null)
 +    {
 +      this.hmm = new HiddenMarkovModel(seq.getHMM());
 +    }
 +
    }
  
    @Override
-   public void setSequenceFeatures(SequenceFeature[] features)
+   public void setSequenceFeatures(List<SequenceFeature> features)
    {
-     if (datasetSequence == null)
-     {
-       sequenceFeatures = features;
-     }
-     else
+     if (datasetSequence != null)
      {
-       if (datasetSequence.getSequenceFeatures() != features
-               && datasetSequence.getSequenceFeatures() != null
-               && datasetSequence.getSequenceFeatures().length > 0)
-       {
-         new Exception(
-                 "Warning: JAL-2046 side effect ? Possible implementation error: overwriting dataset sequence features by setting sequence features on alignment")
-                         .printStackTrace();
-       }
        datasetSequence.setSequenceFeatures(features);
+       return;
      }
+     sequenceFeatureStore = new SequenceFeatures(features);
    }
  
    @Override
    public synchronized boolean addSequenceFeature(SequenceFeature sf)
    {
-     if (sequenceFeatures == null && datasetSequence != null)
-     {
-       return datasetSequence.addSequenceFeature(sf);
-     }
-     if (sequenceFeatures == null)
+     if (sf.getType() == null)
      {
-       sequenceFeatures = new SequenceFeature[0];
+       System.err.println("SequenceFeature type may not be null: "
+               + sf.toString());
+       return false;
      }
  
-     for (int i = 0; i < sequenceFeatures.length; i++)
+     if (datasetSequence != null)
      {
-       if (sequenceFeatures[i].equals(sf))
-       {
-         return false;
-       }
+       return datasetSequence.addSequenceFeature(sf);
      }
  
-     SequenceFeature[] temp = new SequenceFeature[sequenceFeatures.length
-             + 1];
-     System.arraycopy(sequenceFeatures, 0, temp, 0, sequenceFeatures.length);
-     temp[sequenceFeatures.length] = sf;
-     sequenceFeatures = temp;
-     return true;
+     return sequenceFeatureStore.add(sf);
    }
  
    @Override
    public void deleteFeature(SequenceFeature sf)
    {
-     if (sequenceFeatures == null)
-     {
-       if (datasetSequence != null)
-       {
-         datasetSequence.deleteFeature(sf);
-       }
-       return;
-     }
-     int index = 0;
-     for (index = 0; index < sequenceFeatures.length; index++)
-     {
-       if (sequenceFeatures[index].equals(sf))
-       {
-         break;
-       }
-     }
-     if (index == sequenceFeatures.length)
-     {
-       return;
-     }
-     int sfLength = sequenceFeatures.length;
-     if (sfLength < 2)
+     if (datasetSequence != null)
      {
-       sequenceFeatures = null;
+       datasetSequence.deleteFeature(sf);
      }
      else
      {
-       SequenceFeature[] temp = new SequenceFeature[sfLength - 1];
-       System.arraycopy(sequenceFeatures, 0, temp, 0, index);
-       if (index < sfLength)
-       {
-         System.arraycopy(sequenceFeatures, index + 1, temp, index,
-                 sequenceFeatures.length - index - 1);
-       }
-       sequenceFeatures = temp;
+       sequenceFeatureStore.delete(sf);
      }
    }
  
    /**
-    * Returns the sequence features (if any), looking first on the sequence, then
-    * on its dataset sequence, and so on until a non-null value is found (or
-    * none). This supports retrieval of sequence features stored on the sequence
-    * (as in the applet) or on the dataset sequence (as in the Desktop version).
+    * {@inheritDoc}
     * 
     * @return
     */
    @Override
-   public SequenceFeature[] getSequenceFeatures()
+   public List<SequenceFeature> getSequenceFeatures()
    {
-     SequenceFeature[] features = sequenceFeatures;
-     SequenceI seq = this;
-     int count = 0; // failsafe against loop in sequence.datasetsequence...
-     while (features == null && seq.getDatasetSequence() != null
-             && count++ < 10)
+     if (datasetSequence != null)
      {
-       seq = seq.getDatasetSequence();
-       features = ((Sequence) seq).sequenceFeatures;
+       return datasetSequence.getSequenceFeatures();
      }
-     return features;
+     return sequenceFeatureStore.getAllFeatures();
+   }
+   @Override
+   public SequenceFeaturesI getFeatures()
+   {
+     return datasetSequence != null ? datasetSequence.getFeatures()
+             : sequenceFeatureStore;
    }
  
    @Override
    {
      if (pdbIds == null)
      {
 -      pdbIds = new Vector<PDBEntry>();
 +      pdbIds = new Vector<>();
        pdbIds.add(entry);
        return true;
      }
    @Override
    public Vector<PDBEntry> getAllPDBEntries()
    {
--    return pdbIds == null ? new Vector<PDBEntry>() : pdbIds;
++    return pdbIds == null ? new Vector<>() : pdbIds;
    }
  
    /**
    }
  
    /**
-    * DOCUMENT ME!
+    * Sets the sequence name. If the name ends in /start-end, then the start-end
+    * values are parsed out and set, and the suffix is removed from the name.
     * 
-    * @param name
-    *          DOCUMENT ME!
+    * @param theName
     */
    @Override
-   public void setName(String name)
+   public void setName(String theName)
    {
-     this.name = name;
+     this.name = theName;
      this.parseId();
    }
  
    {
      this.sequence = seq.toCharArray();
      checkValidRange();
+     sequenceChanged();
    }
  
    @Override
    @Override
    public char[] getSequence()
    {
-     return sequence;
+     // return sequence;
+     return sequence == null ? null : Arrays.copyOf(sequence,
+             sequence.length);
    }
  
    /*
    }
  
    /**
-    * DOCUMENT ME!
+    * Sets the sequence description, and also parses out any special formats of
+    * interest
     * 
     * @param desc
-    *          DOCUMENT ME!
     */
    @Override
    public void setDescription(String desc)
      this.description = desc;
    }
  
+   @Override
+   public void setGeneLoci(String speciesId, String assemblyId,
+           String chromosomeId, MapList map)
+   {
+     addDBRef(new DBRefEntry(speciesId, assemblyId, DBRefEntry.CHROMOSOME
+             + ":" + chromosomeId, new Mapping(map)));
+   }
    /**
-    * DOCUMENT ME!
+    * Returns the gene loci mapping for the sequence (may be null)
     * 
-    * @return DOCUMENT ME!
+    * @return
+    */
+   @Override
+   public GeneLociI getGeneLoci()
+   {
+     DBRefEntry[] refs = getDBRefs();
+     if (refs != null)
+     {
+       for (final DBRefEntry ref : refs)
+       {
+         if (ref.isChromosome())
+         {
+           return new GeneLociI()
+           {
+             @Override
+             public String getSpeciesId()
+             {
+               return ref.getSource();
+             }
+             @Override
+             public String getAssemblyId()
+             {
+               return ref.getVersion();
+             }
+             @Override
+             public String getChromosomeId()
+             {
+               // strip off "chromosome:" prefix to chrId
+               return ref.getAccessionId().substring(
+                       DBRefEntry.CHROMOSOME.length() + 1);
+             }
+             @Override
+             public MapList getMap()
+             {
+               return ref.getMap().getMap();
+             }
+           };
+         }
+       }
+     }
+     return null;
+   }
+   /**
+    * Answers the description
+    * 
+    * @return
     */
    @Override
    public String getDescription()
      return this.description;
    }
  
-   /*
-    * (non-Javadoc)
-    * 
-    * @see jalview.datamodel.SequenceI#findIndex(int)
+   /**
+    * {@inheritDoc}
     */
    @Override
    public int findIndex(int pos)
    {
-     // returns the alignment position for a residue
+     /*
+      * use a valid, hopefully nearby, cursor if available
+      */
+     if (isValidCursor(cursor))
+     {
+       return findIndex(pos, cursor);
+     }
      int j = start;
      int i = 0;
-     // Rely on end being at least as long as the length of the sequence.
+     int startColumn = 0;
+     /*
+      * traverse sequence from the start counting gaps; make a note of
+      * the column of the first residue to save in the cursor
+      */
      while ((i < sequence.length) && (j <= end) && (j <= pos))
      {
-       if (!jalview.util.Comparison.isGap(sequence[i]))
+       if (!Comparison.isGap(sequence[i]))
        {
+         if (j == start)
+         {
+           startColumn = i;
+         }
          j++;
        }
        i++;
      }
  
-     if ((j == end) && (j < pos))
+     if (j == end && j < pos)
      {
        return end + 1;
      }
-     else
+     updateCursor(pos, i, startColumn);
+     return i;
+   }
+   /**
+    * Updates the cursor to the latest found residue and column position
+    * 
+    * @param residuePos
+    *          (start..)
+    * @param column
+    *          (1..)
+    * @param startColumn
+    *          column position of the first sequence residue
+    */
+   protected void updateCursor(int residuePos, int column, int startColumn)
+   {
+     /*
+      * preserve end residue column provided cursor was valid
+      */
+     int endColumn = isValidCursor(cursor) ? cursor.lastColumnPosition : 0;
+     if (residuePos == this.end)
+     {
+       endColumn = column;
+     }
+     cursor = new SequenceCursor(this, residuePos, column, startColumn,
+             endColumn, this.changeCount);
+   }
+   /**
+    * Answers the aligned column position (1..) for the given residue position
+    * (start..) given a 'hint' of a residue/column location in the neighbourhood.
+    * The hint may be left of, at, or to the right of the required position.
+    * 
+    * @param pos
+    * @param curs
+    * @return
+    */
+   protected int findIndex(int pos, SequenceCursor curs)
+   {
+     if (!isValidCursor(curs))
+     {
+       /*
+        * wrong or invalidated cursor, compute de novo
+        */
+       return findIndex(pos);
+     }
+     if (curs.residuePosition == pos)
+     {
+       return curs.columnPosition;
+     }
+     /*
+      * move left or right to find pos from hint.position
+      */
+     int col = curs.columnPosition - 1; // convert from base 1 to 0-based array
+                                        // index
+     int newPos = curs.residuePosition;
+     int delta = newPos > pos ? -1 : 1;
+     while (newPos != pos)
      {
-       return i;
+       col += delta; // shift one column left or right
+       if (col < 0 || col == sequence.length)
+       {
+         break;
+       }
+       if (!Comparison.isGap(sequence[col]))
+       {
+         newPos += delta;
+       }
      }
+     col++; // convert back to base 1
+     updateCursor(pos, col, curs.firstColumnPosition);
+     return col;
    }
  
+   /**
+    * {@inheritDoc}
+    */
    @Override
-   public int findPosition(int i)
+   public int findPosition(final int column)
    {
+     /*
+      * use a valid, hopefully nearby, cursor if available
+      */
+     if (isValidCursor(cursor))
+     {
+       return findPosition(column + 1, cursor);
+     }
+     
+     // TODO recode this more naturally i.e. count residues only
+     // as they are found, not 'in anticipation'
+     /*
+      * traverse the sequence counting gaps; note the column position
+      * of the first residue, to save in the cursor
+      */
+     int firstResidueColumn = 0;
+     int lastPosFound = 0;
+     int lastPosFoundColumn = 0;
+     int seqlen = sequence.length;
+     if (seqlen > 0 && !Comparison.isGap(sequence[0]))
+     {
+       lastPosFound = start;
+       lastPosFoundColumn = 0;
+     }
      int j = 0;
      int pos = start;
-     int seqlen = sequence.length;
-     while ((j < i) && (j < seqlen))
+     while (j < column && j < seqlen)
      {
-       if (!jalview.util.Comparison.isGap(sequence[j]))
+       if (!Comparison.isGap(sequence[j]))
        {
+         lastPosFound = pos;
+         lastPosFoundColumn = j;
+         if (pos == this.start)
+         {
+           firstResidueColumn = j;
+         }
          pos++;
        }
        j++;
      }
+     if (j < seqlen && !Comparison.isGap(sequence[j]))
+     {
+       lastPosFound = pos;
+       lastPosFoundColumn = j;
+       if (pos == this.start)
+       {
+         firstResidueColumn = j;
+       }
+     }
+     /*
+      * update the cursor to the last residue position found (if any)
+      * (converting column position to base 1)
+      */
+     if (lastPosFound != 0)
+     {
+       updateCursor(lastPosFound, lastPosFoundColumn + 1,
+               firstResidueColumn + 1);
+     }
  
      return pos;
    }
  
    /**
+    * Answers true if the given cursor is not null, is for this sequence object,
+    * and has a token value that matches this object's changeCount, else false.
+    * This allows us to ignore a cursor as 'stale' if the sequence has been
+    * modified since the cursor was created.
+    * 
+    * @param curs
+    * @return
+    */
+   protected boolean isValidCursor(SequenceCursor curs)
+   {
+     if (curs == null || curs.sequence != this || curs.token != changeCount)
+     {
+       return false;
+     }
+     /*
+      * sanity check against range
+      */
+     if (curs.columnPosition < 0 || curs.columnPosition > sequence.length)
+     {
+       return false;
+     }
+     if (curs.residuePosition < start || curs.residuePosition > end)
+     {
+       return false;
+     }
+     return true;
+   }
+   /**
+    * Answers the sequence position (start..) for the given aligned column
+    * position (1..), given a hint of a cursor in the neighbourhood. The cursor
+    * may lie left of, at, or to the right of the column position.
+    * 
+    * @param col
+    * @param curs
+    * @return
+    */
+   protected int findPosition(final int col, SequenceCursor curs)
+   {
+     if (!isValidCursor(curs))
+     {
+       /*
+        * wrong or invalidated cursor, compute de novo
+        */
+       return findPosition(col - 1);// ugh back to base 0
+     }
+     if (curs.columnPosition == col)
+     {
+       cursor = curs; // in case this method becomes public
+       return curs.residuePosition; // easy case :-)
+     }
+     if (curs.lastColumnPosition > 0 && curs.lastColumnPosition < col)
+     {
+       /*
+        * sequence lies entirely to the left of col
+        * - return last residue + 1
+        */
+       return end + 1;
+     }
+     if (curs.firstColumnPosition > 0 && curs.firstColumnPosition > col)
+     {
+       /*
+        * sequence lies entirely to the right of col
+        * - return first residue
+        */
+       return start;
+     }
+     // todo could choose closest to col out of column,
+     // firstColumnPosition, lastColumnPosition as a start point
+     /*
+      * move left or right to find pos from cursor position
+      */
+     int firstResidueColumn = curs.firstColumnPosition;
+     int column = curs.columnPosition - 1; // to base 0
+     int newPos = curs.residuePosition;
+     int delta = curs.columnPosition > col ? -1 : 1;
+     boolean gapped = false;
+     int lastFoundPosition = curs.residuePosition;
+     int lastFoundPositionColumn = curs.columnPosition;
+     while (column != col - 1)
+     {
+       column += delta; // shift one column left or right
+       if (column < 0 || column == sequence.length)
+       {
+         break;
+       }
+       gapped = Comparison.isGap(sequence[column]);
+       if (!gapped)
+       {
+         newPos += delta;
+         lastFoundPosition = newPos;
+         lastFoundPositionColumn = column + 1;
+         if (lastFoundPosition == this.start)
+         {
+           firstResidueColumn = column + 1;
+         }
+       }
+     }
+     if (cursor == null || lastFoundPosition != cursor.residuePosition)
+     {
+       updateCursor(lastFoundPosition, lastFoundPositionColumn,
+               firstResidueColumn);
+     }
+     /*
+      * hack to give position to the right if on a gap
+      * or beyond the length of the sequence (see JAL-2562)
+      */
+     if (delta > 0 && (gapped || column >= sequence.length))
+     {
+       newPos++;
+     }
+     return newPos;
+   }
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Range findPositions(int fromColumn, int toColumn)
+   {
+     if (toColumn < fromColumn || fromColumn < 1)
+     {
+       return null;
+     }
+     /*
+      * find the first non-gapped position, if any
+      */
+     int firstPosition = 0;
+     int col = fromColumn - 1;
+     int length = sequence.length;
+     while (col < length && col < toColumn)
+     {
+       if (!Comparison.isGap(sequence[col]))
+       {
+         firstPosition = findPosition(col++);
+         break;
+       }
+       col++;
+     }
+     if (firstPosition == 0)
+     {
+       return null;
+     }
+     /*
+      * find the last non-gapped position
+      */
+     int lastPosition = firstPosition;
+     while (col < length && col < toColumn)
+     {
+       if (!Comparison.isGap(sequence[col++]))
+       {
+         lastPosition++;
+       }
+     }
+     return new Range(firstPosition, lastPosition);
+   }
+   /**
     * Returns an int array where indices correspond to each residue in the
     * sequence and the element value gives its position in the alignment
     * 
    @Override
    public List<int[]> getInsertions()
    {
 -    ArrayList<int[]> map = new ArrayList<int[]>();
 +    ArrayList<int[]> map = new ArrayList<>();
      int lastj = -1, j = 0;
      int pos = start;
      int seqlen = sequence.length;
    }
  
    @Override
-   public void deleteChars(int i, int j)
+   public void deleteChars(final int i, final int j)
    {
      int newstart = start, newend = end;
      if (i >= sequence.length || i < 0)
      boolean createNewDs = false;
      // TODO: take a (second look) at the dataset creation validation method for
      // the very large sequence case
-     int eindex = -1, sindex = -1;
-     boolean ecalc = false, scalc = false;
+     int startIndex = findIndex(start) - 1;
+     int endIndex = findIndex(end) - 1;
+     int startDeleteColumn = -1; // for dataset sequence deletions
+     int deleteCount = 0;
      for (int s = i; s < j; s++)
      {
-       if (jalview.schemes.ResidueProperties.aaIndex[sequence[s]] != 23)
+       if (Comparison.isGap(sequence[s]))
        {
-         if (createNewDs)
+         continue;
+       }
+       deleteCount++;
+       if (startDeleteColumn == -1)
+       {
+         startDeleteColumn = findPosition(s) - start;
+       }
+       if (createNewDs)
+       {
+         newend--;
+       }
+       else
+       {
+         if (startIndex == s)
          {
-           newend--;
+           /*
+            * deleting characters from start of sequence; new start is the
+            * sequence position of the next column (position to the right
+            * if the column position is gapped)
+            */
+           newstart = findPosition(j);
+           break;
          }
          else
          {
-           if (!scalc)
-           {
-             sindex = findIndex(start) - 1;
-             scalc = true;
-           }
-           if (sindex == s)
+           if (endIndex < j)
            {
-             // delete characters including start of sequence
-             newstart = findPosition(j);
-             break; // don't need to search for any more residue characters.
+             /*
+              * deleting characters at end of sequence; new end is the sequence
+              * position of the column before the deletion; subtract 1 if this is
+              * gapped since findPosition returns the next sequence position
+              */
+             newend = findPosition(i - 1);
+             if (Comparison.isGap(sequence[i - 1]))
+             {
+               newend--;
+             }
+             break;
            }
            else
            {
-             // delete characters after start.
-             if (!ecalc)
-             {
-               eindex = findIndex(end) - 1;
-               ecalc = true;
-             }
-             if (eindex < j)
-             {
-               // delete characters at end of sequence
-               newend = findPosition(i - 1);
-               break; // don't need to search for any more residue characters.
-             }
-             else
-             {
-               createNewDs = true;
-               newend--; // decrease end position by one for the deleted residue
-               // and search further
-             }
+             createNewDs = true;
+             newend--;
            }
          }
        }
      }
-     // deletion occured in the middle of the sequence
      if (createNewDs && this.datasetSequence != null)
      {
-       // construct a new sequence
+       /*
+        * if deletion occured in the middle of the sequence,
+        * construct a new dataset sequence and delete the residues
+        * that were deleted from the aligned sequence
+        */
        Sequence ds = new Sequence(datasetSequence);
+       ds.deleteChars(startDeleteColumn, startDeleteColumn + deleteCount);
+       datasetSequence = ds;
        // TODO: remove any non-inheritable properties ?
        // TODO: create a sequence mapping (since there is a relation here ?)
-       ds.deleteChars(i, j);
-       datasetSequence = ds;
      }
      start = newstart;
      end = newend;
      sequence = tmp;
+     sequenceChanged();
    }
  
    @Override
      }
  
      sequence = tmp;
+     sequenceChanged();
    }
  
    @Override
    {
      if (this.annotation == null)
      {
 -      this.annotation = new Vector<AlignmentAnnotation>();
 +      this.annotation = new Vector<>();
      }
      if (!this.annotation.contains(annotation))
      {
  
    private boolean _isNa;
  
-   private long _seqhash = 0;
+   private int _seqhash = 0;
  
    /**
     * Answers false if the sequence is more than 85% nucleotide (ACGTU), else
  
        dsseq.setDescription(description);
        // move features and database references onto dataset sequence
-       dsseq.sequenceFeatures = sequenceFeatures;
-       sequenceFeatures = null;
+       dsseq.sequenceFeatureStore = sequenceFeatureStore;
+       sequenceFeatureStore = null;
        dsseq.dbrefs = dbrefs;
        dbrefs = null;
        // TODO: search and replace any references to this sequence with
        return null;
      }
  
-     Vector subset = new Vector();
-     Enumeration e = annotation.elements();
 -    Vector<AlignmentAnnotation> subset = new Vector<AlignmentAnnotation>();
++    Vector<AlignmentAnnotation> subset = new Vector<>();
+     Enumeration<AlignmentAnnotation> e = annotation.elements();
      while (e.hasMoreElements())
      {
-       AlignmentAnnotation ann = (AlignmentAnnotation) e.nextElement();
+       AlignmentAnnotation ann = e.nextElement();
        if (ann.label != null && ann.label.equals(label))
        {
          subset.addElement(ann);
      e = subset.elements();
      while (e.hasMoreElements())
      {
-       anns[i++] = (AlignmentAnnotation) e.nextElement();
+       anns[i++] = e.nextElement();
      }
      subset.removeAllElements();
      return anns;
      if (entry.getSequenceFeatures() != null)
      {
  
-       SequenceFeature[] sfs = entry.getSequenceFeatures();
-       for (int si = 0; si < sfs.length; si++)
+       List<SequenceFeature> sfs = entry.getSequenceFeatures();
+       for (SequenceFeature feature : sfs)
        {
-         SequenceFeature sf[] = (mp != null) ? mp.locateFeature(sfs[si])
-                 : new SequenceFeature[]
-                 { new SequenceFeature(sfs[si]) };
-         if (sf != null && sf.length > 0)
+        SequenceFeature sf[] = (mp != null) ? mp.locateFeature(feature)
+                 : new SequenceFeature[] { new SequenceFeature(feature) };
+         if (sf != null)
          {
            for (int sfi = 0; sfi < sf.length; sfi++)
            {
      // transfer PDB entries
      if (entry.getAllPDBEntries() != null)
      {
-       Enumeration e = entry.getAllPDBEntries().elements();
+       Enumeration<PDBEntry> e = entry.getAllPDBEntries().elements();
        while (e.hasMoreElements())
        {
-         PDBEntry pdb = (PDBEntry) e.nextElement();
+         PDBEntry pdb = e.nextElement();
          addPDBId(pdb);
        }
      }
      }
    }
  
-   /**
-    * @return The index (zero-based) on this sequence in the MSA. It returns
-    *         {@code -1} if this information is not available.
-    */
-   @Override
-   public int getIndex()
-   {
-     return index;
-   }
-   /**
-    * Defines the position of this sequence in the MSA. Use the value {@code -1}
-    * if this information is undefined.
-    * 
-    * @param The
-    *          position for this sequence. This value is zero-based (zero for
-    *          this first sequence)
-    */
-   @Override
-   public void setIndex(int value)
-   {
-     index = value;
-   }
    @Override
    public void setRNA(RNA r)
    {
    public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
            String label)
    {
 -    List<AlignmentAnnotation> result = new ArrayList<AlignmentAnnotation>();
 +    List<AlignmentAnnotation> result = new ArrayList<>();
      if (this.annotation != null)
      {
        for (AlignmentAnnotation ann : annotation)
      }
      synchronized (dbrefs)
      {
 -      List<DBRefEntry> primaries = new ArrayList<DBRefEntry>();
 +      List<DBRefEntry> primaries = new ArrayList<>();
        DBRefEntry[] tmp = new DBRefEntry[1];
        for (DBRefEntry ref : dbrefs)
        {
      }
    }
  
 +  @Override
 +  public HiddenMarkovModel getHMM()
 +  {
 +    return hmm;
 +  }
 +
 +  @Override
 +  public void setHMM(HiddenMarkovModel hmm)
 +  {
 +    this.hmm = hmm;
 +  }
 +
 +  @Override
 +  public void updateHMMMapping()
 +  {
 +    int node = 1;
 +    int column = 0;
 +    hmm.emptyNodeLookup();
 +    for (char residue : sequence)
 +    {
 +      if (!Comparison.isGap(residue))
 +      {
 +        hmm.setAlignmentColumn(node, column);
 +        node++;
 +      }
 +      column++;
 +    }
 +  }
 +
 +  /**
 +   * Maps the HMM sequence to the reference annotation.
 +   * 
 +   * @param rf
 +   */
 +  @Override
 +  public void mapToReference(AlignmentAnnotation rf)
 +  {
 +    if (this.isHMMConsensusSequence)
 +    {
 +      int node = 1;
 +      hmm.emptyNodeLookup();
 +      for (int i = 0; i < getLength(); i++)
 +      {
 +        if (rf.annotations[i].displayCharacter.equals("x")
 +                || rf.annotations[i].displayCharacter.equals("X"))
 +        {
 +          if (i < hmm.getNodeAlignmentColumn(node))
 +          {
 +            this.deleteChars(i, hmm.getNodeAlignmentColumn(node));
 +            updateHMMMapping();
 +          }
 +          else if (i > hmm.getNodeAlignmentColumn(node))
 +          {
 +            int length = i - hmm.getNodeAlignmentColumn(node);
 +            this.insertCharAt(hmm.getNodeAlignmentColumn(node), length,
 +                    '-');
 +            updateHMMMapping();
 +          }
 +          node++;
 +        }
 +      }
 +    }
 +  }
 +
 +  @Override
 +  public boolean isHMMConsensusSequence()
 +  {
 +    return isHMMConsensusSequence;
 +  }
 +
 +  @Override
 +  public void setIsHMMConsensusSequence(boolean isHMMConsensusSequence)
 +  {
 +    this.isHMMConsensusSequence = isHMMConsensusSequence;
 +  }
 +
 +  @Override
 +  public boolean hasHMMAnnotation()
 +  {
 +    return hasInfo;
 +  }
 +
 +  @Override
 +  public void setHasInfo(boolean status)
 +  {
 +    hasInfo = true;
 +  }
 +
 +  @Override
 +  public int getPreviousPosition()
 +  {
 +    return previousPosition;
 +  }
 +
 +  @Override
 +  public void setPreviousPosition(int previousPosition)
 +  {
 +    this.previousPosition = previousPosition;
 +  }
 +
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public List<SequenceFeature> findFeatures(int fromColumn, int toColumn,
+           String... types)
+   {
+     int startPos = findPosition(fromColumn - 1); // convert base 1 to base 0
+     int endPos = fromColumn == toColumn ? startPos
+             : findPosition(toColumn - 1);
+     List<SequenceFeature> result = getFeatures().findFeatures(startPos,
+             endPos, types);
+     /*
+      * if end column is gapped, endPos may be to the right, 
+      * and we may have included adjacent or enclosing features;
+      * remove any that are not enclosing, non-contact features
+      */
+     boolean endColumnIsGapped = toColumn > 0 && toColumn <= sequence.length
+             && Comparison.isGap(sequence[toColumn - 1]);
+     if (endPos > this.end || endColumnIsGapped)
+     {
+       ListIterator<SequenceFeature> it = result.listIterator();
+       while (it.hasNext())
+       {
+         SequenceFeature sf = it.next();
+         int sfBegin = sf.getBegin();
+         int sfEnd = sf.getEnd();
+         int featureStartColumn = findIndex(sfBegin);
+         if (featureStartColumn > toColumn)
+         {
+           it.remove();
+         }
+         else if (featureStartColumn < fromColumn)
+         {
+           int featureEndColumn = sfEnd == sfBegin ? featureStartColumn
+                   : findIndex(sfEnd);
+           if (featureEndColumn < fromColumn)
+           {
+             it.remove();
+           }
+           else if (featureEndColumn > toColumn && sf.isContactFeature())
+           {
+             /*
+              * remove an enclosing feature if it is a contact feature
+              */
+             it.remove();
+           }
+         }
+       }
+     }
+     return result;
+   }
+   /**
+    * Invalidates any stale cursors (forcing recalculation) by incrementing the
+    * token that has to match the one presented by the cursor
+    */
+   @Override
+   public void sequenceChanged()
+   {
+     changeCount++;
+   }
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public int replace(char c1, char c2)
+   {
+     if (c1 == c2)
+     {
+       return 0;
+     }
+     int count = 0;
+     synchronized (sequence)
+     {
+       for (int c = 0; c < sequence.length; c++)
+       {
+         if (sequence[c] == c1)
+         {
+           sequence[c] = c2;
+           count++;
+         }
+       }
+     }
+     if (count > 0)
+     {
+       sequenceChanged();
+     }
+     return count;
+   }
  }
@@@ -25,10 -25,12 +25,13 @@@ import jalview.analysis.Conservation
  import jalview.renderer.ResidueShader;
  import jalview.renderer.ResidueShaderI;
  import jalview.schemes.ColourSchemeI;
 +import jalview.util.MessageManager;
  
  import java.awt.Color;
+ import java.beans.PropertyChangeListener;
+ import java.beans.PropertyChangeSupport;
  import java.util.ArrayList;
+ import java.util.Arrays;
  import java.util.List;
  import java.util.Map;
  
   */
  public class SequenceGroup implements AnnotatedCollectionI
  {
+   // TODO ideally this event notification functionality should be separated into
+   // a
+   // subclass of ViewportProperties similarly to ViewportRanges. Done here as
+   // quick fix for JAL-2665
+   public static final String SEQ_GROUP_CHANGED = "Sequence group changed";
+   protected PropertyChangeSupport changeSupport = new PropertyChangeSupport(
+           this);
+   public void addPropertyChangeListener(PropertyChangeListener listener)
+   {
+     changeSupport.addPropertyChangeListener(listener);
+   }
+   public void removePropertyChangeListener(PropertyChangeListener listener)
+   {
+     changeSupport.removePropertyChangeListener(listener);
+   }
+   // end of event notification functionality initialisation
    String groupName;
  
    String description;
     */
    private boolean ignoreGapsInConsensus = true;
  
 +  private boolean ignoreBelowBackground = true;
 +
 +  private boolean infoLetterHeight = false;
 +
    /**
     * consensus calculation property
     */
  
    AlignmentAnnotation conservation = null;
  
 +  AlignmentAnnotation information = null;
 +
    private boolean showConsensusHistogram;
  
    private AnnotatedCollectionI context;
  
 +  private boolean showHMMSequenceLogo;
 +
 +  private boolean normaliseHMMSequenceLogo;
 +
 +  private boolean showInformationHistogram;
 +
    /**
     * Creates a new SequenceGroup object.
     */
        showSequenceLogo = seqsel.showSequenceLogo;
        normaliseSequenceLogo = seqsel.normaliseSequenceLogo;
        showConsensusHistogram = seqsel.showConsensusHistogram;
 +      showHMMSequenceLogo = seqsel.showHMMSequenceLogo;
 +      normaliseHMMSequenceLogo = seqsel.normaliseHMMSequenceLogo;
 +      showInformationHistogram = seqsel.showInformationHistogram;
        idColour = seqsel.idColour;
        outlineColour = seqsel.outlineColour;
        seqrep = seqsel.seqrep;
        thresholdTextColour = seqsel.thresholdTextColour;
        width = seqsel.width;
        ignoreGapsInConsensus = seqsel.ignoreGapsInConsensus;
 +      ignoreBelowBackground = seqsel.ignoreBelowBackground;
 +      infoLetterHeight = seqsel.infoLetterHeight;
        if (seqsel.conserve != null)
        {
          recalcConservation(); // safer than
        if (s != null && !sequences.contains(s))
        {
          sequences.add(s);
+         changeSupport.firePropertyChange(SEQ_GROUP_CHANGED,
+                 sequences.size() - 1, sequences.size());
        }
  
        if (recalc)
     */
    public boolean recalcConservation(boolean defer)
    {
 -    if (cs == null && consensus == null && conservation == null)
 +    if (cs == null && consensus == null && conservation == null
 +            && information == null)
      {
        return false;
      }
      {
        ProfilesI cnsns = AAFrequency.calculate(sequences, startRes,
                endRes + 1, showSequenceLogo);
 +      if (information != null)
 +      {
 +        HiddenMarkovModel hmm = information.sequenceRef.getHMM();
 +
 +        ProfilesI info = AAFrequency.calculateHMMProfiles(hmm,
 +                (endRes + 1) - startRes, startRes, endRes + 1,
 +                showHMMSequenceLogo, ignoreBelowBackground,
 +                infoLetterHeight);
 +        _updateInformationRow(info, sequences.size());
 +        upd = true;
 +      }
        if (consensus != null)
        {
          _updateConsensusRow(cnsns, sequences.size());
  
    public ProfilesI consensusData = null;
  
 +  public ProfilesI informationData = null;
 +
    private void _updateConsensusRow(ProfilesI cnsns, long nseq)
    {
      if (consensus == null)
    }
  
    /**
 +   * Recalculates the information content on the HMM annotation.
 +   * 
 +   * @param cnsns
 +   * @param nseq
 +   */
 +  private void _updateInformationRow(ProfilesI cnsns, long nseq)
 +  {
 +    if (information == null)
 +    {
 +      getInformation();
 +    }
 +    information.description = MessageManager
 +            .getString("label.information_description");
 +    informationData = cnsns;
 +    // preserve width if already set
 +    int aWidth = (information.annotations != null)
 +            ? (endRes < information.annotations.length
 +                    ? information.annotations.length : endRes + 1)
 +            : endRes + 1;
 +    information.annotations = null;
 +    information.annotations = new Annotation[aWidth]; // should be alignment
 +                                                      // width
 +    information.calcId = "HMM";
 +    AAFrequency.completeInformation(information, cnsns, startRes,
 +            endRes + 1, nseq, 0f); // TODO:
 +                                                                        // setting
 +                                                            // container
 +    // for
 +    // ignoreGapsInInformationCalculation);
 +  }
 +
 +  /**
     * @param s
     *          sequence to either add or remove from group
     * @param recalc
      synchronized (sequences)
      {
        sequences.remove(s);
+       changeSupport.firePropertyChange(SEQ_GROUP_CHANGED,
+               sequences.size() + 1, sequences.size());
  
        if (recalc)
        {
     */
    public void setStartRes(int i)
    {
+     int before = startRes;
      startRes = i;
+     changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before, startRes);
    }
  
    /**
     */
    public void setEndRes(int i)
    {
+     int before = endRes;
      endRes = i;
+     changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before, endRes);
    }
  
    /**
    }
  
    /**
 +   * 
 +   * @return information content annotation.
 +   */
 +  public AlignmentAnnotation getInformation()
 +  {
 +    // TODO get or calculate and get information annotation row for this group
 +    int aWidth = this.getWidth();
 +    // pointer
 +    // possibility
 +    // here.
 +    if (aWidth < 0)
 +    {
 +      return null;
 +    }
 +    if (information == null)
 +    {
 +      information = new AlignmentAnnotation("", "", new Annotation[1], 0f,
 +              6.25f, AlignmentAnnotation.BAR_GRAPH);
 +      information.hasText = true;
 +      information.autoCalculated = false;
 +      information.groupRef = this;
 +      information.label = getName();
 +      information.description = "Information content, measured in bits";
 +      information.calcId = "HMM";
 +    }
 +    return information;
 +  }
 +
 +  /**
     * set this alignmentAnnotation object as the one used to render consensus
     * annotation
     * 
      return ignoreGapsInConsensus;
    }
  
 +  public void setIgnoreBelowBackground(boolean state)
 +  {
 +    if (this.ignoreBelowBackground != state)
 +    {
 +      ignoreBelowBackground = state;
 +    }
 +    ignoreBelowBackground = state;
 +  }
 +
 +  public boolean getIgnoreBelowBackground()
 +  {
 +    return ignoreBelowBackground;
 +  }
 +
 +  public void setInfoLetterHeight(boolean state)
 +  {
 +    if (this.infoLetterHeight != state)
 +    {
 +      infoLetterHeight = state;
 +    }
 +    infoLetterHeight = state;
 +  }
 +
 +  public boolean getInfoLetterHeight()
 +  {
 +    return infoLetterHeight;
 +  }
 +
    /**
     * @param showSequenceLogo
     *          indicates if a sequence logo is shown for consensus annotation
    @Override
    public Iterable<AlignmentAnnotation> findAnnotation(String calcId)
    {
-     List<AlignmentAnnotation> aa = new ArrayList<>();
-     if (calcId == null)
-     {
-       return aa;
-     }
-     for (AlignmentAnnotation a : getAlignmentAnnotation())
-     {
-       if (calcId.equals(a.getCalcId()))
-       {
-         aa.add(a);
-       }
-     }
-     return aa;
+     return AlignmentAnnotation.findAnnotation(
+             Arrays.asList(getAlignmentAnnotation()), calcId);
    }
  
    @Override
    public Iterable<AlignmentAnnotation> findAnnotations(SequenceI seq,
            String calcId, String label)
    {
-     ArrayList<AlignmentAnnotation> aa = new ArrayList<>();
-     for (AlignmentAnnotation ann : getAlignmentAnnotation())
-     {
-       if ((calcId == null || (ann.getCalcId() != null
-               && ann.getCalcId().equals(calcId)))
-               && (seq == null || (ann.sequenceRef != null
-                       && ann.sequenceRef == seq))
-               && (label == null
-                       || (ann.label != null && ann.label.equals(label))))
-       {
-         aa.add(ann);
-       }
-     }
-     return aa;
+     return AlignmentAnnotation.findAnnotations(
+             Arrays.asList(getAlignmentAnnotation()), seq, calcId, label);
    }
  
    /**
     */
    public boolean hasAnnotation(String calcId)
    {
-     if (calcId != null && !"".equals(calcId))
-     {
-       for (AlignmentAnnotation a : getAlignmentAnnotation())
-       {
-         if (a.getCalcId() == calcId)
-         {
-           return true;
-         }
-       }
-     }
-     return false;
+     return AlignmentAnnotation
+             .hasAnnotation(Arrays.asList(getAlignmentAnnotation()), calcId);
    }
  
    /**
    {
      synchronized (sequences)
      {
+       int before = sequences.size();
        sequences.clear();
+       changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before,
+               sequences.size());
      }
    }
  
    {
      return (startRes <= apos && endRes >= apos) && sequences.contains(seq);
    }
 +
 +  public boolean isShowInformationHistogram()
 +  {
 +    return showInformationHistogram;
 +  }
 +
 +  public void setShowInformationHistogram(boolean state)
 +  {
 +    if (showInformationHistogram != state && information != null)
 +    {
 +      this.showInformationHistogram = state;
 +      // recalcConservation(); TODO don't know what to do here next
 +    }
 +    this.showInformationHistogram = state;
 +
 +  }
 +
 +  public boolean isShowHMMSequenceLogo()
 +  {
 +    // TODO Auto-generated method stub
 +    return showHMMSequenceLogo;
 +  }
 +
 +  public void setshowHMMSequenceLogo(boolean state)
 +  {
 +    showHMMSequenceLogo = state;
 +
 +  }
 +
 +  public boolean isNormaliseHMMSequenceLogo()
 +  {
 +    // TODO Auto-generated method stub
 +    return normaliseHMMSequenceLogo;
 +  }
 +
 +  public void setNormaliseHMMSequenceLogo(boolean state)
 +  {
 +    normaliseSequenceLogo = state;
 +  }
 +
 +  /**
 +   * Returns all HMM consensus sequences. This will not return real sequences
 +   * with HMMs. If remove is set to true, the consensus sequences will be
 +   * removed from the alignment.
 +   */
 +  @Override // TODO make this more efficient.
 +  public List<SequenceI> getHMMConsensusSequences(boolean remove)
 +  {
 +    List<SequenceI> seqs = new ArrayList<>();
 +    int position = 0;
 +    int seqsRemoved = 0;
 +    boolean endReached = false;
 +
 +    while (!endReached)
 +    {
 +      SequenceI seq = sequences.get(position);
 +      if (seq.isHMMConsensusSequence())
 +      {
 +        if (remove)
 +        {
 +          sequences.remove(position);
 +          seqsRemoved++;
 +          seq.setPreviousPosition(seqsRemoved + position - 1);
 +        }
 +        else
 +        {
 +          position++;
 +        }
 +        seqs.add(seq);
 +      }
 +      else
 +      {
 +        position++;
 +      }
 +
 +      if (position >= sequences.size())
 +      {
 +        endReached = true;
 +      }
 +    }
 +    return seqs;
 +  }
 +
  }
@@@ -20,6 -20,9 +20,9 @@@
   */
  package jalview.datamodel;
  
+ import jalview.datamodel.features.SequenceFeaturesI;
+ import jalview.util.MapList;
  import java.util.BitSet;
  import java.util.List;
  import java.util.Vector;
@@@ -42,10 -45,6 +45,10 @@@ public interface SequenceI extends ASeq
     */
    public void setName(String name);
  
 +  public HiddenMarkovModel getHMM();
 +
 +  public void setHMM(HiddenMarkovModel hmm);
 +
    /**
     * Get the display name
     */
    public String getSequenceAsString(int start, int end);
  
    /**
-    * Get the sequence as a character array
+    * Answers a copy of the sequence as a character array
     * 
-    * @return seqeunce and any gaps
+    * @return
     */
    public char[] getSequence();
  
    public String getDescription();
  
    /**
-    * Return the alignment column for a sequence position
+    * Return the alignment column (from 1..) for a sequence position
     * 
     * @param pos
     *          lying from start to end
    public int findIndex(int pos);
  
    /**
-    * Returns the sequence position for an alignment position.
+    * Returns the sequence position for an alignment (column) position. If at a
+    * gap, returns the position of the next residue to the right. If beyond the
+    * end of the sequence, returns 1 more than the last residue position.
     * 
     * @param i
     *          column index in alignment (from 0..<length)
     * 
-    * @return TODO: JAL-2562 - residue number for residue (left of and) nearest
-    *         ith column
+    * @return
     */
    public int findPosition(int i);
  
    /**
+    * Returns the from-to sequence positions (start..) for the given column
+    * positions (1..), or null if no residues are included in the range
+    * 
+    * @param fromColum
+    * @param toColumn
+    * @return
+    */
+   public Range findPositions(int fromColum, int toColumn);
+   /**
     * Returns an int array where indices correspond to each residue in the
     * sequence and the element value gives its position in the alignment
     * 
    public void insertCharAt(int position, int count, char ch);
  
    /**
-    * Gets array holding sequence features associated with this sequence. The
-    * array may be held by the sequence's dataset sequence if that is defined.
+    * Answers a list of all sequence features associated with this sequence. The
+    * list may be held by the sequence's dataset sequence if that is defined.
+    * 
+    * @return
+    */
+   public List<SequenceFeature> getSequenceFeatures();
+   /**
+    * Answers the object holding features for the sequence
     * 
-    * @return hard reference to array
+    * @return
     */
-   public SequenceFeature[] getSequenceFeatures();
+   SequenceFeaturesI getFeatures();
  
    /**
-    * Replaces the array of sequence features associated with this sequence with
-    * a new array reference. If this sequence has a dataset sequence, then this
-    * method will update the dataset sequence's feature array
+    * Replaces the sequence features associated with this sequence with the given
+    * features. If this sequence has a dataset sequence, then this method will
+    * update the dataset sequence's features instead.
     * 
     * @param features
-    *          New array of sequence features
     */
-   public void setSequenceFeatures(SequenceFeature[] features);
+   public void setSequenceFeatures(List<SequenceFeature> features);
  
    /**
     * DOCUMENT ME!
  
    /**
     * Adds the given sequence feature and returns true, or returns false if it is
-    * already present on the sequence
+    * already present on the sequence, or if the feature type is null.
     * 
     * @param sf
     * @return
    public void transferAnnotation(SequenceI entry, Mapping mp);
  
    /**
-    * @param index
-    *          The sequence index in the MSA
-    */
-   public void setIndex(int index);
-   /**
-    * @return The index of the sequence in the alignment
-    */
-   public int getIndex();
-   /**
     * @return The RNA of the sequence in the alignment
     */
  
     */
    public List<DBRefEntry> getPrimaryDBRefs();
  
 +  public void updateHMMMapping();
 +
 +  boolean isHMMConsensusSequence();
 +
 +  void setIsHMMConsensusSequence(boolean isHMMConsensusSequence);
 +
 +  boolean hasHMMAnnotation();
 +
 +  void setHasInfo(boolean status);
 +
 +  int getPreviousPosition();
 +
 +  void setPreviousPosition(int previousPosition);
 +
    /**
+    * Returns a (possibly empty) list of sequence features that overlap the given
+    * alignment column range, optionally restricted to one or more specified
+    * feature types. If the range is all gaps, then features which enclose it are
+    * included (but not contact features).
+    * 
+    * @param fromCol
+    *          start column of range inclusive (1..)
+    * @param toCol
+    *          end column of range inclusive (1..)
+    * @param types
+    *          optional feature types to restrict results to
+    * @return
+    */
+   List<SequenceFeature> findFeatures(int fromCol, int toCol, String... types);
+   /**
+    * Method to call to indicate that the sequence (characters or alignment/gaps)
+    * has been modified. Provided to allow any cursors on residue/column
+    * positions to be invalidated.
+    */
+   void sequenceChanged();
+   
+   /**
     * 
     * @return BitSet corresponding to index [0,length) where Comparison.isGap()
     *         returns true.
     */
    BitSet getInsertionsAsBits();
  
 +  void mapToReference(AlignmentAnnotation rf);
++
+   /**
+    * Replaces every occurrence of c1 in the sequence with c2 and returns the
+    * number of characters changed
+    * 
+    * @param c1
+    * @param c2
+    */
 -  public int replace(char c1, char c2);
++  int replace(char c1, char c2);
+   /**
+    * Answers the GeneLociI, or null if not known
+    * 
+    * @return
+    */
+   GeneLociI getGeneLoci();
+   /**
+    * Sets the mapping to gene loci for the sequence
+    * 
+    * @param speciesId
+    * @param assemblyId
+    * @param chromosomeId
+    * @param map
+    */
+   void setGeneLoci(String speciesId, String assemblyId,
+           String chromosomeId, MapList map);
  }
@@@ -54,7 -54,6 +54,7 @@@ import jalview.datamodel.AlignmentOrder
  import jalview.datamodel.AlignmentView;
  import jalview.datamodel.ColumnSelection;
  import jalview.datamodel.HiddenColumns;
 +import jalview.datamodel.HiddenMarkovModel;
  import jalview.datamodel.HiddenSequences;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.SeqCigar;
@@@ -63,12 -62,6 +63,12 @@@ import jalview.datamodel.SequenceGroup
  import jalview.datamodel.SequenceI;
  import jalview.gui.ColourMenuHelper.ColourChangeListener;
  import jalview.gui.ViewSelectionMenu.ViewSetProvider;
 +import jalview.hmmer.HMMAlignThread;
 +import jalview.hmmer.HMMBuildThread;
 +import jalview.hmmer.HMMERParamStore;
 +import jalview.hmmer.HMMERPreset;
 +import jalview.hmmer.HMMSearchThread;
 +import jalview.hmmer.HmmerCommand;
  import jalview.io.AlignmentProperties;
  import jalview.io.AnnotationFile;
  import jalview.io.BioJsHTMLOutput;
@@@ -88,13 -81,13 +88,14 @@@ import jalview.io.JnetAnnotationMaker
  import jalview.io.NewickFile;
  import jalview.io.ScoreMatrixFile;
  import jalview.io.TCoffeeScoreFile;
+ import jalview.io.vcf.VCFLoader;
  import jalview.jbgui.GAlignFrame;
  import jalview.schemes.ColourSchemeI;
  import jalview.schemes.ColourSchemes;
  import jalview.schemes.ResidueColourScheme;
  import jalview.schemes.TCoffeeColourScheme;
  import jalview.util.MessageManager;
 +import jalview.util.StringUtils;
  import jalview.viewmodel.AlignmentViewport;
  import jalview.viewmodel.ViewportRanges;
  import jalview.ws.DBRefFetcher;
@@@ -102,9 -95,6 +103,9 @@@ import jalview.ws.DBRefFetcher.FetchFin
  import jalview.ws.jws1.Discoverer;
  import jalview.ws.jws2.Jws2Discoverer;
  import jalview.ws.jws2.jabaws2.Jws2Instance;
 +import jalview.ws.params.ArgumentI;
 +import jalview.ws.params.ParamDatastoreI;
 +import jalview.ws.params.WsParamSetI;
  import jalview.ws.seqfetcher.DbSourceProxy;
  
  import java.awt.BorderLayout;
@@@ -134,29 -124,22 +135,29 @@@ import java.awt.print.PrinterJob
  import java.beans.PropertyChangeEvent;
  import java.io.File;
  import java.io.FileWriter;
 +import java.io.IOException;
  import java.io.PrintWriter;
  import java.net.URL;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Deque;
  import java.util.Enumeration;
 +import java.util.HashMap;
  import java.util.Hashtable;
  import java.util.List;
 +import java.util.Map;
 +import java.util.Scanner;
  import java.util.Vector;
  
  import javax.swing.JCheckBoxMenuItem;
  import javax.swing.JEditorPane;
 +import javax.swing.JFileChooser;
 +import javax.swing.JFrame;
  import javax.swing.JInternalFrame;
  import javax.swing.JLayeredPane;
  import javax.swing.JMenu;
  import javax.swing.JMenuItem;
 +import javax.swing.JOptionPane;
  import javax.swing.JScrollPane;
  import javax.swing.SwingUtilities;
  
@@@ -170,8 -153,6 +171,8 @@@ public class AlignFrame extends GAlignF
          IProgressIndicator, AlignViewControllerGuiI, ColourChangeListener
  {
  
 +  Map<String, Float> distribution = new HashMap<>(); // temporary
 +
    public static final int DEFAULT_WIDTH = 700;
  
    public static final int DEFAULT_HEIGHT = 500;
  
    AlignViewport viewport;
  
-   ViewportRanges vpRanges;
    public AlignViewControllerI avc;
 +  /*
 +   * The selected HMM for this align frame
 +   */
 +  SequenceI selectedHMMSequence;
  
    List<AlignmentPanel> alignPanels = new ArrayList<>();
  
     */
    String fileName = null;
  
 +
    /**
     * Creates a new AlignFrame object with specific width and height.
     * 
        progressBar = new ProgressBar(this.statusPanel, this.statusBar);
      }
  
-     vpRanges = viewport.getRanges();
      avc = new jalview.controller.AlignViewController(this, viewport,
              alignPanel);
      if (viewport.getAlignmentConservationAnnotation() == null)
                    { (viewport.cursorMode ? "on" : "off") }));
            if (viewport.cursorMode)
            {
-             alignPanel.getSeqPanel().seqCanvas.cursorX = vpRanges
+             ViewportRanges ranges = viewport.getRanges();
+             alignPanel.getSeqPanel().seqCanvas.cursorX = ranges
                      .getStartRes();
-             alignPanel.getSeqPanel().seqCanvas.cursorY = vpRanges
+             alignPanel.getSeqPanel().seqCanvas.cursorY = ranges
                      .getStartSeq();
            }
            alignPanel.getSeqPanel().seqCanvas.repaint();
            break;
          }
          case KeyEvent.VK_PAGE_UP:
-           vpRanges.pageUp();
+           viewport.getRanges().pageUp();
            break;
          case KeyEvent.VK_PAGE_DOWN:
-           vpRanges.pageDown();
+           viewport.getRanges().pageDown();
            break;
          }
        }
        ap.av.updateConservation(ap);
        ap.av.updateConsensus(ap);
        ap.av.updateStrucConsensus(ap);
 +      ap.av.updateInformation(ap);
      }
    }
  
      AlignmentI al = getViewport().getAlignment();
      boolean nucleotide = al.isNucleotide();
  
+     loadVcf.setVisible(nucleotide);
      showTranslation.setVisible(nucleotide);
      showReverse.setVisible(nucleotide);
      showReverseComplement.setVisible(nucleotide);
      showConsensusHistogram.setSelected(av.isShowConsensusHistogram());
      showSequenceLogo.setSelected(av.isShowSequenceLogo());
      normaliseSequenceLogo.setSelected(av.isNormaliseSequenceLogo());
 +    showInformationHistogram.setSelected(av.isShowInformationHistogram());
 +    showHMMSequenceLogo.setSelected(av.isShowHMMSequenceLogo());
 +    normaliseHMMSequenceLogo.setSelected(av.isNormaliseHMMSequenceLogo());
  
      ColourMenuHelper.setColourSelected(colourMenu,
              av.getGlobalColourScheme());
    }
  
    @Override
 +  public void hmmBuildSettings_actionPerformed()
 +  {
 +    if (!(alignmentIsSufficient(1)))
 +    {
 +      return;
 +    }
 +    WsParamSetI set = new HMMERPreset();
 +    List<ArgumentI> args = new ArrayList<>();
 +    ParamDatastoreI store = new HMMERParamStore("hmmbuild");
 +    WsJobParameters params = new WsJobParameters(new JFrame(), store, set,
 +            args);
 +    if (params.showRunDialog())
 +    {
 +      new Thread(new HMMBuildThread(this, params.getJobParams())).start();
 +    }
 +    alignPanel.repaint();
 +
 +  }
 +
 +  @Override
 +  public void hmmAlignSettings_actionPerformed()
 +  {
 +    if (!(checkForHMM() && alignmentIsSufficient(2)))
 +    {
 +      return;
 +    }
 +    WsParamSetI set = new HMMERPreset();
 +    List<ArgumentI> args = new ArrayList<>();
 +    ParamDatastoreI store = new HMMERParamStore("hmmalign");
 +    WsJobParameters params = new WsJobParameters(new JFrame(), store, set,
 +            args);
 +    if (params.showRunDialog())
 +    {
 +      new Thread(new HMMAlignThread(this, true, params.getJobParams()))
 +            .start();
 +    }
 +    alignPanel.repaint();
 +  }
 +
 +  @Override
 +  public void hmmSearchSettings_actionPerformed()
 +  {
 +    if (!checkForHMM())
 +    {
 +      return;
 +    }
 +    WsParamSetI set = new HMMERPreset();
 +    List<ArgumentI> args = new ArrayList<>();
 +    ParamDatastoreI store = new HMMERParamStore("hmmsearch");
 +    WsJobParameters params = new WsJobParameters(new JFrame(), store, set,
 +            args);
 +    if (params.showRunDialog())
 +    {
 +      new Thread(new HMMSearchThread(this, true, params.getJobParams()))
 +            .start();
 +    }
 +    alignPanel.repaint();
 +  }
 +
 +  @Override
 +  public void hmmBuildRun_actionPerformed()
 +  {
 +    if (!alignmentIsSufficient(1))
 +    {
 +      return;
 +    }
 +    new Thread(new HMMBuildThread(this, null))
 +            .start();
 +  }
 +
 +  @Override
 +  public void hmmAlignRun_actionPerformed()
 +  {
 +    if (!(checkForHMM() && alignmentIsSufficient(2)))
 +    {
 +      return;
 +    }
 +    new Thread(new HMMAlignThread(this, true, null))
 +            .start();
 +  }
 +
 +  @Override
 +  public void hmmSearchRun_actionPerformed()
 +  {
 +    if (!checkForHMM())
 +    {
 +      return;
 +    }
 +    new Thread(new HMMSearchThread(this, true, null))
 +            .start();
 +  }
 +
 +  /**
 +   * Checks if the frame has a selected hidden Markov model
 +   * 
 +   * @return
 +   */
 +  private boolean checkForHMM()
 +  {
 +    if (getSelectedHMM() == null)
 +    {
 +      JOptionPane.showMessageDialog(this,
 +              MessageManager.getString("warn.no_selected_hmm"));
 +      return false;
 +    }
 +    return true;
 +  }
 +
 +  /**
 +   * Checks if the alignment contains the required number of sequences.
 +   * 
 +   * @param required
 +   * @return
 +   */
 +  public boolean alignmentIsSufficient(int required)
 +  {
 +    if (getViewport().getAlignment().getSequences().size() < required)
 +    {
 +      JOptionPane.showMessageDialog(this,
 +              MessageManager.getString("warn.not_enough_sequences"));
 +      return false;
 +    }
 +    return true;
 +  }
 +
 +  @Override
 +  public void addDatabase_actionPerformed() throws IOException
 +  {
 +    if (Cache.getProperty(Preferences.HMMSEARCH_DB_PATHS) == null)
 +    {
 +      Cache.setProperty(Preferences.HMMSEARCH_DBS, "");
 +      Cache.setProperty(Preferences.HMMSEARCH_DB_PATHS, "");
 +    }
 +
 +    String path = openFileChooser(false);
 +    if (new File(path).exists())
 +    {
 +      IdentifyFile identifier = new IdentifyFile();
 +      FileFormatI format = identifier.identify(path, DataSourceType.FILE);
 +      if (format == FileFormat.Fasta || format == FileFormat.Stockholm
 +              || format == FileFormat.Pfam)
 +      {
 +        String currentDbs = Cache.getProperty(Preferences.HMMSEARCH_DBS);
 +        String currentDbPaths = Cache
 +                .getProperty(Preferences.HMMSEARCH_DB_PATHS);
 +        currentDbPaths += " " + path;
 +
 +        String fileName = StringUtils.getLastToken(path, File.separator);
 +        Scanner scanner = new Scanner(fileName).useDelimiter(".");
 +        String name = scanner.next();
 +        scanner.close();
 +        currentDbs += " " + path; // TODO remove path from file name
 +        scanner.close();
 +
 +        Cache.setProperty(Preferences.HMMSEARCH_DB_PATHS, currentDbPaths);
 +        Cache.setProperty(Preferences.HMMSEARCH_DBS, currentDbPaths);
 +      }
 +      else
 +      {
 +        JOptionPane.showMessageDialog(this,
 +                MessageManager.getString("warn.invalid_format"));
 +      }
 +    }
 +    else
 +    {
 +      JOptionPane.showMessageDialog(this,
 +              MessageManager.getString("warn.not_enough_sequences"));
 +    }
 +  }
 +
 +  /**
 +   * Opens a file chooser
 +   * 
 +   * @param forFolder
 +   * @return
 +   */
 +  protected String openFileChooser(boolean forFolder)
 +  {
 +    String choice = null;
 +    JFileChooser chooser = new JFileChooser();
 +    if (forFolder)
 +    {
 +      chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
 +    }
 +    chooser.setDialogTitle(
 +            MessageManager.getString("label.open_local_file"));
 +    chooser.setToolTipText(MessageManager.getString("action.open"));
 +
 +    int value = chooser.showOpenDialog(this);
 +
 +    if (value == JFileChooser.APPROVE_OPTION)
 +    {
 +      choice = chooser.getSelectedFile().getPath();
 +    }
 +    return choice;
 +  }
 +
 +  @Override
    public void reload_actionPerformed(ActionEvent e)
    {
      if (fileName != null)
    @Override
    public void exportFeatures_actionPerformed(ActionEvent e)
    {
-     new AnnotationExporter().exportFeatures(alignPanel);
+     new AnnotationExporter(alignPanel).exportFeatures();
    }
  
    @Override
    public void exportAnnotations_actionPerformed(ActionEvent e)
    {
-     new AnnotationExporter().exportAnnotations(alignPanel);
+     new AnnotationExporter(alignPanel).exportAnnotations();
    }
  
    @Override
    public void associatedData_actionPerformed(ActionEvent e)
 +          throws IOException, InterruptedException
    {
      // Pick the tree file
      JalviewFileChooser chooser = new JalviewFileChooser(
      }
      viewport.getAlignment().moveSelectedSequencesByOne(sg,
              viewport.getHiddenRepSequences(), up);
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, false);
    }
  
    synchronized void slideSequences(boolean right, int size)
      if (viewport.hasHiddenColumns())
      {
        hiddenColumns = new ArrayList<>();
 +
        int hiddenOffset = viewport.getSelectionGroup().getStartRes();
        int hiddenCutoff = viewport.getSelectionGroup().getEndRes();
        ArrayList<int[]> hiddenRegions = viewport.getAlignment()
                .getHiddenColumns().getHiddenColumnsCopy();
        for (int[] region : hiddenRegions)
 +
        {
          if (region[0] >= hiddenOffset && region[1] <= hiddenCutoff)
          {
     * 
     * @param e
     *          DOCUMENT ME!
 +   * @throws InterruptedException
 +   * @throws IOException
     */
    @Override
    protected void pasteNew_actionPerformed(ActionEvent e)
 +          throws IOException, InterruptedException
    {
      paste(true);
    }
     * 
     * @param e
     *          DOCUMENT ME!
 +   * @throws InterruptedException
 +   * @throws IOException
     */
    @Override
    protected void pasteThis_actionPerformed(ActionEvent e)
 +          throws IOException, InterruptedException
    {
      paste(false);
    }
     * 
     * @param newAlignment
     *          true to paste to a new alignment, otherwise add to this.
 +   * @throws InterruptedException
 +   * @throws IOException
     */
 -  void paste(boolean newAlignment)
 +  void paste(boolean newAlignment) throws IOException, InterruptedException
    {
      boolean externalPaste = true;
      try
        {
  
          // propagate alignment changed.
-         vpRanges.setEndSeq(alignment.getHeight());
+         viewport.getRanges().setEndSeq(alignment.getHeight());
          if (annotationAdded)
          {
            // Duplicate sequence annotation in all views.
        System.out.println("Exception whilst pasting: " + ex);
        // could be anything being pasted in here
      }
 -
    }
  
    @Override
      {
        PaintRefresher.Refresh(this, viewport.getSequenceSetId());
        alignPanel.updateAnnotation();
-       alignPanel.paintAlignment(true);
+       alignPanel.paintAlignment(true, true);
      }
    }
  
      // JAL-2034 - should delegate to
      // alignPanel to decide if overview needs
      // updating.
-     alignPanel.paintAlignment(false);
+     alignPanel.paintAlignment(false, false);
      PaintRefresher.Refresh(alignPanel, viewport.getSequenceSetId());
    }
  
      viewport.setSelectionGroup(null);
      viewport.getColumnSelection().clear();
      viewport.setSelectionGroup(null);
-     alignPanel.getSeqPanel().seqCanvas.highlightSearchResults(null);
      alignPanel.getIdPanel().getIdCanvas().searchResults = null;
      // JAL-2034 - should delegate to
      // alignPanel to decide if overview needs
      // updating.
-     alignPanel.paintAlignment(false);
+     alignPanel.paintAlignment(false, false);
      PaintRefresher.Refresh(alignPanel, viewport.getSequenceSetId());
      viewport.sendSelection();
    }
      // alignPanel to decide if overview needs
      // updating.
  
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, false);
      PaintRefresher.Refresh(alignPanel, viewport.getSequenceSetId());
      viewport.sendSelection();
    }
    public void invertColSel_actionPerformed(ActionEvent e)
    {
      viewport.invertColumnSelection();
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, false);
      viewport.sendSelection();
    }
  
        {
          trimRegion = new TrimRegionCommand("Remove Left", true, seqs,
                  column, viewport.getAlignment());
-         vpRanges.setStartRes(0);
+         viewport.getRanges().setStartRes(0);
        }
        else
        {
      // This is to maintain viewport position on first residue
      // of first sequence
      SequenceI seq = viewport.getAlignment().getSequenceAt(0);
-     int startRes = seq.findPosition(vpRanges.getStartRes());
+     ViewportRanges ranges = viewport.getRanges();
+     int startRes = seq.findPosition(ranges.getStartRes());
      // ShiftList shifts;
      // viewport.getAlignment().removeGaps(shifts=new ShiftList());
      // edit.alColumnChanges=shifts.getInverse();
      // if (viewport.hasHiddenColumns)
      // viewport.getColumnSelection().compensateForEdits(shifts);
-     vpRanges.setStartRes(seq.findIndex(startRes) - 1);
+     ranges.setStartRes(seq.findIndex(startRes) - 1);
      viewport.firePropertyChange("alignment", null,
              viewport.getAlignment().getSequences());
  
      // This is to maintain viewport position on first residue
      // of first sequence
      SequenceI seq = viewport.getAlignment().getSequenceAt(0);
-     int startRes = seq.findPosition(vpRanges.getStartRes());
+     int startRes = seq.findPosition(viewport.getRanges().getStartRes());
  
      addHistoryItem(new RemoveGapsCommand("Remove Gaps", seqs, start, end,
              viewport.getAlignment()));
  
-     vpRanges.setStartRes(seq.findIndex(startRes) - 1);
+     viewport.getRanges().setStartRes(seq.findIndex(startRes) - 1);
  
      viewport.firePropertyChange("alignment", null,
              viewport.getAlignment().getSequences());
      /*
       * Create a new AlignmentPanel (with its own, new Viewport)
       */
-     AlignmentPanel newap = new Jalview2XML().copyAlignPanel(alignPanel,
-             true);
+     AlignmentPanel newap = new Jalview2XML().copyAlignPanel(alignPanel);
      if (!copyAnnotation)
      {
        /*
  
      alignPanel.getIdPanel().getIdCanvas()
              .setPreferredSize(alignPanel.calculateIdWidth());
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, false);
    }
  
    @Override
    public void idRightAlign_actionPerformed(ActionEvent e)
    {
      viewport.setRightAlignIds(idRightAlign.isSelected());
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(false, false);
    }
  
    @Override
    public void centreColumnLabels_actionPerformed(ActionEvent e)
    {
      viewport.setCentreColumnLabels(centreColumnLabelsMenuItem.getState());
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(false, false);
    }
  
    /*
    protected void colourTextMenuItem_actionPerformed(ActionEvent e)
    {
      viewport.setColourText(colourTextMenuItem.isSelected());
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(false, false);
    }
  
    /**
    public void showAllColumns_actionPerformed(ActionEvent e)
    {
      viewport.showAllHiddenColumns();
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, true);
      viewport.sendSelection();
    }
  
      viewport.expandColSelection(sg, false);
      viewport.hideAllSelectedSeqs();
      viewport.hideSelectedColumns();
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, true);
      viewport.sendSelection();
    }
  
    {
      viewport.showAllHiddenColumns();
      viewport.showAllHiddenSeqs();
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, true);
      viewport.sendSelection();
    }
  
    public void hideSelColumns_actionPerformed(ActionEvent e)
    {
      viewport.hideSelectedColumns();
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, true);
      viewport.sendSelection();
    }
  
    protected void scaleAbove_actionPerformed(ActionEvent e)
    {
      viewport.setScaleAboveWrapped(scaleAbove.isSelected());
-     alignPanel.paintAlignment(true);
+     // TODO: do we actually need to update overview for scale above change ?
+     alignPanel.paintAlignment(true, false);
    }
  
    /**
    protected void scaleLeft_actionPerformed(ActionEvent e)
    {
      viewport.setScaleLeftWrapped(scaleLeft.isSelected());
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, false);
    }
  
    /**
    protected void scaleRight_actionPerformed(ActionEvent e)
    {
      viewport.setScaleRightWrapped(scaleRight.isSelected());
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, false);
    }
  
    /**
    public void viewBoxesMenuItem_actionPerformed(ActionEvent e)
    {
      viewport.setShowBoxes(viewBoxesMenuItem.isSelected());
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(false, false);
    }
  
    /**
    public void viewTextMenuItem_actionPerformed(ActionEvent e)
    {
      viewport.setShowText(viewTextMenuItem.isSelected());
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(false, false);
    }
  
    /**
    protected void renderGapsMenuItem_actionPerformed(ActionEvent e)
    {
      viewport.setRenderGaps(renderGapsMenuItem.isSelected());
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(false, false);
    }
  
    public FeatureSettings featureSettings;
    public void showSeqFeatures_actionPerformed(ActionEvent evt)
    {
      viewport.setShowSequenceFeatures(showSeqFeatures.isSelected());
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, true);
    }
  
    /**
  
      viewport.setGlobalColourScheme(cs);
  
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, true);
    }
  
    /**
              viewport.getAlignment().getSequenceAt(0));
      addHistoryItem(new OrderCommand("Pairwise Sort", oldOrder,
              viewport.getAlignment()));
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, false);
    }
  
    /**
      AlignmentSorter.sortByID(viewport.getAlignment());
      addHistoryItem(
              new OrderCommand("ID Sort", oldOrder, viewport.getAlignment()));
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, false);
    }
  
    /**
      AlignmentSorter.sortByLength(viewport.getAlignment());
      addHistoryItem(new OrderCommand("Length Sort", oldOrder,
              viewport.getAlignment()));
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, false);
    }
  
    /**
      addHistoryItem(new OrderCommand("Group Sort", oldOrder,
              viewport.getAlignment()));
  
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, false);
    }
  
    /**
          addHistoryItem(new OrderCommand(order.getName(), oldOrder,
                  viewport.getAlignment()));
  
-         alignPanel.paintAlignment(true);
+         alignPanel.paintAlignment(true, false);
        }
      });
    }
                  viewport.getAlignment());// ,viewport.getSelectionGroup());
          addHistoryItem(new OrderCommand("Sort by " + scoreLabel, oldOrder,
                  viewport.getAlignment()));
-         alignPanel.paintAlignment(true);
+         alignPanel.paintAlignment(true, false);
        }
      });
    }
        addHistoryItem(new OrderCommand(undoname, oldOrder,
                viewport.getAlignment()));
      }
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(true, false);
      return true;
    }
  
    protected void showProductsFor(final SequenceI[] sel, final boolean _odna,
            final String source)
    {
-     new Thread(CrossRefAction.showProductsFor(sel, _odna, source, this))
+     new Thread(CrossRefAction.getHandlerFor(sel, _odna, source, this))
              .start();
    }
  
      // Java's Transferable for native dnd
      evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
      Transferable t = evt.getTransferable();
-     List<String> files = new ArrayList<>();
+     final AlignFrame thisaf = this;
+     final List<String> files = new ArrayList<>();
      List<DataSourceType> protocols = new ArrayList<>();
  
      try
      }
      if (files != null)
      {
-       try
+       new Thread(new Runnable()
        {
-         // check to see if any of these files have names matching sequences in
-         // the alignment
-         SequenceIdMatcher idm = new SequenceIdMatcher(
-                 viewport.getAlignment().getSequencesArray());
-         /**
-          * Object[] { String,SequenceI}
-          */
-         ArrayList<Object[]> filesmatched = new ArrayList<>();
-         ArrayList<String> filesnotmatched = new ArrayList<>();
-         for (int i = 0; i < files.size(); i++)
+         @Override
+         public void run()
          {
-           String file = files.get(i).toString();
-           String pdbfn = "";
-           DataSourceType protocol = FormatAdapter.checkProtocol(file);
-           if (protocol == DataSourceType.FILE)
-           {
-             File fl = new File(file);
-             pdbfn = fl.getName();
-           }
-           else if (protocol == DataSourceType.URL)
-           {
-             URL url = new URL(file);
-             pdbfn = url.getFile();
-           }
-           if (pdbfn.length() > 0)
+           try
            {
-             // attempt to find a match in the alignment
-             SequenceI[] mtch = idm.findAllIdMatches(pdbfn);
-             int l = 0, c = pdbfn.indexOf(".");
-             while (mtch == null && c != -1)
+             // check to see if any of these files have names matching sequences
+             // in
+             // the alignment
+             SequenceIdMatcher idm = new SequenceIdMatcher(
+                     viewport.getAlignment().getSequencesArray());
+             /**
+              * Object[] { String,SequenceI}
+              */
+             ArrayList<Object[]> filesmatched = new ArrayList<>();
+             ArrayList<String> filesnotmatched = new ArrayList<>();
+             for (int i = 0; i < files.size(); i++)
              {
-               do
+               String file = files.get(i).toString();
+               String pdbfn = "";
+               DataSourceType protocol = FormatAdapter.checkProtocol(file);
+               if (protocol == DataSourceType.FILE)
                {
-                 l = c;
-               } while ((c = pdbfn.indexOf(".", l)) > l);
-               if (l > -1)
-               {
-                 pdbfn = pdbfn.substring(0, l);
+                 File fl = new File(file);
+                 pdbfn = fl.getName();
                }
-               mtch = idm.findAllIdMatches(pdbfn);
-             }
-             if (mtch != null)
-             {
-               FileFormatI type = null;
-               try
-               {
-                 type = new IdentifyFile().identify(file, protocol);
-               } catch (Exception ex)
+               else if (protocol == DataSourceType.URL)
                {
-                 type = null;
+                 URL url = new URL(file);
+                 pdbfn = url.getFile();
                }
-               if (type != null && type.isStructureFile())
+               if (pdbfn.length() > 0)
                {
-                 filesmatched.add(new Object[] { file, protocol, mtch });
-                 continue;
+                 // attempt to find a match in the alignment
+                 SequenceI[] mtch = idm.findAllIdMatches(pdbfn);
+                 int l = 0, c = pdbfn.indexOf(".");
+                 while (mtch == null && c != -1)
+                 {
+                   do
+                   {
+                     l = c;
+                   } while ((c = pdbfn.indexOf(".", l)) > l);
+                   if (l > -1)
+                   {
+                     pdbfn = pdbfn.substring(0, l);
+                   }
+                   mtch = idm.findAllIdMatches(pdbfn);
+                 }
+                 if (mtch != null)
+                 {
+                   FileFormatI type = null;
+                   try
+                   {
+                     type = new IdentifyFile().identify(file, protocol);
+                   } catch (Exception ex)
+                   {
+                     type = null;
+                   }
+                   if (type != null && type.isStructureFile())
+                   {
+                     filesmatched.add(new Object[] { file, protocol, mtch });
+                     continue;
+                   }
+                 }
+                 // File wasn't named like one of the sequences or wasn't a PDB
+                 // file.
+                 filesnotmatched.add(file);
                }
              }
-             // File wasn't named like one of the sequences or wasn't a PDB file.
-             filesnotmatched.add(file);
-           }
-         }
-         int assocfiles = 0;
-         if (filesmatched.size() > 0)
-         {
-           if (Cache.getDefault("AUTOASSOCIATE_PDBANDSEQS", false)
-                   || JvOptionPane.showConfirmDialog(this,
-                           MessageManager.formatMessage(
-                                   "label.automatically_associate_structure_files_with_sequences_same_name",
-                                   new Object[]
-                                   { Integer.valueOf(filesmatched.size())
-                                           .toString() }),
-                           MessageManager.getString(
-                                   "label.automatically_associate_structure_files_by_name"),
-                           JvOptionPane.YES_NO_OPTION) == JvOptionPane.YES_OPTION)
-           {
-             for (Object[] fm : filesmatched)
+             int assocfiles = 0;
+             if (filesmatched.size() > 0)
              {
-               // try and associate
-               // TODO: may want to set a standard ID naming formalism for
-               // associating PDB files which have no IDs.
-               for (SequenceI toassoc : (SequenceI[]) fm[2])
+               if (Cache.getDefault("AUTOASSOCIATE_PDBANDSEQS", false)
+                       || JvOptionPane.showConfirmDialog(thisaf,
+                               MessageManager.formatMessage(
+                                       "label.automatically_associate_structure_files_with_sequences_same_name",
+                                       new Object[]
+                                       { Integer.valueOf(filesmatched.size())
+                                               .toString() }),
+                               MessageManager.getString(
+                                       "label.automatically_associate_structure_files_by_name"),
+                               JvOptionPane.YES_NO_OPTION) == JvOptionPane.YES_OPTION)
                {
-                 PDBEntry pe = new AssociatePdbFileWithSeq()
-                         .associatePdbWithSeq((String) fm[0],
-                                 (DataSourceType) fm[1], toassoc, false,
-                                 Desktop.instance);
-                 if (pe != null)
+                 for (Object[] fm : filesmatched)
                  {
-                   System.err.println("Associated file : " + ((String) fm[0])
-                           + " with " + toassoc.getDisplayId(true));
-                   assocfiles++;
+                   // try and associate
+                   // TODO: may want to set a standard ID naming formalism for
+                   // associating PDB files which have no IDs.
+                   for (SequenceI toassoc : (SequenceI[]) fm[2])
+                   {
+                     PDBEntry pe = new AssociatePdbFileWithSeq()
+                             .associatePdbWithSeq((String) fm[0],
+                                     (DataSourceType) fm[1], toassoc, false,
+                                     Desktop.instance);
+                     if (pe != null)
+                     {
+                       System.err.println("Associated file : "
+                               + ((String) fm[0]) + " with "
+                               + toassoc.getDisplayId(true));
+                       assocfiles++;
+                     }
+                   }
+                   // TODO: do we need to update overview ? only if features are
+                   // shown I guess
+                   alignPanel.paintAlignment(true, false);
                  }
                }
-               alignPanel.paintAlignment(true);
              }
-           }
-         }
-         if (filesnotmatched.size() > 0)
-         {
-           if (assocfiles > 0 && (Cache.getDefault(
-                   "AUTOASSOCIATE_PDBANDSEQS_IGNOREOTHERS", false)
-                   || JvOptionPane.showConfirmDialog(this,
-                           "<html>" + MessageManager.formatMessage(
-                                   "label.ignore_unmatched_dropped_files_info",
-                                   new Object[]
-                                   { Integer.valueOf(filesnotmatched.size())
-                                           .toString() })
-                                   + "</html>",
-                           MessageManager.getString(
-                                   "label.ignore_unmatched_dropped_files"),
-                           JvOptionPane.YES_NO_OPTION) == JvOptionPane.YES_OPTION))
-           {
-             return;
-           }
-           for (String fn : filesnotmatched)
+             if (filesnotmatched.size() > 0)
+             {
+               if (assocfiles > 0 && (Cache.getDefault(
+                       "AUTOASSOCIATE_PDBANDSEQS_IGNOREOTHERS", false)
+                       || JvOptionPane.showConfirmDialog(thisaf,
+                               "<html>" + MessageManager.formatMessage(
+                                       "label.ignore_unmatched_dropped_files_info",
+                                       new Object[]
+                                       { Integer.valueOf(
+                                               filesnotmatched.size())
+                                               .toString() })
+                                       + "</html>",
+                               MessageManager.getString(
+                                       "label.ignore_unmatched_dropped_files"),
+                               JvOptionPane.YES_NO_OPTION) == JvOptionPane.YES_OPTION))
+               {
+                 return;
+               }
+               for (String fn : filesnotmatched)
+               {
+                 loadJalviewDataFile(fn, null, null, null);
+               }
+             }
+           } catch (Exception ex)
            {
-             loadJalviewDataFile(fn, null, null, null);
+             ex.printStackTrace();
            }
          }
-       } catch (Exception ex)
-       {
-         ex.printStackTrace();
-       }
+       }).start();
      }
    }
  
     * 
     * @param file
     *          either a filename or a URL string.
 +   * @throws InterruptedException
 +   * @throws IOException
     */
    public void loadJalviewDataFile(String file, DataSourceType sourceType,
            FileFormatI format, SequenceI assocSeq)
            {
              if (parseFeaturesFile(file, sourceType))
              {
-               alignPanel.paintAlignment(true);
+               alignPanel.paintAlignment(true, true);
              }
            }
            else
        }
        if (isAnnotation)
        {
          alignPanel.adjustAnnotationHeight();
          viewport.updateSequenceIdColours();
          buildSortByAnnotationScoresMenu();
-         alignPanel.paintAlignment(true);
+         alignPanel.paintAlignment(true, true);
        }
      } catch (Exception ex)
      {
    protected void showUnconservedMenuItem_actionPerformed(ActionEvent e)
    {
      viewport.setShowUnconserved(showNonconservedMenuItem.getState());
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(false, false);
    }
  
    /*
    }
  
    @Override
 +  protected void showInformationHistogram_actionPerformed(ActionEvent e)
 +  {
 +    viewport.setShowInformationHistogram(
 +            showInformationHistogram.getState());
 +    alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
 +  }
 +
 +  @Override
 +  protected void showHMMSequenceLogo_actionPerformed(ActionEvent e)
 +  {
 +    viewport.setShowHMMSequenceLogo(showHMMSequenceLogo.getState());
 +    alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
 +  }
 +
 +  @Override
 +  protected void normaliseHMMSequenceLogo_actionPerformed(ActionEvent e)
 +  {
 +    showHMMSequenceLogo.setState(true);
 +    viewport.setShowHMMSequenceLogo(true);
 +    viewport.setNormaliseHMMSequenceLogo(normaliseSequenceLogo.getState());
 +    alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
 +  }
 +
 +  @Override
    protected void applyAutoAnnotationSettings_actionPerformed(ActionEvent e)
    {
      alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
      {
        PaintRefresher.Refresh(this, viewport.getSequenceSetId());
        alignPanel.updateAnnotation();
-       alignPanel.paintAlignment(true);
+       alignPanel.paintAlignment(true, true);
      }
    }
  
        viewport.getAlignment().setSeqrep(null);
        PaintRefresher.Refresh(this, viewport.getSequenceSetId());
        alignPanel.updateAnnotation();
-       alignPanel.paintAlignment(true);
+       alignPanel.paintAlignment(true, true);
      }
    }
  
      this.alignPanel.av.setSortAnnotationsBy(getAnnotationSortOrder());
      this.alignPanel.av
              .setShowAutocalculatedAbove(isShowAutoCalculatedAbove());
-     alignPanel.paintAlignment(true);
+     alignPanel.paintAlignment(false, false);
    }
  
    /**
      }
    }
  
 +  /**
 +   * Sets the status of the HMMER menu
 +   */
 +  public void updateHMMERStatus()
 +  {
 +    hmmerMenu.setEnabled(HmmerCommand.isHmmerAvailable());
 +  }
 +
 +  /**
 +   * Returns the selected hidden Markov model.
 +   * 
 +   * @return
 +   */
 +  public HiddenMarkovModel getSelectedHMM()
 +  {
 +    if (selectedHMMSequence == null)
 +    {
 +      return null;
 +    }
 +    return selectedHMMSequence.getHMM();
 +  }
 +
 +  /**
 +   * Returns the selected hidden Markov model.
 +   * 
 +   * @return
 +   */
 +  public SequenceI getSelectedHMMSequence()
 +  {
 +    return selectedHMMSequence;
 +  }
 +
 +  /**
 +   * Sets the selected hidden Markov model
 +   * 
 +   * @param selectedHMM
 +   */
 +  public void setSelectedHMMSequence(SequenceI selectedHMM)
 +  {
 +    this.selectedHMMSequence = selectedHMM;
 +    hmmAlign.setText(MessageManager.getString("label.hmmalign") + " to "
 +            + selectedHMM.getHMM().getName());
 +    hmmSearch.setText(MessageManager.getString("label.hmmsearch") + " with "
 +            + selectedHMM.getHMM().getName());
 +  }
 +
 +  @Override
 +  public void hmmerMenu_actionPerformed(ActionEvent e)
 +  {
 +    SequenceGroup grp = getViewport().getSelectionGroup();
 +    if (grp != null)
 +    {
 +      hmmBuild.setText(MessageManager.getString("label.hmmbuild") + " from "
 +              + grp.getName());
 +    }
 +    else
 +    {
 +      hmmBuild.setText(MessageManager.getString("label.hmmbuild")
 +              + " from Alignment");
 +    }
 +  }
 +
+   @Override
+   protected void loadVcf_actionPerformed()
+   {
+     JalviewFileChooser chooser = new JalviewFileChooser(
+             Cache.getProperty("LAST_DIRECTORY"));
+     chooser.setFileView(new JalviewFileView());
+     chooser.setDialogTitle(MessageManager.getString("label.load_vcf_file"));
+     chooser.setToolTipText(MessageManager.getString("label.load_vcf_file"));
+     int value = chooser.showOpenDialog(null);
+     if (value == JalviewFileChooser.APPROVE_OPTION)
+     {
+       String choice = chooser.getSelectedFile().getPath();
+       Cache.setProperty("LAST_DIRECTORY", choice);
+       SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
+       new VCFLoader(choice).loadVCF(seqs, this);
+     }
+   }
  }
  
  class PrintThread extends Thread
@@@ -22,7 -22,7 +22,6 @@@ package jalview.gui
  
  import jalview.analysis.AlignmentUtils;
  import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
--import jalview.analysis.TreeModel;
  import jalview.api.AlignViewportI;
  import jalview.api.AlignmentViewPanel;
  import jalview.api.FeatureColourI;
@@@ -76,8 -76,6 +75,6 @@@ public class AlignViewport extends Alig
  {
    Font font;
  
-   TreeModel currentTree = null;
    boolean cursorMode = false;
  
    boolean antiAlias = false;
        showOccupancy = Cache.getDefault(Preferences.SHOW_OCCUPANCY, true);
      }
      initAutoAnnotation();
 +    initInformation();
 +
 +
      String colourProperty = alignment.isNucleotide()
              ? Preferences.DEFAULT_COLOUR_NUC
              : Preferences.DEFAULT_COLOUR_PROT;
    }
  
    /**
-    * DOCUMENT ME!
-    * 
-    * @param tree
-    *          DOCUMENT ME!
-    */
-   public void setCurrentTree(TreeModel tree)
-   {
-     currentTree = tree;
-   }
-   /**
-    * DOCUMENT ME!
-    * 
-    * @return DOCUMENT ME!
-    */
-   public TreeModel getCurrentTree()
-   {
-     return currentTree;
-   }
-   /**
     * returns the visible column regions of the alignment
     * 
     * @param selectedRegionOnly
              .getStructureSelectionManager(Desktop.instance);
    }
  
 +  /**
 +   * 
 +   * @param pdbEntries
 +   * @return an array of SequenceI arrays, one for each PDBEntry, listing which
 +   *         sequences in the alignment hold a reference to it
 +   */
 +  public SequenceI[][] collateForPDB(PDBEntry[] pdbEntries)
 +  {
 +    List<SequenceI[]> seqvectors = new ArrayList<>();
 +    for (PDBEntry pdb : pdbEntries)
 +    {
 +      List<SequenceI> choosenSeqs = new ArrayList<>();
 +      for (SequenceI sq : alignment.getSequences())
 +      {
 +        Vector<PDBEntry> pdbRefEntries = sq.getDatasetSequence()
 +                .getAllPDBEntries();
 +        if (pdbRefEntries == null)
 +        {
 +          continue;
 +        }
 +        for (PDBEntry pdbRefEntry : pdbRefEntries)
 +        {
 +          if (pdbRefEntry.getId().equals(pdb.getId()))
 +          {
 +            if (pdbRefEntry.getChainCode() != null
 +                    && pdb.getChainCode() != null)
 +            {
 +              if (pdbRefEntry.getChainCode().equalsIgnoreCase(
 +                      pdb.getChainCode()) && !choosenSeqs.contains(sq))
 +              {
 +                choosenSeqs.add(sq);
 +                continue;
 +              }
 +            }
 +            else
 +            {
 +              if (!choosenSeqs.contains(sq))
 +              {
 +                choosenSeqs.add(sq);
 +                continue;
 +              }
 +            }
 +
 +          }
 +        }
 +      }
 +      seqvectors
 +              .add(choosenSeqs.toArray(new SequenceI[choosenSeqs.size()]));
 +    }
 +    return seqvectors.toArray(new SequenceI[seqvectors.size()][]);
 +  }
 +
    @Override
    public boolean isNormaliseSequenceLogo()
    {
      return normaliseSequenceLogo;
    }
  
 +
    public void setNormaliseSequenceLogo(boolean state)
    {
      normaliseSequenceLogo = state;
    }
  
 +  public void setNormaliseHMMSequenceLogo(boolean state)
 +  {
 +    normaliseHMMSequenceLogo = state;
 +  }
 +
    /**
     * 
     * @return true if alignment characters should be displayed
      return validCharWidth;
    }
  
 -  private Hashtable<String, AutoCalcSetting> calcIdParams = new Hashtable<String, AutoCalcSetting>();
 +  private Hashtable<String, AutoCalcSetting> calcIdParams = new Hashtable<>();
  
    public AutoCalcSetting getCalcIdSettingsFor(String calcId)
    {
      }
      fr.setTransparency(featureSettings.getTransparency());
    }
 +
 +  @Override
 +  public boolean isNormaliseHMMSequenceLogo()
 +  {
 +    return normaliseHMMSequenceLogo;
 +  }
 +
  }
@@@ -20,6 -20,7 +20,7 @@@
   */
  package jalview.gui;
  
+ import jalview.analysis.AlignSeq;
  import jalview.analysis.AlignmentUtils;
  import jalview.datamodel.Alignment;
  import jalview.datamodel.AlignmentAnnotation;
@@@ -29,9 -30,12 +30,12 @@@ import jalview.datamodel.SequenceGroup
  import jalview.datamodel.SequenceI;
  import jalview.io.FileFormat;
  import jalview.io.FormatAdapter;
+ import jalview.util.Comparison;
  import jalview.util.MessageManager;
+ import jalview.util.Platform;
  
  import java.awt.Color;
+ import java.awt.Cursor;
  import java.awt.Dimension;
  import java.awt.Font;
  import java.awt.FontMetrics;
@@@ -62,64 -66,71 +66,71 @@@ import javax.swing.SwingUtilities
  import javax.swing.ToolTipManager;
  
  /**
-  * DOCUMENT ME!
-  * 
-  * @author $author$
-  * @version $Revision$
+  * The panel that holds the labels for alignment annotations, providing
+  * tooltips, context menus, drag to reorder rows, and drag to adjust panel
+  * height
   */
  public class AnnotationLabels extends JPanel
          implements MouseListener, MouseMotionListener, ActionListener
  {
+   // width in pixels within which height adjuster arrows are shown and active
+   private static final int HEIGHT_ADJUSTER_WIDTH = 50;
    private static final Pattern LEFT_ANGLE_BRACKET_PATTERN = Pattern
            .compile("<");
  
+   private static final Font font = new Font("Arial", Font.PLAIN, 11);
  
-   String TOGGLE_LABELSCALE = MessageManager
+   private static final String TOGGLE_LABELSCALE = MessageManager
            .getString("label.scale_label_to_column");
  
-   String ADDNEW = MessageManager.getString("label.add_new_row");
+   private static final String ADDNEW = MessageManager
+           .getString("label.add_new_row");
  
-   String EDITNAME = MessageManager
+   private static final String EDITNAME = MessageManager
            .getString("label.edit_label_description");
  
-   String HIDE = MessageManager.getString("label.hide_row");
+   private static final String HIDE = MessageManager
+           .getString("label.hide_row");
  
-   String DELETE = MessageManager.getString("label.delete_row");
+   private static final String DELETE = MessageManager
+           .getString("label.delete_row");
  
-   String SHOWALL = MessageManager.getString("label.show_all_hidden_rows");
+   private static final String SHOWALL = MessageManager
+           .getString("label.show_all_hidden_rows");
  
-   String OUTPUT_TEXT = MessageManager.getString("label.export_annotation");
+   private static final String OUTPUT_TEXT = MessageManager
+           .getString("label.export_annotation");
  
-   String COPYCONS_SEQ = MessageManager
+   private static final String COPYCONS_SEQ = MessageManager
            .getString("label.copy_consensus_sequence");
  
-   boolean resizePanel = false;
+   private static Image adjusterImage;
  
-   Image image;
+   private static int adjusterImageHeight;
  
-   AlignmentPanel ap;
+   private final boolean debugRedraw = false;
  
-   AlignViewport av;
+   private AlignmentPanel ap;
  
-   boolean resizing = false;
+   AlignViewport av;
  
-   MouseEvent dragEvent;
+   private MouseEvent dragEvent;
  
-   int oldY;
+   private int oldY;
  
-   int selectedRow;
+   private int selectedRow;
  
    private int scrollOffset = 0;
  
-   Font font = new Font("Arial", Font.PLAIN, 11);
    private boolean hasHiddenRows;
  
+   private boolean resizePanel = false;
    /**
-    * Creates a new AnnotationLabels object.
+    * Creates a new AnnotationLabels object
     * 
     * @param ap
-    *          DOCUMENT ME!
     */
    public AnnotationLabels(AlignmentPanel ap)
    {
      av = ap.av;
      ToolTipManager.sharedInstance().registerComponent(this);
  
+     if (adjusterImage == null)
+     {
+       loadAdjusterImage();
+     }
+     addMouseListener(this);
+     addMouseMotionListener(this);
+     addMouseWheelListener(ap.getAnnotationPanel());
+   }
+   public AnnotationLabels(AlignViewport av)
+   {
+     this.av = av;
+   }
+   /**
+    * Loads the gif for the panel height adjustment
+    */
+   protected void loadAdjusterImage()
+   {
      java.net.URL url = getClass().getResource("/images/idwidth.gif");
      Image temp = null;
  
      if (url != null)
      {
-       temp = java.awt.Toolkit.getDefaultToolkit().createImage(url);
+       temp = Toolkit.getDefaultToolkit().createImage(url);
      }
  
      try
      Graphics2D g = (Graphics2D) bi.getGraphics();
      g.rotate(Math.toRadians(90));
      g.drawImage(temp, 0, -bi.getWidth(this), this);
-     image = bi;
-     addMouseListener(this);
-     addMouseMotionListener(this);
-     addMouseWheelListener(ap.getAnnotationPanel());
-   }
-   public AnnotationLabels(AlignViewport av)
-   {
-     this.av = av;
+     adjusterImage = bi;
+     adjusterImageHeight = bi.getHeight();
    }
  
    /**
      }
      else if (evt.getActionCommand().equals(OUTPUT_TEXT))
      {
-       new AnnotationExporter().exportAnnotations(ap,
-               new AlignmentAnnotation[]
-               { aa[selectedRow] });
+       new AnnotationExporter(ap).exportAnnotation(aa[selectedRow]);
      }
      else if (evt.getActionCommand().equals(COPYCONS_SEQ))
      {
      if (selectedRow < aa.length)
      {
        final String label = aa[selectedRow].label;
 -      if (!aa[selectedRow].autoCalculated)
 +      if (!(aa[selectedRow].autoCalculated)
 +              && !("HMM".equals(aa[selectedRow].getCalcId())))
        {
          if (aa[selectedRow].graph == AlignmentAnnotation.NO_GRAPH)
          {
        }
        else if (label.indexOf("Consensus") > -1)
        {
 +
 +
          pop.addSeparator();
          // av and sequencegroup need to implement same interface for
 +
          final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
                  MessageManager.getString("label.ignore_gaps_consensus"),
                  (aa[selectedRow].groupRef != null)
                // can be
                // updated.
                av.setShowConsensusHistogram(chist.getState());
 -              ap.alignFrame.setMenusForViewport();
                ap.repaint();
                // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
              }
                // can be
                // updated.
                av.setShowSequenceLogo(cprof.getState());
 -              ap.alignFrame.setMenusForViewport();
                ap.repaint();
                // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
              }
                // updated.
                av.setShowSequenceLogo(true);
                av.setNormaliseSequenceLogo(cprofnorm.getState());
 -              ap.alignFrame.setMenusForViewport();
                ap.repaint();
                // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
              }
          consclipbrd.addActionListener(this);
          pop.add(consclipbrd);
        }
 +      else if ("HMM".equals(aa[selectedRow].getCalcId())) // TODO create labels
 +                                                          // in message resource
 +                                                          // for these
 +      {
 +        pop.addSeparator();
 +        final AlignmentAnnotation aaa = aa[selectedRow];
 +
 +        final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
 +                MessageManager.getString(
 +                        "label.ignore_below_background_frequency"),
 +                (aa[selectedRow].groupRef != null)
 +                        ? aa[selectedRow].groupRef
 +                                .getIgnoreBelowBackground()
 +                        : ap.av.isIgnoreBelowBackground());
 +
 +        cbmi.addActionListener(new ActionListener()
 +        {
 +          @Override
 +          public void actionPerformed(ActionEvent e)
 +          {
 +
 +            if (aaa.groupRef != null)
 +            {
 +              // TODO: pass on reference to ap so the view can be updated.
 +              if (aaa.groupRef.getInfoLetterHeight() == false)
 +              {
 +                aaa.groupRef.setIgnoreBelowBackground(cbmi.getState());
 +                ap.getAnnotationPanel()
 +                        .paint(ap.getAnnotationPanel().getGraphics());
 +              }
 +            }
 +            else if (ap.av.isInfoLetterHeight() == false)
 +            {
 +              ap.av.setIgnoreBelowBackground(cbmi.getState(), ap);
 +            }
 +            ap.alignmentChanged();
 +          }
 +        });
 +        pop.add(cbmi);
 +        final JCheckBoxMenuItem letteHeight = new JCheckBoxMenuItem(
 +                MessageManager.getString("label.use_info_for_height"),
 +                (aa[selectedRow].groupRef != null)
 +                        ? aa[selectedRow].groupRef.getInfoLetterHeight()
 +                        : ap.av.isInfoLetterHeight());
 +
 +        letteHeight.addActionListener(new ActionListener()
 +        {
 +          @Override
 +          public void actionPerformed(ActionEvent e)
 +          {
 +            if (aaa.groupRef != null)
 +            {
 +              // TODO: pass on reference to ap so the view can be updated.
 +              aaa.groupRef.setInfoLetterHeight((letteHeight.getState()));
 +              if (aaa.groupRef.getIgnoreBelowBackground() == false)
 +              {
 +                aaa.groupRef.setIgnoreBelowBackground(true);
 +              }
 +              ap.getAnnotationPanel()
 +                      .paint(ap.getAnnotationPanel().getGraphics());
 +            }
 +            else
 +            {
 +              ap.av.setInfoLetterHeight(letteHeight.getState(), ap);
 +              if (ap.av.isIgnoreBelowBackground() == false)
 +              {
 +                ap.av.setIgnoreBelowBackground(true, ap);
 +              }
 +            }
 +            ap.alignmentChanged();
 +          }
 +        });
 +        pop.add(letteHeight);
 +        if (aaa.groupRef != null)
 +        {
 +          final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
 +                  MessageManager.getString("label.show_group_histogram"),
 +                  aa[selectedRow].groupRef.isShowInformationHistogram());
 +          chist.addActionListener(new ActionListener()
 +          {
 +            @Override
 +            public void actionPerformed(ActionEvent e)
 +            {
 +              // TODO: pass on reference
 +              // to ap
 +              // so the
 +              // view
 +              // can be
 +              // updated.
 +              aaa.groupRef.setShowInformationHistogram(chist.getState());
 +              ap.repaint();
 +              // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
 +            }
 +          });
 +          pop.add(chist);
 +          final JCheckBoxMenuItem cprofl = new JCheckBoxMenuItem(
 +                  MessageManager.getString("label.show_group_logo"),
 +                  aa[selectedRow].groupRef.isShowHMMSequenceLogo());
 +          cprofl.addActionListener(new ActionListener()
 +          {
 +            @Override
 +            public void actionPerformed(ActionEvent e)
 +            {
 +              // TODO: pass on reference
 +              // to ap
 +              // so the
 +              // view
 +              // can be
 +              // updated.
 +              aaa.groupRef.setshowHMMSequenceLogo(cprofl.getState());
 +              ap.repaint();
 +              // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
 +            }
 +          });
 +          pop.add(cprofl);
 +          final JCheckBoxMenuItem cproflnorm = new JCheckBoxMenuItem(
 +                  MessageManager.getString("label.normalise_group_logo"),
 +                  aa[selectedRow].groupRef.isNormaliseHMMSequenceLogo());
 +          cproflnorm.addActionListener(new ActionListener()
 +          {
 +            @Override
 +            public void actionPerformed(ActionEvent e)
 +            {
 +
 +              // TODO: pass on reference
 +              // to ap
 +              // so the
 +              // view
 +              // can be
 +              // updated.
 +              aaa.groupRef
 +                      .setNormaliseHMMSequenceLogo(cproflnorm.getState());
 +              // automatically enable logo display if we're clicked
 +              aaa.groupRef.setshowHMMSequenceLogo(true);
 +              ap.repaint();
 +              // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
 +            }
 +          });
 +          pop.add(cproflnorm);
 +        }
 +        else
 +        {
 +          final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
 +                  MessageManager.getString("label.show_histogram"),
 +                  av.isShowInformationHistogram());
 +          chist.addActionListener(new ActionListener()
 +          {
 +            @Override
 +            public void actionPerformed(ActionEvent e)
 +            {
 +              // TODO: pass on reference
 +              // to ap
 +              // so the
 +              // view
 +              // can be
 +              // updated.
 +              av.setShowInformationHistogram(chist.getState());
 +              ap.repaint();
 +              // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
 +            }
 +          });
 +          pop.add(chist);
 +          final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
 +                  MessageManager.getString("label.show_logo"),
 +                  av.isShowHMMSequenceLogo());
 +          cprof.addActionListener(new ActionListener()
 +          {
 +            @Override
 +            public void actionPerformed(ActionEvent e)
 +            {
 +              // TODO: pass on reference
 +              // to ap
 +              // so the
 +              // view
 +              // can be
 +              // updated.
 +              av.updateInformation(ap);
 +              av.setShowHMMSequenceLogo(cprof.getState());
 +              ap.repaint();
 +              // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
 +            }
 +          });
 +          pop.add(cprof);
 +          final JCheckBoxMenuItem cprofnorm = new JCheckBoxMenuItem(
 +                  MessageManager.getString("label.normalise_logo"),
 +                  av.isNormaliseHMMSequenceLogo());
 +          cprofnorm.addActionListener(new ActionListener()
 +          {
 +            @Override
 +            public void actionPerformed(ActionEvent e)
 +            {
 +              // TODO: pass on reference
 +              // to ap
 +              // so the
 +              // view
 +              // can be
 +              // updated.
 +              av.setShowHMMSequenceLogo(true);
 +              av.setNormaliseHMMSequenceLogo(cprofnorm.getState());
 +              ap.repaint();
 +              // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
 +            }
 +          });
 +          pop.add(cprofnorm);
 +        }
 +      }
      }
      pop.show(this, evt.getX(), evt.getY());
    }
  
    /**
-    * DOCUMENT ME!
+    * Reorders annotation rows after a drag of a label
     * 
     * @param evt
-    *          DOCUMENT ME!
     */
    @Override
    public void mouseReleased(MouseEvent evt)
      getSelectedRow(evt.getY() - getScrollOffset());
      int end = selectedRow;
  
+     /*
+      * if dragging to resize instead, start == end
+      */
      if (start != end)
      {
        // Swap these annotations
    }
  
    /**
-    * DOCUMENT ME!
-    * 
-    * @param evt
-    *          DOCUMENT ME!
-    */
-   @Override
-   public void mouseEntered(MouseEvent evt)
-   {
-     if (evt.getY() < 10)
-     {
-       resizePanel = true;
-       repaint();
-     }
-   }
-   /**
-    * DOCUMENT ME!
-    * 
-    * @param evt
-    *          DOCUMENT ME!
+    * Removes the height adjuster image on leaving the panel, unless currently
+    * dragging it
     */
    @Override
    public void mouseExited(MouseEvent evt)
    {
-     if (dragEvent == null)
+     if (resizePanel && dragEvent == null)
      {
        resizePanel = false;
        repaint();
    }
  
    /**
-    * DOCUMENT ME!
+    * A mouse drag may be either an adjustment of the panel height (if flag
+    * resizePanel is set on), or a reordering of the annotation rows. The former
+    * is dealt with by this method, the latter in mouseReleased.
     * 
     * @param evt
-    *          DOCUMENT ME!
     */
    @Override
    public void mouseDragged(MouseEvent evt)
          d = ap.annotationSpaceFillerHolder.getPreferredSize();
          ap.annotationSpaceFillerHolder
                  .setPreferredSize(new Dimension(d.width, d.height - dif));
-         ap.paintAlignment(true);
+         ap.paintAlignment(true, false);
        }
  
        ap.addNotify();
    }
  
    /**
-    * DOCUMENT ME!
+    * Updates the tooltip as the mouse moves over the labels
     * 
     * @param evt
-    *          DOCUMENT ME!
     */
    @Override
    public void mouseMoved(MouseEvent evt)
    {
-     resizePanel = evt.getY() < 10;
+     showOrHideAdjuster(evt);
  
      getSelectedRow(evt.getY() - getScrollOffset());
  
      }
    }
  
+   /**
+    * Shows the height adjuster image if the mouse moves into the top left
+    * region, or hides it if the mouse leaves the regio
+    * 
+    * @param evt
+    */
+   protected void showOrHideAdjuster(MouseEvent evt)
+   {
+     boolean was = resizePanel;
+     resizePanel = evt.getY() < adjusterImageHeight && evt.getX() < HEIGHT_ADJUSTER_WIDTH;
+     if (resizePanel != was)
+     {
+       setCursor(Cursor.getPredefinedCursor(
+               resizePanel ? Cursor.S_RESIZE_CURSOR
+                       : Cursor.DEFAULT_CURSOR));
+       repaint();
+     }
+   }
    @Override
    public void mouseClicked(MouseEvent evt)
    {
              // process modifiers
              SequenceGroup sg = ap.av.getSelectionGroup();
              if (sg == null || sg == aa[selectedRow].groupRef
-                     || !(jalview.util.Platform.isControlDown(evt)
-                             || evt.isShiftDown()))
+                     || !(Platform.isControlDown(evt) || evt.isShiftDown()))
              {
-               if (jalview.util.Platform.isControlDown(evt)
-                       || evt.isShiftDown())
+               if (Platform.isControlDown(evt) || evt.isShiftDown())
                {
                  // clone a new selection group from the associated group
                  ap.av.setSelectionGroup(
                }
              }
  
-             ap.paintAlignment(false);
+             ap.paintAlignment(false, false);
              PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
              ap.av.sendSelection();
            }
                // we make a copy rather than edit the current selection if no
                // modifiers pressed
                // see Enhancement JAL-1557
-               if (!(jalview.util.Platform.isControlDown(evt)
-                       || evt.isShiftDown()))
+               if (!(Platform.isControlDown(evt) || evt.isShiftDown()))
                {
                  sg = new SequenceGroup(sg);
                  sg.clear();
                }
                else
                {
-                 if (jalview.util.Platform.isControlDown(evt))
+                 if (Platform.isControlDown(evt))
                  {
                    sg.addOrRemove(aa[selectedRow].sequenceRef, true);
                  }
                sg.addSequence(aa[selectedRow].sequenceRef, false);
              }
              ap.av.setSelectionGroup(sg);
-             ap.paintAlignment(false);
+             ap.paintAlignment(false, false);
              PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
              ap.av.sendSelection();
            }
      if (dseqs[0] == null)
      {
        dseqs[0] = new Sequence(sq);
-       dseqs[0].setSequence(jalview.analysis.AlignSeq.extractGaps(
-               jalview.util.Comparison.GapChars, sq.getSequenceAsString()));
+       dseqs[0].setSequence(AlignSeq.extractGaps(Comparison.GapChars,
+               sq.getSequenceAsString()));
  
        sq.setDatasetSequence(dseqs[0]);
      }
  
      if (av.hasHiddenColumns())
      {
 +
        hiddenColumns = av.getAlignment().getHiddenColumns()
                .getHiddenColumnsCopy();
 +
      }
  
      Desktop.jalviewClipboard = new Object[] { seqs, ds, // what is the dataset
      drawComponent(g, false, width);
    }
  
-   private final boolean debugRedraw = false;
    /**
     * Draw the full set of annotation Labels for the alignment at the given
     * cursor
  
      if (resizePanel)
      {
-       g.drawImage(image, 2, 0 - getScrollOffset(), this);
+       // g.drawImage(adjusterImage, 2, 0 - getScrollOffset(), this);
      }
      else if (dragEvent != null && aa != null)
      {
    {
      return scrollOffset;
    }
+   @Override
+   public void mouseEntered(MouseEvent e)
+   {
+   }
  }
@@@ -34,7 -34,6 +34,6 @@@ import jalview.datamodel.Annotation
  import jalview.datamodel.DBRefEntry;
  import jalview.datamodel.HiddenColumns;
  import jalview.datamodel.PDBEntry;
- import jalview.datamodel.Sequence;
  import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceGroup;
  import jalview.datamodel.SequenceI;
@@@ -50,6 -49,7 +49,7 @@@ import jalview.schemes.PIDColourScheme
  import jalview.util.GroupUrlLink;
  import jalview.util.GroupUrlLink.UrlStringTooLongException;
  import jalview.util.MessageManager;
+ import jalview.util.StringUtils;
  import jalview.util.UrlLink;
  
  import java.awt.Color;
@@@ -158,7 -158,6 +158,7 @@@ public class PopupMenu extends JPopupMe
  
    JMenuItem sequenceFeature = new JMenuItem();
  
 +
    JMenuItem textColour = new JMenuItem();
  
    JMenu jMenu1 = new JMenu();
     * Creates a new PopupMenu object.
     * 
     * @param ap
-    *          DOCUMENT ME!
     * @param seq
-    *          DOCUMENT ME!
+    * @param features
+    *          non-positional features (for seq not null), or positional features
+    *          at residue (for seq equal to null)
     */
-   public PopupMenu(final AlignmentPanel ap, Sequence seq,
-           List<String> links)
+   public PopupMenu(final AlignmentPanel ap, SequenceI seq,
+           List<SequenceFeature> features)
    {
-     this(ap, seq, links, null);
+     this(ap, seq, features, null);
    }
  
    /**
+    * Constructor
     * 
-    * @param ap
+    * @param alignPanel
     * @param seq
-    * @param links
+    *          the sequence under the cursor if in the Id panel, null if in the
+    *          sequence panel
+    * @param features
+    *          non-positional features if in the Id panel, features at the
+    *          clicked residue if in the sequence panel
     * @param groupLinks
     */
-   public PopupMenu(final AlignmentPanel ap, final SequenceI seq,
-           List<String> links, List<String> groupLinks)
+   public PopupMenu(final AlignmentPanel alignPanel, final SequenceI seq,
+           List<SequenceFeature> features, List<String> groupLinks)
    {
      // /////////////////////////////////////////////////////////
      // If this is activated from the sequence panel, the user may want to
      //
      // If from the IDPanel, we must display the sequence menu
      // ////////////////////////////////////////////////////////
-     this.ap = ap;
+     this.ap = alignPanel;
      sequence = seq;
  
      for (String ff : FileFormats.getInstance().getWritableFormats(true))
       * currently selected sequence (if there is one):
       */
      final List<SequenceI> selectedSequence = (seq == null
 -            ? Collections.<SequenceI> emptyList()
 -            : Arrays.asList(seq));
 +            ? Collections.<SequenceI> emptyList() : Arrays.asList(seq));
 +
      buildAnnotationTypesMenus(seqShowAnnotationsMenu,
              seqHideAnnotationsMenu, selectedSequence);
      configureReferenceAnnotationsMenu(seqAddReferenceAnnotations,
      /*
       * And repeat for the current selection group (if there is one):
       */
-     final List<SequenceI> selectedGroup = (ap.av.getSelectionGroup() == null
+     final List<SequenceI> selectedGroup = (alignPanel.av.getSelectionGroup() == null
              ? Collections.<SequenceI> emptyList()
-             : ap.av.getSelectionGroup().getSequences());
+             : alignPanel.av.getSelectionGroup().getSequences());
      buildAnnotationTypesMenus(groupShowAnnotationsMenu,
              groupHideAnnotationsMenu, selectedGroup);
      configureReferenceAnnotationsMenu(groupAddReferenceAnnotations,
      if (seq != null)
      {
        sequenceMenu.setText(sequence.getName());
-       if (seq == ap.av.getAlignment().getSeqrep())
+       if (seq == alignPanel.av.getAlignment().getSeqrep())
        {
          makeReferenceSeq.setText(
                  MessageManager.getString("action.unmark_as_reference"));
                  MessageManager.getString("action.set_as_reference"));
        }
  
-       if (!ap.av.getAlignment().isNucleotide())
+       if (!alignPanel.av.getAlignment().isNucleotide())
        {
          remove(rnaStructureMenu);
        }
           * add menu items to 2D-render any alignment or sequence secondary
           * structure annotation
           */
-         AlignmentAnnotation[] aas = ap.av.getAlignment()
+         AlignmentAnnotation[] aas = alignPanel.av.getAlignment()
                  .getAlignmentAnnotation();
          if (aas != null)
          {
                  @Override
                  public void actionPerformed(ActionEvent e)
                  {
-                   new AppVarna(seq, aa, ap);
+                   new AppVarna(seq, aa, alignPanel);
                  }
                });
                rnaStructureMenu.add(menuItem);
                  public void actionPerformed(ActionEvent e)
                  {
                    // TODO: VARNA does'nt print gaps in the sequence
-                   new AppVarna(seq, aa, ap);
+                   new AppVarna(seq, aa, alignPanel);
                  }
                });
                rnaStructureMenu.add(menuItem);
        });
        add(menuItem);
  
 +      if (sequence.isHMMConsensusSequence())
 +      {
 +        JMenuItem selectHMM = new JCheckBoxMenuItem();
 +        selectHMM.setText(MessageManager.getString("label.select_hmm"));
 +        selectHMM.addActionListener(new ActionListener()
 +        {
 +
 +          @Override
 +          public void actionPerformed(ActionEvent e)
 +          {
 +            selectHMM_actionPerformed(e);
 +          }
 +        });
 +        add(selectHMM);
 +      }
 +
 +
-       if (ap.av.getSelectionGroup() != null
-               && ap.av.getSelectionGroup().getSize() > 1)
+       if (alignPanel.av.getSelectionGroup() != null
+               && alignPanel.av.getSelectionGroup().getSize() > 1)
        {
          menuItem = new JMenuItem(MessageManager
                  .formatMessage("label.represent_group_with", new Object[]
          sequenceMenu.add(menuItem);
        }
  
-       if (ap.av.hasHiddenRows())
+       if (alignPanel.av.hasHiddenRows())
        {
-         final int index = ap.av.getAlignment().findIndex(seq);
+         final int index = alignPanel.av.getAlignment().findIndex(seq);
  
-         if (ap.av.adjustForHiddenSeqs(index)
-                 - ap.av.adjustForHiddenSeqs(index - 1) > 1)
+         if (alignPanel.av.adjustForHiddenSeqs(index)
+                 - alignPanel.av.adjustForHiddenSeqs(index - 1) > 1)
          {
            menuItem = new JMenuItem(
                    MessageManager.getString("action.reveal_sequences"));
              @Override
              public void actionPerformed(ActionEvent e)
              {
-               ap.av.showSequence(index);
-               if (ap.overviewPanel != null)
+               alignPanel.av.showSequence(index);
+               if (alignPanel.overviewPanel != null)
                {
-                 ap.overviewPanel.updateOverviewImage();
+                 alignPanel.overviewPanel.updateOverviewImage();
                }
              }
            });
        }
      }
      // for the case when no sequences are even visible
-     if (ap.av.hasHiddenRows())
+     if (alignPanel.av.hasHiddenRows())
      {
        {
          menuItem = new JMenuItem(
            @Override
            public void actionPerformed(ActionEvent e)
            {
-             ap.av.showAllHiddenSeqs();
-             if (ap.overviewPanel != null)
+             alignPanel.av.showAllHiddenSeqs();
+             if (alignPanel.overviewPanel != null)
              {
-               ap.overviewPanel.updateOverviewImage();
+               alignPanel.overviewPanel.updateOverviewImage();
              }
            }
          });
        }
      }
  
-     SequenceGroup sg = ap.av.getSelectionGroup();
+     SequenceGroup sg = alignPanel.av.getSelectionGroup();
      boolean isDefinedGroup = (sg != null)
-             ? ap.av.getAlignment().getGroups().contains(sg)
+             ? alignPanel.av.getAlignment().getGroups().contains(sg)
              : false;
  
      if (sg != null && sg.getSize() > 0)
          buildGroupURLMenu(sg, groupLinks);
        }
        // Add a 'show all structures' for the current selection
-       Hashtable<String, PDBEntry> pdbe = new Hashtable<>(),
-               reppdb = new Hashtable<>();
 -      Hashtable<String, PDBEntry> pdbe = new Hashtable<>(), reppdb = new Hashtable<>();
++      Hashtable<String, PDBEntry> pdbe = new Hashtable<>();
++      Hashtable<String, PDBEntry> reppdb = new Hashtable<>();
        SequenceI sqass = null;
-       for (SequenceI sq : ap.av.getSequenceSelection())
+       for (SequenceI sq : alignPanel.av.getSequenceSelection())
        {
          Vector<PDBEntry> pes = sq.getDatasetSequence().getAllPDBEntries();
          if (pes != null && pes.size() > 0)
        rnaStructureMenu.setVisible(false);
      }
  
-     if (links != null && links.size() > 0)
+     addLinks(seq, features);
+     if (seq == null)
      {
-       addFeatureLinks(seq, links);
+       addFeatureDetails(features);
      }
    }
  
    /**
+    * Add a link to show feature details for each sequence feature
+    * 
+    * @param features
+    */
+   protected void addFeatureDetails(List<SequenceFeature> features)
+   {
+     if (features == null || features.isEmpty())
+     {
+       return;
+     }
+     JMenu details = new JMenu(
+             MessageManager.getString("label.feature_details"));
+     add(details);
+     for (final SequenceFeature sf : features)
+     {
+       int start = sf.getBegin();
+       int end = sf.getEnd();
+       String desc = null;
+       if (start == end)
+       {
+         desc = String.format("%s %d", sf.getType(), start);
+       }
+       else
+       {
+         desc = String.format("%s %d-%d", sf.getType(), start, end);
+       }
+       String tooltip = desc;
+       String description = sf.getDescription();
+       if (description != null)
+       {
+         description = StringUtils.stripHtmlTags(description);
+         if (description.length() > 12)
+         {
+           desc = desc + " " + description.substring(0, 12) + "..";
+         }
+         else
+         {
+           desc = desc + " " + description;
+         }
+         tooltip = tooltip + " " + description;
+       }
+       if (sf.getFeatureGroup() != null)
+       {
+         tooltip = tooltip + (" (" + sf.getFeatureGroup() + ")");
+       }
+       JMenuItem item = new JMenuItem(desc);
+       item.setToolTipText(tooltip);
+       item.addActionListener(new ActionListener()
+       {
+         @Override
+         public void actionPerformed(ActionEvent e)
+         {
+           showFeatureDetails(sf);
+         }
+       });
+       details.add(item);
+     }
+   }
+   /**
+    * Opens a panel showing a text report of feature dteails
+    * 
+    * @param sf
+    */
+   protected void showFeatureDetails(SequenceFeature sf)
+   {
+     CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
+     // it appears Java's CSS does not support border-collaps :-(
+     cap.addStylesheetRule("table { border-collapse: collapse;}");
+     cap.addStylesheetRule("table, td, th {border: 1px solid black;}");
+     cap.setText(sf.getDetailsReport());
+     Desktop.addInternalFrame(cap,
+             MessageManager.getString("label.feature_details"), 500, 500);
+   }
+   /**
     * Adds a 'Link' menu item with a sub-menu item for each hyperlink provided.
+    * When seq is not null, these are links for the sequence id, which may be to
+    * external web sites for the sequence accession, and/or links embedded in
+    * non-positional features. When seq is null, only links embedded in the
+    * provided features are added.
     * 
     * @param seq
-    * @param links
+    * @param features
     */
-   void addFeatureLinks(final SequenceI seq, List<String> links)
+   void addLinks(final SequenceI seq, List<SequenceFeature> features)
    {
      JMenu linkMenu = new JMenu(MessageManager.getString("action.link"));
+     List<String> nlinks = null;
+     if (seq != null)
+     {
+       nlinks = Preferences.sequenceUrlLinks.getLinksForMenu();
+     }
+     else
+     {
+       nlinks = new ArrayList<>();
+     }
+     if (features != null)
+     {
+       for (SequenceFeature sf : features)
+       {
+         if (sf.links != null)
+         {
+           for (String link : sf.links)
+           {
+             nlinks.add(link);
+           }
+         }
+       }
+     }
      Map<String, List<String>> linkset = new LinkedHashMap<>();
  
-     for (String link : links)
+     for (String link : nlinks)
      {
        UrlLink urlLink = null;
        try
  
      addshowLinks(linkMenu, linkset.values());
  
-     // disable link menu if there are no valid entries
+     // only add link menu if it has entries
      if (linkMenu.getItemCount() > 0)
      {
-       linkMenu.setEnabled(true);
-     }
-     else
-     {
-       linkMenu.setEnabled(false);
-     }
-     if (sequence != null)
-     {
-       sequenceMenu.add(linkMenu);
-     }
-     else
-     {
-       add(linkMenu);
+       if (sequence != null)
+       {
+         sequenceMenu.add(linkMenu);
+       }
+       else
+       {
+         add(linkMenu);
+       }
      }
    }
  
    /**
      jMenu1.add(displayNonconserved);
    }
  
 +
 +  protected void selectHMM_actionPerformed(ActionEvent e)
 +  {
 +    SequenceI hmm = ap.av.getSequenceSelection()[0];
 +    ap.alignFrame.setSelectedHMMSequence(hmm);
 +  }
 +
    /**
     * Constructs the entries for the colour menu
     */
                new Object[]
                { seq.getDisplayId(true) }) + "</h2></p><p>");
        new SequenceAnnotationReport(null).createSequenceAnnotationReport(
-               contents, seq, true, true,
-               (ap.getSeqPanel().seqCanvas.fr != null)
-                       ? ap.getSeqPanel().seqCanvas.fr.getMinMax()
-                       : null);
+               contents, seq, true, true, ap.getSeqPanel().seqCanvas.fr);
        contents.append("</p>");
      }
      cap.setText("<html>" + contents.toString() + "</html>");
    void refresh()
    {
      ap.updateAnnotation();
-     ap.paintAlignment(true);
+     // removed paintAlignment(true) here:
+     // updateAnnotation calls paintAlignment already, so don't need to call
+     // again
  
      PaintRefresher.Refresh(this, ap.av.getSequenceSetId());
    }
        }
  
        sequence.setName(dialog.getName().replace(' ', '_'));
-       ap.paintAlignment(false);
+       ap.paintAlignment(false, false);
      }
  
      sequence.setDescription(dialog.getDescription());
        if (start <= end)
        {
          seqs.add(sg.getSequenceAt(i).getDatasetSequence());
-         features.add(
-                 new SequenceFeature(null, null, null, start, end, null));
+         features.add(new SequenceFeature(null, null, start, end, null));
        }
      }
  
                .amendFeatures(seqs, features, true, ap))
        {
          ap.alignFrame.setShowSeqFeatures(true);
-         ap.highlightSearchResults(null);
+         ap.av.setSearchResults(null); // clear highlighting
+         ap.repaint(); // draw new/amended features
        }
      }
    }
@@@ -24,7 -24,6 +24,7 @@@ import jalview.analysis.AnnotationSorte
  import jalview.bin.Cache;
  import jalview.gui.Help.HelpId;
  import jalview.gui.StructureViewer.ViewerType;
 +import jalview.hmmer.HmmerCommand;
  import jalview.io.FileFormatI;
  import jalview.io.JalviewFileChooser;
  import jalview.io.JalviewFileView;
@@@ -49,8 -48,6 +49,8 @@@ import java.awt.Dimension
  import java.awt.Font;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
 +import java.awt.event.FocusAdapter;
 +import java.awt.event.FocusEvent;
  import java.awt.event.MouseEvent;
  import java.io.File;
  import java.util.ArrayList;
@@@ -105,12 -102,6 +105,12 @@@ public class Preferences extends GPrefe
    public static final String STRUCTURE_DISPLAY = "STRUCTURE_DISPLAY";
  
    public static final String CHIMERA_PATH = "CHIMERA_PATH";
 +  
 +  public static final String HMMER_PATH = "HMMER_PATH";
 +
 +  public static final String HMMSEARCH_DB_PATHS = "HMMSEARCH_DB_PATHS";
 +
 +  public static final String HMMSEARCH_DBS = "HMMSEARCH_DBS";
  
    public static final String SORT_ANNOTATIONS = "SORT_ANNOTATIONS";
  
  
    public static final String SHOW_OCCUPANCY = "SHOW_OCCUPANCY";
  
+   public static final String SHOW_OV_HIDDEN_AT_START = "SHOW_OV_HIDDEN_AT_START";
+   public static final String USE_LEGACY_GAP = "USE_LEGACY_GAP";
+   public static final String GAP_COLOUR = "GAP_COLOUR";
+   public static final String HIDDEN_COLOUR = "HIDDEN_COLOUR";
    private static final int MIN_FONT_SIZE = 1;
  
    private static final int MAX_FONT_SIZE = 30;
      frame.setMinimumSize(new Dimension(width, height));
  
      /*
 +     * Set HMMER tab defaults
 +     */
 +    hmmrTrimTermini.setSelected(Cache.getDefault("TRIM_TERMINI", false));
 +    if (Cache.getDefault("USE_UNIPROT", false))
 +    {
 +      hmmerBackgroundUniprot.setSelected(true);
 +    }
 +    else
 +    {
 +      hmmerBackgroundAlignment.setSelected(true);
 +    }
 +    hmmerSequenceCount
 +            .setText(Cache.getProperty("SEQUENCES_TO_KEEP"));
 +    hmmerPath.setText(Cache.getProperty(HMMER_PATH));
 +    hmmerPath.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        validateHMMERPath(true);
 +      }
 +    });
 +    hmmerPath.addFocusListener(new FocusAdapter()
 +    {
 +      @Override
 +      public void focusLost(FocusEvent e)
 +      {
 +        validateHMMERPath(true);
 +      }
 +    });
 +
 +    /*
       * Set Visual tab defaults
       */
      seqLimit.setSelected(Cache.getDefault("SHOW_JVSUFFIX", true));
              Cache.getDefault("SHOW_CONSENSUS_HISTOGRAM", true));
      showConsensLogo
              .setSelected(Cache.getDefault("SHOW_CONSENSUS_LOGO", false));
 +    showInformationHistogram.setSelected(
 +            Cache.getDefault("SHOW_INFORMATION_HISTOGRAM", true));
 +    showHMMLogo.setSelected(Cache.getDefault("SHOW_HMM_LOGO", false));
      showNpTooltip
              .setSelected(Cache.getDefault("SHOW_NPFEATS_TOOLTIP", true));
      showDbRefTooltip
              Cache.getDefaultColour("ANNOTATIONCOLOUR_MAX", Color.red));
  
      /*
+      * Set overview panel defaults
+      */
+     gapColour.setBackground(
+             Cache.getDefaultColour(GAP_COLOUR,
+                     jalview.renderer.OverviewResColourFinder.OVERVIEW_DEFAULT_GAP));
+     hiddenColour.setBackground(
+             Cache.getDefaultColour(HIDDEN_COLOUR,
+                     jalview.renderer.OverviewResColourFinder.OVERVIEW_DEFAULT_HIDDEN));
+     useLegacyGap.setSelected(Cache.getDefault(USE_LEGACY_GAP, false));
+     gapLabel.setEnabled(!useLegacyGap.isSelected());
+     gapColour.setEnabled(!useLegacyGap.isSelected());
+     showHiddenAtStart
+             .setSelected(Cache.getDefault(SHOW_OV_HIDDEN_AT_START, true));
+     /*
       * Set Structure tab defaults.
       */
      final boolean structSelected = Cache.getDefault(STRUCT_FROM_PDB, false);
      userIdWidthlabel.setEnabled(!autoIdWidth.isSelected());
      Integer wi = Cache.getIntegerProperty("FIGURE_USERIDWIDTH");
      userIdWidth.setText(wi == null ? "" : wi.toString());
+     // TODO: refactor to use common enum via FormatAdapter and allow extension
+     // for new flat file formats
      blcjv.setSelected(Cache.getDefault("BLC_JVSUFFIX", true));
      clustaljv.setSelected(Cache.getDefault("CLUSTAL_JVSUFFIX", true));
      fastajv.setSelected(Cache.getDefault("FASTA_JVSUFFIX", true));
              Boolean.toString(showConsensHistogram.isSelected()));
      Cache.applicationProperties.setProperty("SHOW_CONSENSUS_LOGO",
              Boolean.toString(showConsensLogo.isSelected()));
 +    Cache.applicationProperties.setProperty("SHOW_INFORMATION_HISTOGRAM",
 +            Boolean.toString(showConsensHistogram.isSelected()));
 +    Cache.applicationProperties.setProperty("SHOW_HMM_LOGO",
 +            Boolean.toString(showHMMLogo.isSelected()));
      Cache.applicationProperties.setProperty("ANTI_ALIAS",
              Boolean.toString(smoothFont.isSelected()));
      Cache.applicationProperties.setProperty(SCALE_PROTEIN_TO_CDNA,
              maxColour.getBackground());
  
      /*
 +     * Save HMMER settings
 +     */
 +    Cache.applicationProperties.setProperty("TRIM_TERMINI",
 +            Boolean.toString(hmmrTrimTermini.isSelected()));
 +    Cache.applicationProperties.setProperty("USE_UNIPROT",
 +            Boolean.toString(hmmerBackgroundUniprot.isSelected()));
 +    Cache.applicationProperties.setProperty("SEQUENCES_TO_KEEP",
 +            hmmerSequenceCount.getText());
 +    Cache.applicationProperties.setProperty(HMMER_PATH,
 +            hmmerPath.getText());
 +    AlignFrame[] frames = Desktop.getAlignFrames();
 +    if (frames != null && frames.length > 0)
 +    {
 +      for (AlignFrame f : frames)
 +      {
 +        f.updateHMMERStatus();
 +      }
 +    }
 +    
 +    hmmrTrimTermini.setSelected(Cache.getDefault("TRIM_TERMINI", false));
 +    if (Cache.getDefault("USE_UNIPROT", false))
 +    {
 +      hmmerBackgroundUniprot.setSelected(true);
 +    }
 +    else
 +    {
 +      hmmerBackgroundAlignment.setSelected(true);
 +    }
 +    hmmerSequenceCount
 +            .setText(Cache.getProperty("SEQUENCES_TO_KEEP"));
 +    hmmerPath.setText(Cache.getProperty(HMMER_PATH));
 +
 +    /*
+      * Save Overview settings
+      */
+     Cache.setColourProperty(GAP_COLOUR, gapColour.getBackground());
+     Cache.setColourProperty(HIDDEN_COLOUR, hiddenColour.getBackground());
+     Cache.applicationProperties.setProperty(USE_LEGACY_GAP,
+             Boolean.toString(useLegacyGap.isSelected()));
+     Cache.applicationProperties.setProperty(SHOW_OV_HIDDEN_AT_START,
+             Boolean.toString(showHiddenAtStart.isSelected()));
+     /*
       * Save Structure settings
       */
      Cache.applicationProperties.setProperty(ADD_TEMPFACT_ANN,
              && (identity.isSelected() || showGroupConsensus.isSelected()));
      showConsensLogo.setEnabled(annotations.isSelected()
              && (identity.isSelected() || showGroupConsensus.isSelected()));
 +    showInformationHistogram.setEnabled(annotations.isSelected());
 +    showHMMLogo.setEnabled(annotations.isSelected());
    }
  
    @Override
    }
  
    @Override
+   public void gapColour_actionPerformed(JPanel gap)
+   {
+     if (!useLegacyGap.isSelected())
+     {
+       Color col = JColorChooser.showDialog(this,
+               MessageManager.getString("label.select_gap_colour"),
+               gapColour.getBackground());
+       if (col != null)
+       {
+         gap.setBackground(col);
+       }
+       gap.repaint();
+     }
+   }
+   @Override
+   public void hiddenColour_actionPerformed(JPanel hidden)
+   {
+     Color col = JColorChooser.showDialog(this,
+             MessageManager.getString("label.select_hidden_colour"),
+             hiddenColour.getBackground());
+     if (col != null)
+     {
+       hidden.setBackground(col);
+     }
+     hidden.repaint();
+   }
+   @Override
+   protected void useLegacyGaps_actionPerformed(ActionEvent e)
+   {
+     boolean enabled = useLegacyGap.isSelected();
+     if (enabled)
+     {
+       gapColour.setBackground(
+               jalview.renderer.OverviewResColourFinder.OVERVIEW_DEFAULT_LEGACY_GAP);
+     }
+     else
+     {
+       gapColour.setBackground(
+               jalview.renderer.OverviewResColourFinder.OVERVIEW_DEFAULT_GAP);
+     }
+     gapColour.setEnabled(!enabled);
+     gapLabel.setEnabled(!enabled);
+   }
+   @Override
+   protected void resetOvDefaults_actionPerformed(ActionEvent e)
+   {
+     useLegacyGap.setSelected(false);
+     useLegacyGaps_actionPerformed(null);
+     showHiddenAtStart.setSelected(true);
+     hiddenColour.setBackground(
+             jalview.renderer.OverviewResColourFinder.OVERVIEW_DEFAULT_HIDDEN);
+   }
+   @Override
    protected void userIdWidth_actionPerformed()
    {
      try
      }
      return true;
    }
 +  
 +  /**
 +   * Returns true if hmmer path is to a folder that contains an executable
 +   * hmmbuild or hmmbuild.exe, else false (optionally after showing a warning
 +   * dialog)
 +   */
 +  @Override
 +  protected boolean validateHMMERPath(boolean showWarning)
 +  {
 +    String folder = hmmerPath.getText().trim();
 +
 +    if (HmmerCommand.getExecutable(HmmerCommand.HMMBUILD, folder) != null)
 +    {
 +      return true;
 +    }
 +    if (showWarning && folder.length() > 0)
 +    {
 +      JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 +              MessageManager.getString("label.hmmbuild_not_found"),
 +              MessageManager.getString("label.invalid_folder"),
 +              JvOptionPane.ERROR_MESSAGE);
 +    }
 +    return false;
 +  }
 +
 +  /**
 +   * Checks if a file can be executed
 +   * 
 +   * @param path
 +   *          the path to the file
 +   * @return
 +   */
 +  public boolean canExecute(String path)
 +  {
 +    File file = new File(path);
 +    if (!file.canExecute())
 +    {
 +      file = new File(path + ".exe");
 +      {
 +        if (!file.canExecute())
 +        {
 +          return false;
 +        }
 +      }
 +    }
 +    return true;
 +  }
  
    /**
     * If Chimera is selected, check it can be found on default or user-specified
@@@ -174,11 -174,6 +174,6 @@@ public abstract class AlignFile extend
      }
      parseCalled = true;
      parse();
-     // sets the index of each sequence in the alignment
-     for (int i = 0, c = seqs.size(); i < c; i++)
-     {
-       seqs.get(i).setIndex(i);
-     }
    }
  
    /**
     */
    protected void initData()
    {
 -    seqs = new Vector<SequenceI>();
 -    annotations = new Vector<AlignmentAnnotation>();
 -    seqGroups = new ArrayList<SequenceGroup>();
 +    seqs = new Vector<>();
 +    annotations = new Vector<>();
 +    seqGroups = new ArrayList<>();
      parseCalled = false;
    }
  
    @Override
    public void setSeqs(SequenceI[] s)
    {
 -    seqs = new Vector<SequenceI>();
 +    seqs = new Vector<>();
  
      for (int i = 0; i < s.length; i++)
      {
    {
      if (newickStrings == null)
      {
 -      newickStrings = new Vector<String[]>();
 +      newickStrings = new Vector<>();
      }
      newickStrings.addElement(new String[] { treeName, newickString });
    }
    {
      seqs.add(seq);
    }
 +
 +  /**
 +   * Used only for hmmer statistics, so should probably be removed at some
 +   * point. TODO remove this
 +   * 
 +   * @return
 +   */
 +  public Vector<AlignmentAnnotation> getAnnotations()
 +  {
 +    return annotations;
 +  }
 +
  }
@@@ -26,7 -26,6 +26,7 @@@ import jalview.api.FeaturesDisplayedI
  import jalview.api.FeaturesSourceI;
  import jalview.bin.Cache;
  import jalview.bin.Jalview;
 +import jalview.datamodel.AlignmentAnnotation;
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.HiddenColumns;
  import jalview.datamodel.PDBEntry;
@@@ -200,32 -199,6 +200,32 @@@ public class FileLoader implements Runn
      return alignFrame;
    }
  
 +  public void LoadFileOntoAlignmentWaitTillLoaded(AlignViewport viewport,
 +          String file, DataSourceType sourceType, FileFormatI format)
 +  {
 +    this.viewport = viewport;
 +    this.file = file;
 +    this.protocol = sourceType;
 +    this.format = format;
 +    _LoadAlignmentFileWaitTillLoaded();
 +  }
 +
 +  protected void _LoadAlignmentFileWaitTillLoaded()
 +  {
 +    Thread loader = new Thread(this);
 +    loader.start();
 +
 +    while (loader.isAlive())
 +    {
 +      try
 +      {
 +        Thread.sleep(500);
 +      } catch (Exception ex)
 +      {
 +      }
 +    }
 +  }
 +
    public void updateRecentlyOpened()
    {
      Vector recent = new Vector();
              }
              // append to existing alignment
              viewport.addAlignment(al, title);
 +            if (source instanceof HMMFile)
 +            {
 +              AlignmentI alignment = viewport.getAlignment();
 +              SequenceI seq = alignment
 +                      .getSequenceAt(alignment.getAbsoluteHeight() - 1);
 +              seq.setIsHMMConsensusSequence(true);
 +              AlignmentAnnotation[] annots = viewport.getAlignment()
 +                      .getAlignmentAnnotation();
 +              for (AlignmentAnnotation annot : annots)
 +              {
 +                if ("RF".equals(annot.label)
 +                        || annot.label.contains("Reference"))
 +                {
 +                  seq.mapToReference(annot);
 +                  break;
 +                }
 +              }
 +              alignment.deleteSequence(alignment.getAbsoluteHeight() - 1);
 +              alignment.insertSequenceAt(0, seq);
 +              viewport.getAlignPanel().adjustAnnotationHeight();
 +              viewport.updateSequenceIdColours();
 +
 +            }
            }
            else
            {
      return tempStructFile.toString();
    }
  
-   /*
-    * (non-Javadoc)
-    * 
-    * @see java.lang.Object#finalize()
-    */
-   @Override
-   protected void finalize() throws Throwable
-   {
-     source = null;
-     alignFrame = null;
-     viewport = null;
-     super.finalize();
-   }
  }
@@@ -150,11 -150,6 +150,11 @@@ public class IdentifyFil
            reply = FileFormat.ScoreMatrix;
            break;
          }
 +        if (data.startsWith("HMMER3"))
 +        {
 +          reply = FileFormat.HMMER3;
 +          break;
 +        }
          if (data.startsWith("H ") && !aaIndexHeaderRead)
          {
            aaIndexHeaderRead = true;
            // read as a FASTA (probably)
            break;
          }
+         if (data.indexOf("{\"") > -1)
+         {
+           reply = FileFormat.Json;
+           break;
+         }
          int lessThan = data.indexOf("<");
          if ((lessThan > -1)) // possible Markup Language data i.e HTML,
                               // RNAML, XML
            }
          }
  
-         if (data.indexOf("{\"") > -1)
-         {
-           reply = FileFormat.Json;
-           break;
-         }
          if ((data.length() < 1) || (data.indexOf("#") == 0))
          {
            lineswereskipped = true;
@@@ -74,6 -74,8 +74,8 @@@ import fr.orsay.lri.varna.models.rna.RN
   */
  public class StockholmFile extends AlignFile
  {
+   private static final String ANNOTATION = "annotation";
    private static final Regex OPEN_PAREN = new Regex("(<|\\[)", "(");
  
    private static final Regex CLOSE_PAREN = new Regex("(>|\\])", ")");
      String version;
      // String id;
      Hashtable seqAnn = new Hashtable(); // Sequence related annotations
 -    LinkedHashMap<String, String> seqs = new LinkedHashMap<String, String>();
 +    LinkedHashMap<String, String> seqs = new LinkedHashMap<>();
      Regex p, r, rend, s, x;
      // Temporary line for processing RNA annotation
      // String RNAannot = "";
                while (j.hasMoreElements())
                {
                  String desc = j.nextElement().toString();
-                 if ("annotations".equals(desc) && annotsAdded)
+                 if (ANNOTATION.equals(desc) && annotsAdded)
                  {
                    // don't add features if we already added an annotation row
                    continue;
                      int new_pos = posmap[k]; // look up nearest seqeunce
                      // position to this column
                      SequenceFeature feat = new SequenceFeature(type, desc,
-                             new_pos, new_pos, 0f, null);
+                             new_pos, new_pos, null);
  
                      seqO.addSequenceFeature(feat);
                    }
                content = new Hashtable();
                features.put(this.id2type(type), content);
              }
-             String ns = (String) content.get("annotation");
+             String ns = (String) content.get(ANNOTATION);
  
              if (ns == null)
              {
              }
              // finally, append the annotation line
              ns += seq;
-             content.put("annotation", ns);
+             content.put(ANNOTATION, ns);
              // // end of wrapped annotation block.
              // // Now a new row is created with the current set of data
  
                strucAnn = new Hashtable();
              }
  
 -            Vector<AlignmentAnnotation> newStruc = new Vector<AlignmentAnnotation>();
 +            Vector<AlignmentAnnotation> newStruc = new Vector<>();
              parseAnnotationRow(newStruc, type, ns);
              for (AlignmentAnnotation alan : newStruc)
              {
    private void guessDatabaseFor(Sequence seqO, String dbr, String dbsource)
    {
      DBRefEntry dbrf = null;
 -    List<DBRefEntry> dbrs = new ArrayList<DBRefEntry>();
 +    List<DBRefEntry> dbrs = new ArrayList<>();
      String seqdb = "Unknown", sdbac = "" + dbr;
      int st = -1, en = -1, p;
      if ((st = sdbac.indexOf("/")) > -1)
      while ((in < s.length) && (s[in] != null))
      {
        String tmp = printId(s[in], jvSuffix);
-       if (s[in].getSequence().length > max)
-       {
-         max = s[in].getSequence().length;
-       }
+       max = Math.max(max, s[in].getLength());
  
        if (tmp.length() > maxid)
        {
        }
      }
  
 +
      // output annotations
      while (i < s.length && s[i] != null)
      {
  
            String key = type2id(alAnot[j].label);
            boolean isrna = alAnot[j].isValidStruc();
 -
            if (isrna)
            {
              // hardwire to secondary structure if there is RNA secondary
            for (int k = 0; k < ann.length; k++)
            {
              seq += outputCharacter(key, k, isrna, ann, s[i]);
 -          }
 +
 +        }
            out.append(seq);
            out.append(newline);
          }
            {
              label = aa.label;
            }
 +          else if ("RF".equals(key))
 +          {
 +            label = key;
 +          }
            else
            {
              label = key + "_cons";
          out.append(newline);
        }
      }
 +    
 +
  
      out.append("//");
      out.append(newline);
      out.append("# STOCKHOLM 1.0");
      out.append(newline);
      print(getSeqsAsArray(), false);
 -
 -    out.append("//");
 -    out.append(newline);
      return out.toString();
    }
  
@@@ -25,8 -25,6 +25,8 @@@ import jalview.api.SplitContainerI
  import jalview.bin.Cache;
  import jalview.gui.JvSwingUtils;
  import jalview.gui.Preferences;
 +import jalview.hmmer.HmmerCommand;
 +import jalview.io.FileFormatException;
  import jalview.io.FileFormats;
  import jalview.util.MessageManager;
  import jalview.util.Platform;
@@@ -42,7 -40,6 +42,7 @@@ import java.awt.event.FocusEvent
  import java.awt.event.KeyEvent;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
 +import java.io.IOException;
  import java.util.HashMap;
  import java.util.Map;
  
@@@ -70,28 -67,6 +70,28 @@@ public class GAlignFrame extends JInter
  
    protected JMenu webService = new JMenu();
  
 +  protected JMenu hmmerMenu = new JMenu();
 +
 +  protected JMenu hmmAlign = new JMenu();
 +
 +  protected JMenuItem hmmAlignRun = new JMenuItem();
 +
 +  protected JMenuItem hmmAlignSettings = new JMenuItem();
 +
 +  protected JMenu hmmSearch = new JMenu();
 +
 +  protected JMenuItem hmmSearchRun = new JMenuItem();
 +
 +  protected JMenuItem hmmSearchSettings = new JMenuItem();
 +
 +  protected JMenuItem addDatabase = new JMenuItem();
 +
 +  protected JMenu hmmBuild = new JMenu();
 +
 +  protected JMenuItem hmmBuildRun = new JMenuItem();
 +
 +  protected JMenuItem hmmBuildSettings = new JMenuItem();
 +
    protected JMenuItem webServiceNoServices;
  
    protected JCheckBoxMenuItem viewBoxesMenuItem = new JCheckBoxMenuItem();
  
    protected JMenuItem runGroovy = new JMenuItem();
  
+   protected JMenuItem loadVcf;
    protected JCheckBoxMenuItem autoCalculate = new JCheckBoxMenuItem();
  
    protected JCheckBoxMenuItem sortByTree = new JCheckBoxMenuItem();
  
    protected JCheckBoxMenuItem normaliseSequenceLogo = new JCheckBoxMenuItem();
  
 +  protected JCheckBoxMenuItem showInformationHistogram = new JCheckBoxMenuItem();
 +
 +  protected JCheckBoxMenuItem showHMMSequenceLogo = new JCheckBoxMenuItem();
 +
 +  protected JCheckBoxMenuItem normaliseHMMSequenceLogo = new JCheckBoxMenuItem();
 +
    protected JCheckBoxMenuItem applyAutoAnnotationSettings = new JCheckBoxMenuItem();
  
    private SequenceAnnotationOrder annotationSortOrder;
  
    private boolean showAutoCalculatedAbove = false;
  
 -  private Map<KeyStroke, JMenuItem> accelerators = new HashMap<KeyStroke, JMenuItem>();
 +  private Map<KeyStroke, JMenuItem> accelerators = new HashMap<>();
  
    private SplitContainerI splitFrame;
  
    private void jbInit() throws Exception
    {
      initColourMenu();
 -
 +    initHMMERMenu();
 +  
      JMenuItem saveAs = new JMenuItem(
              MessageManager.getString("action.save_as"));
      ActionListener al = new ActionListener()
          saveAs_actionPerformed(e);
        }
      };
 -
 +  
      // FIXME getDefaultToolkit throws an exception in Headless mode
      KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_S,
              Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
                      | KeyEvent.SHIFT_MASK,
              false);
      addMenuActionAndAccelerator(keyStroke, saveAs, al);
 -
 +  
      closeMenuItem.setText(MessageManager.getString("action.close"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_W,
              Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
        }
      };
      addMenuActionAndAccelerator(keyStroke, closeMenuItem, al);
 -
 +  
      JMenu editMenu = new JMenu(MessageManager.getString("action.edit"));
      JMenu viewMenu = new JMenu(MessageManager.getString("action.view"));
      JMenu annotationsMenu = new JMenu(
      JMenu calculateMenu = new JMenu(
              MessageManager.getString("action.calculate"));
      webService.setText(MessageManager.getString("action.web_service"));
 +    hmmerMenu.setText(MessageManager.getString("action.hmmer"));
 +    hmmerMenu.setEnabled(HmmerCommand.isHmmerAvailable());
 +    hmmerMenu.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        hmmerMenu_actionPerformed(e);
 +      }
 +    });
 +    hmmerMenu.add(hmmBuild);
 +    hmmerMenu.add(hmmAlign);
 +    hmmerMenu.add(hmmSearch);
 +
      JMenuItem selectAllSequenceMenuItem = new JMenuItem(
              MessageManager.getString("action.select_all"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_A,
        }
      };
      addMenuActionAndAccelerator(keyStroke, selectAllSequenceMenuItem, al);
 -
 +  
      JMenuItem deselectAllSequenceMenuItem = new JMenuItem(
              MessageManager.getString("action.deselect_all"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
        }
      };
      addMenuActionAndAccelerator(keyStroke, deselectAllSequenceMenuItem, al);
 -
 +  
      JMenuItem invertSequenceMenuItem = new JMenuItem(
              MessageManager.getString("action.invert_sequence_selection"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_I,
        }
      };
      addMenuActionAndAccelerator(keyStroke, invertSequenceMenuItem, al);
 -
 +  
      JMenuItem grpsFromSelection = new JMenuItem(
              MessageManager.getString("action.make_groups_selection"));
      grpsFromSelection.addActionListener(new ActionListener()
        }
      };
      addMenuActionAndAccelerator(keyStroke, remove2LeftMenuItem, al);
 -
 +  
      JMenuItem remove2RightMenuItem = new JMenuItem(
              MessageManager.getString("action.remove_right"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_R,
        }
      };
      addMenuActionAndAccelerator(keyStroke, remove2RightMenuItem, al);
 -
 +  
      JMenuItem removeGappedColumnMenuItem = new JMenuItem(
              MessageManager.getString("action.remove_empty_columns"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_E,
        }
      };
      addMenuActionAndAccelerator(keyStroke, removeGappedColumnMenuItem, al);
 -
 +  
      JMenuItem removeAllGapsMenuItem = new JMenuItem(
              MessageManager.getString("action.remove_all_gaps"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_E,
        }
      };
      addMenuActionAndAccelerator(keyStroke, removeAllGapsMenuItem, al);
 -
 +  
      JMenuItem justifyLeftMenuItem = new JMenuItem(
              MessageManager.getString("action.left_justify_alignment"));
      justifyLeftMenuItem.addActionListener(new ActionListener()
          sortGroupMenuItem_actionPerformed(e);
        }
      });
 -
 +  
      JMenuItem removeRedundancyMenuItem = new JMenuItem(
              MessageManager.getString("action.remove_redundancy"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_D,
        }
      };
      addMenuActionAndAccelerator(keyStroke, removeRedundancyMenuItem, al);
 -
 +  
      JMenuItem pairwiseAlignmentMenuItem = new JMenuItem(
              MessageManager.getString("action.pairwise_alignment"));
      pairwiseAlignmentMenuItem.addActionListener(new ActionListener()
          pairwiseAlignmentMenuItem_actionPerformed(e);
        }
      });
 -
 +  
      this.getContentPane().setLayout(new BorderLayout());
      alignFrameMenuBar.setFont(new java.awt.Font("Verdana", 0, 11));
      statusBar.setBackground(Color.white);
      statusBar.setFont(new java.awt.Font("Verdana", 0, 11));
      statusBar.setBorder(BorderFactory.createLineBorder(Color.black));
      statusBar.setText(MessageManager.getString("label.status_bar"));
 +
      outputTextboxMenu
              .setText(MessageManager.getString("label.out_to_textbox"));
  
 +
      annotationPanelMenuItem.setActionCommand("");
      annotationPanelMenuItem
              .setText(MessageManager.getString("label.show_annotations"));
      final JCheckBoxMenuItem sortAnnByLabel = new JCheckBoxMenuItem(
              MessageManager.getString("label.sort_annotations_by_label"));
  
 +
      sortAnnBySequence.setSelected(
              sortAnnotationsBy == SequenceAnnotationOrder.SEQUENCE_AND_LABEL);
      sortAnnBySequence.addActionListener(new ActionListener()
          colourTextMenuItem_actionPerformed(e);
        }
      });
 -
 +  
      JMenuItem htmlMenuItem = new JMenuItem(
              MessageManager.getString("label.html"));
      htmlMenuItem.addActionListener(new ActionListener()
          htmlMenuItem_actionPerformed(e);
        }
      });
 -
 +  
      JMenuItem createBioJS = new JMenuItem(
              MessageManager.getString("label.biojs_html_export"));
      createBioJS.addActionListener(new java.awt.event.ActionListener()
          bioJSMenuItem_actionPerformed(e);
        }
      });
 -
 +  
      JMenuItem overviewMenuItem = new JMenuItem(
              MessageManager.getString("label.overview_window"));
      overviewMenuItem.addActionListener(new ActionListener()
          overviewMenuItem_actionPerformed(e);
        }
      });
 -
 +  
      undoMenuItem.setEnabled(false);
      undoMenuItem.setText(MessageManager.getString("action.undo"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_Z,
        }
      };
      addMenuActionAndAccelerator(keyStroke, undoMenuItem, al);
 -
 +  
      redoMenuItem.setEnabled(false);
      redoMenuItem.setText(MessageManager.getString("action.redo"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_Y,
        }
      };
      addMenuActionAndAccelerator(keyStroke, redoMenuItem, al);
 -
 +  
      wrapMenuItem.setText(MessageManager.getString("label.wrap"));
      wrapMenuItem.addActionListener(new ActionListener()
      {
          wrapMenuItem_actionPerformed(e);
        }
      });
 -
 +  
      JMenuItem printMenuItem = new JMenuItem(
              MessageManager.getString("action.print"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_P,
        }
      };
      addMenuActionAndAccelerator(keyStroke, printMenuItem, al);
 -
 +  
      renderGapsMenuItem
              .setText(MessageManager.getString("action.show_gaps"));
      renderGapsMenuItem.setState(true);
          renderGapsMenuItem_actionPerformed(e);
        }
      });
 -
 +  
      JMenuItem findMenuItem = new JMenuItem(
              MessageManager.getString("action.find"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_F,
  
      showSeqFeatures.setText(
              MessageManager.getString("label.show_sequence_features"));
 +
      showSeqFeatures.addActionListener(new ActionListener()
      {
        @Override
              .setText(MessageManager.getString("label.show_database_refs"));
      showDbRefsMenuitem.addActionListener(new ActionListener()
      {
 -
 +  
        @Override
        public void actionPerformed(ActionEvent e)
        {
          showDbRefs_actionPerformed(e);
        }
 -
 +  
      });
      showNpFeatsMenuitem.setText(
              MessageManager.getString("label.show_non_positional_features"));
      showNpFeatsMenuitem.addActionListener(new ActionListener()
      {
 -
 +  
        @Override
        public void actionPerformed(ActionEvent e)
        {
          showNpFeats_actionPerformed(e);
        }
 -
 +  
      });
      showGroupConservation
              .setText(MessageManager.getString("label.group_conservation"));
      showGroupConservation.addActionListener(new ActionListener()
      {
 -
 +  
        @Override
        public void actionPerformed(ActionEvent e)
        {
          showGroupConservation_actionPerformed(e);
        }
 -
 +  
      });
  
      showGroupConsensus
              .setText(MessageManager.getString("label.group_consensus"));
      showGroupConsensus.addActionListener(new ActionListener()
      {
 -
 +  
        @Override
        public void actionPerformed(ActionEvent e)
        {
          showGroupConsensus_actionPerformed(e);
        }
 -
 +  
      });
      showConsensusHistogram.setText(
              MessageManager.getString("label.show_consensus_histogram"));
      showConsensusHistogram.addActionListener(new ActionListener()
      {
 -
 +  
        @Override
        public void actionPerformed(ActionEvent e)
        {
          showConsensusHistogram_actionPerformed(e);
        }
 -
 +  
      });
      showSequenceLogo
              .setText(MessageManager.getString("label.show_consensus_logo"));
      showSequenceLogo.addActionListener(new ActionListener()
      {
 -
 +  
        @Override
        public void actionPerformed(ActionEvent e)
        {
          showSequenceLogo_actionPerformed(e);
        }
 -
 +  
      });
      normaliseSequenceLogo
              .setText(MessageManager.getString("label.norm_consensus_logo"));
      normaliseSequenceLogo.addActionListener(new ActionListener()
      {
 -
 +  
        @Override
        public void actionPerformed(ActionEvent e)
        {
          normaliseSequenceLogo_actionPerformed(e);
        }
 -
 +  
      });
      applyAutoAnnotationSettings
              .setText(MessageManager.getString("label.apply_all_groups"));
          applyAutoAnnotationSettings_actionPerformed(e);
        }
      });
 -
 +  
      ButtonGroup buttonGroup = new ButtonGroup();
      final JRadioButtonMenuItem showAutoFirst = new JRadioButtonMenuItem(
              MessageManager.getString("label.show_first"));
          sortAnnotations_actionPerformed();
        }
      });
 -
 +  
      JMenuItem deleteGroups = new JMenuItem(
              MessageManager.getString("action.undefine_groups"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_U,
        }
      };
      addMenuActionAndAccelerator(keyStroke, deleteGroups, al);
 -
 +  
      JMenuItem annotationColumn = new JMenuItem(
              MessageManager.getString("action.select_by_annotation"));
      annotationColumn.addActionListener(new ActionListener()
          annotationColumn_actionPerformed(e);
        }
      });
 -
 +  
      JMenuItem createGroup = new JMenuItem(
              MessageManager.getString("action.create_group"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_G,
        }
      };
      addMenuActionAndAccelerator(keyStroke, createGroup, al);
 -
 +  
      JMenuItem unGroup = new JMenuItem(
              MessageManager.getString("action.remove_group"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_G,
        }
      };
      addMenuActionAndAccelerator(keyStroke, unGroup, al);
 -
 +  
      copy.setText(MessageManager.getString("action.copy"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_C,
              Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
        }
      };
      addMenuActionAndAccelerator(keyStroke, copy, al);
 -
 +  
      cut.setText(MessageManager.getString("action.cut"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_X,
              Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
        }
      };
      addMenuActionAndAccelerator(keyStroke, cut, al);
 -
 +  
      JMenuItem delete = new JMenuItem(
              MessageManager.getString("action.delete"));
      delete.addActionListener(new ActionListener()
          delete_actionPerformed(e);
        }
      });
 -
 +  
      pasteMenu.setText(MessageManager.getString("action.paste"));
      JMenuItem pasteNew = new JMenuItem(
              MessageManager.getString("label.to_new_alignment"));
        @Override
        public void actionPerformed(ActionEvent e)
        {
 -        pasteNew_actionPerformed(e);
 +        try
 +        {
 +          pasteNew_actionPerformed(e);
 +        } catch (IOException | InterruptedException e1)
 +        {
 +          // TODO Auto-generated catch block
 +          e1.printStackTrace();
 +        }
        }
      };
      addMenuActionAndAccelerator(keyStroke, pasteNew, al);
 -
 +  
      JMenuItem pasteThis = new JMenuItem(
              MessageManager.getString("label.to_this_alignment"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_V,
        @Override
        public void actionPerformed(ActionEvent e)
        {
 -        pasteThis_actionPerformed(e);
 +        try
 +        {
 +          pasteThis_actionPerformed(e);
 +        } catch (IOException | InterruptedException e1)
 +        {
 +          // TODO Auto-generated catch block
 +          e1.printStackTrace();
 +        }
        }
      };
      addMenuActionAndAccelerator(keyStroke, pasteThis, al);
 -
 +  
      JMenuItem createPNG = new JMenuItem("PNG");
      createPNG.addActionListener(new ActionListener()
      {
      });
      createPNG.setActionCommand(
              MessageManager.getString("label.save_png_image"));
 -
      JMenuItem font = new JMenuItem(MessageManager.getString("action.font"));
      font.addActionListener(new ActionListener()
      {
          createEPS(null);
        }
      });
 -
 +  
      JMenuItem createSVG = new JMenuItem("SVG");
      createSVG.addActionListener(new ActionListener()
      {
          createSVG(null);
        }
      });
 -
 +  
      JMenuItem loadTreeMenuItem = new JMenuItem(
              MessageManager.getString("label.load_associated_tree"));
      loadTreeMenuItem.setActionCommand(
          loadTreeMenuItem_actionPerformed(e);
        }
      });
 -
 +  
      scaleAbove.setVisible(false);
      scaleAbove.setText(MessageManager.getString("action.scale_above"));
      scaleAbove.addActionListener(new ActionListener()
              .setText(MessageManager.getString("label.automatic_scrolling"));
      followHighlightMenuItem.addActionListener(new ActionListener()
      {
 -
 +  
        @Override
        public void actionPerformed(ActionEvent e)
        {
          followHighlight_actionPerformed();
        }
 -
 +  
      });
 -
 +  
      sortByTreeMenu
              .setText(MessageManager.getString("action.by_tree_order"));
      sort.setText(MessageManager.getString("action.sort"));
        {
          buildTreeSortMenu();
        }
 -
 +  
        @Override
        public void menuDeselected(MenuEvent e)
        {
        }
 -
 +  
        @Override
        public void menuCanceled(MenuEvent e)
        {
      sort.add(sortByAnnotScore);
      sort.addMenuListener(new javax.swing.event.MenuListener()
      {
 -
 +  
        @Override
        public void menuCanceled(MenuEvent e)
        {
        }
 -
 +  
        @Override
        public void menuDeselected(MenuEvent e)
        {
        }
 -
 +  
        @Override
        public void menuSelected(MenuEvent e)
        {
          showReverse_actionPerformed(true);
        }
      });
 -
 +  
      JMenuItem extractScores = new JMenuItem(
              MessageManager.getString("label.extract_scores"));
      extractScores.addActionListener(new ActionListener()
      });
      extractScores.setVisible(true);
      // JBPNote: TODO: make gui for regex based score extraction
 -
 +  
      // for show products actions see AlignFrame.canShowProducts
      showProducts.setText(MessageManager.getString("label.get_cross_refs"));
 -
 +  
      runGroovy.setText(MessageManager.getString("label.run_groovy"));
      runGroovy.setToolTipText(
              MessageManager.getString("label.run_groovy_tip"));
          runGroovy_actionPerformed();
        }
      });
 -
 +  
      JMenuItem openFeatureSettings = new JMenuItem(
              MessageManager.getString("action.feature_settings"));
      openFeatureSettings.addActionListener(new ActionListener()
          fetchSequence_actionPerformed(e);
        }
      });
 -
 +  
      JMenuItem associatedData = new JMenuItem(
              MessageManager.getString("label.load_features_annotations"));
      associatedData.addActionListener(new ActionListener()
        @Override
        public void actionPerformed(ActionEvent e)
        {
 -        associatedData_actionPerformed(e);
 +        try
 +        {
 +          associatedData_actionPerformed(e);
 +        } catch (IOException | InterruptedException e1)
 +        {
 +          // TODO Auto-generated catch block
 +          e1.printStackTrace();
 +        }
        }
      });
+     loadVcf = new JMenuItem(MessageManager.getString("label.load_vcf_file"));
+     loadVcf.setToolTipText(MessageManager.getString("label.load_vcf"));
+     loadVcf.addActionListener(new ActionListener()
+     {
+       @Override
+       public void actionPerformed(ActionEvent e)
+       {
+         loadVcf_actionPerformed();
+       }
+     });
      autoCalculate.setText(
              MessageManager.getString("label.autocalculate_consensus"));
      autoCalculate.setState(
          listenToViewSelections_actionPerformed(e);
        }
      });
 -
 +  
      JMenu addSequenceMenu = new JMenu(
              MessageManager.getString("label.add_sequences"));
      JMenuItem addFromFile = new JMenuItem(
          hiddenMarkers_actionPerformed(e);
        }
      });
 -
 +  
      JMenuItem invertColSel = new JMenuItem(
              MessageManager.getString("action.invert_column_selection"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_I,
        }
      };
      addMenuActionAndAccelerator(keyStroke, invertColSel, al);
 -
 +  
      showComplementMenuItem.setVisible(false);
      showComplementMenuItem.addActionListener(new ActionListener()
      {
          showComplement_actionPerformed(showComplementMenuItem.getState());
        }
      });
 -
 +  
      tabbedPane.addChangeListener(new javax.swing.event.ChangeListener()
      {
        @Override
            tabbedPane_mousePressed(e);
          }
        }
 -
 +  
        @Override
        public void mouseReleased(MouseEvent e)
        {
          tabbedPane_focusGained(e);
        }
      });
 -
 +  
      JMenuItem save = new JMenuItem(MessageManager.getString("action.save"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_S,
              Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
        }
      };
      addMenuActionAndAccelerator(keyStroke, save, al);
 -
 +  
      reload.setEnabled(false);
      reload.setText(MessageManager.getString("action.reload"));
      reload.addActionListener(new ActionListener()
          reload_actionPerformed(e);
        }
      });
 -
 +  
      JMenuItem newView = new JMenuItem(
              MessageManager.getString("action.new_view"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_T,
        }
      };
      addMenuActionAndAccelerator(keyStroke, newView, al);
 -
 +  
      tabbedPane.setToolTipText("<html><i>"
              + MessageManager.getString("label.rename_tab_eXpand_reGroup")
              + "</i></html>");
 -
 +  
      formatMenu.setText(MessageManager.getString("action.format"));
      JMenu selectMenu = new JMenu(MessageManager.getString("action.select"));
  
          idRightAlign_actionPerformed(e);
        }
      });
 -
 +  
      gatherViews.setEnabled(false);
      gatherViews.setText(MessageManager.getString("action.gather_views"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_G, 0, false);
        }
      };
      addMenuActionAndAccelerator(keyStroke, gatherViews, al);
 -
 +  
      expandViews.setEnabled(false);
      expandViews.setText(MessageManager.getString("action.expand_views"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_X, 0, false);
        }
      };
      addMenuActionAndAccelerator(keyStroke, expandViews, al);
 -
 +  
      JMenuItem pageSetup = new JMenuItem(
              MessageManager.getString("action.page_setup"));
      pageSetup.addActionListener(new ActionListener()
          selectHighlightedColumns_actionPerformed(actionEvent);
        }
      };
 +    hmmBuildRun.setText(MessageManager.formatMessage(
 +            "label.action_with_default_settings", "hmmbuild"));
 +    hmmBuildRun.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        hmmBuildRun_actionPerformed();
 +      }
 +    });
 +    hmmBuildSettings.setText(
 +            MessageManager.getString("label.edit_settings_and_run"));
 +    hmmBuildSettings.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        hmmBuildSettings_actionPerformed();
 +      }
 +    });
 +    hmmAlignRun.setText(MessageManager.formatMessage(
 +            "label.action_with_default_settings", "hmmalign"));
 +    hmmAlignRun.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        hmmAlignRun_actionPerformed();
 +      }
 +    });
 +    hmmAlignSettings.setText(
 +            MessageManager.getString("label.edit_settings_and_run"));
 +    hmmAlignSettings.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        hmmAlignSettings_actionPerformed();
 +      }
 +    });
 +    hmmSearchRun.setText(MessageManager.formatMessage(
 +            "label.action_with_default_settings", "hmmsearch"));
 +    hmmSearchRun.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        hmmSearchRun_actionPerformed();
 +      }
 +    });
 +    addDatabase.setText(MessageManager.getString("label.add_database"));
 +    addDatabase.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        try
 +        {
 +          addDatabase_actionPerformed();
 +        } catch (IOException e1)
 +        {
 +          e1.printStackTrace();
 +        }
 +      }
 +    });
 +    hmmSearchSettings.setText(
 +            MessageManager.getString("label.edit_settings_and_run"));
 +    hmmSearchSettings.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        hmmSearchSettings_actionPerformed();
 +      }
 +    });
      selectHighlighted.addActionListener(al);
      JMenu tooltipSettingsMenu = new JMenu(
              MessageManager.getString("label.sequence_id_tooltip"));
      JMenu autoAnnMenu = new JMenu(
              MessageManager.getString("label.autocalculated_annotation"));
 -
 +  
      JMenu exportImageMenu = new JMenu(
              MessageManager.getString("label.export_image"));
      JMenu fileMenu = new JMenu(MessageManager.getString("action.file"));
      alignFrameMenuBar.add(colourMenu);
      alignFrameMenuBar.add(calculateMenu);
      alignFrameMenuBar.add(webService);
 -
 +    alignFrameMenuBar.add(hmmerMenu);
 +  
      fileMenu.add(fetchSequence);
      fileMenu.add(addSequenceMenu);
      fileMenu.add(reload);
      fileMenu.add(exportAnnotations);
      fileMenu.add(loadTreeMenuItem);
      fileMenu.add(associatedData);
+     fileMenu.add(loadVcf);
      fileMenu.addSeparator();
      fileMenu.add(closeMenuItem);
 -
 +  
      pasteMenu.add(pasteNew);
      pasteMenu.add(pasteThis);
      editMenu.add(undoMenuItem);
      // editMenu.add(justifyRightMenuItem);
      // editMenu.addSeparator();
      editMenu.add(padGapsMenuitem);
 -
 +  
      showMenu.add(showAllColumns);
      showMenu.add(showAllSeqs);
      showMenu.add(showAllhidden);
      viewMenu.add(alignmentProperties);
      viewMenu.addSeparator();
      viewMenu.add(overviewMenuItem);
 -
 +  
      annotationsMenu.add(annotationPanelMenuItem);
      annotationsMenu.addSeparator();
      annotationsMenu.add(showAllAlAnnotations);
      calculateMenu.add(extractScores);
      calculateMenu.addSeparator();
      calculateMenu.add(runGroovy);
 -
 +  
      webServiceNoServices = new JMenuItem(
              MessageManager.getString("label.no_services"));
      webService.add(webServiceNoServices);
      this.getContentPane().add(statusPanel, java.awt.BorderLayout.SOUTH);
      statusPanel.add(statusBar, null);
      this.getContentPane().add(tabbedPane, java.awt.BorderLayout.CENTER);
 -
 +  
      formatMenu.add(font);
      formatMenu.addSeparator();
      formatMenu.add(wrapMenuItem);
      // selectMenu.add(listenToViewSelections);
    }
  
 +  public void hmmerMenu_actionPerformed(ActionEvent e)
 +  {
 +
 +  }
 +
 +  /**
 +   * Constructs the entries on the HMMER menu (does not add them to the menu).
 +   */
 +  protected void initHMMERMenu()
 +  {
 +    hmmAlign = new JMenu(MessageManager.getString("label.hmmalign"));
 +    hmmAlignSettings = new JMenuItem(
 +            MessageManager.getString("label.edit_settings_and_run"));
 +    hmmAlignRun = new JMenuItem(MessageManager.formatMessage(
 +            "label.action_with_default_settings", "hmmalign"));
 +    hmmAlign.add(hmmAlignSettings);
 +    hmmAlign.add(hmmAlignRun);
 +    hmmBuild = new JMenu(MessageManager.getString("label.hmmbuild"));
 +    hmmBuildSettings = new JMenuItem(
 +            MessageManager.getString("label.edit_settings_and_run"));
 +    hmmBuildRun = new JMenuItem(MessageManager.formatMessage(
 +            "label.action_with_default_settings", "hmmbuild"));
 +    hmmBuild.add(hmmBuildSettings);
 +    hmmBuild.add(hmmBuildRun);
 +    hmmSearch = new JMenu(MessageManager.getString("label.hmmsearch"));
 +    hmmSearchSettings = new JMenuItem(
 +            MessageManager.getString("label.edit_settings_and_run"));
 +    hmmSearchRun = new JMenuItem(MessageManager.formatMessage(
 +            "label.action_with_default_settings", "hmmsearch"));
 +    addDatabase = new JMenuItem(
 +            MessageManager.getString("label.add_database"));
 +    hmmSearch.add(hmmSearchSettings);
 +    hmmSearch.add(hmmSearchRun);
 +    hmmSearch.add(addDatabase);
 +  }
 +
+   protected void loadVcf_actionPerformed()
+   {
+   }
    /**
     * Constructs the entries on the Colour menu (but does not add them to the
     * menu).
    }
  
    protected void pasteNew_actionPerformed(ActionEvent e)
 +          throws IOException, InterruptedException
    {
    }
  
    protected void pasteThis_actionPerformed(ActionEvent e)
 +          throws IOException, InterruptedException
    {
    }
  
    {
    }
  
 +  protected void hmmBuildRun_actionPerformed()
 +  {
 +  }
 +
 +  protected void hmmSearchRun_actionPerformed()
 +  {
 +  }
 +
 +  protected void addDatabase_actionPerformed()
 +          throws FileFormatException, IOException
 +  {
 +  }
 +
 +  protected void hmmAlignRun_actionPerformed()
 +  {
 +  }
 +
 +  protected void hmmBuildSettings_actionPerformed()
 +  {
 +  }
 +
 +  protected void hmmSearchSettings_actionPerformed()
 +  {
 +  }
 +
 +  protected void hmmAlignSettings_actionPerformed()
 +  {
 +  }
 +
    public void createPNG(java.io.File f)
    {
    }
    }
  
    public void associatedData_actionPerformed(ActionEvent e)
 +          throws IOException, InterruptedException
    {
  
    }
    protected void showComplement_actionPerformed(boolean complement)
    {
    }
 +
 +  protected void showInformationHistogram_actionPerformed(ActionEvent e)
 +  {
 +    // TODO Auto-generated method stub
 +
 +  }
 +
 +  protected void showHMMSequenceLogo_actionPerformed(ActionEvent e)
 +  {
 +
 +  }
 +
 +  protected void normaliseHMMSequenceLogo_actionPerformed(ActionEvent e)
 +  {
 +
 +  }
  }
@@@ -40,11 -40,11 +40,11 @@@ import java.awt.Insets
  import java.awt.Rectangle;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
 -import java.awt.event.FocusEvent;
  import java.awt.event.KeyEvent;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
  
 +import javax.swing.AbstractButton;
  import javax.swing.AbstractCellEditor;
  import javax.swing.BorderFactory;
  import javax.swing.ButtonGroup;
@@@ -142,15 -142,10 +142,15 @@@ public class GPreferences extends JPane
  
    protected JCheckBox showConsensLogo = new JCheckBox();
  
 +  protected JCheckBox showInformationHistogram = new JCheckBox();
 +
 +  protected JCheckBox showHMMLogo = new JCheckBox();
 +
    protected JCheckBox showDbRefTooltip = new JCheckBox();
  
    protected JCheckBox showNpTooltip = new JCheckBox();
  
 +
    /*
     * Structure tab and components
     */
    protected JComboBox<String> nucColour = new JComboBox<>();
  
    /*
+    * Overview tab components
+    */
+   protected JPanel gapColour = new JPanel();
+   protected JPanel hiddenColour = new JPanel();
+   protected JCheckBox useLegacyGap;
+   protected JCheckBox showHiddenAtStart;
+   protected JLabel gapLabel;
+   /*
     * Connections tab components
     */
    protected JTable linkUrlTable = new JTable();
    protected JCheckBox sortByTree = new JCheckBox();
  
    /*
 +   * hmmer tab and components
 +   */
 +  protected JPanel hmmerTab = new JPanel();
 +
 +  protected JCheckBox hmmrTrimTermini = new JCheckBox();
 +
 +  protected AbstractButton hmmerBackgroundUniprot = new JCheckBox();
 +
 +  protected AbstractButton hmmerBackgroundAlignment = new JCheckBox();
 +
 +  protected JTextField hmmerSequenceCount = new JTextField();
 +
 +  protected JTextField hmmerPath = new JTextField();
 +
 +  /*
     * DAS Settings tab
     */
    protected JPanel dasTab = new JPanel();
     */
    protected JPanel wsTab = new JPanel();
  
 +
 +
    /**
     * Creates a new GPreferences object.
     */
      tabbedPane.add(initColoursTab(),
              MessageManager.getString("label.colours"));
  
+     tabbedPane.add(initOverviewTab(),
+             MessageManager.getString("label.overview"));
      tabbedPane.add(initStructureTab(),
              MessageManager.getString("label.structure"));
  
      tabbedPane.add(initEditingTab(),
              MessageManager.getString("label.editing"));
  
 +    tabbedPane.add(initHMMERTab(), MessageManager.getString("label.hmmer"));
 +
      /*
       * See DasSourceBrowser for the real work of configuring this tab.
       */
  
      /*
       * Handler to validate a tab before leaving it - currently only for
 -     * Structure.
 +     * Structure
       */
      tabbedPane.addChangeListener(new ChangeListener()
      {
    }
  
    /**
 +   * Initialises the hmmer tabbed panel
 +   * 
 +   * @return
 +   */
 +  private JPanel initHMMERTab()
 +  {
 +    hmmerTab.setLayout(null);
 +
 +    JLabel installationLocation = new JLabel(
 +            MessageManager.getString("label.hmmer_location"));
 +    installationLocation.setFont(LABEL_FONT);
 +    installationLocation.setBounds(new Rectangle(22, 10, 250, 23));
 +    hmmerPath.setBounds(new Rectangle(22, 30, 300, 23));
 +    hmmerPath.addMouseListener(new MouseAdapter()
 +    {
 +      @Override
 +      public void mouseClicked(MouseEvent e)
 +      {
 +        if (e.getClickCount() == 2)
 +        {
 +          String chosen = openFileChooser(true);
 +          if (chosen != null)
 +          {
 +            hmmerPath.setText(chosen);
 +          }
 +        }
 +      }
 +    });
 +
 +    JLabel hmmalign = new JLabel(
 +            MessageManager.getString("label.hmmalign_label"));
 +    hmmalign.setFont(LABEL_FONT);
 +    hmmalign.setBounds(new Rectangle(22, 50, 200, 23));
 +
 +    hmmrTrimTermini.setFont(LABEL_FONT);
 +    hmmrTrimTermini.setText(MessageManager.getString("label.trim_termini"));
 +    hmmrTrimTermini.setBounds(new Rectangle(22, 70, 200, 23));
 +
 +    JLabel hmmsearch = new JLabel(
 +            MessageManager.getString("label.hmmsearch_label"));
 +    hmmsearch.setFont(LABEL_FONT);
 +    hmmsearch.setBounds(new Rectangle(22, 90, 200, 23));
 +
 +    JLabel sequencesToKeep = new JLabel(
 +            MessageManager.getString("label.no_of_sequences"));
 +    sequencesToKeep.setFont(LABEL_FONT);
 +    sequencesToKeep.setBounds(new Rectangle(22, 110, 125, 23));
 +    hmmerSequenceCount.setBounds(new Rectangle(150, 110, 40, 23));
 +
 +    ButtonGroup backgroundFreqSource = new ButtonGroup();
 +    backgroundFreqSource.add(hmmerBackgroundUniprot);
 +    backgroundFreqSource.add(hmmerBackgroundAlignment);
 +    backgroundFreqSource.setSelected(hmmerBackgroundUniprot.getModel(), true);
 +
 +    hmmerBackgroundUniprot.setText(MessageManager.getString("label.freq_uniprot"));
 +    hmmerBackgroundUniprot.setFont(LABEL_FONT);
 +    hmmerBackgroundUniprot.setBounds(new Rectangle(22, 130, 255, 23));
 +
 +    hmmerBackgroundAlignment.setText(MessageManager.getString("label.freq_alignment"));
 +    hmmerBackgroundAlignment.setFont(LABEL_FONT);
 +    hmmerBackgroundAlignment.setBounds(new Rectangle(22, 150, 300, 23));
 +
 +    hmmerTab.add(hmmerBackgroundUniprot);
 +    hmmerTab.add(hmmerBackgroundAlignment);
 +    hmmerTab.add(hmmalign);
 +    hmmerTab.add(hmmsearch);
 +    hmmerTab.add(installationLocation);
 +    hmmerTab.add(hmmerPath);
 +    hmmerTab.add(hmmrTrimTermini);
 +    hmmerTab.add(sequencesToKeep);
 +    hmmerTab.add(sequencesToKeep);
 +    hmmerTab.add(hmmerSequenceCount);
 +
 +    return hmmerTab;
 +  }
 +
 +  /**
     * Initialises the Output tabbed panel.
     * 
     * @return
    }
  
    /**
+    * Initialises the Overview tabbed panel.
+    * 
+    * @return
+    */
+   private JPanel initOverviewTab()
+   {
+     JPanel overviewPanel = new JPanel();
+     overviewPanel.setBorder(new TitledBorder(
+             MessageManager.getString("label.overview_settings")));
+     gapColour.setFont(LABEL_FONT);
+     // fixing the border colours stops apparent colour bleed from the panel
+     gapColour.setBorder(
+             BorderFactory.createEtchedBorder(Color.white, Color.lightGray));
+     gapColour.setPreferredSize(new Dimension(40, 20));
+     gapColour.addMouseListener(new MouseAdapter()
+     {
+       @Override
+       public void mousePressed(MouseEvent e)
+       {
+         gapColour_actionPerformed(gapColour);
+       }
+     });
+     hiddenColour.setFont(LABEL_FONT);
+     // fixing the border colours stops apparent colour bleed from the panel
+     hiddenColour.setBorder(
+             BorderFactory.createEtchedBorder(Color.white, Color.lightGray));
+     hiddenColour.setPreferredSize(new Dimension(40, 20));
+     hiddenColour.addMouseListener(new MouseAdapter()
+     {
+       @Override
+       public void mousePressed(MouseEvent e)
+       {
+         hiddenColour_actionPerformed(hiddenColour);
+       }
+     });
+     
+     useLegacyGap = new JCheckBox(
+             MessageManager.getString("label.ov_legacy_gap"));
+     useLegacyGap.setFont(LABEL_FONT);
+     useLegacyGap.setHorizontalAlignment(SwingConstants.LEFT);
+     useLegacyGap.setVerticalTextPosition(SwingConstants.TOP);
+     gapLabel = new JLabel(
+             MessageManager.getString("label.gap_colour"));
+     gapLabel.setFont(LABEL_FONT);
+     gapLabel.setHorizontalAlignment(SwingConstants.LEFT);
+     gapLabel.setVerticalTextPosition(SwingConstants.TOP);
+     showHiddenAtStart = new JCheckBox(
+             MessageManager.getString("label.ov_show_hide_default"));
+     showHiddenAtStart.setFont(LABEL_FONT);
+     showHiddenAtStart.setHorizontalAlignment(SwingConstants.LEFT);
+     showHiddenAtStart.setVerticalTextPosition(SwingConstants.TOP);
+     JLabel hiddenLabel = new JLabel(
+             MessageManager.getString("label.hidden_colour"));
+     hiddenLabel.setFont(LABEL_FONT);
+     hiddenLabel.setHorizontalAlignment(SwingConstants.LEFT);
+     hiddenLabel.setVerticalTextPosition(SwingConstants.TOP);
+     useLegacyGap.addActionListener(new ActionListener()
+     {
+       @Override
+       public void actionPerformed(ActionEvent e)
+       {
+         useLegacyGaps_actionPerformed(e);
+       }
+     });
+     overviewPanel.setLayout(new GridBagLayout());
+     GridBagConstraints c1 = new GridBagConstraints();
+     c1.fill = GridBagConstraints.HORIZONTAL;
+     c1.gridx = 0;
+     c1.gridy = 0;
+     c1.weightx = 1;
+     c1.ipady = 20;
+     c1.anchor = GridBagConstraints.FIRST_LINE_START;
+     overviewPanel.add(useLegacyGap, c1);
+     GridBagConstraints c2 = new GridBagConstraints();
+     c2.fill = GridBagConstraints.HORIZONTAL;
+     c2.gridx = 1;
+     c2.gridy = 0;
+     c2.insets = new Insets(0, 15, 0, 10);
+     overviewPanel.add(gapLabel, c2);
+     GridBagConstraints c3 = new GridBagConstraints();
+     c3.fill = GridBagConstraints.HORIZONTAL;
+     c3.gridx = 2;
+     c3.gridy = 0;
+     c3.insets = new Insets(0, 0, 0, 15);
+     overviewPanel.add(gapColour, c3);
+     GridBagConstraints c4 = new GridBagConstraints();
+     c4.fill = GridBagConstraints.HORIZONTAL;
+     c4.gridx = 0;
+     c4.gridy = 1;
+     c4.weightx = 1;
+     overviewPanel.add(showHiddenAtStart, c4);
+     GridBagConstraints c5 = new GridBagConstraints();
+     c5.fill = GridBagConstraints.HORIZONTAL;
+     c5.gridx = 1;
+     c5.gridy = 1;
+     c5.insets = new Insets(0, 15, 0, 10);
+     overviewPanel.add(hiddenLabel, c5);
+     GridBagConstraints c6 = new GridBagConstraints();
+     c6.fill = GridBagConstraints.HORIZONTAL;
+     c6.gridx = 2;
+     c6.gridy = 1;
+     c6.insets = new Insets(0, 0, 0, 15);
+     overviewPanel.add(hiddenColour, c6);
+     JButton resetButton = new JButton(
+             MessageManager.getString("label.reset_to_defaults"));
+     resetButton.addActionListener(new ActionListener()
+     {
+       @Override
+       public void actionPerformed(ActionEvent e)
+       {
+         resetOvDefaults_actionPerformed(e);
+       }
+     });
+     GridBagConstraints c7 = new GridBagConstraints();
+     c7.fill = GridBagConstraints.NONE;
+     c7.gridx = 0;
+     c7.gridy = 2;
+     c7.insets = new Insets(10, 0, 0, 0);
+     c7.anchor = GridBagConstraints.WEST;
+     overviewPanel.add(resetButton, c7);
+     // Add padding so the panel doesn't look ridiculous
+     JPanel spacePanel = new JPanel();
+     overviewPanel.add(spacePanel,
+             new GridBagConstraints(0, 3, 1, 1, 1.0, 1.0,
+                     GridBagConstraints.WEST, GridBagConstraints.BOTH,
+                     new Insets(0, 0, 0, 5), 0, 0));
+     return overviewPanel;
+   }
+   /**
     * Initialises the Structure tabbed panel.
     * 
     * @return
        {
          if (e.getClickCount() == 2)
          {
 -          String chosen = openFileChooser();
 +          String chosen = openFileChooser(false);
            if (chosen != null)
            {
              chimeraPath.setText(chosen);
     * 
     * @return
     */
 -  protected String openFileChooser()
 +  protected String openFileChooser(boolean forFolder)
    {
      String choice = null;
      JFileChooser chooser = new JFileChooser();
 +    if (forFolder)
 +    {
 +      chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
 +    }
  
      // chooser.setFileView(new JalviewFileView());
      chooser.setDialogTitle(
      return choice;
    }
  
 -  /**
 -   * Validate the structure tab preferences; if invalid, set focus on this tab.
 -   * 
 -   * @param e
 -   */
 -  protected boolean validateStructure(FocusEvent e)
 +  protected boolean validateStructure()
    {
 -    if (!validateStructure())
 -    {
 -      e.getComponent().requestFocusInWindow();
 -      return false;
 -    }
 -    return true;
 +    return false;
    }
  
 -  protected boolean validateStructure()
 +  protected boolean validateHMMERPath(boolean showWarning)
    {
      return false;
    }
    {
    }
  
+   protected void gapColour_actionPerformed(JPanel panel)
+   {
+   }
+   protected void hiddenColour_actionPerformed(JPanel panel)
+   {
+   }
    protected void showunconserved_actionPerformed(ActionEvent e)
    {
      // TODO Auto-generated method stub
  
    }
  
+   protected void useLegacyGaps_actionPerformed(ActionEvent e)
+   {
+   }
+   protected void resetOvDefaults_actionPerformed(ActionEvent e)
+   {
+   }
    /**
     * DOCUMENT ME!
     * 
      }
  
    }
 +
 +  public void hmmerPath_actionPerformed(ActionEvent e)
 +  {
 +
 +  }
  }
@@@ -29,7 -29,6 +29,7 @@@ import jalview.datamodel.AlignmentAnnot
  import jalview.datamodel.Annotation;
  import jalview.datamodel.ColumnSelection;
  import jalview.datamodel.HiddenColumns;
 +import jalview.datamodel.HiddenMarkovModel;
  import jalview.datamodel.ProfilesI;
  import jalview.schemes.ColourSchemeI;
  import jalview.schemes.NucleotideColourScheme;
@@@ -74,9 -73,6 +74,9 @@@ public class AnnotationRendere
    boolean av_renderHistogram = true, av_renderProfile = true,
            av_normaliseProfile = false;
  
 +  boolean av_renderInformationHistogram = true, av_renderHMMProfile = true,
 +          av_normaliseHMMProfile = false, av_infoHeight = false;
 +
    ResidueShaderI profcolour = null;
  
    private ColumnSelection columnSelection;
@@@ -91,8 -87,6 +91,8 @@@
  
    private boolean av_ignoreGapsConsensus;
  
 +  private boolean av_ignoreBelowBackground;
 +
    /**
     * attributes set from AwtRenderPanelI
     */
            boolean validRes, boolean validEnd)
    {
      g.setColor(STEM_COLOUR);
-     int sCol = (lastSSX / charWidth) + startRes;
+     int sCol = (lastSSX / charWidth)
+             + hiddenColumns.adjustForHiddenColumns(startRes);
      int x1 = lastSSX;
      int x2 = (x * charWidth);
  
      // System.out.println(nonCanColor);
  
      g.setColor(nonCanColor);
-     int sCol = (lastSSX / charWidth) + startRes;
+     int sCol = (lastSSX / charWidth)
+             + hiddenColumns.adjustForHiddenColumns(startRes);
      int x1 = lastSSX;
      int x2 = (x * charWidth);
  
      av_renderHistogram = av.isShowConsensusHistogram();
      av_renderProfile = av.isShowSequenceLogo();
      av_normaliseProfile = av.isNormaliseSequenceLogo();
 +    av_renderInformationHistogram = av.isShowInformationHistogram();
 +    av_renderHMMProfile = av.isShowHMMSequenceLogo();
 +    av_normaliseHMMProfile = av.isNormaliseHMMSequenceLogo();
      profcolour = av.getResidueShading();
      if (profcolour == null || profcolour.getColourScheme() == null)
      {
      complementConsensus = av.getComplementConsensusHash();
      hStrucConsensus = av.getRnaStructureConsensusHash();
      av_ignoreGapsConsensus = av.isIgnoreGapsConsensus();
 +    av_ignoreBelowBackground = av.isIgnoreBelowBackground();
 +    av_infoHeight = av.isInfoLetterHeight();
    }
  
 +
 +
    /**
     * Returns profile data; the first element is the profile type, the second is
     * the number of distinct values, the third the total count, and the remainder
      // properties/rendering attributes as a global 'alignment group' which holds
      // all vis settings for the alignment as a whole rather than a subset
      //
 -    if (aa.autoCalculated && (aa.label.startsWith("Consensus")
 -            || aa.label.startsWith("cDNA Consensus")))
 +    if ("HMM".equals(aa.getCalcId()))
 +    {
 +      HiddenMarkovModel hmm = aa.sequenceRef.getHMM();
 +      return AAFrequency.extractHMMProfile(hmm, column,
 +              av_ignoreBelowBackground, av_infoHeight); // TODO check if this follows standard
 +                                         // pipeline
 +    }
 +    if (aa.autoCalculated
 +            && (aa.label.startsWith("Consensus") || aa.label
 +                    .startsWith("cDNA Consensus")))
      {
        boolean forComplement = aa.label.startsWith("cDNA Consensus");
        if (aa.groupRef != null && aa.groupRef.consensusData != null
            renderProfile = av_renderProfile;
            normaliseProfile = av_normaliseProfile;
          }
 +        else if ("HMM".equals(row.getCalcId()))
 +        {
 +          renderHistogram = av_renderInformationHistogram;
 +          renderProfile = av_renderHMMProfile;
 +          normaliseProfile = av_normaliseHMMProfile;
 +        }
          else
          {
            renderHistogram = true;
    {
      g.setColor(HELIX_COLOUR);
  
-     int sCol = (lastSSX / charWidth) + startRes;
+     int sCol = (lastSSX / charWidth)
+             + hiddenColumns.adjustForHiddenColumns(startRes);
      int x1 = lastSSX;
      int x2 = (x * charWidth);
  
@@@ -235,6 -235,11 +235,11 @@@ public class ResidueShader implements R
    @Override
    public Color findColour(char symbol, int position, SequenceI seq)
    {
+     if (colourScheme == null)
+     {
+       return Color.white; // Colour is 'None'
+     }
      /*
       * get 'base' colour
       */
              : profile.getModalResidue();
      float pid = profile == null ? 0f
              : profile.getPercentageIdentity(ignoreGaps);
-     Color colour = colourScheme == null ? Color.white
-             : colourScheme.findColour(symbol, position, seq, modalResidue,
-                     pid);
+     Color colour = colourScheme.findColour(symbol, position, seq,
+             modalResidue, pid);
  
      /*
       * apply PID threshold and consensus fading if in force
       */
-     colour = adjustColour(symbol, position, colour);
+     if (!Comparison.isGap(symbol))
+     {
+       colour = adjustColour(symbol, position, colour);
+     }
  
      return colour;
    }
    {
      colourScheme = cs;
    }
 +
 +  @Override
 +  public void setInformation(ProfilesI info)
 +  {
 +    // TODO Auto-generated method stub
 +
 +  }
  }
@@@ -48,9 -48,6 +48,9 @@@ public class ResiduePropertie
    // lookup from modified amino acid (e.g. MSE) to canonical form (e.g. MET)
    public static final Map<String, String> modifications = new HashMap<>();
  
 +  // residue background frequencies across different alphabets
 +  public static final Map<String, Map<Character, Float>> backgroundFrequencies = new HashMap<>();
 +
    static
    {
      aaIndex = new int[255];
     * Color.white, // R Color.white, // Y Color.white, // N Color.white, // Gap
     */
  
-   public static List<String> STOP = Arrays.asList("TGA", "TAA", "TAG");
+   public static String STOP = "STOP";
+   public static List<String> STOP_CODONS = Arrays.asList("TGA", "TAA", "TAG");
  
    public static String START = "ATG";
  
      String cdn = codonHash2.get(lccodon.toUpperCase());
      if ("*".equals(cdn))
      {
-       return "STOP";
+       return STOP;
      }
      return cdn;
    }
  
    }
  
 +  static
 +  {
 +    Map<Character, Float> amino = new HashMap<>();
 +    amino.put('A', 0.0826f);
 +    amino.put('Q', 0.0393f);
 +    amino.put('L', 0.0965f);
 +    amino.put('S', 0.0661f);
 +    amino.put('R', 0.0553f);
 +    amino.put('E', 0.0674f);
 +    amino.put('K', 0.0582f);
 +    amino.put('T', 0.0535f);
 +    amino.put('N', 0.0406f);
 +    amino.put('G', 0.0708f);
 +    amino.put('M', 0.0241f);
 +    amino.put('W', 0.0109f);
 +    amino.put('D', 0.0546f);
 +    amino.put('H', 0.0227f);
 +    amino.put('F', 0.0386f);
 +    amino.put('Y', 0.0292f);
 +    amino.put('C', 0.0137f);
 +    amino.put('I', 0.0593f);
 +    amino.put('P', 0.0472f);
 +    amino.put('V', 0.0686f);
 +    backgroundFrequencies.put("amino", amino);
 +
 +  }
 +
 +  // TODO get correct frequencies
 +
 +  static
 +  {
 +    Map<Character, Float> dna = new HashMap<>();
 +    dna.put('A', 0.25f);
 +    dna.put('C', 0.25f);
 +    dna.put('T', 0.25f);
 +    dna.put('G', 0.25f);
 +    backgroundFrequencies.put("DNA", dna);
 +
 +  }
 +
 +  static
 +  {
 +    Map<Character, Float> rna = new HashMap<>();
 +    rna.put('A', 0.25f);
 +    rna.put('C', 0.25f);
 +    rna.put('T', 0.25f);
 +    rna.put('G', 0.25f);
 +    backgroundFrequencies.put("RNA", rna);
 +
 +  }
 +
    public static String getCanonicalAminoAcid(String aA)
    {
      String canonical = modifications.get(aA);
@@@ -22,6 -22,7 +22,7 @@@ package jalview.viewmodel
  
  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;
@@@ -33,12 -34,9 +34,11 @@@ import jalview.datamodel.AlignmentAnnot
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.AlignmentView;
  import jalview.datamodel.Annotation;
  import jalview.datamodel.ColumnSelection;
  import jalview.datamodel.HiddenColumns;
  import jalview.datamodel.HiddenSequences;
 +import jalview.datamodel.ProfileI;
 +import jalview.datamodel.Profiles;
  import jalview.datamodel.ProfilesI;
  import jalview.datamodel.SearchResultsI;
  import jalview.datamodel.Sequence;
@@@ -59,7 -57,6 +59,7 @@@ import jalview.viewmodel.styles.ViewSty
  import jalview.workers.AlignCalcManager;
  import jalview.workers.ComplementConsensusThread;
  import jalview.workers.ConsensusThread;
 +import jalview.workers.InformationThread;
  import jalview.workers.StrucConsensusThread;
  
  import java.awt.Color;
@@@ -83,7 -80,7 +83,7 @@@ import java.util.Map
  public abstract class AlignmentViewport
          implements AlignViewportI, CommandListener, VamsasSource
  {
-   final protected ViewportRanges ranges;
+   protected ViewportRanges ranges;
  
    protected ViewStyleI viewStyle = new ViewStyle();
  
@@@ -99,7 -96,6 +99,7 @@@
  
    protected Deque<CommandI> redoList = new ArrayDeque<>();
  
 +
    /**
     * alignment displayed in the viewport. Please use get/setter
     */
  
    public boolean autoCalculateConsensus = true;
  
 +  public boolean autoCalculateInformation = true;
 +
    protected boolean autoCalculateStrucConsensus = true;
  
    protected boolean ignoreGapsInConsensusCalculation = false;
  
 +  protected boolean ignoreBelowBackGroundFrequencyCalculation = false;
 +
 +  protected boolean infoLetterHeight = false;
 +
    protected ResidueShaderI residueShading = new ResidueShader();
  
    @Override
  
    protected AlignmentAnnotation[] groupConservation;
  
 +  protected List<AlignmentAnnotation> groupInformation = new ArrayList<>();
 +
 +  protected List<AlignmentAnnotation> information = new ArrayList<>();
 +
    /**
     * results of alignment consensus analysis for visible portion of view
     */
    protected ProfilesI hconsensus = null;
  
    /**
 +   * results of information annotation analysis for the visible portion of view
 +   */
 +  protected List<ProfilesI> hinformation = new ArrayList<>();
 +
 +  /**
     * results of cDNA complement consensus visible portion of view
     */
    protected Hashtable[] hcomplementConsensus = null;
    }
  
    @Override
 +  public void setSequenceInformationHashes(List<ProfilesI> info)
 +  {
 +    hinformation = info;
 +  }
 +
 +  @Override
 +  public void setSequenceInformationHash(ProfilesI info, int index)
 +  {
 +    if (hinformation.size() < index + 1)
 +    {
 +      return;
 +    }
 +    hinformation.set(index, info);
 +  }
 +
 +  @Override
 +  public List<ProfilesI> getSequenceInformationHashes()
 +  {
 +    return hinformation;
 +  }
 +
 +  @Override
 +  public ProfilesI getSequenceInformationHash(int index)
 +  {
 +    return hinformation.get(index);
 +  }
 +
 +  @Override
    public Hashtable[] getComplementConsensusHash()
    {
      return hcomplementConsensus;
    }
  
    @Override
 +  public List<AlignmentAnnotation> getInformationAnnotations()
 +  {
 +    return information;
 +  }
 +
 +  @Override
 +  public AlignmentAnnotation getInformationAnnotation(int index)
 +  {
 +    return information.get(index);
 +  }
 +
 +  @Override
    public AlignmentAnnotation getAlignmentGapAnnotation()
    {
      return gapcounts;
      }
    }
  
 +  /**
 +   * trigger update of information annotation
 +   */
 +  @Override
 +  public void updateInformation(final AlignmentViewPanel ap)
 +  {
 +    if (calculator
 +            .getRegisteredWorkersOfClass(InformationThread.class) == null)
 +    {
 +      calculator.registerWorker(new InformationThread(this, ap));
 +    }
 +
 +  }
 +
    // --------START Structure Conservation
    public void updateStrucConsensus(final AlignmentViewPanel ap)
    {
      groupConsensus = null;
      groupConservation = null;
      hconsensus = null;
+     hconservation = null;
      hcomplementConsensus = null;
-     // colour scheme may hold reference to consensus
-     residueShading = null;
-     // TODO remove listeners from changeSupport?
+     gapcounts = null;
+     calculator = null;
+     residueShading = null; // may hold a reference to Consensus
      changeSupport = null;
+     ranges = null;
+     currentTree = null;
+     selectionGroup = null;
      setAlignment(null);
    }
  
    protected boolean showConsensusHistogram = true;
  
    /**
 +   * should hmm profile be rendered by default
 +   */
 +  protected boolean showHMMSequenceLogo = false;
 +
 +  /**
 +   * should hmm profile be rendered normalised to row height
 +   */
 +  protected boolean normaliseHMMSequenceLogo = false;
 +
 +  /**
 +   * should information histograms be rendered by default
 +   */
 +  protected boolean showInformationHistogram = true;
 +
 +  /**
     * @return the showConsensusProfile
     */
    @Override
    }
  
    /**
 +   * @return the showInformationProfile
 +   */
 +  @Override
 +  public boolean isShowHMMSequenceLogo()
 +  {
 +    return showHMMSequenceLogo;
 +  }
 +
 +  /**
     * @param showSequenceLogo
     *          the new value
     */
      this.showSequenceLogo = showSequenceLogo;
    }
  
 +  public void setShowHMMSequenceLogo(boolean showHMMSequenceLogo)
 +  {
 +    if (showHMMSequenceLogo != this.showHMMSequenceLogo)
 +    {
 +      this.showHMMSequenceLogo = showHMMSequenceLogo;
 +      calculator.updateAnnotationFor(InformationThread.class);
 +    }
 +    this.showHMMSequenceLogo = showHMMSequenceLogo;
 +  }
 +
    /**
     * @param showConsensusHistogram
     *          the showConsensusHistogram to set
    }
  
    /**
 +   * @param showInformationHistogram
 +   *          the showInformationHistogram to set
 +   */
 +  public void setShowInformationHistogram(boolean showInformationHistogram)
 +  {
 +    this.showInformationHistogram = showInformationHistogram;
 +  }
 +
 +  /**
     * @return the showGroupConservation
     */
    public boolean isShowGroupConservation()
    }
  
    /**
 +   * 
 +   * @return flag to indicate if the information content histogram should be
 +   *         rendered by default
 +   */
 +  @Override
 +  public boolean isShowInformationHistogram()
 +  {
 +    return this.showInformationHistogram;
 +  }
 +
 +  /**
     * when set, updateAlignment will always ensure sequences are of equal length
     */
    private boolean padGaps = false;
  
    }
  
 +  public void setIgnoreBelowBackground(boolean b, AlignmentViewPanel ap)
 +  {
 +    ignoreBelowBackGroundFrequencyCalculation = b;
 +    if (ap != null)
 +    {
 +      updateInformation(ap);
 +    }
 +
 +  }
 +
 +  public void setInfoLetterHeight(boolean b, AlignmentViewPanel ap)
 +  {
 +    infoLetterHeight = b;
 +    if (ap != null)
 +    {
 +      updateInformation(ap);
 +    }
 +
 +  }
 +
    private long sgrouphash = -1, colselhash = -1;
  
    /**
      return ignoreGapsInConsensusCalculation;
    }
  
 +  @Override
 +  public boolean isIgnoreBelowBackground()
 +  {
 +    return ignoreBelowBackGroundFrequencyCalculation;
 +  }
 +
 +  @Override
 +  public boolean isInfoLetterHeight()
 +  {
 +    return infoLetterHeight;
 +  }
 +
    // property change stuff
    // JBPNote Prolly only need this in the applet version.
    private PropertyChangeSupport changeSupport = new PropertyChangeSupport(
    public void removePropertyChangeListener(
            java.beans.PropertyChangeListener listener)
    {
-     changeSupport.removePropertyChangeListener(listener);
+     if (changeSupport != null)
+     {
+       changeSupport.removePropertyChangeListener(listener);
+     }
    }
  
    /**
    }
  
    @Override
-   public CigarArray getViewAsCigars(boolean selectedRegionOnly)
-   {
-     return new CigarArray(alignment, alignment.getHiddenColumns(),
-             (selectedRegionOnly ? selectionGroup : null));
-   }
-   @Override
    public jalview.datamodel.AlignmentView getAlignmentView(
            boolean selectedOnly)
    {
      {
        updateStrucConsensus(ap);
      }
 +    initInformation();
 +    updateInformation(ap);
 +
 +    List<SequenceI> hmmSequences;
 +    hmmSequences = alignment.getHMMConsensusSequences(false);
 +
 +    for (SequenceI seq : hmmSequences)
 +    {
 +      seq.updateHMMMapping();
 +    }
  
      // Reset endRes of groups if beyond alignment width
      int alWidth = alignment.getWidth();
                MessageManager.getString("label.consensus_descr"),
                new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
        initConsensus(consensus);
 +
        initGapCounts();
  
        initComplementConsensus();
      }
    }
  
 +  @Override
 +  public void initInformation()
 +  {
 +    for (SequenceI seq : alignment.getHMMConsensusSequences(false))
 +    {
 +      if (!seq.hasHMMAnnotation())
 +      {
 +        AlignmentAnnotation information;
 +        information = new AlignmentAnnotation(seq.getName(),
 +                MessageManager.getString("label.information_description"),
 +                new Annotation[1], 0f, 6.52f,
 +                AlignmentAnnotation.BAR_GRAPH);
 +        information.hasText = true;
 +        information.autoCalculated = true;
 +        information.hasText = true;
 +        information.autoCalculated = false;
 +        information.sequenceRef = seq;
 +        information.setCalcId("HMM");
 +        this.information.add(information);
 +        hinformation.add(new Profiles(new ProfileI[1]));
 +        alignment.addAnnotation(information);
 +        seq.updateHMMMapping();
 +        seq.setHasInfo(true);
 +        seq.addAlignmentAnnotation(information);
 +      }
 +    }
 +
 +  }
 +
    // these should be extracted from the view model - style and settings for
    // derived annotation
    private void initGapCounts()
      boolean showprf = isShowSequenceLogo();
      boolean showConsHist = isShowConsensusHistogram();
      boolean normLogo = isNormaliseSequenceLogo();
 +    boolean showHMMPrf = isShowHMMSequenceLogo();
 +    boolean showInfoHist = isShowInformationHistogram();
 +    boolean normHMMLogo = isNormaliseHMMSequenceLogo();
  
      /**
       * TODO reorder the annotation rows according to group/sequence ordering on
            sg.setshowSequenceLogo(showprf);
            sg.setShowConsensusHistogram(showConsHist);
            sg.setNormaliseSequenceLogo(normLogo);
 +          sg.setshowHMMSequenceLogo(showHMMPrf);
 +          sg.setShowInformationHistogram(showInfoHist);
 +          sg.setNormaliseHMMSequenceLogo(normHMMLogo);
          }
          if (conv)
          {
     */
    private SearchResultsI searchResults = null;
  
+   protected TreeModel currentTree = null;
    @Override
    public boolean hasSearchResults()
    {
      return sq;
    }
  
 +  public boolean hasReferenceAnnotation()
 +  {
 +    AlignmentAnnotation[] annots = this.alignment.getAlignmentAnnotation();
 +    for (AlignmentAnnotation annot : annots)
 +    {
 +      if ("RF".equals(annot.label) || annot.label.contains("Reference"))
 +      {
 +        return true;
 +      }
 +    }
 +    return false;
 +  }
 +
+   @Override
+   public void setCurrentTree(TreeModel tree)
+   {
+     currentTree = tree;
+   }
+   @Override
+   public TreeModel getCurrentTree()
+   {
+     return currentTree;
+   }
  }
index 2abfc69,0000000..8f8dac5
mode 100644,000000..100644
--- /dev/null
@@@ -1,250 -1,0 +1,236 @@@
 +package jalview.workers;
 +
 +import jalview.analysis.AAFrequency;
 +import jalview.api.AlignViewportI;
 +import jalview.api.AlignmentViewPanel;
 +import jalview.datamodel.AlignmentAnnotation;
 +import jalview.datamodel.AlignmentI;
 +import jalview.datamodel.Annotation;
 +import jalview.datamodel.HiddenMarkovModel;
 +import jalview.datamodel.ProfilesI;
 +import jalview.datamodel.SequenceI;
 +import jalview.renderer.ResidueShaderI;
 +
 +import java.util.List;
 +
 +public class InformationThread extends AlignCalcWorker
 +{
-   
++
 +  Float max = 0f;
-   
++
 +  /**
 +   * Constructor for information thread.
 +   * 
 +   * @param alignViewport
 +   * @param alignPanel
 +   */
 +  public InformationThread(AlignViewportI alignViewport,
-             AlignmentViewPanel alignPanel)
++          AlignmentViewPanel alignPanel)
++  {
++    super(alignViewport, alignPanel);
++  }
++
++  @Override
++  public void run()
++  {
++    if (calcMan.isPending(this))
 +    {
-       super(alignViewport, alignPanel);
++      return;
 +    }
++    calcMan.notifyStart(this);
++    long started = System.currentTimeMillis();
 +
-     @Override
-     public void run()
++    List<AlignmentAnnotation> information = getInformationAnnotations();
++    try
 +    {
-       if (calcMan.isPending(this))
++      if ((information == null) || calcMan.isPending(this))
 +      {
++        calcMan.workerComplete(this);
 +        return;
 +      }
-       calcMan.notifyStart(this);
-       long started = System.currentTimeMillis();
-     List<AlignmentAnnotation> information = getInformationAnnotations();
-       try
++      while (!calcMan.notifyWorking(this))
 +      {
-       if ((information == null) || calcMan.isPending(this))
-         {
-           calcMan.workerComplete(this);
-           return;
-         }
-         while (!calcMan.notifyWorking(this))
-         {
-           // System.err.println("Thread
++        // System.err.println("Thread
 +        // (Information"+Thread.currentThread().getName()+") Waiting around.");
-           try
-           {
-             if (ap != null)
-             {
-               ap.paintAlignment(false);
-             }
-             Thread.sleep(200);
-           } catch (Exception ex)
++        try
++        {
++          if (ap != null)
 +          {
-             ex.printStackTrace();
++            ap.paintAlignment(false, false);
 +          }
-         }
-         if (alignViewport.isClosed())
++          Thread.sleep(200);
++        } catch (Exception ex)
 +        {
-           abortAndDestroy();
-           return;
++          ex.printStackTrace();
 +        }
-         AlignmentI alignment = alignViewport.getAlignment();
++      }
++      if (alignViewport.isClosed())
++      {
++        abortAndDestroy();
++        return;
++      }
++      AlignmentI alignment = alignViewport.getAlignment();
 +
-         int aWidth = -1;
++      int aWidth = -1;
 +
-         if (alignment == null || (aWidth = alignment.getWidth()) < 0)
-         {
-           calcMan.workerComplete(this);
-           return;
-         }
++      if (alignment == null || (aWidth = alignment.getWidth()) < 0)
++      {
++        calcMan.workerComplete(this);
++        return;
++      }
 +
 +      eraseInformation(aWidth);
 +      computeInformation(alignment);
-         updateResultAnnotation(true);
++      updateResultAnnotation(true);
 +
-         if (ap != null)
-         {
-           ap.paintAlignment(true);
-         }
-       } catch (OutOfMemoryError error)
++      if (ap != null)
 +      {
-         calcMan.disableWorker(this);
++        ap.paintAlignment(true, true);
++      }
++    } catch (OutOfMemoryError error)
++    {
++      calcMan.disableWorker(this);
 +      ap.raiseOOMWarning("calculating information", error);
 +    } finally
 +    {
 +      calcMan.workerComplete(this);
-       }
 +    }
++  }
 +
-     /**
++  /**
 +   * Clear out any existing information annotations
 +   * 
 +   * @param aWidth
 +   *          the width (number of columns) of the annotated alignment
 +   */
 +  protected void eraseInformation(int aWidth)
-     {
++  {
 +
 +    List<AlignmentAnnotation> information = getInformationAnnotations();
 +    for (AlignmentAnnotation info : information)
 +    {
 +      info.annotations = new Annotation[aWidth];
 +    }
-     }
++  }
 +
-     /**
++  /**
 +   * Computes the profiles from a HMM for an alignment.
 +   * 
 +   * @param alignment
 +   */
 +  protected void computeInformation(AlignmentI alignment)
-     {
-       int width = alignment.getWidth();
++  {
++    int width = alignment.getWidth();
 +    List<SequenceI> hmmSeqs = alignment.getHMMConsensusSequences(false);
 +    int index = 0;
 +
 +    for (SequenceI seq : hmmSeqs)
 +    {
 +      HiddenMarkovModel hmm = seq.getHMM();
 +      ProfilesI hinformation = AAFrequency.calculateHMMProfiles(hmm, width,
 +              0, width, true, alignViewport.isIgnoreBelowBackground(),
 +              alignViewport.isInfoLetterHeight());
 +      alignViewport.setSequenceInformationHash(hinformation, index);
 +      // setColourSchemeInformation(hinformation);
 +      index++;
 +    }
-     }
++  }
 +
-     /**
++  /**
 +   * gets the sequences on the alignment on the viewport.
 +   * 
 +   * @return
 +   */
-     protected SequenceI[] getSequences()
-     {
-       return alignViewport.getAlignment().getSequencesArray();
-     }
++  protected SequenceI[] getSequences()
++  {
++    return alignViewport.getAlignment().getSequencesArray();
++  }
 +
 +  protected void setColourSchemeInformation(ProfilesI information)
++  {
++    ResidueShaderI cs = alignViewport.getResidueShading();
++    if (cs != null)
 +    {
-       ResidueShaderI cs = alignViewport.getResidueShading();
-       if (cs != null)
-       {
 +      cs.setInformation(information);
-       }
 +    }
++  }
 +
-     /**
++  /**
 +   * Get the Information annotation for the alignment
 +   * 
 +   * @return
 +   */
 +  protected List<AlignmentAnnotation> getInformationAnnotations()
-     {
++  {
 +    return alignViewport.getInformationAnnotations();
-     }
++  }
 +
-     /**
-      * Get the Gap annotation for the alignment
-      * 
-      * @return
-      */
-     protected AlignmentAnnotation getGapAnnotation()
-     {
-       return alignViewport.getAlignmentGapAnnotation();
-     }
++  /**
++   * Get the Gap annotation for the alignment
++   * 
++   * @return
++   */
++  protected AlignmentAnnotation getGapAnnotation()
++  {
++    return alignViewport.getAlignmentGapAnnotation();
++  }
 +
-     /**
++  /**
 +   * update the information annotation from the sequence profile data using
 +   * current visualization settings.
 +   */
-     @Override
-     public void updateAnnotation()
-     {
-       updateResultAnnotation(false);
-     }
++  @Override
++  public void updateAnnotation()
++  {
++    updateResultAnnotation(false);
++  }
 +
 +  /**
 +   * Derives the information content for an information annotation.
 +   * 
 +   * @param immediate
 +   */
-     public void updateResultAnnotation(boolean immediate)
-     {
++  public void updateResultAnnotation(boolean immediate)
++  {
 +    List<AlignmentAnnotation> annots = getInformationAnnotations();
 +    int index = 0;
 +    for (AlignmentAnnotation information : annots)
 +    {
 +      ProfilesI hinformation = (ProfilesI) getSequenceInformation(index);
 +      if (immediate || !calcMan.isWorking(this) && information != null
-             && hinformation != null)
++              && hinformation != null)
 +      {
 +        deriveInformation(information, hinformation);
 +      }
 +      index++;
-       }
 +    }
++  }
 +
-     /**
++  /**
 +   * Convert the computed information data into the desired annotation for
 +   * display.
 +   * 
 +   * @param informationAnnotation
 +   *          the annotation to be populated
 +   * @param hinformation
 +   *          the computed information data
 +   */
 +  protected void deriveInformation(
 +          AlignmentAnnotation informationAnnotation, ProfilesI hinformation)
-     {
-       long nseq = getSequences().length;
++  {
++    long nseq = getSequences().length;
 +    max = AAFrequency.completeInformation(informationAnnotation,
 +            hinformation, hinformation.getStartColumn(),
 +            hinformation.getEndColumn() + 1, nseq, max);
-     }
++  }
 +
-     /**
++  /**
 +   * Get the information data stored on the viewport.
 +   * 
 +   * @return
 +   */
 +  protected Object getSequenceInformation(int index)
-     {
-     // TODO convert ComplementInformationThread to use Profile
++  {
 +    return alignViewport.getSequenceInformationHash(index);
-     }
 +  }
++}