JAL-2629 add option to set HMM Logo letter height to info content
[jalview.git] / src / jalview / datamodel / SequenceGroup.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.datamodel;
22
23 import jalview.analysis.AAFrequency;
24 import jalview.analysis.Conservation;
25 import jalview.renderer.ResidueShader;
26 import jalview.renderer.ResidueShaderI;
27 import jalview.schemes.ColourSchemeI;
28 import jalview.util.MessageManager;
29
30 import java.awt.Color;
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.Map;
34
35 /**
36  * Collects a set contiguous ranges on a set of sequences
37  * 
38  * @author $author$
39  * @version $Revision$
40  */
41 public class SequenceGroup implements AnnotatedCollectionI
42 {
43   String groupName;
44
45   String description;
46
47   Conservation conserve;
48
49   boolean displayBoxes = true;
50
51   boolean displayText = true;
52
53   boolean colourText = false;
54
55   /**
56    * True if the group is defined as a group on the alignment, false if it is
57    * just a selection.
58    */
59   boolean isDefined = false;
60
61   /**
62    * after Olivier's non-conserved only character display
63    */
64   boolean showNonconserved = false;
65
66   /**
67    * group members
68    */
69   private List<SequenceI> sequences = new ArrayList<>();
70
71   /**
72    * representative sequence for this group (if any)
73    */
74   private SequenceI seqrep = null;
75
76   int width = -1;
77
78   /**
79    * Colourscheme applied to group if any
80    */
81   public ResidueShaderI cs;
82
83   // start column (base 0)
84   int startRes = 0;
85
86   // end column (base 0)
87   int endRes = 0;
88
89   public Color outlineColour = Color.black;
90
91   public Color idColour = null;
92
93   public int thresholdTextColour = 0;
94
95   public Color textColour = Color.black;
96
97   public Color textColour2 = Color.white;
98
99   /**
100    * consensus calculation property
101    */
102   private boolean ignoreGapsInConsensus = true;
103
104   private boolean ignoreBelowBackground = true;
105
106   private boolean infoLetterHeight = false;
107
108   /**
109    * consensus calculation property
110    */
111   private boolean showSequenceLogo = false;
112
113   /**
114    * flag indicating if logo should be rendered normalised
115    */
116   private boolean normaliseSequenceLogo;
117
118   /*
119    * visibility of rows or represented rows covered by group
120    */
121   private boolean hidereps = false;
122
123   /*
124    * visibility of columns intersecting this group
125    */
126   private boolean hidecols = false;
127
128   AlignmentAnnotation consensus = null;
129
130   AlignmentAnnotation conservation = null;
131
132   AlignmentAnnotation information = null;
133
134   private boolean showConsensusHistogram;
135
136   private AnnotatedCollectionI context;
137
138   private boolean showHMMSequenceLogo;
139
140   private boolean normaliseHMMSequenceLogo;
141
142   private boolean showInformationHistogram;
143
144   /**
145    * Creates a new SequenceGroup object.
146    */
147   public SequenceGroup()
148   {
149     groupName = "JGroup:" + this.hashCode();
150     cs = new ResidueShader();
151   }
152
153   /**
154    * Creates a new SequenceGroup object.
155    * 
156    * @param sequences
157    * @param groupName
158    * @param scheme
159    * @param displayBoxes
160    * @param displayText
161    * @param colourText
162    * @param start
163    *          first column of group
164    * @param end
165    *          last column of group
166    */
167   public SequenceGroup(List<SequenceI> sequences, String groupName,
168           ColourSchemeI scheme, boolean displayBoxes, boolean displayText,
169           boolean colourText, int start, int end)
170   {
171     this();
172     this.sequences = sequences;
173     this.groupName = groupName;
174     this.displayBoxes = displayBoxes;
175     this.displayText = displayText;
176     this.colourText = colourText;
177     this.cs = new ResidueShader(scheme);
178     startRes = start;
179     endRes = end;
180     recalcConservation();
181   }
182
183   /**
184    * copy constructor
185    * 
186    * @param seqsel
187    */
188   public SequenceGroup(SequenceGroup seqsel)
189   {
190     this();
191     if (seqsel != null)
192     {
193       sequences = new ArrayList<>();
194       sequences.addAll(seqsel.sequences);
195       if (seqsel.groupName != null)
196       {
197         groupName = new String(seqsel.groupName);
198       }
199       displayBoxes = seqsel.displayBoxes;
200       displayText = seqsel.displayText;
201       colourText = seqsel.colourText;
202       startRes = seqsel.startRes;
203       endRes = seqsel.endRes;
204       cs = new ResidueShader((ResidueShader) seqsel.cs);
205       if (seqsel.description != null)
206       {
207         description = new String(seqsel.description);
208       }
209       hidecols = seqsel.hidecols;
210       hidereps = seqsel.hidereps;
211       showNonconserved = seqsel.showNonconserved;
212       showSequenceLogo = seqsel.showSequenceLogo;
213       normaliseSequenceLogo = seqsel.normaliseSequenceLogo;
214       showConsensusHistogram = seqsel.showConsensusHistogram;
215       showHMMSequenceLogo = seqsel.showHMMSequenceLogo;
216       normaliseHMMSequenceLogo = seqsel.normaliseHMMSequenceLogo;
217       showInformationHistogram = seqsel.showInformationHistogram;
218       idColour = seqsel.idColour;
219       outlineColour = seqsel.outlineColour;
220       seqrep = seqsel.seqrep;
221       textColour = seqsel.textColour;
222       textColour2 = seqsel.textColour2;
223       thresholdTextColour = seqsel.thresholdTextColour;
224       width = seqsel.width;
225       ignoreGapsInConsensus = seqsel.ignoreGapsInConsensus;
226       ignoreBelowBackground = seqsel.ignoreBelowBackground;
227       infoLetterHeight = seqsel.infoLetterHeight;
228       if (seqsel.conserve != null)
229       {
230         recalcConservation(); // safer than
231         // aaFrequency = (Vector) seqsel.aaFrequency.clone(); // ??
232       }
233     }
234   }
235
236   public boolean isShowSequenceLogo()
237   {
238     return showSequenceLogo;
239   }
240
241   public SequenceI[] getSelectionAsNewSequences(AlignmentI align)
242   {
243     int iSize = sequences.size();
244     SequenceI[] seqs = new SequenceI[iSize];
245     SequenceI[] inorder = getSequencesInOrder(align);
246
247     for (int i = 0, ipos = 0; i < inorder.length; i++)
248     {
249       SequenceI seq = inorder[i];
250
251       seqs[ipos] = seq.getSubSequence(startRes, endRes + 1);
252       if (seqs[ipos] != null)
253       {
254         seqs[ipos].setDescription(seq.getDescription());
255         seqs[ipos].setDBRefs(seq.getDBRefs());
256         seqs[ipos].setSequenceFeatures(seq.getSequenceFeatures());
257         if (seq.getDatasetSequence() != null)
258         {
259           seqs[ipos].setDatasetSequence(seq.getDatasetSequence());
260         }
261
262         if (seq.getAnnotation() != null)
263         {
264           AlignmentAnnotation[] alann = align.getAlignmentAnnotation();
265           // Only copy annotation that is either a score or referenced by the
266           // alignment's annotation vector
267           for (int a = 0; a < seq.getAnnotation().length; a++)
268           {
269             AlignmentAnnotation tocopy = seq.getAnnotation()[a];
270             if (alann != null)
271             {
272               boolean found = false;
273               for (int pos = 0; pos < alann.length; pos++)
274               {
275                 if (alann[pos] == tocopy)
276                 {
277                   found = true;
278                   break;
279                 }
280               }
281               if (!found)
282               {
283                 continue;
284               }
285             }
286             AlignmentAnnotation newannot = new AlignmentAnnotation(
287                     seq.getAnnotation()[a]);
288             newannot.restrict(startRes, endRes);
289             newannot.setSequenceRef(seqs[ipos]);
290             newannot.adjustForAlignment();
291             seqs[ipos].addAlignmentAnnotation(newannot);
292           }
293         }
294         ipos++;
295       }
296       else
297       {
298         iSize--;
299       }
300     }
301     if (iSize != inorder.length)
302     {
303       SequenceI[] nseqs = new SequenceI[iSize];
304       System.arraycopy(seqs, 0, nseqs, 0, iSize);
305       seqs = nseqs;
306     }
307     return seqs;
308
309   }
310
311   /**
312    * If sequence ends in gaps, the end residue can be correctly calculated here
313    * 
314    * @param seq
315    *          SequenceI
316    * @return int
317    */
318   public int findEndRes(SequenceI seq)
319   {
320     int eres = 0;
321     char ch;
322
323     for (int j = 0; j < endRes + 1 && j < seq.getLength(); j++)
324     {
325       ch = seq.getCharAt(j);
326       if (!jalview.util.Comparison.isGap((ch)))
327       {
328         eres++;
329       }
330     }
331
332     if (eres > 0)
333     {
334       eres += seq.getStart() - 1;
335     }
336
337     return eres;
338   }
339
340   @Override
341   public List<SequenceI> getSequences()
342   {
343     return sequences;
344   }
345
346   @Override
347   public List<SequenceI> getSequences(
348           Map<SequenceI, SequenceCollectionI> hiddenReps)
349   {
350     if (hiddenReps == null)
351     {
352       // TODO: need a synchronizedCollection here ?
353       return sequences;
354     }
355     else
356     {
357       List<SequenceI> allSequences = new ArrayList<>();
358       for (SequenceI seq : sequences)
359       {
360         allSequences.add(seq);
361         if (hiddenReps.containsKey(seq))
362         {
363           SequenceCollectionI hsg = hiddenReps.get(seq);
364           for (SequenceI seq2 : hsg.getSequences())
365           {
366             if (seq2 != seq && !allSequences.contains(seq2))
367             {
368               allSequences.add(seq2);
369             }
370           }
371         }
372       }
373
374       return allSequences;
375     }
376   }
377
378   public SequenceI[] getSequencesAsArray(
379           Map<SequenceI, SequenceCollectionI> map)
380   {
381     List<SequenceI> tmp = getSequences(map);
382     if (tmp == null)
383     {
384       return null;
385     }
386     return tmp.toArray(new SequenceI[tmp.size()]);
387   }
388
389   /**
390    * DOCUMENT ME!
391    * 
392    * @param col
393    *          DOCUMENT ME!
394    * 
395    * @return DOCUMENT ME!
396    */
397   public boolean adjustForRemoveLeft(int col)
398   {
399     // return value is true if the group still exists
400     if (startRes >= col)
401     {
402       startRes = startRes - col;
403     }
404
405     if (endRes >= col)
406     {
407       endRes = endRes - col;
408
409       if (startRes > endRes)
410       {
411         startRes = 0;
412       }
413     }
414     else
415     {
416       // must delete this group!!
417       return false;
418     }
419
420     return true;
421   }
422
423   /**
424    * DOCUMENT ME!
425    * 
426    * @param col
427    *          DOCUMENT ME!
428    * 
429    * @return DOCUMENT ME!
430    */
431   public boolean adjustForRemoveRight(int col)
432   {
433     if (startRes > col)
434     {
435       // delete this group
436       return false;
437     }
438
439     if (endRes >= col)
440     {
441       endRes = col;
442     }
443
444     return true;
445   }
446
447   /**
448    * DOCUMENT ME!
449    * 
450    * @return DOCUMENT ME!
451    */
452   public String getName()
453   {
454     return groupName;
455   }
456
457   public String getDescription()
458   {
459     return description;
460   }
461
462   /**
463    * DOCUMENT ME!
464    * 
465    * @param name
466    *          DOCUMENT ME!
467    */
468   public void setName(String name)
469   {
470     groupName = name;
471     // TODO: URGENT: update dependent objects (annotation row)
472   }
473
474   public void setDescription(String desc)
475   {
476     description = desc;
477   }
478
479   /**
480    * DOCUMENT ME!
481    * 
482    * @return DOCUMENT ME!
483    */
484   public Conservation getConservation()
485   {
486     return conserve;
487   }
488
489   /**
490    * DOCUMENT ME!
491    * 
492    * @param c
493    *          DOCUMENT ME!
494    */
495   public void setConservation(Conservation c)
496   {
497     conserve = c;
498   }
499
500   /**
501    * Add s to this sequence group. If aligment sequence is already contained in
502    * group, it will not be added again, but recalculation may happen if the flag
503    * is set.
504    * 
505    * @param s
506    *          alignment sequence to be added
507    * @param recalc
508    *          true means Group's conservation should be recalculated
509    */
510   public void addSequence(SequenceI s, boolean recalc)
511   {
512     synchronized (sequences)
513     {
514       if (s != null && !sequences.contains(s))
515       {
516         sequences.add(s);
517       }
518
519       if (recalc)
520       {
521         recalcConservation();
522       }
523     }
524   }
525
526   /**
527    * Max Gaps Threshold (percent) for performing a conservation calculation
528    */
529   private int consPercGaps = 25;
530
531   /**
532    * @return Max Gaps Threshold for performing a conservation calculation
533    */
534   public int getConsPercGaps()
535   {
536     return consPercGaps;
537   }
538
539   /**
540    * set Max Gaps Threshold (percent) for performing a conservation calculation
541    * 
542    * @param consPercGaps
543    */
544   public void setConsPercGaps(int consPercGaps)
545   {
546     this.consPercGaps = consPercGaps;
547   }
548
549   /**
550    * calculate residue conservation and colourschemes for group - but only if
551    * necessary. returns true if the calculation resulted in a visible change to
552    * group
553    */
554   public boolean recalcConservation()
555   {
556     return recalcConservation(false);
557   }
558
559   /**
560    * calculate residue conservation for group - but only if necessary. returns
561    * true if the calculation resulted in a visible change to group
562    * 
563    * @param defer
564    *          when set, colourschemes for this group are not refreshed after
565    *          recalculation
566    */
567   public boolean recalcConservation(boolean defer)
568   {
569     if (cs == null && consensus == null && conservation == null
570             && information == null)
571     {
572       return false;
573     }
574     // TODO: try harder to detect changes in state in order to minimise
575     // recalculation effort
576     boolean upd = false;
577     try
578     {
579       ProfilesI cnsns = AAFrequency.calculate(sequences, startRes,
580               endRes + 1, showSequenceLogo);
581       if (information != null)
582       {
583         HiddenMarkovModel hmm = information.sequenceRef.getHMM();
584
585         ProfilesI info = AAFrequency.calculateHMMProfiles(hmm,
586                 (endRes + 1) - startRes, startRes, endRes + 1,
587                 showHMMSequenceLogo, ignoreBelowBackground,
588                 infoLetterHeight);
589         _updateInformationRow(info, sequences.size());
590         upd = true;
591       }
592       if (consensus != null)
593       {
594         _updateConsensusRow(cnsns, sequences.size());
595         upd = true;
596       }
597       if (cs != null)
598       {
599         cs.setConsensus(cnsns);
600         upd = true;
601       }
602
603       if ((conservation != null)
604               || (cs != null && cs.conservationApplied()))
605       {
606         Conservation c = new Conservation(groupName, sequences, startRes,
607                 endRes + 1);
608         c.calculate();
609         c.verdict(false, consPercGaps);
610         if (conservation != null)
611         {
612           _updateConservationRow(c);
613         }
614         if (cs != null)
615         {
616           if (cs.conservationApplied())
617           {
618             cs.setConservation(c);
619           }
620         }
621         // eager update - will cause a refresh of overview regardless
622         upd = true;
623       }
624       if (cs != null && !defer)
625       {
626         // TODO: JAL-2034 should cs.alignmentChanged modify return state
627         cs.alignmentChanged(context != null ? context : this, null);
628         return true;
629       }
630       else
631       {
632         return upd;
633       }
634     } catch (java.lang.OutOfMemoryError err)
635     {
636       // TODO: catch OOM
637       System.out.println("Out of memory loading groups: " + err);
638     }
639     return upd;
640   }
641
642   private void _updateConservationRow(Conservation c)
643   {
644     if (conservation == null)
645     {
646       getConservation();
647     }
648     // update Labels
649     conservation.label = "Conservation for " + getName();
650     conservation.description = "Conservation for group " + getName()
651             + " less than " + consPercGaps + "% gaps";
652     // preserve width if already set
653     int aWidth = (conservation.annotations != null)
654             ? (endRes < conservation.annotations.length
655                     ? conservation.annotations.length
656                     : endRes + 1)
657             : endRes + 1;
658     conservation.annotations = null;
659     conservation.annotations = new Annotation[aWidth]; // should be alignment
660                                                        // width
661     c.completeAnnotations(conservation, null, startRes, endRes + 1);
662   }
663
664   public ProfilesI consensusData = null;
665
666   public ProfilesI informationData = null;
667
668   private void _updateConsensusRow(ProfilesI cnsns, long nseq)
669   {
670     if (consensus == null)
671     {
672       getConsensus();
673     }
674     consensus.label = "Consensus for " + getName();
675     consensus.description = "Percent Identity";
676     consensusData = cnsns;
677     // preserve width if already set
678     int aWidth = (consensus.annotations != null)
679             ? (endRes < consensus.annotations.length
680                     ? consensus.annotations.length
681                     : endRes + 1)
682             : endRes + 1;
683     consensus.annotations = null;
684     consensus.annotations = new Annotation[aWidth]; // should be alignment width
685
686     AAFrequency.completeConsensus(consensus, cnsns, startRes, endRes + 1,
687             ignoreGapsInConsensus, showSequenceLogo, nseq); // TODO: setting
688                                                             // container
689     // for
690     // ignoreGapsInConsensusCalculation);
691   }
692
693   /**
694    * Recalculates the information content on the HMM annotation.
695    * 
696    * @param cnsns
697    * @param nseq
698    */
699   private void _updateInformationRow(ProfilesI cnsns, long nseq)
700   {
701     if (information == null)
702     {
703       getInformation();
704     }
705     information.description = MessageManager
706             .getString("label.information_description");
707     informationData = cnsns;
708     // preserve width if already set
709     int aWidth = (information.annotations != null)
710             ? (endRes < information.annotations.length
711                     ? information.annotations.length : endRes + 1)
712             : endRes + 1;
713     information.annotations = null;
714     information.annotations = new Annotation[aWidth]; // should be alignment
715                                                       // width
716     information.calcId = "HMM";
717     AAFrequency.completeInformation(information, cnsns, startRes,
718             endRes + 1, nseq, 0f); // TODO:
719                                                                         // setting
720                                                             // container
721     // for
722     // ignoreGapsInInformationCalculation);
723   }
724
725   /**
726    * @param s
727    *          sequence to either add or remove from group
728    * @param recalc
729    *          flag passed to delete/addSequence to indicate if group properties
730    *          should be recalculated
731    */
732   public void addOrRemove(SequenceI s, boolean recalc)
733   {
734     synchronized (sequences)
735     {
736       if (sequences.contains(s))
737       {
738         deleteSequence(s, recalc);
739       }
740       else
741       {
742         addSequence(s, recalc);
743       }
744     }
745   }
746
747   /**
748    * remove
749    * 
750    * @param s
751    *          to be removed
752    * @param recalc
753    *          true means recalculate conservation
754    */
755   public void deleteSequence(SequenceI s, boolean recalc)
756   {
757     synchronized (sequences)
758     {
759       sequences.remove(s);
760
761       if (recalc)
762       {
763         recalcConservation();
764       }
765     }
766   }
767
768   /**
769    * 
770    * 
771    * @return the first column selected by this group. Runs from 0<=i<N_cols
772    */
773   @Override
774   public int getStartRes()
775   {
776     return startRes;
777   }
778
779   /**
780    * 
781    * @return the groups last selected column. Runs from 0<=i<N_cols
782    */
783   @Override
784   public int getEndRes()
785   {
786     return endRes;
787   }
788
789   /**
790    * Set the first column selected by this group. Runs from 0<=i<N_cols
791    * 
792    * @param i
793    */
794   public void setStartRes(int i)
795   {
796     startRes = i;
797   }
798
799   /**
800    * Set the groups last selected column. Runs from 0<=i<N_cols
801    * 
802    * @param i
803    */
804   public void setEndRes(int i)
805   {
806     endRes = i;
807   }
808
809   /**
810    * @return number of sequences in group
811    */
812   public int getSize()
813   {
814     return sequences.size();
815   }
816
817   /**
818    * @param i
819    * @return the ith sequence
820    */
821   public SequenceI getSequenceAt(int i)
822   {
823     return sequences.get(i);
824   }
825
826   /**
827    * @param state
828    *          colourText
829    */
830   public void setColourText(boolean state)
831   {
832     colourText = state;
833   }
834
835   /**
836    * DOCUMENT ME!
837    * 
838    * @return DOCUMENT ME!
839    */
840   public boolean getColourText()
841   {
842     return colourText;
843   }
844
845   /**
846    * DOCUMENT ME!
847    * 
848    * @param state
849    *          DOCUMENT ME!
850    */
851   public void setDisplayText(boolean state)
852   {
853     displayText = state;
854   }
855
856   /**
857    * DOCUMENT ME!
858    * 
859    * @return DOCUMENT ME!
860    */
861   public boolean getDisplayText()
862   {
863     return displayText;
864   }
865
866   /**
867    * DOCUMENT ME!
868    * 
869    * @param state
870    *          DOCUMENT ME!
871    */
872   public void setDisplayBoxes(boolean state)
873   {
874     displayBoxes = state;
875   }
876
877   /**
878    * DOCUMENT ME!
879    * 
880    * @return DOCUMENT ME!
881    */
882   public boolean getDisplayBoxes()
883   {
884     return displayBoxes;
885   }
886
887   /**
888    * computes the width of current set of sequences and returns it
889    * 
890    * @return DOCUMENT ME!
891    */
892   @Override
893   public int getWidth()
894   {
895     synchronized (sequences)
896     {
897       // MC This needs to get reset when characters are inserted and deleted
898       boolean first = true;
899       for (SequenceI seq : sequences)
900       {
901         if (first || seq.getLength() > width)
902         {
903           width = seq.getLength();
904           first = false;
905         }
906       }
907       return width;
908     }
909   }
910
911   /**
912    * DOCUMENT ME!
913    * 
914    * @param c
915    *          DOCUMENT ME!
916    */
917   public void setOutlineColour(Color c)
918   {
919     outlineColour = c;
920   }
921
922   /**
923    * DOCUMENT ME!
924    * 
925    * @return DOCUMENT ME!
926    */
927   public Color getOutlineColour()
928   {
929     return outlineColour;
930   }
931
932   /**
933    * 
934    * returns the sequences in the group ordered by the ordering given by al.
935    * this used to return an array with null entries regardless, new behaviour is
936    * below. TODO: verify that this does not affect use in applet or application
937    * 
938    * @param al
939    *          Alignment
940    * @return SequenceI[] intersection of sequences in group with al, ordered by
941    *         al, or null if group does not intersect with al
942    */
943   public SequenceI[] getSequencesInOrder(AlignmentI al)
944   {
945     return getSequencesInOrder(al, true);
946   }
947
948   /**
949    * return an array representing the intersection of the group with al,
950    * optionally returning an array the size of al.getHeight() where nulls mark
951    * the non-intersected sequences
952    * 
953    * @param al
954    * @param trim
955    * @return null or array
956    */
957   public SequenceI[] getSequencesInOrder(AlignmentI al, boolean trim)
958   {
959     synchronized (sequences)
960     {
961       int sSize = sequences.size();
962       int alHeight = al.getHeight();
963
964       SequenceI[] seqs = new SequenceI[(trim) ? sSize : alHeight];
965
966       int index = 0;
967       for (int i = 0; i < alHeight && index < sSize; i++)
968       {
969         if (sequences.contains(al.getSequenceAt(i)))
970         {
971           seqs[(trim) ? index : i] = al.getSequenceAt(i);
972           index++;
973         }
974       }
975       if (index == 0)
976       {
977         return null;
978       }
979       if (!trim)
980       {
981         return seqs;
982       }
983       if (index < seqs.length)
984       {
985         SequenceI[] dummy = seqs;
986         seqs = new SequenceI[index];
987         while (--index >= 0)
988         {
989           seqs[index] = dummy[index];
990           dummy[index] = null;
991         }
992       }
993       return seqs;
994     }
995   }
996
997   /**
998    * @return the idColour
999    */
1000   public Color getIdColour()
1001   {
1002     return idColour;
1003   }
1004
1005   /**
1006    * @param idColour
1007    *          the idColour to set
1008    */
1009   public void setIdColour(Color idColour)
1010   {
1011     this.idColour = idColour;
1012   }
1013
1014   /**
1015    * @return the representative sequence for this group
1016    */
1017   @Override
1018   public SequenceI getSeqrep()
1019   {
1020     return seqrep;
1021   }
1022
1023   /**
1024    * set the representative sequence for this group. Note - this affects the
1025    * interpretation of the Hidereps attribute.
1026    * 
1027    * @param seqrep
1028    *          the seqrep to set (null means no sequence representative)
1029    */
1030   @Override
1031   public void setSeqrep(SequenceI seqrep)
1032   {
1033     this.seqrep = seqrep;
1034   }
1035
1036   /**
1037    * 
1038    * @return true if group has a sequence representative
1039    */
1040   @Override
1041   public boolean hasSeqrep()
1042   {
1043     return seqrep != null;
1044   }
1045
1046   /**
1047    * set visibility of sequences covered by (if no sequence representative is
1048    * defined) or represented by this group.
1049    * 
1050    * @param visibility
1051    */
1052   public void setHidereps(boolean visibility)
1053   {
1054     hidereps = visibility;
1055   }
1056
1057   /**
1058    * 
1059    * @return true if sequences represented (or covered) by this group should be
1060    *         hidden
1061    */
1062   public boolean isHidereps()
1063   {
1064     return hidereps;
1065   }
1066
1067   /**
1068    * set intended visibility of columns covered by this group
1069    * 
1070    * @param visibility
1071    */
1072   public void setHideCols(boolean visibility)
1073   {
1074     hidecols = visibility;
1075   }
1076
1077   /**
1078    * 
1079    * @return true if columns covered by group should be hidden
1080    */
1081   public boolean isHideCols()
1082   {
1083     return hidecols;
1084   }
1085
1086   /**
1087    * create a new sequence group from the intersection of this group with an
1088    * alignment Hashtable of hidden representatives
1089    * 
1090    * @param alignment
1091    *          (may not be null)
1092    * @param map
1093    *          (may be null)
1094    * @return new group containing sequences common to this group and alignment
1095    */
1096   public SequenceGroup intersect(AlignmentI alignment,
1097           Map<SequenceI, SequenceCollectionI> map)
1098   {
1099     SequenceGroup sgroup = new SequenceGroup(this);
1100     SequenceI[] insect = getSequencesInOrder(alignment);
1101     sgroup.sequences = new ArrayList<>();
1102     for (int s = 0; insect != null && s < insect.length; s++)
1103     {
1104       if (map == null || map.containsKey(insect[s]))
1105       {
1106         sgroup.sequences.add(insect[s]);
1107       }
1108     }
1109     return sgroup;
1110   }
1111
1112   /**
1113    * @return the showUnconserved
1114    */
1115   public boolean getShowNonconserved()
1116   {
1117     return showNonconserved;
1118   }
1119
1120   /**
1121    * @param showNonconserved
1122    *          the showUnconserved to set
1123    */
1124   public void setShowNonconserved(boolean displayNonconserved)
1125   {
1126     this.showNonconserved = displayNonconserved;
1127   }
1128
1129   /**
1130    * set this alignmentAnnotation object as the one used to render consensus
1131    * annotation
1132    * 
1133    * @param aan
1134    */
1135   public void setConsensus(AlignmentAnnotation aan)
1136   {
1137     if (consensus == null)
1138     {
1139       consensus = aan;
1140     }
1141   }
1142
1143   /**
1144    * 
1145    * @return automatically calculated consensus row note: the row is a stub if a
1146    *         consensus calculation has not yet been performed on the group
1147    */
1148   public AlignmentAnnotation getConsensus()
1149   {
1150     // TODO get or calculate and get consensus annotation row for this group
1151     int aWidth = this.getWidth();
1152     // pointer
1153     // possibility
1154     // here.
1155     if (aWidth < 0)
1156     {
1157       return null;
1158     }
1159     if (consensus == null)
1160     {
1161       consensus = new AlignmentAnnotation("", "", new Annotation[1], 0f,
1162               100f, AlignmentAnnotation.BAR_GRAPH);
1163       consensus.hasText = true;
1164       consensus.autoCalculated = true;
1165       consensus.groupRef = this;
1166       consensus.label = "Consensus for " + getName();
1167       consensus.description = "Percent Identity";
1168     }
1169     return consensus;
1170   }
1171
1172   /**
1173    * 
1174    * @return information content annotation.
1175    */
1176   public AlignmentAnnotation getInformation()
1177   {
1178     // TODO get or calculate and get information annotation row for this group
1179     int aWidth = this.getWidth();
1180     // pointer
1181     // possibility
1182     // here.
1183     if (aWidth < 0)
1184     {
1185       return null;
1186     }
1187     if (information == null)
1188     {
1189       information = new AlignmentAnnotation("", "", new Annotation[1], 0f,
1190               6.25f, AlignmentAnnotation.BAR_GRAPH);
1191       information.hasText = true;
1192       information.autoCalculated = false;
1193       information.groupRef = this;
1194       information.label = getName();
1195       information.description = "Information content, measured in bits";
1196       information.calcId = "HMM";
1197     }
1198     return information;
1199   }
1200
1201   /**
1202    * set this alignmentAnnotation object as the one used to render consensus
1203    * annotation
1204    * 
1205    * @param aan
1206    */
1207   public void setConservationRow(AlignmentAnnotation aan)
1208   {
1209     if (conservation == null)
1210     {
1211       conservation = aan;
1212     }
1213   }
1214
1215   /**
1216    * get the conservation annotation row for this group
1217    * 
1218    * @return autoCalculated annotation row
1219    */
1220   public AlignmentAnnotation getConservationRow()
1221   {
1222     if (conservation == null)
1223     {
1224       conservation = new AlignmentAnnotation("", "", new Annotation[1], 0f,
1225               11f, AlignmentAnnotation.BAR_GRAPH);
1226     }
1227
1228     conservation.hasText = true;
1229     conservation.autoCalculated = true;
1230     conservation.groupRef = this;
1231     conservation.label = "Conservation for " + getName();
1232     conservation.description = "Conservation for group " + getName()
1233             + " less than " + consPercGaps + "% gaps";
1234     return conservation;
1235   }
1236
1237   /**
1238    * 
1239    * @return true if annotation rows have been instantiated for this group
1240    */
1241   public boolean hasAnnotationRows()
1242   {
1243     return consensus != null || conservation != null;
1244   }
1245
1246   public SequenceI getConsensusSeq()
1247   {
1248     getConsensus();
1249     StringBuffer seqs = new StringBuffer();
1250     for (int i = 0; i < consensus.annotations.length; i++)
1251     {
1252       if (consensus.annotations[i] != null)
1253       {
1254         if (consensus.annotations[i].description.charAt(0) == '[')
1255         {
1256           seqs.append(consensus.annotations[i].description.charAt(1));
1257         }
1258         else
1259         {
1260           seqs.append(consensus.annotations[i].displayCharacter);
1261         }
1262       }
1263     }
1264
1265     SequenceI sq = new Sequence("Group" + getName() + " Consensus",
1266             seqs.toString());
1267     sq.setDescription("Percentage Identity Consensus "
1268             + ((ignoreGapsInConsensus) ? " without gaps" : ""));
1269     return sq;
1270   }
1271
1272   public void setIgnoreGapsConsensus(boolean state)
1273   {
1274     if (this.ignoreGapsInConsensus != state && consensus != null)
1275     {
1276       ignoreGapsInConsensus = state;
1277       recalcConservation();
1278     }
1279     ignoreGapsInConsensus = state;
1280   }
1281
1282   public boolean getIgnoreGapsConsensus()
1283   {
1284     return ignoreGapsInConsensus;
1285   }
1286
1287   public void setIgnoreBelowBackground(boolean state)
1288   {
1289     if (this.ignoreBelowBackground != state)
1290     {
1291       ignoreBelowBackground = state;
1292     }
1293     ignoreBelowBackground = state;
1294   }
1295
1296   public boolean getIgnoreBelowBackground()
1297   {
1298     return ignoreBelowBackground;
1299   }
1300
1301   public void setInfoLetterHeight(boolean state)
1302   {
1303     if (this.infoLetterHeight != state)
1304     {
1305       infoLetterHeight = state;
1306     }
1307     infoLetterHeight = state;
1308   }
1309
1310   public boolean getInfoLetterHeight()
1311   {
1312     return infoLetterHeight;
1313   }
1314
1315   /**
1316    * @param showSequenceLogo
1317    *          indicates if a sequence logo is shown for consensus annotation
1318    */
1319   public void setshowSequenceLogo(boolean showSequenceLogo)
1320   {
1321     // TODO: decouple calculation from settings update
1322     if (this.showSequenceLogo != showSequenceLogo && consensus != null)
1323     {
1324       this.showSequenceLogo = showSequenceLogo;
1325       recalcConservation();
1326     }
1327     this.showSequenceLogo = showSequenceLogo;
1328   }
1329
1330   /**
1331    * 
1332    * @param showConsHist
1333    *          flag indicating if the consensus histogram for this group should
1334    *          be rendered
1335    */
1336   public void setShowConsensusHistogram(boolean showConsHist)
1337   {
1338
1339     if (showConsensusHistogram != showConsHist && consensus != null)
1340     {
1341       this.showConsensusHistogram = showConsHist;
1342       recalcConservation();
1343     }
1344     this.showConsensusHistogram = showConsHist;
1345   }
1346
1347   /**
1348    * @return the showConsensusHistogram
1349    */
1350   public boolean isShowConsensusHistogram()
1351   {
1352     return showConsensusHistogram;
1353   }
1354
1355   /**
1356    * set flag indicating if logo should be normalised when rendered
1357    * 
1358    * @param norm
1359    */
1360   public void setNormaliseSequenceLogo(boolean norm)
1361   {
1362     normaliseSequenceLogo = norm;
1363   }
1364
1365   public boolean isNormaliseSequenceLogo()
1366   {
1367     return normaliseSequenceLogo;
1368   }
1369
1370   @Override
1371   /**
1372    * returns a new array with all annotation involving this group
1373    */
1374   public AlignmentAnnotation[] getAlignmentAnnotation()
1375   {
1376     // TODO add in other methods like 'getAlignmentAnnotation(String label),
1377     // etc'
1378     ArrayList<AlignmentAnnotation> annot = new ArrayList<>();
1379     synchronized (sequences)
1380     {
1381       for (SequenceI seq : sequences)
1382       {
1383         AlignmentAnnotation[] aa = seq.getAnnotation();
1384         if (aa != null)
1385         {
1386           for (AlignmentAnnotation al : aa)
1387           {
1388             if (al.groupRef == this)
1389             {
1390               annot.add(al);
1391             }
1392           }
1393         }
1394       }
1395       if (consensus != null)
1396       {
1397         annot.add(consensus);
1398       }
1399       if (conservation != null)
1400       {
1401         annot.add(conservation);
1402       }
1403     }
1404     return annot.toArray(new AlignmentAnnotation[0]);
1405   }
1406
1407   @Override
1408   public Iterable<AlignmentAnnotation> findAnnotation(String calcId)
1409   {
1410     List<AlignmentAnnotation> aa = new ArrayList<>();
1411     if (calcId == null)
1412     {
1413       return aa;
1414     }
1415     for (AlignmentAnnotation a : getAlignmentAnnotation())
1416     {
1417       if (calcId.equals(a.getCalcId()))
1418       {
1419         aa.add(a);
1420       }
1421     }
1422     return aa;
1423   }
1424
1425   @Override
1426   public Iterable<AlignmentAnnotation> findAnnotations(SequenceI seq,
1427           String calcId, String label)
1428   {
1429     ArrayList<AlignmentAnnotation> aa = new ArrayList<>();
1430     for (AlignmentAnnotation ann : getAlignmentAnnotation())
1431     {
1432       if ((calcId == null || (ann.getCalcId() != null
1433               && ann.getCalcId().equals(calcId)))
1434               && (seq == null || (ann.sequenceRef != null
1435                       && ann.sequenceRef == seq))
1436               && (label == null
1437                       || (ann.label != null && ann.label.equals(label))))
1438       {
1439         aa.add(ann);
1440       }
1441     }
1442     return aa;
1443   }
1444
1445   /**
1446    * Answer true if any annotation matches the calcId passed in (if not null).
1447    * 
1448    * @param calcId
1449    * @return
1450    */
1451   public boolean hasAnnotation(String calcId)
1452   {
1453     if (calcId != null && !"".equals(calcId))
1454     {
1455       for (AlignmentAnnotation a : getAlignmentAnnotation())
1456       {
1457         if (a.getCalcId() == calcId)
1458         {
1459           return true;
1460         }
1461       }
1462     }
1463     return false;
1464   }
1465
1466   /**
1467    * Remove all sequences from the group (leaving other properties unchanged).
1468    */
1469   public void clear()
1470   {
1471     synchronized (sequences)
1472     {
1473       sequences.clear();
1474     }
1475   }
1476
1477   /**
1478    * Sets the alignment or group context for this group, and whether it is
1479    * defined as a group
1480    * 
1481    * @param ctx
1482    *          the context for the group
1483    * @param defined
1484    *          whether the group is defined on the alignment or is just a
1485    *          selection
1486    * @throws IllegalArgumentException
1487    *           if setting the context would result in a circular reference chain
1488    */
1489   public void setContext(AnnotatedCollectionI ctx, boolean defined)
1490   {
1491     setContext(ctx);
1492     this.isDefined = defined;
1493   }
1494
1495   /**
1496    * Sets the alignment or group context for this group
1497    * 
1498    * @param ctx
1499    *          the context for the group
1500    * @throws IllegalArgumentException
1501    *           if setting the context would result in a circular reference chain
1502    */
1503   public void setContext(AnnotatedCollectionI ctx)
1504   {
1505     AnnotatedCollectionI ref = ctx;
1506     while (ref != null)
1507     {
1508       if (ref == this || ref.getContext() == ctx)
1509       {
1510         throw new IllegalArgumentException(
1511                 "Circular reference in SequenceGroup.context");
1512       }
1513       ref = ref.getContext();
1514     }
1515     this.context = ctx;
1516   }
1517
1518   /*
1519    * (non-Javadoc)
1520    * 
1521    * @see jalview.datamodel.AnnotatedCollectionI#getContext()
1522    */
1523   @Override
1524   public AnnotatedCollectionI getContext()
1525   {
1526     return context;
1527   }
1528
1529   public boolean isDefined()
1530   {
1531     return isDefined;
1532   }
1533
1534   public void setColourScheme(ColourSchemeI scheme)
1535   {
1536     if (cs == null)
1537     {
1538       cs = new ResidueShader();
1539     }
1540     cs.setColourScheme(scheme);
1541   }
1542
1543   public void setGroupColourScheme(ResidueShaderI scheme)
1544   {
1545     cs = scheme;
1546   }
1547
1548   public ColourSchemeI getColourScheme()
1549   {
1550     return cs == null ? null : cs.getColourScheme();
1551   }
1552
1553   public ResidueShaderI getGroupColourScheme()
1554   {
1555     return cs;
1556   }
1557
1558   @Override
1559   public boolean isNucleotide()
1560   {
1561     if (context != null)
1562     {
1563       return context.isNucleotide();
1564     }
1565     return false;
1566   }
1567
1568   /**
1569    * @param seq
1570    * @return true if seq is a member of the group
1571    */
1572
1573   public boolean contains(SequenceI seq1)
1574   {
1575     return sequences.contains(seq1);
1576   }
1577
1578   /**
1579    * @param seq
1580    * @param apos
1581    * @return true if startRes<=apos and endRes>=apos and seq is in the group
1582    */
1583   public boolean contains(SequenceI seq, int apos)
1584   {
1585     return (startRes <= apos && endRes >= apos) && sequences.contains(seq);
1586   }
1587
1588   public boolean isShowInformationHistogram()
1589   {
1590     return showInformationHistogram;
1591   }
1592
1593   public void setShowInformationHistogram(boolean state)
1594   {
1595     if (showInformationHistogram != state && information != null)
1596     {
1597       this.showInformationHistogram = state;
1598       // recalcConservation(); TODO don't know what to do here next
1599     }
1600     this.showInformationHistogram = state;
1601
1602   }
1603
1604   public boolean isShowHMMSequenceLogo()
1605   {
1606     // TODO Auto-generated method stub
1607     return showHMMSequenceLogo;
1608   }
1609
1610   public void setshowHMMSequenceLogo(boolean state)
1611   {
1612     showHMMSequenceLogo = state;
1613
1614   }
1615
1616   public boolean isNormaliseHMMSequenceLogo()
1617   {
1618     // TODO Auto-generated method stub
1619     return normaliseHMMSequenceLogo;
1620   }
1621
1622   public void setNormaliseHMMSequenceLogo(boolean state)
1623   {
1624     normaliseSequenceLogo = state;
1625   }
1626
1627   /**
1628    * Returns all HMM consensus sequences. This will not return real sequences
1629    * with HMMs. If remove is set to true, the consensus sequences will be
1630    * removed from the alignment.
1631    */
1632   @Override // TODO make this more efficient.
1633   public List<SequenceI> getHMMConsensusSequences(boolean remove)
1634   {
1635     List<SequenceI> seqs = new ArrayList<>();
1636     int position = 0;
1637     int seqsRemoved = 0;
1638     boolean endReached = false;
1639
1640     while (!endReached)
1641     {
1642       SequenceI seq = sequences.get(position);
1643       if (seq.isHMMConsensusSequence())
1644       {
1645         if (remove)
1646         {
1647           sequences.remove(position);
1648           seqsRemoved++;
1649           seq.setPreviousPosition(seqsRemoved + position - 1);
1650         }
1651         else
1652         {
1653           position++;
1654         }
1655         seqs.add(seq);
1656       }
1657       else
1658       {
1659         position++;
1660       }
1661
1662       if (position >= sequences.size())
1663       {
1664         endReached = true;
1665       }
1666     }
1667     return seqs;
1668   }
1669
1670 }