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