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