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