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