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