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