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