fix null pointer exception in redraw for short sequence associated annotation rows
[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)
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[] graphGroupDrawn = new boolean[aa.length];
775
776     // \u03B2 \u03B1
777     for (int i = 0; i < aa.length; i++)
778     {
779       AlignmentAnnotation row = aa[i];
780
781       if (!row.visible)
782       {
783         continue;
784       }
785
786       lastSS = ' ';
787       lastSSX = 0;
788
789       if (row.graph > 0)
790       {
791         if (row.graphGroup > -1 && graphGroupDrawn[row.graphGroup])
792         {
793           continue;
794         }
795
796         // this is so that we draw the characters below the graph
797         y += row.height;
798
799         if (row.hasText)
800         {
801           iconOffset = av.charHeight - fm.getDescent();
802           y -= av.charHeight;
803         }
804       }
805       else if (row.hasText)
806       {
807         iconOffset = av.charHeight - fm.getDescent();
808
809       }
810       else
811       {
812         iconOffset = 0;
813       }
814
815       if (av.updatingConsensus && aa[i] == av.consensus)
816       {
817         y += av.charHeight;
818
819         g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0, y
820                 - row.height, imgWidth, y, this);
821         g.setColor(Color.black);
822         // g.drawString("Calculating Consensus....",20, y-row.height/2);
823
824         continue;
825       }
826       else if (av.updatingConservation
827               && aa[i].label.equals("Conservation"))
828       {
829
830         y += av.charHeight;
831         g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0, y
832                 - row.height, imgWidth, y, this);
833
834         g.setColor(Color.black);
835         // g.drawString("Calculating Conservation.....",20, y-row.height/2);
836
837         continue;
838       }
839       else if (av.updatingConservation && aa[i].label.equals("Quality"))
840       {
841
842         y += av.charHeight;
843         g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0, y
844                 - row.height, imgWidth, y, this);
845         g.setColor(Color.black);
846         // / g.drawString("Calculating Quality....",20, y-row.height/2);
847
848         continue;
849       }
850
851       x = 0;
852       while (x < endRes - startRes)
853       {
854         if (av.hasHiddenColumns)
855         {
856           column = av.getColumnSelection().adjustForHiddenColumns(
857                   startRes + x);
858           if (column > row.annotations.length - 1)
859           {
860             break;
861           }
862         }
863         else
864         {
865           column = startRes + x;
866         }
867
868         if ((row.annotations == null) || (row.annotations.length <= column)
869                 || (row.annotations[column] == null))
870         {
871           validRes = false;
872         }
873         else
874         {
875           validRes = true;
876         }
877
878         if (activeRow == i)
879         {
880           g.setColor(Color.red);
881
882           if (av.getColumnSelection() != null)
883           {
884             for (int n = 0; n < av.getColumnSelection().size(); n++)
885             {
886               int v = av.getColumnSelection().columnAt(n);
887
888               if (v == column)
889               {
890                 g
891                         .fillRect(x * av.charWidth, y, av.charWidth,
892                                 av.charHeight);
893               }
894             }
895           }
896         }
897
898         if (av.validCharWidth && validRes
899                 && row.annotations[column].displayCharacter != null
900                 && (row.annotations[column].displayCharacter.length() > 0))
901         {
902
903           int charOffset = (av.getCentreColumnLabels()) ? ((av.charWidth - fm
904                   .charsWidth(row.annotations[column].displayCharacter
905                           .toCharArray(), 0,
906                           row.annotations[column].displayCharacter.length())) / 2)
907                   : (av.charWidth - fm
908                           .charWidth(row.annotations[column].displayCharacter
909                                   .charAt(0))) / 2;
910
911           if (row.annotations[column].colour == null)
912             g.setColor(Color.black);
913           else
914             g.setColor(row.annotations[column].colour);
915
916           if (column == 0 || row.graph > 0)
917           {
918             g.drawString(row.annotations[column].displayCharacter,
919                     (x * av.charWidth) + charOffset, y + iconOffset);
920           }
921           else if (row.annotations[column - 1] == null
922                   || (!row.annotations[column].displayCharacter
923                           .equals(row.annotations[column - 1].displayCharacter) || (row.annotations[column].displayCharacter
924                           .length() < 2 && row.annotations[column].secondaryStructure == ' ')))
925           {
926             g.drawString(row.annotations[column].displayCharacter, x
927                     * av.charWidth + charOffset, y + iconOffset);
928           }
929         }
930
931         if (row.hasIcons)
932         {
933           if (!validRes
934                   || (row.annotations[column].secondaryStructure != lastSS))
935           {
936             switch (lastSS)
937             {
938             case 'H':
939               g.setColor(HELIX_COLOUR);
940               if (MAC)
941               {
942                 // Off by 1 offset when drawing rects and ovals
943                 // to offscreen image on the MAC
944                 g.fillRoundRect(lastSSX, y + 4 + iconOffset,
945                         (x * av.charWidth) - lastSSX, 7, 8, 8);
946                 break;
947               }
948
949               int sCol = (lastSSX / av.charWidth) + startRes;
950               int x1 = lastSSX;
951               int x2 = (x * av.charWidth);
952
953               if (sCol == 0
954                       || row.annotations[sCol - 1] == null
955                       || row.annotations[sCol - 1].secondaryStructure != 'H')
956               {
957                 g.fillArc(lastSSX, y + 4 + iconOffset, av.charWidth, 8, 90,
958                         180);
959                 x1 += av.charWidth / 2;
960               }
961
962               if (!validRes || row.annotations[column] == null
963                       || row.annotations[column].secondaryStructure != 'H')
964               {
965                 g.fillArc((x * av.charWidth) - av.charWidth, y + 4
966                         + iconOffset, av.charWidth, 8, 270, 180);
967                 x2 -= av.charWidth / 2;
968               }
969
970               g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 8);
971               break;
972
973             case 'E':
974               g.setColor(SHEET_COLOUR);
975               g.fillRect(lastSSX, y + 4 + iconOffset, (x * av.charWidth)
976                       - lastSSX - 4, 7);
977               g.fillPolygon(new int[]
978               { (x * av.charWidth) - 4, (x * av.charWidth) - 4,
979                   (x * av.charWidth) }, new int[]
980               { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset },
981                       3);
982
983               break;
984
985             default:
986               g.setColor(Color.gray);
987               g.fillRect(lastSSX, y + 6 + iconOffset, (x * av.charWidth)
988                       - lastSSX, 2);
989
990               break;
991             }
992
993             if (validRes)
994             {
995               lastSS = row.annotations[column].secondaryStructure;
996             }
997             else
998             {
999               lastSS = ' ';
1000             }
1001
1002             lastSSX = (x * av.charWidth);
1003           }
1004         }
1005
1006         column++;
1007         x++;
1008       }
1009
1010       if (column >= row.annotations.length)
1011       {
1012         column = row.annotations.length - 1;
1013         validEnd = false;
1014       }
1015       else
1016       {
1017         validEnd = true;
1018       }
1019
1020       // x ++;
1021
1022       if (row.hasIcons)
1023       {
1024         switch (lastSS)
1025         {
1026         case 'H':
1027           g.setColor(HELIX_COLOUR);
1028           if (MAC)
1029           {
1030             // Off by 1 offset when drawing rects and ovals
1031             // to offscreen image on the MAC
1032             g.fillRoundRect(lastSSX, y + 4 + iconOffset, (x * av.charWidth)
1033                     - lastSSX, 7, 8, 8);
1034             break;
1035           }
1036
1037           int sCol = (lastSSX / av.charWidth) + startRes;
1038           int x1 = lastSSX;
1039           int x2 = (x * av.charWidth);
1040
1041           if (sCol == 0 || row.annotations[sCol - 1] == null
1042                   || row.annotations[sCol - 1].secondaryStructure != 'H')
1043           {
1044             g
1045                     .fillArc(lastSSX, y + 4 + iconOffset, av.charWidth, 8,
1046                             90, 180);
1047             x1 += av.charWidth / 2;
1048           }
1049
1050           if (row.annotations[column] == null
1051                   || row.annotations[column].secondaryStructure != 'H')
1052           {
1053             g.fillArc((x * av.charWidth) - av.charWidth,
1054                     y + 4 + iconOffset, av.charWidth, 8, 270, 180);
1055             x2 -= av.charWidth / 2;
1056           }
1057
1058           g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 8);
1059
1060           break;
1061
1062         case 'E':
1063           g.setColor(SHEET_COLOUR);
1064
1065           if (!validEnd || row.annotations[endRes] == null
1066                   || row.annotations[endRes].secondaryStructure != 'E')
1067           {
1068             g.fillRect(lastSSX, y + 4 + iconOffset, (x * av.charWidth)
1069                     - lastSSX - 4, 7);
1070             g.fillPolygon(new int[]
1071             { (x * av.charWidth) - 4, (x * av.charWidth) - 4,
1072                 (x * av.charWidth) }, new int[]
1073             { y + iconOffset, y + 14 + iconOffset, y + 7 + iconOffset }, 3);
1074           }
1075           else
1076           {
1077             g.fillRect(lastSSX, y + 4 + iconOffset, (x + 1) * av.charWidth
1078                     - lastSSX, 7);
1079           }
1080           break;
1081
1082         default:
1083           g.setColor(Color.gray);
1084           g.fillRect(lastSSX, y + 6 + iconOffset, (x * av.charWidth)
1085                   - lastSSX, 2);
1086
1087           break;
1088         }
1089       }
1090
1091       if (row.graph > 0 && row.graphHeight > 0)
1092       {
1093         if (row.graph == AlignmentAnnotation.LINE_GRAPH)
1094         {
1095           if (row.graphGroup > -1 && !graphGroupDrawn[row.graphGroup])
1096           {
1097             float groupmax = -999999, groupmin = 9999999;
1098             for (int gg = 0; gg < aa.length; gg++)
1099             {
1100               if (aa[gg].graphGroup != row.graphGroup)
1101               {
1102                 continue;
1103               }
1104
1105               if (aa[gg] != row)
1106               {
1107                 aa[gg].visible = false;
1108               }
1109
1110               if (aa[gg].graphMax > groupmax)
1111               {
1112                 groupmax = aa[gg].graphMax;
1113               }
1114               if (aa[gg].graphMin < groupmin)
1115               {
1116                 groupmin = aa[gg].graphMin;
1117               }
1118             }
1119
1120             for (int gg = 0; gg < aa.length; gg++)
1121             {
1122               if (aa[gg].graphGroup == row.graphGroup)
1123               {
1124                 drawLineGraph(g, aa[gg], startRes, endRes, y, groupmin,
1125                         groupmax, row.graphHeight);
1126               }
1127             }
1128
1129             graphGroupDrawn[row.graphGroup] = true;
1130           }
1131           else
1132           {
1133             drawLineGraph(g, row, startRes, endRes, y, row.graphMin,
1134                     row.graphMax, row.graphHeight);
1135           }
1136         }
1137         else if (row.graph == AlignmentAnnotation.BAR_GRAPH)
1138         {
1139           drawBarGraph(g, row, startRes, endRes, row.graphMin,
1140                   row.graphMax, y);
1141         }
1142       }
1143
1144       if (row.graph > 0 && row.hasText)
1145       {
1146         y += av.charHeight;
1147       }
1148
1149       if (row.graph == 0)
1150       {
1151         y += aa[i].height;
1152       }
1153     }
1154   }
1155
1156   public void drawLineGraph(Graphics g, AlignmentAnnotation aa, int sRes,
1157           int eRes, int y, float min, float max, int graphHeight)
1158   {
1159     if (sRes > aa.annotations.length)
1160     {
1161       return;
1162     }
1163
1164     int x = 0;
1165
1166     // Adjustment for fastpaint to left
1167     if (eRes < av.endRes)
1168     {
1169       eRes++;
1170     }
1171
1172     eRes = Math.min(eRes, aa.annotations.length);
1173
1174     if (sRes == 0)
1175     {
1176       x++;
1177     }
1178
1179     int y1 = y, y2 = y;
1180     float range = max - min;
1181
1182     // //Draw origin
1183     if (min < 0)
1184     {
1185       y2 = y - (int) ((0 - min / range) * graphHeight);
1186     }
1187
1188     g.setColor(Color.gray);
1189     g.drawLine(x - av.charWidth, y2, (eRes - sRes + 1) * av.charWidth, y2);
1190
1191     eRes = Math.min(eRes, aa.annotations.length);
1192
1193     int column;
1194     int aaMax = aa.annotations.length - 1;
1195
1196     while (x < eRes - sRes)
1197     {
1198       column = sRes + x;
1199       if (av.hasHiddenColumns)
1200       {
1201         column = av.getColumnSelection().adjustForHiddenColumns(column);
1202       }
1203
1204       if (column > aaMax)
1205       {
1206         break;
1207       }
1208
1209       if (aa.annotations[column] == null
1210               || aa.annotations[column - 1] == null)
1211       {
1212         x++;
1213         continue;
1214       }
1215
1216       if (aa.annotations[column].colour == null)
1217         g.setColor(Color.black);
1218       else
1219         g.setColor(aa.annotations[column].colour);
1220
1221       y1 = y
1222               - (int) (((aa.annotations[column - 1].value - min) / range) * graphHeight);
1223       y2 = y
1224               - (int) (((aa.annotations[column].value - min) / range) * graphHeight);
1225
1226       g.drawLine(x * av.charWidth - av.charWidth / 2, y1, x * av.charWidth
1227               + av.charWidth / 2, y2);
1228       x++;
1229     }
1230
1231     if (aa.threshold != null)
1232     {
1233       g.setColor(aa.threshold.colour);
1234       Graphics2D g2 = (Graphics2D) g;
1235       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
1236               BasicStroke.JOIN_ROUND, 3f, new float[]
1237               { 5f, 3f }, 0f));
1238
1239       y2 = (int) (y - ((aa.threshold.value - min) / range) * graphHeight);
1240       g.drawLine(0, y2, (eRes - sRes) * av.charWidth, y2);
1241       g2.setStroke(new BasicStroke());
1242     }
1243   }
1244
1245   public void drawBarGraph(Graphics g, AlignmentAnnotation aa, int sRes,
1246           int eRes, float min, float max, int y)
1247   {
1248     ColourSchemeI profcolour = av.getGlobalColourScheme();
1249     if (profcolour==null)
1250     {
1251       profcolour = new jalview.schemes.ZappoColourScheme();
1252     }
1253     if (sRes > aa.annotations.length)
1254     {
1255       return;
1256     }
1257     Font ofont = g.getFont();
1258     eRes = Math.min(eRes, aa.annotations.length);
1259
1260     int x = 0, y1 = y, y2 = y;
1261
1262     float range = max - min;
1263
1264     if (min < 0)
1265     {
1266       y2 = y - (int) ((0 - min / (range)) * aa.graphHeight);
1267     }
1268
1269     g.setColor(Color.gray);
1270
1271     g.drawLine(x, y2, (eRes - sRes) * av.charWidth, y2);
1272
1273     int column;
1274     int aaMax = aa.annotations.length - 1;
1275
1276     while (x < eRes - sRes)
1277     {
1278       column = sRes + x;
1279       if (av.hasHiddenColumns)
1280       {
1281         column = av.getColumnSelection().adjustForHiddenColumns(column);
1282       }
1283
1284       if (column > aaMax)
1285       {
1286         break;
1287       }
1288
1289       if (aa.annotations[column] == null)
1290       {
1291         x++;
1292         continue;
1293       }
1294
1295       if (aa.annotations[column].colour == null)
1296         g.setColor(Color.black);
1297       else
1298         g.setColor(aa.annotations[column].colour);
1299
1300       y1 = y
1301               - (int) (((aa.annotations[column].value - min) / (range)) * aa.graphHeight);
1302
1303       if (y1 - y2 > 0)
1304       {
1305         g.fillRect(x * av.charWidth, y2, av.charWidth, y1 - y2);
1306       }
1307       else
1308       {
1309         g.fillRect(x * av.charWidth, y1, av.charWidth, y2 - y1);
1310       }
1311       // draw profile if available
1312       if (aa.annotations[column].value!=0)
1313       {
1314         int profl[] = getProfileFor(aa,column);
1315         int ht = y1,htn=y2-y1;//aa.graphHeight;
1316           float wdth;
1317           double ht2=0;
1318           char[] dc = new char[1];
1319           LineMetrics lm;
1320           for (int c=1;profl!=null && c<profl[0];)
1321           {
1322             dc[0] = (char) profl[c++];
1323             wdth = av.charWidth;
1324             wdth/=(float) fm
1325                     .charsWidth(dc,0,1);
1326             
1327             if (c>2)
1328             {
1329               ht+=(int)ht2;
1330             }
1331             {
1332               //if (aa.annotations[column].value==0) {
1333               //  g.setFont(ofont.deriveFont(AffineTransform.getScaleInstance(wdth, (ht2=(aa.graphHeight*0.1/av.charHeight)))));
1334               //  ht = y2-(int)ht2;
1335               //} else {
1336                 g.setFont(ofont.deriveFont(AffineTransform.getScaleInstance(wdth, (ht2=(htn*((double)profl[c++])/100.0))/av.charHeight)));
1337                 lm = g.getFontMetrics().getLineMetrics(dc,0,1, g);
1338                 //htn -=ht2;
1339               //}
1340               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));
1341               g.drawChars(dc,0,1,x*av.charWidth, (int) (ht+lm.getHeight()));
1342               // ht+=g.getFontMetrics().getAscent()-g.getFontMetrics().getDescent();
1343             }
1344           }
1345           g.setFont(ofont);
1346       }
1347       x++;
1348     }
1349     if (aa.threshold != null)
1350     {
1351       g.setColor(aa.threshold.colour);
1352       Graphics2D g2 = (Graphics2D) g;
1353       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
1354               BasicStroke.JOIN_ROUND, 3f, new float[]
1355               { 5f, 3f }, 0f));
1356
1357       y2 = (int) (y - ((aa.threshold.value - min) / range) * aa.graphHeight);
1358       g.drawLine(0, y2, (eRes - sRes) * av.charWidth, y2);
1359       g2.setStroke(new BasicStroke());
1360     }
1361   }
1362
1363   private int[] getProfileFor(AlignmentAnnotation aa, int column)
1364   {
1365     if (aa.groupRef!=null && aa.groupRef.consensusData!=null) {
1366       return AAFrequency.extractProfile(aa.groupRef.consensusData[column],aa.groupRef.getIgnoreGapsConsensus());
1367     }
1368     // TODO extend annotation row to enable dynamic and static profile data to be stored  
1369     if (aa.autoCalculated && aa.groupRef==null && aa.sequenceRef==null && aa.label.equals("Consensus"))
1370     {
1371       return AAFrequency.extractProfile(av.hconsensus[column],av.getIgnoreGapsConsensus());
1372     }
1373     return null;
1374   }
1375
1376   // used by overview window
1377   public void drawGraph(Graphics g, AlignmentAnnotation aa, int width,
1378           int y, int sRes, int eRes)
1379   {
1380     eRes = Math.min(eRes, aa.annotations.length);
1381     g.setColor(Color.white);
1382     g.fillRect(0, 0, width, y);
1383     g.setColor(new Color(0, 0, 180));
1384
1385     int x = 0, height;
1386
1387     for (int j = sRes; j < eRes; j++)
1388     {
1389       if (aa.annotations[j] != null)
1390       {
1391         if (aa.annotations[j].colour == null)
1392           g.setColor(Color.black);
1393         else
1394           g.setColor(aa.annotations[j].colour);
1395
1396         height = (int) ((aa.annotations[j].value / aa.graphMax) * y);
1397         if (height > y)
1398         {
1399           height = y;
1400         }
1401
1402         g.fillRect(x, y - height, av.charWidth, height);
1403       }
1404       x += av.charWidth;
1405     }
1406   }
1407
1408 }