JAL-882; VARNA crashed when non-standard gaps where in structure; the
[jalview.git] / src / jalview / gui / AnnotationPanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.6)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.gui;
19
20 import java.awt.*;
21 import java.awt.event.*;
22 import java.awt.font.LineMetrics;
23 import java.awt.geom.AffineTransform;
24 import java.awt.image.*;
25 import java.util.Hashtable;
26
27 import javax.swing.*;
28
29 import com.stevesoft.pat.Regex;
30
31 import jalview.analysis.AAFrequency;
32 import jalview.datamodel.*;
33 import jalview.schemes.ColourSchemeI;
34
35 /**
36  * DOCUMENT ME!
37  * 
38  * @author $author$
39  * @version $Revision$
40  */
41 public class AnnotationPanel extends JPanel implements MouseListener,
42         MouseMotionListener, ActionListener, AdjustmentListener
43 {
44   final String HELIX = "Helix";
45
46   final String SHEET = "Sheet";
47
48   /**
49    * For RNA secondary structure "stems" aka helices
50    */
51   final String STEM = "RNA Helix";
52
53   final String LABEL = "Label";
54
55   final String REMOVE = "Remove Annotation";
56
57   final String COLOUR = "Colour";
58
59   final Color HELIX_COLOUR = Color.red.darker();
60
61   final Color SHEET_COLOUR = Color.green.darker().darker();
62
63   final Color STEM_COLOUR = Color.blue.darker();
64
65   /** DOCUMENT ME!! */
66   AlignViewport av;
67
68   AlignmentPanel ap;
69
70   int activeRow = -1;
71
72   BufferedImage image;
73
74   BufferedImage fadedImage;
75
76   Graphics2D gg;
77
78   FontMetrics fm;
79
80   int imgWidth = 0;
81
82   boolean fastPaint = false;
83
84   // Used For mouse Dragging and resizing graphs
85   int graphStretch = -1;
86
87   int graphStretchY = -1;
88
89   int min; // used by mouseDragged to see if user
90
91   int max; // used by mouseDragged to see if user
92
93   boolean mouseDragging = false;
94
95   boolean MAC = false;
96   
97   //for editing cursor
98   int cursorX = 0;
99   int cursorY = 0;
100
101   /**
102    * Creates a new AnnotationPanel object.
103    * 
104    * @param ap
105    *          DOCUMENT ME!
106    */
107   public AnnotationPanel(AlignmentPanel ap)
108   {
109
110     MAC = new jalview.util.Platform().isAMac();
111
112     ToolTipManager.sharedInstance().registerComponent(this);
113     ToolTipManager.sharedInstance().setInitialDelay(0);
114     ToolTipManager.sharedInstance().setDismissDelay(10000);
115     this.ap = ap;
116     av = ap.av;
117     this.setLayout(null);
118     addMouseListener(this);
119     addMouseMotionListener(this);
120     ap.annotationScroller.getVerticalScrollBar()
121             .addAdjustmentListener(this);
122   }
123
124   public AnnotationPanel(AlignViewport av)
125   {
126     this.av = av;
127   }
128
129   /**
130    * DOCUMENT ME!
131    * 
132    * @param evt
133    *          DOCUMENT ME!
134    */
135   public void adjustmentValueChanged(AdjustmentEvent evt)
136   {
137     ap.alabels.setScrollOffset(-evt.getValue());
138   }
139
140   /**
141    * Calculates the height of the annotation displayed in the annotation panel.
142    * Callers should normally call the ap.adjustAnnotationHeight method to ensure
143    * all annotation associated components are updated correctly.
144    * 
145    */
146   public int adjustPanelHeight()
147   {
148     int height=calcPanelHeight();
149     this.setPreferredSize(new Dimension(1, height));
150     if (ap != null)
151     {
152       // revalidate only when the alignment panel is fully constructed
153       ap.validate();
154     }
155
156     return height;
157   }
158
159   /**
160    * calculate the height for visible annotation, revalidating bounds where necessary
161    * ABSTRACT GUI METHOD
162    * @return total height of annotation
163    */
164   public int calcPanelHeight()
165   {
166     // setHeight of panels
167     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
168     int height = 0;
169
170     if (aa != null)
171     {
172       for (int i = 0; i < aa.length; i++)
173       {
174         if (aa[i] == null)
175         {
176           System.err.println("Null annotation row: ignoring.");
177           continue;
178         }
179         if (!aa[i].visible)
180         {
181           continue;
182         }
183
184         aa[i].height = 0;
185
186         if (aa[i].hasText)
187         {
188           aa[i].height += av.charHeight;
189         }
190
191         if (aa[i].hasIcons)
192         {
193           aa[i].height += 16;
194         }
195
196         if (aa[i].graph > 0)
197         {
198           aa[i].height += aa[i].graphHeight;
199         }
200
201         if (aa[i].height == 0)
202         {
203           aa[i].height = 20;
204         }
205
206         height += aa[i].height;
207       }
208     }
209     if (height == 0)
210     {
211       // set minimum
212       height = 20;
213     }
214     return height;
215   }
216
217   /**
218    * DOCUMENT ME!
219    * 
220    * @param evt
221    *          DOCUMENT ME!
222    */
223   public void actionPerformed(ActionEvent evt)
224   {
225     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
226     if (aa == null)
227     {
228       return;
229     }
230     Annotation[] anot = aa[activeRow].annotations;
231
232     if (anot.length < av.getColumnSelection().getMax())
233     {
234       Annotation[] temp = new Annotation[av.getColumnSelection().getMax() + 2];
235       System.arraycopy(anot, 0, temp, 0, anot.length);
236       anot = temp;
237       aa[activeRow].annotations = anot;
238     }
239
240     if (evt.getActionCommand().equals(REMOVE))
241     {
242       for (int i = 0; i < av.getColumnSelection().size(); i++)
243       {
244         anot[av.getColumnSelection().columnAt(i)] = null;
245       }
246     }
247     else if (evt.getActionCommand().equals(LABEL))
248     {
249       String exMesg = collectAnnotVals(anot, av.getColumnSelection(), LABEL);
250       String label = JOptionPane.showInputDialog(this, "Enter label",
251               exMesg);
252
253       if (label == null)
254       {
255         return;
256       }
257
258       if ((label.length() > 0) && !aa[activeRow].hasText)
259       {
260         aa[activeRow].hasText = true;
261       }
262
263       for (int i = 0; i < av.getColumnSelection().size(); i++)
264       {
265         int index = av.getColumnSelection().columnAt(i);
266
267         if (!av.colSel.isVisible(index))
268           continue;
269
270         if (anot[index] == null)
271         {
272           anot[index] = new Annotation(label, "", ' ', 0); // TODO: verify that
273           // null exceptions
274           // aren't raised
275           // elsewhere.
276         }
277         else
278         {
279           anot[index].displayCharacter = label;
280         }
281       }
282     }
283     else if (evt.getActionCommand().equals(COLOUR))
284     {
285       Color col = JColorChooser.showDialog(this,
286               "Choose foreground colour", Color.black);
287
288       for (int i = 0; i < av.getColumnSelection().size(); i++)
289       {
290         int index = av.getColumnSelection().columnAt(i);
291
292         if (!av.colSel.isVisible(index))
293           continue;
294
295         if (anot[index] == null)
296         {
297           anot[index] = new Annotation("", "", ' ', 0);
298         }
299
300         anot[index].colour = col;
301       }
302     }
303     else
304     // HELIX OR SHEET
305     {
306       char type = 0;
307       String symbol = "\u03B1";
308
309       if (evt.getActionCommand().equals(HELIX))
310       {
311         type = 'H';
312       }
313       else if (evt.getActionCommand().equals(SHEET))
314       {
315         type = 'E';
316         symbol = "\u03B2";
317       }
318
319       // Added by LML to color stems
320       else if (evt.getActionCommand().equals(STEM))
321       {
322         type = 'S';
323         symbol = "\u03C3";
324       }
325
326       if (!aa[activeRow].hasIcons)
327       {
328         aa[activeRow].hasIcons = true;
329       }
330
331       String label = JOptionPane.showInputDialog(
332               "Enter a label for the structure?", symbol);
333
334       if (label == null)
335       {
336         return;
337       }
338
339       if ((label.length() > 0) && !aa[activeRow].hasText)
340       {
341         aa[activeRow].hasText = true;
342       }
343
344       for (int i = 0; i < av.getColumnSelection().size(); i++)
345       {
346         int index = av.getColumnSelection().columnAt(i);
347
348         if (!av.colSel.isVisible(index))
349           continue;
350
351         if (anot[index] == null)
352         {
353           anot[index] = new Annotation(label, "", type, 0);
354         }
355
356         anot[index].secondaryStructure = type;
357         anot[index].displayCharacter = label;
358       }
359     }
360
361     adjustPanelHeight();
362     repaint();
363
364     return;
365   }
366
367   private String collectAnnotVals(Annotation[] anot,
368           ColumnSelection columnSelection, String label2)
369   {
370     String collatedInput = "";
371     String last = "";
372     for (int i = 0; i < columnSelection.size(); i++)
373     {
374       int index = columnSelection.columnAt(i);
375       // always check for current display state - just in case
376       if (!av.colSel.isVisible(index))
377         continue;
378       String tlabel = null;
379       if (anot[index] != null)
380       { // LML added stem code
381         if (label2.equals(HELIX) || label2.equals(SHEET)
382                 || label2.equals(STEM) || label2.equals(LABEL))
383         {
384           tlabel = anot[index].description;
385           if (tlabel == null || tlabel.length() < 1)
386           {
387             if (label2.equals(HELIX) || label2.equals(SHEET)
388                     || label2.equals(STEM))
389             {
390               tlabel = "" + anot[index].secondaryStructure;
391             }
392             else
393             {
394               tlabel = "" + anot[index].displayCharacter;
395             }
396           }
397         }
398         if (tlabel != null && !tlabel.equals(last))
399         {
400           if (last.length() > 0)
401           {
402             collatedInput += " ";
403           }
404           collatedInput += tlabel;
405         }
406       }
407     }
408     return collatedInput;
409   }
410
411   /**
412    * DOCUMENT ME!
413    * 
414    * @param evt
415    *          DOCUMENT ME!
416    */
417   public void mousePressed(MouseEvent evt)
418   {
419
420     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
421     if (aa == null)
422     {
423       return;
424     }
425
426     int height = 0;
427     activeRow = -1;
428
429     for (int i = 0; i < aa.length; i++)
430     {
431       if (aa[i].visible)
432       {
433         height += aa[i].height;
434       }
435
436       if (evt.getY() < height)
437       {
438         if (aa[i].editable)
439         {
440           activeRow = i;
441         }
442         else if (aa[i].graph > 0)
443         {
444           // Stretch Graph
445           graphStretch = i;
446           graphStretchY = evt.getY();
447         }
448
449         break;
450       }
451     }
452
453     if (SwingUtilities.isRightMouseButton(evt) && activeRow != -1)
454     {
455       if (av.getColumnSelection() == null)
456       {
457         return;
458       }
459
460       JPopupMenu pop = new JPopupMenu("Structure type");
461       JMenuItem item;
462       /*
463        * Just display the needed structure options
464        */
465       if(av.alignment.isNucleotide()==true){
466           item = new JMenuItem(STEM);
467           item.addActionListener(this);
468           pop.add(item); 
469       }else{
470           item = new JMenuItem(HELIX);
471           item.addActionListener(this);
472           pop.add(item);
473           item = new JMenuItem(SHEET);
474           item.addActionListener(this);
475           pop.add(item);          
476       }
477       item = new JMenuItem(LABEL);
478       item.addActionListener(this);
479       pop.add(item);
480       item = new JMenuItem(COLOUR);
481       item.addActionListener(this);
482       pop.add(item);
483       item = new JMenuItem(REMOVE);
484       item.addActionListener(this);
485       pop.add(item);
486       pop.show(this, evt.getX(), evt.getY());
487
488       return;
489     }
490
491     if (aa == null)
492     {
493       return;
494     }
495
496     ap.scalePanel.mousePressed(evt);
497
498   }
499
500   /**
501    * DOCUMENT ME!
502    * 
503    * @param evt
504    *          DOCUMENT ME!
505    */
506   public void mouseReleased(MouseEvent evt)
507   {
508     graphStretch = -1;
509     graphStretchY = -1;
510     mouseDragging = false;
511     ap.scalePanel.mouseReleased(evt);
512   }
513
514   /**
515    * DOCUMENT ME!
516    * 
517    * @param evt
518    *          DOCUMENT ME!
519    */
520   public void mouseEntered(MouseEvent evt)
521   {
522     ap.scalePanel.mouseEntered(evt);
523   }
524
525   /**
526    * DOCUMENT ME!
527    * 
528    * @param evt
529    *          DOCUMENT ME!
530    */
531   public void mouseExited(MouseEvent evt)
532   {
533     ap.scalePanel.mouseExited(evt);
534   }
535
536   /**
537    * DOCUMENT ME!
538    * 
539    * @param evt
540    *          DOCUMENT ME!
541    */
542   public void mouseDragged(MouseEvent evt)
543   {
544     if (graphStretch > -1)
545     {
546       av.alignment.getAlignmentAnnotation()[graphStretch].graphHeight += graphStretchY
547               - evt.getY();
548       if (av.alignment.getAlignmentAnnotation()[graphStretch].graphHeight < 0)
549       {
550         av.alignment.getAlignmentAnnotation()[graphStretch].graphHeight = 0;
551       }
552       graphStretchY = evt.getY();
553       adjustPanelHeight();
554       ap.paintAlignment(true);
555     }
556     else
557     {
558       ap.scalePanel.mouseDragged(evt);
559     }
560   }
561
562   /**
563    * DOCUMENT ME!
564    * 
565    * @param evt
566    *          DOCUMENT ME!
567    */
568   public void mouseMoved(MouseEvent evt)
569   {
570     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
571
572     if (aa == null)
573     {
574       this.setToolTipText(null);
575       return;
576     }
577
578     int row = -1;
579     int height = 0;
580
581     for (int i = 0; i < aa.length; i++)
582     {
583       if (aa[i].visible)
584       {
585         height += aa[i].height;
586       }
587
588       if (evt.getY() < height)
589       {
590         row = i;
591
592         break;
593       }
594     }
595
596     if (row == -1)
597     {
598       this.setToolTipText(null);
599       return;
600     }
601
602     int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();
603
604     if (av.hasHiddenColumns)
605     {
606       res = av.getColumnSelection().adjustForHiddenColumns(res);
607     }
608
609     if (row > -1 && aa[row].annotations != null
610             && res < (int) aa[row].annotations.length)
611     {
612       if (aa[row].graphGroup > -1)
613       {
614         StringBuffer tip = new StringBuffer("<html>");
615         for (int gg = 0; gg < aa.length; gg++)
616         {
617           if (aa[gg].graphGroup == aa[row].graphGroup
618                   && aa[gg].annotations[res] != null)
619           {
620             tip.append(aa[gg].label + " "
621                     + aa[gg].annotations[res].description + "<br>");
622           }
623         }
624         if (tip.length() != 6)
625         {
626           tip.setLength(tip.length() - 4);
627           this.setToolTipText(tip.toString() + "</html>");
628         }
629       }
630       else if (aa[row].annotations[res] != null
631               && aa[row].annotations[res].description != null
632               && aa[row].annotations[res].description.length() > 0)
633       {
634         this.setToolTipText(aa[row].annotations[res].description);
635       }
636       else
637       {
638         // clear the tooltip.
639         this.setToolTipText(null);
640       }
641
642       if (aa[row].annotations[res] != null)
643       {
644         StringBuffer text = new StringBuffer("Sequence position "
645                 + (res + 1));
646
647         if (aa[row].annotations[res].description != null)
648         {
649           text.append("  " + aa[row].annotations[res].description);
650         }
651
652         ap.alignFrame.statusBar.setText(text.toString());
653       }
654     }
655     else
656     {
657       this.setToolTipText(null);
658     }
659   }
660
661   /**
662    * DOCUMENT ME!
663    * 
664    * @param evt
665    *          DOCUMENT ME!
666    */
667   public void mouseClicked(MouseEvent evt)
668   {
669           if(activeRow !=-1){
670             AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();              
671             AlignmentAnnotation anot = aa[activeRow];
672             
673             if(anot.description.equals("secondary structure")){
674                 System.out.println(anot.description+" "+anot.getRNAStruc());       
675             }
676           }
677   }
678           
679           public void drawCursor(Graphics graphics, SequenceI seq, int res, int x1, int y1)
680           {
681             int pady = av.charHeight / 5;
682             int charOffset = 0;
683             graphics.setColor(Color.black);
684             graphics.fillRect(x1, y1, av.charWidth, av.charHeight);
685
686             if (av.validCharWidth)
687             {
688               graphics.setColor(Color.white);
689
690               char s = seq.getCharAt(res);
691
692               charOffset = (av.charWidth - fm.charWidth(s)) / 2;
693               graphics.drawString(String.valueOf(s), charOffset + x1,
694                       (y1 + av.charHeight) - pady);
695             }
696
697           }
698
699   /**
700    * DOCUMENT ME!
701    * 
702    * @param g
703    *          DOCUMENT ME!
704    */
705   public void paintComponent(Graphics g)
706   {
707     g.setColor(Color.white);
708     g.fillRect(0, 0, getWidth(), getHeight());
709
710     if (image != null)
711     {
712       if (fastPaint || (getVisibleRect().width != g.getClipBounds().width)
713               || (getVisibleRect().height != g.getClipBounds().height))
714       {
715         g.drawImage(image, 0, 0, this);
716         fastPaint = false;
717         return;
718       }
719     }
720     imgWidth = (av.endRes - av.startRes + 1) * av.charWidth;
721     if (imgWidth < 1)
722       return;
723     if (image == null || imgWidth != image.getWidth()
724             || image.getHeight(this) != getHeight())
725     {
726       image = new BufferedImage(imgWidth, ap.annotationPanel.getHeight(),
727               BufferedImage.TYPE_INT_RGB);
728       gg = (Graphics2D) image.getGraphics();
729
730       if (av.antiAlias)
731       {
732         gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
733                 RenderingHints.VALUE_ANTIALIAS_ON);
734       }
735
736       gg.setFont(av.getFont());
737       fm = gg.getFontMetrics();
738       gg.setColor(Color.white);
739       gg.fillRect(0, 0, imgWidth, image.getHeight());
740     }
741
742     drawComponent(gg, av.startRes, av.endRes + 1);
743     g.drawImage(image, 0, 0, this);
744   }
745
746   /**
747    * non-Thread safe repaint
748    * 
749    * @param horizontal
750    *          repaint with horizontal shift in alignment
751    */
752   public void fastPaint(int horizontal)
753   {
754
755     if ((horizontal == 0) || gg == null
756             || av.alignment.getAlignmentAnnotation() == null
757             || av.alignment.getAlignmentAnnotation().length < 1
758             || av.updatingConsensus || av.updatingConservation)
759     {
760       repaint();
761       return;
762     }
763     gg.copyArea(0, 0, imgWidth, getHeight(), -horizontal * av.charWidth, 0);
764
765     int sr = av.startRes;
766     int er = av.endRes + 1;
767     int transX = 0;
768
769     if (horizontal > 0) // scrollbar pulled right, image to the left
770     {
771       transX = (er - sr - horizontal) * av.charWidth;
772       sr = er - horizontal;
773     }
774     else if (horizontal < 0)
775     {
776       er = sr - horizontal;
777     }
778
779     gg.translate(transX, 0);
780
781     drawComponent(gg, sr, er);
782
783     gg.translate(-transX, 0);
784
785     fastPaint = true;
786     repaint();
787
788   }
789
790   /**
791    * DOCUMENT ME!
792    * 
793    * @param g
794    *          DOCUMENT ME!
795    * @param startRes
796    *          DOCUMENT ME!
797    * @param endRes
798    *          DOCUMENT ME!
799    */
800   public void drawComponent(Graphics g, int startRes, int endRes)
801   {
802     if (av.updatingConsensus || av.updatingConservation)
803     {
804       if (image == null)
805       {
806         return;
807       }
808       // We'll keep a record of the old image,
809       // and draw a faded image until the calculation
810       // has completed
811       if (fadedImage == null || fadedImage.getWidth() != imgWidth
812               || fadedImage.getHeight() != image.getHeight())
813       {
814         fadedImage = new BufferedImage(imgWidth, image.getHeight(),
815                 BufferedImage.TYPE_INT_RGB);
816
817         Graphics2D fadedG = (Graphics2D) fadedImage.getGraphics();
818
819         fadedG.setColor(Color.white);
820         fadedG.fillRect(0, 0, imgWidth, image.getHeight());
821
822         fadedG.setComposite(AlphaComposite.getInstance(
823                 AlphaComposite.SRC_OVER, .3f));
824         fadedG.drawImage(image, 0, 0, this);
825
826       }
827
828     }
829     else
830     {
831       fadedImage = null;
832     }
833
834     g.setColor(Color.white);
835     g.fillRect(0, 0, (endRes - startRes) * av.charWidth, getHeight());
836
837     g.setFont(av.getFont());
838     if (fm == null)
839     {
840       fm = g.getFontMetrics();
841     }
842
843     if ((av.alignment.getAlignmentAnnotation() == null)
844             || (av.alignment.getAlignmentAnnotation().length < 1))
845     {
846       g.setColor(Color.white);
847       g.fillRect(0, 0, getWidth(), getHeight());
848       g.setColor(Color.black);
849       if (av.validCharWidth)
850       {
851         g.drawString("Alignment has no annotations", 20, 15);
852       }
853
854       return;
855     }
856
857     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
858
859     int x = 0, y = 0;
860     int column = 0;
861     char lastSS;
862     int lastSSX;
863     int iconOffset = 0;
864     boolean validRes = false;
865     boolean validEnd = false;
866     boolean labelAllCols = false;
867     boolean centreColLabels, centreColLabelsDef = av
868             .getCentreColumnLabels();
869     boolean scaleColLabel = false;
870     boolean[] graphGroupDrawn = new boolean[aa.length];
871     int charOffset = 0; // offset for a label
872     float fmWidth, fmScaling = 1f; // scaling for a label to fit it into a
873                                    // column.
874     Font ofont = g.getFont();
875     // \u03B2 \u03B1
876     for (int i = 0; i < aa.length; i++)
877     {
878       AlignmentAnnotation row = aa[i];
879
880       if (!row.visible)
881       {
882         continue;
883       }
884       centreColLabels = row.centreColLabels || centreColLabelsDef;
885       labelAllCols = row.showAllColLabels;
886       scaleColLabel = row.scaleColLabel;
887       lastSS = ' ';
888       lastSSX = 0;
889
890       if (row.graph > 0)
891       {
892         if (row.graphGroup > -1 && graphGroupDrawn[row.graphGroup])
893         {
894           continue;
895         }
896
897         // this is so that we draw the characters below the graph
898         y += row.height;
899
900         if (row.hasText)
901         {
902           iconOffset = av.charHeight - fm.getDescent();
903           y -= av.charHeight;
904         }
905       }
906       else if (row.hasText)
907       {
908         iconOffset = av.charHeight - fm.getDescent();
909
910       }
911       else
912       {
913         iconOffset = 0;
914       }
915
916       if (av.updatingConsensus && aa[i] == av.consensus)
917       {
918         y += av.charHeight;
919
920         g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0, y
921                 - row.height, imgWidth, y, this);
922         g.setColor(Color.black);
923         // g.drawString("Calculating Consensus....",20, y-row.height/2);
924
925         continue;
926       }
927       else if (av.updatingConservation
928               && aa[i].label.equals("Conservation"))
929       {
930
931         y += av.charHeight;
932         g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0, y
933                 - row.height, imgWidth, y, this);
934
935         g.setColor(Color.black);
936         // g.drawString("Calculating Conservation.....",20, y-row.height/2);
937
938         continue;
939       }
940       else if (av.updatingConservation && aa[i].label.equals("Quality"))
941       {
942
943         y += av.charHeight;
944         g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0, y
945                 - row.height, imgWidth, y, this);
946         g.setColor(Color.black);
947         // / g.drawString("Calculating Quality....",20, y-row.height/2);
948
949         continue;
950       }
951
952       x = 0;
953       while (x < endRes - startRes)
954       {
955         if (av.hasHiddenColumns)
956         {
957           column = av.getColumnSelection().adjustForHiddenColumns(
958                   startRes + x);
959           if (column > row.annotations.length - 1)
960           {
961             break;
962           }
963         }
964         else
965         {
966           column = startRes + x;
967         }
968
969         if ((row.annotations == null) || (row.annotations.length <= column)
970                 || (row.annotations[column] == null))
971         {
972           validRes = false;
973         }
974         else
975         {
976           validRes = true;
977         }
978
979         if (activeRow == i)
980         {
981           g.setColor(Color.red);
982
983           if (av.getColumnSelection() != null)
984           {
985             for (int n = 0; n < av.getColumnSelection().size(); n++)
986             {
987               int v = av.getColumnSelection().columnAt(n);
988
989               if (v == column)
990               {
991                 g.fillRect(x * av.charWidth, y, av.charWidth, av.charHeight);
992               }
993             }
994           }
995         }
996
997         if (av.validCharWidth && validRes
998                 && row.annotations[column].displayCharacter != null
999                 && (row.annotations[column].displayCharacter.length() > 0))
1000         {
1001
1002           if (centreColLabels || scaleColLabel)
1003           {
1004             fmWidth = (float) fm.charsWidth(
1005                     row.annotations[column].displayCharacter.toCharArray(),
1006                     0, row.annotations[column].displayCharacter.length());
1007
1008             if (scaleColLabel)
1009             {
1010               // justify the label and scale to fit in column
1011               if (fmWidth > av.charWidth)
1012               {
1013                 // scale only if the current font isn't already small enough
1014                 fmScaling = av.charWidth;
1015                 fmScaling /= fmWidth;
1016                 g.setFont(ofont.deriveFont(AffineTransform
1017                         .getScaleInstance(fmScaling, 1.0)));
1018                 // and update the label's width to reflect the scaling.
1019                 fmWidth = av.charWidth;
1020               }
1021             }
1022           }
1023           else
1024           {
1025             fmWidth = (float) fm
1026                     .charWidth(row.annotations[column].displayCharacter
1027                             .charAt(0));
1028           }
1029           charOffset = (int) ((av.charWidth - fmWidth) / 2f);
1030
1031           if (row.annotations[column].colour == null)
1032             g.setColor(Color.black);
1033           else
1034             g.setColor(row.annotations[column].colour);
1035
1036           if (column == 0 || row.graph > 0)
1037           {
1038             g.drawString(row.annotations[column].displayCharacter,
1039                     (x * av.charWidth) + charOffset, y + iconOffset);
1040           }
1041           else if (row.annotations[column - 1] == null
1042                   || (labelAllCols
1043                           || !row.annotations[column].displayCharacter
1044                                   .equals(row.annotations[column - 1].displayCharacter) || (row.annotations[column].displayCharacter
1045                           .length() < 2 && row.annotations[column].secondaryStructure == ' ')))
1046           {
1047             g.drawString(row.annotations[column].displayCharacter, x
1048                     * av.charWidth + charOffset, y + iconOffset);
1049           }
1050           g.setFont(ofont);
1051         }
1052
1053         if (row.hasIcons)
1054         {
1055           if (!validRes
1056                   || (row.annotations[column].secondaryStructure != lastSS))
1057           {
1058             switch (lastSS)
1059             {
1060             case 'H':
1061               g.setColor(HELIX_COLOUR);
1062               if (MAC)
1063               {
1064                 // Off by 1 offset when drawing rects and ovals
1065                 // to offscreen image on the MAC
1066                 g.fillRoundRect(lastSSX, y + 4 + iconOffset,
1067                         (x * av.charWidth) - lastSSX, 7, 8, 8);
1068                 break;
1069               }
1070
1071               int sCol = (lastSSX / av.charWidth) + startRes;
1072               int x1 = lastSSX;
1073               int x2 = (x * av.charWidth);
1074
1075               if (sCol == 0
1076                       || row.annotations[sCol - 1] == null
1077                       || row.annotations[sCol - 1].secondaryStructure != 'H')
1078               {
1079                 g.fillArc(lastSSX, y + 4 + iconOffset, av.charWidth, 8, 90,
1080                         180);
1081                 x1 += av.charWidth / 2;
1082               }
1083
1084               if (!validRes || row.annotations[column] == null
1085                       || row.annotations[column].secondaryStructure != 'H')
1086               {
1087                 g.fillArc((x * av.charWidth) - av.charWidth, y + 4
1088                         + iconOffset, av.charWidth, 8, 270, 180);
1089                 x2 -= av.charWidth / 2;
1090               }
1091
1092               g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 8);
1093               break;
1094
1095             case 'E':
1096               g.setColor(SHEET_COLOUR);
1097               g.fillRect(lastSSX, y + 4 + iconOffset, (x * av.charWidth)
1098                       - lastSSX - 4, 7);
1099               g.fillPolygon(new int[]
1100               { (x * av.charWidth) - 4, (x * av.charWidth) - 4,
1101                   (x * av.charWidth) }, new int[]
1102               { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset },
1103                       3);
1104
1105               break;
1106
1107             case 'S': // Stem case for RNA secondary structure
1108               g.setColor(STEM_COLOUR); // row.annotations[column].colour for By
1109                                        // RNA Helices colouring
1110               // System.out.println("last SSX displayx" +
1111               // row.annotations[column-1].displayCharacter +"x");
1112               Regex closeparen = new Regex("(\\))");
1113               
1114               //If a closing base pair half of the stem, display a backward arrow
1115               if (closeparen
1116                       .search(row.annotations[column - 1].displayCharacter))// row.annotations[column-1].displayCharacter))
1117               {
1118                 g.fillPolygon(new int[]
1119                 { lastSSX + 5, lastSSX + 5, lastSSX },
1120                         new int[]
1121                         { y + iconOffset, y + 14 + iconOffset,
1122                             y + 8 + iconOffset }, 3);
1123                 g.fillRect(lastSSX + 2, y + 4 + iconOffset,
1124                         (x * av.charWidth) - lastSSX - 2, 7);
1125               }
1126               else //display a forward arrow
1127               {
1128                 g.fillRect(lastSSX, y + 4 + iconOffset, (x * av.charWidth)
1129                         - lastSSX - 4, 7);
1130                 g.fillPolygon(new int[]
1131                 { (x * av.charWidth) - 5, (x * av.charWidth) - 5,
1132                     (x * av.charWidth) },
1133                         new int[]
1134                         { y + iconOffset, y + 14 + iconOffset,
1135                             y + 8 + iconOffset }, 3);
1136               }
1137               break;
1138
1139             default:
1140               g.setColor(Color.gray);
1141               g.fillRect(lastSSX, y + 6 + iconOffset, (x * av.charWidth)
1142                       - lastSSX, 2);
1143
1144               break;
1145             }
1146
1147             if (validRes)
1148             {
1149               lastSS = row.annotations[column].secondaryStructure;
1150             }
1151             else
1152             {
1153               lastSS = ' ';
1154             }
1155
1156             lastSSX = (x * av.charWidth);
1157           }
1158         }
1159
1160         column++;
1161         x++;
1162       }
1163
1164       if (column >= row.annotations.length)
1165       {
1166         column = row.annotations.length - 1;
1167         validEnd = false;
1168       }
1169       else
1170       {
1171         validEnd = true;
1172       }
1173
1174       // x ++;
1175
1176       if (row.hasIcons)
1177       {
1178         switch (lastSS)
1179         {
1180         case 'H':
1181           g.setColor(HELIX_COLOUR);
1182           if (MAC)
1183           {
1184             // Off by 1 offset when drawing rects and ovals
1185             // to offscreen image on the MAC
1186             g.fillRoundRect(lastSSX, y + 4 + iconOffset, (x * av.charWidth)
1187                     - lastSSX, 7, 8, 8);
1188             break;
1189           }
1190
1191           int sCol = (lastSSX / av.charWidth) + startRes;
1192           int x1 = lastSSX;
1193           int x2 = (x * av.charWidth);
1194
1195           if (sCol == 0 || row.annotations[sCol - 1] == null
1196                   || row.annotations[sCol - 1].secondaryStructure != 'H')
1197           {
1198             g.fillArc(lastSSX, y + 4 + iconOffset, av.charWidth, 8, 90, 180);
1199             x1 += av.charWidth / 2;
1200           }
1201
1202           if (row.annotations[column] == null
1203                   || row.annotations[column].secondaryStructure != 'H')
1204           {
1205             g.fillArc((x * av.charWidth) - av.charWidth,
1206                     y + 4 + iconOffset, av.charWidth, 8, 270, 180);
1207             x2 -= av.charWidth / 2;
1208           }
1209
1210           g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 8);
1211
1212           break;
1213
1214         case 'E':
1215           g.setColor(SHEET_COLOUR);
1216
1217           if (!validEnd || row.annotations[endRes] == null
1218                   || row.annotations[endRes].secondaryStructure != 'E')
1219           {
1220             g.fillRect(lastSSX, y + 4 + iconOffset, (x * av.charWidth)
1221                     - lastSSX - 4, 7);
1222             g.fillPolygon(new int[]
1223             { (x * av.charWidth) - 4, (x * av.charWidth) - 4,
1224                 (x * av.charWidth) }, new int[]
1225             { y + iconOffset, y + 14 + iconOffset, y + 7 + iconOffset }, 3);
1226           }
1227           else
1228           {
1229             g.fillRect(lastSSX, y + 4 + iconOffset, (x + 1) * av.charWidth
1230                     - lastSSX, 7);
1231           }
1232           break;
1233
1234         default:
1235           g.setColor(Color.gray);
1236           g.fillRect(lastSSX, y + 6 + iconOffset, (x * av.charWidth)
1237                   - lastSSX, 2);
1238
1239           break;
1240         }
1241       }
1242
1243       if (row.graph > 0 && row.graphHeight > 0)
1244       {
1245         if (row.graph == AlignmentAnnotation.LINE_GRAPH)
1246         {
1247           if (row.graphGroup > -1 && !graphGroupDrawn[row.graphGroup])
1248           {
1249             float groupmax = -999999, groupmin = 9999999;
1250             for (int gg = 0; gg < aa.length; gg++)
1251             {
1252               if (aa[gg].graphGroup != row.graphGroup)
1253               {
1254                 continue;
1255               }
1256
1257               if (aa[gg] != row)
1258               {
1259                 aa[gg].visible = false;
1260               }
1261
1262               if (aa[gg].graphMax > groupmax)
1263               {
1264                 groupmax = aa[gg].graphMax;
1265               }
1266               if (aa[gg].graphMin < groupmin)
1267               {
1268                 groupmin = aa[gg].graphMin;
1269               }
1270             }
1271
1272             for (int gg = 0; gg < aa.length; gg++)
1273             {
1274               if (aa[gg].graphGroup == row.graphGroup)
1275               {
1276                 drawLineGraph(g, aa[gg], startRes, endRes, y, groupmin,
1277                         groupmax, row.graphHeight);
1278               }
1279             }
1280
1281             graphGroupDrawn[row.graphGroup] = true;
1282           }
1283           else
1284           {
1285             drawLineGraph(g, row, startRes, endRes, y, row.graphMin,
1286                     row.graphMax, row.graphHeight);
1287           }
1288         }
1289         else if (row.graph == AlignmentAnnotation.BAR_GRAPH)
1290         {
1291           drawBarGraph(g, row, startRes, endRes, row.graphMin,
1292                   row.graphMax, y);
1293         }
1294       }
1295
1296       if (row.graph > 0 && row.hasText)
1297       {
1298         y += av.charHeight;
1299       }
1300
1301       if (row.graph == 0)
1302       {
1303         y += aa[i].height;
1304       }
1305     }
1306   }
1307
1308   public void drawLineGraph(Graphics g, AlignmentAnnotation aa, int sRes,
1309           int eRes, int y, float min, float max, int graphHeight)
1310   {
1311     if (sRes > aa.annotations.length)
1312     {
1313       return;
1314     }
1315
1316     int x = 0;
1317
1318     // Adjustment for fastpaint to left
1319     if (eRes < av.endRes)
1320     {
1321       eRes++;
1322     }
1323
1324     eRes = Math.min(eRes, aa.annotations.length);
1325
1326     if (sRes == 0)
1327     {
1328       x++;
1329     }
1330
1331     int y1 = y, y2 = y;
1332     float range = max - min;
1333
1334     // //Draw origin
1335     if (min < 0)
1336     {
1337       y2 = y - (int) ((0 - min / range) * graphHeight);
1338     }
1339
1340     g.setColor(Color.gray);
1341     g.drawLine(x - av.charWidth, y2, (eRes - sRes + 1) * av.charWidth, y2);
1342
1343     eRes = Math.min(eRes, aa.annotations.length);
1344
1345     int column;
1346     int aaMax = aa.annotations.length - 1;
1347
1348     while (x < eRes - sRes)
1349     {
1350       column = sRes + x;
1351       if (av.hasHiddenColumns)
1352       {
1353         column = av.getColumnSelection().adjustForHiddenColumns(column);
1354       }
1355
1356       if (column > aaMax)
1357       {
1358         break;
1359       }
1360
1361       if (aa.annotations[column] == null
1362               || aa.annotations[column - 1] == null)
1363       {
1364         x++;
1365         continue;
1366       }
1367
1368       if (aa.annotations[column].colour == null)
1369         g.setColor(Color.black);
1370       else
1371         g.setColor(aa.annotations[column].colour);
1372
1373       y1 = y
1374               - (int) (((aa.annotations[column - 1].value - min) / range) * graphHeight);
1375       y2 = y
1376               - (int) (((aa.annotations[column].value - min) / range) * graphHeight);
1377
1378       g.drawLine(x * av.charWidth - av.charWidth / 2, y1, x * av.charWidth
1379               + av.charWidth / 2, y2);
1380       x++;
1381     }
1382
1383     if (aa.threshold != null)
1384     {
1385       g.setColor(aa.threshold.colour);
1386       Graphics2D g2 = (Graphics2D) g;
1387       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
1388               BasicStroke.JOIN_ROUND, 3f, new float[]
1389               { 5f, 3f }, 0f));
1390
1391       y2 = (int) (y - ((aa.threshold.value - min) / range) * graphHeight);
1392       g.drawLine(0, y2, (eRes - sRes) * av.charWidth, y2);
1393       g2.setStroke(new BasicStroke());
1394     }
1395   }
1396
1397   public void drawBarGraph(Graphics g, AlignmentAnnotation aa, int sRes,
1398           int eRes, float min, float max, int y)
1399   {
1400     ColourSchemeI profcolour = av.getGlobalColourScheme();
1401     if (profcolour == null)
1402     {
1403       profcolour = new jalview.schemes.ZappoColourScheme();
1404     }
1405     if (sRes > aa.annotations.length)
1406     {
1407       return;
1408     }
1409     Font ofont = g.getFont();
1410     eRes = Math.min(eRes, aa.annotations.length);
1411
1412     int x = 0, y1 = y, y2 = y;
1413
1414     float range = max - min;
1415
1416     if (min < 0)
1417     {
1418       y2 = y - (int) ((0 - min / (range)) * aa.graphHeight);
1419     }
1420
1421     g.setColor(Color.gray);
1422
1423     g.drawLine(x, y2, (eRes - sRes) * av.charWidth, y2);
1424
1425     int column;
1426     int aaMax = aa.annotations.length - 1;
1427     boolean renderHistogram = true, renderProfile = true;
1428     if (aa.autoCalculated && aa.label.startsWith("Consensus"))
1429     {
1430       // TODO: generalise this to have render styles for consensus/profile data
1431       if (aa.groupRef != null)
1432       {
1433         renderHistogram = aa.groupRef.isShowConsensusHistogram();
1434         renderProfile = aa.groupRef.isShowSequenceLogo();
1435       }
1436       else
1437       {
1438         renderHistogram = av.isShowConsensusHistogram();
1439         renderProfile = av.isShowSequenceLogo();
1440       }
1441     }
1442     while (x < eRes - sRes)
1443     {
1444       column = sRes + x;
1445       if (av.hasHiddenColumns)
1446       {
1447         column = av.getColumnSelection().adjustForHiddenColumns(column);
1448       }
1449
1450       if (column > aaMax)
1451       {
1452         break;
1453       }
1454
1455       if (aa.annotations[column] == null)
1456       {
1457         x++;
1458         continue;
1459       }
1460       if (aa.annotations[column].colour == null)
1461         g.setColor(Color.black);
1462       else
1463         g.setColor(aa.annotations[column].colour);
1464
1465       y1 = y
1466               - (int) (((aa.annotations[column].value - min) / (range)) * aa.graphHeight);
1467
1468       if (renderHistogram)
1469       {
1470         if (y1 - y2 > 0)
1471         {
1472           g.fillRect(x * av.charWidth, y2, av.charWidth, y1 - y2);
1473         }
1474         else
1475         {
1476           g.fillRect(x * av.charWidth, y1, av.charWidth, y2 - y1);
1477         }
1478       }
1479       // draw profile if available
1480       if (renderProfile && aa.annotations[column].value != 0)
1481       {
1482         int profl[] = getProfileFor(aa, column);
1483         int ht = y1, htn = y2 - y1;// aa.graphHeight;
1484         float wdth;
1485         double ht2 = 0;
1486         char[] dc = new char[1];
1487         LineMetrics lm;
1488         for (int c = 1; profl != null && c < profl[0];)
1489         {
1490           dc[0] = (char) profl[c++];
1491           wdth = av.charWidth;
1492           wdth /= (float) fm.charsWidth(dc, 0, 1);
1493
1494           if (c > 2)
1495           {
1496             ht += (int) ht2;
1497           }
1498           {
1499             // if (aa.annotations[column].value==0) {
1500             // g.setFont(ofont.deriveFont(AffineTransform.getScaleInstance(wdth,
1501             // (ht2=(aa.graphHeight*0.1/av.charHeight)))));
1502             // ht = y2-(int)ht2;
1503             // } else {
1504             g.setFont(ofont.deriveFont(AffineTransform.getScaleInstance(
1505                     wdth, (ht2 = (htn * ((double) profl[c++]) / 100.0))
1506                             / av.charHeight)));
1507             lm = g.getFontMetrics().getLineMetrics(dc, 0, 1, g);
1508             // htn -=ht2;
1509             // }
1510             g.setColor(profcolour.findColour(dc[0])); // (av.globalColourScheme!=null)
1511                                                       // ? );// try to get a
1512                                                       // colourscheme for the
1513                                                       // group(aa.groupRef.cs==null)
1514                                                       // ? av.textColour2 :
1515                                                       // cs.findColour(dc));
1516             g.drawChars(dc, 0, 1, x * av.charWidth,
1517                     (int) (ht + lm.getHeight()));
1518             // ht+=g.getFontMetrics().getAscent()-g.getFontMetrics().getDescent();
1519           }
1520         }
1521         g.setFont(ofont);
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 - ((aa.threshold.value - min) / range) * aa.graphHeight);
1534       g.drawLine(0, y2, (eRes - sRes) * av.charWidth, y2);
1535       g2.setStroke(new BasicStroke());
1536     }
1537   }
1538
1539   private int[] getProfileFor(AlignmentAnnotation aa, int column)
1540   {
1541     if (aa.autoCalculated && aa.label.startsWith("Consensus"))
1542     {
1543       if (aa.groupRef != null && aa.groupRef.consensusData != null
1544               && aa.groupRef.isShowSequenceLogo())
1545       {
1546         return AAFrequency.extractProfile(
1547                 aa.groupRef.consensusData[column],
1548                 aa.groupRef.getIgnoreGapsConsensus());
1549       }
1550       // TODO extend annotation row to enable dynamic and static profile data to
1551       // be stored
1552       if (aa.groupRef == null && aa.sequenceRef == null
1553               && av.isShowSequenceLogo())
1554       {
1555         return AAFrequency.extractProfile(av.hconsensus[column],
1556                 av.getIgnoreGapsConsensus());
1557       }
1558     }
1559     return null;
1560   }
1561
1562   // used by overview window
1563   public void drawGraph(Graphics g, AlignmentAnnotation aa, int width,
1564           int y, int sRes, int eRes)
1565   {
1566     eRes = Math.min(eRes, aa.annotations.length);
1567     g.setColor(Color.white);
1568     g.fillRect(0, 0, width, y);
1569     g.setColor(new Color(0, 0, 180));
1570
1571     int x = 0, height;
1572
1573     for (int j = sRes; j < eRes; j++)
1574     {
1575       if (aa.annotations[j] != null)
1576       {
1577         if (aa.annotations[j].colour == null)
1578           g.setColor(Color.black);
1579         else
1580           g.setColor(aa.annotations[j].colour);
1581
1582         height = (int) ((aa.annotations[j].value / aa.graphMax) * y);
1583         if (height > y)
1584         {
1585           height = y;
1586         }
1587
1588         g.fillRect(x, y - height, av.charWidth, height);
1589       }
1590       x += av.charWidth;
1591     }
1592   }
1593
1594 }