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