050e9f26dc861a1fec392e6577f535a3a397ef96
[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           //TODO mouseClicked-content and drawCursor are quite experimental!
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       if (row.graph > 0)
890       {
891         if (row.graphGroup > -1 && graphGroupDrawn[row.graphGroup])
892         {
893           continue;
894         }
895
896         // this is so that we draw the characters below the graph
897         y += row.height;
898
899         if (row.hasText)
900         {
901           iconOffset = av.charHeight - fm.getDescent();
902           y -= av.charHeight;
903         }
904       }
905       else if (row.hasText)
906       {
907         iconOffset = av.charHeight - fm.getDescent();
908
909       }
910       else
911       {
912         iconOffset = 0;
913       }
914
915       if (av.updatingConsensus && aa[i] == av.consensus)
916       {
917         y += av.charHeight;
918
919         g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0, y
920                 - row.height, imgWidth, y, this);
921         g.setColor(Color.black);
922         // g.drawString("Calculating Consensus....",20, y-row.height/2);
923
924         continue;
925       }
926       else if (av.updatingConservation
927               && aa[i].label.equals("Conservation"))
928       {
929
930         y += av.charHeight;
931         g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0, y
932                 - row.height, imgWidth, y, this);
933
934         g.setColor(Color.black);
935         // g.drawString("Calculating Conservation.....",20, y-row.height/2);
936
937         continue;
938       }
939       else if (av.updatingConservation && aa[i].label.equals("Quality"))
940       {
941
942         y += av.charHeight;
943         g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0, y
944                 - row.height, imgWidth, y, this);
945         g.setColor(Color.black);
946         // / g.drawString("Calculating Quality....",20, y-row.height/2);
947
948         continue;
949       }
950
951       // first pass sets up state for drawing continuation from left-hand column
952       // of startRes
953       x = (startRes == 0) ? 0 : -1;
954       while (x < endRes - startRes)
955       {
956         if (av.hasHiddenColumns)
957         {
958           column = av.getColumnSelection().adjustForHiddenColumns(
959                   startRes + x);
960           if (column > row.annotations.length - 1)
961           {
962             break;
963           }
964         }
965         else
966         {
967           column = startRes + x;
968         }
969
970         if ((row.annotations == null) || (row.annotations.length <= column)
971                 || (row.annotations[column] == null))
972         {
973           validRes = false;
974         }
975         else
976         {
977           validRes = true;
978         }
979         if (x > -1)
980         {
981           if (activeRow == i)
982           {
983             g.setColor(Color.red);
984
985             if (av.getColumnSelection() != null)
986             {
987               for (int n = 0; n < av.getColumnSelection().size(); n++)
988               {
989                 int v = av.getColumnSelection().columnAt(n);
990
991                 if (v == column)
992                 {
993                   g.fillRect(x * av.charWidth, y, av.charWidth,
994                           av.charHeight);
995                 }
996               }
997             }
998           }
999
1000           if (av.validCharWidth
1001                   && validRes
1002                   && row.annotations[column].displayCharacter != null
1003                   && (row.annotations[column].displayCharacter.length() > 0))
1004           {
1005
1006             if (centreColLabels || scaleColLabel)
1007             {
1008               fmWidth = (float) fm.charsWidth(
1009                       row.annotations[column].displayCharacter
1010                               .toCharArray(), 0,
1011                       row.annotations[column].displayCharacter.length());
1012
1013               if (scaleColLabel)
1014               {
1015                 // justify the label and scale to fit in column
1016                 if (fmWidth > av.charWidth)
1017                 {
1018                   // scale only if the current font isn't already small enough
1019                   fmScaling = av.charWidth;
1020                   fmScaling /= fmWidth;
1021                   g.setFont(ofont.deriveFont(AffineTransform
1022                           .getScaleInstance(fmScaling, 1.0)));
1023                   // and update the label's width to reflect the scaling.
1024                   fmWidth = av.charWidth;
1025                 }
1026               }
1027             }
1028             else
1029             {
1030               fmWidth = (float) fm
1031                       .charWidth(row.annotations[column].displayCharacter
1032                               .charAt(0));
1033             }
1034             charOffset = (int) ((av.charWidth - fmWidth) / 2f);
1035
1036             if (row.annotations[column].colour == null)
1037               g.setColor(Color.black);
1038             else
1039               g.setColor(row.annotations[column].colour);
1040
1041             if (column == 0 || row.graph > 0)
1042             {
1043               g.drawString(row.annotations[column].displayCharacter,
1044                       (x * av.charWidth) + charOffset, y + iconOffset);
1045             }
1046             else if (row.annotations[column - 1] == null
1047                     || (labelAllCols
1048                             || !row.annotations[column].displayCharacter
1049                                     .equals(row.annotations[column - 1].displayCharacter) || (row.annotations[column].displayCharacter
1050                             .length() < 2 && row.annotations[column].secondaryStructure == ' ')))
1051             {
1052               g.drawString(row.annotations[column].displayCharacter, x
1053                       * av.charWidth + charOffset, y + iconOffset);
1054             }
1055             g.setFont(ofont);
1056           }
1057         }
1058         if (row.hasIcons)
1059         {
1060           if (!validRes
1061                   || (row.annotations[column].secondaryStructure != lastSS))
1062           {
1063             if (x > -1)
1064             {
1065               switch (lastSS)
1066               {
1067               case 'H':
1068                 drawHelixAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
1069                         column, validRes, validEnd);
1070                 break;
1071
1072               case 'E':
1073                 drawSheetAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
1074                         column, validRes, validEnd);
1075                 break;
1076
1077               case 'S': // Stem case for RNA secondary structure
1078                 drawStemAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
1079                         column, validRes, validEnd);
1080                 break;
1081
1082               default:
1083                 g.setColor(Color.gray);
1084                 g.fillRect(lastSSX, y + 6 + iconOffset, (x * av.charWidth)
1085                         - lastSSX, 2);
1086
1087                 break;
1088               }
1089             }
1090             if (validRes)
1091             {
1092               lastSS = row.annotations[column].secondaryStructure;
1093             }
1094             else
1095             {
1096               lastSS = ' ';
1097             }
1098             if (x > -1)
1099             {
1100               lastSSX = (x * av.charWidth);
1101             }
1102           }
1103         }
1104
1105         column++;
1106         x++;
1107       }
1108
1109       if (column >= row.annotations.length)
1110       {
1111         column = row.annotations.length - 1;
1112         validEnd = false;
1113       }
1114       else
1115       {
1116         validEnd = true;
1117       }
1118
1119       // x ++;
1120
1121       if (row.hasIcons)
1122       {
1123         switch (lastSS)
1124         {
1125         case 'H':
1126           drawHelixAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
1127                   column, validRes, validEnd);
1128           break;
1129
1130         case 'E':
1131           drawSheetAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
1132                   column, validRes, validEnd);
1133           break;
1134         case 'S': // Stem case for RNA secondary structure
1135           drawStemAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
1136                   column, validRes, validEnd);
1137           break;
1138         default:
1139           drawGlyphLine(g, row, lastSSX, x, y, iconOffset, startRes,
1140                   column, validRes, validEnd);
1141           break;
1142         }
1143       }
1144
1145       if (row.graph > 0 && row.graphHeight > 0)
1146       {
1147         if (row.graph == AlignmentAnnotation.LINE_GRAPH)
1148         {
1149           if (row.graphGroup > -1 && !graphGroupDrawn[row.graphGroup])
1150           {
1151             float groupmax = -999999, groupmin = 9999999;
1152             for (int gg = 0; gg < aa.length; gg++)
1153             {
1154               if (aa[gg].graphGroup != row.graphGroup)
1155               {
1156                 continue;
1157               }
1158
1159               if (aa[gg] != row)
1160               {
1161                 aa[gg].visible = false;
1162               }
1163
1164               if (aa[gg].graphMax > groupmax)
1165               {
1166                 groupmax = aa[gg].graphMax;
1167               }
1168               if (aa[gg].graphMin < groupmin)
1169               {
1170                 groupmin = aa[gg].graphMin;
1171               }
1172             }
1173
1174             for (int gg = 0; gg < aa.length; gg++)
1175             {
1176               if (aa[gg].graphGroup == row.graphGroup)
1177               {
1178                 drawLineGraph(g, aa[gg], startRes, endRes, y, groupmin,
1179                         groupmax, row.graphHeight);
1180               }
1181             }
1182
1183             graphGroupDrawn[row.graphGroup] = true;
1184           }
1185           else
1186           {
1187             drawLineGraph(g, row, startRes, endRes, y, row.graphMin,
1188                     row.graphMax, row.graphHeight);
1189           }
1190         }
1191         else if (row.graph == AlignmentAnnotation.BAR_GRAPH)
1192         {
1193           drawBarGraph(g, row, startRes, endRes, row.graphMin,
1194                   row.graphMax, y);
1195         }
1196       }
1197
1198       if (row.graph > 0 && row.hasText)
1199       {
1200         y += av.charHeight;
1201       }
1202
1203       if (row.graph == 0)
1204       {
1205         y += aa[i].height;
1206       }
1207     }
1208   }
1209
1210   private void drawStemAnnot(Graphics g, AlignmentAnnotation row,
1211           int lastSSX, int x, int y, int iconOffset, int startRes,
1212           int column, boolean validRes, boolean validEnd)
1213   {
1214     g.setColor(STEM_COLOUR); 
1215     int sCol = (lastSSX / av.charWidth) + startRes;
1216     int x1 = lastSSX;
1217     int x2 = (x * av.charWidth);
1218     Regex closeparen = new Regex("(\\))");
1219
1220     String dc="";
1221     // If a closing base pair half of the stem, display a backward arrow
1222     if (column>0 && closeparen.search(dc=row.annotations[column - 1].displayCharacter))
1223     {
1224       if (sCol == 0 || row.annotations[sCol - 1] == null
1225               || !dc.equals(row.annotations[sCol - 1].displayCharacter))
1226       //if (validRes && column>1 && row.annotations[column-2]!=null && dc.equals(row.annotations[column-2].displayCharacter))
1227       {      g.fillPolygon(new int[]
1228       { lastSSX + 5, lastSSX + 5, lastSSX }, new int[]
1229       { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset }, 3);
1230       x1+=5;
1231       }
1232     }
1233     else
1234     {
1235       // display a forward arrow
1236       if (!validRes  || row.annotations[column] == null
1237               || !dc.equals(row.annotations[column].displayCharacter))
1238       {
1239       g.fillPolygon(
1240               new int[]
1241               { x2 - 5, x2 - 5,
1242                   x2 }, new int[]
1243               { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset },
1244               3);
1245       x2-=5;
1246       }
1247     }
1248     // draw arrow body
1249     g.fillRect(x1, y + 4 + iconOffset, x2-x1, 7);
1250   }
1251
1252   private void drawGlyphLine(Graphics g, AlignmentAnnotation row,
1253           int lastSSX, int x, int y, int iconOffset, int startRes,
1254           int column, boolean validRes, boolean validEnd)
1255   {
1256     g.setColor(Color.gray);
1257     g.fillRect(lastSSX, y + 6 + iconOffset, (x * av.charWidth) - lastSSX, 2);
1258   }
1259
1260   private void drawSheetAnnot(Graphics g, AlignmentAnnotation row,
1261           int lastSSX, int x, int y, int iconOffset, int startRes,
1262           int column, boolean validRes, boolean validEnd)
1263   {
1264     g.setColor(SHEET_COLOUR);
1265
1266     if (!validEnd || !validRes || row.annotations[column] == null
1267             || row.annotations[column].secondaryStructure != 'E')
1268     {
1269       g.fillRect(lastSSX, y + 4 + iconOffset, (x * av.charWidth) - lastSSX
1270               - 4, 7);
1271       g.fillPolygon(
1272               new int[]
1273               { (x * av.charWidth) - 4, (x * av.charWidth) - 4,
1274                   (x * av.charWidth) }, new int[]
1275               { y + iconOffset, y + 14 + iconOffset, y + 7 + iconOffset },
1276               3);
1277     }
1278     else
1279     {
1280       g.fillRect(lastSSX, y + 4 + iconOffset, (x + 1) * av.charWidth
1281               - lastSSX, 7);
1282     }
1283
1284   }
1285
1286   private void drawHelixAnnot(Graphics g, AlignmentAnnotation row,
1287           int lastSSX, int x, int y, int iconOffset, int startRes,
1288           int column, boolean validRes, boolean validEnd)
1289   {
1290     g.setColor(HELIX_COLOUR);
1291
1292     int sCol = (lastSSX / av.charWidth) + startRes;
1293     int x1 = lastSSX;
1294     int x2 = (x * av.charWidth);
1295
1296     if (MAC)
1297     {
1298       int ofs=av.charWidth/2;
1299       // Off by 1 offset when drawing rects and ovals
1300       // to offscreen image on the MAC
1301       g.fillRoundRect(lastSSX, y + 4 + iconOffset, x2-x1, 8, 8, 8);
1302       if (sCol == 0 || row.annotations[sCol - 1] == null
1303               || row.annotations[sCol - 1].secondaryStructure != 'H')
1304       {
1305       } else {
1306 //        g.setColor(Color.orange);
1307         g.fillRoundRect(lastSSX, y + 4 + iconOffset, x2-x1-ofs+1, 8, 0, 0);
1308       }
1309       if (!validRes  || row.annotations[column] == null
1310               || row.annotations[column].secondaryStructure != 'H')
1311       {
1312         
1313       } else {
1314 //        g.setColor(Color.magenta);
1315         g.fillRoundRect(lastSSX+ofs, y + 4 + iconOffset, x2-x1-ofs+1, 8, 0, 0);
1316         
1317       }
1318       
1319       return;
1320     }
1321
1322     if (sCol == 0 || row.annotations[sCol - 1] == null
1323             || row.annotations[sCol - 1].secondaryStructure != 'H')
1324     {
1325       g.fillArc(lastSSX, y + 4 + iconOffset, av.charWidth, 8, 90, 180);
1326       x1 += av.charWidth / 2;
1327     }
1328
1329     if (!validRes  || row.annotations[column] == null
1330             || row.annotations[column].secondaryStructure != 'H')
1331     {
1332       g.fillArc((x * av.charWidth) - av.charWidth, y + 4 + iconOffset,
1333               av.charWidth, 8, 270, 180);
1334       x2 -= av.charWidth / 2;
1335     }
1336
1337     g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 8);
1338   }
1339
1340 public void drawLineGraph(Graphics g, AlignmentAnnotation aa, int sRes,
1341           int eRes, int y, float min, float max, int graphHeight)
1342   {
1343     if (sRes > aa.annotations.length)
1344     {
1345       return;
1346     }
1347
1348     int x = 0;
1349
1350     // Adjustment for fastpaint to left
1351     if (eRes < av.endRes)
1352     {
1353       eRes++;
1354     }
1355
1356     eRes = Math.min(eRes, aa.annotations.length);
1357
1358     if (sRes == 0)
1359     {
1360       x++;
1361     }
1362
1363     int y1 = y, y2 = y;
1364     float range = max - min;
1365
1366     // //Draw origin
1367     if (min < 0)
1368     {
1369       y2 = y - (int) ((0 - min / range) * graphHeight);
1370     }
1371
1372     g.setColor(Color.gray);
1373     g.drawLine(x - av.charWidth, y2, (eRes - sRes + 1) * av.charWidth, y2);
1374
1375     eRes = Math.min(eRes, aa.annotations.length);
1376
1377     int column;
1378     int aaMax = aa.annotations.length - 1;
1379
1380     while (x < eRes - sRes)
1381     {
1382       column = sRes + x;
1383       if (av.hasHiddenColumns)
1384       {
1385         column = av.getColumnSelection().adjustForHiddenColumns(column);
1386       }
1387
1388       if (column > aaMax)
1389       {
1390         break;
1391       }
1392
1393       if (aa.annotations[column] == null
1394               || aa.annotations[column - 1] == null)
1395       {
1396         x++;
1397         continue;
1398       }
1399
1400       if (aa.annotations[column].colour == null)
1401         g.setColor(Color.black);
1402       else
1403         g.setColor(aa.annotations[column].colour);
1404
1405       y1 = y
1406               - (int) (((aa.annotations[column - 1].value - min) / range) * graphHeight);
1407       y2 = y
1408               - (int) (((aa.annotations[column].value - min) / range) * graphHeight);
1409
1410       g.drawLine(x * av.charWidth - av.charWidth / 2, y1, x * av.charWidth
1411               + av.charWidth / 2, y2);
1412       x++;
1413     }
1414
1415     if (aa.threshold != null)
1416     {
1417       g.setColor(aa.threshold.colour);
1418       Graphics2D g2 = (Graphics2D) g;
1419       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
1420               BasicStroke.JOIN_ROUND, 3f, new float[]
1421               { 5f, 3f }, 0f));
1422
1423       y2 = (int) (y - ((aa.threshold.value - min) / range) * graphHeight);
1424       g.drawLine(0, y2, (eRes - sRes) * av.charWidth, y2);
1425       g2.setStroke(new BasicStroke());
1426     }
1427   }
1428
1429   public void drawBarGraph(Graphics g, AlignmentAnnotation aa, int sRes,
1430           int eRes, float min, float max, int y)
1431   {
1432     ColourSchemeI profcolour = av.getGlobalColourScheme();
1433     if (profcolour == null)
1434     {
1435       profcolour = new jalview.schemes.ZappoColourScheme();
1436     }
1437     if (sRes > aa.annotations.length)
1438     {
1439       return;
1440     }
1441     Font ofont = g.getFont();
1442     eRes = Math.min(eRes, aa.annotations.length);
1443
1444     int x = 0, y1 = y, y2 = y;
1445
1446     float range = max - min;
1447
1448     if (min < 0)
1449     {
1450       y2 = y - (int) ((0 - min / (range)) * aa.graphHeight);
1451     }
1452
1453     g.setColor(Color.gray);
1454
1455     g.drawLine(x, y2, (eRes - sRes) * av.charWidth, y2);
1456
1457     int column;
1458     int aaMax = aa.annotations.length - 1;
1459     boolean renderHistogram = true, renderProfile = true;
1460     if (aa.autoCalculated && aa.label.startsWith("Consensus"))
1461     {
1462       // TODO: generalise this to have render styles for consensus/profile data
1463       if (aa.groupRef != null)
1464       {
1465         renderHistogram = aa.groupRef.isShowConsensusHistogram();
1466         renderProfile = aa.groupRef.isShowSequenceLogo();
1467       }
1468       else
1469       {
1470         renderHistogram = av.isShowConsensusHistogram();
1471         renderProfile = av.isShowSequenceLogo();
1472       }
1473     }
1474     while (x < eRes - sRes)
1475     {
1476       column = sRes + x;
1477       if (av.hasHiddenColumns)
1478       {
1479         column = av.getColumnSelection().adjustForHiddenColumns(column);
1480       }
1481
1482       if (column > aaMax)
1483       {
1484         break;
1485       }
1486
1487       if (aa.annotations[column] == null)
1488       {
1489         x++;
1490         continue;
1491       }
1492       if (aa.annotations[column].colour == null)
1493         g.setColor(Color.black);
1494       else
1495         g.setColor(aa.annotations[column].colour);
1496
1497       y1 = y
1498               - (int) (((aa.annotations[column].value - min) / (range)) * aa.graphHeight);
1499
1500       if (renderHistogram)
1501       {
1502         if (y1 - y2 > 0)
1503         {
1504           g.fillRect(x * av.charWidth, y2, av.charWidth, y1 - y2);
1505         }
1506         else
1507         {
1508           g.fillRect(x * av.charWidth, y1, av.charWidth, y2 - y1);
1509         }
1510       }
1511       // draw profile if available
1512       if (renderProfile && aa.annotations[column].value != 0)
1513       {
1514         int profl[] = getProfileFor(aa, column);
1515         int ht = y1, htn = y2 - y1;// aa.graphHeight;
1516         float wdth;
1517         double ht2 = 0;
1518         char[] dc = new char[1];
1519         LineMetrics lm;
1520         for (int c = 1; profl != null && c < profl[0];)
1521         {
1522           dc[0] = (char) profl[c++];
1523           wdth = av.charWidth;
1524           wdth /= (float) fm.charsWidth(dc, 0, 1);
1525
1526           if (c > 2)
1527           {
1528             ht += (int) ht2;
1529           }
1530           {
1531             // if (aa.annotations[column].value==0) {
1532             // g.setFont(ofont.deriveFont(AffineTransform.getScaleInstance(wdth,
1533             // (ht2=(aa.graphHeight*0.1/av.charHeight)))));
1534             // ht = y2-(int)ht2;
1535             // } else {
1536             g.setFont(ofont.deriveFont(AffineTransform.getScaleInstance(
1537                     wdth, (ht2 = (htn * ((double) profl[c++]) / 100.0))
1538                             / av.charHeight)));
1539             lm = g.getFontMetrics().getLineMetrics(dc, 0, 1, g);
1540             // htn -=ht2;
1541             // }
1542             g.setColor(profcolour.findColour(dc[0])); // (av.globalColourScheme!=null)
1543                                                       // ? );// try to get a
1544                                                       // colourscheme for the
1545                                                       // group(aa.groupRef.cs==null)
1546                                                       // ? av.textColour2 :
1547                                                       // cs.findColour(dc));
1548             g.drawChars(dc, 0, 1, x * av.charWidth,
1549                     (int) (ht + lm.getHeight()));
1550             // ht+=g.getFontMetrics().getAscent()-g.getFontMetrics().getDescent();
1551           }
1552         }
1553         g.setFont(ofont);
1554       }
1555       x++;
1556     }
1557     if (aa.threshold != null)
1558     {
1559       g.setColor(aa.threshold.colour);
1560       Graphics2D g2 = (Graphics2D) g;
1561       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
1562               BasicStroke.JOIN_ROUND, 3f, new float[]
1563               { 5f, 3f }, 0f));
1564
1565       y2 = (int) (y - ((aa.threshold.value - min) / range) * aa.graphHeight);
1566       g.drawLine(0, y2, (eRes - sRes) * av.charWidth, y2);
1567       g2.setStroke(new BasicStroke());
1568     }
1569   }
1570
1571   private int[] getProfileFor(AlignmentAnnotation aa, int column)
1572   {
1573     if (aa.autoCalculated && aa.label.startsWith("Consensus"))
1574     {
1575       if (aa.groupRef != null && aa.groupRef.consensusData != null
1576               && aa.groupRef.isShowSequenceLogo())
1577       {
1578         return AAFrequency.extractProfile(
1579                 aa.groupRef.consensusData[column],
1580                 aa.groupRef.getIgnoreGapsConsensus());
1581       }
1582       // TODO extend annotation row to enable dynamic and static profile data to
1583       // be stored
1584       if (aa.groupRef == null && aa.sequenceRef == null
1585               && av.isShowSequenceLogo())
1586       {
1587         return AAFrequency.extractProfile(av.hconsensus[column],
1588                 av.getIgnoreGapsConsensus());
1589       }
1590     }
1591     return null;
1592   }
1593
1594   // used by overview window
1595   public void drawGraph(Graphics g, AlignmentAnnotation aa, int width,
1596           int y, int sRes, int eRes)
1597   {
1598     eRes = Math.min(eRes, aa.annotations.length);
1599     g.setColor(Color.white);
1600     g.fillRect(0, 0, width, y);
1601     g.setColor(new Color(0, 0, 180));
1602
1603     int x = 0, height;
1604
1605     for (int j = sRes; j < eRes; j++)
1606     {
1607       if (aa.annotations[j] != null)
1608       {
1609         if (aa.annotations[j].colour == null)
1610           g.setColor(Color.black);
1611         else
1612           g.setColor(aa.annotations[j].colour);
1613
1614         height = (int) ((aa.annotations[j].value / aa.graphMax) * y);
1615         if (height > y)
1616         {
1617           height = y;
1618         }
1619
1620         g.fillRect(x, y - height, av.charWidth, height);
1621       }
1622       x += av.charWidth;
1623     }
1624   }
1625
1626 }