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