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