JAL-1432 updated copyright notices
[jalview.git] / src / jalview / datamodel / AlignmentAnnotation.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.0b1)
3  * Copyright (C) 2014 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 of the License, or (at your option) any later version.
10  *  
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  * The Jalview Authors are detailed in the 'AUTHORS' file.
18  */
19 package jalview.datamodel;
20
21 import jalview.analysis.Rna;
22 import jalview.analysis.WUSSParseException;
23
24 import java.util.Enumeration;
25 import java.util.Hashtable;
26
27 /**
28  * DOCUMENT ME!
29  * 
30  * @author $author$
31  * @version $Revision$
32  */
33 public class AlignmentAnnotation
34 {
35   /**
36    * If true, this annotations is calculated every edit, eg consensus, quality
37    * or conservation graphs
38    */
39   public boolean autoCalculated = false;
40
41   public String annotationId;
42
43   public SequenceI sequenceRef;
44
45   /** DOCUMENT ME!! */
46   public String label;
47
48   /** DOCUMENT ME!! */
49   public String description;
50
51   /** DOCUMENT ME!! */
52   public Annotation[] annotations;
53
54   /**
55    * RNA secondary structure contact positions
56    */
57   public SequenceFeature[] _rnasecstr = null;
58
59   /**
60    * position of annotation resulting in invalid WUSS parsing or -1
61    */
62   private long invalidrnastruc = -1;
63
64   /**
65    * Updates the _rnasecstr field Determines the positions that base pair and
66    * the positions of helices based on secondary structure from a Stockholm file
67    * 
68    * @param RNAannot
69    */
70   private void _updateRnaSecStr(CharSequence RNAannot)
71   {
72     try
73     {
74       _rnasecstr = Rna.GetBasePairs(RNAannot);
75       invalidrnastruc = -1;
76     } catch (WUSSParseException px)
77     {
78       invalidrnastruc = px.getProblemPos();
79     }
80     if (invalidrnastruc > -1)
81     {
82       return;
83     }
84     Rna.HelixMap(_rnasecstr);
85     // setRNAStruc(RNAannot);
86
87     if (_rnasecstr != null && _rnasecstr.length > 0)
88     {
89       // show all the RNA secondary structure annotation symbols.
90       isrna = true;
91       showAllColLabels = true;
92       scaleColLabel = true;
93     }
94     // System.out.println("featuregroup " + _rnasecstr[0].getFeatureGroup());
95   }
96
97   public java.util.Hashtable sequenceMapping;
98
99   /** DOCUMENT ME!! */
100   public float graphMin;
101
102   /** DOCUMENT ME!! */
103   public float graphMax;
104
105   /**
106    * Score associated with label and description.
107    */
108   public double score = Double.NaN;
109
110   /**
111    * flag indicating if annotation has a score.
112    */
113   public boolean hasScore = false;
114
115   public GraphLine threshold;
116
117   // Graphical hints and tips
118
119   /** Can this row be edited by the user ? */
120   public boolean editable = false;
121
122   /** Indicates if annotation has a graphical symbol track */
123   public boolean hasIcons; //
124
125   /** Indicates if annotation has a text character label */
126   public boolean hasText;
127
128   /** is the row visible */
129   public boolean visible = true;
130
131   public int graphGroup = -1;
132
133   /** Displayed height of row in pixels */
134   public int height = 0;
135
136   public int graph = 0;
137
138   public int graphHeight = 40;
139
140   public boolean padGaps = false;
141
142   public static final int NO_GRAPH = 0;
143
144   public static final int BAR_GRAPH = 1;
145
146   public static final int LINE_GRAPH = 2;
147
148   public boolean belowAlignment = true;
149
150   public SequenceGroup groupRef = null;
151
152   /**
153    * display every column label, even if there is a row of identical labels
154    */
155   public boolean showAllColLabels = false;
156
157   /**
158    * scale the column label to fit within the alignment column.
159    */
160   public boolean scaleColLabel = false;
161
162   /**
163    * centre the column labels relative to the alignment column
164    */
165   public boolean centreColLabels = false;
166
167   private boolean isrna;
168
169   /*
170    * (non-Javadoc)
171    * 
172    * @see java.lang.Object#finalize()
173    */
174   protected void finalize() throws Throwable
175   {
176     sequenceRef = null;
177     groupRef = null;
178     super.finalize();
179   }
180
181   public static int getGraphValueFromString(String string)
182   {
183     if (string.equalsIgnoreCase("BAR_GRAPH"))
184     {
185       return BAR_GRAPH;
186     }
187     else if (string.equalsIgnoreCase("LINE_GRAPH"))
188     {
189       return LINE_GRAPH;
190     }
191     else
192     {
193       return NO_GRAPH;
194     }
195   }
196
197   /**
198    * Creates a new AlignmentAnnotation object.
199    * 
200    * @param label
201    *          short label shown under sequence labels
202    * @param description
203    *          text displayed on mouseover
204    * @param annotations
205    *          set of positional annotation elements
206    */
207   public AlignmentAnnotation(String label, String description,
208           Annotation[] annotations)
209   {
210     // always editable?
211     editable = true;
212     this.label = label;
213     this.description = description;
214     this.annotations = annotations;
215
216     validateRangeAndDisplay();
217   }
218
219   /**
220    * Checks if annotation labels represent secondary structures
221    * 
222    */
223   void areLabelsSecondaryStructure()
224   {
225     boolean nonSSLabel = false;
226     isrna = false;
227     StringBuffer rnastring = new StringBuffer();
228
229     char firstChar = 0;
230     for (int i = 0; i < annotations.length; i++)
231     {
232       if (annotations[i] == null)
233       {
234         continue;
235       }
236       if (annotations[i].secondaryStructure == 'H'
237               || annotations[i].secondaryStructure == 'E')
238       {
239         hasIcons |= true;
240       }
241       else
242       // Check for RNA secondary structure
243       {
244         if (annotations[i].secondaryStructure == 'S')
245         {
246           hasIcons |= true;
247           isrna |= true;
248         }
249       }
250
251       // System.out.println("displaychar " + annotations[i].displayCharacter);
252
253       if (annotations[i].displayCharacter == null
254               || annotations[i].displayCharacter.length() == 0)
255       {
256         rnastring.append('.');
257         continue;
258       }
259       if (annotations[i].displayCharacter.length() == 1)
260       {
261         firstChar = annotations[i].displayCharacter.charAt(0);
262         // check to see if it looks like a sequence or is secondary structure
263         // labelling.
264         if (annotations[i].secondaryStructure != ' '
265                 && !hasIcons
266                 &&
267                 // Uncomment to only catch case where
268                 // displayCharacter==secondary
269                 // Structure
270                 // to correctly redisplay SS annotation imported from Stockholm,
271                 // exported to JalviewXML and read back in again.
272                 // &&
273                 // annotations[i].displayCharacter.charAt(0)==annotations[i].secondaryStructure
274                 firstChar != ' '
275                 && firstChar != 'H'
276                 && firstChar != 'E'
277                 && firstChar != 'S'
278                 && firstChar != '-'
279                 && firstChar < jalview.schemes.ResidueProperties.aaIndex.length)
280         {
281           if (jalview.schemes.ResidueProperties.aaIndex[firstChar] < 23) // TODO:
282                                                                          // parameterise
283                                                                          // to
284                                                                          // gap
285                                                                          // symbol
286                                                                          // number
287           {
288             nonSSLabel = true;
289           }
290         }
291       }
292       else
293       {
294         rnastring.append(annotations[i].displayCharacter.charAt(1));
295       }
296
297       if (annotations[i].displayCharacter.length() > 0)
298       {
299         hasText = true;
300       }
301     }
302
303     if (nonSSLabel)
304     {
305       hasIcons = false;
306       for (int j = 0; j < annotations.length; j++)
307       {
308         if (annotations[j] != null
309                 && annotations[j].secondaryStructure != ' ')
310         {
311           annotations[j].displayCharacter = String
312                   .valueOf(annotations[j].secondaryStructure);
313           annotations[j].secondaryStructure = ' ';
314         }
315
316       }
317     }
318     else
319     {
320       if (isrna)
321       {
322         _updateRnaSecStr(new AnnotCharSequence());
323       }
324     }
325
326     annotationId = this.hashCode() + "";
327   }
328
329   /**
330    * flyweight access to positions in the alignment annotation row for RNA
331    * processing
332    * 
333    * @author jimp
334    * 
335    */
336   private class AnnotCharSequence implements CharSequence
337   {
338     int offset = 0;
339
340     int max = 0;
341
342     public AnnotCharSequence()
343     {
344       this(0, annotations.length);
345     }
346
347     public AnnotCharSequence(int start, int end)
348     {
349       offset = start;
350       max = end;
351     }
352
353     @Override
354     public CharSequence subSequence(int start, int end)
355     {
356       return new AnnotCharSequence(offset + start, offset + end);
357     }
358
359     @Override
360     public int length()
361     {
362       return max - offset;
363     }
364
365     @Override
366     public char charAt(int index)
367     {
368       String dc;
369       return ((index + offset < 0) || (index + offset) >= max
370               || annotations[index + offset] == null || (dc = annotations[index
371               + offset].displayCharacter.trim()).length() < 1) ? '.' : dc
372               .charAt(0);
373     }
374
375     public String toString()
376     {
377       char[] string = new char[max - offset];
378       int mx = annotations.length;
379
380       for (int i = offset; i < mx; i++)
381       {
382         String dc;
383         string[i] = (annotations[i] == null || (dc = annotations[i].displayCharacter
384                 .trim()).length() < 1) ? '.' : dc.charAt(0);
385       }
386       return new String(string);
387     }
388   };
389
390   private long _lastrnaannot = -1;
391
392   public String getRNAStruc()
393   {
394     if (isrna)
395     {
396       String rnastruc = new AnnotCharSequence().toString();
397       if (_lastrnaannot != rnastruc.hashCode())
398       {
399         // ensure rna structure contacts are up to date
400         _lastrnaannot = rnastruc.hashCode();
401         _updateRnaSecStr(rnastruc);
402       }
403       return rnastruc;
404     }
405     return null;
406   }
407
408   /**
409    * Creates a new AlignmentAnnotation object.
410    * 
411    * @param label
412    *          DOCUMENT ME!
413    * @param description
414    *          DOCUMENT ME!
415    * @param annotations
416    *          DOCUMENT ME!
417    * @param min
418    *          DOCUMENT ME!
419    * @param max
420    *          DOCUMENT ME!
421    * @param winLength
422    *          DOCUMENT ME!
423    */
424   public AlignmentAnnotation(String label, String description,
425           Annotation[] annotations, float min, float max, int graphType)
426   {
427     // graphs are not editable
428     editable = graphType == 0;
429
430     this.label = label;
431     this.description = description;
432     this.annotations = annotations;
433     graph = graphType;
434     graphMin = min;
435     graphMax = max;
436     validateRangeAndDisplay();
437   }
438
439   /**
440    * checks graphMin and graphMax, secondary structure symbols, sets graphType
441    * appropriately, sets null labels to the empty string if appropriate.
442    */
443   public void validateRangeAndDisplay()
444   {
445
446     if (annotations == null)
447     {
448       visible = false; // try to prevent renderer from displaying.
449       return; // this is a non-annotation row annotation - ie a sequence score.
450     }
451
452     int graphType = graph;
453     float min = graphMin;
454     float max = graphMax;
455     boolean drawValues = true;
456     _linecolour = null;
457     if (min == max)
458     {
459       min = 999999999;
460       for (int i = 0; i < annotations.length; i++)
461       {
462         if (annotations[i] == null)
463         {
464           continue;
465         }
466
467         if (drawValues && annotations[i].displayCharacter != null
468                 && annotations[i].displayCharacter.length() > 1)
469         {
470           drawValues = false;
471         }
472
473         if (annotations[i].value > max)
474         {
475           max = annotations[i].value;
476         }
477
478         if (annotations[i].value < min)
479         {
480           min = annotations[i].value;
481         }
482         if (_linecolour == null && annotations[i].colour != null)
483         {
484           _linecolour = annotations[i].colour;
485         }
486       }
487       // ensure zero is origin for min/max ranges on only one side of zero
488       if (min > 0)
489       {
490         min = 0;
491       }
492       else
493       {
494         if (max < 0)
495         {
496           max = 0;
497         }
498       }
499     }
500
501     graphMin = min;
502     graphMax = max;
503
504     areLabelsSecondaryStructure();
505
506     if (!drawValues && graphType != NO_GRAPH)
507     {
508       for (int i = 0; i < annotations.length; i++)
509       {
510         if (annotations[i] != null)
511         {
512           annotations[i].displayCharacter = "";
513         }
514       }
515     }
516   }
517
518   /**
519    * Copy constructor creates a new independent annotation row with the same
520    * associated sequenceRef
521    * 
522    * @param annotation
523    */
524   public AlignmentAnnotation(AlignmentAnnotation annotation)
525   {
526     this.label = new String(annotation.label);
527     if (annotation.description != null)
528       this.description = new String(annotation.description);
529     this.graphMin = annotation.graphMin;
530     this.graphMax = annotation.graphMax;
531     this.graph = annotation.graph;
532     this.graphHeight = annotation.graphHeight;
533     this.graphGroup = annotation.graphGroup;
534     this.groupRef = annotation.groupRef;
535     this.editable = annotation.editable;
536     this.autoCalculated = annotation.autoCalculated;
537     this.hasIcons = annotation.hasIcons;
538     this.hasText = annotation.hasText;
539     this.height = annotation.height;
540     this.label = annotation.label;
541     this.padGaps = annotation.padGaps;
542     this.visible = annotation.visible;
543     this.centreColLabels = annotation.centreColLabels;
544     this.scaleColLabel = annotation.scaleColLabel;
545     this.showAllColLabels = annotation.showAllColLabels;
546     this.calcId = annotation.calcId;
547     if (this.hasScore = annotation.hasScore)
548     {
549       this.score = annotation.score;
550     }
551     if (annotation.threshold != null)
552     {
553       threshold = new GraphLine(annotation.threshold);
554     }
555     if (annotation.annotations != null)
556     {
557       Annotation[] ann = annotation.annotations;
558       this.annotations = new Annotation[ann.length];
559       for (int i = 0; i < ann.length; i++)
560       {
561         if (ann[i] != null)
562         {
563           annotations[i] = new Annotation(ann[i]);
564           if (_linecolour != null)
565           {
566             _linecolour = annotations[i].colour;
567           }
568         }
569       }
570       ;
571       if (annotation.sequenceRef != null)
572       {
573         this.sequenceRef = annotation.sequenceRef;
574         if (annotation.sequenceMapping != null)
575         {
576           Integer p = null;
577           sequenceMapping = new Hashtable();
578           Enumeration pos = annotation.sequenceMapping.keys();
579           while (pos.hasMoreElements())
580           {
581             // could optimise this!
582             p = (Integer) pos.nextElement();
583             Annotation a = (Annotation) annotation.sequenceMapping.get(p);
584             if (a == null)
585             {
586               continue;
587             }
588             for (int i = 0; i < ann.length; i++)
589             {
590               if (ann[i] == a)
591               {
592                 sequenceMapping.put(p, annotations[i]);
593               }
594             }
595           }
596         }
597         else
598         {
599           this.sequenceMapping = null;
600         }
601       }
602     }
603     // TODO: check if we need to do this: JAL-952
604     // if (this.isrna=annotation.isrna)
605     {
606       // _rnasecstr=new SequenceFeature[annotation._rnasecstr];
607     }
608     validateRangeAndDisplay(); // construct hashcodes, etc.
609   }
610
611   /**
612    * clip the annotation to the columns given by startRes and endRes (inclusive)
613    * and prune any existing sequenceMapping to just those columns.
614    * 
615    * @param startRes
616    * @param endRes
617    */
618   public void restrict(int startRes, int endRes)
619   {
620     if (annotations == null)
621     {
622       // non-positional
623       return;
624     }
625     if (startRes < 0)
626       startRes = 0;
627     if (startRes >= annotations.length)
628       startRes = annotations.length - 1;
629     if (endRes >= annotations.length)
630       endRes = annotations.length - 1;
631     if (annotations == null)
632       return;
633     Annotation[] temp = new Annotation[endRes - startRes + 1];
634     if (startRes < annotations.length)
635     {
636       System.arraycopy(annotations, startRes, temp, 0, endRes - startRes
637               + 1);
638     }
639     if (sequenceRef != null)
640     {
641       // Clip the mapping, if it exists.
642       int spos = sequenceRef.findPosition(startRes);
643       int epos = sequenceRef.findPosition(endRes);
644       if (sequenceMapping != null)
645       {
646         Hashtable newmapping = new Hashtable();
647         Enumeration e = sequenceMapping.keys();
648         while (e.hasMoreElements())
649         {
650           Integer pos = (Integer) e.nextElement();
651           if (pos.intValue() >= spos && pos.intValue() <= epos)
652           {
653             newmapping.put(pos, sequenceMapping.get(pos));
654           }
655         }
656         sequenceMapping.clear();
657         sequenceMapping = newmapping;
658       }
659     }
660     annotations = temp;
661   }
662
663   /**
664    * set the annotation row to be at least length Annotations
665    * 
666    * @param length
667    *          minimum number of columns required in the annotation row
668    * @return false if the annotation row is greater than length
669    */
670   public boolean padAnnotation(int length)
671   {
672     if (annotations == null)
673     {
674       return true; // annotation row is correct - null == not visible and
675       // undefined length
676     }
677     if (annotations.length < length)
678     {
679       Annotation[] na = new Annotation[length];
680       System.arraycopy(annotations, 0, na, 0, annotations.length);
681       annotations = na;
682       return true;
683     }
684     return annotations.length > length;
685
686   }
687
688   /**
689    * DOCUMENT ME!
690    * 
691    * @return DOCUMENT ME!
692    */
693   public String toString()
694   {
695     StringBuffer buffer = new StringBuffer();
696
697     for (int i = 0; i < annotations.length; i++)
698     {
699       if (annotations[i] != null)
700       {
701         if (graph != 0)
702         {
703           buffer.append(annotations[i].value);
704         }
705         else if (hasIcons)
706         {
707           buffer.append(annotations[i].secondaryStructure);
708         }
709         else
710         {
711           buffer.append(annotations[i].displayCharacter);
712         }
713       }
714
715       buffer.append(", ");
716     }
717     // TODO: remove disgusting hack for 'special' treatment of consensus line.
718     if (label.indexOf("Consensus") == 0)
719     {
720       buffer.append("\n");
721
722       for (int i = 0; i < annotations.length; i++)
723       {
724         if (annotations[i] != null)
725         {
726           buffer.append(annotations[i].description);
727         }
728
729         buffer.append(", ");
730       }
731     }
732
733     return buffer.toString();
734   }
735
736   public void setThreshold(GraphLine line)
737   {
738     threshold = line;
739   }
740
741   public GraphLine getThreshold()
742   {
743     return threshold;
744   }
745
746   /**
747    * Attach the annotation to seqRef, starting from startRes position. If
748    * alreadyMapped is true then the indices of the annotation[] array are
749    * sequence positions rather than alignment column positions.
750    * 
751    * @param seqRef
752    * @param startRes
753    * @param alreadyMapped
754    */
755   public void createSequenceMapping(SequenceI seqRef, int startRes,
756           boolean alreadyMapped)
757   {
758
759     if (seqRef == null)
760     {
761       return;
762     }
763     sequenceRef = seqRef;
764     if (annotations == null)
765     {
766       return;
767     }
768     sequenceMapping = new java.util.Hashtable();
769
770     int seqPos;
771
772     for (int i = 0; i < annotations.length; i++)
773     {
774       if (annotations[i] != null)
775       {
776         if (alreadyMapped)
777         {
778           seqPos = seqRef.findPosition(i);
779         }
780         else
781         {
782           seqPos = i + startRes;
783         }
784
785         sequenceMapping.put(new Integer(seqPos), annotations[i]);
786       }
787     }
788
789   }
790
791   public void adjustForAlignment()
792   {
793     if (sequenceRef == null)
794       return;
795
796     if (annotations == null)
797     {
798       return;
799     }
800
801     int a = 0, aSize = sequenceRef.getLength();
802
803     if (aSize == 0)
804     {
805       // Its been deleted
806       return;
807     }
808
809     int position;
810     Annotation[] temp = new Annotation[aSize];
811     Integer index;
812
813     for (a = sequenceRef.getStart(); a <= sequenceRef.getEnd(); a++)
814     {
815       index = new Integer(a);
816       if (sequenceMapping.containsKey(index))
817       {
818         position = sequenceRef.findIndex(a) - 1;
819
820         temp[position] = (Annotation) sequenceMapping.get(index);
821       }
822     }
823
824     annotations = temp;
825   }
826
827   /**
828    * remove any null entries in annotation row and return the number of non-null
829    * annotation elements.
830    * 
831    * @return
832    */
833   public int compactAnnotationArray()
834   {
835     int i = 0, iSize = annotations.length;
836     while (i < iSize)
837     {
838       if (annotations[i] == null)
839       {
840         if (i + 1 < iSize)
841           System.arraycopy(annotations, i + 1, annotations, i, iSize - i
842                   - 1);
843         iSize--;
844       }
845       else
846       {
847         i++;
848       }
849     }
850     Annotation[] ann = annotations;
851     annotations = new Annotation[i];
852     System.arraycopy(ann, 0, annotations, 0, i);
853     ann = null;
854     return iSize;
855   }
856
857   /**
858    * Associate this annotion with the aligned residues of a particular sequence.
859    * sequenceMapping will be updated in the following way: null sequenceI -
860    * existing mapping will be discarded but annotations left in mapped
861    * positions. valid sequenceI not equal to current sequenceRef: mapping is
862    * discarded and rebuilt assuming 1:1 correspondence TODO: overload with
863    * parameter to specify correspondence between current and new sequenceRef
864    * 
865    * @param sequenceI
866    */
867   public void setSequenceRef(SequenceI sequenceI)
868   {
869     if (sequenceI != null)
870     {
871       if (sequenceRef != null)
872       {
873         if (sequenceRef != sequenceI
874                 && !sequenceRef.equals(sequenceI)
875                 && sequenceRef.getDatasetSequence() != sequenceI
876                         .getDatasetSequence())
877         {
878           // if sequenceRef isn't intersecting with sequenceI
879           // throw away old mapping and reconstruct.
880           sequenceRef = null;
881           if (sequenceMapping != null)
882           {
883             sequenceMapping = null;
884             // compactAnnotationArray();
885           }
886           createSequenceMapping(sequenceI, 1, true);
887           adjustForAlignment();
888         }
889         else
890         {
891           // Mapping carried over
892           sequenceRef = sequenceI;
893         }
894       }
895       else
896       {
897         // No mapping exists
898         createSequenceMapping(sequenceI, 1, true);
899         adjustForAlignment();
900       }
901     }
902     else
903     {
904       // throw away the mapping without compacting.
905       sequenceMapping = null;
906       sequenceRef = null;
907     }
908   }
909
910   /**
911    * @return the score
912    */
913   public double getScore()
914   {
915     return score;
916   }
917
918   /**
919    * @param score
920    *          the score to set
921    */
922   public void setScore(double score)
923   {
924     hasScore = true;
925     this.score = score;
926   }
927
928   /**
929    * 
930    * @return true if annotation has an associated score
931    */
932   public boolean hasScore()
933   {
934     return hasScore || !Double.isNaN(score);
935   }
936
937   /**
938    * Score only annotation
939    * 
940    * @param label
941    * @param description
942    * @param score
943    */
944   public AlignmentAnnotation(String label, String description, double score)
945   {
946     this(label, description, null);
947     setScore(score);
948   }
949
950   /**
951    * copy constructor with edit based on the hidden columns marked in colSel
952    * 
953    * @param alignmentAnnotation
954    * @param colSel
955    */
956   public AlignmentAnnotation(AlignmentAnnotation alignmentAnnotation,
957           ColumnSelection colSel)
958   {
959     this(alignmentAnnotation);
960     if (annotations == null)
961     {
962       return;
963     }
964     colSel.makeVisibleAnnotation(this);
965   }
966
967   public void setPadGaps(boolean padgaps, char gapchar)
968   {
969     this.padGaps = padgaps;
970     if (padgaps)
971     {
972       hasText = true;
973       for (int i = 0; i < annotations.length; i++)
974       {
975         if (annotations[i] == null)
976           annotations[i] = new Annotation(String.valueOf(gapchar), null,
977                   ' ', 0f);
978         else if (annotations[i].displayCharacter == null
979                 || annotations[i].displayCharacter.equals(" "))
980           annotations[i].displayCharacter = String.valueOf(gapchar);
981       }
982     }
983   }
984
985   /**
986    * format description string for display
987    * 
988    * @param seqname
989    * @return Get the annotation description string optionally prefixed by
990    *         associated sequence name (if any)
991    */
992   public String getDescription(boolean seqname)
993   {
994     if (seqname && this.sequenceRef != null)
995     {
996       int i = description.toLowerCase().indexOf("<html>");
997       if (i > -1)
998       {
999         // move the html tag to before the sequence reference.
1000         return "<html>" + sequenceRef.getName() + " : "
1001                 + description.substring(i + 6);
1002       }
1003       return sequenceRef.getName() + " : " + description;
1004     }
1005     return description;
1006   }
1007
1008   public boolean isValidStruc()
1009   {
1010     return invalidrnastruc == -1;
1011   }
1012
1013   public long getInvalidStrucPos()
1014   {
1015     return invalidrnastruc;
1016   }
1017
1018   /**
1019    * machine readable ID string indicating what generated this annotation
1020    */
1021   protected String calcId = "";
1022
1023   /**
1024    * base colour for line graphs. If null, will be set automatically by
1025    * searching the alignment annotation
1026    */
1027   public java.awt.Color _linecolour;
1028
1029   public String getCalcId()
1030   {
1031     return calcId;
1032   }
1033
1034   public void setCalcId(String calcId)
1035   {
1036     this.calcId = calcId;
1037   }
1038
1039 }