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