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