0c29840034c1629a0d54a385d1699d47b5c9af0c
[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           if (evt.getClickCount() == 1)
543           {
544             ap.seqPanel.ap.idPanel.highlightSearchResults(Arrays
545                     .asList(new SequenceI[]
546                     { aa[selectedRow].sequenceRef }));
547           }
548           else if (evt.getClickCount() >= 2)
549           {
550             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
551             SequenceGroup sg = ap.av.getSelectionGroup();
552             if (sg!=null)
553             {
554               // we make a copy rather than edit the current selection if no modifiers pressed
555               // see Enhancement JAL-1557
556               if (!(evt.isControlDown() || evt.isShiftDown()))
557               {
558                 sg = new SequenceGroup(sg);
559                 sg.clear();
560                 sg.addSequence(aa[selectedRow].sequenceRef, false);
561               } else {
562                 if (evt.isControlDown())
563                 {
564                   sg.addOrRemove(aa[selectedRow].sequenceRef, true);
565                 } else {
566                   // notionally, we should also add intermediate sequences from last added sequence ?
567                   sg.addSequence(aa[selectedRow].sequenceRef, true);
568                 }
569               }
570             } else {
571               sg = new SequenceGroup();
572               sg.setStartRes(0);
573               sg.setEndRes(ap.av.getAlignment().getWidth()-1);
574               sg.addSequence(aa[selectedRow].sequenceRef, false);
575             }
576             ap.av.setSelectionGroup(sg);
577             ap.av.sendSelection();
578             ap.paintAlignment(false);
579             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
580           }
581
582         }
583       }
584     }
585     if (!SwingUtilities.isRightMouseButton(evt))
586     {
587       return;
588     }
589
590     JPopupMenu pop = new JPopupMenu(
591             MessageManager.getString("label.annotations"));
592     JMenuItem item = new JMenuItem(ADDNEW);
593     item.addActionListener(this);
594     pop.add(item);
595     if (selectedRow < 0)
596     {
597       if (hasHiddenRows)
598       { // let the user make everything visible again
599         item = new JMenuItem(SHOWALL);
600         item.addActionListener(this);
601         pop.add(item);
602       }
603       pop.show(this, evt.getX(), evt.getY());
604       return;
605     }
606     item = new JMenuItem(EDITNAME);
607     item.addActionListener(this);
608     pop.add(item);
609     item = new JMenuItem(HIDE);
610     item.addActionListener(this);
611     pop.add(item);
612     // JAL-1264 hide all sequence-specific annotations of this type
613     final String label = aa[selectedRow].label;
614     if (selectedRow < aa.length)
615     {
616       if (aa[selectedRow].sequenceRef != null)
617       {
618         JMenuItem hideType = new JMenuItem();
619         String text = MessageManager.getString("label.hide_all") + " " + label;
620         hideType.setText(text);
621         hideType.addActionListener(new ActionListener()
622         {
623           @Override
624           public void actionPerformed(ActionEvent e)
625           {
626             for (AlignmentAnnotation ann : ap.av.getAlignment()
627                     .getAlignmentAnnotation())
628             {
629               if (ann.sequenceRef != null && ann.label != null
630                       && ann.label.equals(label))
631               {
632                 ann.visible = false;
633               }
634             }
635             refresh();
636           }
637         });
638         pop.add(hideType);
639       }
640     }
641     item = new JMenuItem(DELETE);
642     item.addActionListener(this);
643     pop.add(item);
644     if (hasHiddenRows)
645     {
646       item = new JMenuItem(SHOWALL);
647       item.addActionListener(this);
648       pop.add(item);
649     }
650     item = new JMenuItem(OUTPUT_TEXT);
651     item.addActionListener(this);
652     pop.add(item);
653     // TODO: annotation object should be typed for autocalculated/derived
654     // property methods
655     if (selectedRow < aa.length)
656     {
657       if (!aa[selectedRow].autoCalculated)
658       {
659         if (aa[selectedRow].graph == AlignmentAnnotation.NO_GRAPH)
660         {
661           // display formatting settings for this row.
662           pop.addSeparator();
663           // av and sequencegroup need to implement same interface for
664           item = new JCheckBoxMenuItem(TOGGLE_LABELSCALE,
665                   aa[selectedRow].scaleColLabel);
666           item.addActionListener(this);
667           pop.add(item);
668         }
669       }
670       else if (label.indexOf("Consensus") > -1)
671       {
672         pop.addSeparator();
673         // av and sequencegroup need to implement same interface for
674         final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
675                 "Ignore Gaps In Consensus",
676                 (aa[selectedRow].groupRef != null) ? aa[selectedRow].groupRef
677                         .getIgnoreGapsConsensus() : ap.av
678                         .getIgnoreGapsConsensus());
679         final AlignmentAnnotation aaa = aa[selectedRow];
680         cbmi.addActionListener(new ActionListener()
681         {
682           public void actionPerformed(ActionEvent e)
683           {
684             if (aaa.groupRef != null)
685             {
686               // TODO: pass on reference to ap so the view can be updated.
687               aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState());
688               ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
689             }
690             else
691             {
692               ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
693             }
694           }
695         });
696         pop.add(cbmi);
697         // av and sequencegroup need to implement same interface for
698         if (aaa.groupRef != null)
699         {
700           final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
701                   "Show Group Histogram",
702                   aa[selectedRow].groupRef.isShowConsensusHistogram());
703           chist.addActionListener(new ActionListener()
704           {
705             public void actionPerformed(ActionEvent e)
706             {
707               // TODO: pass on reference
708               // to ap
709               // so the
710               // view
711               // can be
712               // updated.
713               aaa.groupRef.setShowConsensusHistogram(chist.getState());
714               ap.repaint();
715               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
716             }
717           });
718           pop.add(chist);
719           final JCheckBoxMenuItem cprofl = new JCheckBoxMenuItem(
720                   "Show Group Logo",
721                   aa[selectedRow].groupRef.isShowSequenceLogo());
722           cprofl.addActionListener(new ActionListener()
723           {
724             public void actionPerformed(ActionEvent e)
725             {
726               // TODO: pass on reference
727               // to ap
728               // so the
729               // view
730               // can be
731               // updated.
732               aaa.groupRef.setshowSequenceLogo(cprofl.getState());
733               ap.repaint();
734               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
735             }
736           });
737           pop.add(cprofl);
738           final JCheckBoxMenuItem cproflnorm = new JCheckBoxMenuItem(
739                   "Normalise Group Logo",
740                   aa[selectedRow].groupRef.isNormaliseSequenceLogo());
741           cproflnorm.addActionListener(new ActionListener()
742           {
743             public void actionPerformed(ActionEvent e)
744             {
745
746               // TODO: pass on reference
747               // to ap
748               // so the
749               // view
750               // can be
751               // updated.
752               aaa.groupRef.setNormaliseSequenceLogo(cproflnorm.getState());
753               // automatically enable logo display if we're clicked
754               aaa.groupRef.setshowSequenceLogo(true);
755               ap.repaint();
756               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
757             }
758           });
759           pop.add(cproflnorm);
760         }
761         else
762         {
763           final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
764                   "Show Histogram", av.isShowConsensusHistogram());
765           chist.addActionListener(new ActionListener()
766           {
767             public void actionPerformed(ActionEvent e)
768             {
769               // TODO: pass on reference
770               // to ap
771               // so the
772               // view
773               // can be
774               // updated.
775               av.setShowConsensusHistogram(chist.getState());
776               ap.alignFrame.setMenusForViewport();
777               ap.repaint();
778               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
779             }
780           });
781           pop.add(chist);
782           final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
783                   "Show Logo", av.isShowSequenceLogo());
784           cprof.addActionListener(new ActionListener()
785           {
786             public void actionPerformed(ActionEvent e)
787             {
788               // TODO: pass on reference
789               // to ap
790               // so the
791               // view
792               // can be
793               // updated.
794               av.setShowSequenceLogo(cprof.getState());
795               ap.alignFrame.setMenusForViewport();
796               ap.repaint();
797               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
798             }
799           });
800           pop.add(cprof);
801           final JCheckBoxMenuItem cprofnorm = new JCheckBoxMenuItem(
802                   "Normalise Logo", av.isNormaliseSequenceLogo());
803           cprofnorm.addActionListener(new ActionListener()
804           {
805             public void actionPerformed(ActionEvent e)
806             {
807               // TODO: pass on reference
808               // to ap
809               // so the
810               // view
811               // can be
812               // updated.
813               av.setShowSequenceLogo(true);
814               av.setNormaliseSequenceLogo(cprofnorm.getState());
815               ap.alignFrame.setMenusForViewport();
816               ap.repaint();
817               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
818             }
819           });
820           pop.add(cprofnorm);
821         }
822         final JMenuItem consclipbrd = new JMenuItem(COPYCONS_SEQ);
823         consclipbrd.addActionListener(this);
824         pop.add(consclipbrd);
825       }
826     }
827     pop.show(this, evt.getX(), evt.getY());
828   }
829
830   /**
831    * do a single sequence copy to jalview and the system clipboard
832    * 
833    * @param sq
834    *          sequence to be copied to clipboard
835    */
836   protected void copy_annotseqtoclipboard(SequenceI sq)
837   {
838     SequenceI[] seqs = new SequenceI[]
839     { sq };
840     String[] omitHidden = null;
841     SequenceI[] dseqs = new SequenceI[]
842     { sq.getDatasetSequence() };
843     if (dseqs[0] == null)
844     {
845       dseqs[0] = new Sequence(sq);
846       dseqs[0].setSequence(jalview.analysis.AlignSeq.extractGaps(
847               jalview.util.Comparison.GapChars, sq.getSequenceAsString()));
848
849       sq.setDatasetSequence(dseqs[0]);
850     }
851     Alignment ds = new Alignment(dseqs);
852     if (av.hasHiddenColumns())
853     {
854       omitHidden = av.getColumnSelection().getVisibleSequenceStrings(0,
855               sq.getLength(), seqs);
856     }
857
858     String output = new FormatAdapter().formatSequences("Fasta", seqs,
859             omitHidden);
860
861     Toolkit.getDefaultToolkit().getSystemClipboard()
862             .setContents(new StringSelection(output), Desktop.instance);
863
864     Vector hiddenColumns = null;
865     if (av.hasHiddenColumns())
866     {
867       hiddenColumns = new Vector();
868       for (int i = 0; i < av.getColumnSelection().getHiddenColumns().size(); i++)
869       {
870         int[] region = (int[]) av.getColumnSelection().getHiddenColumns()
871                 .elementAt(i);
872
873         hiddenColumns.addElement(new int[]
874         { region[0], region[1] });
875       }
876     }
877
878     Desktop.jalviewClipboard = new Object[]
879     { seqs, ds, // what is the dataset of a consensus sequence ? need to flag
880         // sequence as special.
881         hiddenColumns };
882   }
883
884   /**
885    * DOCUMENT ME!
886    * 
887    * @param g1
888    *          DOCUMENT ME!
889    */
890   public void paintComponent(Graphics g)
891   {
892
893     int width = getWidth();
894     if (width == 0)
895     {
896       width = ap.calculateIdWidth().width + 4;
897     }
898
899     Graphics2D g2 = (Graphics2D) g;
900     if (av.antiAlias)
901     {
902       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
903               RenderingHints.VALUE_ANTIALIAS_ON);
904     }
905
906     drawComponent(g2, true, width);
907
908   }
909
910   /**
911    * Draw the full set of annotation Labels for the alignment at the given
912    * cursor
913    * 
914    * @param g
915    *          Graphics2D instance (needed for font scaling)
916    * @param width
917    *          Width for scaling labels
918    * 
919    */
920   public void drawComponent(Graphics g, int width)
921   {
922     drawComponent(g, false, width);
923   }
924
925   private final boolean debugRedraw = false;
926
927   /**
928    * Draw the full set of annotation Labels for the alignment at the given
929    * cursor
930    * 
931    * @param g
932    *          Graphics2D instance (needed for font scaling)
933    * @param clip
934    *          - true indicates that only current visible area needs to be
935    *          rendered
936    * @param width
937    *          Width for scaling labels
938    */
939   public void drawComponent(Graphics g, boolean clip, int width)
940   {
941     if (av.getFont().getSize() < 10)
942     {
943       g.setFont(font);
944     }
945     else
946     {
947       g.setFont(av.getFont());
948     }
949
950     FontMetrics fm = g.getFontMetrics(g.getFont());
951     g.setColor(Color.white);
952     g.fillRect(0, 0, getWidth(), getHeight());
953
954     g.translate(0, scrollOffset);
955     g.setColor(Color.black);
956
957     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
958     int fontHeight = g.getFont().getSize();
959     int y = 0;
960     int x = 0;
961     int graphExtras = 0;
962     int offset = 0;
963     Font baseFont = g.getFont();
964     FontMetrics baseMetrics = fm;
965     int ofontH = fontHeight;
966     int sOffset = 0;
967     int visHeight = 0;
968     int[] visr = (ap != null && ap.annotationPanel != null) ? ap.annotationPanel
969             .getVisibleVRange() : null;
970     if (clip && visr != null)
971     {
972       sOffset = visr[0];
973       visHeight = visr[1];
974     }
975     boolean visible = true, before = false, after = false;
976     if (aa != null)
977     {
978       hasHiddenRows = false;
979       int olY = 0;
980       for (int i = 0; i < aa.length; i++)
981       {
982         visible = true;
983         if (!aa[i].visible)
984         {
985           hasHiddenRows = true;
986           continue;
987         }
988         olY = y;
989         y += aa[i].height;
990         if (clip)
991         {
992           if (y < sOffset)
993           {
994             if (!before)
995             {
996               if (debugRedraw)
997               {
998                 System.out.println("before vis: " + i);
999               }
1000               before = true;
1001             }
1002             // don't draw what isn't visible
1003             continue;
1004           }
1005           if (olY > visHeight)
1006           {
1007
1008             if (!after)
1009             {
1010               if (debugRedraw)
1011               {
1012                 System.out.println("Scroll offset: " + sOffset
1013                         + " after vis: " + i);
1014               }
1015               after = true;
1016             }
1017             // don't draw what isn't visible
1018             continue;
1019           }
1020         }
1021         g.setColor(Color.black);
1022
1023         offset = -aa[i].height / 2;
1024
1025         if (aa[i].hasText)
1026         {
1027           offset += fm.getHeight() / 2;
1028           offset -= fm.getDescent();
1029         }
1030         else
1031           offset += fm.getDescent();
1032
1033         x = width - fm.stringWidth(aa[i].label) - 3;
1034
1035         if (aa[i].graphGroup > -1)
1036         {
1037           int groupSize = 0;
1038           // TODO: JAL-1291 revise rendering model so the graphGroup map is
1039           // computed efficiently for all visible labels
1040           for (int gg = 0; gg < aa.length; gg++)
1041           {
1042             if (aa[gg].graphGroup == aa[i].graphGroup)
1043             {
1044               groupSize++;
1045             }
1046           }
1047           if (groupSize * (fontHeight + 8) < aa[i].height)
1048           {
1049             graphExtras = (aa[i].height - (groupSize * (fontHeight + 8))) / 2;
1050           }
1051           else
1052           {
1053             // scale font to fit
1054             float h = aa[i].height / (float) groupSize, s;
1055             if (h < 9)
1056             {
1057               visible = false;
1058             }
1059             else
1060             {
1061               fontHeight = -8 + (int) h;
1062               s = ((float) fontHeight) / (float) ofontH;
1063               Font f = baseFont.deriveFont(AffineTransform
1064                       .getScaleInstance(s, s));
1065               g.setFont(f);
1066               fm = g.getFontMetrics();
1067               graphExtras = (aa[i].height - (groupSize * (fontHeight + 8))) / 2;
1068             }
1069           }
1070           if (visible)
1071           {
1072             for (int gg = 0; gg < aa.length; gg++)
1073             {
1074               if (aa[gg].graphGroup == aa[i].graphGroup)
1075               {
1076                 x = width - fm.stringWidth(aa[gg].label) - 3;
1077                 g.drawString(aa[gg].label, x, y - graphExtras);
1078
1079                 if (aa[gg]._linecolour != null)
1080                 {
1081
1082                   g.setColor(aa[gg]._linecolour);
1083                   g.drawLine(x, y - graphExtras + 3,
1084                           x + fm.stringWidth(aa[gg].label), y - graphExtras
1085                                   + 3);
1086                 }
1087
1088                 g.setColor(Color.black);
1089                 graphExtras += fontHeight + 8;
1090               }
1091             }
1092           }
1093           g.setFont(baseFont);
1094           fm = baseMetrics;
1095           fontHeight = ofontH;
1096         }
1097         else
1098         {
1099           g.drawString(aa[i].label, x, y + offset);
1100         }
1101       }
1102     }
1103
1104     if (resizePanel)
1105     {
1106       g.drawImage(image, 2, 0 - scrollOffset, this);
1107     }
1108     else if (dragEvent != null && aa != null)
1109     {
1110       g.setColor(Color.lightGray);
1111       g.drawString(aa[selectedRow].label, dragEvent.getX(),
1112               dragEvent.getY() - scrollOffset);
1113     }
1114
1115     if (!av.wrapAlignment && ((aa == null) || (aa.length < 1)))
1116     {
1117       g.drawString(MessageManager.getString("label.right_click"), 2, 8);
1118       g.drawString(MessageManager.getString("label.to_add_annotation"), 2,
1119               18);
1120     }
1121   }
1122 }