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