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