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