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