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