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