JAL-2077 - reinstate popups on osx
[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 OR SHEET
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     av.getAlignment().validateAnnotation(aa[activeRow]);
416     ap.alignmentChanged();
417
418     adjustPanelHeight();
419     repaint();
420
421     return;
422   }
423
424   private String collectAnnotVals(Annotation[] anot, String label2)
425   {
426     String collatedInput = "";
427     String last = "";
428     ColumnSelection viscols = av.getColumnSelection();
429     // TODO: refactor and save av.getColumnSelection for efficiency
430     for (int index : viscols.getSelected())
431     {
432       // always check for current display state - just in case
433       if (!viscols.isVisible(index))
434       {
435         continue;
436       }
437       String tlabel = null;
438       if (anot[index] != null)
439       { // LML added stem code
440         if (label2.equals(HELIX) || label2.equals(SHEET)
441                 || label2.equals(STEM) || label2.equals(LABEL))
442         {
443           tlabel = anot[index].description;
444           if (tlabel == null || tlabel.length() < 1)
445           {
446             if (label2.equals(HELIX) || label2.equals(SHEET)
447                     || label2.equals(STEM))
448             {
449               tlabel = "" + anot[index].secondaryStructure;
450             }
451             else
452             {
453               tlabel = "" + anot[index].displayCharacter;
454             }
455           }
456         }
457         if (tlabel != null && !tlabel.equals(last))
458         {
459           if (last.length() > 0)
460           {
461             collatedInput += " ";
462           }
463           collatedInput += tlabel;
464         }
465       }
466     }
467     return collatedInput;
468   }
469
470   /**
471    * DOCUMENT ME!
472    * 
473    * @param evt
474    *          DOCUMENT ME!
475    */
476   @Override
477   public void mousePressed(MouseEvent evt)
478   {
479
480     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
481     if (aa == null)
482     {
483       return;
484     }
485
486     int height = 0;
487     activeRow = -1;
488
489     for (int i = 0; i < aa.length; i++)
490     {
491       if (aa[i].visible)
492       {
493         height += aa[i].height;
494       }
495
496       if (evt.getY() < height)
497       {
498         if (aa[i].editable)
499         {
500           activeRow = i;
501         }
502         else if (aa[i].graph > 0)
503         {
504           // Stretch Graph
505           graphStretch = i;
506           graphStretchY = evt.getY();
507         }
508
509         break;
510       }
511     }
512
513     if (evt.isPopupTrigger() && activeRow != -1)
514     {
515       if (av.getColumnSelection() == null)
516       {
517         return;
518       }
519
520       JPopupMenu pop = new JPopupMenu(
521               MessageManager.getString("label.structure_type"));
522       JMenuItem item;
523       /*
524        * Just display the needed structure options
525        */
526       if (av.getAlignment().isNucleotide() == true)
527       {
528         item = new JMenuItem(STEM);
529         item.addActionListener(this);
530         pop.add(item);
531       }
532       else
533       {
534         item = new JMenuItem(HELIX);
535         item.addActionListener(this);
536         pop.add(item);
537         item = new JMenuItem(SHEET);
538         item.addActionListener(this);
539         pop.add(item);
540       }
541       item = new JMenuItem(LABEL);
542       item.addActionListener(this);
543       pop.add(item);
544       item = new JMenuItem(COLOUR);
545       item.addActionListener(this);
546       pop.add(item);
547       item = new JMenuItem(REMOVE);
548       item.addActionListener(this);
549       pop.add(item);
550       pop.show(this, evt.getX(), evt.getY());
551
552       return;
553     }
554
555     if (aa == null)
556     {
557       return;
558     }
559
560     ap.getScalePanel().mousePressed(evt);
561
562   }
563
564   /**
565    * DOCUMENT ME!
566    * 
567    * @param evt
568    *          DOCUMENT ME!
569    */
570   @Override
571   public void mouseReleased(MouseEvent evt)
572   {
573     graphStretch = -1;
574     graphStretchY = -1;
575     mouseDragging = false;
576     ap.getScalePanel().mouseReleased(evt);
577   }
578
579   /**
580    * DOCUMENT ME!
581    * 
582    * @param evt
583    *          DOCUMENT ME!
584    */
585   @Override
586   public void mouseEntered(MouseEvent evt)
587   {
588     ap.getScalePanel().mouseEntered(evt);
589   }
590
591   /**
592    * DOCUMENT ME!
593    * 
594    * @param evt
595    *          DOCUMENT ME!
596    */
597   @Override
598   public void mouseExited(MouseEvent evt)
599   {
600     ap.getScalePanel().mouseExited(evt);
601   }
602
603   /**
604    * DOCUMENT ME!
605    * 
606    * @param evt
607    *          DOCUMENT ME!
608    */
609   @Override
610   public void mouseDragged(MouseEvent evt)
611   {
612     if (graphStretch > -1)
613     {
614       av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight += graphStretchY
615               - evt.getY();
616       if (av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight < 0)
617       {
618         av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight = 0;
619       }
620       graphStretchY = evt.getY();
621       adjustPanelHeight();
622       ap.paintAlignment(true);
623     }
624     else
625     {
626       ap.getScalePanel().mouseDragged(evt);
627     }
628   }
629
630   /**
631    * DOCUMENT ME!
632    * 
633    * @param evt
634    *          DOCUMENT ME!
635    */
636   @Override
637   public void mouseMoved(MouseEvent evt)
638   {
639     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
640
641     if (aa == null)
642     {
643       this.setToolTipText(null);
644       return;
645     }
646
647     int row = -1;
648     int height = 0;
649
650     for (int i = 0; i < aa.length; i++)
651     {
652       if (aa[i].visible)
653       {
654         height += aa[i].height;
655       }
656
657       if (evt.getY() < height)
658       {
659         row = i;
660
661         break;
662       }
663     }
664
665     if (row == -1)
666     {
667       this.setToolTipText(null);
668       return;
669     }
670
671     int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();
672
673     if (av.hasHiddenColumns())
674     {
675       res = av.getColumnSelection().adjustForHiddenColumns(res);
676     }
677
678     if (row > -1 && aa[row].annotations != null
679             && res < aa[row].annotations.length)
680     {
681       if (aa[row].graphGroup > -1)
682       {
683         StringBuffer tip = new StringBuffer("<html>");
684         for (int gg = 0; gg < aa.length; gg++)
685         {
686           if (aa[gg].graphGroup == aa[row].graphGroup
687                   && aa[gg].annotations[res] != null)
688           {
689             tip.append(aa[gg].label + " "
690                     + aa[gg].annotations[res].description + "<br>");
691           }
692         }
693         if (tip.length() != 6)
694         {
695           tip.setLength(tip.length() - 4);
696           this.setToolTipText(tip.toString() + "</html>");
697         }
698       }
699       else if (aa[row].annotations[res] != null
700               && aa[row].annotations[res].description != null
701               && aa[row].annotations[res].description.length() > 0)
702       {
703         this.setToolTipText(JvSwingUtils.wrapTooltip(true,
704                 aa[row].annotations[res].description));
705       }
706       else
707       {
708         // clear the tooltip.
709         this.setToolTipText(null);
710       }
711
712       if (aa[row].annotations[res] != null)
713       {
714         StringBuffer text = new StringBuffer("Sequence position "
715                 + (res + 1));
716
717         if (aa[row].annotations[res].description != null)
718         {
719           text.append("  " + aa[row].annotations[res].description);
720         }
721
722         ap.alignFrame.statusBar.setText(text.toString());
723       }
724     }
725     else
726     {
727       this.setToolTipText(null);
728     }
729   }
730
731   /**
732    * DOCUMENT ME!
733    * 
734    * @param evt
735    *          DOCUMENT ME!
736    */
737   @Override
738   public void mouseClicked(MouseEvent evt)
739   {
740     // if (activeRow != -1)
741     // {
742     // AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
743     // AlignmentAnnotation anot = aa[activeRow];
744     // }
745   }
746
747   // TODO mouseClicked-content and drawCursor are quite experimental!
748   public void drawCursor(Graphics graphics, SequenceI seq, int res, int x1,
749           int y1)
750   {
751     int pady = av.getCharHeight() / 5;
752     int charOffset = 0;
753     graphics.setColor(Color.black);
754     graphics.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight());
755
756     if (av.validCharWidth)
757     {
758       graphics.setColor(Color.white);
759
760       char s = seq.getCharAt(res);
761
762       charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
763       graphics.drawString(String.valueOf(s), charOffset + x1,
764               (y1 + av.getCharHeight()) - pady);
765     }
766
767   }
768
769   private volatile boolean imageFresh = false;
770
771   /**
772    * DOCUMENT ME!
773    * 
774    * @param g
775    *          DOCUMENT ME!
776    */
777   @Override
778   public void paintComponent(Graphics g)
779   {
780     g.setColor(Color.white);
781     g.fillRect(0, 0, getWidth(), getHeight());
782
783     if (image != null)
784     {
785       if (fastPaint || (getVisibleRect().width != g.getClipBounds().width)
786               || (getVisibleRect().height != g.getClipBounds().height))
787       {
788         g.drawImage(image, 0, 0, this);
789         fastPaint = false;
790         return;
791       }
792     }
793     imgWidth = (av.endRes - av.startRes + 1) * av.getCharWidth();
794     if (imgWidth < 1)
795     {
796       return;
797     }
798     if (image == null || imgWidth != image.getWidth(this)
799             || image.getHeight(this) != getHeight())
800     {
801       try
802       {
803         image = new BufferedImage(imgWidth, ap.getAnnotationPanel()
804                 .getHeight(), BufferedImage.TYPE_INT_RGB);
805       } catch (OutOfMemoryError oom)
806       {
807         try
808         {
809           System.gc();
810         } catch (Exception x)
811         {
812         }
813         ;
814         new OOMWarning(
815                 "Couldn't allocate memory to redraw screen. Please restart Jalview",
816                 oom);
817         return;
818       }
819       gg = (Graphics2D) image.getGraphics();
820
821       if (av.antiAlias)
822       {
823         gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
824                 RenderingHints.VALUE_ANTIALIAS_ON);
825       }
826
827       gg.setFont(av.getFont());
828       fm = gg.getFontMetrics();
829       gg.setColor(Color.white);
830       gg.fillRect(0, 0, imgWidth, image.getHeight());
831       imageFresh = true;
832     }
833
834     drawComponent(gg, av.startRes, av.endRes + 1);
835     imageFresh = false;
836     g.drawImage(image, 0, 0, this);
837   }
838
839   /**
840    * set true to enable redraw timing debug output on stderr
841    */
842   private final boolean debugRedraw = false;
843
844   /**
845    * non-Thread safe repaint
846    * 
847    * @param horizontal
848    *          repaint with horizontal shift in alignment
849    */
850   public void fastPaint(int horizontal)
851   {
852     if ((horizontal == 0) || gg == null
853             || av.getAlignment().getAlignmentAnnotation() == null
854             || av.getAlignment().getAlignmentAnnotation().length < 1
855             || av.isCalcInProgress())
856     {
857       repaint();
858       return;
859     }
860     long stime = System.currentTimeMillis();
861     gg.copyArea(0, 0, imgWidth, getHeight(),
862             -horizontal * av.getCharWidth(), 0);
863     long mtime = System.currentTimeMillis();
864     int sr = av.startRes;
865     int er = av.endRes + 1;
866     int transX = 0;
867
868     if (horizontal > 0) // scrollbar pulled right, image to the left
869     {
870       transX = (er - sr - horizontal) * av.getCharWidth();
871       sr = er - horizontal;
872     }
873     else if (horizontal < 0)
874     {
875       er = sr - horizontal;
876     }
877
878     gg.translate(transX, 0);
879
880     drawComponent(gg, sr, er);
881
882     gg.translate(-transX, 0);
883     long dtime = System.currentTimeMillis();
884     fastPaint = true;
885     repaint();
886     long rtime = System.currentTimeMillis();
887     if (debugRedraw)
888     {
889       System.err.println("Scroll:\t" + horizontal + "\tCopyArea:\t"
890               + (mtime - stime) + "\tDraw component:\t" + (dtime - mtime)
891               + "\tRepaint call:\t" + (rtime - dtime));
892     }
893
894   }
895
896   private volatile boolean lastImageGood = false;
897
898   /**
899    * DOCUMENT ME!
900    * 
901    * @param g
902    *          DOCUMENT ME!
903    * @param startRes
904    *          DOCUMENT ME!
905    * @param endRes
906    *          DOCUMENT ME!
907    */
908   public void drawComponent(Graphics g, int startRes, int endRes)
909   {
910     BufferedImage oldFaded = fadedImage;
911     if (av.isCalcInProgress())
912     {
913       if (image == null)
914       {
915         lastImageGood = false;
916         return;
917       }
918       // We'll keep a record of the old image,
919       // and draw a faded image until the calculation
920       // has completed
921       if (lastImageGood
922               && (fadedImage == null || fadedImage.getWidth() != imgWidth || fadedImage
923                       .getHeight() != image.getHeight()))
924       {
925         // System.err.println("redraw faded image ("+(fadedImage==null ?
926         // "null image" : "") + " lastGood="+lastImageGood+")");
927         fadedImage = new BufferedImage(imgWidth, image.getHeight(),
928                 BufferedImage.TYPE_INT_RGB);
929
930         Graphics2D fadedG = (Graphics2D) fadedImage.getGraphics();
931
932         fadedG.setColor(Color.white);
933         fadedG.fillRect(0, 0, imgWidth, image.getHeight());
934
935         fadedG.setComposite(AlphaComposite.getInstance(
936                 AlphaComposite.SRC_OVER, .3f));
937         fadedG.drawImage(image, 0, 0, this);
938
939       }
940       // make sure we don't overwrite the last good faded image until all
941       // calculations have finished
942       lastImageGood = false;
943
944     }
945     else
946     {
947       if (fadedImage != null)
948       {
949         oldFaded = fadedImage;
950       }
951       fadedImage = null;
952     }
953
954     g.setColor(Color.white);
955     g.fillRect(0, 0, (endRes - startRes) * av.getCharWidth(), getHeight());
956
957     g.setFont(av.getFont());
958     if (fm == null)
959     {
960       fm = g.getFontMetrics();
961     }
962
963     if ((av.getAlignment().getAlignmentAnnotation() == null)
964             || (av.getAlignment().getAlignmentAnnotation().length < 1))
965     {
966       g.setColor(Color.white);
967       g.fillRect(0, 0, getWidth(), getHeight());
968       g.setColor(Color.black);
969       if (av.validCharWidth)
970       {
971         g.drawString(MessageManager
972                 .getString("label.alignment_has_no_annotations"), 20, 15);
973       }
974
975       return;
976     }
977     lastImageGood = renderer.drawComponent(this, av, g, activeRow,
978             startRes, endRes);
979     if (!lastImageGood && fadedImage == null)
980     {
981       fadedImage = oldFaded;
982     }
983   }
984
985   @Override
986   public FontMetrics getFontMetrics()
987   {
988     return fm;
989   }
990
991   @Override
992   public Image getFadedImage()
993   {
994     return fadedImage;
995   }
996
997   @Override
998   public int getFadedImageWidth()
999   {
1000     return imgWidth;
1001   }
1002
1003   private int[] bounds = new int[2];
1004
1005   @Override
1006   public int[] getVisibleVRange()
1007   {
1008     if (ap != null && ap.getAlabels() != null)
1009     {
1010       int sOffset = -ap.getAlabels().getScrollOffset();
1011       int visHeight = sOffset + ap.annotationSpaceFillerHolder.getHeight();
1012       bounds[0] = sOffset;
1013       bounds[1] = visHeight;
1014       return bounds;
1015     }
1016     else
1017     {
1018       return null;
1019     }
1020   }
1021 }