JAL-1432 updated copyright notices
[jalview.git] / src / jalview / gui / AnnotationPanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.0b1)
3  * Copyright (C) 2014 The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3 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  * The Jalview Authors are detailed in the 'AUTHORS' file.
18  */
19 package jalview.gui;
20
21 import java.awt.*;
22 import java.awt.event.*;
23 import java.awt.image.*;
24 import javax.swing.*;
25
26 import jalview.datamodel.*;
27 import jalview.renderer.AnnotationRenderer;
28 import jalview.renderer.AwtRenderPanelI;
29
30 /**
31  * AnnotationPanel displays visible portion of annotation rows below unwrapped
32  * alignment
33  * 
34  * @author $author$
35  * @version $Revision$
36  */
37 public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
38         MouseListener, MouseWheelListener, MouseMotionListener,
39         ActionListener, AdjustmentListener, Scrollable
40 {
41   final String HELIX = "Helix";
42
43   final String SHEET = "Sheet";
44
45   /**
46    * For RNA secondary structure "stems" aka helices
47    */
48   final String STEM = "RNA Helix";
49
50   final String LABEL = "Label";
51
52   final String REMOVE = "Remove Annotation";
53
54   final String COLOUR = "Colour";
55
56   public final Color HELIX_COLOUR = Color.red.darker();
57
58   public final Color SHEET_COLOUR = Color.green.darker().darker();
59
60   public final Color STEM_COLOUR = Color.blue.darker();
61
62   /** DOCUMENT ME!! */
63   public AlignViewport av;
64
65   AlignmentPanel ap;
66
67   public int activeRow = -1;
68
69   public BufferedImage image;
70
71   public volatile BufferedImage fadedImage;
72
73   Graphics2D gg;
74
75   public FontMetrics fm;
76
77   public int imgWidth = 0;
78
79   boolean fastPaint = false;
80
81   // Used For mouse Dragging and resizing graphs
82   int graphStretch = -1;
83
84   int graphStretchY = -1;
85
86   int min; // used by mouseDragged to see if user
87
88   int max; // used by mouseDragged to see if user
89
90   boolean mouseDragging = false;
91
92   boolean MAC = false;
93
94   // for editing cursor
95   int cursorX = 0;
96
97   int cursorY = 0;
98
99   public final AnnotationRenderer renderer;
100
101   private MouseWheelListener[] _mwl;
102
103   /**
104    * Creates a new AnnotationPanel object.
105    * 
106    * @param ap
107    *          DOCUMENT ME!
108    */
109   public AnnotationPanel(AlignmentPanel ap)
110   {
111
112     MAC = new jalview.util.Platform().isAMac();
113
114     ToolTipManager.sharedInstance().registerComponent(this);
115     ToolTipManager.sharedInstance().setInitialDelay(0);
116     ToolTipManager.sharedInstance().setDismissDelay(10000);
117     this.ap = ap;
118     av = ap.av;
119     this.setLayout(null);
120     addMouseListener(this);
121     addMouseMotionListener(this);
122     ap.annotationScroller.getVerticalScrollBar()
123             .addAdjustmentListener(this);
124     // save any wheel listeners on the scroller, so we can propagate scroll
125     // events to them.
126     _mwl = ap.annotationScroller.getMouseWheelListeners();
127     // and then set our own listener to consume all mousewheel events
128     ap.annotationScroller.addMouseWheelListener(this);
129     renderer = new AnnotationRenderer();
130   }
131
132   public AnnotationPanel(AlignViewport av)
133   {
134     this.av = av;
135     renderer = new AnnotationRenderer();
136   }
137
138   @Override
139   public void mouseWheelMoved(MouseWheelEvent e)
140   {
141     if (e.isShiftDown())
142     {
143       e.consume();
144       if (e.getWheelRotation() > 0)
145       {
146         ap.scrollRight(true);
147       }
148       else
149       {
150         ap.scrollRight(false);
151       }
152     }
153     else
154     {
155       // TODO: find the correct way to let the event bubble up to
156       // ap.annotationScroller
157       for (MouseWheelListener mwl : _mwl)
158       {
159         if (mwl != null)
160         {
161           mwl.mouseWheelMoved(e);
162         }
163         if (e.isConsumed())
164         {
165           break;
166         }
167       }
168     }
169   }
170
171   @Override
172   public Dimension getPreferredScrollableViewportSize()
173   {
174     return getPreferredSize();
175   }
176
177   @Override
178   public int getScrollableBlockIncrement(Rectangle visibleRect,
179           int orientation, int direction)
180   {
181     return 30;
182   }
183
184   @Override
185   public boolean getScrollableTracksViewportHeight()
186   {
187     return false;
188   }
189
190   @Override
191   public boolean getScrollableTracksViewportWidth()
192   {
193     return true;
194   }
195
196   @Override
197   public int getScrollableUnitIncrement(Rectangle visibleRect,
198           int orientation, int direction)
199   {
200     return 30;
201   }
202
203   /*
204    * (non-Javadoc)
205    * 
206    * @see
207    * java.awt.event.AdjustmentListener#adjustmentValueChanged(java.awt.event
208    * .AdjustmentEvent)
209    */
210   @Override
211   public void adjustmentValueChanged(AdjustmentEvent evt)
212   {
213     // update annotation label display
214     ap.alabels.setScrollOffset(-evt.getValue());
215   }
216
217   /**
218    * Calculates the height of the annotation displayed in the annotation panel.
219    * Callers should normally call the ap.adjustAnnotationHeight method to ensure
220    * all annotation associated components are updated correctly.
221    * 
222    */
223   public int adjustPanelHeight()
224   {
225     int height = av.calcPanelHeight();
226     this.setPreferredSize(new Dimension(1, height));
227     if (ap != null)
228     {
229       // revalidate only when the alignment panel is fully constructed
230       ap.validate();
231     }
232
233     return height;
234   }
235
236   /**
237    * DOCUMENT ME!
238    * 
239    * @param evt
240    *          DOCUMENT ME!
241    */
242   @Override
243   public void actionPerformed(ActionEvent evt)
244   {
245     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
246     if (aa == null)
247     {
248       return;
249     }
250     Annotation[] anot = aa[activeRow].annotations;
251
252     if (anot.length < av.getColumnSelection().getMax())
253     {
254       Annotation[] temp = new Annotation[av.getColumnSelection().getMax() + 2];
255       System.arraycopy(anot, 0, temp, 0, anot.length);
256       anot = temp;
257       aa[activeRow].annotations = anot;
258     }
259
260     if (evt.getActionCommand().equals(REMOVE))
261     {
262       for (int i = 0; i < av.getColumnSelection().size(); i++)
263       {
264         anot[av.getColumnSelection().columnAt(i)] = null;
265       }
266     }
267     else if (evt.getActionCommand().equals(LABEL))
268     {
269       String exMesg = collectAnnotVals(anot, av.getColumnSelection(), LABEL);
270       String label = JOptionPane.showInputDialog(this, "Enter label",
271               exMesg);
272
273       if (label == null)
274       {
275         return;
276       }
277
278       if ((label.length() > 0) && !aa[activeRow].hasText)
279       {
280         aa[activeRow].hasText = true;
281       }
282
283       for (int i = 0; i < av.getColumnSelection().size(); i++)
284       {
285         int index = av.getColumnSelection().columnAt(i);
286
287         if (!av.getColumnSelection().isVisible(index))
288           continue;
289
290         if (anot[index] == null)
291         {
292           anot[index] = new Annotation(label, "", ' ', 0); // TODO: verify that
293           // null exceptions
294           // aren't raised
295           // elsewhere.
296         }
297         else
298         {
299           anot[index].displayCharacter = label;
300         }
301       }
302     }
303     else if (evt.getActionCommand().equals(COLOUR))
304     {
305       Color col = JColorChooser.showDialog(this,
306               "Choose foreground colour", Color.black);
307
308       for (int i = 0; i < av.getColumnSelection().size(); i++)
309       {
310         int index = av.getColumnSelection().columnAt(i);
311
312         if (!av.getColumnSelection().isVisible(index))
313           continue;
314
315         if (anot[index] == null)
316         {
317           anot[index] = new Annotation("", "", ' ', 0);
318         }
319
320         anot[index].colour = col;
321       }
322     }
323     else
324     // HELIX OR SHEET
325     {
326       char type = 0;
327       String symbol = "\u03B1";
328
329       if (evt.getActionCommand().equals(HELIX))
330       {
331         type = 'H';
332       }
333       else if (evt.getActionCommand().equals(SHEET))
334       {
335         type = 'E';
336         symbol = "\u03B2";
337       }
338
339       // Added by LML to color stems
340       else if (evt.getActionCommand().equals(STEM))
341       {
342         type = 'S';
343         symbol = "\u03C3";
344       }
345
346       if (!aa[activeRow].hasIcons)
347       {
348         aa[activeRow].hasIcons = true;
349       }
350
351       String label = JOptionPane.showInputDialog(
352               "Enter a label for the structure?", symbol);
353
354       if (label == null)
355       {
356         return;
357       }
358
359       if ((label.length() > 0) && !aa[activeRow].hasText)
360       {
361         aa[activeRow].hasText = true;
362       }
363
364       for (int i = 0; i < av.getColumnSelection().size(); i++)
365       {
366         int index = av.getColumnSelection().columnAt(i);
367
368         if (!av.getColumnSelection().isVisible(index))
369           continue;
370
371         if (anot[index] == null)
372         {
373           anot[index] = new Annotation(label, "", type, 0);
374         }
375
376         anot[index].secondaryStructure = type;
377         anot[index].displayCharacter = label;
378       }
379     }
380     av.getAlignment().validateAnnotation(aa[activeRow]);
381     ap.alignmentChanged();
382     
383     adjustPanelHeight();
384     repaint();
385
386     return;
387   }
388
389   private String collectAnnotVals(Annotation[] anot,
390           ColumnSelection columnSelection, String label2)
391   {
392     String collatedInput = "";
393     String last = "";
394     ColumnSelection viscols = av.getColumnSelection();
395     // TODO: refactor and save av.getColumnSelection for efficiency
396     for (int i = 0; i < columnSelection.size(); i++)
397     {
398       int index = columnSelection.columnAt(i);
399       // always check for current display state - just in case
400       if (!viscols.isVisible(index))
401         continue;
402       String tlabel = null;
403       if (anot[index] != null)
404       { // LML added stem code
405         if (label2.equals(HELIX) || label2.equals(SHEET)
406                 || label2.equals(STEM) || label2.equals(LABEL))
407         {
408           tlabel = anot[index].description;
409           if (tlabel == null || tlabel.length() < 1)
410           {
411             if (label2.equals(HELIX) || label2.equals(SHEET)
412                     || label2.equals(STEM))
413             {
414               tlabel = "" + anot[index].secondaryStructure;
415             }
416             else
417             {
418               tlabel = "" + anot[index].displayCharacter;
419             }
420           }
421         }
422         if (tlabel != null && !tlabel.equals(last))
423         {
424           if (last.length() > 0)
425           {
426             collatedInput += " ";
427           }
428           collatedInput += tlabel;
429         }
430       }
431     }
432     return collatedInput;
433   }
434
435   /**
436    * DOCUMENT ME!
437    * 
438    * @param evt
439    *          DOCUMENT ME!
440    */
441   @Override
442   public void mousePressed(MouseEvent evt)
443   {
444
445     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
446     if (aa == null)
447     {
448       return;
449     }
450
451     int height = 0;
452     activeRow = -1;
453
454     for (int i = 0; i < aa.length; i++)
455     {
456       if (aa[i].visible)
457       {
458         height += aa[i].height;
459       }
460
461       if (evt.getY() < height)
462       {
463         if (aa[i].editable)
464         {
465           activeRow = i;
466         }
467         else if (aa[i].graph > 0)
468         {
469           // Stretch Graph
470           graphStretch = i;
471           graphStretchY = evt.getY();
472         }
473
474         break;
475       }
476     }
477
478     if (SwingUtilities.isRightMouseButton(evt) && activeRow != -1)
479     {
480       if (av.getColumnSelection() == null)
481       {
482         return;
483       }
484
485       JPopupMenu pop = new JPopupMenu("Structure type");
486       JMenuItem item;
487       /*
488        * Just display the needed structure options
489        */
490       if (av.getAlignment().isNucleotide() == true)
491       {
492         item = new JMenuItem(STEM);
493         item.addActionListener(this);
494         pop.add(item);
495       }
496       else
497       {
498         item = new JMenuItem(HELIX);
499         item.addActionListener(this);
500         pop.add(item);
501         item = new JMenuItem(SHEET);
502         item.addActionListener(this);
503         pop.add(item);
504       }
505       item = new JMenuItem(LABEL);
506       item.addActionListener(this);
507       pop.add(item);
508       item = new JMenuItem(COLOUR);
509       item.addActionListener(this);
510       pop.add(item);
511       item = new JMenuItem(REMOVE);
512       item.addActionListener(this);
513       pop.add(item);
514       pop.show(this, evt.getX(), evt.getY());
515
516       return;
517     }
518
519     if (aa == null)
520     {
521       return;
522     }
523
524     ap.scalePanel.mousePressed(evt);
525
526   }
527
528   /**
529    * DOCUMENT ME!
530    * 
531    * @param evt
532    *          DOCUMENT ME!
533    */
534   @Override
535   public void mouseReleased(MouseEvent evt)
536   {
537     graphStretch = -1;
538     graphStretchY = -1;
539     mouseDragging = false;
540     ap.scalePanel.mouseReleased(evt);
541   }
542
543   /**
544    * DOCUMENT ME!
545    * 
546    * @param evt
547    *          DOCUMENT ME!
548    */
549   @Override
550   public void mouseEntered(MouseEvent evt)
551   {
552     ap.scalePanel.mouseEntered(evt);
553   }
554
555   /**
556    * DOCUMENT ME!
557    * 
558    * @param evt
559    *          DOCUMENT ME!
560    */
561   @Override
562   public void mouseExited(MouseEvent evt)
563   {
564     ap.scalePanel.mouseExited(evt);
565   }
566
567   /**
568    * DOCUMENT ME!
569    * 
570    * @param evt
571    *          DOCUMENT ME!
572    */
573   @Override
574   public void mouseDragged(MouseEvent evt)
575   {
576     if (graphStretch > -1)
577     {
578       av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight += graphStretchY
579               - evt.getY();
580       if (av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight < 0)
581       {
582         av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight = 0;
583       }
584       graphStretchY = evt.getY();
585       adjustPanelHeight();
586       ap.paintAlignment(true);
587     }
588     else
589     {
590       ap.scalePanel.mouseDragged(evt);
591     }
592   }
593
594   /**
595    * DOCUMENT ME!
596    * 
597    * @param evt
598    *          DOCUMENT ME!
599    */
600   @Override
601   public void mouseMoved(MouseEvent evt)
602   {
603     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
604
605     if (aa == null)
606     {
607       this.setToolTipText(null);
608       return;
609     }
610
611     int row = -1;
612     int height = 0;
613
614     for (int i = 0; i < aa.length; i++)
615     {
616       if (aa[i].visible)
617       {
618         height += aa[i].height;
619       }
620
621       if (evt.getY() < height)
622       {
623         row = i;
624
625         break;
626       }
627     }
628
629     if (row == -1)
630     {
631       this.setToolTipText(null);
632       return;
633     }
634
635     int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();
636
637     if (av.hasHiddenColumns())
638     {
639       res = av.getColumnSelection().adjustForHiddenColumns(res);
640     }
641
642     if (row > -1 && aa[row].annotations != null
643             && res < aa[row].annotations.length)
644     {
645       if (aa[row].graphGroup > -1)
646       {
647         StringBuffer tip = new StringBuffer("<html>");
648         for (int gg = 0; gg < aa.length; gg++)
649         {
650           if (aa[gg].graphGroup == aa[row].graphGroup
651                   && aa[gg].annotations[res] != null)
652           {
653             tip.append(aa[gg].label + " "
654                     + aa[gg].annotations[res].description + "<br>");
655           }
656         }
657         if (tip.length() != 6)
658         {
659           tip.setLength(tip.length() - 4);
660           this.setToolTipText(tip.toString() + "</html>");
661         }
662       }
663       else if (aa[row].annotations[res] != null
664               && aa[row].annotations[res].description != null
665               && aa[row].annotations[res].description.length() > 0)
666       {
667         this.setToolTipText(aa[row].annotations[res].description);
668       }
669       else
670       {
671         // clear the tooltip.
672         this.setToolTipText(null);
673       }
674
675       if (aa[row].annotations[res] != null)
676       {
677         StringBuffer text = new StringBuffer("Sequence position "
678                 + (res + 1));
679
680         if (aa[row].annotations[res].description != null)
681         {
682           text.append("  " + aa[row].annotations[res].description);
683         }
684
685         ap.alignFrame.statusBar.setText(text.toString());
686       }
687     }
688     else
689     {
690       this.setToolTipText(null);
691     }
692   }
693
694   /**
695    * DOCUMENT ME!
696    * 
697    * @param evt
698    *          DOCUMENT ME!
699    */
700   @Override
701   public void mouseClicked(MouseEvent evt)
702   {
703 //    if (activeRow != -1)
704 //    {
705 //      AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
706 //      AlignmentAnnotation anot = aa[activeRow];
707 //    }
708   }
709
710   // TODO mouseClicked-content and drawCursor are quite experimental!
711   public void drawCursor(Graphics graphics, SequenceI seq, int res, int x1,
712           int y1)
713   {
714     int pady = av.charHeight / 5;
715     int charOffset = 0;
716     graphics.setColor(Color.black);
717     graphics.fillRect(x1, y1, av.charWidth, av.charHeight);
718
719     if (av.validCharWidth)
720     {
721       graphics.setColor(Color.white);
722
723       char s = seq.getCharAt(res);
724
725       charOffset = (av.charWidth - fm.charWidth(s)) / 2;
726       graphics.drawString(String.valueOf(s), charOffset + x1,
727               (y1 + av.charHeight) - pady);
728     }
729
730   }
731
732   private volatile boolean imageFresh = false;
733
734   /**
735    * DOCUMENT ME!
736    * 
737    * @param g
738    *          DOCUMENT ME!
739    */
740   @Override
741   public void paintComponent(Graphics g)
742   {
743     g.setColor(Color.white);
744     g.fillRect(0, 0, getWidth(), getHeight());
745
746     if (image != null)
747     {
748       if (fastPaint || (getVisibleRect().width != g.getClipBounds().width)
749               || (getVisibleRect().height != g.getClipBounds().height))
750       {
751         g.drawImage(image, 0, 0, this);
752         fastPaint = false;
753         return;
754       }
755     }
756     imgWidth = (av.endRes - av.startRes + 1) * av.charWidth;
757     if (imgWidth < 1)
758       return;
759     if (image == null || imgWidth != image.getWidth(this)
760             || image.getHeight(this) != getHeight())
761     {
762       try
763       {
764         image = new BufferedImage(imgWidth, ap.annotationPanel.getHeight(),
765                 BufferedImage.TYPE_INT_RGB);
766       } catch (OutOfMemoryError oom)
767       {
768         try
769         {
770           System.gc();
771         } catch (Exception x)
772         {
773         }
774         ;
775         new OOMWarning(
776                 "Couldn't allocate memory to redraw screen. Please restart Jalview",
777                 oom);
778         return;
779       }
780       gg = (Graphics2D) image.getGraphics();
781
782       if (av.antiAlias)
783       {
784         gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
785                 RenderingHints.VALUE_ANTIALIAS_ON);
786       }
787
788       gg.setFont(av.getFont());
789       fm = gg.getFontMetrics();
790       gg.setColor(Color.white);
791       gg.fillRect(0, 0, imgWidth, image.getHeight());
792       imageFresh = true;
793     }
794
795     drawComponent(gg, av.startRes, av.endRes + 1);
796     imageFresh = false;
797     g.drawImage(image, 0, 0, this);
798   }
799   /**
800    * set true to enable redraw timing debug output on stderr
801    */
802   private final boolean debugRedraw = false;
803   /**
804    * non-Thread safe repaint
805    * 
806    * @param horizontal
807    *          repaint with horizontal shift in alignment
808    */
809   public void fastPaint(int horizontal)
810   {
811     if ((horizontal == 0) || gg == null
812             || av.getAlignment().getAlignmentAnnotation() == null
813             || av.getAlignment().getAlignmentAnnotation().length < 1
814             || av.isCalcInProgress())
815     {
816       repaint();
817       return;
818     }
819     long stime=System.currentTimeMillis();
820     gg.copyArea(0, 0, imgWidth, getHeight(), -horizontal * av.charWidth, 0);
821     long mtime=System.currentTimeMillis();
822     int sr = av.startRes;
823     int er = av.endRes + 1;
824     int transX = 0;
825
826     if (horizontal > 0) // scrollbar pulled right, image to the left
827     {
828       transX = (er - sr - horizontal) * av.charWidth;
829       sr = er - horizontal;
830     }
831     else if (horizontal < 0)
832     {
833       er = sr - horizontal;
834     }
835
836     gg.translate(transX, 0);
837
838     drawComponent(gg, sr, er);
839
840     gg.translate(-transX, 0);
841     long dtime=System.currentTimeMillis();
842     fastPaint = true;
843     repaint();
844     long rtime=System.currentTimeMillis();
845     if (debugRedraw) {
846       System.err.println("Scroll:\t"+horizontal+"\tCopyArea:\t"+(mtime-stime)+"\tDraw component:\t"+(dtime-mtime)+"\tRepaint call:\t"+(rtime-dtime));
847     }
848
849   }
850
851   private volatile boolean lastImageGood = false;
852
853   /**
854    * DOCUMENT ME!
855    * 
856    * @param g
857    *          DOCUMENT ME!
858    * @param startRes
859    *          DOCUMENT ME!
860    * @param endRes
861    *          DOCUMENT ME!
862    */
863   public void drawComponent(Graphics g, int startRes, int endRes)
864   {
865     BufferedImage oldFaded = fadedImage;
866     if (av.isCalcInProgress())
867     {
868       if (image == null)
869       {
870         lastImageGood = false;
871         return;
872       }
873       // We'll keep a record of the old image,
874       // and draw a faded image until the calculation
875       // has completed
876       if (lastImageGood
877               && (fadedImage == null || fadedImage.getWidth() != imgWidth || fadedImage
878                       .getHeight() != image.getHeight()))
879       {
880         // System.err.println("redraw faded image ("+(fadedImage==null ?
881         // "null image" : "") + " lastGood="+lastImageGood+")");
882         fadedImage = new BufferedImage(imgWidth, image.getHeight(),
883                 BufferedImage.TYPE_INT_RGB);
884
885         Graphics2D fadedG = (Graphics2D) fadedImage.getGraphics();
886
887         fadedG.setColor(Color.white);
888         fadedG.fillRect(0, 0, imgWidth, image.getHeight());
889
890         fadedG.setComposite(AlphaComposite.getInstance(
891                 AlphaComposite.SRC_OVER, .3f));
892         fadedG.drawImage(image, 0, 0, this);
893
894       }
895       // make sure we don't overwrite the last good faded image until all
896       // calculations have finished
897       lastImageGood = false;
898
899     }
900     else
901     {
902       if (fadedImage != null)
903       {
904         oldFaded = fadedImage;
905       }
906       fadedImage = null;
907     }
908     
909     g.setColor(Color.white);
910     g.fillRect(0, 0, (endRes - startRes) * av.charWidth, getHeight());
911
912     g.setFont(av.getFont());
913     if (fm == null)
914     {
915       fm = g.getFontMetrics();
916     }
917
918     if ((av.getAlignment().getAlignmentAnnotation() == null)
919             || (av.getAlignment().getAlignmentAnnotation().length < 1))
920     {
921       g.setColor(Color.white);
922       g.fillRect(0, 0, getWidth(), getHeight());
923       g.setColor(Color.black);
924       if (av.validCharWidth)
925       {
926         g.drawString("Alignment has no annotations", 20, 15);
927       }
928
929       return;
930     }
931     lastImageGood = renderer.drawComponent(this, av, g, activeRow,
932             startRes, endRes);
933     if (!lastImageGood && fadedImage == null)
934     {
935       fadedImage = oldFaded;
936     }
937   }
938
939   @Override
940   public FontMetrics getFontMetrics()
941   {
942     return fm;
943   }
944
945   @Override
946   public Image getFadedImage()
947   {
948     return fadedImage;
949   }
950
951   @Override
952   public int getFadedImageWidth()
953   {
954     return imgWidth;
955   }
956   private int[] bounds = new int[2];
957   @Override
958   public int[] getVisibleVRange()
959   {
960     if (ap!=null && ap.alabels!=null)
961     {
962     int sOffset=-ap.alabels.scrollOffset;
963     int visHeight = sOffset+ap.annotationSpaceFillerHolder.getHeight();
964     bounds[0] = sOffset; bounds[1]=visHeight;
965     return bounds;
966     } else return null;
967   }
968 }