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