JAL-2591 Hidden columns regions can be output to string + test
[jalview.git] / src / jalview / io / AnnotationFile.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.io;
22
23 import jalview.analysis.Conservation;
24 import jalview.api.AlignViewportI;
25 import jalview.datamodel.AlignmentAnnotation;
26 import jalview.datamodel.AlignmentI;
27 import jalview.datamodel.Annotation;
28 import jalview.datamodel.ColumnSelection;
29 import jalview.datamodel.GraphLine;
30 import jalview.datamodel.HiddenColumns;
31 import jalview.datamodel.HiddenSequences;
32 import jalview.datamodel.SequenceGroup;
33 import jalview.datamodel.SequenceI;
34 import jalview.schemes.ColourSchemeI;
35 import jalview.schemes.ColourSchemeProperty;
36 import jalview.util.ColorUtils;
37
38 import java.awt.Color;
39 import java.io.BufferedReader;
40 import java.io.FileReader;
41 import java.io.InputStreamReader;
42 import java.io.StringReader;
43 import java.net.URL;
44 import java.util.ArrayList;
45 import java.util.BitSet;
46 import java.util.Enumeration;
47 import java.util.Hashtable;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.StringTokenizer;
51 import java.util.Vector;
52
53 public class AnnotationFile
54 {
55   public AnnotationFile()
56   {
57     init();
58   }
59
60   /**
61    * character used to write newlines
62    */
63   protected String newline = System.getProperty("line.separator");
64
65   /**
66    * set new line string and reset the output buffer
67    * 
68    * @param nl
69    */
70   public void setNewlineString(String nl)
71   {
72     newline = nl;
73     init();
74   }
75
76   public String getNewlineString()
77   {
78     return newline;
79   }
80
81   StringBuffer text;
82
83   private void init()
84   {
85     text = new StringBuffer("JALVIEW_ANNOTATION" + newline + "# Created: "
86             + new java.util.Date() + newline + newline);
87     refSeq = null;
88     refSeqId = null;
89   }
90
91   /**
92    * convenience method for pre-2.9 annotation files which have no view, hidden
93    * columns or hidden row keywords.
94    * 
95    * @param annotations
96    * @param list
97    * @param properties
98    * @return annotation file as a string.
99    */
100   public String printAnnotations(AlignmentAnnotation[] annotations,
101           List<SequenceGroup> list, Hashtable properties)
102   {
103     return printAnnotations(annotations, list, properties, null, null, null);
104
105   }
106
107   /**
108    * hold all the information about a particular view definition read from or
109    * written out in an annotations file.
110    */
111   public class ViewDef
112   {
113     // TODO this class is not used - remove?
114     public final String viewname;
115
116     public final HiddenSequences hidseqs;
117
118     public final HiddenColumns hiddencols;
119
120     public final Hashtable hiddenRepSeqs;
121
122     public ViewDef(String vname, HiddenSequences hseqs,
123             HiddenColumns hcols, Hashtable hRepSeqs)
124     {
125       this.viewname = vname;
126       this.hidseqs = hseqs;
127       this.hiddencols = hcols;
128       this.hiddenRepSeqs = hRepSeqs;
129     }
130   }
131
132   /**
133    * Prepare an annotation file given a set of annotations, groups, alignment
134    * properties and views.
135    * 
136    * @param annotations
137    * @param list
138    * @param properties
139    * @param views
140    * @return annotation file
141    */
142   public String printAnnotations(AlignmentAnnotation[] annotations,
143           List<SequenceGroup> list, Hashtable properties,
144  HiddenColumns cs,
145           AlignmentI al, ViewDef view)
146   {
147     if (view != null)
148     {
149       if (view.viewname != null)
150       {
151         text.append("VIEW_DEF\t" + view.viewname + "\n");
152       }
153       if (list == null)
154       {
155         // list = view.visibleGroups;
156       }
157       if (cs == null)
158       {
159         cs = view.hiddencols;
160       }
161       if (al == null)
162       {
163         // add hidden rep sequences.
164       }
165     }
166     // first target - store and restore all settings for a view.
167     if (al != null && al.hasSeqrep())
168     {
169       text.append("VIEW_SETREF\t" + al.getSeqrep().getName() + "\n");
170     }
171     if (cs != null && cs.hasHiddenColumns())
172     {
173       text.append("VIEW_HIDECOLS\t");
174
175       String regions = cs.regionsToString(",", "-");
176       text.append(regions);
177       text.append("\n");
178     }
179     // TODO: allow efficient recovery of annotation data shown in several
180     // different views
181     if (annotations != null)
182     {
183       boolean oneColour = true;
184       AlignmentAnnotation row;
185       String comma;
186       SequenceI refSeq = null;
187       SequenceGroup refGroup = null;
188
189       StringBuffer colours = new StringBuffer();
190       StringBuffer graphLine = new StringBuffer();
191       StringBuffer rowprops = new StringBuffer();
192       Hashtable<Integer, String> graphGroup = new Hashtable<>();
193       Hashtable<Integer, Object[]> graphGroup_refs = new Hashtable<>();
194       BitSet graphGroupSeen = new BitSet();
195
196       java.awt.Color color;
197
198       for (int i = 0; i < annotations.length; i++)
199       {
200         row = annotations[i];
201
202         if (!row.visible
203                 && !row.hasScore()
204                 && !(row.graphGroup > -1 && graphGroupSeen
205                         .get(row.graphGroup)))
206         {
207           continue;
208         }
209
210         color = null;
211         oneColour = true;
212
213         // mark any sequence references for the row
214         writeSequence_Ref(refSeq, row.sequenceRef);
215         refSeq = row.sequenceRef;
216         // mark any group references for the row
217         writeGroup_Ref(refGroup, row.groupRef);
218         refGroup = row.groupRef;
219
220         boolean hasGlyphs = row.hasIcons, hasLabels = row.hasText, hasValues = row.hasScore, hasText = false;
221         // lookahead to check what the annotation row object actually contains.
222         for (int j = 0; row.annotations != null
223                 && j < row.annotations.length
224                 && (!hasGlyphs || !hasLabels || !hasValues); j++)
225         {
226           if (row.annotations[j] != null)
227           {
228             hasLabels |= (row.annotations[j].displayCharacter != null
229                     && row.annotations[j].displayCharacter.length() > 0 && !row.annotations[j].displayCharacter
230                     .equals(" "));
231             hasGlyphs |= (row.annotations[j].secondaryStructure != 0 && row.annotations[j].secondaryStructure != ' ');
232             hasValues |= (!Float.isNaN(row.annotations[j].value)); // NaNs can't
233             // be
234             // rendered..
235             hasText |= (row.annotations[j].description != null && row.annotations[j].description
236                     .length() > 0);
237           }
238         }
239
240         if (row.graph == AlignmentAnnotation.NO_GRAPH)
241         {
242           text.append("NO_GRAPH\t");
243           hasValues = false; // only secondary structure
244           // hasLabels = false; // and annotation description string.
245         }
246         else
247         {
248           if (row.graph == AlignmentAnnotation.BAR_GRAPH)
249           {
250             text.append("BAR_GRAPH\t");
251             hasGlyphs = false; // no secondary structure
252
253           }
254           else if (row.graph == AlignmentAnnotation.LINE_GRAPH)
255           {
256             hasGlyphs = false; // no secondary structure
257             text.append("LINE_GRAPH\t");
258           }
259
260           if (row.getThreshold() != null)
261           {
262             graphLine.append("GRAPHLINE\t");
263             graphLine.append(row.label);
264             graphLine.append("\t");
265             graphLine.append(row.getThreshold().value);
266             graphLine.append("\t");
267             graphLine.append(row.getThreshold().label);
268             graphLine.append("\t");
269             graphLine.append(jalview.util.Format.getHexString(row
270                     .getThreshold().colour));
271             graphLine.append(newline);
272           }
273
274           if (row.graphGroup > -1)
275           {
276             graphGroupSeen.set(row.graphGroup);
277             Integer key = new Integer(row.graphGroup);
278             if (graphGroup.containsKey(key))
279             {
280               graphGroup.put(key, graphGroup.get(key) + "\t" + row.label);
281
282             }
283             else
284             {
285               graphGroup_refs.put(key, new Object[] { refSeq, refGroup });
286               graphGroup.put(key, row.label);
287             }
288           }
289         }
290
291         text.append(row.label + "\t");
292         if (row.description != null)
293         {
294           text.append(row.description + "\t");
295         }
296         for (int j = 0; row.annotations != null
297                 && j < row.annotations.length; j++)
298         {
299           if (refSeq != null
300                   && jalview.util.Comparison.isGap(refSeq.getCharAt(j)))
301           {
302             continue;
303           }
304
305           if (row.annotations[j] != null)
306           {
307             comma = "";
308             if (hasGlyphs) // could be also hasGlyphs || ...
309             {
310
311               text.append(comma);
312               if (row.annotations[j].secondaryStructure != ' ')
313               {
314                 // only write out the field if its not whitespace.
315                 text.append(row.annotations[j].secondaryStructure);
316               }
317               comma = ",";
318             }
319             if (hasValues)
320             {
321               if (!Float.isNaN(row.annotations[j].value))
322               {
323                 text.append(comma + row.annotations[j].value);
324               }
325               else
326               {
327                 // System.err.println("Skipping NaN - not valid value.");
328                 text.append(comma + 0f);// row.annotations[j].value);
329               }
330               comma = ",";
331             }
332             if (hasLabels)
333             {
334               // TODO: labels are emitted after values for bar graphs.
335               if // empty labels are allowed, so
336               (row.annotations[j].displayCharacter != null
337                       && row.annotations[j].displayCharacter.length() > 0
338                       && !row.annotations[j].displayCharacter.equals(" "))
339               {
340                 text.append(comma + row.annotations[j].displayCharacter);
341                 comma = ",";
342               }
343             }
344             if (hasText)
345             {
346               if (row.annotations[j].description != null
347                       && row.annotations[j].description.length() > 0
348                       && !row.annotations[j].description
349                               .equals(row.annotations[j].displayCharacter))
350               {
351                 text.append(comma + row.annotations[j].description);
352                 comma = ",";
353               }
354             }
355             if (color != null && !color.equals(row.annotations[j].colour))
356             {
357               oneColour = false;
358             }
359
360             color = row.annotations[j].colour;
361
362             if (row.annotations[j].colour != null
363                     && row.annotations[j].colour != java.awt.Color.black)
364             {
365               text.append(comma
366                       + "["
367                       + jalview.util.Format
368                               .getHexString(row.annotations[j].colour)
369                       + "]");
370               comma = ",";
371             }
372           }
373           text.append("|");
374         }
375
376         if (row.hasScore())
377         {
378           text.append("\t" + row.score);
379         }
380
381         text.append(newline);
382
383         if (color != null && color != java.awt.Color.black && oneColour)
384         {
385           colours.append("COLOUR\t");
386           colours.append(row.label);
387           colours.append("\t");
388           colours.append(jalview.util.Format.getHexString(color));
389           colours.append(newline);
390         }
391         if (row.scaleColLabel || row.showAllColLabels
392                 || row.centreColLabels)
393         {
394           rowprops.append("ROWPROPERTIES\t");
395           rowprops.append(row.label);
396           rowprops.append("\tscaletofit=");
397           rowprops.append(row.scaleColLabel);
398           rowprops.append("\tshowalllabs=");
399           rowprops.append(row.showAllColLabels);
400           rowprops.append("\tcentrelabs=");
401           rowprops.append(row.centreColLabels);
402           rowprops.append(newline);
403         }
404         if (graphLine.length() > 0)
405         {
406           text.append(graphLine.toString());
407           graphLine.setLength(0);
408         }
409       }
410
411       text.append(newline);
412
413       text.append(colours.toString());
414       if (graphGroup.size() > 0)
415       {
416         SequenceI oldRefSeq = refSeq;
417         SequenceGroup oldRefGroup = refGroup;
418         for (Map.Entry<Integer, String> combine_statement : graphGroup
419                 .entrySet())
420         {
421           Object[] seqRefAndGroup = graphGroup_refs.get(combine_statement
422                   .getKey());
423
424           writeSequence_Ref(refSeq, (SequenceI) seqRefAndGroup[0]);
425           refSeq = (SequenceI) seqRefAndGroup[0];
426
427           writeGroup_Ref(refGroup, (SequenceGroup) seqRefAndGroup[1]);
428           refGroup = (SequenceGroup) seqRefAndGroup[1];
429           text.append("COMBINE\t");
430           text.append(combine_statement.getValue());
431           text.append(newline);
432         }
433         writeSequence_Ref(refSeq, oldRefSeq);
434         refSeq = oldRefSeq;
435
436         writeGroup_Ref(refGroup, oldRefGroup);
437         refGroup = oldRefGroup;
438       }
439       text.append(rowprops.toString());
440     }
441
442     if (list != null)
443     {
444       printGroups(list);
445     }
446
447     if (properties != null)
448     {
449       text.append(newline);
450       text.append(newline);
451       text.append("ALIGNMENT");
452       Enumeration en = properties.keys();
453       while (en.hasMoreElements())
454       {
455         String key = en.nextElement().toString();
456         text.append("\t");
457         text.append(key);
458         text.append("=");
459         text.append(properties.get(key));
460       }
461       // TODO: output alignment visualization settings here if required
462       // iterate through one or more views, defining, marking columns and rows
463       // as visible/hidden, and emmitting view properties.
464       // View specific annotation is
465     }
466
467     return text.toString();
468   }
469
470   private Object writeGroup_Ref(SequenceGroup refGroup,
471           SequenceGroup next_refGroup)
472   {
473     if (next_refGroup == null)
474     {
475
476       if (refGroup != null)
477       {
478         text.append(newline);
479         text.append("GROUP_REF\t");
480         text.append("ALIGNMENT");
481         text.append(newline);
482       }
483       return true;
484     }
485     else
486     {
487       if (refGroup == null || refGroup != next_refGroup)
488       {
489         text.append(newline);
490         text.append("GROUP_REF\t");
491         text.append(next_refGroup.getName());
492         text.append(newline);
493         return true;
494       }
495     }
496     return false;
497   }
498
499   private boolean writeSequence_Ref(SequenceI refSeq, SequenceI next_refSeq)
500   {
501
502     if (next_refSeq == null)
503     {
504       if (refSeq != null)
505       {
506         text.append(newline);
507         text.append("SEQUENCE_REF\t");
508         text.append("ALIGNMENT");
509         text.append(newline);
510         return true;
511       }
512     }
513     else
514     {
515       if (refSeq == null || refSeq != next_refSeq)
516       {
517         text.append(newline);
518         text.append("SEQUENCE_REF\t");
519         text.append(next_refSeq.getName());
520         text.append(newline);
521         return true;
522       }
523     }
524     return false;
525   }
526
527   protected void printGroups(List<SequenceGroup> list)
528   {
529     SequenceI seqrep = null;
530     for (SequenceGroup sg : list)
531     {
532       if (!sg.hasSeqrep())
533       {
534         text.append("SEQUENCE_GROUP\t" + sg.getName() + "\t"
535                 + (sg.getStartRes() + 1) + "\t" + (sg.getEndRes() + 1)
536                 + "\t" + "-1\t");
537         seqrep = null;
538       }
539       else
540       {
541         seqrep = sg.getSeqrep();
542         text.append("SEQUENCE_REF\t");
543         text.append(seqrep.getName());
544         text.append(newline);
545         text.append("SEQUENCE_GROUP\t");
546         text.append(sg.getName());
547         text.append("\t");
548         text.append((seqrep.findPosition(sg.getStartRes())));
549         text.append("\t");
550         text.append((seqrep.findPosition(sg.getEndRes())));
551         text.append("\t");
552         text.append("-1\t");
553       }
554       for (int s = 0; s < sg.getSize(); s++)
555       {
556         text.append(sg.getSequenceAt(s).getName());
557         text.append("\t");
558       }
559       text.append(newline);
560       text.append("PROPERTIES\t");
561       text.append(sg.getName());
562       text.append("\t");
563
564       if (sg.getDescription() != null)
565       {
566         text.append("description=");
567         text.append(sg.getDescription());
568         text.append("\t");
569       }
570       if (sg.cs != null)
571       {
572         text.append("colour=");
573         text.append(ColourSchemeProperty.getColourName(sg.cs
574                 .getColourScheme()));
575         text.append("\t");
576         if (sg.cs.getThreshold() != 0)
577         {
578           text.append("pidThreshold=");
579           text.append(sg.cs.getThreshold());
580         }
581         if (sg.cs.conservationApplied())
582         {
583           text.append("consThreshold=");
584           text.append(sg.cs.getConservationInc());
585           text.append("\t");
586         }
587       }
588       text.append("outlineColour=");
589       text.append(jalview.util.Format.getHexString(sg.getOutlineColour()));
590       text.append("\t");
591
592       text.append("displayBoxes=");
593       text.append(sg.getDisplayBoxes());
594       text.append("\t");
595       text.append("displayText=");
596       text.append(sg.getDisplayText());
597       text.append("\t");
598       text.append("colourText=");
599       text.append(sg.getColourText());
600       text.append("\t");
601       text.append("showUnconserved=");
602       text.append(sg.getShowNonconserved());
603       text.append("\t");
604       if (sg.textColour != java.awt.Color.black)
605       {
606         text.append("textCol1=");
607         text.append(jalview.util.Format.getHexString(sg.textColour));
608         text.append("\t");
609       }
610       if (sg.textColour2 != java.awt.Color.white)
611       {
612         text.append("textCol2=");
613         text.append(jalview.util.Format.getHexString(sg.textColour2));
614         text.append("\t");
615       }
616       if (sg.thresholdTextColour != 0)
617       {
618         text.append("textColThreshold=");
619         text.append(sg.thresholdTextColour);
620         text.append("\t");
621       }
622       if (sg.idColour != null)
623       {
624         text.append("idColour=");
625         text.append(jalview.util.Format.getHexString(sg.idColour));
626         text.append("\t");
627       }
628       if (sg.isHidereps())
629       {
630         text.append("hide=true\t");
631       }
632       if (sg.isHideCols())
633       {
634         text.append("hidecols=true\t");
635       }
636       if (seqrep != null)
637       {
638         // terminate the last line and clear the sequence ref for the group
639         text.append(newline);
640         text.append("SEQUENCE_REF");
641       }
642       text.append(newline);
643       text.append(newline);
644
645     }
646   }
647
648   SequenceI refSeq = null;
649
650   String refSeqId = null;
651
652   public boolean annotateAlignmentView(AlignViewportI viewport,
653           String file, DataSourceType protocol)
654   {
655     ColumnSelection colSel = viewport.getColumnSelection();
656     HiddenColumns hidden = viewport.getAlignment().getHiddenColumns();
657     if (colSel == null)
658     {
659       colSel = new ColumnSelection();
660     }
661     if (hidden == null)
662     {
663       hidden = new HiddenColumns();
664     }
665     boolean rslt = readAnnotationFile(viewport.getAlignment(), hidden,
666             file, protocol);
667     if (rslt && (colSel.hasSelectedColumns() || hidden.hasHiddenColumns()))
668     {
669       viewport.setColumnSelection(colSel);
670       viewport.getAlignment().setHiddenColumns(hidden);
671     }
672
673     return rslt;
674   }
675
676   public boolean readAnnotationFile(AlignmentI al, String file,
677           DataSourceType sourceType)
678   {
679     return readAnnotationFile(al, null, file, sourceType);
680   }
681
682   public boolean readAnnotationFile(AlignmentI al, HiddenColumns hidden,
683           String file, DataSourceType sourceType)
684   {
685     BufferedReader in = null;
686     try
687     {
688       if (sourceType == DataSourceType.FILE)
689       {
690         in = new BufferedReader(new FileReader(file));
691       }
692       else if (sourceType == DataSourceType.URL)
693       {
694         URL url = new URL(file);
695         in = new BufferedReader(new InputStreamReader(url.openStream()));
696       }
697       else if (sourceType == DataSourceType.PASTE)
698       {
699         in = new BufferedReader(new StringReader(file));
700       }
701       else if (sourceType == DataSourceType.CLASSLOADER)
702       {
703         java.io.InputStream is = getClass().getResourceAsStream("/" + file);
704         if (is != null)
705         {
706           in = new BufferedReader(new java.io.InputStreamReader(is));
707         }
708       }
709       if (in != null)
710       {
711         return parseAnnotationFrom(al, hidden, in);
712       }
713
714     } catch (Exception ex)
715     {
716       ex.printStackTrace();
717       System.out.println("Problem reading annotation file: " + ex);
718       if (nlinesread > 0)
719       {
720         System.out.println("Last read line " + nlinesread + ": '"
721                 + lastread + "' (first 80 chars) ...");
722       }
723       return false;
724     }
725     return false;
726   }
727
728   long nlinesread = 0;
729
730   String lastread = "";
731
732   private static String GRAPHLINE = "GRAPHLINE", COMBINE = "COMBINE";
733
734   public boolean parseAnnotationFrom(AlignmentI al, HiddenColumns hidden,
735           BufferedReader in) throws Exception
736   {
737     nlinesread = 0;
738     ArrayList<Object[]> combineAnnotation_calls = new ArrayList<>();
739     ArrayList<Object[]> deferredAnnotation_calls = new ArrayList<>();
740     boolean modified = false;
741     String groupRef = null;
742     Hashtable groupRefRows = new Hashtable();
743
744     Hashtable autoAnnots = new Hashtable();
745     {
746       String line, label, description, token;
747       int graphStyle, index;
748       int refSeqIndex = 1;
749       int existingAnnotations = 0;
750       // when true - will add new rows regardless of whether they are duplicate
751       // auto-annotation like consensus or conservation graphs
752       boolean overrideAutoAnnot = false;
753       if (al.getAlignmentAnnotation() != null)
754       {
755         existingAnnotations = al.getAlignmentAnnotation().length;
756         if (existingAnnotations > 0)
757         {
758           AlignmentAnnotation[] aa = al.getAlignmentAnnotation();
759           for (int aai = 0; aai < aa.length; aai++)
760           {
761             if (aa[aai].autoCalculated)
762             {
763               // make a note of the name and description
764               autoAnnots.put(
765                       autoAnnotsKey(aa[aai], aa[aai].sequenceRef,
766                               (aa[aai].groupRef == null ? null
767                                       : aa[aai].groupRef.getName())),
768                       new Integer(1));
769             }
770           }
771         }
772       }
773
774       int alWidth = al.getWidth();
775
776       StringTokenizer st;
777       Annotation[] annotations;
778       AlignmentAnnotation annotation = null;
779
780       // First confirm this is an Annotation file
781       boolean jvAnnotationFile = false;
782       while ((line = in.readLine()) != null)
783       {
784         nlinesread++;
785         lastread = new String(line);
786         if (line.indexOf("#") == 0)
787         {
788           continue;
789         }
790
791         if (line.indexOf("JALVIEW_ANNOTATION") > -1)
792         {
793           jvAnnotationFile = true;
794           break;
795         }
796       }
797
798       if (!jvAnnotationFile)
799       {
800         in.close();
801         return false;
802       }
803
804       while ((line = in.readLine()) != null)
805       {
806         nlinesread++;
807         lastread = new String(line);
808         if (line.indexOf("#") == 0
809                 || line.indexOf("JALVIEW_ANNOTATION") > -1
810                 || line.length() == 0)
811         {
812           continue;
813         }
814
815         st = new StringTokenizer(line, "\t");
816         token = st.nextToken();
817         if (token.equalsIgnoreCase("COLOUR"))
818         {
819           // TODO: use graduated colour def'n here too
820           colourAnnotations(al, st.nextToken(), st.nextToken());
821           modified = true;
822           continue;
823         }
824
825         else if (token.equalsIgnoreCase(COMBINE))
826         {
827           // keep a record of current state and resolve groupRef at end
828           combineAnnotation_calls
829                   .add(new Object[] { st, refSeq, groupRef });
830           modified = true;
831           continue;
832         }
833         else if (token.equalsIgnoreCase("ROWPROPERTIES"))
834         {
835           addRowProperties(al, st);
836           modified = true;
837           continue;
838         }
839         else if (token.equalsIgnoreCase(GRAPHLINE))
840         {
841           // resolve at end
842           deferredAnnotation_calls.add(new Object[] { GRAPHLINE, st,
843               refSeq, groupRef });
844           modified = true;
845           continue;
846         }
847
848         else if (token.equalsIgnoreCase("SEQUENCE_REF"))
849         {
850           if (st.hasMoreTokens())
851           {
852             refSeq = al.findName(refSeqId = st.nextToken());
853             if (refSeq == null)
854             {
855               refSeqId = null;
856             }
857             try
858             {
859               refSeqIndex = Integer.parseInt(st.nextToken());
860               if (refSeqIndex < 1)
861               {
862                 refSeqIndex = 1;
863                 System.out
864                         .println("WARNING: SEQUENCE_REF index must be > 0 in AnnotationFile");
865               }
866             } catch (Exception ex)
867             {
868               refSeqIndex = 1;
869             }
870           }
871           else
872           {
873             refSeq = null;
874             refSeqId = null;
875           }
876           continue;
877         }
878         else if (token.equalsIgnoreCase("GROUP_REF"))
879         {
880           // Group references could be forward or backwards, so they are
881           // resolved after the whole file is read in
882           groupRef = null;
883           if (st.hasMoreTokens())
884           {
885             groupRef = st.nextToken();
886             if (groupRef.length() < 1)
887             {
888               groupRef = null; // empty string
889             }
890             else
891             {
892               if (groupRefRows.get(groupRef) == null)
893               {
894                 groupRefRows.put(groupRef, new Vector());
895               }
896             }
897           }
898           continue;
899         }
900         else if (token.equalsIgnoreCase("SEQUENCE_GROUP"))
901         {
902           addGroup(al, st);
903           modified = true;
904           continue;
905         }
906
907         else if (token.equalsIgnoreCase("PROPERTIES"))
908         {
909           addProperties(al, st);
910           modified = true;
911           continue;
912         }
913
914         else if (token.equalsIgnoreCase("BELOW_ALIGNMENT"))
915         {
916           setBelowAlignment(al, st);
917           modified = true;
918           continue;
919         }
920         else if (token.equalsIgnoreCase("ALIGNMENT"))
921         {
922           addAlignmentDetails(al, st);
923           modified = true;
924           continue;
925         }
926         // else if (token.equalsIgnoreCase("VIEW_DEF"))
927         // {
928         // addOrSetView(al,st);
929         // modified = true;
930         // continue;
931         // }
932         else if (token.equalsIgnoreCase("VIEW_SETREF"))
933         {
934           if (refSeq != null)
935           {
936             al.setSeqrep(refSeq);
937           }
938           modified = true;
939           continue;
940         }
941         else if (token.equalsIgnoreCase("VIEW_HIDECOLS"))
942         {
943           if (st.hasMoreTokens())
944           {
945             if (hidden == null)
946             {
947               hidden = new HiddenColumns();
948             }
949             parseHideCols(hidden, st.nextToken());
950           }
951           modified = true;
952           continue;
953         }
954         else if (token.equalsIgnoreCase("HIDE_INSERTIONS"))
955         {
956           SequenceI sr = refSeq == null ? al.getSeqrep() : refSeq;
957           if (sr == null)
958           {
959             sr = al.getSequenceAt(0);
960           }
961           if (sr != null)
962           {
963             if (hidden == null)
964             {
965               System.err
966                       .println("Cannot process HIDE_INSERTIONS without an alignment view: Ignoring line: "
967                               + line);
968             }
969             else
970             {
971               // consider deferring this till after the file has been parsed ?
972               hidden.hideInsertionsFor(sr);
973             }
974           }
975           modified = true;
976           continue;
977         }
978
979         // Parse out the annotation row
980         graphStyle = AlignmentAnnotation.getGraphValueFromString(token);
981         label = st.nextToken();
982
983         index = 0;
984         annotations = new Annotation[alWidth];
985         description = null;
986         float score = Float.NaN;
987
988         if (st.hasMoreTokens())
989         {
990           line = st.nextToken();
991
992           if (line.indexOf("|") == -1)
993           {
994             description = line;
995             if (st.hasMoreTokens())
996             {
997               line = st.nextToken();
998             }
999           }
1000
1001           if (st.hasMoreTokens())
1002           {
1003             // This must be the score
1004             score = Float.valueOf(st.nextToken()).floatValue();
1005           }
1006
1007           st = new StringTokenizer(line, "|", true);
1008
1009           boolean emptyColumn = true;
1010           boolean onlyOneElement = (st.countTokens() == 1);
1011
1012           while (st.hasMoreElements() && index < alWidth)
1013           {
1014             token = st.nextToken().trim();
1015
1016             if (onlyOneElement)
1017             {
1018               try
1019               {
1020                 score = Float.valueOf(token).floatValue();
1021                 break;
1022               } catch (NumberFormatException ex)
1023               {
1024               }
1025             }
1026
1027             if (token.equals("|"))
1028             {
1029               if (emptyColumn)
1030               {
1031                 index++;
1032               }
1033
1034               emptyColumn = true;
1035             }
1036             else
1037             {
1038               annotations[index++] = parseAnnotation(token, graphStyle);
1039               emptyColumn = false;
1040             }
1041           }
1042
1043         }
1044
1045         annotation = new AlignmentAnnotation(label, description,
1046                 (index == 0) ? null : annotations, 0, 0, graphStyle);
1047
1048         annotation.score = score;
1049         if (!overrideAutoAnnot
1050                 && autoAnnots.containsKey(autoAnnotsKey(annotation, refSeq,
1051                         groupRef)))
1052         {
1053           // skip - we've already got an automatic annotation of this type.
1054           continue;
1055         }
1056         // otherwise add it!
1057         if (refSeq != null)
1058         {
1059
1060           annotation.belowAlignment = false;
1061           // make a copy of refSeq so we can find other matches in the alignment
1062           SequenceI referedSeq = refSeq;
1063           do
1064           {
1065             // copy before we do any mapping business.
1066             // TODO: verify that undo/redo with 1:many sequence associated
1067             // annotations can be undone correctly
1068             AlignmentAnnotation ann = new AlignmentAnnotation(annotation);
1069             annotation
1070                     .createSequenceMapping(referedSeq, refSeqIndex, false);
1071             annotation.adjustForAlignment();
1072             referedSeq.addAlignmentAnnotation(annotation);
1073             al.addAnnotation(annotation);
1074             al.setAnnotationIndex(annotation,
1075                     al.getAlignmentAnnotation().length
1076                             - existingAnnotations - 1);
1077             if (groupRef != null)
1078             {
1079               ((Vector) groupRefRows.get(groupRef)).addElement(annotation);
1080             }
1081             // and recover our virgin copy to use again if necessary.
1082             annotation = ann;
1083
1084           } while (refSeqId != null
1085                   && (referedSeq = al.findName(referedSeq, refSeqId, true)) != null);
1086         }
1087         else
1088         {
1089           al.addAnnotation(annotation);
1090           al.setAnnotationIndex(annotation,
1091                   al.getAlignmentAnnotation().length - existingAnnotations
1092                           - 1);
1093           if (groupRef != null)
1094           {
1095             ((Vector) groupRefRows.get(groupRef)).addElement(annotation);
1096           }
1097         }
1098         // and set modification flag
1099         modified = true;
1100       }
1101       // Resolve the groupRefs
1102       Hashtable<String, SequenceGroup> groupRefLookup = new Hashtable<>();
1103       Enumeration en = groupRefRows.keys();
1104
1105       while (en.hasMoreElements())
1106       {
1107         groupRef = (String) en.nextElement();
1108         boolean matched = false;
1109         // Resolve group: TODO: add a getGroupByName method to alignments
1110         for (SequenceGroup theGroup : al.getGroups())
1111         {
1112           if (theGroup.getName().equals(groupRef))
1113           {
1114             if (matched)
1115             {
1116               // TODO: specify and implement duplication of alignment annotation
1117               // for multiple group references.
1118               System.err
1119                       .println("Ignoring 1:many group reference mappings for group name '"
1120                               + groupRef + "'");
1121             }
1122             else
1123             {
1124               matched = true;
1125               Vector rowset = (Vector) groupRefRows.get(groupRef);
1126               groupRefLookup.put(groupRef, theGroup);
1127               if (rowset != null && rowset.size() > 0)
1128               {
1129                 AlignmentAnnotation alan = null;
1130                 for (int elm = 0, elmSize = rowset.size(); elm < elmSize; elm++)
1131                 {
1132                   alan = (AlignmentAnnotation) rowset.elementAt(elm);
1133                   alan.groupRef = theGroup;
1134                 }
1135               }
1136             }
1137           }
1138         }
1139         ((Vector) groupRefRows.get(groupRef)).removeAllElements();
1140       }
1141       // process any deferred attribute settings for each context
1142       for (Object[] _deferred_args : deferredAnnotation_calls)
1143       {
1144         if (_deferred_args[0] == GRAPHLINE)
1145         {
1146           addLine(al,
1147                   (StringTokenizer) _deferred_args[1], // st
1148                   (SequenceI) _deferred_args[2], // refSeq
1149                   (_deferred_args[3] == null) ? null : groupRefLookup
1150                           .get(_deferred_args[3]) // the reference
1151                                                   // group, or null
1152           );
1153         }
1154       }
1155
1156       // finally, combine all the annotation rows within each context.
1157       /**
1158        * number of combine statements in this annotation file. Used to create
1159        * new groups for combined annotation graphs without disturbing existing
1160        * ones
1161        */
1162       int combinecount = 0;
1163       for (Object[] _combine_args : combineAnnotation_calls)
1164       {
1165         combineAnnotations(al,
1166                 ++combinecount,
1167                 (StringTokenizer) _combine_args[0], // st
1168                 (SequenceI) _combine_args[1], // refSeq
1169                 (_combine_args[2] == null) ? null : groupRefLookup
1170                         .get(_combine_args[2]) // the reference group,
1171                                                // or null
1172         );
1173       }
1174     }
1175     return modified;
1176   }
1177
1178   private void parseHideCols(HiddenColumns hidden, String nextToken)
1179   {
1180     StringTokenizer inval = new StringTokenizer(nextToken, ",");
1181     while (inval.hasMoreTokens())
1182     {
1183       String range = inval.nextToken().trim();
1184       int from, to = range.indexOf("-");
1185       if (to == -1)
1186       {
1187         from = to = Integer.parseInt(range);
1188         if (from >= 0)
1189         {
1190           hidden.hideColumns(from, to);
1191         }
1192       }
1193       else
1194       {
1195         from = Integer.parseInt(range.substring(0, to));
1196         if (to < range.length() - 1)
1197         {
1198           to = Integer.parseInt(range.substring(to + 1));
1199         }
1200         else
1201         {
1202           to = from;
1203         }
1204         if (from > 0 && to >= from)
1205         {
1206           hidden.hideColumns(from, to);
1207         }
1208       }
1209     }
1210   }
1211
1212   private Object autoAnnotsKey(AlignmentAnnotation annotation,
1213           SequenceI refSeq, String groupRef)
1214   {
1215     return annotation.graph + "\t" + annotation.label + "\t"
1216             + annotation.description + "\t"
1217             + (refSeq != null ? refSeq.getDisplayId(true) : "");
1218   }
1219
1220   Annotation parseAnnotation(String string, int graphStyle)
1221   {
1222     // don't do the glyph test if we don't want secondary structure
1223     boolean hasSymbols = (graphStyle == AlignmentAnnotation.NO_GRAPH);
1224     String desc = null, displayChar = null;
1225     char ss = ' '; // secondaryStructure
1226     float value = 0;
1227     boolean parsedValue = false, dcset = false;
1228
1229     // find colour here
1230     Color colour = null;
1231     int i = string.indexOf("[");
1232     int j = string.indexOf("]");
1233     if (i > -1 && j > -1)
1234     {
1235       colour = ColorUtils.parseColourString(string.substring(i + 1,
1236               j));
1237       if (i > 0 && string.charAt(i - 1) == ',')
1238       {
1239         // clip the preceding comma as well
1240         i--;
1241       }
1242       string = string.substring(0, i) + string.substring(j + 1);
1243     }
1244
1245     StringTokenizer st = new StringTokenizer(string, ",", true);
1246     String token;
1247     boolean seenContent = false;
1248     int pass = 0;
1249     while (st.hasMoreTokens())
1250     {
1251       pass++;
1252       token = st.nextToken().trim();
1253       if (token.equals(","))
1254       {
1255         if (!seenContent && parsedValue && !dcset)
1256         {
1257           // allow the value below the bar/line to be empty
1258           dcset = true;
1259           displayChar = " ";
1260         }
1261         seenContent = false;
1262         continue;
1263       }
1264       else
1265       {
1266         seenContent = true;
1267       }
1268
1269       if (!parsedValue)
1270       {
1271         try
1272         {
1273           displayChar = token;
1274           // foo
1275           value = new Float(token).floatValue();
1276           parsedValue = true;
1277           continue;
1278         } catch (NumberFormatException ex)
1279         {
1280         }
1281       }
1282       else
1283       {
1284         if (token.length() == 1)
1285         {
1286           displayChar = token;
1287         }
1288       }
1289       if (hasSymbols
1290               && (token.length() == 1 && "()<>[]{}AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"
1291                       .contains(token)))
1292       {
1293         // Either this character represents a helix or sheet
1294         // or an integer which can be displayed
1295         ss = token.charAt(0);
1296         if (displayChar.equals(token.substring(0, 1)))
1297         {
1298           displayChar = "";
1299         }
1300       }
1301       else if (desc == null || (parsedValue && pass > 2))
1302       {
1303         desc = token;
1304       }
1305
1306     }
1307     // if (!dcset && string.charAt(string.length() - 1) == ',')
1308     // {
1309     // displayChar = " "; // empty display char symbol.
1310     // }
1311     if (displayChar != null && desc != null && desc.length() == 1)
1312     {
1313       if (displayChar.length() > 1)
1314       {
1315         // switch desc and displayChar - legacy support
1316         String tmp = displayChar;
1317         displayChar = desc;
1318         desc = tmp;
1319       }
1320       else
1321       {
1322         if (displayChar.equals(desc))
1323         {
1324           // duplicate label - hangover from the 'robust parser' above
1325           desc = null;
1326         }
1327       }
1328     }
1329     Annotation anot = new Annotation(displayChar, desc, ss, value);
1330
1331     anot.colour = colour;
1332
1333     return anot;
1334   }
1335
1336   void colourAnnotations(AlignmentI al, String label, String colour)
1337   {
1338     Color awtColour = ColorUtils.parseColourString(colour);
1339     Annotation[] annotations;
1340     for (int i = 0; i < al.getAlignmentAnnotation().length; i++)
1341     {
1342       if (al.getAlignmentAnnotation()[i].label.equalsIgnoreCase(label))
1343       {
1344         annotations = al.getAlignmentAnnotation()[i].annotations;
1345         for (int j = 0; j < annotations.length; j++)
1346         {
1347           if (annotations[j] != null)
1348           {
1349             annotations[j].colour = awtColour;
1350           }
1351         }
1352       }
1353     }
1354   }
1355
1356   void combineAnnotations(AlignmentI al, int combineCount,
1357           StringTokenizer st, SequenceI seqRef, SequenceGroup groupRef)
1358   {
1359     String group = st.nextToken();
1360     // First make sure we are not overwriting the graphIndex
1361     int graphGroup = 0;
1362     if (al.getAlignmentAnnotation() != null)
1363     {
1364       for (int i = 0; i < al.getAlignmentAnnotation().length; i++)
1365       {
1366         AlignmentAnnotation aa = al.getAlignmentAnnotation()[i];
1367
1368         if (aa.graphGroup > graphGroup)
1369         {
1370           // try to number graphGroups in order of occurence.
1371           graphGroup = aa.graphGroup + 1;
1372         }
1373         if (aa.sequenceRef == seqRef && aa.groupRef == groupRef
1374                 && aa.label.equalsIgnoreCase(group))
1375         {
1376           if (aa.graphGroup > -1)
1377           {
1378             graphGroup = aa.graphGroup;
1379           }
1380           else
1381           {
1382             if (graphGroup <= combineCount)
1383             {
1384               graphGroup = combineCount + 1;
1385             }
1386             aa.graphGroup = graphGroup;
1387           }
1388           break;
1389         }
1390       }
1391
1392       // Now update groups
1393       while (st.hasMoreTokens())
1394       {
1395         group = st.nextToken();
1396         for (int i = 0; i < al.getAlignmentAnnotation().length; i++)
1397         {
1398           AlignmentAnnotation aa = al.getAlignmentAnnotation()[i];
1399           if (aa.sequenceRef == seqRef && aa.groupRef == groupRef
1400                   && aa.label.equalsIgnoreCase(group))
1401           {
1402             aa.graphGroup = graphGroup;
1403             break;
1404           }
1405         }
1406       }
1407     }
1408     else
1409     {
1410       System.err
1411               .println("Couldn't combine annotations. None are added to alignment yet!");
1412     }
1413   }
1414
1415   void addLine(AlignmentI al, StringTokenizer st, SequenceI seqRef,
1416           SequenceGroup groupRef)
1417   {
1418     String group = st.nextToken();
1419     AlignmentAnnotation[] alannot = al.getAlignmentAnnotation();
1420     String nextToken = st.nextToken();
1421     float value = 0f;
1422     try
1423     {
1424       value = Float.valueOf(nextToken);
1425     } catch (NumberFormatException e)
1426     {
1427       System.err.println("line " + nlinesread + ": Threshold '" + nextToken
1428               + "' invalid, setting to zero");
1429     }
1430     String label = st.hasMoreTokens() ? st.nextToken() : null;
1431     Color colour = null;
1432     if (st.hasMoreTokens())
1433     {
1434       colour = ColorUtils.parseColourString(st.nextToken());
1435     }
1436     if (alannot != null)
1437     {
1438       for (int i = 0; i < alannot.length; i++)
1439       {
1440         if (alannot[i].label.equalsIgnoreCase(group)
1441                 && (seqRef == null || alannot[i].sequenceRef == seqRef)
1442                 && (groupRef == null || alannot[i].groupRef == groupRef))
1443         {
1444           alannot[i].setThreshold(new GraphLine(value, label, colour));
1445         }
1446       }
1447     }
1448   }
1449
1450   void addGroup(AlignmentI al, StringTokenizer st)
1451   {
1452     SequenceGroup sg = new SequenceGroup();
1453     sg.setName(st.nextToken());
1454     String rng = "";
1455     try
1456     {
1457       rng = st.nextToken();
1458       if (rng.length() > 0 && !rng.startsWith("*"))
1459       {
1460         sg.setStartRes(Integer.parseInt(rng) - 1);
1461       }
1462       else
1463       {
1464         sg.setStartRes(0);
1465       }
1466       rng = st.nextToken();
1467       if (rng.length() > 0 && !rng.startsWith("*"))
1468       {
1469         sg.setEndRes(Integer.parseInt(rng) - 1);
1470       }
1471       else
1472       {
1473         sg.setEndRes(al.getWidth() - 1);
1474       }
1475     } catch (Exception e)
1476     {
1477       System.err
1478               .println("Couldn't parse Group Start or End Field as '*' or a valid column or sequence index: '"
1479                       + rng + "' - assuming alignment width for group.");
1480       // assume group is full width
1481       sg.setStartRes(0);
1482       sg.setEndRes(al.getWidth() - 1);
1483     }
1484
1485     String index = st.nextToken();
1486     if (index.equals("-1"))
1487     {
1488       while (st.hasMoreElements())
1489       {
1490         sg.addSequence(al.findName(st.nextToken()), false);
1491       }
1492     }
1493     else
1494     {
1495       StringTokenizer st2 = new StringTokenizer(index, ",");
1496
1497       while (st2.hasMoreTokens())
1498       {
1499         String tmp = st2.nextToken();
1500         if (tmp.equals("*"))
1501         {
1502           for (int i = 0; i < al.getHeight(); i++)
1503           {
1504             sg.addSequence(al.getSequenceAt(i), false);
1505           }
1506         }
1507         else if (tmp.indexOf("-") >= 0)
1508         {
1509           StringTokenizer st3 = new StringTokenizer(tmp, "-");
1510
1511           int start = (Integer.parseInt(st3.nextToken()));
1512           int end = (Integer.parseInt(st3.nextToken()));
1513
1514           if (end > start)
1515           {
1516             for (int i = start; i <= end; i++)
1517             {
1518               sg.addSequence(al.getSequenceAt(i - 1), false);
1519             }
1520           }
1521         }
1522         else
1523         {
1524           sg.addSequence(al.getSequenceAt(Integer.parseInt(tmp) - 1), false);
1525         }
1526       }
1527     }
1528
1529     if (refSeq != null)
1530     {
1531       sg.setStartRes(refSeq.findIndex(sg.getStartRes() + 1) - 1);
1532       sg.setEndRes(refSeq.findIndex(sg.getEndRes() + 1) - 1);
1533       sg.setSeqrep(refSeq);
1534     }
1535
1536     if (sg.getSize() > 0)
1537     {
1538       al.addGroup(sg);
1539     }
1540   }
1541
1542   void addRowProperties(AlignmentI al, StringTokenizer st)
1543   {
1544     String label = st.nextToken(), keyValue, key, value;
1545     boolean scaletofit = false, centerlab = false, showalllabs = false;
1546     while (st.hasMoreTokens())
1547     {
1548       keyValue = st.nextToken();
1549       key = keyValue.substring(0, keyValue.indexOf("="));
1550       value = keyValue.substring(keyValue.indexOf("=") + 1);
1551       if (key.equalsIgnoreCase("scaletofit"))
1552       {
1553         scaletofit = Boolean.valueOf(value).booleanValue();
1554       }
1555       if (key.equalsIgnoreCase("showalllabs"))
1556       {
1557         showalllabs = Boolean.valueOf(value).booleanValue();
1558       }
1559       if (key.equalsIgnoreCase("centrelabs"))
1560       {
1561         centerlab = Boolean.valueOf(value).booleanValue();
1562       }
1563       AlignmentAnnotation[] alr = al.getAlignmentAnnotation();
1564       if (alr != null)
1565       {
1566         for (int i = 0; i < alr.length; i++)
1567         {
1568           if (alr[i].label.equalsIgnoreCase(label))
1569           {
1570             alr[i].centreColLabels = centerlab;
1571             alr[i].scaleColLabel = scaletofit;
1572             alr[i].showAllColLabels = showalllabs;
1573           }
1574         }
1575       }
1576     }
1577   }
1578
1579   void addProperties(AlignmentI al, StringTokenizer st)
1580   {
1581
1582     // So far we have only added groups to the annotationHash,
1583     // the idea is in the future properties can be added to
1584     // alignments, other annotations etc
1585     if (al.getGroups() == null)
1586     {
1587       return;
1588     }
1589
1590     String name = st.nextToken();
1591     SequenceGroup sg = null;
1592     for (SequenceGroup _sg : al.getGroups())
1593     {
1594       if ((sg = _sg).getName().equals(name))
1595       {
1596         break;
1597       }
1598       else
1599       {
1600         sg = null;
1601       }
1602     }
1603
1604     if (sg != null)
1605     {
1606       String keyValue, key, value;
1607       ColourSchemeI def = sg.getColourScheme();
1608       while (st.hasMoreTokens())
1609       {
1610         keyValue = st.nextToken();
1611         key = keyValue.substring(0, keyValue.indexOf("="));
1612         value = keyValue.substring(keyValue.indexOf("=") + 1);
1613
1614         if (key.equalsIgnoreCase("description"))
1615         {
1616           sg.setDescription(value);
1617         }
1618         else if (key.equalsIgnoreCase("colour"))
1619         {
1620           sg.cs.setColourScheme(ColourSchemeProperty
1621                   .getColourScheme(al, value));
1622         }
1623         else if (key.equalsIgnoreCase("pidThreshold"))
1624         {
1625           sg.cs.setThreshold(Integer.parseInt(value), true);
1626
1627         }
1628         else if (key.equalsIgnoreCase("consThreshold"))
1629         {
1630           sg.cs.setConservationInc(Integer.parseInt(value));
1631           Conservation c = new Conservation("Group", sg.getSequences(null),
1632                   sg.getStartRes(), sg.getEndRes() + 1);
1633
1634           c.calculate();
1635           c.verdict(false, 25); // TODO: refer to conservation percent threshold
1636
1637           sg.cs.setConservation(c);
1638
1639         }
1640         else if (key.equalsIgnoreCase("outlineColour"))
1641         {
1642           sg.setOutlineColour(ColorUtils.parseColourString(value));
1643         }
1644         else if (key.equalsIgnoreCase("displayBoxes"))
1645         {
1646           sg.setDisplayBoxes(Boolean.valueOf(value).booleanValue());
1647         }
1648         else if (key.equalsIgnoreCase("showUnconserved"))
1649         {
1650           sg.setShowNonconserved(Boolean.valueOf(value).booleanValue());
1651         }
1652         else if (key.equalsIgnoreCase("displayText"))
1653         {
1654           sg.setDisplayText(Boolean.valueOf(value).booleanValue());
1655         }
1656         else if (key.equalsIgnoreCase("colourText"))
1657         {
1658           sg.setColourText(Boolean.valueOf(value).booleanValue());
1659         }
1660         else if (key.equalsIgnoreCase("textCol1"))
1661         {
1662           sg.textColour = ColorUtils.parseColourString(value);
1663         }
1664         else if (key.equalsIgnoreCase("textCol2"))
1665         {
1666           sg.textColour2 = ColorUtils.parseColourString(value);
1667         }
1668         else if (key.equalsIgnoreCase("textColThreshold"))
1669         {
1670           sg.thresholdTextColour = Integer.parseInt(value);
1671         }
1672         else if (key.equalsIgnoreCase("idColour"))
1673         {
1674           Color idColour = ColorUtils.parseColourString(value);
1675           sg.setIdColour(idColour == null ? Color.black : idColour);
1676         }
1677         else if (key.equalsIgnoreCase("hide"))
1678         {
1679           // see bug https://mantis.lifesci.dundee.ac.uk/view.php?id=25847
1680           sg.setHidereps(true);
1681         }
1682         else if (key.equalsIgnoreCase("hidecols"))
1683         {
1684           // see bug https://mantis.lifesci.dundee.ac.uk/view.php?id=25847
1685           sg.setHideCols(true);
1686         }
1687         sg.recalcConservation();
1688       }
1689       if (sg.getColourScheme() == null)
1690       {
1691         sg.setColourScheme(def);
1692       }
1693     }
1694   }
1695
1696   void setBelowAlignment(AlignmentI al, StringTokenizer st)
1697   {
1698     String token;
1699     AlignmentAnnotation aa, ala[] = al.getAlignmentAnnotation();
1700     if (ala == null)
1701     {
1702       System.err
1703               .print("Warning - no annotation to set below for sequence associated annotation:");
1704     }
1705     while (st.hasMoreTokens())
1706     {
1707       token = st.nextToken();
1708       if (ala == null)
1709       {
1710         System.err.print(" " + token);
1711       }
1712       else
1713       {
1714         for (int i = 0; i < al.getAlignmentAnnotation().length; i++)
1715         {
1716           aa = al.getAlignmentAnnotation()[i];
1717           if (aa.sequenceRef == refSeq && aa.label.equals(token))
1718           {
1719             aa.belowAlignment = true;
1720           }
1721         }
1722       }
1723     }
1724     if (ala == null)
1725     {
1726       System.err.print("\n");
1727     }
1728   }
1729
1730   void addAlignmentDetails(AlignmentI al, StringTokenizer st)
1731   {
1732     String keyValue, key, value;
1733     while (st.hasMoreTokens())
1734     {
1735       keyValue = st.nextToken();
1736       key = keyValue.substring(0, keyValue.indexOf("="));
1737       value = keyValue.substring(keyValue.indexOf("=") + 1);
1738       al.setProperty(key, value);
1739     }
1740   }
1741
1742   /**
1743    * Write annotations as a CSV file of the form 'label, value, value, ...' for
1744    * each row.
1745    * 
1746    * @param annotations
1747    * @return CSV file as a string.
1748    */
1749   public String printCSVAnnotations(AlignmentAnnotation[] annotations)
1750   {
1751     if (annotations == null)
1752     {
1753       return "";
1754     }
1755     StringBuffer sp = new StringBuffer();
1756     for (int i = 0; i < annotations.length; i++)
1757     {
1758       String atos = annotations[i].toString();
1759       int p = 0;
1760       do
1761       {
1762         int cp = atos.indexOf("\n", p);
1763         sp.append(annotations[i].label);
1764         sp.append(",");
1765         if (cp > p)
1766         {
1767           sp.append(atos.substring(p, cp + 1));
1768         }
1769         else
1770         {
1771           sp.append(atos.substring(p));
1772           sp.append(newline);
1773         }
1774         p = cp + 1;
1775       } while (p > 0);
1776     }
1777     return sp.toString();
1778   }
1779
1780   public String printAnnotationsForView(AlignViewportI viewport)
1781   {
1782     return printAnnotations(viewport.isShowAnnotation() ? viewport
1783             .getAlignment().getAlignmentAnnotation() : null, viewport
1784             .getAlignment().getGroups(), viewport.getAlignment()
1785             .getProperties(), viewport.getAlignment().getHiddenColumns(),
1786             viewport.getAlignment(), null);
1787   }
1788
1789   public String printAnnotationsForAlignment(AlignmentI al)
1790   {
1791     return printAnnotations(al.getAlignmentAnnotation(), al.getGroups(),
1792             al.getProperties(), null, al, null);
1793   }
1794 }