update author list in license for (JAL-826)
[jalview.git] / src / jalview / appletgui / AnnotationPanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
3  * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, 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       calcPanelHeight();
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     int height = calcPanelHeight();
430     this.setSize(new Dimension(getSize().width, height));
431     if (repaint)
432     {
433       repaint();
434     }
435     return height;
436   }
437   /**
438    * calculate the height for visible annotation, revalidating bounds where necessary
439    * ABSTRACT GUI METHOD
440    * @return total height of annotation
441    */
442   public int calcPanelHeight()
443   {
444     // setHeight of panels
445     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
446     int height = 0;
447
448     if (aa != null)
449     {
450       for (int i = 0; i < aa.length; i++)
451       {
452         if (!aa[i].visible)
453         {
454           continue;
455         }
456
457         aa[i].height = 0;
458
459         if (aa[i].hasText)
460         {
461           aa[i].height += av.charHeight;
462         }
463
464         if (aa[i].hasIcons)
465         {
466           aa[i].height += 16;
467         }
468
469         if (aa[i].graph > 0)
470         {
471           aa[i].height += aa[i].graphHeight;
472         }
473
474         if (aa[i].height == 0)
475         {
476           aa[i].height = 20;
477         }
478
479         height += aa[i].height;
480       }
481     }
482     if (height == 0)
483     {
484       height = 20;
485     }
486
487     return height;
488
489   }
490
491   public void addEditableColumn(int i)
492   {
493     if (activeRow == -1)
494     {
495       AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
496       if (aa == null)
497       {
498         return;
499       }
500
501       for (int j = 0; j < aa.length; j++)
502       {
503         if (aa[j].editable)
504         {
505           activeRow = j;
506           break;
507         }
508       }
509     }
510
511     if (activeRes == null)
512     {
513       activeRes = new Vector();
514       activeRes.addElement(String.valueOf(i));
515       return;
516     }
517
518     activeRes.addElement(String.valueOf(i));
519   }
520
521   public void update(Graphics g)
522   {
523     paint(g);
524   }
525
526   public void paint(Graphics g)
527   {
528     Dimension d = getSize();
529     imgWidth = d.width;
530     // (av.endRes - av.startRes + 1) * av.charWidth;
531     if (imgWidth<1 || d.height<1)
532     {
533       return;
534     }
535     if (image == null || imgWidth != image.getWidth(this) || d.height != image.getHeight(this))
536     {
537       image = createImage(imgWidth, d.height);
538       gg = image.getGraphics();
539       gg.setFont(av.getFont());
540       fm = gg.getFontMetrics();
541       fastPaint = false;
542     }
543
544     if (fastPaint)
545     {
546       g.drawImage(image, 0, 0, this);
547       fastPaint = false;
548       return;
549     }
550
551     gg.setColor(Color.white);
552     gg.fillRect(0, 0, getSize().width, getSize().height);
553     drawComponent(gg, av.startRes, av.endRes + 1);
554
555     g.drawImage(image, 0, 0, this);
556   }
557
558   public void fastPaint(int horizontal)
559   {
560     if (horizontal == 0 || av.alignment.getAlignmentAnnotation() == null
561             || av.alignment.getAlignmentAnnotation().length < 1)
562     {
563       repaint();
564       return;
565     }
566
567     gg.copyArea(0, 0, imgWidth, getSize().height, -horizontal
568             * av.charWidth, 0);
569     int sr = av.startRes, er = av.endRes + 1, transX = 0;
570
571     if (horizontal > 0) // scrollbar pulled right, image to the left
572     {
573       transX = (er - sr - horizontal) * av.charWidth;
574       sr = er - horizontal;
575     }
576     else if (horizontal < 0)
577     {
578       er = sr - horizontal;
579     }
580
581     gg.translate(transX, 0);
582
583     drawComponent(gg, sr, er);
584
585     gg.translate(-transX, 0);
586
587     fastPaint = true;
588     repaint();
589   }
590
591   /**
592    * DOCUMENT ME!
593    * 
594    * @param g
595    *          DOCUMENT ME!
596    * @param startRes
597    *          DOCUMENT ME!
598    * @param endRes
599    *          DOCUMENT ME!
600    */
601   public void drawComponent(Graphics g, int startRes, int endRes)
602   {
603     Font ofont = av.getFont();
604     g.setFont(ofont);
605
606     g.setColor(Color.white);
607     g.fillRect(0, 0, (endRes - startRes) * av.charWidth, getSize().height);
608
609     if (fm == null)
610     {
611       fm = g.getFontMetrics();
612     }
613
614     if ((av.alignment.getAlignmentAnnotation() == null)
615             || (av.alignment.getAlignmentAnnotation().length < 1))
616     {
617       g.setColor(Color.white);
618       g.fillRect(0, 0, getSize().width, getSize().height);
619       g.setColor(Color.black);
620       if (av.validCharWidth)
621       {
622         g.drawString("Alignment has no annotations", 20, 15);
623       }
624
625       return;
626     }
627
628     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
629     g.translate(0, -scrollOffset);
630     int x = 0;
631     int y = 0;
632     int column = 0;
633     char lastSS;
634     int lastSSX;
635     int iconOffset = av.charHeight / 2;
636     boolean validRes = false;
637     boolean validEnd = false;
638     boolean labelAllCols = false;
639     boolean centreColLabels, centreColLabelsDef = av
640             .getCentreColumnLabels();
641     boolean scaleColLabel = false;
642     boolean[] graphGroupDrawn = new boolean[aa.length];
643     int charOffset = 0; // offset for a label
644     float fmWidth, fmScaling = 1f; // scaling for a label to fit it into a
645                                    // column.
646     // \u03B2 \u03B1
647     for (int i = 0; i < aa.length; i++)
648     {
649       AlignmentAnnotation row = aa[i];
650
651       if (!row.visible)
652       {
653         continue;
654       }
655       centreColLabels = row.centreColLabels || centreColLabelsDef;
656       labelAllCols = row.showAllColLabels;
657       scaleColLabel = row.scaleColLabel;
658       lastSS = ' ';
659       lastSSX = 0;
660
661       if (row.graph > 0)
662       {
663         if (row.graphGroup > -1 && graphGroupDrawn[row.graphGroup])
664         {
665           continue;
666         }
667
668         // this is so that we draw the characters below the graph
669         y += row.height;
670
671         if (row.hasText)
672         {
673           iconOffset = av.charHeight - fm.getDescent();
674           y -= av.charHeight;
675         }
676       }
677       // TODO: else is the logic used in application, applet had no 'else'
678       else if (row.hasText)
679       {
680         iconOffset = av.charHeight - fm.getDescent();
681
682       }
683       else
684       {
685         iconOffset = 0;
686       }
687
688       x = 0;
689       while (x < endRes - startRes)
690       {
691         if (av.hasHiddenColumns)
692         {
693           column = av.getColumnSelection().adjustForHiddenColumns(
694                   startRes + x);
695           if (column > row.annotations.length - 1)
696           {
697             break;
698           }
699         }
700         else
701         {
702           column = startRes + x;
703         }
704
705         if ((row.annotations.length <= column)
706                 || (row.annotations[column] == null))
707         {
708           validRes = false;
709         }
710         else
711         {
712           validRes = true;
713         }
714
715         if (activeRow == i)
716         {
717           g.setColor(Color.red);
718
719           if (av.getColumnSelection() != null)
720           {
721             for (int n = 0; n < av.getColumnSelection().size(); n++)
722             {
723               int v = av.getColumnSelection().columnAt(n);
724
725               if (v == column)
726               {
727                 g.fillRect(x * av.charWidth, y, av.charWidth, av.charHeight);
728               }
729             }
730           }
731         }
732
733         if (av.validCharWidth
734                 && validRes
735                 && (row.annotations[column].displayCharacter != null && row.annotations[column].displayCharacter
736                         .length() > 0))
737         {
738
739           if (centreColLabels || scaleColLabel)
740           {
741             fmWidth = (float) fm.charsWidth(
742                     row.annotations[column].displayCharacter.toCharArray(),
743                     0, row.annotations[column].displayCharacter.length());
744
745             if (scaleColLabel)
746             {
747               // justify the label and scale to fit in column
748               if (fmWidth > av.charWidth)
749               {
750                 // scale only if the current font isn't already small enough
751                 fmScaling = av.charWidth;
752                 fmScaling /= fmWidth;
753                 // not 1.1 // g.setFont(new
754                 // Font(ofont,AffineTransform.getScaleInstance(fmScaling,
755                 // 1.0)));
756                 // and update the label's width to reflect the scaling.
757                 fmWidth = av.charWidth;
758               }
759             }
760           }
761           else
762           {
763             fmWidth = (float) fm
764                     .charWidth(row.annotations[column].displayCharacter
765                             .charAt(0));
766           }
767           charOffset = (int) ((av.charWidth - fmWidth) / 2f);
768
769           if (row.annotations[column].colour == null)
770             g.setColor(Color.black);
771           else
772             g.setColor(row.annotations[column].colour);
773
774           if (column == 0 || row.graph > 0)
775           {
776             g.drawString(row.annotations[column].displayCharacter,
777                     (x * av.charWidth) + charOffset, y + iconOffset + 3); // +
778                                                                           // 3?
779           }
780           else if (row.annotations[column - 1] == null
781                   || (labelAllCols
782                           || !row.annotations[column].displayCharacter
783                                   .equals(row.annotations[column - 1].displayCharacter) || (row.annotations[column].displayCharacter
784                           .length() < 2 && row.annotations[column].secondaryStructure == ' ')))
785           {
786             g.drawString(row.annotations[column].displayCharacter,
787                     (x * av.charWidth) + charOffset, y + iconOffset + 3); // +3?
788           }
789           g.setFont(ofont);
790         }
791
792         if (row.hasIcons)
793         {
794           if (!validRes
795                   || (row.annotations[column].secondaryStructure != lastSS))
796           {
797             switch (lastSS)
798             {
799             case 'H':
800               g.setColor(HELIX_COLOUR);
801               if (MAC)
802               {
803                 // Off by 1 offset when drawing rects and ovals
804                 // to offscreen image on the MAC
805                 g.fillRoundRect(lastSSX, y + 4 + iconOffset,
806                         (x * av.charWidth) - lastSSX, 7, 8, 8);
807                 break;
808               }
809
810               int sCol = (lastSSX / av.charWidth) + startRes;
811               int x1 = lastSSX;
812               int x2 = (x * av.charWidth);
813
814               if (sCol == 0
815                       || row.annotations[sCol - 1] == null
816                       || row.annotations[sCol - 1].secondaryStructure != 'H')
817               {
818                 g.fillArc(lastSSX, y + 4 + iconOffset, av.charWidth, 8, 90,
819                         180);
820                 x1 += av.charWidth / 2;
821               }
822
823               if (!validRes || row.annotations[column] == null
824                       || row.annotations[column].secondaryStructure != 'H')
825               {
826                 g.fillArc((x * av.charWidth) - av.charWidth, y + 4
827                         + iconOffset, av.charWidth, 8, 270, 180);
828                 x2 -= av.charWidth / 2;
829               }
830
831               g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 8);
832               break;
833
834             case 'E':
835               g.setColor(SHEET_COLOUR);
836               g.fillRect(lastSSX, y + 4 + iconOffset, (x * av.charWidth)
837                       - lastSSX - 4, 7);
838               g.fillPolygon(new int[]
839               { (x * av.charWidth) - 4, (x * av.charWidth) - 4,
840                   (x * av.charWidth) }, new int[]
841               { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset },
842                       3);
843
844               break;
845
846             default:
847               g.setColor(Color.gray);
848               g.fillRect(lastSSX, y + 6 + iconOffset, (x * av.charWidth)
849                       - lastSSX, 2);
850
851               break;
852             }
853
854             if (validRes)
855             {
856               lastSS = row.annotations[column].secondaryStructure;
857             }
858             else
859             {
860               lastSS = ' ';
861             }
862
863             lastSSX = (x * av.charWidth);
864           }
865         }
866
867         column++;
868         x++;
869       }
870
871       if (column >= row.annotations.length)
872       {
873         column = row.annotations.length - 1;
874         validEnd = false;
875       }
876       else
877       {
878         validEnd = true;
879       }
880
881       // x ++;
882
883       if (row.hasIcons)
884       {
885         switch (lastSS)
886         {
887         case 'H':
888           g.setColor(HELIX_COLOUR);
889           if (MAC)
890           {
891             // Off by 1 offset when drawing rects and ovals
892             // to offscreen image on the MAC
893             g.fillRoundRect(lastSSX, y + 4 + iconOffset, (x * av.charWidth)
894                     - lastSSX, 7, 8, 8);
895             break;
896           }
897
898           int sCol = (lastSSX / av.charWidth) + startRes;
899           int x1 = lastSSX;
900           int x2 = (x * av.charWidth);
901
902           if (sCol == 0 || row.annotations[sCol - 1] == null
903                   || row.annotations[sCol - 1].secondaryStructure != 'H')
904           {
905             g.fillArc(lastSSX, y + 4 + iconOffset, av.charWidth, 8, 90, 180);
906             x1 += av.charWidth / 2;
907           }
908
909           if (row.annotations[column] == null
910                   || row.annotations[column].secondaryStructure != 'H')
911           {
912             g.fillArc((x * av.charWidth) - av.charWidth,
913                     y + 4 + iconOffset, av.charWidth, 8, 270, 180);
914             x2 -= av.charWidth / 2;
915           }
916
917           g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 8);
918
919           break;
920
921         case 'E':
922           g.setColor(SHEET_COLOUR);
923
924           if (!validEnd || row.annotations[endRes] == null
925                   || row.annotations[endRes].secondaryStructure != 'E')
926           {
927             g.fillRect(lastSSX, y + 4 + iconOffset, (x * av.charWidth)
928                     - lastSSX - 4, 7);
929             g.fillPolygon(new int[]
930             { (x * av.charWidth) - 4, (x * av.charWidth) - 4,
931                 (x * av.charWidth) }, new int[]
932             { y + iconOffset, y + 14 + iconOffset, y + 7 + iconOffset }, 3);
933           }
934           else
935           {
936             g.fillRect(lastSSX, y + 4 + iconOffset, x * av.charWidth
937                     - lastSSX, 7);
938           }
939           break;
940
941         default:
942           g.setColor(Color.gray);
943           if (!av.wrapAlignment || endRes == av.endRes)
944           {
945             g.fillRect(lastSSX, y + 6 + iconOffset, (x * av.charWidth)
946                     - lastSSX, 2);
947           }
948
949           break;
950         }
951       }
952
953       if (row.graph > 0 && row.graphHeight > 0)
954       {
955         if (row.graph == AlignmentAnnotation.LINE_GRAPH)
956         {
957           if (row.graphGroup > -1 && !graphGroupDrawn[row.graphGroup])
958           {
959             float groupmax = -999999, groupmin = 9999999;
960             for (int gg = 0; gg < aa.length; gg++)
961             {
962               if (aa[gg].graphGroup != row.graphGroup)
963               {
964                 continue;
965               }
966
967               if (aa[gg] != row)
968               {
969                 aa[gg].visible = false;
970               }
971
972               if (aa[gg].graphMax > groupmax)
973               {
974                 groupmax = aa[gg].graphMax;
975               }
976               if (aa[gg].graphMin < groupmin)
977               {
978                 groupmin = aa[gg].graphMin;
979               }
980             }
981
982             for (int gg = 0; gg < aa.length; gg++)
983             {
984               if (aa[gg].graphGroup == row.graphGroup)
985               {
986                 drawLineGraph(g, aa[gg], startRes, endRes, y, groupmin,
987                         groupmax, row.graphHeight);
988               }
989             }
990
991             graphGroupDrawn[row.graphGroup] = true;
992           }
993           else
994           {
995             drawLineGraph(g, row, startRes, endRes, y, row.graphMin,
996                     row.graphMax, row.graphHeight);
997           }
998         }
999         else if (row.graph == AlignmentAnnotation.BAR_GRAPH)
1000         {
1001           drawBarGraph(g, row, startRes, endRes, row.graphMin,
1002                   row.graphMax, y);
1003         }
1004       }
1005
1006       if (row.graph > 0 && row.hasText)
1007       {
1008         y += av.charHeight;
1009       }
1010
1011       if (row.graph == 0)
1012       {
1013         y += aa[i].height;
1014       }
1015     }
1016     g.translate(0, +scrollOffset);
1017   }
1018
1019   public void drawLineGraph(Graphics g, AlignmentAnnotation aa, int sRes,
1020           int eRes, int y, float min, float max, int graphHeight)
1021   {
1022     if (sRes > aa.annotations.length)
1023     {
1024       return;
1025     }
1026
1027     int x = 0;
1028
1029     // Adjustment for fastpaint to left
1030     if (eRes < av.endRes)
1031     {
1032       eRes++;
1033     }
1034
1035     eRes = Math.min(eRes, aa.annotations.length);
1036
1037     int y1 = y, y2 = y;
1038     float range = max - min;
1039
1040     // //Draw origin
1041     if (min < 0)
1042     {
1043       y2 = y - (int) ((0 - min / range) * graphHeight);
1044     }
1045
1046     g.setColor(Color.gray);
1047     g.drawLine(x - av.charWidth, y2, (eRes - sRes) * av.charWidth, y2);
1048
1049     eRes = Math.min(eRes, aa.annotations.length);
1050
1051     int column;
1052     int aaMax = aa.annotations.length - 1;
1053
1054     while (x < eRes - sRes)
1055     {
1056       column = sRes + x;
1057       if (av.hasHiddenColumns)
1058       {
1059         column = av.getColumnSelection().adjustForHiddenColumns(column);
1060       }
1061
1062       if (column > aaMax)
1063       {
1064         break;
1065       }
1066
1067       if (aa.annotations[column] == null) // || coaa.annotations[column - 1] ==
1068       // null)
1069       {
1070         x++;
1071         continue;
1072       }
1073
1074       if (aa.annotations[column].colour == null)
1075         g.setColor(Color.black);
1076       else
1077         g.setColor(aa.annotations[column].colour);
1078       if (column == 0 || aa.annotations[column - 1] == null)
1079       {
1080         y1 = y
1081                 - (int) (((aa.annotations[column].value - min) / range) * graphHeight);
1082       }
1083       else
1084       {
1085         y1 = y
1086                 - (int) (((aa.annotations[column - 1].value - min) / range) * graphHeight);
1087       }
1088       y2 = y
1089               - (int) (((aa.annotations[column].value - min) / range) * graphHeight);
1090
1091       g.drawLine(x * av.charWidth - av.charWidth / 2, y1, x * av.charWidth
1092               + av.charWidth / 2, y2);
1093       x++;
1094     }
1095
1096     if (aa.threshold != null)
1097     {
1098       g.setColor(aa.threshold.colour);
1099
1100       y2 = (int) (y - ((aa.threshold.value - min) / range) * graphHeight);
1101       g.drawLine(0, y2, (eRes - sRes) * av.charWidth, y2);
1102     }
1103   }
1104
1105   public void drawBarGraph(Graphics g, AlignmentAnnotation aa, int sRes,
1106           int eRes, float min, float max, int y)
1107   {
1108     ColourSchemeI profcolour = av.getGlobalColourScheme();
1109     if (profcolour == null)
1110     {
1111       profcolour = new jalview.schemes.ZappoColourScheme();
1112     }
1113     if (sRes > aa.annotations.length)
1114     {
1115       return;
1116     }
1117     Font ofont = g.getFont();
1118     eRes = Math.min(eRes, aa.annotations.length);
1119
1120     int x = 0, y1 = y, y2 = y;
1121
1122     float range = max - min;
1123
1124     if (min < 0)
1125     {
1126       y2 = y - (int) ((0 - min / (range)) * aa.graphHeight);
1127     }
1128
1129     g.setColor(Color.gray);
1130
1131     g.drawLine(x, y2, (eRes - sRes) * av.charWidth, y2);
1132
1133     int column;
1134     int aaMax = aa.annotations.length - 1;
1135     boolean renderHistogram = true, renderProfile = false;
1136     if (aa.autoCalculated && aa.label.startsWith("Consensus"))
1137     { // TODO: generalise this to have render styles for consensus/profile data
1138       if (aa.groupRef != null)
1139       {
1140         renderHistogram = aa.groupRef.isShowConsensusHistogram();
1141         renderProfile = aa.groupRef.isShowSequenceLogo();
1142       }
1143       else
1144       {
1145         renderHistogram = av.isShowConsensusHistogram();
1146         renderProfile = av.isShowSequenceLogo();
1147       }
1148     }
1149
1150     while (x < eRes - sRes)
1151     {
1152       column = sRes + x;
1153       if (av.hasHiddenColumns)
1154       {
1155         column = av.getColumnSelection().adjustForHiddenColumns(column);
1156       }
1157
1158       if (column > aaMax)
1159       {
1160         break;
1161       }
1162
1163       if (aa.annotations[column] == null)
1164       {
1165         x++;
1166         continue;
1167       }
1168
1169       if (aa.annotations[column].colour == null)
1170         g.setColor(Color.black);
1171       else
1172         g.setColor(aa.annotations[column].colour);
1173
1174       y1 = y
1175               - (int) (((aa.annotations[column].value - min) / (range)) * aa.graphHeight);
1176
1177       if (renderHistogram)
1178       {
1179         if (y1 - y2 > 0)
1180         {
1181           g.fillRect(x * av.charWidth, y2, av.charWidth, y1 - y2);
1182         }
1183         else
1184         {
1185           g.fillRect(x * av.charWidth, y1, av.charWidth, y2 - y1);
1186         }
1187       }
1188       // draw profile if available
1189       if (aa.annotations[column].value != 0 && renderProfile)
1190       {
1191         int profl[] = getProfileFor(aa, column);
1192         int ht = y1, htn = y2 - y1;// aa.graphHeight;
1193         float wdth;
1194         double ht2 = 0;
1195         char[] dc = new char[1];
1196         LineMetrics lm;
1197         for (int c = 1; profl != null && c < profl[0];)
1198         {
1199           dc[0] = (char) profl[c++];
1200           wdth = av.charWidth;
1201           wdth /= (float) fm.charsWidth(dc, 0, 1);
1202
1203           if (c > 2)
1204           {
1205             ht += (int) ht2;
1206           }
1207           { // not java 1.1 compatible: Bug # 0060064
1208             g.setFont(ofont.deriveFont(AffineTransform.getScaleInstance(
1209                     wdth, (ht2 = (htn * ((double) profl[c++]) / 100.0))
1210                             / av.charHeight)));
1211             lm = g.getFontMetrics().getLineMetrics(dc, 0, 1, g);
1212             g.setColor(profcolour.findColour(dc[0]));
1213             g.drawChars(dc, 0, 1, x * av.charWidth,
1214                     (int) (ht + lm.getHeight()));
1215           }
1216         }
1217         g.setFont(ofont);
1218       }
1219
1220       x++;
1221
1222     }
1223     if (aa.threshold != null)
1224     {
1225       g.setColor(aa.threshold.colour);
1226       y2 = (int) (y - ((aa.threshold.value - min) / range) * aa.graphHeight);
1227       g.drawLine(0, y2, (eRes - sRes) * av.charWidth, y2);
1228     }
1229   }
1230
1231   private int[] getProfileFor(AlignmentAnnotation aa, int column)
1232   {
1233     // if (aa.autoCalculated && aa.label.startsWith("Consensus")) {
1234     if (aa.groupRef != null && aa.groupRef.consensusData != null)
1235     {
1236       // && aa.groupRef.isShowSequenceLogo()) {
1237       return AAFrequency.extractProfile(aa.groupRef.consensusData[column],
1238               aa.groupRef.getIgnoreGapsConsensus());
1239     }
1240     // TODO extend annotation row to enable dynamic and static profile data to
1241     // be stored
1242     if (aa.groupRef == null && aa.sequenceRef == null)
1243     // && av.isShowSequenceLogo())
1244     {
1245       return AAFrequency.extractProfile(av.hconsensus[column],
1246               av.getIgnoreGapsConsensus());
1247     }
1248     // }
1249     return null;
1250   }
1251
1252   // used by overview window
1253   public void drawGraph(Graphics g, AlignmentAnnotation aa, int width,
1254           int y, int sRes, int eRes)
1255   {
1256     eRes = Math.min(eRes, aa.annotations.length);
1257     g.setColor(Color.white);
1258     g.fillRect(0, 0, width, y);
1259     g.setColor(new Color(0, 0, 180));
1260
1261     int x = 0, height;
1262
1263     for (int j = sRes; j < eRes; j++)
1264     {
1265       if (aa.annotations[j].colour == null)
1266         g.setColor(Color.black);
1267       else
1268         g.setColor(aa.annotations[j].colour);
1269
1270       height = (int) ((aa.annotations[j].value / aa.graphMax) * GRAPH_HEIGHT);
1271       if (height > y)
1272       {
1273         height = y;
1274       }
1275       g.fillRect(x, y - height, av.charWidth, height);
1276       x += av.charWidth;
1277     }
1278   }
1279
1280   int scrollOffset = 0;
1281
1282   public void setScrollOffset(int value)
1283   {
1284     scrollOffset = value;
1285     repaint();
1286   }
1287 }