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