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