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