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