clean code
[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     if (SwingUtilities.isLeftMouseButton(evt))
443     {
444       if (selectedRow > -1 && selectedRow < aa.length)
445       {
446         if (aa[selectedRow].groupRef != null)
447         {
448           if (evt.getClickCount() >= 2)
449           {
450             // todo: make the ap scroll to the selection - not necessary, first click highlights/scrolls, second selects
451             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
452             ap.av.setSelectionGroup(// new SequenceGroup(
453             aa[selectedRow].groupRef); // );
454             ap.paintAlignment(false);
455             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
456             ap.av.sendSelection();
457           }
458           else
459           {
460             ap.seqPanel.ap.idPanel
461                     .highlightSearchResults(aa[selectedRow].groupRef
462                             .getSequences(null));
463           }
464           return;
465         }
466         else if (aa[selectedRow].sequenceRef != null)
467         {
468           Vector sr = new Vector();
469           sr.addElement(aa[selectedRow].sequenceRef);
470           if (evt.getClickCount() == 1)
471           {
472             ap.seqPanel.ap.idPanel.highlightSearchResults(sr);
473           }
474           else if (evt.getClickCount() >= 2)
475           {
476             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
477             SequenceGroup sg = new SequenceGroup();
478             sg.addSequence(aa[selectedRow].sequenceRef, false);
479             ap.av.setSelectionGroup(sg);
480             ap.av.sendSelection();
481             ap.paintAlignment(false);
482             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
483           }
484
485         }
486       }
487     }
488     if (!SwingUtilities.isRightMouseButton(evt))
489     {
490       return;
491     }
492
493     JPopupMenu pop = new JPopupMenu("Annotations");
494     JMenuItem item = new JMenuItem(ADDNEW);
495     item.addActionListener(this);
496     pop.add(item);
497     if (selectedRow < 0)
498     {
499       if (hasHiddenRows)
500       { // let the user make everything visible again
501         item = new JMenuItem(SHOWALL);
502         item.addActionListener(this);
503         pop.add(item);
504       }
505       pop.show(this, evt.getX(), evt.getY());
506       return;
507     }
508     item = new JMenuItem(EDITNAME);
509     item.addActionListener(this);
510     pop.add(item);
511     item = new JMenuItem(HIDE);
512     item.addActionListener(this);
513     pop.add(item);
514     item = new JMenuItem(DELETE);
515     item.addActionListener(this);
516     pop.add(item);
517     if (hasHiddenRows)
518     {
519       item = new JMenuItem(SHOWALL);
520       item.addActionListener(this);
521       pop.add(item);
522     }
523     item = new JMenuItem(OUTPUT_TEXT);
524     item.addActionListener(this);
525     pop.add(item);
526     // TODO: annotation object should be typed for autocalculated/derived
527     // property methods
528     if (selectedRow < aa.length)
529     {
530       if (!aa[selectedRow].autoCalculated)
531       {
532         if (aa[selectedRow].graph == AlignmentAnnotation.NO_GRAPH)
533         {
534           // display formatting settings for this row.
535           pop.addSeparator();
536           // av and sequencegroup need to implement same interface for
537           item = new JCheckBoxMenuItem(TOGGLE_LABELSCALE,
538                   aa[selectedRow].scaleColLabel);
539           item.addActionListener(this);
540           pop.add(item);
541         }
542       }
543       else if (aa[selectedRow].label.indexOf("Consensus") > -1)
544       {
545         pop.addSeparator();
546         // av and sequencegroup need to implement same interface for
547         final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
548                 "Ignore Gaps In Consensus",
549                 (aa[selectedRow].groupRef != null) ? aa[selectedRow].groupRef
550                         .getIgnoreGapsConsensus() : ap.av
551                         .getIgnoreGapsConsensus());
552         final AlignmentAnnotation aaa = aa[selectedRow];
553         cbmi.addActionListener(new ActionListener()
554         {
555           public void actionPerformed(ActionEvent e)
556           {
557             if (aaa.groupRef != null)
558             {
559               // TODO: pass on reference to ap so the view can be updated.
560               aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState());
561               ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
562             }
563             else
564             {
565               ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
566             }
567           }
568         });
569         pop.add(cbmi);
570         // av and sequencegroup need to implement same interface for
571         if (aaa.groupRef != null)
572         {
573           final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
574                   "Show Group Histogram",
575                   aa[selectedRow].groupRef.isShowConsensusHistogram());
576           chist.addActionListener(new ActionListener()
577           {
578             public void actionPerformed(ActionEvent e)
579             {
580               // TODO: pass on reference
581               // to ap
582               // so the
583               // view
584               // can be
585               // updated.
586               aaa.groupRef.setShowConsensusHistogram(chist.getState());
587               ap.repaint();
588               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
589             }
590           });
591           pop.add(chist);
592           final JCheckBoxMenuItem cprofl = new JCheckBoxMenuItem(
593                   "Show Group Logo",
594                   aa[selectedRow].groupRef.isShowSequenceLogo());
595           cprofl.addActionListener(new ActionListener()
596           {
597             public void actionPerformed(ActionEvent e)
598             {
599               // TODO: pass on reference
600               // to ap
601               // so the
602               // view
603               // can be
604               // updated.
605               aaa.groupRef.setshowSequenceLogo(cprofl.getState());
606               ap.repaint();
607               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
608             }
609           });
610           pop.add(cprofl);
611         }
612         else
613         {
614           final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
615                   "Show Histogram", av.isShowConsensusHistogram());
616           chist.addActionListener(new ActionListener()
617           {
618             public void actionPerformed(ActionEvent e)
619             {
620               // TODO: pass on reference
621               // to ap
622               // so the
623               // view
624               // can be
625               // updated.
626               av.setShowConsensusHistogram(chist.getState());
627               ap.repaint();
628               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
629             }
630           });
631           pop.add(chist);
632           final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
633                   "Show Logo", av.isShowSequenceLogo());
634           cprof.addActionListener(new ActionListener()
635           {
636             public void actionPerformed(ActionEvent e)
637             {
638               // TODO: pass on reference
639               // to ap
640               // so the
641               // view
642               // can be
643               // updated.
644               av.setShowSequenceLogo(cprof.getState());
645               ap.repaint();
646               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
647             }
648           });
649           pop.add(cprof);
650         }
651         final JMenuItem consclipbrd = new JMenuItem(COPYCONS_SEQ);
652         consclipbrd.addActionListener(this);
653         pop.add(consclipbrd);
654       }
655     }
656     pop.show(this, evt.getX(), evt.getY());
657   }
658
659   /**
660    * do a single sequence copy to jalview and the system clipboard
661    * 
662    * @param sq
663    *          sequence to be copied to clipboard
664    */
665   protected void copy_annotseqtoclipboard(SequenceI sq)
666   {
667     SequenceI[] seqs = new SequenceI[]
668     { sq };
669     String[] omitHidden = null;
670     SequenceI[] dseqs = new SequenceI[]
671     { sq.getDatasetSequence() };
672     if (dseqs[0] == null)
673     {
674       dseqs[0] = new Sequence(sq);
675       dseqs[0].setSequence(jalview.analysis.AlignSeq.extractGaps(
676               jalview.util.Comparison.GapChars, sq.getSequenceAsString()));
677
678       sq.setDatasetSequence(dseqs[0]);
679     }
680     Alignment ds = new Alignment(dseqs);
681     if (av.hasHiddenColumns)
682     {
683       omitHidden = av.getColumnSelection().getVisibleSequenceStrings(0,
684               sq.getLength(), seqs);
685     }
686
687     String output = new FormatAdapter().formatSequences("Fasta", seqs,
688             omitHidden);
689
690     Toolkit.getDefaultToolkit().getSystemClipboard()
691             .setContents(new StringSelection(output), Desktop.instance);
692
693     Vector hiddenColumns = null;
694     if (av.hasHiddenColumns)
695     {
696       hiddenColumns = new Vector();
697       for (int i = 0; i < av.getColumnSelection().getHiddenColumns().size(); i++)
698       {
699         int[] region = (int[]) av.getColumnSelection().getHiddenColumns()
700                 .elementAt(i);
701
702         hiddenColumns.addElement(new int[]
703         { region[0], region[1] });
704       }
705     }
706
707     Desktop.jalviewClipboard = new Object[]
708     { seqs, ds, // what is the dataset of a consensus sequence ? need to flag
709         // sequence as special.
710         hiddenColumns };
711   }
712
713   /**
714    * DOCUMENT ME!
715    * 
716    * @param g1
717    *          DOCUMENT ME!
718    */
719   public void paintComponent(Graphics g)
720   {
721
722     int width = getWidth();
723     if (width == 0)
724     {
725       width = ap.calculateIdWidth().width + 4;
726     }
727
728     Graphics2D g2 = (Graphics2D) g;
729     if (av.antiAlias)
730     {
731       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
732               RenderingHints.VALUE_ANTIALIAS_ON);
733     }
734
735     drawComponent(g2, width);
736
737   }
738
739   /**
740    * DOCUMENT ME!
741    * 
742    * @param g
743    *          DOCUMENT ME!
744    */
745   public void drawComponent(Graphics g, int width)
746   {
747     if (av.getFont().getSize() < 10)
748     {
749       g.setFont(font);
750     }
751     else
752     {
753       g.setFont(av.getFont());
754     }
755
756     FontMetrics fm = g.getFontMetrics(g.getFont());
757     g.setColor(Color.white);
758     g.fillRect(0, 0, getWidth(), getHeight());
759
760     g.translate(0, scrollOffset);
761     g.setColor(Color.black);
762
763     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
764     int fontHeight = g.getFont().getSize();
765     int y = 0;
766     int x = 0;
767     int graphExtras = 0;
768     int offset = 0;
769
770     if (aa != null)
771     {
772       hasHiddenRows = false;
773       for (int i = 0; i < aa.length; i++)
774       {
775         g.setColor(Color.black);
776
777         if (!aa[i].visible)
778         {
779           hasHiddenRows = true;
780           continue;
781         }
782
783         y += aa[i].height;
784
785         offset = -aa[i].height / 2;
786
787         if (aa[i].hasText)
788         {
789           offset += fm.getHeight() / 2;
790           offset -= fm.getDescent();
791         }
792         else
793           offset += fm.getDescent();
794
795         x = width - fm.stringWidth(aa[i].label) - 3;
796
797         if (aa[i].graphGroup > -1)
798         {
799           int groupSize = 0;
800           for (int gg = 0; gg < aa.length; gg++)
801           {
802             if (aa[gg].graphGroup == aa[i].graphGroup)
803             {
804               groupSize++;
805             }
806           }
807
808           if (groupSize * (fontHeight + 8) < aa[i].height)
809           {
810             graphExtras = (aa[i].height - (groupSize * (fontHeight + 8))) / 2;
811           }
812
813           for (int gg = 0; gg < aa.length; gg++)
814           {
815             if (aa[gg].graphGroup == aa[i].graphGroup)
816             {
817               x = width - fm.stringWidth(aa[gg].label) - 3;
818               g.drawString(aa[gg].label, x, y - graphExtras);
819               if (aa[gg].annotations[0] != null)
820               {
821                 g.setColor(aa[gg].annotations[0].colour);
822               }
823
824               g.drawLine(x, y - graphExtras - 3,
825                       x + fm.stringWidth(aa[gg].label), y - graphExtras - 3);
826
827               g.setColor(Color.black);
828               graphExtras += fontHeight + 8;
829             }
830           }
831         }
832         else
833         {
834           g.drawString(aa[i].label, x, y + offset);
835         }
836       }
837     }
838
839     if (resizePanel)
840     {
841       g.drawImage(image, 2, 0 - scrollOffset, this);
842     }
843     else if (dragEvent != null && aa != null)
844     {
845       g.setColor(Color.lightGray);
846       g.drawString(aa[selectedRow].label, dragEvent.getX(),
847               dragEvent.getY() - scrollOffset);
848     }
849
850     if ((aa == null) || (aa.length < 1))
851     {
852       g.drawString("Right click", 2, 8);
853       g.drawString("to add annotation", 2, 18);
854     }
855   }
856 }