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