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