cbd243461837ac9a7dde3f0345f69fb133add4a2
[jalview.git] / src / jalview / appletgui / 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.appletgui;
22
23 import jalview.analysis.AlignmentUtils;
24 import jalview.datamodel.AlignmentAnnotation;
25 import jalview.datamodel.Annotation;
26 import jalview.datamodel.SequenceGroup;
27 import jalview.datamodel.SequenceI;
28 import jalview.util.MessageManager;
29 import jalview.util.ParseHtmlBodyAndLinks;
30
31 import java.awt.Checkbox;
32 import java.awt.CheckboxMenuItem;
33 import java.awt.Color;
34 import java.awt.Cursor;
35 import java.awt.Dimension;
36 import java.awt.FlowLayout;
37 import java.awt.FontMetrics;
38 import java.awt.Frame;
39 import java.awt.Graphics;
40 import java.awt.Image;
41 import java.awt.MenuItem;
42 import java.awt.Panel;
43 import java.awt.PopupMenu;
44 import java.awt.event.ActionEvent;
45 import java.awt.event.ActionListener;
46 import java.awt.event.InputEvent;
47 import java.awt.event.ItemEvent;
48 import java.awt.event.ItemListener;
49 import java.awt.event.MouseEvent;
50 import java.awt.event.MouseListener;
51 import java.awt.event.MouseMotionListener;
52 import java.util.Arrays;
53 import java.util.Collections;
54 import java.util.Vector;
55
56 public class AnnotationLabels extends Panel
57         implements ActionListener, MouseListener, MouseMotionListener
58 {
59   Image image;
60
61   boolean active = false;
62
63   AlignmentPanel ap;
64
65   AlignViewport av;
66
67   boolean resizing = false;
68
69   int oldY, mouseX;
70
71   static String ADDNEW = "Add New Row";
72
73   static String EDITNAME = "Edit Label/Description";
74
75   static String HIDE = "Hide This Row";
76
77   static String SHOWALL = "Show All Hidden Rows";
78
79   static String OUTPUT_TEXT = "Show Values In Textbox";
80
81   static String COPYCONS_SEQ = "Copy Consensus Sequence";
82
83   int scrollOffset = 0;
84
85   int selectedRow = -1;
86
87   Tooltip tooltip;
88
89   private boolean hasHiddenRows;
90
91   public AnnotationLabels(AlignmentPanel ap)
92   {
93     this.ap = ap;
94     this.av = ap.av;
95     setLayout(null);
96
97     /**
98      * this retrieves the adjustable height glyph from resources. we don't use
99      * it at the moment. java.net.URL url =
100      * getClass().getResource("/images/idwidth.gif"); Image temp = null;
101      * 
102      * if (url != null) { temp =
103      * java.awt.Toolkit.getDefaultToolkit().createImage(url); }
104      * 
105      * try { MediaTracker mt = new MediaTracker(this); mt.addImage(temp, 0);
106      * mt.waitForID(0); } catch (Exception ex) { }
107      * 
108      * BufferedImage bi = new BufferedImage(temp.getHeight(this),
109      * temp.getWidth(this), BufferedImage.TYPE_INT_RGB); Graphics2D g =
110      * (Graphics2D) bi.getGraphics(); g.rotate(Math.toRadians(90));
111      * g.drawImage(temp, 0, -bi.getWidth(this), this); image = (Image) bi;
112      */
113     addMouseListener(this);
114     addMouseMotionListener(this);
115   }
116
117   public AnnotationLabels(AlignViewport av)
118   {
119     this.av = av;
120   }
121
122   public void setScrollOffset(int y, boolean repaint)
123   {
124     scrollOffset = y;
125     if (repaint)
126     {
127       repaint();
128     }
129   }
130
131   /**
132    * 
133    * @param y
134    * @return -2 if no rows are visible at all, -1 if no visible rows were
135    *         selected
136    */
137   int getSelectedRow(int y)
138   {
139     int row = -2;
140     AlignmentAnnotation[] aa = ap.av.getAlignment()
141             .getAlignmentAnnotation();
142
143     if (aa == null)
144     {
145       return row;
146     }
147     int height = 0;
148     for (int i = 0; i < aa.length; i++)
149     {
150       row = -1;
151       if (!aa[i].visible)
152       {
153         continue;
154       }
155       height += aa[i].height;
156       if (y < height)
157       {
158         row = i;
159         break;
160       }
161     }
162
163     return row;
164   }
165
166   @Override
167   public void actionPerformed(ActionEvent evt)
168   {
169     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
170
171     if (evt.getActionCommand().equals(ADDNEW))
172     {
173       AlignmentAnnotation newAnnotation = new AlignmentAnnotation("", null,
174               new Annotation[ap.av.getAlignment().getWidth()]);
175
176       if (!editLabelDescription(newAnnotation))
177       {
178         return;
179       }
180
181       ap.av.getAlignment().addAnnotation(newAnnotation);
182       ap.av.getAlignment().setAnnotationIndex(newAnnotation, 0);
183     }
184     else if (evt.getActionCommand().equals(EDITNAME))
185     {
186       editLabelDescription(aa[selectedRow]);
187     }
188     else if (evt.getActionCommand().equals(HIDE))
189     {
190       aa[selectedRow].visible = false;
191     }
192     else if (evt.getActionCommand().equals(SHOWALL))
193     {
194       for (int i = 0; i < aa.length; i++)
195       {
196         aa[i].visible = (aa[i].annotations == null) ? false : true;
197       }
198     }
199     else if (evt.getActionCommand().equals(OUTPUT_TEXT))
200     {
201       CutAndPasteTransfer cap = new CutAndPasteTransfer(false,
202               ap.alignFrame);
203       Frame frame = new Frame();
204       frame.add(cap);
205       jalview.bin.JalviewLite.addFrame(frame,
206               ap.alignFrame.getTitle() + " - " + aa[selectedRow].label, 500,
207               100);
208       cap.setText(aa[selectedRow].toString());
209     }
210     else if (evt.getActionCommand().equals(COPYCONS_SEQ))
211     {
212       SequenceI cons = av.getConsensusSeq();
213       if (cons != null)
214       {
215         copy_annotseqtoclipboard(cons);
216       }
217
218     }
219     refresh();
220   }
221
222   /**
223    * Adjust size and repaint
224    */
225   protected void refresh()
226   {
227     ap.annotationPanel.adjustPanelHeight();
228     setSize(getSize().width, ap.annotationPanel.getSize().height);
229     ap.validate();
230     // TODO: only paint if we needed to
231     ap.paintAlignment(true, true);
232   }
233
234   boolean editLabelDescription(AlignmentAnnotation annotation)
235   {
236     Checkbox padGaps = new Checkbox(
237             "Fill Empty Gaps With \"" + ap.av.getGapCharacter() + "\"",
238             annotation.padGaps);
239
240     EditNameDialog dialog = new EditNameDialog(annotation.label,
241             annotation.description, "      Annotation Label",
242             "Annotation Description", ap.alignFrame,
243             "Edit Annotation Name / Description", 500, 180, false);
244
245     Panel empty = new Panel(new FlowLayout());
246     empty.add(padGaps);
247     dialog.add(empty);
248     dialog.pack();
249
250     dialog.setVisible(true);
251
252     if (dialog.accept)
253     {
254       annotation.label = dialog.getName();
255       annotation.description = dialog.getDescription();
256       annotation.setPadGaps(padGaps.getState(), av.getGapCharacter());
257       repaint();
258       return true;
259     }
260     else
261     {
262       return false;
263     }
264
265   }
266
267   boolean resizePanel = false;
268
269   @Override
270   public void mouseMoved(MouseEvent evt)
271   {
272     resizePanel = evt.getY() < 10 && evt.getX() < 14;
273     setCursor(Cursor.getPredefinedCursor(
274             resizePanel ? Cursor.S_RESIZE_CURSOR : Cursor.DEFAULT_CURSOR));
275     int row = getSelectedRow(evt.getY() + scrollOffset);
276
277     if (row > -1)
278     {
279       ParseHtmlBodyAndLinks phb = new ParseHtmlBodyAndLinks(
280               av.getAlignment().getAlignmentAnnotation()[row]
281                       .getDescription(true),
282               true, "\n");
283       if (tooltip == null)
284       {
285         tooltip = new Tooltip(phb.getNonHtmlContent(), this);
286       }
287       else
288       {
289         tooltip.setTip(phb.getNonHtmlContent());
290       }
291     }
292     else if (tooltip != null)
293     {
294       tooltip.setTip("");
295     }
296   }
297
298   /**
299    * curent drag position
300    */
301   MouseEvent dragEvent = null;
302
303   /**
304    * flag to indicate drag events should be ignored
305    */
306   private boolean dragCancelled = false;
307
308   /**
309    * clear any drag events in progress
310    */
311   public void cancelDrag()
312   {
313     dragEvent = null;
314     dragCancelled = true;
315   }
316
317   @Override
318   public void mouseDragged(MouseEvent evt)
319   {
320     if (dragCancelled)
321     {
322       return;
323     }
324     ;
325     dragEvent = evt;
326
327     if (resizePanel)
328     {
329       Dimension d = ap.annotationPanelHolder.getSize(),
330               e = ap.annotationSpaceFillerHolder.getSize(),
331               f = ap.seqPanelHolder.getSize();
332       int dif = evt.getY() - oldY;
333
334       dif /= ap.av.getCharHeight();
335       dif *= ap.av.getCharHeight();
336
337       if ((d.height - dif) > 20 && (f.height + dif) > 20)
338       {
339         ap.annotationPanel.setSize(d.width, d.height - dif);
340         setSize(new Dimension(e.width, d.height - dif));
341         ap.annotationSpaceFillerHolder
342                 .setSize(new Dimension(e.width, d.height - dif));
343         ap.annotationPanelHolder
344                 .setSize(new Dimension(d.width, d.height - dif));
345         ap.apvscroll.setValues(ap.apvscroll.getValue(), d.height - dif, 0,
346                 av.calcPanelHeight());
347         f.height += dif;
348         ap.seqPanelHolder.setPreferredSize(f);
349         ap.setScrollValues(av.getRanges().getStartRes(),
350                 av.getRanges().getStartSeq());
351         ap.validate();
352         // ap.paintAlignment(true);
353         ap.addNotify();
354       }
355
356     }
357     else
358     {
359       int diff;
360       if ((diff = 6 - evt.getY()) > 0)
361       {
362         // nudge scroll up
363         ap.apvscroll.setValue(ap.apvscroll.getValue() - diff);
364         ap.adjustmentValueChanged(null);
365
366       }
367       else if ((0 < (diff = 6
368               - ap.annotationSpaceFillerHolder.getSize().height
369               + evt.getY())))
370       {
371         // nudge scroll down
372         ap.apvscroll.setValue(ap.apvscroll.getValue() + diff);
373         ap.adjustmentValueChanged(null);
374       }
375       repaint();
376     }
377   }
378
379   @Override
380   public void mouseClicked(MouseEvent evt)
381   {
382   }
383
384   @Override
385   public void mouseReleased(MouseEvent evt)
386   {
387     if (!resizePanel && !dragCancelled)
388     {
389       int start = selectedRow;
390
391       int end = getSelectedRow(evt.getY() + scrollOffset);
392
393       if (start > -1 && start != end)
394       {
395         // Swap these annotations
396         AlignmentAnnotation startAA = ap.av.getAlignment()
397                 .getAlignmentAnnotation()[start];
398         if (end == -1)
399         {
400           end = ap.av.getAlignment().getAlignmentAnnotation().length - 1;
401         }
402         AlignmentAnnotation endAA = ap.av.getAlignment()
403                 .getAlignmentAnnotation()[end];
404
405         ap.av.getAlignment().getAlignmentAnnotation()[end] = startAA;
406         ap.av.getAlignment().getAlignmentAnnotation()[start] = endAA;
407       }
408     }
409     resizePanel = false;
410     dragEvent = null;
411     dragCancelled = false;
412     setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
413     repaint();
414     ap.annotationPanel.repaint();
415   }
416
417   @Override
418   public void mouseEntered(MouseEvent evt)
419   {
420     if (evt.getY() < 10 && evt.getX() < 14)
421     {
422       resizePanel = true;
423       repaint();
424     }
425     setCursor(Cursor.getPredefinedCursor(
426             resizePanel ? Cursor.S_RESIZE_CURSOR : Cursor.DEFAULT_CURSOR));
427   }
428
429   @Override
430   public void mouseExited(MouseEvent evt)
431   {
432     dragCancelled = false;
433
434     if (dragEvent == null)
435     {
436       resizePanel = false;
437     }
438     else
439     {
440       if (!resizePanel)
441       {
442         dragEvent = null;
443       }
444     }
445     repaint();
446   }
447
448   @Override
449   public void mousePressed(MouseEvent evt)
450   {
451     oldY = evt.getY();
452     if (resizePanel)
453     {
454       return;
455     }
456     dragCancelled = false;
457     // todo: move below to mouseClicked ?
458     selectedRow = getSelectedRow(evt.getY() + scrollOffset);
459
460     AlignmentAnnotation[] aa = ap.av.getAlignment()
461             .getAlignmentAnnotation();
462
463     // DETECT RIGHT MOUSE BUTTON IN AWT
464     if ((evt.getModifiers()
465             & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
466     {
467
468       PopupMenu popup = new PopupMenu(
469               MessageManager.getString("label.annotations"));
470
471       MenuItem item = new MenuItem(ADDNEW);
472       item.addActionListener(this);
473       popup.add(item);
474       if (selectedRow < 0)
475       {
476         // this never happens at moment: - see comment on JAL-563
477         if (hasHiddenRows)
478         {
479           item = new MenuItem(SHOWALL);
480           item.addActionListener(this);
481           popup.add(item);
482         }
483         this.add(popup);
484         popup.show(this, evt.getX(), evt.getY());
485         return;
486       }
487       // add the rest if there are actually rows to show
488       item = new MenuItem(EDITNAME);
489       item.addActionListener(this);
490       popup.add(item);
491       item = new MenuItem(HIDE);
492       item.addActionListener(this);
493       popup.add(item);
494
495       /*
496        * Hide all <label>:
497        */
498       if (selectedRow < aa.length)
499       {
500         if (aa[selectedRow].sequenceRef != null)
501         {
502           final String label = aa[selectedRow].label;
503           MenuItem hideType = new MenuItem(
504                   MessageManager.getString("label.hide_all") + " " + label);
505           hideType.addActionListener(new ActionListener()
506           {
507             @Override
508             public void actionPerformed(ActionEvent e)
509             {
510               AlignmentUtils.showOrHideSequenceAnnotations(
511                       ap.av.getAlignment(), Collections.singleton(label),
512                       null, false, false);
513               refresh();
514             }
515           });
516           popup.add(hideType);
517         }
518       }
519
520       if (hasHiddenRows)
521       {
522         item = new MenuItem(SHOWALL);
523         item.addActionListener(this);
524         popup.add(item);
525       }
526       this.add(popup);
527       item = new MenuItem(OUTPUT_TEXT);
528       item.addActionListener(this);
529       popup.add(item);
530       if (selectedRow < aa.length)
531       {
532         if (aa[selectedRow].autoCalculated)
533         {
534           if (aa[selectedRow].label.indexOf("Consensus") > -1)
535           {
536             popup.addSeparator();
537             final CheckboxMenuItem cbmi = new CheckboxMenuItem(
538                     MessageManager.getString("label.ignore_gaps_consensus"),
539                     (aa[selectedRow].groupRef != null)
540                             ? aa[selectedRow].groupRef
541                                     .getIgnoreGapsConsensus()
542                             : ap.av.isIgnoreGapsConsensus());
543             final AlignmentAnnotation aaa = aa[selectedRow];
544             cbmi.addItemListener(new ItemListener()
545             {
546               @Override
547               public void itemStateChanged(ItemEvent e)
548               {
549                 if (aaa.groupRef != null)
550                 {
551                   // TODO: pass on reference to ap so the view can be updated.
552                   aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState());
553                 }
554                 else
555                 {
556                   ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
557                 }
558                 ap.paintAlignment(true, true);
559               }
560             });
561             popup.add(cbmi);
562             if (aaa.groupRef != null)
563             {
564               final CheckboxMenuItem chist = new CheckboxMenuItem(
565                       MessageManager
566                               .getString("label.show_group_histogram"),
567                       aa[selectedRow].groupRef.isShowConsensusHistogram());
568               chist.addItemListener(new ItemListener()
569               {
570                 @Override
571                 public void itemStateChanged(ItemEvent e)
572                 {
573                   // TODO: pass on reference
574                   // to ap
575                   // so the
576                   // view
577                   // can be
578                   // updated.
579                   aaa.groupRef.setShowConsensusHistogram(chist.getState());
580                   ap.repaint();
581                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
582                 }
583               });
584               popup.add(chist);
585               final CheckboxMenuItem cprofl = new CheckboxMenuItem(
586                       MessageManager.getString("label.show_group_logo"),
587                       aa[selectedRow].groupRef.isShowSequenceLogo());
588               cprofl.addItemListener(new ItemListener()
589               {
590                 @Override
591                 public void itemStateChanged(ItemEvent e)
592                 {
593                   // TODO: pass on reference
594                   // to ap
595                   // so the
596                   // view
597                   // can be
598                   // updated.
599                   aaa.groupRef.setshowSequenceLogo(cprofl.getState());
600                   ap.repaint();
601                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
602                 }
603               });
604
605               popup.add(cprofl);
606               final CheckboxMenuItem cprofn = new CheckboxMenuItem(
607                       MessageManager
608                               .getString("label.normalise_group_logo"),
609                       aa[selectedRow].groupRef.isNormaliseSequenceLogo());
610               cprofn.addItemListener(new ItemListener()
611               {
612                 @Override
613                 public void itemStateChanged(ItemEvent e)
614                 {
615                   // TODO: pass on reference
616                   // to ap
617                   // so the
618                   // view
619                   // can be
620                   // updated.
621                   aaa.groupRef.setshowSequenceLogo(true);
622                   aaa.groupRef.setNormaliseSequenceLogo(cprofn.getState());
623                   ap.repaint();
624                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
625                 }
626               });
627               popup.add(cprofn);
628             }
629             else
630             {
631               final CheckboxMenuItem chist = new CheckboxMenuItem(
632                       MessageManager.getString("label.show_histogram"),
633                       av.isShowConsensusHistogram());
634               chist.addItemListener(new ItemListener()
635               {
636                 @Override
637                 public void itemStateChanged(ItemEvent e)
638                 {
639                   // TODO: pass on reference
640                   // to ap
641                   // so the
642                   // view
643                   // can be
644                   // updated.
645                   av.setShowConsensusHistogram(chist.getState());
646                   ap.alignFrame.showConsensusHistogram
647                           .setState(chist.getState()); // TODO: implement
648                                                        // ap.updateGUI()/alignFrame.updateGUI
649                                                        // for applet
650                   ap.repaint();
651                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
652                 }
653               });
654               popup.add(chist);
655               final CheckboxMenuItem cprof = new CheckboxMenuItem(
656                       MessageManager.getString("label.show_logo"),
657                       av.isShowSequenceLogo());
658               cprof.addItemListener(new ItemListener()
659               {
660                 @Override
661                 public void itemStateChanged(ItemEvent e)
662                 {
663                   // TODO: pass on reference
664                   // to ap
665                   // so the
666                   // view
667                   // can be
668                   // updated.
669                   av.setShowSequenceLogo(cprof.getState());
670                   ap.alignFrame.showSequenceLogo.setState(cprof.getState()); // TODO:
671                                                                              // implement
672                                                                              // ap.updateGUI()/alignFrame.updateGUI
673                                                                              // for
674                                                                              // applet
675                   ap.repaint();
676                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
677                 }
678               });
679               popup.add(cprof);
680               final CheckboxMenuItem cprofn = new CheckboxMenuItem(
681                       MessageManager.getString("label.normalise_logo"),
682                       av.isNormaliseSequenceLogo());
683               cprofn.addItemListener(new ItemListener()
684               {
685                 @Override
686                 public void itemStateChanged(ItemEvent e)
687                 {
688                   // TODO: pass on reference
689                   // to ap
690                   // so the
691                   // view
692                   // can be
693                   // updated.
694                   av.setShowSequenceLogo(true);
695                   ap.alignFrame.normSequenceLogo
696                           .setState(cprofn.getState()); // TODO:
697                                                         // implement
698                                                         // ap.updateGUI()/alignFrame.updateGUI
699                                                         // for
700                                                         // applet
701                   av.setNormaliseSequenceLogo(cprofn.getState());
702                   ap.repaint();
703                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
704                 }
705               });
706               popup.add(cprofn);
707             }
708
709             item = new MenuItem(COPYCONS_SEQ);
710             item.addActionListener(this);
711             popup.add(item);
712           }
713         }
714       }
715       popup.show(this, evt.getX(), evt.getY());
716     }
717     else
718     {
719       // selection action.
720       if (selectedRow > -1 && selectedRow < aa.length)
721       {
722         if (aa[selectedRow].groupRef != null)
723         {
724           if (evt.getClickCount() >= 2)
725           {
726             // todo: make the ap scroll to the selection - not necessary, first
727             // click highlights/scrolls, second selects
728             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
729             // process modifiers
730             SequenceGroup sg = ap.av.getSelectionGroup();
731             if (sg == null || sg == aa[selectedRow].groupRef
732                     || !(jalview.util.Platform.isControlDown(evt)
733                             || evt.isShiftDown()))
734             {
735               if (jalview.util.Platform.isControlDown(evt)
736                       || evt.isShiftDown())
737               {
738                 // clone a new selection group from the associated group
739                 ap.av.setSelectionGroup(
740                         new SequenceGroup(aa[selectedRow].groupRef));
741               }
742               else
743               {
744                 // set selection to the associated group so it can be edited
745                 ap.av.setSelectionGroup(aa[selectedRow].groupRef);
746               }
747             }
748             else
749             {
750               // modify current selection with associated group
751               int remainToAdd = aa[selectedRow].groupRef.getSize();
752               for (SequenceI sgs : aa[selectedRow].groupRef.getSequences())
753               {
754                 if (jalview.util.Platform.isControlDown(evt))
755                 {
756                   sg.addOrRemove(sgs, --remainToAdd == 0);
757                 }
758                 else
759                 {
760                   // notionally, we should also add intermediate sequences from
761                   // last added sequence ?
762                   sg.addSequence(sgs, --remainToAdd == 0);
763                 }
764               }
765             }
766             ap.paintAlignment(false, false);
767             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
768             ap.av.sendSelection();
769           }
770           else
771           {
772             ap.seqPanel.ap.idPanel.highlightSearchResults(
773                     aa[selectedRow].groupRef.getSequences(null));
774           }
775           return;
776         }
777         else if (aa[selectedRow].sequenceRef != null)
778         {
779           if (evt.getClickCount() == 1)
780           {
781             ap.seqPanel.ap.idPanel
782                     .highlightSearchResults(Arrays.asList(new SequenceI[]
783                     { aa[selectedRow].sequenceRef }));
784           }
785           else if (evt.getClickCount() >= 2)
786           {
787             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
788             SequenceGroup sg = ap.av.getSelectionGroup();
789             if (sg != null)
790             {
791               // we make a copy rather than edit the current selection if no
792               // modifiers pressed
793               // see Enhancement JAL-1557
794               if (!(jalview.util.Platform.isControlDown(evt)
795                       || evt.isShiftDown()))
796               {
797                 sg = new SequenceGroup(sg);
798                 sg.clear();
799                 sg.addSequence(aa[selectedRow].sequenceRef, false);
800               }
801               else
802               {
803                 if (jalview.util.Platform.isControlDown(evt))
804                 {
805                   sg.addOrRemove(aa[selectedRow].sequenceRef, true);
806                 }
807                 else
808                 {
809                   // notionally, we should also add intermediate sequences from
810                   // last added sequence ?
811                   sg.addSequence(aa[selectedRow].sequenceRef, true);
812                 }
813               }
814             }
815             else
816             {
817               sg = new SequenceGroup();
818               sg.setStartRes(0);
819               sg.setEndRes(ap.av.getAlignment().getWidth() - 1);
820               sg.addSequence(aa[selectedRow].sequenceRef, false);
821             }
822             ap.av.setSelectionGroup(sg);
823             ap.paintAlignment(false, false);
824             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
825             ap.av.sendSelection();
826           }
827
828         }
829       }
830
831     }
832   }
833
834   /**
835    * DOCUMENT ME!
836    * 
837    * @param e
838    *          DOCUMENT ME!
839    */
840   protected void copy_annotseqtoclipboard(SequenceI sq)
841   {
842     if (sq == null || sq.getLength() < 1)
843     {
844       return;
845     }
846     jalview.appletgui.AlignFrame.copiedSequences = new StringBuffer();
847     jalview.appletgui.AlignFrame.copiedSequences
848             .append(sq.getName() + "\t" + sq.getStart() + "\t" + sq.getEnd()
849                     + "\t" + sq.getSequenceAsString() + "\n");
850     if (av.hasHiddenColumns())
851     {
852       jalview.appletgui.AlignFrame.copiedHiddenColumns = new Vector<>(
853               av.getAlignment().getHiddenColumns().getHiddenColumnsCopy());
854     }
855   }
856
857   @Override
858   public void update(Graphics g)
859   {
860     paint(g);
861   }
862
863   @Override
864   public void paint(Graphics g)
865   {
866     int w = getSize().width;
867     int h = getSize().height;
868     if (image == null || w != image.getWidth(this)
869             || h != image.getHeight(this))
870     {
871       image = createImage(w, ap.annotationPanel.getSize().height);
872     }
873
874     drawComponent(image.getGraphics(), w);
875     g.drawImage(image, 0, 0, this);
876   }
877
878   public void drawComponent(Graphics g, int width)
879   {
880     g.setFont(av.getFont());
881     FontMetrics fm = g.getFontMetrics(av.getFont());
882     g.setColor(Color.white);
883     g.fillRect(0, 0, getSize().width, getSize().height);
884
885     g.translate(0, -scrollOffset);
886     g.setColor(Color.black);
887
888     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
889     int y = 0, fy = g.getFont().getSize();
890     int x = 0, offset;
891
892     if (aa != null)
893     {
894       hasHiddenRows = false;
895       for (int i = 0; i < aa.length; i++)
896       {
897         if (!aa[i].visible)
898         {
899           hasHiddenRows = true;
900           continue;
901         }
902
903         x = width - fm.stringWidth(aa[i].label) - 3;
904
905         y += aa[i].height;
906         offset = -(aa[i].height - fy) / 2;
907
908         g.drawString(aa[i].label, x, y + offset);
909       }
910     }
911     g.translate(0, +scrollOffset);
912
913     if (!resizePanel && !dragCancelled && dragEvent != null && aa != null)
914     {
915       g.setColor(Color.lightGray);
916       g.drawString(aa[selectedRow].label, dragEvent.getX(),
917               dragEvent.getY());
918     }
919
920     if (!av.getWrapAlignment() && ((aa == null) || (aa.length < 1)))
921     {
922       g.setColor(Color.black);
923       g.drawString(MessageManager.getString("label.right_click"), 2, 8);
924       g.drawString(MessageManager.getString("label.to_add_annotation"), 2,
925               18);
926     }
927   }
928 }