added centre annotation labels menu entry (and commented out SeqSearch web service...
[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
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       }
984
985       //  x ++;
986
987       if (row.hasIcons)
988       {
989         switch (lastSS)
990         {
991           case 'H':
992             g.setColor(HELIX_COLOUR);
993             if (MAC)
994             {
995               //Off by 1 offset when drawing rects and ovals
996               //to offscreen image on the MAC
997               g.fillRoundRect(lastSSX, y + 4 + iconOffset,
998                               (x * av.charWidth) - lastSSX, 7, 8, 8);
999               break;
1000             }
1001
1002             int sCol = (lastSSX / av.charWidth) + startRes;
1003             int x1 = lastSSX;
1004             int x2 = (x * av.charWidth);
1005
1006             if (sCol == 0 ||
1007                 row.annotations[sCol - 1] == null ||
1008                 row.annotations[sCol - 1].secondaryStructure != 'H')
1009             {
1010               g.fillArc(lastSSX, y + 4 + iconOffset, av.charWidth, 8, 90, 180);
1011               x1 += av.charWidth / 2;
1012             }
1013
1014             if (row.annotations[column] == null ||
1015                 row.annotations[column].secondaryStructure != 'H')
1016             {
1017               g.fillArc( (x * av.charWidth) - av.charWidth,
1018                         y + 4 + iconOffset, av.charWidth, 8, 270,
1019                         180);
1020               x2 -= av.charWidth / 2;
1021             }
1022
1023             g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 8);
1024
1025             break;
1026
1027           case 'E':
1028             g.setColor(SHEET_COLOUR);
1029
1030             if (row.annotations[endRes] == null
1031                 || row.annotations[endRes].secondaryStructure != 'E')
1032             {
1033               g.fillRect(lastSSX, y + 4 + iconOffset,
1034                          (x * av.charWidth) - lastSSX - 4, 7);
1035               g.fillPolygon(new int[]
1036                             { (x * av.charWidth) - 4,
1037                             (x * av.charWidth) - 4,
1038                             (x * av.charWidth)},
1039                             new int[]
1040                             {
1041                             y + iconOffset, y + 14 + iconOffset,
1042                             y + 7 + iconOffset
1043               }, 3);
1044             }
1045             else
1046             {
1047               g.fillRect(lastSSX, y + 4 + iconOffset,
1048                          (x + 1) * av.charWidth - lastSSX, 7);
1049             }
1050             break;
1051
1052           default:
1053             g.setColor(Color.gray);
1054             g.fillRect(lastSSX, y + 6 + iconOffset,
1055                        (x * av.charWidth) - lastSSX, 2);
1056
1057             break;
1058         }
1059       }
1060
1061       if (row.graph > 0 && row.graphHeight > 0)
1062       {
1063         if (row.graph == AlignmentAnnotation.LINE_GRAPH)
1064         {
1065           if (row.graphGroup > -1 && !graphGroupDrawn[row.graphGroup])
1066           {
1067             float groupmax = -999999, groupmin = 9999999;
1068             for (int gg = 0; gg < aa.length; gg++)
1069             {
1070               if (aa[gg].graphGroup != row.graphGroup)
1071               {
1072                 continue;
1073               }
1074
1075               if (aa[gg] != row)
1076               {
1077                 aa[gg].visible = false;
1078               }
1079
1080               if (aa[gg].graphMax > groupmax)
1081               {
1082                 groupmax = aa[gg].graphMax;
1083               }
1084               if (aa[gg].graphMin < groupmin)
1085               {
1086                 groupmin = aa[gg].graphMin;
1087               }
1088             }
1089
1090             for (int gg = 0; gg < aa.length; gg++)
1091             {
1092               if (aa[gg].graphGroup == row.graphGroup)
1093               {
1094                 drawLineGraph(g, aa[gg], startRes, endRes, y,
1095                               groupmin, groupmax,
1096                               row.graphHeight);
1097               }
1098             }
1099
1100             graphGroupDrawn[row.graphGroup] = true;
1101           }
1102           else
1103           {
1104             drawLineGraph(g, row, startRes, endRes,
1105                           y, row.graphMin, row.graphMax, row.graphHeight);
1106           }
1107         }
1108         else if (row.graph == AlignmentAnnotation.BAR_GRAPH)
1109         {
1110           drawBarGraph(g, row, startRes, endRes,
1111                        row.graphMin, row.graphMax, y);
1112         }
1113       }
1114
1115       if (row.graph > 0 && row.hasText)
1116       {
1117         y += av.charHeight;
1118       }
1119
1120       if (row.graph == 0)
1121       {
1122         y += aa[i].height;
1123       }
1124     }
1125   }
1126
1127   public void drawLineGraph(Graphics g, AlignmentAnnotation aa,
1128                             int sRes, int eRes,
1129                             int y,
1130                             float min, float max,
1131                             int graphHeight)
1132   {
1133     if (sRes > aa.annotations.length)
1134     {
1135       return;
1136     }
1137
1138     int x = 0;
1139
1140     //Adjustment for fastpaint to left
1141     if (eRes < av.endRes)
1142     {
1143       eRes++;
1144     }
1145
1146     eRes = Math.min(eRes, aa.annotations.length);
1147
1148     if (sRes == 0)
1149     {
1150       x++;
1151     }
1152
1153     int y1 = y, y2 = y;
1154     float range = max - min;
1155
1156     ////Draw origin
1157     if (min < 0)
1158     {
1159       y2 = y - (int) ( (0 - min / range) * graphHeight);
1160     }
1161
1162     g.setColor(Color.gray);
1163     g.drawLine(x - av.charWidth, y2, (eRes - sRes + 1) * av.charWidth, y2);
1164
1165     eRes = Math.min(eRes, aa.annotations.length);
1166
1167     int column;
1168     int aaMax = aa.annotations.length - 1;
1169
1170     while (x < eRes - sRes)
1171     {
1172       column = sRes + x;
1173       if (av.hasHiddenColumns)
1174       {
1175         column = av.getColumnSelection().adjustForHiddenColumns(column);
1176       }
1177
1178       if (column > aaMax)
1179       {
1180         break;
1181       }
1182
1183       if (aa.annotations[column] == null || aa.annotations[column - 1] == null)
1184       {
1185         x++;
1186         continue;
1187       }
1188
1189       if (aa.annotations[column].colour == null)
1190         g.setColor(Color.black);
1191       else
1192         g.setColor(aa.annotations[column].colour);
1193
1194       y1 = y -
1195           (int) ( ( (aa.annotations[column - 1].value - min) / range) * graphHeight);
1196       y2 = y -
1197           (int) ( ( (aa.annotations[column].value - min) / range) * graphHeight);
1198
1199       g.drawLine(x * av.charWidth - av.charWidth / 2, y1,
1200                  x * av.charWidth + av.charWidth / 2, y2);
1201       x++;
1202     }
1203
1204     if (aa.threshold != null)
1205     {
1206       g.setColor(aa.threshold.colour);
1207       Graphics2D g2 = (Graphics2D) g;
1208       g2.setStroke(new BasicStroke(1,
1209                                    BasicStroke.CAP_SQUARE,
1210                                    BasicStroke.JOIN_ROUND, 3f,
1211                                    new float[]
1212                                    {5f, 3f}, 0f));
1213
1214       y2 = (int) (y - ( (aa.threshold.value - min) / range) * graphHeight);
1215       g.drawLine(0, y2, (eRes - sRes) * av.charWidth, y2);
1216       g2.setStroke(new BasicStroke());
1217     }
1218   }
1219
1220   public void drawBarGraph(Graphics g, AlignmentAnnotation aa,
1221                            int sRes, int eRes,
1222                            float min, float max,
1223                            int y)
1224   {
1225     if (sRes > aa.annotations.length)
1226     {
1227       return;
1228     }
1229
1230     eRes = Math.min(eRes, aa.annotations.length);
1231
1232     int x = 0, y1 = y, y2 = y;
1233
1234     float range = max - min;
1235
1236     if (min < 0)
1237     {
1238       y2 = y - (int) ( (0 - min / (range)) * aa.graphHeight);
1239     }
1240
1241     g.setColor(Color.gray);
1242
1243     g.drawLine(x, y2, (eRes - sRes) * av.charWidth, y2);
1244
1245     int column;
1246     int aaMax = aa.annotations.length - 1;
1247
1248     while (x < eRes - sRes)
1249     {
1250       column = sRes + x;
1251       if (av.hasHiddenColumns)
1252       {
1253         column = av.getColumnSelection().adjustForHiddenColumns(column);
1254       }
1255
1256       if (column > aaMax)
1257       {
1258         break;
1259       }
1260
1261       if (aa.annotations[column] == null)
1262       {
1263         x++;
1264         continue;
1265       }
1266
1267       if (aa.annotations[column].colour == null)
1268         g.setColor(Color.black);
1269       else
1270         g.setColor(aa.annotations[column].colour);
1271
1272       y1 = y -
1273           (int) ( ( (aa.annotations[column].value - min) / (range)) * aa.graphHeight);
1274
1275       if (y1 - y2 > 0)
1276       {
1277         g.fillRect(x * av.charWidth, y2, av.charWidth, y1 - y2);
1278       }
1279       else
1280       {
1281         g.fillRect(x * av.charWidth, y1, av.charWidth, y2 - y1);
1282       }
1283
1284       x++;
1285
1286     }
1287     if (aa.threshold != null)
1288     {
1289       g.setColor(aa.threshold.colour);
1290       Graphics2D g2 = (Graphics2D) g;
1291       g2.setStroke(new BasicStroke(1,
1292                                    BasicStroke.CAP_SQUARE,
1293                                    BasicStroke.JOIN_ROUND, 3f,
1294                                    new float[]
1295                                    {5f, 3f}, 0f));
1296
1297       y2 = (int) (y - ( (aa.threshold.value - min) / range) * aa.graphHeight);
1298       g.drawLine(0, y2, (eRes - sRes) * av.charWidth, y2);
1299       g2.setStroke(new BasicStroke());
1300     }
1301   }
1302
1303   // used by overview window
1304   public void drawGraph(Graphics g, AlignmentAnnotation aa, int width, int y,
1305                         int sRes, int eRes)
1306   {
1307     eRes = Math.min(eRes, aa.annotations.length);
1308     g.setColor(Color.white);
1309     g.fillRect(0, 0, width, y);
1310     g.setColor(new Color(0, 0, 180));
1311
1312     int x = 0, height;
1313
1314     for (int j = sRes; j < eRes; j++)
1315     {
1316       if (aa.annotations[j] != null)
1317       {
1318         if (aa.annotations[j].colour == null)
1319           g.setColor(Color.black);
1320         else
1321           g.setColor(aa.annotations[j].colour);
1322
1323         height = (int) ( (aa.annotations[j].value / aa.graphMax) * y);
1324         if (height > y)
1325         {
1326           height = y;
1327         }
1328
1329         g.fillRect(x, y - height, av.charWidth, height);
1330       }
1331       x += av.charWidth;
1332     }
1333   }
1334
1335 }