ec7b46e65af683908772ce4df6100f79d78f44ad
[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   String TOGGLE_LABELSCALE = MessageManager.getString("label.scale_label_to_column");
47
48   String ADDNEW = MessageManager.getString("label.add_new_row");
49
50   String EDITNAME = MessageManager.getString("label.edit_label_description");
51
52   String HIDE = MessageManager.getString("label.hide_row");
53
54   String DELETE = MessageManager.getString("label.delete_row");
55
56   String SHOWALL = MessageManager.getString("label.show_all_hidden_rows");
57
58   String OUTPUT_TEXT = MessageManager.getString("label.export_annotation");
59
60   String COPYCONS_SEQ = MessageManager.getString("label.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(
538             MessageManager.getString("label.annotations"));
539     JMenuItem item = new JMenuItem(ADDNEW);
540     item.addActionListener(this);
541     pop.add(item);
542     if (selectedRow < 0)
543     {
544       if (hasHiddenRows)
545       { // let the user make everything visible again
546         item = new JMenuItem(SHOWALL);
547         item.addActionListener(this);
548         pop.add(item);
549       }
550       pop.show(this, evt.getX(), evt.getY());
551       return;
552     }
553     item = new JMenuItem(EDITNAME);
554     item.addActionListener(this);
555     pop.add(item);
556     item = new JMenuItem(HIDE);
557     item.addActionListener(this);
558     pop.add(item);
559     item = new JMenuItem(DELETE);
560     item.addActionListener(this);
561     pop.add(item);
562     if (hasHiddenRows)
563     {
564       item = new JMenuItem(SHOWALL);
565       item.addActionListener(this);
566       pop.add(item);
567     }
568     item = new JMenuItem(OUTPUT_TEXT);
569     item.addActionListener(this);
570     pop.add(item);
571     // TODO: annotation object should be typed for autocalculated/derived
572     // property methods
573     if (selectedRow < aa.length)
574     {
575       if (!aa[selectedRow].autoCalculated)
576       {
577         if (aa[selectedRow].graph == AlignmentAnnotation.NO_GRAPH)
578         {
579           // display formatting settings for this row.
580           pop.addSeparator();
581           // av and sequencegroup need to implement same interface for
582           item = new JCheckBoxMenuItem(TOGGLE_LABELSCALE,
583                   aa[selectedRow].scaleColLabel);
584           item.addActionListener(this);
585           pop.add(item);
586         }
587       }
588       else if (aa[selectedRow].label.indexOf("Consensus") > -1)
589       {
590         pop.addSeparator();
591         // av and sequencegroup need to implement same interface for
592         final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
593                 "Ignore Gaps In Consensus",
594                 (aa[selectedRow].groupRef != null) ? aa[selectedRow].groupRef
595                         .getIgnoreGapsConsensus() : ap.av
596                         .getIgnoreGapsConsensus());
597         final AlignmentAnnotation aaa = aa[selectedRow];
598         cbmi.addActionListener(new ActionListener()
599         {
600           public void actionPerformed(ActionEvent e)
601           {
602             if (aaa.groupRef != null)
603             {
604               // TODO: pass on reference to ap so the view can be updated.
605               aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState());
606               ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
607             }
608             else
609             {
610               ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
611             }
612           }
613         });
614         pop.add(cbmi);
615         // av and sequencegroup need to implement same interface for
616         if (aaa.groupRef != null)
617         {
618           final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
619                   "Show Group Histogram",
620                   aa[selectedRow].groupRef.isShowConsensusHistogram());
621           chist.addActionListener(new ActionListener()
622           {
623             public void actionPerformed(ActionEvent e)
624             {
625               // TODO: pass on reference
626               // to ap
627               // so the
628               // view
629               // can be
630               // updated.
631               aaa.groupRef.setShowConsensusHistogram(chist.getState());
632               ap.repaint();
633               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
634             }
635           });
636           pop.add(chist);
637           final JCheckBoxMenuItem cprofl = new JCheckBoxMenuItem(
638                   "Show Group Logo",
639                   aa[selectedRow].groupRef.isShowSequenceLogo());
640           cprofl.addActionListener(new ActionListener()
641           {
642             public void actionPerformed(ActionEvent e)
643             {
644               // TODO: pass on reference
645               // to ap
646               // so the
647               // view
648               // can be
649               // updated.
650               aaa.groupRef.setshowSequenceLogo(cprofl.getState());
651               ap.repaint();
652               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
653             }
654           });
655           pop.add(cprofl);
656           final JCheckBoxMenuItem cproflnorm = new JCheckBoxMenuItem(
657                   "Normalise Group Logo",
658                   aa[selectedRow].groupRef.isNormaliseSequenceLogo());
659           cproflnorm.addActionListener(new ActionListener()
660           {
661             public void actionPerformed(ActionEvent e)
662             {
663
664               // TODO: pass on reference
665               // to ap
666               // so the
667               // view
668               // can be
669               // updated.
670               aaa.groupRef.setNormaliseSequenceLogo(cproflnorm.getState());
671               // automatically enable logo display if we're clicked
672               aaa.groupRef.setshowSequenceLogo(true);
673               ap.repaint();
674               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
675             }
676           });
677           pop.add(cproflnorm);
678         }
679         else
680         {
681           final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
682                   "Show Histogram", av.isShowConsensusHistogram());
683           chist.addActionListener(new ActionListener()
684           {
685             public void actionPerformed(ActionEvent e)
686             {
687               // TODO: pass on reference
688               // to ap
689               // so the
690               // view
691               // can be
692               // updated.
693               av.setShowConsensusHistogram(chist.getState());
694               ap.alignFrame.setMenusForViewport();
695               ap.repaint();
696               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
697             }
698           });
699           pop.add(chist);
700           final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
701                   "Show Logo", av.isShowSequenceLogo());
702           cprof.addActionListener(new ActionListener()
703           {
704             public void actionPerformed(ActionEvent e)
705             {
706               // TODO: pass on reference
707               // to ap
708               // so the
709               // view
710               // can be
711               // updated.
712               av.setShowSequenceLogo(cprof.getState());
713               ap.alignFrame.setMenusForViewport();
714               ap.repaint();
715               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
716             }
717           });
718           pop.add(cprof);
719           final JCheckBoxMenuItem cprofnorm = new JCheckBoxMenuItem(
720                   "Normalise Logo", av.isNormaliseSequenceLogo());
721           cprofnorm.addActionListener(new ActionListener()
722           {
723             public void actionPerformed(ActionEvent e)
724             {
725               // TODO: pass on reference
726               // to ap
727               // so the
728               // view
729               // can be
730               // updated.
731               av.setShowSequenceLogo(true);
732               av.setNormaliseSequenceLogo(cprofnorm.getState());
733               ap.alignFrame.setMenusForViewport();
734               ap.repaint();
735               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
736             }
737           });
738           pop.add(cprofnorm);
739         }
740         final JMenuItem consclipbrd = new JMenuItem(COPYCONS_SEQ);
741         consclipbrd.addActionListener(this);
742         pop.add(consclipbrd);
743       }
744     }
745     pop.show(this, evt.getX(), evt.getY());
746   }
747
748   /**
749    * do a single sequence copy to jalview and the system clipboard
750    * 
751    * @param sq
752    *          sequence to be copied to clipboard
753    */
754   protected void copy_annotseqtoclipboard(SequenceI sq)
755   {
756     SequenceI[] seqs = new SequenceI[]
757     { sq };
758     String[] omitHidden = null;
759     SequenceI[] dseqs = new SequenceI[]
760     { sq.getDatasetSequence() };
761     if (dseqs[0] == null)
762     {
763       dseqs[0] = new Sequence(sq);
764       dseqs[0].setSequence(jalview.analysis.AlignSeq.extractGaps(
765               jalview.util.Comparison.GapChars, sq.getSequenceAsString()));
766
767       sq.setDatasetSequence(dseqs[0]);
768     }
769     Alignment ds = new Alignment(dseqs);
770     if (av.hasHiddenColumns())
771     {
772       omitHidden = av.getColumnSelection().getVisibleSequenceStrings(0,
773               sq.getLength(), seqs);
774     }
775
776     String output = new FormatAdapter().formatSequences("Fasta", seqs,
777             omitHidden);
778
779     Toolkit.getDefaultToolkit().getSystemClipboard()
780             .setContents(new StringSelection(output), Desktop.instance);
781
782     Vector hiddenColumns = null;
783     if (av.hasHiddenColumns())
784     {
785       hiddenColumns = new Vector();
786       for (int i = 0; i < av.getColumnSelection().getHiddenColumns().size(); i++)
787       {
788         int[] region = (int[]) av.getColumnSelection().getHiddenColumns()
789                 .elementAt(i);
790
791         hiddenColumns.addElement(new int[]
792         { region[0], region[1] });
793       }
794     }
795
796     Desktop.jalviewClipboard = new Object[]
797     { seqs, ds, // what is the dataset of a consensus sequence ? need to flag
798         // sequence as special.
799         hiddenColumns };
800   }
801
802   /**
803    * DOCUMENT ME!
804    * 
805    * @param g1
806    *          DOCUMENT ME!
807    */
808   public void paintComponent(Graphics g)
809   {
810
811     int width = getWidth();
812     if (width == 0)
813     {
814       width = ap.calculateIdWidth().width + 4;
815     }
816
817     Graphics2D g2 = (Graphics2D) g;
818     if (av.antiAlias)
819     {
820       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
821               RenderingHints.VALUE_ANTIALIAS_ON);
822     }
823
824     drawComponent(g2, true, width);
825
826   }
827
828   /**
829    * Draw the full set of annotation Labels for the alignment at the given
830    * cursor
831    * 
832    * @param g
833    *          Graphics2D instance (needed for font scaling)
834    * @param width
835    *          Width for scaling labels
836    * 
837    */
838   public void drawComponent(Graphics g, int width)
839   {
840     drawComponent(g, false, width);
841   }
842
843   private final boolean debugRedraw = false;
844
845   /**
846    * Draw the full set of annotation Labels for the alignment at the given
847    * cursor
848    * 
849    * @param g
850    *          Graphics2D instance (needed for font scaling)
851    * @param clip
852    *          - true indicates that only current visible area needs to be
853    *          rendered
854    * @param width
855    *          Width for scaling labels
856    */
857   public void drawComponent(Graphics g, boolean clip, int width)
858   {
859     if (av.getFont().getSize() < 10)
860     {
861       g.setFont(font);
862     }
863     else
864     {
865       g.setFont(av.getFont());
866     }
867
868     FontMetrics fm = g.getFontMetrics(g.getFont());
869     g.setColor(Color.white);
870     g.fillRect(0, 0, getWidth(), getHeight());
871
872     g.translate(0, scrollOffset);
873     g.setColor(Color.black);
874
875     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
876     int fontHeight = g.getFont().getSize();
877     int y = 0;
878     int x = 0;
879     int graphExtras = 0;
880     int offset = 0;
881     Font baseFont = g.getFont();
882     FontMetrics baseMetrics = fm;
883     int ofontH = fontHeight;
884     int sOffset = 0;
885     int visHeight = 0;
886     int[] visr = (ap != null && ap.annotationPanel != null) ? ap.annotationPanel
887             .getVisibleVRange() : null;
888     if (clip && visr != null)
889     {
890       sOffset = visr[0];
891       visHeight = visr[1];
892     }
893     boolean visible = true, before = false, after = false;
894     if (aa != null)
895     {
896       hasHiddenRows = false;
897       int olY = 0;
898       for (int i = 0; i < aa.length; i++)
899       {
900         visible = true;
901         if (!aa[i].visible)
902         {
903           hasHiddenRows = true;
904           continue;
905         }
906         olY = y;
907         y += aa[i].height;
908         if (clip)
909         {
910           if (y < sOffset)
911           {
912             if (!before)
913             {
914               if (debugRedraw)
915               {
916                 System.out.println("before vis: " + i);
917               }
918               before = true;
919             }
920             // don't draw what isn't visible
921             continue;
922           }
923           if (olY > visHeight)
924           {
925
926             if (!after)
927             {
928               if (debugRedraw)
929               {
930                 System.out.println("Scroll offset: " + sOffset
931                         + " after vis: " + i);
932               }
933               after = true;
934             }
935             // don't draw what isn't visible
936             continue;
937           }
938         }
939         g.setColor(Color.black);
940
941         offset = -aa[i].height / 2;
942
943         if (aa[i].hasText)
944         {
945           offset += fm.getHeight() / 2;
946           offset -= fm.getDescent();
947         }
948         else
949           offset += fm.getDescent();
950
951         x = width - fm.stringWidth(aa[i].label) - 3;
952
953         if (aa[i].graphGroup > -1)
954         {
955           int groupSize = 0;
956           // TODO: JAL-1291 revise rendering model so the graphGroup map is
957           // computed efficiently for all visible labels
958           for (int gg = 0; gg < aa.length; gg++)
959           {
960             if (aa[gg].graphGroup == aa[i].graphGroup)
961             {
962               groupSize++;
963             }
964           }
965           if (groupSize * (fontHeight + 8) < aa[i].height)
966           {
967             graphExtras = (aa[i].height - (groupSize * (fontHeight + 8))) / 2;
968           }
969           else
970           {
971             // scale font to fit
972             float h = aa[i].height / (float) groupSize, s;
973             if (h < 9)
974             {
975               visible = false;
976             }
977             else
978             {
979               fontHeight = -8 + (int) h;
980               s = ((float) fontHeight) / (float) ofontH;
981               Font f = baseFont.deriveFont(AffineTransform
982                       .getScaleInstance(s, s));
983               g.setFont(f);
984               fm = g.getFontMetrics();
985               graphExtras = (aa[i].height - (groupSize * (fontHeight + 8))) / 2;
986             }
987           }
988           if (visible)
989           {
990             for (int gg = 0; gg < aa.length; gg++)
991             {
992               if (aa[gg].graphGroup == aa[i].graphGroup)
993               {
994                 x = width - fm.stringWidth(aa[gg].label) - 3;
995                 g.drawString(aa[gg].label, x, y - graphExtras);
996
997                 if (aa[gg]._linecolour != null)
998                 {
999
1000                   g.setColor(aa[gg]._linecolour);
1001                   g.drawLine(x, y - graphExtras + 3,
1002                           x + fm.stringWidth(aa[gg].label), y - graphExtras
1003                                   + 3);
1004                 }
1005
1006                 g.setColor(Color.black);
1007                 graphExtras += fontHeight + 8;
1008               }
1009             }
1010           }
1011           g.setFont(baseFont);
1012           fm = baseMetrics;
1013           fontHeight = ofontH;
1014         }
1015         else
1016         {
1017           g.drawString(aa[i].label, x, y + offset);
1018         }
1019       }
1020     }
1021
1022     if (resizePanel)
1023     {
1024       g.drawImage(image, 2, 0 - scrollOffset, this);
1025     }
1026     else if (dragEvent != null && aa != null)
1027     {
1028       g.setColor(Color.lightGray);
1029       g.drawString(aa[selectedRow].label, dragEvent.getX(),
1030               dragEvent.getY() - scrollOffset);
1031     }
1032
1033     if (!av.wrapAlignment && ((aa == null) || (aa.length < 1)))
1034     {
1035       g.drawString(MessageManager.getString("label.right_click"), 2, 8);
1036       g.drawString(MessageManager.getString("label.to_add_annotation"), 2,
1037               18);
1038     }
1039   }
1040 }