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