JAL-3529 test updated for revised signature of getDBRefs()
[jalview.git] / src / jalview / renderer / AnnotationRenderer.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.renderer;
22
23 import jalview.analysis.AAFrequency;
24 import jalview.analysis.CodingUtils;
25 import jalview.analysis.Rna;
26 import jalview.analysis.StructureFrequency;
27 import jalview.api.AlignViewportI;
28 import jalview.datamodel.AlignmentAnnotation;
29 import jalview.datamodel.Annotation;
30 import jalview.datamodel.ColumnSelection;
31 import jalview.datamodel.HiddenColumns;
32 import jalview.datamodel.ProfilesI;
33 import jalview.schemes.ColourSchemeI;
34 import jalview.schemes.NucleotideColourScheme;
35 import jalview.schemes.ResidueProperties;
36 import jalview.schemes.ZappoColourScheme;
37 import jalview.util.Platform;
38
39 import java.awt.BasicStroke;
40 import java.awt.Color;
41 import java.awt.Font;
42 import java.awt.FontMetrics;
43 import java.awt.Graphics;
44 import java.awt.Graphics2D;
45 import java.awt.Image;
46 import java.awt.geom.AffineTransform;
47 import java.awt.image.ImageObserver;
48 import java.util.BitSet;
49 import java.util.Hashtable;
50
51 public class AnnotationRenderer
52 {
53   private static final int UPPER_TO_LOWER = 'a' - 'A'; // 32
54
55   private static final int CHAR_A = 'A'; // 65
56
57   private static final int CHAR_Z = 'Z'; // 90
58
59   /**
60    * flag indicating if timing and redraw parameter info should be output
61    */
62   private final boolean debugRedraw;
63
64   private int charWidth, endRes, charHeight;
65
66   private boolean validCharWidth, hasHiddenColumns;
67
68   private FontMetrics fm;
69
70   private final boolean USE_FILL_ROUND_RECT = Platform.isAMacAndNotJS();
71
72   boolean av_renderHistogram = true, av_renderProfile = true,
73           av_normaliseProfile = false;
74
75   ResidueShaderI profcolour = null;
76
77   private ColumnSelection columnSelection;
78
79   private HiddenColumns hiddenColumns;
80
81   private ProfilesI hconsensus;
82
83   private Hashtable<String, Object>[] complementConsensus;
84
85   private Hashtable<String, Object>[] hStrucConsensus;
86
87   private boolean av_ignoreGapsConsensus;
88
89   /**
90    * attributes set from AwtRenderPanelI
91    */
92   /**
93    * old image used when data is currently being calculated and cannot be
94    * rendered
95    */
96   private Image fadedImage;
97
98   /**
99    * panel being rendered into
100    */
101   private ImageObserver annotationPanel;
102
103   /**
104    * width of image to render in panel
105    */
106   private int imgWidth;
107
108   /**
109    * offset to beginning of visible area
110    */
111   private int sOffset;
112
113   /**
114    * offset to end of visible area
115    */
116   private int visHeight;
117
118   /**
119    * indicate if the renderer should only render the visible portion of the
120    * annotation given the current view settings
121    */
122   private boolean useClip = true;
123
124   /**
125    * master flag indicating if renderer should ever try to clip. not enabled for
126    * jalview 2.8.1
127    */
128   private boolean canClip = false;
129
130   public AnnotationRenderer()
131   {
132     this(false);
133   }
134
135   /**
136    * Create a new annotation Renderer
137    * 
138    * @param debugRedraw
139    *          flag indicating if timing and redraw parameter info should be
140    *          output
141    */
142   public AnnotationRenderer(boolean debugRedraw)
143   {
144     this.debugRedraw = debugRedraw;
145   }
146
147   /**
148    * Remove any references and resources when this object is no longer required
149    */
150   public void dispose()
151   {
152     hconsensus = null;
153     complementConsensus = null;
154     hStrucConsensus = null;
155     fadedImage = null;
156     annotationPanel = null;
157   }
158
159   void drawStemAnnot(Graphics g, Annotation[] row_annotations, int lastSSX,
160           int x, int y, int iconOffset, int startRes, int column,
161           boolean validRes, boolean validEnd)
162   {
163     g.setColor(STEM_COLOUR);
164     int sCol = (lastSSX / charWidth)
165             + hiddenColumns.visibleToAbsoluteColumn(startRes);
166     int x1 = lastSSX;
167     int x2 = (x * charWidth);
168
169     char dc = (column == 0 || row_annotations[column - 1] == null) ? ' '
170             : row_annotations[column - 1].secondaryStructure;
171
172     boolean diffupstream = sCol == 0 || row_annotations[sCol - 1] == null
173             || dc != row_annotations[sCol - 1].secondaryStructure;
174     boolean diffdownstream = !validRes || !validEnd
175             || row_annotations[column] == null
176             || dc != row_annotations[column].secondaryStructure;
177
178     if (column > 0 && Rna.isClosingParenthesis(dc))
179     {
180       if (diffupstream)
181       // if (validRes && column>1 && row_annotations[column-2]!=null &&
182       // dc.equals(row_annotations[column-2].displayCharacter))
183       {
184         /*
185          * if new annotation with a closing base pair half of the stem, 
186          * display a backward arrow
187          */
188         g.fillPolygon(new int[] { lastSSX + 5, lastSSX + 5, lastSSX },
189                 new int[]
190                 { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset },
191                 3);
192         x1 += 5;
193       }
194       if (diffdownstream)
195       {
196         x2 -= 1;
197       }
198     }
199     else
200     {
201       // display a forward arrow
202       if (diffdownstream)
203       {
204         /*
205          * if annotation ending with an opeing base pair half of the stem, 
206          * display a forward arrow
207          */
208         g.fillPolygon(new int[] { x2 - 5, x2 - 5, x2 },
209                 new int[]
210                 { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset },
211                 3);
212         x2 -= 5;
213       }
214       if (diffupstream)
215       {
216         x1 += 1;
217       }
218     }
219     // draw arrow body
220     g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 7);
221   }
222
223   void drawNotCanonicalAnnot(Graphics g, Color nonCanColor,
224           Annotation[] row_annotations, int lastSSX, int x, int y,
225           int iconOffset, int startRes, int column, boolean validRes,
226           boolean validEnd)
227   {
228     // System.out.println(nonCanColor);
229
230     g.setColor(nonCanColor);
231     int sCol = (lastSSX / charWidth)
232             + hiddenColumns.visibleToAbsoluteColumn(startRes);
233     int x1 = lastSSX;
234     int x2 = (x * charWidth);
235
236     String dc = (column == 0 || row_annotations[column - 1] == null) ? ""
237             : row_annotations[column - 1].displayCharacter;
238
239     boolean diffupstream = sCol == 0 || row_annotations[sCol - 1] == null
240             || !dc.equals(row_annotations[sCol - 1].displayCharacter);
241     boolean diffdownstream = !validRes || !validEnd
242             || row_annotations[column] == null
243             || !dc.equals(row_annotations[column].displayCharacter);
244     // System.out.println("Column "+column+" diff up: "+diffupstream+"
245     // down:"+diffdownstream);
246     // If a closing base pair half of the stem, display a backward arrow
247     if (column > 0 && Rna.isClosingParenthesis(dc))
248     {
249
250       if (diffupstream)
251       // if (validRes && column>1 && row_annotations[column-2]!=null &&
252       // dc.equals(row_annotations[column-2].displayCharacter))
253       {
254         g.fillPolygon(new int[] { lastSSX + 5, lastSSX + 5, lastSSX },
255                 new int[]
256                 { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset },
257                 3);
258         x1 += 5;
259       }
260       if (diffdownstream)
261       {
262         x2 -= 1;
263       }
264     }
265     else
266     {
267
268       // display a forward arrow
269       if (diffdownstream)
270       {
271         g.fillPolygon(new int[] { x2 - 5, x2 - 5, x2 },
272                 new int[]
273                 { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset },
274                 3);
275         x2 -= 5;
276       }
277       if (diffupstream)
278       {
279         x1 += 1;
280       }
281     }
282     // draw arrow body
283     g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 7);
284   }
285
286   // public void updateFromAnnotationPanel(FontMetrics annotFM, AlignViewportI
287   // av)
288   public void updateFromAwtRenderPanel(AwtRenderPanelI annotPanel,
289           AlignViewportI av)
290   {
291     fm = annotPanel.getFontMetrics();
292     annotationPanel = annotPanel;
293     fadedImage = annotPanel.getFadedImage();
294     imgWidth = annotPanel.getFadedImageWidth();
295     // visible area for rendering
296     int[] bounds = annotPanel.getVisibleVRange();
297     if (bounds != null)
298     {
299       sOffset = bounds[0];
300       visHeight = bounds[1];
301       if (visHeight == 0)
302       {
303         useClip = false;
304       }
305       else
306       {
307         useClip = canClip;
308       }
309     }
310     else
311     {
312       useClip = false;
313     }
314
315     updateFromAlignViewport(av);
316   }
317
318   public void updateFromAlignViewport(AlignViewportI av)
319   {
320     charWidth = av.getCharWidth();
321     endRes = av.getRanges().getEndRes();
322     charHeight = av.getCharHeight();
323     hasHiddenColumns = av.hasHiddenColumns();
324     validCharWidth = av.isValidCharWidth();
325     av_renderHistogram = av.isShowConsensusHistogram();
326     av_renderProfile = av.isShowSequenceLogo();
327     av_normaliseProfile = av.isNormaliseSequenceLogo();
328     profcolour = av.getResidueShading();
329     if (profcolour == null || profcolour.getColourScheme() == null)
330     {
331       /*
332        * Use default colour for sequence logo if 
333        * the alignment has no colourscheme set
334        * (would like to use user preference but n/a for applet)
335        */
336       ColourSchemeI col = av.getAlignment().isNucleotide()
337               ? new NucleotideColourScheme()
338               : new ZappoColourScheme();
339       profcolour = new ResidueShader(col);
340     }
341     columnSelection = av.getColumnSelection();
342     hiddenColumns = av.getAlignment().getHiddenColumns();
343     hconsensus = av.getSequenceConsensusHash();
344     complementConsensus = av.getComplementConsensusHash();
345     hStrucConsensus = av.getRnaStructureConsensusHash();
346     av_ignoreGapsConsensus = av.isIgnoreGapsConsensus();
347   }
348
349   /**
350    * Returns profile data; the first element is the profile type, the second is
351    * the number of distinct values, the third the total count, and the remainder
352    * depend on the profile type.
353    * 
354    * @param aa
355    * @param column
356    * @return
357    */
358   int[] getProfileFor(AlignmentAnnotation aa, int column)
359   {
360     // TODO : consider refactoring the global alignment calculation
361     // properties/rendering attributes as a global 'alignment group' which holds
362     // all vis settings for the alignment as a whole rather than a subset
363     //
364     if (aa.autoCalculated && (aa.label.startsWith("Consensus")
365             || aa.label.startsWith("cDNA Consensus")))
366     {
367       boolean forComplement = aa.label.startsWith("cDNA Consensus");
368       if (aa.groupRef != null && aa.groupRef.consensusData != null
369               && aa.groupRef.isShowSequenceLogo())
370       {
371         // TODO? group consensus for cDNA complement
372         return AAFrequency.extractProfile(
373                 aa.groupRef.consensusData.get(column),
374                 aa.groupRef.getIgnoreGapsConsensus());
375       }
376       // TODO extend annotation row to enable dynamic and static profile data to
377       // be stored
378       if (aa.groupRef == null && aa.sequenceRef == null)
379       {
380         if (forComplement)
381         {
382           return AAFrequency.extractCdnaProfile(complementConsensus[column],
383                   av_ignoreGapsConsensus);
384         }
385         else
386         {
387           return AAFrequency.extractProfile(hconsensus.get(column),
388                   av_ignoreGapsConsensus);
389         }
390       }
391     }
392     else
393     {
394       if (aa.autoCalculated && aa.label.startsWith("StrucConsensus"))
395       {
396         // TODO implement group structure consensus
397         /*
398          * if (aa.groupRef != null && aa.groupRef.consensusData != null &&
399          * aa.groupRef.isShowSequenceLogo()) { //TODO check what happens for
400          * group selections return StructureFrequency.extractProfile(
401          * aa.groupRef.consensusData[column], aa.groupRef
402          * .getIgnoreGapsConsensus()); }
403          */
404         // TODO extend annotation row to enable dynamic and static profile data
405         // to
406         // be stored
407         if (aa.groupRef == null && aa.sequenceRef == null
408                 && hStrucConsensus != null
409                 && hStrucConsensus.length > column)
410         {
411           return StructureFrequency.extractProfile(hStrucConsensus[column],
412                   av_ignoreGapsConsensus);
413         }
414       }
415     }
416     return null;
417   }
418
419   /**
420    * Render the annotation rows associated with an alignment.
421    * 
422    * @param annotPanel
423    *          container frame
424    * @param av
425    *          data and view settings to render
426    * @param g
427    *          destination for graphics
428    * @param activeRow
429    *          row where a mouse event occured (or -1)
430    * @param startRes
431    *          first column that will be drawn
432    * @param endRes
433    *          last column that will be drawn
434    * @return true if the fadedImage was used for any alignment annotation rows
435    *         currently being calculated
436    */
437   public boolean drawComponent(AwtRenderPanelI annotPanel,
438           AlignViewportI av, Graphics g, int activeRow, int startRes,
439           int endRes)
440   {
441     long stime = System.currentTimeMillis();
442     boolean usedFaded = false;
443     // NOTES:
444     // AnnotationPanel needs to implement: ImageObserver, access to
445     // AlignViewport
446     updateFromAwtRenderPanel(annotPanel, av);
447     fm = g.getFontMetrics();
448     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
449     // int temp = 0;
450     if (aa == null)
451     {
452       return false;
453     }
454     int x = 0, y = 0;
455     int column = 0;
456     char lastSS;
457     int lastSSX;
458     int iconOffset = 0;
459     boolean validRes = false;
460     boolean validEnd = false;
461     boolean labelAllCols = false;
462 //    boolean centreColLabels;
463 //    boolean centreColLabelsDef = av.isCentreColumnLabels();
464     boolean scaleColLabel = false;
465     final AlignmentAnnotation consensusAnnot = av
466             .getAlignmentConsensusAnnotation();
467     final AlignmentAnnotation structConsensusAnnot = av
468             .getAlignmentStrucConsensusAnnotation();
469     final AlignmentAnnotation complementConsensusAnnot = av
470             .getComplementConsensusAnnotation();
471
472     BitSet graphGroupDrawn = new BitSet();
473     int charOffset = 0; // offset for a label
474     // \u03B2 \u03B1
475     // debug ints
476     int yfrom = 0, f_i = 0, yto = 0, f_to = 0;
477     boolean clipst = false, clipend = false;
478     for (int i = 0; i < aa.length; i++)
479     {
480       AlignmentAnnotation row = aa[i];
481       boolean renderHistogram = true;
482       boolean renderProfile = false;
483       boolean normaliseProfile = false;
484       boolean isRNA = row.isRNA();
485
486       // check if this is a consensus annotation row and set the display
487       // settings appropriately
488       // TODO: generalise this to have render styles for consensus/profile
489       // data
490       if (row.groupRef != null && row == row.groupRef.getConsensus())
491       {
492         renderHistogram = row.groupRef.isShowConsensusHistogram();
493         renderProfile = row.groupRef.isShowSequenceLogo();
494         normaliseProfile = row.groupRef.isNormaliseSequenceLogo();
495       }
496       else if (row == consensusAnnot || row == structConsensusAnnot
497               || row == complementConsensusAnnot)
498       {
499         renderHistogram = av_renderHistogram;
500         renderProfile = av_renderProfile;
501         normaliseProfile = av_normaliseProfile;
502       }
503
504       Annotation[] row_annotations = row.annotations;
505       if (!row.visible)
506       {
507         continue;
508       }
509 //      centreColLabels = row.centreColLabels || centreColLabelsDef;
510       labelAllCols = row.showAllColLabels;
511       scaleColLabel = row.scaleColLabel;
512       lastSS = ' ';
513       lastSSX = 0;
514
515       if (!useClip || ((y - charHeight) < visHeight
516               && (y + row.height + charHeight * 2) >= sOffset))
517       {// if_in_visible_region
518         if (!clipst)
519         {
520           clipst = true;
521           yfrom = y;
522           f_i = i;
523         }
524         yto = y;
525         f_to = i;
526         if (row.graph > 0)
527         {
528           if (row.graphGroup > -1 && graphGroupDrawn.get(row.graphGroup))
529           {
530             continue;
531           }
532
533           // this is so that we draw the characters below the graph
534           y += row.height;
535
536           if (row.hasText)
537           {
538             iconOffset = charHeight - fm.getDescent();
539             y -= charHeight;
540           }
541         }
542         else if (row.hasText)
543         {
544           iconOffset = charHeight - fm.getDescent();
545
546         }
547         else
548         {
549           iconOffset = 0;
550         }
551
552         if (row.autoCalculated && av.isCalculationInProgress(row))
553         {
554           y += charHeight;
555           usedFaded = true;
556           g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0,
557                   y - row.height, imgWidth, y, annotationPanel);
558           g.setColor(Color.black);
559           // g.drawString("Calculating "+aa[i].label+"....",20, y-row.height/2);
560
561           continue;
562         }
563
564         /*
565          * else if (annotationPanel.av.updatingConservation &&
566          * aa[i].label.equals("Conservation")) {
567          * 
568          * y += charHeight; g.drawImage(annotationPanel.fadedImage, 0, y -
569          * row.height, annotationPanel.imgWidth, y, 0, y - row.height,
570          * annotationPanel.imgWidth, y, annotationPanel);
571          * 
572          * g.setColor(Color.black); //
573          * g.drawString("Calculating Conservation.....",20, y-row.height/2);
574          * 
575          * continue; } else if (annotationPanel.av.updatingConservation &&
576          * aa[i].label.equals("Quality")) {
577          * 
578          * y += charHeight; g.drawImage(annotationPanel.fadedImage, 0, y -
579          * row.height, annotationPanel.imgWidth, y, 0, y - row.height,
580          * annotationPanel.imgWidth, y, annotationPanel);
581          * g.setColor(Color.black); // /
582          * g.drawString("Calculating Quality....",20, y-row.height/2);
583          * 
584          * continue; }
585          */
586         // first pass sets up state for drawing continuation from left-hand
587         // column
588         // of startRes
589         x = (startRes == 0) ? 0 : -1;
590         while (x < endRes - startRes)
591         {
592           if (hasHiddenColumns)
593           {
594             column = hiddenColumns.visibleToAbsoluteColumn(startRes + x);
595             if (column > row_annotations.length - 1)
596             {
597               break;
598             }
599           }
600           else
601           {
602             column = startRes + x;
603           }
604
605           if ((row_annotations == null)
606                   || (row_annotations.length <= column)
607                   || (row_annotations[column] == null))
608           {
609             validRes = false;
610           }
611           else
612           {
613             validRes = true;
614           }
615           final String displayChar = validRes
616                   ? row_annotations[column].displayCharacter
617                   : null;
618           if (x > -1)
619           {
620             if (activeRow == i)
621             {
622               g.setColor(Color.red);
623
624               if (columnSelection != null)
625               {
626                 if (columnSelection.contains(column))
627                 {
628                   g.fillRect(x * charWidth, y, charWidth, charHeight);
629                 }
630               }
631             }
632             if (row.getInvalidStrucPos() > x)
633             {
634               g.setColor(Color.orange);
635               g.fillRect(x * charWidth, y, charWidth, charHeight);
636             }
637             else if (row.getInvalidStrucPos() == x)
638             {
639               g.setColor(Color.orange.darker());
640               g.fillRect(x * charWidth, y, charWidth, charHeight);
641             }
642             if (validCharWidth && validRes && displayChar != null
643                     && (displayChar.length() > 0))
644             {
645               Graphics2D gg = ((Graphics2D) g);
646               float fmWidth = fm.charsWidth(displayChar.toCharArray(), 0,
647                       displayChar.length());
648
649               /*
650                * shrink label width to fit in column, if that is
651                * both configured and necessary
652                */
653               boolean scaledToFit = false;
654               float fmScaling = 1f;
655               if (scaleColLabel && fmWidth > charWidth)
656               {
657                 scaledToFit = true;
658                 fmScaling = charWidth;
659                 fmScaling /= fmWidth;
660                 // and update the label's width to reflect the scaling.
661                 fmWidth = charWidth;
662               }
663
664               charOffset = (int) ((charWidth - fmWidth) / 2f);
665
666               if (row_annotations[column].colour == null)
667               {
668                 gg.setColor(Color.black);
669               }
670               else
671               {
672                 gg.setColor(row_annotations[column].colour);
673               }
674
675               /*
676                * draw the label, unless it is the same secondary structure
677                * symbol (excluding RNA Helix) as the previous column
678                */
679               final int xPos = (x * charWidth) + charOffset;
680               final int yPos = y + iconOffset;
681
682               /*
683                * translate to drawing position _before_ applying any scaling
684                */
685               gg.translate(xPos, yPos);
686               if (scaledToFit)
687               {
688                 /*
689                  * use a scaling transform to make the label narrower
690                  * (JalviewJS doesn't have Font.deriveFont(AffineTransform))
691                  */
692                 gg.transform(
693                         AffineTransform.getScaleInstance(fmScaling, 1.0));
694               }
695               if (column == 0 || row.graph > 0)
696               {
697                 gg.drawString(displayChar, 0, 0);
698               }
699               else if (row_annotations[column - 1] == null || (labelAllCols
700                       || !displayChar.equals(
701                               row_annotations[column - 1].displayCharacter)
702                       || (displayChar.length() < 2
703                               && row_annotations[column].secondaryStructure == ' ')))
704               {
705                 gg.drawString(displayChar, 0, 0);
706               }
707               if (scaledToFit)
708               {
709                 /*
710                  * undo scaling before translating back 
711                  * (restoring saved transform does NOT work in JS PDFGraphics!)
712                  */
713                 gg.transform(AffineTransform
714                         .getScaleInstance(1D / fmScaling, 1.0));
715               }
716               gg.translate(-xPos, -yPos);
717             }
718           }
719           if (row.hasIcons)
720           {
721             char ss = validRes ? row_annotations[column].secondaryStructure
722                     : '-';
723
724             if (ss == '(')
725             {
726               // distinguish between forward/backward base-pairing
727               if (displayChar.indexOf(')') > -1)
728               {
729
730                 ss = ')';
731
732               }
733             }
734             if (ss == '[')
735             {
736               if ((displayChar.indexOf(']') > -1))
737               {
738                 ss = ']';
739
740               }
741             }
742             if (ss == '{')
743             {
744               // distinguish between forward/backward base-pairing
745               if (displayChar.indexOf('}') > -1)
746               {
747                 ss = '}';
748
749               }
750             }
751             if (ss == '<')
752             {
753               // distinguish between forward/backward base-pairing
754               if (displayChar.indexOf('<') > -1)
755               {
756                 ss = '>';
757
758               }
759             }
760             if (isRNA && (ss >= CHAR_A) && (ss <= CHAR_Z))
761             {
762               // distinguish between forward/backward base-pairing
763               int ssLowerCase = ss + UPPER_TO_LOWER;
764               // TODO would .equals() be safer here? or charAt(0)?
765               if (displayChar.indexOf(ssLowerCase) > -1)
766               {
767                 ss = (char) ssLowerCase;
768               }
769             }
770
771             if (!validRes || (ss != lastSS))
772             {
773
774               if (x > -1)
775               {
776
777                 // int nb_annot = x - temp;
778                 // System.out.println("\t type :"+lastSS+"\t x :"+x+"\t nbre
779                 // annot :"+nb_annot);
780                 switch (lastSS)
781                 {
782                 case '(': // Stem case for RNA secondary structure
783                 case ')': // and opposite direction
784                   drawStemAnnot(g, row_annotations, lastSSX, x, y,
785                           iconOffset, startRes, column, validRes, validEnd);
786                   // temp = x;
787                   break;
788
789                 case 'H':
790                   if (!isRNA)
791                   {
792                     drawHelixAnnot(g, row_annotations, lastSSX, x, y,
793                             iconOffset, startRes, column, validRes,
794                             validEnd);
795                     break;
796                   }
797                   // no break if isRNA - falls through to drawNotCanonicalAnnot!
798                 case 'E':
799                   if (!isRNA)
800                   {
801                     drawSheetAnnot(g, row_annotations, lastSSX, x, y,
802                             iconOffset, startRes, column, validRes,
803                             validEnd);
804                     break;
805                   }
806                   // no break if isRNA - fall through to drawNotCanonicalAnnot!
807
808                 case '{':
809                 case '}':
810                 case '[':
811                 case ']':
812                 case '>':
813                 case '<':
814                 case 'A':
815                 case 'a':
816                 case 'B':
817                 case 'b':
818                 case 'C':
819                 case 'c':
820                 case 'D':
821                 case 'd':
822                 case 'e':
823                 case 'F':
824                 case 'f':
825                 case 'G':
826                 case 'g':
827                 case 'h':
828                 case 'I':
829                 case 'i':
830                 case 'J':
831                 case 'j':
832                 case 'K':
833                 case 'k':
834                 case 'L':
835                 case 'l':
836                 case 'M':
837                 case 'm':
838                 case 'N':
839                 case 'n':
840                 case 'O':
841                 case 'o':
842                 case 'P':
843                 case 'p':
844                 case 'Q':
845                 case 'q':
846                 case 'R':
847                 case 'r':
848                 case 'S':
849                 case 's':
850                 case 'T':
851                 case 't':
852                 case 'U':
853                 case 'u':
854                 case 'V':
855                 case 'v':
856                 case 'W':
857                 case 'w':
858                 case 'X':
859                 case 'x':
860                 case 'Y':
861                 case 'y':
862                 case 'Z':
863                 case 'z':
864
865                   Color nonCanColor = getNotCanonicalColor(lastSS);
866                   drawNotCanonicalAnnot(g, nonCanColor, row_annotations,
867                           lastSSX, x, y, iconOffset, startRes, column,
868                           validRes, validEnd);
869                   // temp = x;
870                   break;
871                 default:
872                   g.setColor(Color.gray);
873                   g.fillRect(lastSSX, y + 6 + iconOffset,
874                           (x * charWidth) - lastSSX, 2);
875                   // temp = x;
876                   break;
877                 }
878               }
879               if (validRes)
880               {
881                 lastSS = ss;
882               }
883               else
884               {
885                 lastSS = ' ';
886               }
887               if (x > -1)
888               {
889                 lastSSX = (x * charWidth);
890               }
891             }
892           }
893           column++;
894           x++;
895         }
896         if (column >= row_annotations.length)
897         {
898           column = row_annotations.length - 1;
899           validEnd = false;
900         }
901         else
902         {
903           validEnd = true;
904         }
905         if ((row_annotations == null) || (row_annotations.length <= column)
906                 || (row_annotations[column] == null))
907         {
908           validRes = false;
909         }
910         else
911         {
912           validRes = true;
913         }
914         // x ++;
915
916         if (row.hasIcons)
917         {
918           switch (lastSS)
919           {
920
921           case 'H':
922             if (!isRNA)
923             {
924               drawHelixAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
925                       startRes, column, validRes, validEnd);
926               break;
927             }
928             // no break if isRNA - fall through to drawNotCanonicalAnnot!
929
930           case 'E':
931             if (!isRNA)
932             {
933               drawSheetAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
934                       startRes, column, validRes, validEnd);
935               break;
936             }
937             // no break if isRNA - fall through to drawNotCanonicalAnnot!
938
939           case '(':
940           case ')': // Stem case for RNA secondary structure
941
942             drawStemAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
943                     startRes, column, validRes, validEnd);
944
945             break;
946           case '{':
947           case '}':
948           case '[':
949           case ']':
950           case '>':
951           case '<':
952           case 'A':
953           case 'a':
954           case 'B':
955           case 'b':
956           case 'C':
957           case 'c':
958           case 'D':
959           case 'd':
960           case 'e':
961           case 'F':
962           case 'f':
963           case 'G':
964           case 'g':
965           case 'h':
966           case 'I':
967           case 'i':
968           case 'J':
969           case 'j':
970           case 'K':
971           case 'k':
972           case 'L':
973           case 'l':
974           case 'M':
975           case 'm':
976           case 'N':
977           case 'n':
978           case 'O':
979           case 'o':
980           case 'P':
981           case 'p':
982           case 'Q':
983           case 'q':
984           case 'R':
985           case 'r':
986           case 'T':
987           case 't':
988           case 'U':
989           case 'u':
990           case 'V':
991           case 'v':
992           case 'W':
993           case 'w':
994           case 'X':
995           case 'x':
996           case 'Y':
997           case 'y':
998           case 'Z':
999           case 'z':
1000             // System.out.println(lastSS);
1001             Color nonCanColor = getNotCanonicalColor(lastSS);
1002             drawNotCanonicalAnnot(g, nonCanColor, row_annotations, lastSSX,
1003                     x, y, iconOffset, startRes, column, validRes, validEnd);
1004             break;
1005           default:
1006             drawGlyphLine(g, row_annotations, lastSSX, x, y, iconOffset,
1007                     startRes, column, validRes, validEnd);
1008             break;
1009           }
1010         }
1011
1012         if (row.graph > 0 && row.graphHeight > 0)
1013         {
1014           if (row.graph == AlignmentAnnotation.LINE_GRAPH)
1015           {
1016             if (row.graphGroup > -1 && !graphGroupDrawn.get(row.graphGroup))
1017             {
1018               // TODO: JAL-1291 revise rendering model so the graphGroup map is
1019               // computed efficiently for all visible labels
1020               float groupmax = -999999, groupmin = 9999999;
1021               for (int gg = 0; gg < aa.length; gg++)
1022               {
1023                 if (aa[gg].graphGroup != row.graphGroup)
1024                 {
1025                   continue;
1026                 }
1027
1028                 if (aa[gg] != row)
1029                 {
1030                   aa[gg].visible = false;
1031                 }
1032                 if (aa[gg].graphMax > groupmax)
1033                 {
1034                   groupmax = aa[gg].graphMax;
1035                 }
1036                 if (aa[gg].graphMin < groupmin)
1037                 {
1038                   groupmin = aa[gg].graphMin;
1039                 }
1040               }
1041
1042               for (int gg = 0; gg < aa.length; gg++)
1043               {
1044                 if (aa[gg].graphGroup == row.graphGroup)
1045                 {
1046                   drawLineGraph(g, aa[gg], aa[gg].annotations, startRes,
1047                           endRes, y, groupmin, groupmax, row.graphHeight);
1048                 }
1049               }
1050
1051               graphGroupDrawn.set(row.graphGroup);
1052             }
1053             else
1054             {
1055               drawLineGraph(g, row, row_annotations, startRes, endRes, y,
1056                       row.graphMin, row.graphMax, row.graphHeight);
1057             }
1058           }
1059           else if (row.graph == AlignmentAnnotation.BAR_GRAPH)
1060           {
1061             drawBarGraph(g, row, row_annotations, startRes, endRes,
1062                     row.graphMin, row.graphMax, y, renderHistogram,
1063                     renderProfile, normaliseProfile);
1064           }
1065         }
1066       }
1067       else
1068       {
1069         if (clipst && !clipend)
1070         {
1071           clipend = true;
1072         }
1073       } // end if_in_visible_region
1074       if (row.graph > 0 && row.hasText)
1075       {
1076         y += charHeight;
1077       }
1078
1079       if (row.graph == 0)
1080       {
1081         y += aa[i].height;
1082       }
1083     }
1084     if (debugRedraw)
1085     {
1086       if (canClip)
1087       {
1088         if (clipst)
1089         {
1090           System.err.println(
1091                   "Start clip at : " + yfrom + " (index " + f_i + ")");
1092         }
1093         if (clipend)
1094         {
1095           System.err.println(
1096                   "End clip at : " + yto + " (index " + f_to + ")");
1097         }
1098       }
1099       ;
1100       System.err.println("Annotation Rendering time:"
1101               + (System.currentTimeMillis() - stime));
1102     }
1103     ;
1104
1105     return !usedFaded;
1106   }
1107
1108   public static final Color GLYPHLINE_COLOR = Color.gray;
1109
1110   public static final Color SHEET_COLOUR = Color.green;
1111
1112   public static final Color HELIX_COLOUR = Color.red;
1113
1114   public static final Color STEM_COLOUR = Color.blue;
1115
1116   // private Color sdNOTCANONICAL_COLOUR;
1117
1118   void drawGlyphLine(Graphics g, Annotation[] row, int lastSSX, int x,
1119           int y, int iconOffset, int startRes, int column, boolean validRes,
1120           boolean validEnd)
1121   {
1122     g.setColor(GLYPHLINE_COLOR);
1123     g.fillRect(lastSSX, y + 6 + iconOffset, (x * charWidth) - lastSSX, 2);
1124   }
1125
1126   void drawSheetAnnot(Graphics g, Annotation[] row,
1127
1128           int lastSSX, int x, int y, int iconOffset, int startRes,
1129           int column, boolean validRes, boolean validEnd)
1130   {
1131     g.setColor(SHEET_COLOUR);
1132
1133     if (!validEnd || !validRes || row == null || row[column] == null
1134             || row[column].secondaryStructure != 'E')
1135     {
1136       g.fillRect(lastSSX, y + 4 + iconOffset, (x * charWidth) - lastSSX - 4,
1137               7);
1138       g.fillPolygon(
1139               new int[]
1140               { (x * charWidth) - 4, (x * charWidth) - 4, (x * charWidth) },
1141               new int[]
1142               { y + iconOffset, y + 14 + iconOffset, y + 7 + iconOffset },
1143               3);
1144     }
1145     else
1146     {
1147       g.fillRect(lastSSX, y + 4 + iconOffset, (x + 1) * charWidth - lastSSX,
1148               7);
1149     }
1150
1151   }
1152
1153   void drawHelixAnnot(Graphics g, Annotation[] row, int lastSSX, int x,
1154           int y, int iconOffset, int startRes, int column, boolean validRes,
1155           boolean validEnd)
1156   {
1157     g.setColor(HELIX_COLOUR);
1158
1159     int sCol = (lastSSX / charWidth)
1160             + hiddenColumns.visibleToAbsoluteColumn(startRes);
1161     int x1 = lastSSX;
1162     int x2 = (x * charWidth);
1163
1164     if (USE_FILL_ROUND_RECT)
1165     {
1166       int ofs = charWidth / 2;
1167       // Off by 1 offset when drawing rects and ovals
1168       // to offscreen image on the MAC
1169       g.fillRoundRect(lastSSX, y + 4 + iconOffset, x2 - x1, 8, 8, 8);
1170       if (sCol == 0 || row[sCol - 1] == null
1171               || row[sCol - 1].secondaryStructure != 'H')
1172       {
1173       }
1174       else
1175       {
1176         // g.setColor(Color.orange);
1177         g.fillRoundRect(lastSSX, y + 4 + iconOffset, x2 - x1 - ofs + 1, 8,
1178                 0, 0);
1179       }
1180       if (!validRes || row[column] == null
1181               || row[column].secondaryStructure != 'H')
1182       {
1183
1184       }
1185       else
1186       {
1187         // g.setColor(Color.magenta);
1188         g.fillRoundRect(lastSSX + ofs, y + 4 + iconOffset,
1189                 x2 - x1 - ofs + 1, 8, 0, 0);
1190
1191       }
1192
1193       return;
1194     }
1195
1196     if (sCol == 0 || row[sCol - 1] == null
1197             || row[sCol - 1].secondaryStructure != 'H')
1198     {
1199       g.fillArc(lastSSX, y + 4 + iconOffset, charWidth, 8, 90, 180);
1200       x1 += charWidth / 2;
1201     }
1202
1203     if (!validRes || row[column] == null
1204             || row[column].secondaryStructure != 'H')
1205     {
1206       g.fillArc((x * charWidth) - charWidth, y + 4 + iconOffset, charWidth,
1207               8, 270, 180);
1208       x2 -= charWidth / 2;
1209     }
1210
1211     g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 8);
1212   }
1213
1214   void drawLineGraph(Graphics g, AlignmentAnnotation _aa,
1215           Annotation[] aa_annotations, int sRes, int eRes, int y, float min,
1216           float max, int graphHeight)
1217   {
1218     if (sRes > aa_annotations.length)
1219     {
1220       return;
1221     }
1222
1223     int x = 0;
1224
1225     // Adjustment for fastpaint to left
1226     if (eRes < endRes)
1227     {
1228       eRes++;
1229     }
1230
1231     eRes = Math.min(eRes, aa_annotations.length);
1232
1233     if (sRes == 0)
1234     {
1235       x++;
1236     }
1237
1238     int y1 = y, y2 = y;
1239     float range = max - min;
1240
1241     // //Draw origin
1242     if (min < 0)
1243     {
1244       y2 = y - (int) ((0 - min / range) * graphHeight);
1245     }
1246
1247     g.setColor(Color.gray);
1248     g.drawLine(x - charWidth, y2, (eRes - sRes + 1) * charWidth, y2);
1249
1250     eRes = Math.min(eRes, aa_annotations.length);
1251
1252     int column;
1253     int aaMax = aa_annotations.length - 1;
1254
1255     while (x < eRes - sRes)
1256     {
1257       column = sRes + x;
1258       if (hasHiddenColumns)
1259       {
1260         column = hiddenColumns.visibleToAbsoluteColumn(column);
1261       }
1262
1263       if (column > aaMax)
1264       {
1265         break;
1266       }
1267
1268       if (aa_annotations[column] == null
1269               || aa_annotations[column - 1] == null)
1270       {
1271         x++;
1272         continue;
1273       }
1274
1275       if (aa_annotations[column].colour == null)
1276       {
1277         g.setColor(Color.black);
1278       }
1279       else
1280       {
1281         g.setColor(aa_annotations[column].colour);
1282       }
1283
1284       y1 = y - (int) (((aa_annotations[column - 1].value - min) / range)
1285               * graphHeight);
1286       y2 = y - (int) (((aa_annotations[column].value - min) / range)
1287               * graphHeight);
1288
1289       g.drawLine(x * charWidth - charWidth / 2, y1,
1290               x * charWidth + charWidth / 2, y2);
1291       x++;
1292     }
1293
1294     if (_aa.threshold != null)
1295     {
1296       g.setColor(_aa.threshold.colour);
1297       Graphics2D g2 = (Graphics2D) g;
1298       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
1299               BasicStroke.JOIN_ROUND, 3f, new float[]
1300               { 5f, 3f }, 0f));
1301
1302       y2 = (int) (y - ((_aa.threshold.value - min) / range) * graphHeight);
1303       g.drawLine(0, y2, (eRes - sRes) * charWidth, y2);
1304       g2.setStroke(new BasicStroke());
1305     }
1306   }
1307
1308   @SuppressWarnings("unused")
1309   void drawBarGraph(Graphics g, AlignmentAnnotation _aa,
1310           Annotation[] aa_annotations, int sRes, int eRes, float min,
1311           float max, int y, boolean renderHistogram, boolean renderProfile,
1312           boolean normaliseProfile)
1313   {
1314     if (sRes > aa_annotations.length)
1315     {
1316       return;
1317     }
1318     Font ofont = g.getFont();
1319     eRes = Math.min(eRes, aa_annotations.length);
1320
1321     int x = 0, y1 = y, y2 = y;
1322
1323     float range = max - min;
1324
1325     if (min < 0)
1326     {
1327       y2 = y - (int) ((0 - min / (range)) * _aa.graphHeight);
1328     }
1329
1330     g.setColor(Color.gray);
1331
1332     g.drawLine(x, y2, (eRes - sRes) * charWidth, y2);
1333
1334     int column;
1335     int aaMax = aa_annotations.length - 1;
1336     while (x < eRes - sRes)
1337     {
1338       column = sRes + x;
1339       if (hasHiddenColumns)
1340       {
1341         column = hiddenColumns.visibleToAbsoluteColumn(column);
1342       }
1343
1344       if (column > aaMax)
1345       {
1346         break;
1347       }
1348
1349       if (aa_annotations[column] == null)
1350       {
1351         x++;
1352         continue;
1353       }
1354       if (aa_annotations[column].colour == null)
1355       {
1356         g.setColor(Color.black);
1357       }
1358       else
1359       {
1360         g.setColor(aa_annotations[column].colour);
1361       }
1362
1363       y1 = y - (int) (((aa_annotations[column].value - min) / (range))
1364               * _aa.graphHeight);
1365
1366       if (renderHistogram)
1367       {
1368         if (y1 - y2 > 0)
1369         {
1370           g.fillRect(x * charWidth, y2, charWidth, y1 - y2);
1371         }
1372         else
1373         {
1374           g.fillRect(x * charWidth, y1, charWidth, y2 - y1);
1375         }
1376       }
1377       // draw profile if available
1378       if (renderProfile)
1379       {
1380
1381         /*
1382          * {profile type, #values, total count, char1, pct1, char2, pct2...}
1383          */
1384         int profl[] = getProfileFor(_aa, column);
1385
1386         // just try to draw the logo if profl is not null
1387         if (profl != null && profl[2] != 0)
1388         {
1389           boolean isStructureProfile = profl[0] == AlignmentAnnotation.STRUCTURE_PROFILE;
1390           boolean isCdnaProfile = profl[0] == AlignmentAnnotation.CDNA_PROFILE;
1391           float ht = normaliseProfile ? y - _aa.graphHeight : y1;
1392           final double normaliseFactor = normaliseProfile ? _aa.graphHeight
1393                   : (y2 - y1);
1394
1395           /**
1396            * Render a single base for a sequence profile, a base pair for
1397            * structure profile, and a triplet for a cdna profile
1398            */
1399           char[] dc = new char[isStructureProfile ? 2
1400                   : (isCdnaProfile ? 3 : 1)];
1401
1402           // lm is not necessary - we can just use fm - could be off by no more
1403           // than 0.5 px
1404           // LineMetrics lm = g.getFontMetrics(ofont).getLineMetrics("Q", g);
1405           // System.out.println(asc + " " + dec + " " + (asc - lm.getAscent())
1406           // + " " + (dec - lm.getDescent()));
1407
1408           double asc = fm.getAscent();
1409           double dec = fm.getDescent();
1410           double fht = fm.getHeight();
1411
1412           double scale = 1f / (normaliseProfile ? profl[2] : 100f);
1413           // float ofontHeight = 1f / fm.getAscent();// magnify to fill box
1414
1415           /*
1416            * Traverse the character(s)/percentage data in the array
1417            */
1418
1419           float ht2 = ht;
1420
1421           // profl[1] is the number of values in the profile
1422           for (int i = 0, c = 3, last = profl[1]; i < last; i++)
1423           {
1424
1425             String s;
1426             if (isStructureProfile)
1427             {
1428               // todo can we encode a structure pair as an int, like codons?
1429               dc[0] = (char) profl[c++];
1430               dc[1] = (char) profl[c++];
1431               s = new String(dc);
1432             }
1433             else if (isCdnaProfile)
1434             {
1435               CodingUtils.decodeCodon2(profl[c++], dc);
1436               s = new String(dc);
1437             }
1438             else
1439             {
1440               dc[0] = (char) profl[c++];
1441               s = new String(dc);
1442             }
1443             // next profl[] position is profile % for the character(s)
1444
1445             int percent = profl[c++];
1446             if (percent == 0)
1447             {
1448               // failsafe in case a count rounds down to 0%
1449               continue;
1450             }
1451             double newHeight = normaliseFactor * scale * percent;
1452
1453             /*
1454              * Set character colour as per alignment colour scheme; use the
1455              * codon translation if a cDNA profile
1456              */
1457             Color colour = null;
1458             if (isCdnaProfile)
1459             {
1460               final String codonTranslation = ResidueProperties
1461                       .codonTranslate(s);
1462               colour = profcolour.findColour(codonTranslation.charAt(0),
1463                       column, null);
1464             }
1465             else
1466             {
1467               colour = profcolour.findColour(dc[0], column, null);
1468             }
1469             g.setColor(colour == Color.white ? Color.lightGray : colour);
1470
1471             // Debug - render boxes around characters
1472             // g.setColor(Color.red);
1473             // g.drawRect(x*av.charWidth, (int)ht, av.charWidth,
1474             // (int)(scl));
1475             // g.setColor(profcolour.findColour(dc[0]).darker());
1476
1477             double sx = 1f * charWidth / fm.charsWidth(dc, 0, dc.length);
1478             double sy = newHeight / asc;
1479             double newAsc = asc * sy;
1480             double newDec = dec * sy;
1481             // it is not necessary to recalculate lm for the new font.
1482             // note: lm.getBaselineOffsets()[lm.getBaselineIndex()]) must be 0
1483             // by definition. Was:
1484             // int hght = (int) (ht + (newAsc - newDec);
1485             // - lm.getBaselineOffsets()[lm.getBaselineIndex()]));
1486
1487             if (Platform.isJS())
1488             {
1489               /*
1490                * SwingJS does not implement font.deriveFont()
1491                * so use a scaling transform to draw instead,
1492                * this is off by a very small amount
1493                */
1494               final int hght = (int) (ht2 + (newAsc - newDec));
1495               Graphics2D gg = (Graphics2D) g;
1496               int xShift = (int) Math.round(x * charWidth / sx);
1497               int yShift = (int) Math.round(hght / sy);
1498               gg.transform(AffineTransform.getScaleInstance(sx, sy));
1499               gg.drawString(s, xShift, yShift);
1500               gg.transform(
1501                       AffineTransform.getScaleInstance(1D / sx, 1D / sy));
1502               ht2 += newHeight;
1503             }
1504             else
1505             /**
1506              * Java only
1507              * 
1508              * @j2sIgnore
1509              */
1510             {
1511               // Java ('normal') method is to scale the font to fit
1512
1513               final int hght = (int) (ht + (newAsc - newDec));
1514               Font font = ofont
1515                       .deriveFont(AffineTransform.getScaleInstance(sx, sy));
1516               g.setFont(font);
1517               g.drawChars(dc, 0, dc.length, x * charWidth, hght);
1518               ht += newHeight;
1519             }
1520           }
1521         }
1522       }
1523       x++;
1524     }
1525     if (_aa.threshold != null)
1526     {
1527       g.setColor(_aa.threshold.colour);
1528       Graphics2D g2 = (Graphics2D) g;
1529       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
1530               BasicStroke.JOIN_ROUND, 3f, new float[]
1531               { 5f, 3f }, 0f));
1532
1533       y2 = (int) (y
1534               - ((_aa.threshold.value - min) / range) * _aa.graphHeight);
1535       g.drawLine(0, y2, (eRes - sRes) * charWidth, y2);
1536       g2.setStroke(new BasicStroke());
1537     }
1538   }
1539
1540   // used by overview window
1541   public void drawGraph(Graphics g, AlignmentAnnotation _aa,
1542           Annotation[] aa_annotations, int width, int y, int sRes, int eRes)
1543   {
1544     eRes = Math.min(eRes, aa_annotations.length);
1545     g.setColor(Color.white);
1546     g.fillRect(0, 0, width, y);
1547     g.setColor(new Color(0, 0, 180));
1548
1549     int x = 0, height;
1550
1551     for (int j = sRes; j < eRes; j++)
1552     {
1553       if (aa_annotations[j] != null)
1554       {
1555         if (aa_annotations[j].colour == null)
1556         {
1557           g.setColor(Color.black);
1558         }
1559         else
1560         {
1561           g.setColor(aa_annotations[j].colour);
1562         }
1563
1564         height = (int) ((aa_annotations[j].value / _aa.graphMax) * y);
1565         if (height > y)
1566         {
1567           height = y;
1568         }
1569
1570         g.fillRect(x, y - height, charWidth, height);
1571       }
1572       x += charWidth;
1573     }
1574   }
1575
1576   Color getNotCanonicalColor(char lastss)
1577   {
1578     switch (lastss)
1579     {
1580     case '{':
1581     case '}':
1582       return new Color(255, 125, 5);
1583
1584     case '[':
1585     case ']':
1586       return new Color(245, 115, 10);
1587
1588     case '>':
1589     case '<':
1590       return new Color(235, 135, 15);
1591
1592     case 'A':
1593     case 'a':
1594       return new Color(225, 105, 20);
1595
1596     case 'B':
1597     case 'b':
1598       return new Color(215, 145, 30);
1599
1600     case 'C':
1601     case 'c':
1602       return new Color(205, 95, 35);
1603
1604     case 'D':
1605     case 'd':
1606       return new Color(195, 155, 45);
1607
1608     case 'E':
1609     case 'e':
1610       return new Color(185, 85, 55);
1611
1612     case 'F':
1613     case 'f':
1614       return new Color(175, 165, 65);
1615
1616     case 'G':
1617     case 'g':
1618       return new Color(170, 75, 75);
1619
1620     case 'H':
1621     case 'h':
1622       return new Color(160, 175, 85);
1623
1624     case 'I':
1625     case 'i':
1626       return new Color(150, 65, 95);
1627
1628     case 'J':
1629     case 'j':
1630       return new Color(140, 185, 105);
1631
1632     case 'K':
1633     case 'k':
1634       return new Color(130, 55, 110);
1635
1636     case 'L':
1637     case 'l':
1638       return new Color(120, 195, 120);
1639
1640     case 'M':
1641     case 'm':
1642       return new Color(110, 45, 130);
1643
1644     case 'N':
1645     case 'n':
1646       return new Color(100, 205, 140);
1647
1648     case 'O':
1649     case 'o':
1650       return new Color(90, 35, 150);
1651
1652     case 'P':
1653     case 'p':
1654       return new Color(85, 215, 160);
1655
1656     case 'Q':
1657     case 'q':
1658       return new Color(75, 25, 170);
1659
1660     case 'R':
1661     case 'r':
1662       return new Color(65, 225, 180);
1663
1664     case 'S':
1665     case 's':
1666       return new Color(55, 15, 185);
1667
1668     case 'T':
1669     case 't':
1670       return new Color(45, 235, 195);
1671
1672     case 'U':
1673     case 'u':
1674       return new Color(35, 5, 205);
1675
1676     case 'V':
1677     case 'v':
1678       return new Color(25, 245, 215);
1679
1680     case 'W':
1681     case 'w':
1682       return new Color(15, 0, 225);
1683
1684     case 'X':
1685     case 'x':
1686       return new Color(10, 255, 235);
1687
1688     case 'Y':
1689     case 'y':
1690       return new Color(5, 150, 245);
1691
1692     case 'Z':
1693     case 'z':
1694       return new Color(0, 80, 255);
1695
1696     default:
1697       System.out.println("This is not a interaction : " + lastss);
1698       return null;
1699
1700     }
1701   }
1702 }