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