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