bf2f152dbd64f83626ba4ff8b5707a6fd223e6bf
[jalview.git] / src / jalview / gui / AnnotationLabels.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.5)
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.paintAlignment(false);
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.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()
551                         : ap.av.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", aa[selectedRow].groupRef
575                           .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 cprof = new JCheckBoxMenuItem(
593                   "Show Group Logo", aa[selectedRow].groupRef
594                           .isShowSequenceLogo());
595           cprof.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.setIncludeAllConsSymbols(cprof.getState());
606               ap.repaint();
607               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
608             }
609           });
610           pop.add(cprof);
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().setContents(
691             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, x
825                       + 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(), dragEvent
847               .getY()
848               - scrollOffset);
849     }
850
851     if ((aa == null) || (aa.length < 1))
852     {
853       g.drawString("Right click", 2, 8);
854       g.drawString("to add annotation", 2, 18);
855     }
856   }
857 }