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