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