a5b07163b4fc50357c3b7470680a4845b20c35b4
[jalview.git] / src / jalview / gui / AnnotationLabels.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 The Jalview Authors
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
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import java.util.*;
24 import java.util.regex.Pattern;
25
26 import java.awt.*;
27 import java.awt.datatransfer.*;
28 import java.awt.event.*;
29 import java.awt.geom.AffineTransform;
30 import java.awt.image.*;
31 import javax.swing.*;
32
33 import jalview.datamodel.*;
34 import jalview.io.*;
35 import jalview.util.MessageManager;
36
37 /**
38  * DOCUMENT ME!
39  * 
40  * @author $author$
41  * @version $Revision$
42  */
43 public class AnnotationLabels extends JPanel implements MouseListener,
44         MouseMotionListener, ActionListener
45 {
46   static String TOGGLE_LABELSCALE = "Scale Label to Column";
47
48   static String ADDNEW = "Add New Row";
49
50   static String EDITNAME = "Edit Label/Description";
51
52   static String HIDE = "Hide This Row";
53
54   static String DELETE = "Delete This Row";
55
56   static String SHOWALL = "Show All Hidden Rows";
57
58   static String OUTPUT_TEXT = "Export Annotation";
59
60   static String COPYCONS_SEQ = "Copy Consensus Sequence";
61
62   boolean resizePanel = false;
63
64   Image image;
65
66   AlignmentPanel ap;
67
68   AlignViewport av;
69
70   boolean resizing = false;
71
72   MouseEvent dragEvent;
73
74   int oldY;
75
76   int selectedRow;
77
78   int scrollOffset = 0;
79
80   Font font = new Font("Arial", Font.PLAIN, 11);
81
82   private boolean hasHiddenRows;
83
84   /**
85    * Creates a new AnnotationLabels object.
86    * 
87    * @param ap
88    *          DOCUMENT ME!
89    */
90   public AnnotationLabels(AlignmentPanel ap)
91   {
92     this.ap = ap;
93     av = ap.av;
94     ToolTipManager.sharedInstance().registerComponent(this);
95
96     java.net.URL url = getClass().getResource("/images/idwidth.gif");
97     Image temp = null;
98
99     if (url != null)
100     {
101       temp = java.awt.Toolkit.getDefaultToolkit().createImage(url);
102     }
103
104     try
105     {
106       MediaTracker mt = new MediaTracker(this);
107       mt.addImage(temp, 0);
108       mt.waitForID(0);
109     } catch (Exception ex)
110     {
111     }
112
113     BufferedImage bi = new BufferedImage(temp.getHeight(this),
114             temp.getWidth(this), BufferedImage.TYPE_INT_RGB);
115     Graphics2D g = (Graphics2D) bi.getGraphics();
116     g.rotate(Math.toRadians(90));
117     g.drawImage(temp, 0, -bi.getWidth(this), this);
118     image = (Image) bi;
119
120     addMouseListener(this);
121     addMouseMotionListener(this);
122     addMouseWheelListener(ap.annotationPanel);
123   }
124
125   public AnnotationLabels(AlignViewport av)
126   {
127     this.av = av;
128   }
129
130   /**
131    * DOCUMENT ME!
132    * 
133    * @param y
134    *          DOCUMENT ME!
135    */
136   public void setScrollOffset(int y)
137   {
138     scrollOffset = y;
139     repaint();
140   }
141
142   /**
143    * sets selectedRow to -2 if no annotation preset, -1 if no visible row is at
144    * y
145    * 
146    * @param y
147    *          coordinate position to search for a row
148    */
149   void getSelectedRow(int y)
150   {
151     int height = 0;
152     AlignmentAnnotation[] aa = ap.av.getAlignment()
153             .getAlignmentAnnotation();
154     selectedRow = -2;
155     if (aa != null)
156     {
157       for (int i = 0; i < aa.length; i++)
158       {
159         selectedRow = -1;
160         if (!aa[i].visible)
161         {
162           continue;
163         }
164
165         height += aa[i].height;
166
167         if (y < height)
168         {
169           selectedRow = i;
170
171           break;
172         }
173       }
174     }
175   }
176
177   /**
178    * DOCUMENT ME!
179    * 
180    * @param evt
181    *          DOCUMENT ME!
182    */
183   public void actionPerformed(ActionEvent evt)
184   {
185     AlignmentAnnotation[] aa = ap.av.getAlignment()
186             .getAlignmentAnnotation();
187
188     if (evt.getActionCommand().equals(ADDNEW))
189     {
190       AlignmentAnnotation newAnnotation = new AlignmentAnnotation(null,
191               null, new Annotation[ap.av.getAlignment().getWidth()]);
192
193       if (!editLabelDescription(newAnnotation))
194       {
195         return;
196       }
197
198       ap.av.getAlignment().addAnnotation(newAnnotation);
199       ap.av.getAlignment().setAnnotationIndex(newAnnotation, 0);
200     }
201     else if (evt.getActionCommand().equals(EDITNAME))
202     {
203       editLabelDescription(aa[selectedRow]);
204       repaint();
205     }
206     else if (evt.getActionCommand().equals(HIDE))
207     {
208       aa[selectedRow].visible = false;
209     }
210     else if (evt.getActionCommand().equals(DELETE))
211     {
212       ap.av.getAlignment().deleteAnnotation(aa[selectedRow]);
213     }
214     else if (evt.getActionCommand().equals(SHOWALL))
215     {
216       for (int i = 0; i < aa.length; i++)
217       {
218         if (!aa[i].visible && aa[i].annotations != null)
219         {
220           aa[i].visible = true;
221         }
222       }
223     }
224     else if (evt.getActionCommand().equals(OUTPUT_TEXT))
225     {
226       new AnnotationExporter().exportAnnotations(ap,
227               new AlignmentAnnotation[]
228               { aa[selectedRow] }, null, null);
229     }
230     else if (evt.getActionCommand().equals(COPYCONS_SEQ))
231     {
232       SequenceI cons = null;
233       if (aa[selectedRow].groupRef != null)
234       {
235         cons = aa[selectedRow].groupRef.getConsensusSeq();
236       }
237       else
238       {
239         cons = av.getConsensusSeq();
240       }
241       if (cons != null)
242       {
243         copy_annotseqtoclipboard(cons);
244       }
245
246     }
247     else if (evt.getActionCommand().equals(TOGGLE_LABELSCALE))
248     {
249       aa[selectedRow].scaleColLabel = !aa[selectedRow].scaleColLabel;
250     }
251
252     ap.validateAnnotationDimensions(false);
253     ap.addNotify();
254     ap.repaint();
255     // validate();
256     // ap.paintAlignment(true);
257   }
258
259   /**
260    * DOCUMENT ME!
261    * 
262    * @param e
263    *          DOCUMENT ME!
264    */
265   boolean editLabelDescription(AlignmentAnnotation annotation)
266   {
267     EditNameDialog dialog = new EditNameDialog(annotation.label,
268             annotation.description, "       Annotation Name ",
269             "Annotation Description ", "Edit Annotation Name/Description",
270             ap.alignFrame);
271
272     if (!dialog.accept)
273     {
274       return false;
275     }
276
277     annotation.label = dialog.getName();
278
279     String text = dialog.getDescription();
280     if (text != null && text.length() == 0)
281     {
282       text = null;
283     }
284     annotation.description = text;
285
286     return true;
287   }
288
289   /**
290    * DOCUMENT ME!
291    * 
292    * @param evt
293    *          DOCUMENT ME!
294    */
295   public void mousePressed(MouseEvent evt)
296   {
297     getSelectedRow(evt.getY() - scrollOffset);
298     oldY = evt.getY();
299   }
300
301   /**
302    * DOCUMENT ME!
303    * 
304    * @param evt
305    *          DOCUMENT ME!
306    */
307   public void mouseReleased(MouseEvent evt)
308   {
309     int start = selectedRow;
310     getSelectedRow(evt.getY() - scrollOffset);
311     int end = selectedRow;
312
313     if (start != end)
314     {
315       // Swap these annotations
316       AlignmentAnnotation startAA = ap.av.getAlignment()
317               .getAlignmentAnnotation()[start];
318       if (end == -1)
319       {
320         end = ap.av.getAlignment().getAlignmentAnnotation().length - 1;
321       }
322       AlignmentAnnotation endAA = ap.av.getAlignment()
323               .getAlignmentAnnotation()[end];
324
325       ap.av.getAlignment().getAlignmentAnnotation()[end] = startAA;
326       ap.av.getAlignment().getAlignmentAnnotation()[start] = endAA;
327     }
328
329     resizePanel = false;
330     dragEvent = null;
331     repaint();
332     ap.annotationPanel.repaint();
333   }
334
335   /**
336    * DOCUMENT ME!
337    * 
338    * @param evt
339    *          DOCUMENT ME!
340    */
341   public void mouseEntered(MouseEvent evt)
342   {
343     if (evt.getY() < 10)
344     {
345       resizePanel = true;
346       repaint();
347     }
348   }
349
350   /**
351    * DOCUMENT ME!
352    * 
353    * @param evt
354    *          DOCUMENT ME!
355    */
356   public void mouseExited(MouseEvent evt)
357   {
358     if (dragEvent == null)
359     {
360       resizePanel = false;
361       repaint();
362     }
363   }
364
365   /**
366    * DOCUMENT ME!
367    * 
368    * @param evt
369    *          DOCUMENT ME!
370    */
371   public void mouseDragged(MouseEvent evt)
372   {
373     dragEvent = evt;
374
375     if (resizePanel)
376     {
377       Dimension d = ap.annotationScroller.getPreferredSize();
378       int dif = evt.getY() - oldY;
379
380       dif /= ap.av.charHeight;
381       dif *= ap.av.charHeight;
382
383       if ((d.height - dif) > 20)
384       {
385         ap.annotationScroller.setPreferredSize(new Dimension(d.width,
386                 d.height - dif));
387         d = ap.annotationSpaceFillerHolder.getPreferredSize();
388         ap.annotationSpaceFillerHolder.setPreferredSize(new Dimension(
389                 d.width, d.height - dif));
390         ap.paintAlignment(true);
391       }
392
393       ap.addNotify();
394     }
395     else
396     {
397       repaint();
398     }
399   }
400
401   /**
402    * DOCUMENT ME!
403    * 
404    * @param evt
405    *          DOCUMENT ME!
406    */
407   public void mouseMoved(MouseEvent evt)
408   {
409     resizePanel = evt.getY() < 10;
410
411     getSelectedRow(evt.getY() - scrollOffset);
412
413     if (selectedRow > -1
414             && ap.av.getAlignment().getAlignmentAnnotation().length > selectedRow)
415     {
416       AlignmentAnnotation aa = ap.av.getAlignment()
417               .getAlignmentAnnotation()[selectedRow];
418
419       StringBuffer desc = new StringBuffer();
420       if (aa.description != null
421               && !aa.description.equals("New description"))
422       {
423         // TODO: we could refactor and merge this code with the code in
424         // jalview.gui.SeqPanel.mouseMoved(..) that formats sequence feature
425         // tooltips
426         desc.append(aa.getDescription(true).trim());
427         // check to see if the description is an html fragment.
428         if (desc.length() < 6
429                 || (desc.substring(0, 6).toLowerCase().indexOf("<html>") < 0))
430         {
431           // clean the description ready for embedding in html
432           desc = new StringBuffer(Pattern.compile("<").matcher(desc)
433                   .replaceAll("&lt;"));
434           desc.insert(0, "<html>");
435         }
436         else
437         {
438           // remove terminating html if any
439           int i = desc.substring(desc.length() - 7).toLowerCase()
440                   .lastIndexOf("</html>");
441           if (i > -1)
442           {
443             desc.setLength(desc.length() - 7 + i);
444           }
445         }
446         if (aa.hasScore())
447         {
448           desc.append("<br/>");
449         }
450
451       }
452       else
453       {
454         // begin the tooltip's html fragment
455         desc.append("<html>");
456       }
457       if (aa.hasScore())
458       {
459         // TODO: limit precision of score to avoid noise from imprecise doubles
460         // (64.7 becomes 64.7+/some tiny value).
461         desc.append(" Score: " + aa.score);
462       }
463
464       if (desc.length() > 6)
465       {
466         desc.append("</html>");
467         this.setToolTipText(desc.toString());
468       }
469       else
470         this.setToolTipText(null);
471     }
472
473   }
474
475   /**
476    * DOCUMENT ME!
477    * 
478    * @param evt
479    *          DOCUMENT ME!
480    */
481   public void mouseClicked(MouseEvent evt)
482   {
483     AlignmentAnnotation[] aa = ap.av.getAlignment()
484             .getAlignmentAnnotation();
485     if (SwingUtilities.isLeftMouseButton(evt))
486     {
487       if (selectedRow > -1 && selectedRow < aa.length)
488       {
489         if (aa[selectedRow].groupRef != null)
490         {
491           if (evt.getClickCount() >= 2)
492           {
493             // todo: make the ap scroll to the selection - not necessary, first
494             // click highlights/scrolls, second selects
495             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
496             ap.av.setSelectionGroup(// new SequenceGroup(
497             aa[selectedRow].groupRef); // );
498             ap.paintAlignment(false);
499             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
500             ap.av.sendSelection();
501           }
502           else
503           {
504             ap.seqPanel.ap.idPanel
505                     .highlightSearchResults(aa[selectedRow].groupRef
506                             .getSequences(null));
507           }
508           return;
509         }
510         else if (aa[selectedRow].sequenceRef != null)
511         {
512           Vector sr = new Vector();
513           sr.addElement(aa[selectedRow].sequenceRef);
514           if (evt.getClickCount() == 1)
515           {
516             ap.seqPanel.ap.idPanel.highlightSearchResults(sr);
517           }
518           else if (evt.getClickCount() >= 2)
519           {
520             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
521             SequenceGroup sg = new SequenceGroup();
522             sg.addSequence(aa[selectedRow].sequenceRef, false);
523             ap.av.setSelectionGroup(sg);
524             ap.av.sendSelection();
525             ap.paintAlignment(false);
526             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
527           }
528
529         }
530       }
531     }
532     if (!SwingUtilities.isRightMouseButton(evt))
533     {
534       return;
535     }
536
537     JPopupMenu pop = new JPopupMenu(MessageManager.getString("label.annotations"));
538     JMenuItem item = new JMenuItem(ADDNEW);
539     item.addActionListener(this);
540     pop.add(item);
541     if (selectedRow < 0)
542     {
543       if (hasHiddenRows)
544       { // let the user make everything visible again
545         item = new JMenuItem(SHOWALL);
546         item.addActionListener(this);
547         pop.add(item);
548       }
549       pop.show(this, evt.getX(), evt.getY());
550       return;
551     }
552     item = new JMenuItem(EDITNAME);
553     item.addActionListener(this);
554     pop.add(item);
555     item = new JMenuItem(HIDE);
556     item.addActionListener(this);
557     pop.add(item);
558     item = new JMenuItem(DELETE);
559     item.addActionListener(this);
560     pop.add(item);
561     if (hasHiddenRows)
562     {
563       item = new JMenuItem(SHOWALL);
564       item.addActionListener(this);
565       pop.add(item);
566     }
567     item = new JMenuItem(OUTPUT_TEXT);
568     item.addActionListener(this);
569     pop.add(item);
570     // TODO: annotation object should be typed for autocalculated/derived
571     // property methods
572     if (selectedRow < aa.length)
573     {
574       if (!aa[selectedRow].autoCalculated)
575       {
576         if (aa[selectedRow].graph == AlignmentAnnotation.NO_GRAPH)
577         {
578           // display formatting settings for this row.
579           pop.addSeparator();
580           // av and sequencegroup need to implement same interface for
581           item = new JCheckBoxMenuItem(TOGGLE_LABELSCALE,
582                   aa[selectedRow].scaleColLabel);
583           item.addActionListener(this);
584           pop.add(item);
585         }
586       }
587       else if (aa[selectedRow].label.indexOf("Consensus") > -1)
588       {
589         pop.addSeparator();
590         // av and sequencegroup need to implement same interface for
591         final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
592                 "Ignore Gaps In Consensus",
593                 (aa[selectedRow].groupRef != null) ? aa[selectedRow].groupRef
594                         .getIgnoreGapsConsensus() : ap.av
595                         .getIgnoreGapsConsensus());
596         final AlignmentAnnotation aaa = aa[selectedRow];
597         cbmi.addActionListener(new ActionListener()
598         {
599           public void actionPerformed(ActionEvent e)
600           {
601             if (aaa.groupRef != null)
602             {
603               // TODO: pass on reference to ap so the view can be updated.
604               aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState());
605               ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
606             }
607             else
608             {
609               ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
610             }
611           }
612         });
613         pop.add(cbmi);
614         // av and sequencegroup need to implement same interface for
615         if (aaa.groupRef != null)
616         {
617           final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
618                   "Show Group Histogram",
619                   aa[selectedRow].groupRef.isShowConsensusHistogram());
620           chist.addActionListener(new ActionListener()
621           {
622             public void actionPerformed(ActionEvent e)
623             {
624               // TODO: pass on reference
625               // to ap
626               // so the
627               // view
628               // can be
629               // updated.
630               aaa.groupRef.setShowConsensusHistogram(chist.getState());
631               ap.repaint();
632               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
633             }
634           });
635           pop.add(chist);
636           final JCheckBoxMenuItem cprofl = new JCheckBoxMenuItem(
637                   "Show Group Logo",
638                   aa[selectedRow].groupRef.isShowSequenceLogo());
639           cprofl.addActionListener(new ActionListener()
640           {
641             public void actionPerformed(ActionEvent e)
642             {
643               // TODO: pass on reference
644               // to ap
645               // so the
646               // view
647               // can be
648               // updated.
649               aaa.groupRef.setshowSequenceLogo(cprofl.getState());
650               ap.repaint();
651               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
652             }
653           });
654           pop.add(cprofl);
655           final JCheckBoxMenuItem cproflnorm = new JCheckBoxMenuItem(
656                   "Normalise Group Logo",
657                   aa[selectedRow].groupRef.isNormaliseSequenceLogo());
658           cproflnorm.addActionListener(new ActionListener()
659           {
660             public void actionPerformed(ActionEvent e)
661             {
662
663               // TODO: pass on reference
664               // to ap
665               // so the
666               // view
667               // can be
668               // updated.
669               aaa.groupRef.setNormaliseSequenceLogo(cproflnorm.getState());
670               // automatically enable logo display if we're clicked
671               aaa.groupRef.setshowSequenceLogo(true);
672               ap.repaint();
673               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
674             }
675           });
676           pop.add(cproflnorm);
677         }
678         else
679         {
680           final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
681                   "Show Histogram", av.isShowConsensusHistogram());
682           chist.addActionListener(new ActionListener()
683           {
684             public void actionPerformed(ActionEvent e)
685             {
686               // TODO: pass on reference
687               // to ap
688               // so the
689               // view
690               // can be
691               // updated.
692               av.setShowConsensusHistogram(chist.getState());
693               ap.alignFrame.setMenusForViewport();
694               ap.repaint();
695               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
696             }
697           });
698           pop.add(chist);
699           final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
700                   "Show Logo", av.isShowSequenceLogo());
701           cprof.addActionListener(new ActionListener()
702           {
703             public void actionPerformed(ActionEvent e)
704             {
705               // TODO: pass on reference
706               // to ap
707               // so the
708               // view
709               // can be
710               // updated.
711               av.setShowSequenceLogo(cprof.getState());
712               ap.alignFrame.setMenusForViewport();
713               ap.repaint();
714               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
715             }
716           });
717           pop.add(cprof);
718           final JCheckBoxMenuItem cprofnorm = new JCheckBoxMenuItem(
719                   "Normalise Logo", av.isNormaliseSequenceLogo());
720           cprofnorm.addActionListener(new ActionListener()
721           {
722             public void actionPerformed(ActionEvent e)
723             {
724               // TODO: pass on reference
725               // to ap
726               // so the
727               // view
728               // can be
729               // updated.
730               av.setShowSequenceLogo(true);
731               av.setNormaliseSequenceLogo(cprofnorm.getState());
732               ap.alignFrame.setMenusForViewport();
733               ap.repaint();
734               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
735             }
736           });
737           pop.add(cprofnorm);
738         }
739         final JMenuItem consclipbrd = new JMenuItem(COPYCONS_SEQ);
740         consclipbrd.addActionListener(this);
741         pop.add(consclipbrd);
742       }
743     }
744     pop.show(this, evt.getX(), evt.getY());
745   }
746
747   /**
748    * do a single sequence copy to jalview and the system clipboard
749    * 
750    * @param sq
751    *          sequence to be copied to clipboard
752    */
753   protected void copy_annotseqtoclipboard(SequenceI sq)
754   {
755     SequenceI[] seqs = new SequenceI[]
756     { sq };
757     String[] omitHidden = null;
758     SequenceI[] dseqs = new SequenceI[]
759     { sq.getDatasetSequence() };
760     if (dseqs[0] == null)
761     {
762       dseqs[0] = new Sequence(sq);
763       dseqs[0].setSequence(jalview.analysis.AlignSeq.extractGaps(
764               jalview.util.Comparison.GapChars, sq.getSequenceAsString()));
765
766       sq.setDatasetSequence(dseqs[0]);
767     }
768     Alignment ds = new Alignment(dseqs);
769     if (av.hasHiddenColumns())
770     {
771       omitHidden = av.getColumnSelection().getVisibleSequenceStrings(0,
772               sq.getLength(), seqs);
773     }
774
775     String output = new FormatAdapter().formatSequences("Fasta", seqs,
776             omitHidden);
777
778     Toolkit.getDefaultToolkit().getSystemClipboard()
779             .setContents(new StringSelection(output), Desktop.instance);
780
781     Vector hiddenColumns = null;
782     if (av.hasHiddenColumns())
783     {
784       hiddenColumns = new Vector();
785       for (int i = 0; i < av.getColumnSelection().getHiddenColumns().size(); i++)
786       {
787         int[] region = (int[]) av.getColumnSelection().getHiddenColumns()
788                 .elementAt(i);
789
790         hiddenColumns.addElement(new int[]
791         { region[0], region[1] });
792       }
793     }
794
795     Desktop.jalviewClipboard = new Object[]
796     { seqs, ds, // what is the dataset of a consensus sequence ? need to flag
797         // sequence as special.
798         hiddenColumns };
799   }
800
801   /**
802    * DOCUMENT ME!
803    * 
804    * @param g1
805    *          DOCUMENT ME!
806    */
807   public void paintComponent(Graphics g)
808   {
809
810     int width = getWidth();
811     if (width == 0)
812     {
813       width = ap.calculateIdWidth().width + 4;
814     }
815
816     Graphics2D g2 = (Graphics2D) g;
817     if (av.antiAlias)
818     {
819       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
820               RenderingHints.VALUE_ANTIALIAS_ON);
821     }
822
823     drawComponent(g2, true, width);
824
825   }
826
827   /**
828    * Draw the full set of annotation Labels for the alignment at the given cursor
829    * 
830    * @param g Graphics2D instance (needed for font scaling)
831    * @param width Width for scaling labels
832    *          
833    */
834   public void drawComponent(Graphics g, int width)
835   {
836     drawComponent(g, false, width);
837   }
838
839   private final boolean debugRedraw = false;
840   /**
841    * Draw the full set of annotation Labels for the alignment at the given cursor
842    * 
843    * @param g Graphics2D instance (needed for font scaling)
844    * @param clip - true indicates that only current visible area needs to be rendered
845    * @param width Width for scaling labels         
846    */
847   public void drawComponent(Graphics g, boolean clip, int width)
848   {
849     if (av.getFont().getSize() < 10)
850     {
851       g.setFont(font);
852     }
853     else
854     {
855       g.setFont(av.getFont());
856     }
857
858     FontMetrics fm = g.getFontMetrics(g.getFont());
859     g.setColor(Color.white);
860     g.fillRect(0, 0, getWidth(), getHeight());
861
862     g.translate(0, scrollOffset);
863     g.setColor(Color.black);
864     
865     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
866     int fontHeight = g.getFont().getSize();
867     int y = 0;
868     int x = 0;
869     int graphExtras = 0;
870     int offset = 0;
871     Font baseFont = g.getFont();
872     FontMetrics baseMetrics = fm;
873     int ofontH = fontHeight;
874     int sOffset=0;
875     int visHeight = 0;
876     int[] visr = (ap!=null && ap.annotationPanel!=null) ? ap.annotationPanel.getVisibleVRange() : null;
877     if (clip && visr!=null){ 
878       sOffset = visr[0]; 
879       visHeight = visr[1];
880     }
881     boolean visible = true,before=false,after=false;
882     if (aa != null)
883     {
884       hasHiddenRows = false;
885       int olY=0;
886       for (int i = 0; i < aa.length; i++)
887       {
888         visible = true;
889         if (!aa[i].visible)
890         {
891           hasHiddenRows = true;
892           continue;
893         }
894         olY=y;
895         y += aa[i].height;
896         if (clip) {if (y<sOffset)
897         {
898           if (!before)
899           {
900             if (debugRedraw) {
901               System.out.println("before vis: "+i);
902             }
903           before=true;
904           }
905           // don't draw what isn't visible
906           continue;
907         }
908         if (olY>visHeight)
909         {
910
911           if (!after)
912           {
913             if (debugRedraw) {
914               System.out.println("Scroll offset: "+sOffset+" after vis: "+i);
915             }
916           after=true;
917           }
918           // don't draw what isn't visible
919           continue;
920         }}
921         g.setColor(Color.black);
922
923         offset = -aa[i].height / 2;
924
925         if (aa[i].hasText)
926         {
927           offset += fm.getHeight() / 2;
928           offset -= fm.getDescent();
929         }
930         else
931           offset += fm.getDescent();
932
933         x = width - fm.stringWidth(aa[i].label) - 3;
934
935         if (aa[i].graphGroup > -1)
936         {
937           int groupSize = 0;
938           // TODO: JAL-1291 revise rendering model so the graphGroup map is computed efficiently for all visible labels
939           for (int gg = 0; gg < aa.length; gg++)
940           {
941             if (aa[gg].graphGroup == aa[i].graphGroup)
942             {
943               groupSize++;
944             }
945           }
946           if (groupSize * (fontHeight + 8) < aa[i].height)
947           {
948             graphExtras = (aa[i].height - (groupSize * (fontHeight + 8))) / 2;
949           }
950           else
951           {
952             // scale font to fit
953             float h = aa[i].height / (float) groupSize, s;
954             if (h < 9)
955             {
956               visible = false;
957             }
958             else
959             {
960               fontHeight = -8 + (int) h;
961               s = ((float) fontHeight) / (float) ofontH;
962               Font f = baseFont.deriveFont(AffineTransform
963                       .getScaleInstance(s, s));
964               g.setFont(f);
965               fm = g.getFontMetrics();
966               graphExtras = (aa[i].height - (groupSize * (fontHeight + 8))) / 2;
967             }
968           }
969           if (visible)
970           {
971             for (int gg = 0; gg < aa.length; gg++)
972             {
973               if (aa[gg].graphGroup == aa[i].graphGroup)
974               {
975                 x = width - fm.stringWidth(aa[gg].label) - 3;
976                 g.drawString(aa[gg].label, x, y - graphExtras);
977
978                 if (aa[gg]._linecolour != null)
979                 {
980
981                   g.setColor(aa[gg]._linecolour);
982                   g.drawLine(x, y - graphExtras + 3,
983                           x + fm.stringWidth(aa[gg].label), y - graphExtras
984                                   + 3);
985                 }
986
987                 g.setColor(Color.black);
988                 graphExtras += fontHeight + 8;
989               }
990             }
991           }
992           g.setFont(baseFont);
993           fm = baseMetrics;
994           fontHeight = ofontH;
995         }
996         else
997         {
998           g.drawString(aa[i].label, x, y + offset);
999         }
1000       }
1001     }
1002
1003     if (resizePanel)
1004     {
1005       g.drawImage(image, 2, 0 - scrollOffset, this);
1006     }
1007     else if (dragEvent != null && aa != null)
1008     {
1009       g.setColor(Color.lightGray);
1010       g.drawString(aa[selectedRow].label, dragEvent.getX(),
1011               dragEvent.getY() - scrollOffset);
1012     }
1013
1014     if (!av.wrapAlignment && ((aa == null) || (aa.length < 1)))
1015     {
1016       g.drawString(MessageManager.getString("label.right_click"), 2, 8);
1017       g.drawString(MessageManager.getString("label.to_add_annotation"), 2, 18);
1018     }
1019   }
1020 }