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