827540ad400c7f2911df3a28ab20e675396b2284
[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           char ss=row.annotations[column].secondaryStructure;
1061           if (ss=='S')
1062           {
1063             // distinguish between forward/backward base-pairing
1064             if (row.annotations[column].displayCharacter.indexOf(')')>-1)
1065             {
1066               ss='s';
1067             }
1068           }
1069           if (!validRes
1070                   || (ss != lastSS))
1071           {
1072             if (x > -1)
1073             {
1074               switch (lastSS)
1075               {
1076               case 'H':
1077                 drawHelixAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
1078                         column, validRes, validEnd);
1079                 break;
1080
1081               case 'E':
1082                 drawSheetAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
1083                         column, validRes, validEnd);
1084                 break;
1085
1086               case 'S': // Stem case for RNA secondary structure
1087               case 's': // and opposite direction
1088                 drawStemAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
1089                         column, validRes, validEnd);
1090                 break;
1091
1092               default:
1093                 g.setColor(Color.gray);
1094                 g.fillRect(lastSSX, y + 6 + iconOffset, (x * av.charWidth)
1095                         - lastSSX, 2);
1096
1097                 break;
1098               }
1099             }
1100             if (validRes)
1101             {
1102               lastSS = ss;
1103             }
1104             else
1105             {
1106               lastSS = ' ';
1107             }
1108             if (x > -1)
1109             {
1110               lastSSX = (x * av.charWidth);
1111             }
1112           }
1113         }
1114
1115         column++;
1116         x++;
1117       }
1118
1119       if (column >= row.annotations.length)
1120       {
1121         column = row.annotations.length - 1;
1122         validEnd = false;
1123       }
1124       else
1125       {
1126         validEnd = true;
1127       }
1128
1129       // x ++;
1130
1131       if (row.hasIcons)
1132       {
1133         switch (lastSS)
1134         {
1135         case 'H':
1136           drawHelixAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
1137                   column, validRes, validEnd);
1138           break;
1139
1140         case 'E':
1141           drawSheetAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
1142                   column, validRes, validEnd);
1143           break;
1144         case 's':
1145         case 'S': // Stem case for RNA secondary structure
1146           drawStemAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
1147                   column, validRes, validEnd);
1148           break;
1149         default:
1150           drawGlyphLine(g, row, lastSSX, x, y, iconOffset, startRes,
1151                   column, validRes, validEnd);
1152           break;
1153         }
1154       }
1155
1156       if (row.graph > 0 && row.graphHeight > 0)
1157       {
1158         if (row.graph == AlignmentAnnotation.LINE_GRAPH)
1159         {
1160           if (row.graphGroup > -1 && !graphGroupDrawn[row.graphGroup])
1161           {
1162             float groupmax = -999999, groupmin = 9999999;
1163             for (int gg = 0; gg < aa.length; gg++)
1164             {
1165               if (aa[gg].graphGroup != row.graphGroup)
1166               {
1167                 continue;
1168               }
1169
1170               if (aa[gg] != row)
1171               {
1172                 aa[gg].visible = false;
1173               }
1174
1175               if (aa[gg].graphMax > groupmax)
1176               {
1177                 groupmax = aa[gg].graphMax;
1178               }
1179               if (aa[gg].graphMin < groupmin)
1180               {
1181                 groupmin = aa[gg].graphMin;
1182               }
1183             }
1184
1185             for (int gg = 0; gg < aa.length; gg++)
1186             {
1187               if (aa[gg].graphGroup == row.graphGroup)
1188               {
1189                 drawLineGraph(g, aa[gg], startRes, endRes, y, groupmin,
1190                         groupmax, row.graphHeight);
1191               }
1192             }
1193
1194             graphGroupDrawn[row.graphGroup] = true;
1195           }
1196           else
1197           {
1198             drawLineGraph(g, row, startRes, endRes, y, row.graphMin,
1199                     row.graphMax, row.graphHeight);
1200           }
1201         }
1202         else if (row.graph == AlignmentAnnotation.BAR_GRAPH)
1203         {
1204           drawBarGraph(g, row, startRes, endRes, row.graphMin,
1205                   row.graphMax, y);
1206         }
1207       }
1208
1209       if (row.graph > 0 && row.hasText)
1210       {
1211         y += av.charHeight;
1212       }
1213
1214       if (row.graph == 0)
1215       {
1216         y += aa[i].height;
1217       }
1218     }
1219   }
1220
1221   private void drawStemAnnot(Graphics g, AlignmentAnnotation row,
1222           int lastSSX, int x, int y, int iconOffset, int startRes,
1223           int column, boolean validRes, boolean validEnd)
1224   {
1225     g.setColor(STEM_COLOUR);
1226     int sCol = (lastSSX / av.charWidth) + startRes;
1227     int x1 = lastSSX;
1228     int x2 = (x * av.charWidth);
1229     Regex closeparen = new Regex("(\\))");
1230     
1231     String dc = column==0 ? "" : row.annotations[column - 1].displayCharacter;
1232     
1233     boolean diffupstream=sCol == 0 || row.annotations[sCol - 1] == null
1234             || !dc.equals(row.annotations[sCol - 1].displayCharacter);
1235     boolean diffdownstream=!validRes || !validEnd || row.annotations[column] == null
1236             || !dc.equals(row.annotations[column].displayCharacter);
1237     System.out.println("Column "+column+" diff up: "+diffupstream+" down:"+diffdownstream);
1238     // If a closing base pair half of the stem, display a backward arrow
1239     if (column > 0
1240             && closeparen
1241                     .search(dc))
1242     {
1243       if (diffupstream)
1244       // if (validRes && column>1 && row.annotations[column-2]!=null &&
1245       // dc.equals(row.annotations[column-2].displayCharacter))
1246       {
1247         g.fillPolygon(new int[]
1248         { lastSSX + 5, lastSSX + 5, lastSSX }, new int[]
1249         { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset }, 3);
1250         x1 += 5;
1251       }
1252       if (diffdownstream)
1253       {
1254         x2-=1;
1255       }
1256     }
1257     else
1258     {
1259       // display a forward arrow
1260       if (diffdownstream)
1261       {
1262         g.fillPolygon(new int[]
1263         { x2 - 5, x2 - 5, x2 }, new int[]
1264         { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset }, 3);
1265         x2 -= 5;
1266       }
1267       if (diffupstream)
1268       {
1269         x1+=1;
1270       }
1271     }
1272     // draw arrow body
1273     g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 7);
1274   }
1275
1276   private void drawGlyphLine(Graphics g, AlignmentAnnotation row,
1277           int lastSSX, int x, int y, int iconOffset, int startRes,
1278           int column, boolean validRes, boolean validEnd)
1279   {
1280     g.setColor(Color.gray);
1281     g.fillRect(lastSSX, y + 6 + iconOffset, (x * av.charWidth) - lastSSX, 2);
1282   }
1283
1284   private void drawSheetAnnot(Graphics g, AlignmentAnnotation row,
1285           int lastSSX, int x, int y, int iconOffset, int startRes,
1286           int column, boolean validRes, boolean validEnd)
1287   {
1288     g.setColor(SHEET_COLOUR);
1289
1290     if (!validEnd || !validRes || row.annotations[column] == null
1291             || row.annotations[column].secondaryStructure != 'E')
1292     {
1293       g.fillRect(lastSSX, y + 4 + iconOffset, (x * av.charWidth) - lastSSX
1294               - 4, 7);
1295       g.fillPolygon(
1296               new int[]
1297               { (x * av.charWidth) - 4, (x * av.charWidth) - 4,
1298                   (x * av.charWidth) }, new int[]
1299               { y + iconOffset, y + 14 + iconOffset, y + 7 + iconOffset },
1300               3);
1301     }
1302     else
1303     {
1304       g.fillRect(lastSSX, y + 4 + iconOffset, (x + 1) * av.charWidth
1305               - lastSSX, 7);
1306     }
1307
1308   }
1309
1310   private void drawHelixAnnot(Graphics g, AlignmentAnnotation row,
1311           int lastSSX, int x, int y, int iconOffset, int startRes,
1312           int column, boolean validRes, boolean validEnd)
1313   {
1314     g.setColor(HELIX_COLOUR);
1315
1316     int sCol = (lastSSX / av.charWidth) + startRes;
1317     int x1 = lastSSX;
1318     int x2 = (x * av.charWidth);
1319
1320     if (MAC)
1321     {
1322       int ofs=av.charWidth/2;
1323       // Off by 1 offset when drawing rects and ovals
1324       // to offscreen image on the MAC
1325       g.fillRoundRect(lastSSX, y + 4 + iconOffset, x2-x1, 8, 8, 8);
1326       if (sCol == 0 || row.annotations[sCol - 1] == null
1327               || row.annotations[sCol - 1].secondaryStructure != 'H')
1328       {
1329       } else {
1330 //        g.setColor(Color.orange);
1331         g.fillRoundRect(lastSSX, y + 4 + iconOffset, x2-x1-ofs+1, 8, 0, 0);
1332       }
1333       if (!validRes  || row.annotations[column] == null
1334               || row.annotations[column].secondaryStructure != 'H')
1335       {
1336         
1337       } else {
1338 //        g.setColor(Color.magenta);
1339         g.fillRoundRect(lastSSX+ofs, y + 4 + iconOffset, x2-x1-ofs+1, 8, 0, 0);
1340         
1341       }
1342       
1343       return;
1344     }
1345
1346     if (sCol == 0 || row.annotations[sCol - 1] == null
1347             || row.annotations[sCol - 1].secondaryStructure != 'H')
1348     {
1349       g.fillArc(lastSSX, y + 4 + iconOffset, av.charWidth, 8, 90, 180);
1350       x1 += av.charWidth / 2;
1351     }
1352
1353     if (!validRes  || row.annotations[column] == null
1354             || row.annotations[column].secondaryStructure != 'H')
1355     {
1356       g.fillArc((x * av.charWidth) - av.charWidth, y + 4 + iconOffset,
1357               av.charWidth, 8, 270, 180);
1358       x2 -= av.charWidth / 2;
1359     }
1360
1361     g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 8);
1362   }
1363
1364 public void drawLineGraph(Graphics g, AlignmentAnnotation aa, int sRes,
1365           int eRes, int y, float min, float max, int graphHeight)
1366   {
1367     if (sRes > aa.annotations.length)
1368     {
1369       return;
1370     }
1371
1372     int x = 0;
1373
1374     // Adjustment for fastpaint to left
1375     if (eRes < av.endRes)
1376     {
1377       eRes++;
1378     }
1379
1380     eRes = Math.min(eRes, aa.annotations.length);
1381
1382     if (sRes == 0)
1383     {
1384       x++;
1385     }
1386
1387     int y1 = y, y2 = y;
1388     float range = max - min;
1389
1390     // //Draw origin
1391     if (min < 0)
1392     {
1393       y2 = y - (int) ((0 - min / range) * graphHeight);
1394     }
1395
1396     g.setColor(Color.gray);
1397     g.drawLine(x - av.charWidth, y2, (eRes - sRes + 1) * av.charWidth, y2);
1398
1399     eRes = Math.min(eRes, aa.annotations.length);
1400
1401     int column;
1402     int aaMax = aa.annotations.length - 1;
1403
1404     while (x < eRes - sRes)
1405     {
1406       column = sRes + x;
1407       if (av.hasHiddenColumns)
1408       {
1409         column = av.getColumnSelection().adjustForHiddenColumns(column);
1410       }
1411
1412       if (column > aaMax)
1413       {
1414         break;
1415       }
1416
1417       if (aa.annotations[column] == null
1418               || aa.annotations[column - 1] == null)
1419       {
1420         x++;
1421         continue;
1422       }
1423
1424       if (aa.annotations[column].colour == null)
1425         g.setColor(Color.black);
1426       else
1427         g.setColor(aa.annotations[column].colour);
1428
1429       y1 = y
1430               - (int) (((aa.annotations[column - 1].value - min) / range) * graphHeight);
1431       y2 = y
1432               - (int) (((aa.annotations[column].value - min) / range) * graphHeight);
1433
1434       g.drawLine(x * av.charWidth - av.charWidth / 2, y1, x * av.charWidth
1435               + av.charWidth / 2, y2);
1436       x++;
1437     }
1438
1439     if (aa.threshold != null)
1440     {
1441       g.setColor(aa.threshold.colour);
1442       Graphics2D g2 = (Graphics2D) g;
1443       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
1444               BasicStroke.JOIN_ROUND, 3f, new float[]
1445               { 5f, 3f }, 0f));
1446
1447       y2 = (int) (y - ((aa.threshold.value - min) / range) * graphHeight);
1448       g.drawLine(0, y2, (eRes - sRes) * av.charWidth, y2);
1449       g2.setStroke(new BasicStroke());
1450     }
1451   }
1452
1453   public void drawBarGraph(Graphics g, AlignmentAnnotation aa, int sRes,
1454           int eRes, float min, float max, int y)
1455   {
1456     ColourSchemeI profcolour = av.getGlobalColourScheme();
1457     if (profcolour == null)
1458     {
1459       profcolour = new jalview.schemes.ZappoColourScheme();
1460     }
1461     if (sRes > aa.annotations.length)
1462     {
1463       return;
1464     }
1465     Font ofont = g.getFont();
1466     eRes = Math.min(eRes, aa.annotations.length);
1467
1468     int x = 0, y1 = y, y2 = y;
1469
1470     float range = max - min;
1471
1472     if (min < 0)
1473     {
1474       y2 = y - (int) ((0 - min / (range)) * aa.graphHeight);
1475     }
1476
1477     g.setColor(Color.gray);
1478
1479     g.drawLine(x, y2, (eRes - sRes) * av.charWidth, y2);
1480
1481     int column;
1482     int aaMax = aa.annotations.length - 1;
1483     boolean renderHistogram = true, renderProfile = true;
1484     if (aa.autoCalculated && aa.label.startsWith("Consensus"))
1485     {
1486       // TODO: generalise this to have render styles for consensus/profile data
1487       if (aa.groupRef != null)
1488       {
1489         renderHistogram = aa.groupRef.isShowConsensusHistogram();
1490         renderProfile = aa.groupRef.isShowSequenceLogo();
1491       }
1492       else
1493       {
1494         renderHistogram = av.isShowConsensusHistogram();
1495         renderProfile = av.isShowSequenceLogo();
1496       }
1497     }
1498     while (x < eRes - sRes)
1499     {
1500       column = sRes + x;
1501       if (av.hasHiddenColumns)
1502       {
1503         column = av.getColumnSelection().adjustForHiddenColumns(column);
1504       }
1505
1506       if (column > aaMax)
1507       {
1508         break;
1509       }
1510
1511       if (aa.annotations[column] == null)
1512       {
1513         x++;
1514         continue;
1515       }
1516       if (aa.annotations[column].colour == null)
1517         g.setColor(Color.black);
1518       else
1519         g.setColor(aa.annotations[column].colour);
1520
1521       y1 = y
1522               - (int) (((aa.annotations[column].value - min) / (range)) * aa.graphHeight);
1523
1524       if (renderHistogram)
1525       {
1526         if (y1 - y2 > 0)
1527         {
1528           g.fillRect(x * av.charWidth, y2, av.charWidth, y1 - y2);
1529         }
1530         else
1531         {
1532           g.fillRect(x * av.charWidth, y1, av.charWidth, y2 - y1);
1533         }
1534       }
1535       // draw profile if available
1536       if (renderProfile && aa.annotations[column].value != 0)
1537       {
1538         int profl[] = getProfileFor(aa, column);
1539         int ht = y1, htn = y2 - y1;// aa.graphHeight;
1540         float wdth;
1541         double ht2 = 0;
1542         char[] dc = new char[1];
1543         LineMetrics lm;
1544         for (int c = 1; profl != null && c < profl[0];)
1545         {
1546           dc[0] = (char) profl[c++];
1547           wdth = av.charWidth;
1548           wdth /= (float) fm.charsWidth(dc, 0, 1);
1549
1550           if (c > 2)
1551           {
1552             ht += (int) ht2;
1553           }
1554           {
1555             // if (aa.annotations[column].value==0) {
1556             // g.setFont(ofont.deriveFont(AffineTransform.getScaleInstance(wdth,
1557             // (ht2=(aa.graphHeight*0.1/av.charHeight)))));
1558             // ht = y2-(int)ht2;
1559             // } else {
1560             g.setFont(ofont.deriveFont(AffineTransform.getScaleInstance(
1561                     wdth, (ht2 = (htn * ((double) profl[c++]) / 100.0))
1562                             / av.charHeight)));
1563             lm = g.getFontMetrics().getLineMetrics(dc, 0, 1, g);
1564             // htn -=ht2;
1565             // }
1566             g.setColor(profcolour.findColour(dc[0])); // (av.globalColourScheme!=null)
1567                                                       // ? );// try to get a
1568                                                       // colourscheme for the
1569                                                       // group(aa.groupRef.cs==null)
1570                                                       // ? av.textColour2 :
1571                                                       // cs.findColour(dc));
1572             g.drawChars(dc, 0, 1, x * av.charWidth,
1573                     (int) (ht + lm.getHeight()));
1574             // ht+=g.getFontMetrics().getAscent()-g.getFontMetrics().getDescent();
1575           }
1576         }
1577         g.setFont(ofont);
1578       }
1579       x++;
1580     }
1581     if (aa.threshold != null)
1582     {
1583       g.setColor(aa.threshold.colour);
1584       Graphics2D g2 = (Graphics2D) g;
1585       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
1586               BasicStroke.JOIN_ROUND, 3f, new float[]
1587               { 5f, 3f }, 0f));
1588
1589       y2 = (int) (y - ((aa.threshold.value - min) / range) * aa.graphHeight);
1590       g.drawLine(0, y2, (eRes - sRes) * av.charWidth, y2);
1591       g2.setStroke(new BasicStroke());
1592     }
1593   }
1594
1595   private int[] getProfileFor(AlignmentAnnotation aa, int column)
1596   {
1597     if (aa.autoCalculated && aa.label.startsWith("Consensus"))
1598     {
1599       if (aa.groupRef != null && aa.groupRef.consensusData != null
1600               && aa.groupRef.isShowSequenceLogo())
1601       {
1602         return AAFrequency.extractProfile(
1603                 aa.groupRef.consensusData[column],
1604                 aa.groupRef.getIgnoreGapsConsensus());
1605       }
1606       // TODO extend annotation row to enable dynamic and static profile data to
1607       // be stored
1608       if (aa.groupRef == null && aa.sequenceRef == null
1609               && av.isShowSequenceLogo())
1610       {
1611         return AAFrequency.extractProfile(av.hconsensus[column],
1612                 av.getIgnoreGapsConsensus());
1613       }
1614     }
1615     return null;
1616   }
1617
1618   // used by overview window
1619   public void drawGraph(Graphics g, AlignmentAnnotation aa, int width,
1620           int y, int sRes, int eRes)
1621   {
1622     eRes = Math.min(eRes, aa.annotations.length);
1623     g.setColor(Color.white);
1624     g.fillRect(0, 0, width, y);
1625     g.setColor(new Color(0, 0, 180));
1626
1627     int x = 0, height;
1628
1629     for (int j = sRes; j < eRes; j++)
1630     {
1631       if (aa.annotations[j] != null)
1632       {
1633         if (aa.annotations[j].colour == null)
1634           g.setColor(Color.black);
1635         else
1636           g.setColor(aa.annotations[j].colour);
1637
1638         height = (int) ((aa.annotations[j].value / aa.graphMax) * y);
1639         if (height > y)
1640         {
1641           height = y;
1642         }
1643
1644         g.fillRect(x, y - height, av.charWidth, height);
1645       }
1646       x += av.charWidth;
1647     }
1648   }
1649
1650 }