Merge develop to Release_2_8_3_Branch
[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 java.awt.Checkbox;
24 import java.awt.CheckboxMenuItem;
25 import java.awt.Color;
26 import java.awt.Dimension;
27 import java.awt.FlowLayout;
28 import java.awt.FontMetrics;
29 import java.awt.Frame;
30 import java.awt.Graphics;
31 import java.awt.Image;
32 import java.awt.MenuItem;
33 import java.awt.Panel;
34 import java.awt.PopupMenu;
35 import java.awt.event.ActionEvent;
36 import java.awt.event.ActionListener;
37 import java.awt.event.InputEvent;
38 import java.awt.event.ItemEvent;
39 import java.awt.event.ItemListener;
40 import java.awt.event.MouseEvent;
41 import java.awt.event.MouseListener;
42 import java.awt.event.MouseMotionListener;
43 import java.util.Arrays;
44 import java.util.Collections;
45 import java.util.Vector;
46
47 import jalview.analysis.AlignmentUtils;
48 import jalview.datamodel.AlignmentAnnotation;
49 import jalview.datamodel.Annotation;
50 import jalview.datamodel.SequenceGroup;
51 import jalview.datamodel.SequenceI;
52 import jalview.util.MessageManager;
53 import jalview.util.ParseHtmlBodyAndLinks;
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.getString("label.show_group_histogram"),
544                       aa[selectedRow].groupRef.isShowConsensusHistogram());
545               chist.addItemListener(new ItemListener()
546               {
547                 public void itemStateChanged(ItemEvent e)
548                 {
549                   // TODO: pass on reference
550                   // to ap
551                   // so the
552                   // view
553                   // can be
554                   // updated.
555                   aaa.groupRef.setShowConsensusHistogram(chist.getState());
556                   ap.repaint();
557                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
558                 }
559               });
560               popup.add(chist);
561               final CheckboxMenuItem cprofl = new CheckboxMenuItem(
562                       MessageManager.getString("label.show_group_logo"),
563                       aa[selectedRow].groupRef.isShowSequenceLogo());
564               cprofl.addItemListener(new ItemListener()
565               {
566                 public void itemStateChanged(ItemEvent e)
567                 {
568                   // TODO: pass on reference
569                   // to ap
570                   // so the
571                   // view
572                   // can be
573                   // updated.
574                   aaa.groupRef.setshowSequenceLogo(cprofl.getState());
575                   ap.repaint();
576                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
577                 }
578               });
579
580               popup.add(cprofl);
581               final CheckboxMenuItem cprofn = new CheckboxMenuItem(
582                       MessageManager.getString("label.normalise_group_logo"),
583                       aa[selectedRow].groupRef.isNormaliseSequenceLogo());
584               cprofn.addItemListener(new ItemListener()
585               {
586                 public void itemStateChanged(ItemEvent e)
587                 {
588                   // TODO: pass on reference
589                   // to ap
590                   // so the
591                   // view
592                   // can be
593                   // updated.
594                   aaa.groupRef.setshowSequenceLogo(true);
595                   aaa.groupRef.setNormaliseSequenceLogo(cprofn.getState());
596                   ap.repaint();
597                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
598                 }
599               });
600               popup.add(cprofn);
601             }
602             else
603             {
604               final CheckboxMenuItem chist = new CheckboxMenuItem(
605                       MessageManager.getString("label.show_histogram"), av.isShowConsensusHistogram());
606               chist.addItemListener(new ItemListener()
607               {
608                 public void itemStateChanged(ItemEvent e)
609                 {
610                   // TODO: pass on reference
611                   // to ap
612                   // so the
613                   // view
614                   // can be
615                   // updated.
616                   av.setShowConsensusHistogram(chist.getState());
617                   ap.alignFrame.showConsensusHistogram.setState(chist
618                           .getState()); // TODO: implement
619                                         // ap.updateGUI()/alignFrame.updateGUI
620                                         // for applet
621                   ap.repaint();
622                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
623                 }
624               });
625               popup.add(chist);
626               final CheckboxMenuItem cprof = new CheckboxMenuItem(
627                       MessageManager.getString("label.show_logo"), av.isShowSequenceLogo());
628               cprof.addItemListener(new ItemListener()
629               {
630                 public void itemStateChanged(ItemEvent e)
631                 {
632                   // TODO: pass on reference
633                   // to ap
634                   // so the
635                   // view
636                   // can be
637                   // updated.
638                   av.setShowSequenceLogo(cprof.getState());
639                   ap.alignFrame.showSequenceLogo.setState(cprof.getState()); // TODO:
640                                                                              // implement
641                                                                              // ap.updateGUI()/alignFrame.updateGUI
642                                                                              // for
643                                                                              // applet
644                   ap.repaint();
645                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
646                 }
647               });
648               popup.add(cprof);
649               final CheckboxMenuItem cprofn = new CheckboxMenuItem(
650                       MessageManager.getString("label.normalise_logo"), av.isNormaliseSequenceLogo());
651               cprofn.addItemListener(new ItemListener()
652               {
653                 public void itemStateChanged(ItemEvent e)
654                 {
655                   // TODO: pass on reference
656                   // to ap
657                   // so the
658                   // view
659                   // can be
660                   // updated.
661                   av.setShowSequenceLogo(true);
662                   ap.alignFrame.normSequenceLogo.setState(cprofn.getState()); // TODO:
663                                                                               // implement
664                                                                               // ap.updateGUI()/alignFrame.updateGUI
665                                                                               // for
666                                                                               // applet
667                   av.setNormaliseSequenceLogo(cprofn.getState());
668                   ap.repaint();
669                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
670                 }
671               });
672               popup.add(cprofn);
673             }
674
675             item = new MenuItem(COPYCONS_SEQ);
676             item.addActionListener(this);
677             popup.add(item);
678           }
679         }
680       }
681       popup.show(this, evt.getX(), evt.getY());
682     }
683     else
684     {
685       // selection action.
686       if (selectedRow > -1 && selectedRow < aa.length)
687       {
688         if (aa[selectedRow].groupRef != null)
689         {
690           if (evt.getClickCount() >= 2)
691           {
692             // todo: make the ap scroll to the selection - not necessary, first
693             // click highlights/scrolls, second selects
694             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
695             ap.av.setSelectionGroup(// new SequenceGroup(
696             aa[selectedRow].groupRef); // );
697             ap.av.sendSelection();
698             ap.paintAlignment(false);
699             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
700           }
701           else
702           {
703             ap.seqPanel.ap.idPanel
704                     .highlightSearchResults(aa[selectedRow].groupRef
705                             .getSequences(null));
706           }
707           return;
708         }
709         else if (aa[selectedRow].sequenceRef != null)
710         {
711           if (evt.getClickCount() == 1)
712           {
713             ap.seqPanel.ap.idPanel.highlightSearchResults(Arrays
714                     .asList(new SequenceI[]
715                     { aa[selectedRow].sequenceRef }));
716           }
717           else if (evt.getClickCount() >= 2)
718           {
719             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
720             SequenceGroup sg = ap.av.getSelectionGroup();
721             if (sg!=null)
722             {
723               // we make a copy rather than edit the current selection if no modifiers pressed
724               // see Enhancement JAL-1557
725               if (!(evt.isControlDown() || evt.isShiftDown()))
726               {
727                 sg = new SequenceGroup(sg);
728                 sg.clear();
729                 sg.addSequence(aa[selectedRow].sequenceRef, false);
730               } else {
731                 if (evt.isControlDown())
732                 {
733                   sg.addOrRemove(aa[selectedRow].sequenceRef, true);
734                 } else {
735                   // notionally, we should also add intermediate sequences from last added sequence ?
736                   sg.addSequence(aa[selectedRow].sequenceRef, true);
737                 }
738               }
739             } else {
740               sg = new SequenceGroup();
741               sg.setStartRes(0);
742               sg.setEndRes(ap.av.getAlignment().getWidth()-1);
743               sg.addSequence(aa[selectedRow].sequenceRef, false);
744             }
745             ap.av.setSelectionGroup(sg);
746             ap.paintAlignment(false);
747             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
748             ap.av.sendSelection();
749           }
750
751         }
752       }
753
754     }
755   }
756
757   /**
758    * DOCUMENT ME!
759    * 
760    * @param e
761    *          DOCUMENT ME!
762    */
763   protected void copy_annotseqtoclipboard(SequenceI sq)
764   {
765     if (sq == null || sq.getLength() < 1)
766     {
767       return;
768     }
769     jalview.appletgui.AlignFrame.copiedSequences = new StringBuffer();
770     jalview.appletgui.AlignFrame.copiedSequences.append(sq.getName() + "\t"
771             + sq.getStart() + "\t" + sq.getEnd() + "\t"
772             + sq.getSequenceAsString() + "\n");
773     if (av.hasHiddenColumns())
774     {
775       jalview.appletgui.AlignFrame.copiedHiddenColumns = new Vector();
776       for (int[] region : av.getColumnSelection().getHiddenColumns())
777       {
778         jalview.appletgui.AlignFrame.copiedHiddenColumns
779                 .addElement(new int[]
780                 { region[0], region[1] });
781       }
782     }
783   }
784
785   public void update(Graphics g)
786   {
787     paint(g);
788   }
789
790   public void paint(Graphics g)
791   {
792     int w = getSize().width;
793     int h = getSize().height;
794     if (image == null || w != image.getWidth(this)
795             || h != image.getHeight(this))
796     {
797       image = createImage(w, ap.annotationPanel.getSize().height);
798     }
799
800     drawComponent(image.getGraphics(), w);
801     g.drawImage(image, 0, 0, this);
802   }
803
804   public void drawComponent(Graphics g, int width)
805   {
806     g.setFont(av.getFont());
807     FontMetrics fm = g.getFontMetrics(av.getFont());
808     g.setColor(Color.white);
809     g.fillRect(0, 0, getSize().width, getSize().height);
810
811     g.translate(0, -scrollOffset);
812     g.setColor(Color.black);
813
814     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
815     int y = 0, fy = g.getFont().getSize();
816     int x = 0, offset;
817
818     if (aa != null)
819     {
820       hasHiddenRows = false;
821       for (int i = 0; i < aa.length; i++)
822       {
823         if (!aa[i].visible)
824         {
825           hasHiddenRows = true;
826           continue;
827         }
828
829         x = width - fm.stringWidth(aa[i].label) - 3;
830
831         y += aa[i].height;
832         offset = -(aa[i].height - fy) / 2;
833
834         g.drawString(aa[i].label, x, y + offset);
835       }
836     }
837     g.translate(0, +scrollOffset);
838     if (resizePanel)
839     {
840       g.setColor(Color.red);
841       g.setPaintMode();
842       g.drawLine(2, 8, 5, 2);
843       g.drawLine(5, 2, 8, 8);
844     }
845     else if (!dragCancelled && dragEvent != null && aa != null)
846     {
847       g.setColor(Color.lightGray);
848       g.drawString(aa[selectedRow].label, dragEvent.getX(),
849               dragEvent.getY());
850     }
851
852     if (!av.getWrapAlignment() && ((aa == null) || (aa.length < 1)))
853     {
854       g.setColor(Color.black);
855       g.drawString(MessageManager.getString("label.right_click"), 2, 8);
856       g.drawString(MessageManager.getString("label.to_add_annotation"), 2,
857               18);
858     }
859   }
860 }