Added checks for X mouse coordinate being >= 0 and a test for JAL-2750
[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   private 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       
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 newStart)
758   {
759    newStart= Math.max(0,newStart); // sanity check for negative start column positions
760    int before = startRes;
761    startRes = newStart;
762
763    changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before, startRes);
764     
765
766
767   }
768
769   /**
770    * Set the groups last selected column. Runs from 0<=i<N_cols
771    * 
772    * @param i
773    */
774   public void setEndRes(int i)
775   {
776     int before = endRes;
777     endRes = i;
778     changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before, endRes);
779   }
780
781   /**
782    * @return number of sequences in group
783    */
784   public int getSize()
785   {
786     return sequences.size();
787   }
788
789   /**
790    * @param i
791    * @return the ith sequence
792    */
793   public SequenceI getSequenceAt(int i)
794   {
795     return sequences.get(i);
796   }
797
798   /**
799    * @param state
800    *          colourText
801    */
802   public void setColourText(boolean state)
803   {
804     colourText = state;
805   }
806
807   /**
808    * DOCUMENT ME!
809    * 
810    * @return DOCUMENT ME!
811    */
812   public boolean getColourText()
813   {
814     return colourText;
815   }
816
817   /**
818    * DOCUMENT ME!
819    * 
820    * @param state
821    *          DOCUMENT ME!
822    */
823   public void setDisplayText(boolean state)
824   {
825     displayText = state;
826   }
827
828   /**
829    * DOCUMENT ME!
830    * 
831    * @return DOCUMENT ME!
832    */
833   public boolean getDisplayText()
834   {
835     return displayText;
836   }
837
838   /**
839    * DOCUMENT ME!
840    * 
841    * @param state
842    *          DOCUMENT ME!
843    */
844   public void setDisplayBoxes(boolean state)
845   {
846     displayBoxes = state;
847   }
848
849   /**
850    * DOCUMENT ME!
851    * 
852    * @return DOCUMENT ME!
853    */
854   public boolean getDisplayBoxes()
855   {
856     return displayBoxes;
857   }
858
859   /**
860    * computes the width of current set of sequences and returns it
861    * 
862    * @return DOCUMENT ME!
863    */
864   @Override
865   public int getWidth()
866   {
867     synchronized (sequences)
868     {
869       // MC This needs to get reset when characters are inserted and deleted
870       boolean first = true;
871       for (SequenceI seq : sequences)
872       {
873         if (first || seq.getLength() > width)
874         {
875           width = seq.getLength();
876           first = false;
877         }
878       }
879       return width;
880     }
881   }
882
883   /**
884    * DOCUMENT ME!
885    * 
886    * @param c
887    *          DOCUMENT ME!
888    */
889   public void setOutlineColour(Color c)
890   {
891     outlineColour = c;
892   }
893
894   /**
895    * DOCUMENT ME!
896    * 
897    * @return DOCUMENT ME!
898    */
899   public Color getOutlineColour()
900   {
901     return outlineColour;
902   }
903
904   /**
905    * 
906    * returns the sequences in the group ordered by the ordering given by al.
907    * this used to return an array with null entries regardless, new behaviour is
908    * below. TODO: verify that this does not affect use in applet or application
909    * 
910    * @param al
911    *          Alignment
912    * @return SequenceI[] intersection of sequences in group with al, ordered by
913    *         al, or null if group does not intersect with al
914    */
915   public SequenceI[] getSequencesInOrder(AlignmentI al)
916   {
917     return getSequencesInOrder(al, true);
918   }
919
920   /**
921    * return an array representing the intersection of the group with al,
922    * optionally returning an array the size of al.getHeight() where nulls mark
923    * the non-intersected sequences
924    * 
925    * @param al
926    * @param trim
927    * @return null or array
928    */
929   public SequenceI[] getSequencesInOrder(AlignmentI al, boolean trim)
930   {
931     synchronized (sequences)
932     {
933       int sSize = sequences.size();
934       int alHeight = al.getHeight();
935
936       SequenceI[] seqs = new SequenceI[(trim) ? sSize : alHeight];
937
938       int index = 0;
939       for (int i = 0; i < alHeight && index < sSize; i++)
940       {
941         if (sequences.contains(al.getSequenceAt(i)))
942         {
943           seqs[(trim) ? index : i] = al.getSequenceAt(i);
944           index++;
945         }
946       }
947       if (index == 0)
948       {
949         return null;
950       }
951       if (!trim)
952       {
953         return seqs;
954       }
955       if (index < seqs.length)
956       {
957         SequenceI[] dummy = seqs;
958         seqs = new SequenceI[index];
959         while (--index >= 0)
960         {
961           seqs[index] = dummy[index];
962           dummy[index] = null;
963         }
964       }
965       return seqs;
966     }
967   }
968
969   /**
970    * @return the idColour
971    */
972   public Color getIdColour()
973   {
974     return idColour;
975   }
976
977   /**
978    * @param idColour
979    *          the idColour to set
980    */
981   public void setIdColour(Color idColour)
982   {
983     this.idColour = idColour;
984   }
985
986   /**
987    * @return the representative sequence for this group
988    */
989   @Override
990   public SequenceI getSeqrep()
991   {
992     return seqrep;
993   }
994
995   /**
996    * set the representative sequence for this group. Note - this affects the
997    * interpretation of the Hidereps attribute.
998    * 
999    * @param seqrep
1000    *          the seqrep to set (null means no sequence representative)
1001    */
1002   @Override
1003   public void setSeqrep(SequenceI seqrep)
1004   {
1005     this.seqrep = seqrep;
1006   }
1007
1008   /**
1009    * 
1010    * @return true if group has a sequence representative
1011    */
1012   @Override
1013   public boolean hasSeqrep()
1014   {
1015     return seqrep != null;
1016   }
1017
1018   /**
1019    * set visibility of sequences covered by (if no sequence representative is
1020    * defined) or represented by this group.
1021    * 
1022    * @param visibility
1023    */
1024   public void setHidereps(boolean visibility)
1025   {
1026     hidereps = visibility;
1027   }
1028
1029   /**
1030    * 
1031    * @return true if sequences represented (or covered) by this group should be
1032    *         hidden
1033    */
1034   public boolean isHidereps()
1035   {
1036     return hidereps;
1037   }
1038
1039   /**
1040    * set intended visibility of columns covered by this group
1041    * 
1042    * @param visibility
1043    */
1044   public void setHideCols(boolean visibility)
1045   {
1046     hidecols = visibility;
1047   }
1048
1049   /**
1050    * 
1051    * @return true if columns covered by group should be hidden
1052    */
1053   public boolean isHideCols()
1054   {
1055     return hidecols;
1056   }
1057
1058   /**
1059    * create a new sequence group from the intersection of this group with an
1060    * alignment Hashtable of hidden representatives
1061    * 
1062    * @param alignment
1063    *          (may not be null)
1064    * @param map
1065    *          (may be null)
1066    * @return new group containing sequences common to this group and alignment
1067    */
1068   public SequenceGroup intersect(AlignmentI alignment,
1069           Map<SequenceI, SequenceCollectionI> map)
1070   {
1071     SequenceGroup sgroup = new SequenceGroup(this);
1072     SequenceI[] insect = getSequencesInOrder(alignment);
1073     sgroup.sequences = new ArrayList<>();
1074     for (int s = 0; insect != null && s < insect.length; s++)
1075     {
1076       if (map == null || map.containsKey(insect[s]))
1077       {
1078         sgroup.sequences.add(insect[s]);
1079       }
1080     }
1081     return sgroup;
1082   }
1083
1084   /**
1085    * @return the showUnconserved
1086    */
1087   public boolean getShowNonconserved()
1088   {
1089     return showNonconserved;
1090   }
1091
1092   /**
1093    * @param showNonconserved
1094    *          the showUnconserved to set
1095    */
1096   public void setShowNonconserved(boolean displayNonconserved)
1097   {
1098     this.showNonconserved = displayNonconserved;
1099   }
1100
1101   /**
1102    * set this alignmentAnnotation object as the one used to render consensus
1103    * annotation
1104    * 
1105    * @param aan
1106    */
1107   public void setConsensus(AlignmentAnnotation aan)
1108   {
1109     if (consensus == null)
1110     {
1111       consensus = aan;
1112     }
1113   }
1114
1115   /**
1116    * 
1117    * @return automatically calculated consensus row note: the row is a stub if a
1118    *         consensus calculation has not yet been performed on the group
1119    */
1120   public AlignmentAnnotation getConsensus()
1121   {
1122     // TODO get or calculate and get consensus annotation row for this group
1123     int aWidth = this.getWidth();
1124     // pointer
1125     // possibility
1126     // here.
1127     if (aWidth < 0)
1128     {
1129       return null;
1130     }
1131     if (consensus == null)
1132     {
1133       consensus = new AlignmentAnnotation("", "", new Annotation[1], 0f,
1134               100f, AlignmentAnnotation.BAR_GRAPH);
1135       consensus.hasText = true;
1136       consensus.autoCalculated = true;
1137       consensus.groupRef = this;
1138       consensus.label = "Consensus for " + getName();
1139       consensus.description = "Percent Identity";
1140     }
1141     return consensus;
1142   }
1143
1144   /**
1145    * set this alignmentAnnotation object as the one used to render consensus
1146    * annotation
1147    * 
1148    * @param aan
1149    */
1150   public void setConservationRow(AlignmentAnnotation aan)
1151   {
1152     if (conservation == null)
1153     {
1154       conservation = aan;
1155     }
1156   }
1157
1158   /**
1159    * get the conservation annotation row for this group
1160    * 
1161    * @return autoCalculated annotation row
1162    */
1163   public AlignmentAnnotation getConservationRow()
1164   {
1165     if (conservation == null)
1166     {
1167       conservation = new AlignmentAnnotation("", "", new Annotation[1], 0f,
1168               11f, AlignmentAnnotation.BAR_GRAPH);
1169     }
1170
1171     conservation.hasText = true;
1172     conservation.autoCalculated = true;
1173     conservation.groupRef = this;
1174     conservation.label = "Conservation for " + getName();
1175     conservation.description = "Conservation for group " + getName()
1176             + " less than " + consPercGaps + "% gaps";
1177     return conservation;
1178   }
1179
1180   /**
1181    * 
1182    * @return true if annotation rows have been instantiated for this group
1183    */
1184   public boolean hasAnnotationRows()
1185   {
1186     return consensus != null || conservation != null;
1187   }
1188
1189   public SequenceI getConsensusSeq()
1190   {
1191     getConsensus();
1192     StringBuffer seqs = new StringBuffer();
1193     for (int i = 0; i < consensus.annotations.length; i++)
1194     {
1195       if (consensus.annotations[i] != null)
1196       {
1197         if (consensus.annotations[i].description.charAt(0) == '[')
1198         {
1199           seqs.append(consensus.annotations[i].description.charAt(1));
1200         }
1201         else
1202         {
1203           seqs.append(consensus.annotations[i].displayCharacter);
1204         }
1205       }
1206     }
1207
1208     SequenceI sq = new Sequence("Group" + getName() + " Consensus",
1209             seqs.toString());
1210     sq.setDescription("Percentage Identity Consensus "
1211             + ((ignoreGapsInConsensus) ? " without gaps" : ""));
1212     return sq;
1213   }
1214
1215   public void setIgnoreGapsConsensus(boolean state)
1216   {
1217     if (this.ignoreGapsInConsensus != state && consensus != null)
1218     {
1219       ignoreGapsInConsensus = state;
1220       recalcConservation();
1221     }
1222     ignoreGapsInConsensus = state;
1223   }
1224
1225   public boolean getIgnoreGapsConsensus()
1226   {
1227     return ignoreGapsInConsensus;
1228   }
1229
1230   /**
1231    * @param showSequenceLogo
1232    *          indicates if a sequence logo is shown for consensus annotation
1233    */
1234   public void setshowSequenceLogo(boolean showSequenceLogo)
1235   {
1236     // TODO: decouple calculation from settings update
1237     if (this.showSequenceLogo != showSequenceLogo && consensus != null)
1238     {
1239       this.showSequenceLogo = showSequenceLogo;
1240       recalcConservation();
1241     }
1242     this.showSequenceLogo = showSequenceLogo;
1243   }
1244
1245   /**
1246    * 
1247    * @param showConsHist
1248    *          flag indicating if the consensus histogram for this group should
1249    *          be rendered
1250    */
1251   public void setShowConsensusHistogram(boolean showConsHist)
1252   {
1253
1254     if (showConsensusHistogram != showConsHist && consensus != null)
1255     {
1256       this.showConsensusHistogram = showConsHist;
1257       recalcConservation();
1258     }
1259     this.showConsensusHistogram = showConsHist;
1260   }
1261
1262   /**
1263    * @return the showConsensusHistogram
1264    */
1265   public boolean isShowConsensusHistogram()
1266   {
1267     return showConsensusHistogram;
1268   }
1269
1270   /**
1271    * set flag indicating if logo should be normalised when rendered
1272    * 
1273    * @param norm
1274    */
1275   public void setNormaliseSequenceLogo(boolean norm)
1276   {
1277     normaliseSequenceLogo = norm;
1278   }
1279
1280   public boolean isNormaliseSequenceLogo()
1281   {
1282     return normaliseSequenceLogo;
1283   }
1284
1285   @Override
1286   /**
1287    * returns a new array with all annotation involving this group
1288    */
1289   public AlignmentAnnotation[] getAlignmentAnnotation()
1290   {
1291     // TODO add in other methods like 'getAlignmentAnnotation(String label),
1292     // etc'
1293     ArrayList<AlignmentAnnotation> annot = new ArrayList<>();
1294     synchronized (sequences)
1295     {
1296       for (SequenceI seq : sequences)
1297       {
1298         AlignmentAnnotation[] aa = seq.getAnnotation();
1299         if (aa != null)
1300         {
1301           for (AlignmentAnnotation al : aa)
1302           {
1303             if (al.groupRef == this)
1304             {
1305               annot.add(al);
1306             }
1307           }
1308         }
1309       }
1310       if (consensus != null)
1311       {
1312         annot.add(consensus);
1313       }
1314       if (conservation != null)
1315       {
1316         annot.add(conservation);
1317       }
1318     }
1319     return annot.toArray(new AlignmentAnnotation[0]);
1320   }
1321
1322   @Override
1323   public Iterable<AlignmentAnnotation> findAnnotation(String calcId)
1324   {
1325     List<AlignmentAnnotation> aa = new ArrayList<>();
1326     if (calcId == null)
1327     {
1328       return aa;
1329     }
1330     for (AlignmentAnnotation a : getAlignmentAnnotation())
1331     {
1332       if (calcId.equals(a.getCalcId()))
1333       {
1334         aa.add(a);
1335       }
1336     }
1337     return aa;
1338   }
1339
1340   @Override
1341   public Iterable<AlignmentAnnotation> findAnnotations(SequenceI seq,
1342           String calcId, String label)
1343   {
1344     ArrayList<AlignmentAnnotation> aa = new ArrayList<>();
1345     for (AlignmentAnnotation ann : getAlignmentAnnotation())
1346     {
1347       if ((calcId == null || (ann.getCalcId() != null
1348               && ann.getCalcId().equals(calcId)))
1349               && (seq == null || (ann.sequenceRef != null
1350                       && ann.sequenceRef == seq))
1351               && (label == null
1352                       || (ann.label != null && ann.label.equals(label))))
1353       {
1354         aa.add(ann);
1355       }
1356     }
1357     return aa;
1358   }
1359
1360   /**
1361    * Answer true if any annotation matches the calcId passed in (if not null).
1362    * 
1363    * @param calcId
1364    * @return
1365    */
1366   public boolean hasAnnotation(String calcId)
1367   {
1368     if (calcId != null && !"".equals(calcId))
1369     {
1370       for (AlignmentAnnotation a : getAlignmentAnnotation())
1371       {
1372         if (a.getCalcId() == calcId)
1373         {
1374           return true;
1375         }
1376       }
1377     }
1378     return false;
1379   }
1380
1381   /**
1382    * Remove all sequences from the group (leaving other properties unchanged).
1383    */
1384   public void clear()
1385   {
1386     synchronized (sequences)
1387     {
1388       int before = sequences.size();
1389       sequences.clear();
1390       changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before,
1391               sequences.size());
1392     }
1393   }
1394
1395   /**
1396    * Sets the alignment or group context for this group, and whether it is
1397    * defined as a group
1398    * 
1399    * @param ctx
1400    *          the context for the group
1401    * @param defined
1402    *          whether the group is defined on the alignment or is just a
1403    *          selection
1404    * @throws IllegalArgumentException
1405    *           if setting the context would result in a circular reference chain
1406    */
1407   public void setContext(AnnotatedCollectionI ctx, boolean defined)
1408   {
1409     setContext(ctx);
1410     this.isDefined = defined;
1411   }
1412
1413   /**
1414    * Sets the alignment or group context for this group
1415    * 
1416    * @param ctx
1417    *          the context for the group
1418    * @throws IllegalArgumentException
1419    *           if setting the context would result in a circular reference chain
1420    */
1421   public void setContext(AnnotatedCollectionI ctx)
1422   {
1423     AnnotatedCollectionI ref = ctx;
1424     while (ref != null)
1425     {
1426       if (ref == this || ref.getContext() == ctx)
1427       {
1428         throw new IllegalArgumentException(
1429                 "Circular reference in SequenceGroup.context");
1430       }
1431       ref = ref.getContext();
1432     }
1433     this.context = ctx;
1434   }
1435
1436   /*
1437    * (non-Javadoc)
1438    * 
1439    * @see jalview.datamodel.AnnotatedCollectionI#getContext()
1440    */
1441   @Override
1442   public AnnotatedCollectionI getContext()
1443   {
1444     return context;
1445   }
1446
1447   public boolean isDefined()
1448   {
1449     return isDefined;
1450   }
1451
1452   public void setColourScheme(ColourSchemeI scheme)
1453   {
1454     if (cs == null)
1455     {
1456       cs = new ResidueShader();
1457     }
1458     cs.setColourScheme(scheme);
1459   }
1460
1461   public void setGroupColourScheme(ResidueShaderI scheme)
1462   {
1463     cs = scheme;
1464   }
1465
1466   public ColourSchemeI getColourScheme()
1467   {
1468     return cs == null ? null : cs.getColourScheme();
1469   }
1470
1471   public ResidueShaderI getGroupColourScheme()
1472   {
1473     return cs;
1474   }
1475
1476   @Override
1477   public boolean isNucleotide()
1478   {
1479     if (context != null)
1480     {
1481       return context.isNucleotide();
1482     }
1483     return false;
1484   }
1485
1486   /**
1487    * @param seq
1488    * @return true if seq is a member of the group
1489    */
1490
1491   public boolean contains(SequenceI seq1)
1492   {
1493     return sequences.contains(seq1);
1494   }
1495
1496   /**
1497    * @param seq
1498    * @param apos
1499    * @return true if startRes<=apos and endRes>=apos and seq is in the group
1500    */
1501   public boolean contains(SequenceI seq, int apos)
1502   {
1503     return (startRes <= apos && endRes >= apos) && sequences.contains(seq);
1504   }
1505 }