Further structure accessing feature
[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),
108             temp.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     else if (evt.getActionCommand().equals(DELETE))
202     {
203       ap.av.alignment.deleteAnnotation(aa[selectedRow]);
204     }
205     else if (evt.getActionCommand().equals(SHOWALL))
206     {
207       for (int i = 0; i < aa.length; i++)
208       {
209         if (!aa[i].visible && aa[i].annotations != null)
210         {
211           aa[i].visible = true;
212         }
213       }
214     }
215     else if (evt.getActionCommand().equals(OUTPUT_TEXT))
216     {
217       new AnnotationExporter().exportAnnotations(ap,
218               new AlignmentAnnotation[]
219               { aa[selectedRow] }, null, null);
220     }
221     else if (evt.getActionCommand().equals(COPYCONS_SEQ))
222     {
223       SequenceI cons = null;
224       if (aa[selectedRow].groupRef != null)
225       {
226         cons = aa[selectedRow].groupRef.getConsensusSeq();
227       }
228       else
229       {
230         cons = av.getConsensusSeq();
231       }
232       if (cons != null)
233       {
234         copy_annotseqtoclipboard(cons);
235       }
236
237     }
238     else if (evt.getActionCommand().equals(TOGGLE_LABELSCALE))
239     {
240       aa[selectedRow].scaleColLabel = !aa[selectedRow].scaleColLabel;
241     }
242
243     ap.validateAnnotationDimensions(false);
244     ap.addNotify();
245     ap.repaint();
246     //validate();
247     //ap.paintAlignment(true);
248   }
249
250   /**
251    * DOCUMENT ME!
252    * 
253    * @param e
254    *          DOCUMENT ME!
255    */
256   boolean editLabelDescription(AlignmentAnnotation annotation)
257   {
258     EditNameDialog dialog = new EditNameDialog(annotation.label,
259             annotation.description, "       Annotation Name ",
260             "Annotation Description ", "Edit Annotation Name/Description",
261             ap.alignFrame);
262
263     if (!dialog.accept)
264     {
265       return false;
266     }
267
268     annotation.label = dialog.getName();
269
270     String text = dialog.getDescription();
271     if (text != null && text.length() == 0)
272     {
273       text = null;
274     }
275     annotation.description = text;
276
277     return true;
278   }
279
280   /**
281    * DOCUMENT ME!
282    * 
283    * @param evt
284    *          DOCUMENT ME!
285    */
286   public void mousePressed(MouseEvent evt)
287   {
288     getSelectedRow(evt.getY() - scrollOffset);
289     oldY = evt.getY();
290   }
291
292   /**
293    * DOCUMENT ME!
294    * 
295    * @param evt
296    *          DOCUMENT ME!
297    */
298   public void mouseReleased(MouseEvent evt)
299   {
300     int start = selectedRow;
301     getSelectedRow(evt.getY() - scrollOffset);
302     int end = selectedRow;
303
304     if (start != end)
305     {
306       // Swap these annotations
307       AlignmentAnnotation startAA = ap.av.alignment
308               .getAlignmentAnnotation()[start];
309       if (end == -1)
310       {
311         end = ap.av.alignment.getAlignmentAnnotation().length - 1;
312       }
313       AlignmentAnnotation endAA = ap.av.alignment.getAlignmentAnnotation()[end];
314
315       ap.av.alignment.getAlignmentAnnotation()[end] = startAA;
316       ap.av.alignment.getAlignmentAnnotation()[start] = endAA;
317     }
318
319     resizePanel = false;
320     dragEvent = null;
321     repaint();
322     ap.annotationPanel.repaint();
323   }
324
325   /**
326    * DOCUMENT ME!
327    * 
328    * @param evt
329    *          DOCUMENT ME!
330    */
331   public void mouseEntered(MouseEvent evt)
332   {
333     if (evt.getY() < 10)
334     {
335       resizePanel = true;
336       repaint();
337     }
338   }
339
340   /**
341    * DOCUMENT ME!
342    * 
343    * @param evt
344    *          DOCUMENT ME!
345    */
346   public void mouseExited(MouseEvent evt)
347   {
348     if (dragEvent == null)
349     {
350       resizePanel = false;
351       repaint();
352     }
353   }
354
355   /**
356    * DOCUMENT ME!
357    * 
358    * @param evt
359    *          DOCUMENT ME!
360    */
361   public void mouseDragged(MouseEvent evt)
362   {
363     dragEvent = evt;
364
365     if (resizePanel)
366     {
367       Dimension d = ap.annotationScroller.getPreferredSize();
368       int dif = evt.getY() - oldY;
369
370       dif /= ap.av.charHeight;
371       dif *= ap.av.charHeight;
372
373       if ((d.height - dif) > 20)
374       {
375         ap.annotationScroller.setPreferredSize(new Dimension(d.width,
376                 d.height - dif));
377         d = ap.annotationSpaceFillerHolder.getPreferredSize();
378         ap.annotationSpaceFillerHolder.setPreferredSize(new Dimension(
379                 d.width, d.height - dif));
380         ap.paintAlignment(true);
381       }
382
383       ap.addNotify();
384     }
385     else
386     {
387       repaint();
388     }
389   }
390
391   /**
392    * DOCUMENT ME!
393    * 
394    * @param evt
395    *          DOCUMENT ME!
396    */
397   public void mouseMoved(MouseEvent evt)
398   {
399     resizePanel = evt.getY() < 10;
400
401     getSelectedRow(evt.getY() - scrollOffset);
402
403     if (selectedRow > -1
404             && ap.av.alignment.getAlignmentAnnotation().length > selectedRow)
405     {
406       AlignmentAnnotation aa = ap.av.alignment.getAlignmentAnnotation()[selectedRow];
407
408       StringBuffer desc = new StringBuffer("<html>");
409
410       if (aa.description != null
411               && !aa.description.equals("New description"))
412       {
413         desc.append(aa.getDescription(true));
414         if (aa.hasScore)
415           desc.append("<br>");
416       }
417       if (aa.hasScore())
418       {
419         desc.append("Score: " + aa.score);
420       }
421
422       if (desc.length() != 6)
423       {
424         desc.append("</html>");
425         this.setToolTipText(desc.toString());
426       }
427       else
428         this.setToolTipText(null);
429     }
430
431   }
432
433   /**
434    * DOCUMENT ME!
435    * 
436    * @param evt
437    *          DOCUMENT ME!
438    */
439   public void mouseClicked(MouseEvent evt)
440   {
441     AlignmentAnnotation[] aa = ap.av.alignment.getAlignmentAnnotation();
442         
443     if (SwingUtilities.isLeftMouseButton(evt))
444     {
445       if (selectedRow > -1 && selectedRow < aa.length)
446       {
447         if (aa[selectedRow].groupRef != null)
448         {
449           if (evt.getClickCount() >= 2)
450           {
451             // todo: make the ap scroll to the selection - not necessary, first click highlights/scrolls, second selects
452             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
453             ap.av.setSelectionGroup(// new SequenceGroup(
454             aa[selectedRow].groupRef); // );
455             ap.paintAlignment(false);
456             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
457             ap.av.sendSelection();
458           }
459           else
460           {
461             ap.seqPanel.ap.idPanel
462                     .highlightSearchResults(aa[selectedRow].groupRef
463                             .getSequences(null));
464           }
465           return;
466         }
467         else if (aa[selectedRow].sequenceRef != null)
468         {
469           Vector sr = new Vector();
470           sr.addElement(aa[selectedRow].sequenceRef);
471           if (evt.getClickCount() == 1)
472           {
473             ap.seqPanel.ap.idPanel.highlightSearchResults(sr);
474           }
475           else if (evt.getClickCount() >= 2)
476           {
477             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
478             SequenceGroup sg = new SequenceGroup();
479             sg.addSequence(aa[selectedRow].sequenceRef, false);
480             ap.av.setSelectionGroup(sg);
481             ap.av.sendSelection();
482             ap.paintAlignment(false);
483             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
484           }
485
486         }
487       }
488     }
489     if (!SwingUtilities.isRightMouseButton(evt))
490     {
491       return;
492     }
493
494     JPopupMenu pop = new JPopupMenu("Annotations");
495     JMenuItem item = new JMenuItem(ADDNEW);
496     item.addActionListener(this);
497     pop.add(item);
498         
499     if (selectedRow < 0)
500     {
501       if (hasHiddenRows)
502       { // let the user make everything visible again
503         item = new JMenuItem(SHOWALL);
504         item.addActionListener(this);
505         pop.add(item);
506       }
507       pop.show(this, evt.getX(), evt.getY());
508       return;
509     }
510     item = new JMenuItem(EDITNAME);
511     item.addActionListener(this);
512     pop.add(item);
513     item = new JMenuItem(HIDE);
514     item.addActionListener(this);
515     pop.add(item);
516     item = new JMenuItem(DELETE);
517     item.addActionListener(this);
518     pop.add(item);
519     if (hasHiddenRows)
520     {
521       item = new JMenuItem(SHOWALL);
522       item.addActionListener(this);
523       pop.add(item);
524     }
525     item = new JMenuItem(OUTPUT_TEXT);
526     item.addActionListener(this);
527     pop.add(item);
528     // TODO: annotation object should be typed for autocalculated/derived
529     // property methods
530     if (selectedRow < aa.length)
531     {
532       if (!aa[selectedRow].autoCalculated)
533       {
534         if (aa[selectedRow].graph == AlignmentAnnotation.NO_GRAPH)
535         {
536           // display formatting settings for this row.
537           pop.addSeparator();
538           // av and sequencegroup need to implement same interface for
539           item = new JCheckBoxMenuItem(TOGGLE_LABELSCALE,
540                   aa[selectedRow].scaleColLabel);
541           item.addActionListener(this);
542           pop.add(item);
543         }
544       }
545       else if (aa[selectedRow].label.indexOf("Consensus") > -1)
546       {
547         pop.addSeparator();
548         // av and sequencegroup need to implement same interface for
549         final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
550                 "Ignore Gaps In Consensus",
551                 (aa[selectedRow].groupRef != null) ? aa[selectedRow].groupRef
552                         .getIgnoreGapsConsensus() : ap.av
553                         .getIgnoreGapsConsensus());
554         final AlignmentAnnotation aaa = aa[selectedRow];
555         cbmi.addActionListener(new ActionListener()
556         {
557           public void actionPerformed(ActionEvent e)
558           {
559             if (aaa.groupRef != null)
560             {
561               // TODO: pass on reference to ap so the view can be updated.
562               aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState());
563               ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
564             }
565             else
566             {
567               ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
568             }
569           }
570         });
571         pop.add(cbmi);
572         // av and sequencegroup need to implement same interface for
573         if (aaa.groupRef != null)
574         {
575           final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
576                   "Show Group Histogram",
577                   aa[selectedRow].groupRef.isShowConsensusHistogram());
578           chist.addActionListener(new ActionListener()
579           {
580             public void actionPerformed(ActionEvent e)
581             {
582               // TODO: pass on reference
583               // to ap
584               // so the
585               // view
586               // can be
587               // updated.
588               aaa.groupRef.setShowConsensusHistogram(chist.getState());
589               ap.repaint();
590               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
591             }
592           });
593           pop.add(chist);
594           final JCheckBoxMenuItem cprofl = new JCheckBoxMenuItem(
595                   "Show Group Logo",
596                   aa[selectedRow].groupRef.isShowSequenceLogo());
597           cprofl.addActionListener(new ActionListener()
598           {
599             public void actionPerformed(ActionEvent e)
600             {
601               // TODO: pass on reference
602               // to ap
603               // so the
604               // view
605               // can be
606               // updated.
607               aaa.groupRef.setshowSequenceLogo(cprofl.getState());
608               ap.repaint();
609               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
610             }
611           });
612           pop.add(cprofl);
613         }
614         else
615         {
616           final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
617                   "Show Histogram", av.isShowConsensusHistogram());
618           chist.addActionListener(new ActionListener()
619           {
620             public void actionPerformed(ActionEvent e)
621             {
622               // TODO: pass on reference
623               // to ap
624               // so the
625               // view
626               // can be
627               // updated.
628               av.setShowConsensusHistogram(chist.getState());
629               ap.repaint();
630               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
631             }
632           });
633           pop.add(chist);
634           final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
635                   "Show Logo", av.isShowSequenceLogo());
636           cprof.addActionListener(new ActionListener()
637           {
638             public void actionPerformed(ActionEvent e)
639             {
640               // TODO: pass on reference
641               // to ap
642               // so the
643               // view
644               // can be
645               // updated.
646               av.setShowSequenceLogo(cprof.getState());
647               ap.repaint();
648               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
649             }
650           });
651           pop.add(cprof);
652         }
653         final JMenuItem consclipbrd = new JMenuItem(COPYCONS_SEQ);
654         consclipbrd.addActionListener(this);
655         pop.add(consclipbrd);
656       }
657     }
658     pop.show(this, evt.getX(), evt.getY());
659   }
660
661   /**
662    * do a single sequence copy to jalview and the system clipboard
663    * 
664    * @param sq
665    *          sequence to be copied to clipboard
666    */
667   protected void copy_annotseqtoclipboard(SequenceI sq)
668   {
669     SequenceI[] seqs = new SequenceI[]
670     { sq };
671     String[] omitHidden = null;
672     SequenceI[] dseqs = new SequenceI[]
673     { sq.getDatasetSequence() };
674     if (dseqs[0] == null)
675     {
676       dseqs[0] = new Sequence(sq);
677       dseqs[0].setSequence(jalview.analysis.AlignSeq.extractGaps(
678               jalview.util.Comparison.GapChars, sq.getSequenceAsString()));
679
680       sq.setDatasetSequence(dseqs[0]);
681     }
682     Alignment ds = new Alignment(dseqs);
683     if (av.hasHiddenColumns)
684     {
685       omitHidden = av.getColumnSelection().getVisibleSequenceStrings(0,
686               sq.getLength(), seqs);
687     }
688
689     String output = new FormatAdapter().formatSequences("Fasta", seqs,
690             omitHidden);
691
692     Toolkit.getDefaultToolkit().getSystemClipboard()
693             .setContents(new StringSelection(output), Desktop.instance);
694
695     Vector hiddenColumns = null;
696     if (av.hasHiddenColumns)
697     {
698       hiddenColumns = new Vector();
699       for (int i = 0; i < av.getColumnSelection().getHiddenColumns().size(); i++)
700       {
701         int[] region = (int[]) av.getColumnSelection().getHiddenColumns()
702                 .elementAt(i);
703
704         hiddenColumns.addElement(new int[]
705         { region[0], region[1] });
706       }
707     }
708
709     Desktop.jalviewClipboard = new Object[]
710     { seqs, ds, // what is the dataset of a consensus sequence ? need to flag
711         // sequence as special.
712         hiddenColumns };
713   }
714
715   /**
716    * DOCUMENT ME!
717    * 
718    * @param g1
719    *          DOCUMENT ME!
720    */
721   public void paintComponent(Graphics g)
722   {
723
724     int width = getWidth();
725     if (width == 0)
726     {
727       width = ap.calculateIdWidth().width + 4;
728     }
729
730     Graphics2D g2 = (Graphics2D) g;
731     if (av.antiAlias)
732     {
733       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
734               RenderingHints.VALUE_ANTIALIAS_ON);
735     }
736
737     drawComponent(g2, width);
738
739   }
740
741   /**
742    * DOCUMENT ME!
743    * 
744    * @param g
745    *          DOCUMENT ME!
746    */
747   public void drawComponent(Graphics g, int width)
748   {
749     if (av.getFont().getSize() < 10)
750     {
751       g.setFont(font);
752     }
753     else
754     {
755       g.setFont(av.getFont());
756     }
757
758     FontMetrics fm = g.getFontMetrics(g.getFont());
759     g.setColor(Color.white);
760     g.fillRect(0, 0, getWidth(), getHeight());
761
762     g.translate(0, scrollOffset);
763     g.setColor(Color.black);
764
765     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
766     int fontHeight = g.getFont().getSize();
767     int y = 0;
768     int x = 0;
769     int graphExtras = 0;
770     int offset = 0;
771
772     if (aa != null)
773     {
774       hasHiddenRows = false;
775       for (int i = 0; i < aa.length; i++)
776       {
777         g.setColor(Color.black);
778
779         if (!aa[i].visible)
780         {
781           hasHiddenRows = true;
782           continue;
783         }
784
785         y += aa[i].height;
786
787         offset = -aa[i].height / 2;
788
789         if (aa[i].hasText)
790         {
791           offset += fm.getHeight() / 2;
792           offset -= fm.getDescent();
793         }
794         else
795           offset += fm.getDescent();
796
797         x = width - fm.stringWidth(aa[i].label) - 3;
798
799         if (aa[i].graphGroup > -1)
800         {
801           int groupSize = 0;
802           for (int gg = 0; gg < aa.length; gg++)
803           {
804             if (aa[gg].graphGroup == aa[i].graphGroup)
805             {
806               groupSize++;
807             }
808           }
809
810           if (groupSize * (fontHeight + 8) < aa[i].height)
811           {
812             graphExtras = (aa[i].height - (groupSize * (fontHeight + 8))) / 2;
813           }
814
815           for (int gg = 0; gg < aa.length; gg++)
816           {
817             if (aa[gg].graphGroup == aa[i].graphGroup)
818             {
819               x = width - fm.stringWidth(aa[gg].label) - 3;
820               g.drawString(aa[gg].label, x, y - graphExtras);
821               if (aa[gg].annotations[0] != null)
822               {
823                 g.setColor(aa[gg].annotations[0].colour);
824               }
825
826               g.drawLine(x, y - graphExtras - 3,
827                       x + fm.stringWidth(aa[gg].label), y - graphExtras - 3);
828
829               g.setColor(Color.black);
830               graphExtras += fontHeight + 8;
831             }
832           }
833         }
834         else
835         {
836           g.drawString(aa[i].label, x, y + offset);
837         }
838       }
839     }
840
841     if (resizePanel)
842     {
843       g.drawImage(image, 2, 0 - scrollOffset, this);
844     }
845     else if (dragEvent != null && aa != null)
846     {
847       g.setColor(Color.lightGray);
848       g.drawString(aa[selectedRow].label, dragEvent.getX(),
849               dragEvent.getY() - scrollOffset);
850     }
851
852     if ((aa == null) || (aa.length < 1))
853     {
854       g.drawString("Right click", 2, 8);
855       g.drawString("to add annotation", 2, 18);
856     }
857   }
858 }