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