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