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