Jalview 2.6 source licence
[jalview.git] / src / jalview / gui / AnnotationLabels.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.6)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.gui;
19
20 import java.util.*;
21
22 import java.awt.*;
23 import java.awt.datatransfer.*;
24 import java.awt.event.*;
25 import java.awt.image.*;
26 import javax.swing.*;
27
28 import jalview.datamodel.*;
29 import jalview.io.*;
30
31 /**
32  * DOCUMENT ME!
33  * 
34  * @author $author$
35  * @version $Revision$
36  */
37 public class AnnotationLabels extends JPanel implements MouseListener,
38         MouseMotionListener, ActionListener
39 {
40   static String TOGGLE_LABELSCALE = "Scale Label to Column";
41
42   static String ADDNEW = "Add New Row";
43
44   static String EDITNAME = "Edit Label/Description";
45
46   static String HIDE = "Hide This Row";
47
48   static String DELETE = "Delete This Row";
49
50   static String SHOWALL = "Show All Hidden Rows";
51
52   static String OUTPUT_TEXT = "Export Annotation";
53
54   static String COPYCONS_SEQ = "Copy Consensus Sequence";
55
56   boolean resizePanel = false;
57
58   Image image;
59
60   AlignmentPanel ap;
61
62   AlignViewport av;
63
64   boolean resizing = false;
65
66   MouseEvent dragEvent;
67
68   int oldY;
69
70   int selectedRow;
71
72   int scrollOffset = 0;
73
74   Font font = new Font("Arial", Font.PLAIN, 11);
75
76   private boolean hasHiddenRows;
77
78   /**
79    * Creates a new AnnotationLabels object.
80    * 
81    * @param ap
82    *          DOCUMENT ME!
83    */
84   public AnnotationLabels(AlignmentPanel ap)
85   {
86     this.ap = ap;
87     av = ap.av;
88     ToolTipManager.sharedInstance().registerComponent(this);
89
90     java.net.URL url = getClass().getResource("/images/idwidth.gif");
91     Image temp = null;
92
93     if (url != null)
94     {
95       temp = java.awt.Toolkit.getDefaultToolkit().createImage(url);
96     }
97
98     try
99     {
100       MediaTracker mt = new MediaTracker(this);
101       mt.addImage(temp, 0);
102       mt.waitForID(0);
103     } catch (Exception ex)
104     {
105     }
106
107     BufferedImage bi = new BufferedImage(temp.getHeight(this), temp
108             .getWidth(this), BufferedImage.TYPE_INT_RGB);
109     Graphics2D g = (Graphics2D) bi.getGraphics();
110     g.rotate(Math.toRadians(90));
111     g.drawImage(temp, 0, -bi.getWidth(this), this);
112     image = (Image) bi;
113
114     addMouseListener(this);
115     addMouseMotionListener(this);
116   }
117
118   public AnnotationLabels(AlignViewport av)
119   {
120     this.av = av;
121   }
122
123   /**
124    * DOCUMENT ME!
125    * 
126    * @param y
127    *          DOCUMENT ME!
128    */
129   public void setScrollOffset(int y)
130   {
131     scrollOffset = y;
132     repaint();
133   }
134
135   /**
136    * sets selectedRow to -2 if no annotation preset, -1 if no visible row is at
137    * y
138    * 
139    * @param y
140    *          coordinate position to search for a row
141    */
142   void getSelectedRow(int y)
143   {
144     int height = 0;
145     AlignmentAnnotation[] aa = ap.av.alignment.getAlignmentAnnotation();
146     selectedRow = -2;
147     if (aa != null)
148     {
149       for (int i = 0; i < aa.length; i++)
150       {
151         selectedRow = -1;
152         if (!aa[i].visible)
153         {
154           continue;
155         }
156
157         height += aa[i].height;
158
159         if (y < height)
160         {
161           selectedRow = i;
162
163           break;
164         }
165       }
166     }
167   }
168
169   /**
170    * DOCUMENT ME!
171    * 
172    * @param evt
173    *          DOCUMENT ME!
174    */
175   public void actionPerformed(ActionEvent evt)
176   {
177     AlignmentAnnotation[] aa = ap.av.alignment.getAlignmentAnnotation();
178
179     if (evt.getActionCommand().equals(ADDNEW))
180     {
181       AlignmentAnnotation newAnnotation = new AlignmentAnnotation(null,
182               null, new Annotation[ap.av.alignment.getWidth()]);
183
184       if (!editLabelDescription(newAnnotation))
185       {
186         return;
187       }
188
189       ap.av.alignment.addAnnotation(newAnnotation);
190       ap.av.alignment.setAnnotationIndex(newAnnotation, 0);
191     }
192     else if (evt.getActionCommand().equals(EDITNAME))
193     {
194       editLabelDescription(aa[selectedRow]);
195       repaint();
196     }
197     else if (evt.getActionCommand().equals(HIDE))
198     {
199       aa[selectedRow].visible = false;
200
201       if (aa[selectedRow].label.equals("Quality"))
202       {
203         ap.av.quality = null;
204       }
205     }
206     else if (evt.getActionCommand().equals(DELETE))
207     {
208       ap.av.alignment.deleteAnnotation(aa[selectedRow]);
209     }
210     else if (evt.getActionCommand().equals(SHOWALL))
211     {
212       for (int i = 0; i < aa.length; i++)
213       {
214         if (!aa[i].visible && aa[i].annotations != null)
215         {
216           aa[i].visible = true;
217         }
218       }
219     }
220     else if (evt.getActionCommand().equals(OUTPUT_TEXT))
221     {
222       new AnnotationExporter().exportAnnotations(ap,
223               new AlignmentAnnotation[]
224               { aa[selectedRow] }, null, null);
225     }
226     else if (evt.getActionCommand().equals(COPYCONS_SEQ))
227     {
228       SequenceI cons = null;
229       if (aa[selectedRow].groupRef != null)
230       {
231         cons = aa[selectedRow].groupRef.getConsensusSeq();
232       }
233       else
234       {
235         cons = av.getConsensusSeq();
236       }
237       if (cons != null)
238       {
239         copy_annotseqtoclipboard(cons);
240       }
241
242     }
243     else if (evt.getActionCommand().equals(TOGGLE_LABELSCALE))
244     {
245       aa[selectedRow].scaleColLabel = !aa[selectedRow].scaleColLabel;
246     }
247
248     ap.annotationPanel.adjustPanelHeight();
249     ap.annotationScroller.validate();
250     ap.paintAlignment(true);
251   }
252
253   /**
254    * DOCUMENT ME!
255    * 
256    * @param e
257    *          DOCUMENT ME!
258    */
259   boolean editLabelDescription(AlignmentAnnotation annotation)
260   {
261     EditNameDialog dialog = new EditNameDialog(annotation.label,
262             annotation.description, "       Annotation Name ",
263             "Annotation Description ", "Edit Annotation Name/Description",
264             ap.alignFrame);
265
266     if (!dialog.accept)
267     {
268       return false;
269     }
270
271     annotation.label = dialog.getName();
272
273     String text = dialog.getDescription();
274     if (text != null && text.length() == 0)
275     {
276       text = null;
277     }
278     annotation.description = text;
279
280     return true;
281   }
282
283   /**
284    * DOCUMENT ME!
285    * 
286    * @param evt
287    *          DOCUMENT ME!
288    */
289   public void mousePressed(MouseEvent evt)
290   {
291     getSelectedRow(evt.getY() - scrollOffset);
292     oldY = evt.getY();
293   }
294
295   /**
296    * DOCUMENT ME!
297    * 
298    * @param evt
299    *          DOCUMENT ME!
300    */
301   public void mouseReleased(MouseEvent evt)
302   {
303     int start = selectedRow;
304     getSelectedRow(evt.getY() - scrollOffset);
305     int end = selectedRow;
306
307     if (start != end)
308     {
309       // Swap these annotations
310       AlignmentAnnotation startAA = ap.av.alignment
311               .getAlignmentAnnotation()[start];
312       if (end == -1)
313       {
314         end = ap.av.alignment.getAlignmentAnnotation().length - 1;
315       }
316       AlignmentAnnotation endAA = ap.av.alignment.getAlignmentAnnotation()[end];
317
318       ap.av.alignment.getAlignmentAnnotation()[end] = startAA;
319       ap.av.alignment.getAlignmentAnnotation()[start] = endAA;
320     }
321
322     resizePanel = false;
323     dragEvent = null;
324     repaint();
325     ap.annotationPanel.repaint();
326   }
327
328   /**
329    * DOCUMENT ME!
330    * 
331    * @param evt
332    *          DOCUMENT ME!
333    */
334   public void mouseEntered(MouseEvent evt)
335   {
336     if (evt.getY() < 10)
337     {
338       resizePanel = true;
339       repaint();
340     }
341   }
342
343   /**
344    * DOCUMENT ME!
345    * 
346    * @param evt
347    *          DOCUMENT ME!
348    */
349   public void mouseExited(MouseEvent evt)
350   {
351     if (dragEvent == null)
352     {
353       resizePanel = false;
354       repaint();
355     }
356   }
357
358   /**
359    * DOCUMENT ME!
360    * 
361    * @param evt
362    *          DOCUMENT ME!
363    */
364   public void mouseDragged(MouseEvent evt)
365   {
366     dragEvent = evt;
367
368     if (resizePanel)
369     {
370       Dimension d = ap.annotationScroller.getPreferredSize();
371       int dif = evt.getY() - oldY;
372
373       dif /= ap.av.charHeight;
374       dif *= ap.av.charHeight;
375
376       if ((d.height - dif) > 20)
377       {
378         ap.annotationScroller.setPreferredSize(new Dimension(d.width,
379                 d.height - dif));
380         d = ap.annotationSpaceFillerHolder.getPreferredSize();
381         ap.annotationSpaceFillerHolder.setPreferredSize(new Dimension(
382                 d.width, d.height - dif));
383         ap.paintAlignment(true);
384       }
385
386       ap.addNotify();
387     }
388     else
389     {
390       repaint();
391     }
392   }
393
394   /**
395    * DOCUMENT ME!
396    * 
397    * @param evt
398    *          DOCUMENT ME!
399    */
400   public void mouseMoved(MouseEvent evt)
401   {
402     resizePanel = evt.getY() < 10;
403
404     getSelectedRow(evt.getY() - scrollOffset);
405
406     if (selectedRow > -1
407             && ap.av.alignment.getAlignmentAnnotation().length > selectedRow)
408     {
409       AlignmentAnnotation aa = ap.av.alignment.getAlignmentAnnotation()[selectedRow];
410
411       StringBuffer desc = new StringBuffer("<html>");
412
413       if (aa.description != null
414               && !aa.description.equals("New description"))
415       {
416         desc.append(aa.getDescription(true));
417         if (aa.hasScore)
418           desc.append("<br>");
419       }
420       if (aa.hasScore())
421       {
422         desc.append("Score: " + aa.score);
423       }
424
425       if (desc.length() != 6)
426       {
427         desc.append("</html>");
428         this.setToolTipText(desc.toString());
429       }
430       else
431         this.setToolTipText(null);
432     }
433
434   }
435
436   /**
437    * DOCUMENT ME!
438    * 
439    * @param evt
440    *          DOCUMENT ME!
441    */
442   public void mouseClicked(MouseEvent evt)
443   {
444     AlignmentAnnotation[] aa = ap.av.alignment.getAlignmentAnnotation();
445     if (SwingUtilities.isLeftMouseButton(evt))
446     {
447       if (selectedRow > -1 && selectedRow < aa.length)
448       {
449         if (aa[selectedRow].groupRef != null)
450         {
451           if (evt.getClickCount() >= 2)
452           {
453             // todo: make the ap scroll to the selection
454             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
455             ap.av.setSelectionGroup(// new SequenceGroup(
456                     aa[selectedRow].groupRef); // );
457             ap.av.sendSelection();
458             ap.paintAlignment(false);
459             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
460           }
461           else
462           {
463             ap.seqPanel.ap.idPanel
464                     .highlightSearchResults(aa[selectedRow].groupRef
465                             .getSequences(null));
466           }
467           return;
468         }
469         else if (aa[selectedRow].sequenceRef != null)
470         {
471           Vector sr = new Vector();
472           sr.addElement(aa[selectedRow].sequenceRef);
473           if (evt.getClickCount() == 1)
474           {
475             ap.seqPanel.ap.idPanel.highlightSearchResults(sr);
476           }
477           else if (evt.getClickCount() >= 2)
478           {
479             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
480             SequenceGroup sg = new SequenceGroup();
481             sg.addSequence(aa[selectedRow].sequenceRef, false);
482             ap.av.setSelectionGroup(sg);
483             ap.av.sendSelection();
484             ap.paintAlignment(false);
485             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
486           }
487
488         }
489       }
490     }
491     if (!SwingUtilities.isRightMouseButton(evt))
492     {
493       return;
494     }
495
496     JPopupMenu pop = new JPopupMenu("Annotations");
497     JMenuItem item = new JMenuItem(ADDNEW);
498     item.addActionListener(this);
499     pop.add(item);
500     if (selectedRow < 0)
501     {
502       if (hasHiddenRows)
503       { // let the user make everything visible again
504         item = new JMenuItem(SHOWALL);
505         item.addActionListener(this);
506         pop.add(item);
507       }
508       pop.show(this, evt.getX(), evt.getY());
509       return;
510     }
511     item = new JMenuItem(EDITNAME);
512     item.addActionListener(this);
513     pop.add(item);
514     item = new JMenuItem(HIDE);
515     item.addActionListener(this);
516     pop.add(item);
517     item = new JMenuItem(DELETE);
518     item.addActionListener(this);
519     pop.add(item);
520     if (hasHiddenRows)
521     {
522       item = new JMenuItem(SHOWALL);
523       item.addActionListener(this);
524       pop.add(item);
525     }
526     item = new JMenuItem(OUTPUT_TEXT);
527     item.addActionListener(this);
528     pop.add(item);
529     // TODO: annotation object should be typed for autocalculated/derived
530     // property methods
531     if (selectedRow < aa.length)
532     {
533       if (!aa[selectedRow].autoCalculated)
534       {
535         if (aa[selectedRow].graph == AlignmentAnnotation.NO_GRAPH)
536         {
537           // display formatting settings for this row.
538           pop.addSeparator();
539           // av and sequencegroup need to implement same interface for
540           item = new JCheckBoxMenuItem(TOGGLE_LABELSCALE,
541                   aa[selectedRow].scaleColLabel);
542           item.addActionListener(this);
543           pop.add(item);
544         }
545       }
546       else if (aa[selectedRow].label.indexOf("Consensus") > -1)
547       {
548         pop.addSeparator();
549         // av and sequencegroup need to implement same interface for
550         final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
551                 "Ignore Gaps In Consensus",
552                 (aa[selectedRow].groupRef != null) ? aa[selectedRow].groupRef
553                         .getIgnoreGapsConsensus()
554                         : ap.av.getIgnoreGapsConsensus());
555         final AlignmentAnnotation aaa = aa[selectedRow];
556         cbmi.addActionListener(new ActionListener()
557         {
558           public void actionPerformed(ActionEvent e)
559           {
560             if (aaa.groupRef != null)
561             {
562               // TODO: pass on reference to ap so the view can be updated.
563               aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState());
564               ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
565             }
566             else
567             {
568               ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
569             }
570           }
571         });
572         pop.add(cbmi);
573         // av and sequencegroup need to implement same interface for
574         if (aaa.groupRef != null)
575         {
576           final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
577                   "Show Group Histogram", aa[selectedRow].groupRef
578                           .isShowConsensusHistogram());
579           chist.addActionListener(new ActionListener()
580           {
581             public void actionPerformed(ActionEvent e)
582             {
583               // TODO: pass on reference
584               // to ap
585               // so the
586               // view
587               // can be
588               // updated.
589               aaa.groupRef.setShowConsensusHistogram(chist.getState());
590               ap.repaint();
591               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
592             }
593           });
594           pop.add(chist);
595           final JCheckBoxMenuItem cprofl = new JCheckBoxMenuItem(
596                   "Show Group Logo", aa[selectedRow].groupRef
597                           .isShowSequenceLogo());
598           cprofl.addActionListener(new ActionListener()
599           {
600             public void actionPerformed(ActionEvent e)
601             {
602               // TODO: pass on reference
603               // to ap
604               // so the
605               // view
606               // can be
607               // updated.
608               aaa.groupRef.setshowSequenceLogo(cprofl.getState());
609               ap.repaint();
610               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
611             }
612           });
613           pop.add(cprofl);
614         }
615         else
616         {
617           final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
618                   "Show Histogram", av.isShowConsensusHistogram());
619           chist.addActionListener(new ActionListener()
620           {
621             public void actionPerformed(ActionEvent e)
622             {
623               // TODO: pass on reference
624               // to ap
625               // so the
626               // view
627               // can be
628               // updated.
629               av.setShowConsensusHistogram(chist.getState());
630               ap.repaint();
631               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
632             }
633           });
634           pop.add(chist);
635           final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
636                   "Show Logo", av.isShowSequenceLogo());
637           cprof.addActionListener(new ActionListener()
638           {
639             public void actionPerformed(ActionEvent e)
640             {
641               // TODO: pass on reference
642               // to ap
643               // so the
644               // view
645               // can be
646               // updated.
647               av.setShowSequenceLogo(cprof.getState());
648               ap.repaint();
649               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
650             }
651           });
652           pop.add(cprof);
653         }
654         final JMenuItem consclipbrd = new JMenuItem(COPYCONS_SEQ);
655         consclipbrd.addActionListener(this);
656         pop.add(consclipbrd);
657       }
658     }
659     pop.show(this, evt.getX(), evt.getY());
660   }
661
662   /**
663    * do a single sequence copy to jalview and the system clipboard
664    * 
665    * @param sq
666    *          sequence to be copied to clipboard
667    */
668   protected void copy_annotseqtoclipboard(SequenceI sq)
669   {
670     SequenceI[] seqs = new SequenceI[]
671     { sq };
672     String[] omitHidden = null;
673     SequenceI[] dseqs = new SequenceI[]
674     { sq.getDatasetSequence() };
675     if (dseqs[0] == null)
676     {
677       dseqs[0] = new Sequence(sq);
678       dseqs[0].setSequence(jalview.analysis.AlignSeq.extractGaps(
679               jalview.util.Comparison.GapChars, sq.getSequenceAsString()));
680
681       sq.setDatasetSequence(dseqs[0]);
682     }
683     Alignment ds = new Alignment(dseqs);
684     if (av.hasHiddenColumns)
685     {
686       omitHidden = av.getColumnSelection().getVisibleSequenceStrings(0,
687               sq.getLength(), seqs);
688     }
689
690     String output = new FormatAdapter().formatSequences("Fasta", seqs,
691             omitHidden);
692
693     Toolkit.getDefaultToolkit().getSystemClipboard().setContents(
694             new StringSelection(output), Desktop.instance);
695
696     Vector hiddenColumns = null;
697     if (av.hasHiddenColumns)
698     {
699       hiddenColumns = new Vector();
700       for (int i = 0; i < av.getColumnSelection().getHiddenColumns().size(); i++)
701       {
702         int[] region = (int[]) av.getColumnSelection().getHiddenColumns()
703                 .elementAt(i);
704
705         hiddenColumns.addElement(new int[]
706         { region[0], region[1] });
707       }
708     }
709
710     Desktop.jalviewClipboard = new Object[]
711     { seqs, ds, // what is the dataset of a consensus sequence ? need to flag
712         // sequence as special.
713         hiddenColumns };
714   }
715
716   /**
717    * DOCUMENT ME!
718    * 
719    * @param g1
720    *          DOCUMENT ME!
721    */
722   public void paintComponent(Graphics g)
723   {
724
725     int width = getWidth();
726     if (width == 0)
727     {
728       width = ap.calculateIdWidth().width + 4;
729     }
730
731     Graphics2D g2 = (Graphics2D) g;
732     if (av.antiAlias)
733     {
734       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
735               RenderingHints.VALUE_ANTIALIAS_ON);
736     }
737
738     drawComponent(g2, width);
739
740   }
741
742   /**
743    * DOCUMENT ME!
744    * 
745    * @param g
746    *          DOCUMENT ME!
747    */
748   public void drawComponent(Graphics g, int width)
749   {
750     if (av.getFont().getSize() < 10)
751     {
752       g.setFont(font);
753     }
754     else
755     {
756       g.setFont(av.getFont());
757     }
758
759     FontMetrics fm = g.getFontMetrics(g.getFont());
760     g.setColor(Color.white);
761     g.fillRect(0, 0, getWidth(), getHeight());
762
763     g.translate(0, scrollOffset);
764     g.setColor(Color.black);
765
766     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
767     int fontHeight = g.getFont().getSize();
768     int y = 0;
769     int x = 0;
770     int graphExtras = 0;
771     int offset = 0;
772
773     if (aa != null)
774     {
775       hasHiddenRows = false;
776       for (int i = 0; i < aa.length; i++)
777       {
778         g.setColor(Color.black);
779
780         if (!aa[i].visible)
781         {
782           hasHiddenRows = true;
783           continue;
784         }
785
786         y += aa[i].height;
787
788         offset = -aa[i].height / 2;
789
790         if (aa[i].hasText)
791         {
792           offset += fm.getHeight() / 2;
793           offset -= fm.getDescent();
794         }
795         else
796           offset += fm.getDescent();
797
798         x = width - fm.stringWidth(aa[i].label) - 3;
799
800         if (aa[i].graphGroup > -1)
801         {
802           int groupSize = 0;
803           for (int gg = 0; gg < aa.length; gg++)
804           {
805             if (aa[gg].graphGroup == aa[i].graphGroup)
806             {
807               groupSize++;
808             }
809           }
810
811           if (groupSize * (fontHeight + 8) < aa[i].height)
812           {
813             graphExtras = (aa[i].height - (groupSize * (fontHeight + 8))) / 2;
814           }
815
816           for (int gg = 0; gg < aa.length; gg++)
817           {
818             if (aa[gg].graphGroup == aa[i].graphGroup)
819             {
820               x = width - fm.stringWidth(aa[gg].label) - 3;
821               g.drawString(aa[gg].label, x, y - graphExtras);
822               if (aa[gg].annotations[0] != null)
823               {
824                 g.setColor(aa[gg].annotations[0].colour);
825               }
826
827               g.drawLine(x, y - graphExtras - 3, x
828                       + fm.stringWidth(aa[gg].label), y - graphExtras - 3);
829
830               g.setColor(Color.black);
831               graphExtras += fontHeight + 8;
832             }
833           }
834         }
835         else
836         {
837           g.drawString(aa[i].label, x, y + offset);
838         }
839       }
840     }
841
842     if (resizePanel)
843     {
844       g.drawImage(image, 2, 0 - scrollOffset, this);
845     }
846     else if (dragEvent != null && aa != null)
847     {
848       g.setColor(Color.lightGray);
849       g.drawString(aa[selectedRow].label, dragEvent.getX(), dragEvent
850               .getY()
851               - scrollOffset);
852     }
853
854     if ((aa == null) || (aa.length < 1))
855     {
856       g.drawString("Right click", 2, 8);
857       g.drawString("to add annotation", 2, 18);
858     }
859   }
860 }