JAL-2171 matching bracket default character for rna SS entry
[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.Comparison;
30 import jalview.util.MessageManager;
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.ToolTipManager;
59
60 /**
61  * AnnotationPanel displays visible portion of annotation rows below unwrapped
62  * alignment
63  * 
64  * @author $author$
65  * @version $Revision$
66  */
67 public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
68         MouseListener, MouseWheelListener, MouseMotionListener,
69         ActionListener, AdjustmentListener, Scrollable
70 {
71   String HELIX = MessageManager.getString("label.helix");
72
73   String SHEET = MessageManager.getString("label.sheet");
74
75   /**
76    * For RNA secondary structure "stems" aka helices
77    */
78   String STEM = MessageManager.getString("label.rna_helix");
79
80   String LABEL = MessageManager.getString("label.label");
81
82   String REMOVE = MessageManager.getString("label.remove_annotation");
83
84   String COLOUR = MessageManager.getString("action.colour");
85
86   public final Color HELIX_COLOUR = Color.red.darker();
87
88   public final Color SHEET_COLOUR = Color.green.darker().darker();
89
90   public final Color STEM_COLOUR = Color.blue.darker();
91
92   /** DOCUMENT ME!! */
93   public AlignViewport av;
94
95   AlignmentPanel ap;
96
97   public int activeRow = -1;
98
99   public BufferedImage image;
100
101   public volatile BufferedImage fadedImage;
102
103   Graphics2D gg;
104
105   public FontMetrics fm;
106
107   public int imgWidth = 0;
108
109   boolean fastPaint = false;
110
111   // Used For mouse Dragging and resizing graphs
112   int graphStretch = -1;
113
114   int graphStretchY = -1;
115
116   int min; // used by mouseDragged to see if user
117
118   int max; // used by mouseDragged to see if user
119
120   boolean mouseDragging = false;
121
122   boolean MAC = false;
123
124   // for editing cursor
125   int cursorX = 0;
126
127   int cursorY = 0;
128
129   public final AnnotationRenderer renderer;
130
131   private MouseWheelListener[] _mwl;
132
133   /**
134    * Creates a new AnnotationPanel object.
135    * 
136    * @param ap
137    *          DOCUMENT ME!
138    */
139   public AnnotationPanel(AlignmentPanel ap)
140   {
141
142     MAC = jalview.util.Platform.isAMac();
143
144     ToolTipManager.sharedInstance().registerComponent(this);
145     ToolTipManager.sharedInstance().setInitialDelay(0);
146     ToolTipManager.sharedInstance().setDismissDelay(10000);
147     this.ap = ap;
148     av = ap.av;
149     this.setLayout(null);
150     addMouseListener(this);
151     addMouseMotionListener(this);
152     ap.annotationScroller.getVerticalScrollBar()
153             .addAdjustmentListener(this);
154     // save any wheel listeners on the scroller, so we can propagate scroll
155     // events to them.
156     _mwl = ap.annotationScroller.getMouseWheelListeners();
157     // and then set our own listener to consume all mousewheel events
158     ap.annotationScroller.addMouseWheelListener(this);
159     renderer = new AnnotationRenderer();
160   }
161
162   public AnnotationPanel(AlignViewport av)
163   {
164     this.av = av;
165     renderer = new AnnotationRenderer();
166   }
167
168   @Override
169   public void mouseWheelMoved(MouseWheelEvent e)
170   {
171     if (e.isShiftDown())
172     {
173       e.consume();
174       if (e.getWheelRotation() > 0)
175       {
176         ap.scrollRight(true);
177       }
178       else
179       {
180         ap.scrollRight(false);
181       }
182     }
183     else
184     {
185       // TODO: find the correct way to let the event bubble up to
186       // ap.annotationScroller
187       for (MouseWheelListener mwl : _mwl)
188       {
189         if (mwl != null)
190         {
191           mwl.mouseWheelMoved(e);
192         }
193         if (e.isConsumed())
194         {
195           break;
196         }
197       }
198     }
199   }
200
201   @Override
202   public Dimension getPreferredScrollableViewportSize()
203   {
204     return getPreferredSize();
205   }
206
207   @Override
208   public int getScrollableBlockIncrement(Rectangle visibleRect,
209           int orientation, int direction)
210   {
211     return 30;
212   }
213
214   @Override
215   public boolean getScrollableTracksViewportHeight()
216   {
217     return false;
218   }
219
220   @Override
221   public boolean getScrollableTracksViewportWidth()
222   {
223     return true;
224   }
225
226   @Override
227   public int getScrollableUnitIncrement(Rectangle visibleRect,
228           int orientation, int direction)
229   {
230     return 30;
231   }
232
233   /*
234    * (non-Javadoc)
235    * 
236    * @see
237    * java.awt.event.AdjustmentListener#adjustmentValueChanged(java.awt.event
238    * .AdjustmentEvent)
239    */
240   @Override
241   public void adjustmentValueChanged(AdjustmentEvent evt)
242   {
243     // update annotation label display
244     ap.getAlabels().setScrollOffset(-evt.getValue());
245   }
246
247   /**
248    * Calculates the height of the annotation displayed in the annotation panel.
249    * Callers should normally call the ap.adjustAnnotationHeight method to ensure
250    * all annotation associated components are updated correctly.
251    * 
252    */
253   public int adjustPanelHeight()
254   {
255     int height = av.calcPanelHeight();
256     this.setPreferredSize(new Dimension(1, height));
257     if (ap != null)
258     {
259       // revalidate only when the alignment panel is fully constructed
260       ap.validate();
261     }
262
263     return height;
264   }
265
266   /**
267    * DOCUMENT ME!
268    * 
269    * @param evt
270    *          DOCUMENT ME!
271    */
272   @Override
273   public void actionPerformed(ActionEvent evt)
274   {
275     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
276     if (aa == null)
277     {
278       return;
279     }
280     Annotation[] anot = aa[activeRow].annotations;
281
282     if (anot.length < av.getColumnSelection().getMax())
283     {
284       Annotation[] temp = new Annotation[av.getColumnSelection().getMax() + 2];
285       System.arraycopy(anot, 0, temp, 0, anot.length);
286       anot = temp;
287       aa[activeRow].annotations = anot;
288     }
289
290     String action = evt.getActionCommand();
291     if (action.equals(REMOVE))
292     {
293       for (int sel : av.getColumnSelection().getSelected())
294       {
295         anot[sel] = null;
296       }
297     }
298     else if (action.equals(LABEL))
299     {
300       String exMesg = collectAnnotVals(anot, 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 index : av.getColumnSelection().getSelected())
315       {
316         if (!av.getColumnSelection().isVisible(index))
317         {
318           continue;
319         }
320
321         if (anot[index] == null)
322         {
323           anot[index] = new Annotation(label, "", ' ', 0);
324         }
325         else
326         {
327           anot[index].displayCharacter = label;
328         }
329       }
330     }
331     else if (action.equals(COLOUR))
332     {
333       Color col = JColorChooser.showDialog(this,
334               MessageManager.getString("label.select_foreground_colour"),
335               Color.black);
336
337       for (int index : av.getColumnSelection().getSelected())
338       {
339         if (!av.getColumnSelection().isVisible(index))
340         {
341           continue;
342         }
343
344         if (anot[index] == null)
345         {
346           anot[index] = new Annotation("", "", ' ', 0);
347         }
348
349         anot[index].colour = col;
350       }
351     }
352     else
353     // HELIX, SHEET or STEM
354     {
355       char type = 0;
356       String symbol = "\u03B1"; // alpha
357
358       if (action.equals(HELIX))
359       {
360         type = 'H';
361       }
362       else if (action.equals(SHEET))
363       {
364         type = 'E';
365         symbol = "\u03B2"; // beta
366       }
367
368       // Added by LML to color stems
369       else if (action.equals(STEM))
370       {
371         type = 'S';
372         int column = av.getColumnSelection().getSelectedRanges().get(0)[0];
373         symbol = aa[activeRow].getDefaultRnaHelixSymbol(column);
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 (action.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               || av.getColumnSelection().isEmpty())
518       {
519         return;
520       }
521
522       JPopupMenu pop = new JPopupMenu(
523               MessageManager.getString("label.structure_type"));
524       JMenuItem item;
525       /*
526        * Just display the needed structure options
527        */
528       if (av.getAlignment().isNucleotide())
529       {
530         item = new JMenuItem(STEM);
531         item.addActionListener(this);
532         pop.add(item);
533       }
534       else
535       {
536         item = new JMenuItem(HELIX);
537         item.addActionListener(this);
538         pop.add(item);
539         item = new JMenuItem(SHEET);
540         item.addActionListener(this);
541         pop.add(item);
542       }
543       item = new JMenuItem(LABEL);
544       item.addActionListener(this);
545       pop.add(item);
546       item = new JMenuItem(COLOUR);
547       item.addActionListener(this);
548       pop.add(item);
549       item = new JMenuItem(REMOVE);
550       item.addActionListener(this);
551       pop.add(item);
552       pop.show(this, evt.getX(), evt.getY());
553
554       return;
555     }
556
557     ap.getScalePanel().mousePressed(evt);
558
559   }
560
561   /**
562    * DOCUMENT ME!
563    * 
564    * @param evt
565    *          DOCUMENT ME!
566    */
567   @Override
568   public void mouseReleased(MouseEvent evt)
569   {
570     graphStretch = -1;
571     graphStretchY = -1;
572     mouseDragging = false;
573     ap.getScalePanel().mouseReleased(evt);
574   }
575
576   /**
577    * DOCUMENT ME!
578    * 
579    * @param evt
580    *          DOCUMENT ME!
581    */
582   @Override
583   public void mouseEntered(MouseEvent evt)
584   {
585     ap.getScalePanel().mouseEntered(evt);
586   }
587
588   /**
589    * DOCUMENT ME!
590    * 
591    * @param evt
592    *          DOCUMENT ME!
593    */
594   @Override
595   public void mouseExited(MouseEvent evt)
596   {
597     ap.getScalePanel().mouseExited(evt);
598   }
599
600   /**
601    * DOCUMENT ME!
602    * 
603    * @param evt
604    *          DOCUMENT ME!
605    */
606   @Override
607   public void mouseDragged(MouseEvent evt)
608   {
609     if (graphStretch > -1)
610     {
611       av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight += graphStretchY
612               - evt.getY();
613       if (av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight < 0)
614       {
615         av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight = 0;
616       }
617       graphStretchY = evt.getY();
618       adjustPanelHeight();
619       ap.paintAlignment(true);
620     }
621     else
622     {
623       ap.getScalePanel().mouseDragged(evt);
624     }
625   }
626
627   /**
628    * Constructs the tooltip, and constructs and displays a status message, for
629    * the current mouse position
630    * 
631    * @param evt
632    */
633   @Override
634   public void mouseMoved(MouseEvent evt)
635   {
636     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
637
638     if (aa == null)
639     {
640       this.setToolTipText(null);
641       return;
642     }
643
644     int row = -1;
645     int height = 0;
646
647     for (int i = 0; i < aa.length; i++)
648     {
649       if (aa[i].visible)
650       {
651         height += aa[i].height;
652       }
653
654       if (evt.getY() < height)
655       {
656         row = i;
657         break;
658       }
659     }
660
661     if (row == -1)
662     {
663       this.setToolTipText(null);
664       return;
665     }
666
667     int column = (evt.getX() / av.getCharWidth()) + av.getStartRes();
668
669     if (av.hasHiddenColumns())
670     {
671       column = av.getColumnSelection().adjustForHiddenColumns(column);
672     }
673
674     AlignmentAnnotation ann = aa[row];
675     if (row > -1 && ann.annotations != null
676             && column < ann.annotations.length)
677     {
678       buildToolTip(ann, column, aa);
679       setStatusMessage(column, ann);
680     }
681     else
682     {
683       this.setToolTipText(null);
684       ap.alignFrame.statusBar.setText(" ");
685     }
686   }
687
688   /**
689    * Builds a tooltip for the annotation at the current mouse position.
690    * 
691    * @param ann
692    * @param column
693    * @param anns
694    */
695   void buildToolTip(AlignmentAnnotation ann, int column,
696           AlignmentAnnotation[] anns)
697   {
698     if (ann.graphGroup > -1)
699     {
700       StringBuilder tip = new StringBuilder(32);
701       tip.append("<html>");
702       for (int i = 0; i < anns.length; i++)
703       {
704         if (anns[i].graphGroup == ann.graphGroup
705                 && anns[i].annotations[column] != null)
706         {
707           tip.append(anns[i].label);
708           String description = anns[i].annotations[column].description;
709           if (description != null && description.length() > 0)
710           {
711             tip.append(" ").append(description);
712           }
713           tip.append("<br>");
714         }
715       }
716       if (tip.length() != 6)
717       {
718         tip.setLength(tip.length() - 4);
719         this.setToolTipText(tip.toString() + "</html>");
720       }
721     }
722     else if (ann.annotations[column] != null)
723     {
724       String description = ann.annotations[column].description;
725       if (description != null && description.length() > 0)
726       {
727         this.setToolTipText(JvSwingUtils.wrapTooltip(true, description));
728       }
729     }
730     else
731     {
732       // clear the tooltip.
733       this.setToolTipText(null);
734     }
735   }
736
737   /**
738    * Constructs and displays the status bar message
739    * 
740    * @param column
741    * @param ann
742    */
743   void setStatusMessage(int column, AlignmentAnnotation ann)
744   {
745     /*
746      * show alignment column and annotation description if any
747      */
748     StringBuilder text = new StringBuilder(32);
749     text.append(MessageManager.getString("label.column")).append(" ")
750             .append(column + 1);
751
752     if (ann.annotations[column] != null)
753     {
754       String description = ann.annotations[column].description;
755       if (description != null && description.trim().length() > 0)
756       {
757         text.append("  ").append(description);
758       }
759     }
760
761     /*
762      * if the annotation is sequence-specific, show the sequence number
763      * in the alignment, and (if not a gap) the residue and position
764      */
765     SequenceI seqref = ann.sequenceRef;
766     if (seqref != null)
767     {
768       int seqIndex = av.getAlignment().findIndex(seqref);
769       if (seqIndex != -1)
770       {
771         text.append(", ")
772                 .append(MessageManager.getString("label.sequence"))
773                 .append(" ")
774                 .append(seqIndex + 1);
775         char residue = seqref.getCharAt(column);
776         if (!Comparison.isGap(residue))
777         {
778           int residuePos = seqref.findPosition(column);
779           text.append(": ").append(residue).append(" (")
780                   .append(residuePos).append(")");
781         }
782       }
783     }
784
785     ap.alignFrame.statusBar.setText(text.toString());
786   }
787
788   /**
789    * DOCUMENT ME!
790    * 
791    * @param evt
792    *          DOCUMENT ME!
793    */
794   @Override
795   public void mouseClicked(MouseEvent evt)
796   {
797     // if (activeRow != -1)
798     // {
799     // AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
800     // AlignmentAnnotation anot = aa[activeRow];
801     // }
802   }
803
804   // TODO mouseClicked-content and drawCursor are quite experimental!
805   public void drawCursor(Graphics graphics, SequenceI seq, int res, int x1,
806           int y1)
807   {
808     int pady = av.getCharHeight() / 5;
809     int charOffset = 0;
810     graphics.setColor(Color.black);
811     graphics.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight());
812
813     if (av.validCharWidth)
814     {
815       graphics.setColor(Color.white);
816
817       char s = seq.getCharAt(res);
818
819       charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
820       graphics.drawString(String.valueOf(s), charOffset + x1,
821               (y1 + av.getCharHeight()) - pady);
822     }
823
824   }
825
826   private volatile boolean imageFresh = false;
827
828   /**
829    * DOCUMENT ME!
830    * 
831    * @param g
832    *          DOCUMENT ME!
833    */
834   @Override
835   public void paintComponent(Graphics g)
836   {
837     g.setColor(Color.white);
838     g.fillRect(0, 0, getWidth(), getHeight());
839
840     if (image != null)
841     {
842       if (fastPaint || (getVisibleRect().width != g.getClipBounds().width)
843               || (getVisibleRect().height != g.getClipBounds().height))
844       {
845         g.drawImage(image, 0, 0, this);
846         fastPaint = false;
847         return;
848       }
849     }
850     imgWidth = (av.endRes - av.startRes + 1) * av.getCharWidth();
851     if (imgWidth < 1)
852     {
853       return;
854     }
855     if (image == null || imgWidth != image.getWidth(this)
856             || image.getHeight(this) != getHeight())
857     {
858       try
859       {
860         image = new BufferedImage(imgWidth, ap.getAnnotationPanel()
861                 .getHeight(), BufferedImage.TYPE_INT_RGB);
862       } catch (OutOfMemoryError oom)
863       {
864         try
865         {
866           System.gc();
867         } catch (Exception x)
868         {
869         }
870         ;
871         new OOMWarning(
872                 "Couldn't allocate memory to redraw screen. Please restart Jalview",
873                 oom);
874         return;
875       }
876       gg = (Graphics2D) image.getGraphics();
877
878       if (av.antiAlias)
879       {
880         gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
881                 RenderingHints.VALUE_ANTIALIAS_ON);
882       }
883
884       gg.setFont(av.getFont());
885       fm = gg.getFontMetrics();
886       gg.setColor(Color.white);
887       gg.fillRect(0, 0, imgWidth, image.getHeight());
888       imageFresh = true;
889     }
890
891     drawComponent(gg, av.startRes, av.endRes + 1);
892     imageFresh = false;
893     g.drawImage(image, 0, 0, this);
894   }
895
896   /**
897    * set true to enable redraw timing debug output on stderr
898    */
899   private final boolean debugRedraw = false;
900
901   /**
902    * non-Thread safe repaint
903    * 
904    * @param horizontal
905    *          repaint with horizontal shift in alignment
906    */
907   public void fastPaint(int horizontal)
908   {
909     if ((horizontal == 0) || gg == null
910             || av.getAlignment().getAlignmentAnnotation() == null
911             || av.getAlignment().getAlignmentAnnotation().length < 1
912             || av.isCalcInProgress())
913     {
914       repaint();
915       return;
916     }
917     long stime = System.currentTimeMillis();
918     gg.copyArea(0, 0, imgWidth, getHeight(),
919             -horizontal * av.getCharWidth(), 0);
920     long mtime = System.currentTimeMillis();
921     int sr = av.startRes;
922     int er = av.endRes + 1;
923     int transX = 0;
924
925     if (horizontal > 0) // scrollbar pulled right, image to the left
926     {
927       transX = (er - sr - horizontal) * av.getCharWidth();
928       sr = er - horizontal;
929     }
930     else if (horizontal < 0)
931     {
932       er = sr - horizontal;
933     }
934
935     gg.translate(transX, 0);
936
937     drawComponent(gg, sr, er);
938
939     gg.translate(-transX, 0);
940     long dtime = System.currentTimeMillis();
941     fastPaint = true;
942     repaint();
943     long rtime = System.currentTimeMillis();
944     if (debugRedraw)
945     {
946       System.err.println("Scroll:\t" + horizontal + "\tCopyArea:\t"
947               + (mtime - stime) + "\tDraw component:\t" + (dtime - mtime)
948               + "\tRepaint call:\t" + (rtime - dtime));
949     }
950
951   }
952
953   private volatile boolean lastImageGood = false;
954
955   /**
956    * DOCUMENT ME!
957    * 
958    * @param g
959    *          DOCUMENT ME!
960    * @param startRes
961    *          DOCUMENT ME!
962    * @param endRes
963    *          DOCUMENT ME!
964    */
965   public void drawComponent(Graphics g, int startRes, int endRes)
966   {
967     BufferedImage oldFaded = fadedImage;
968     if (av.isCalcInProgress())
969     {
970       if (image == null)
971       {
972         lastImageGood = false;
973         return;
974       }
975       // We'll keep a record of the old image,
976       // and draw a faded image until the calculation
977       // has completed
978       if (lastImageGood
979               && (fadedImage == null || fadedImage.getWidth() != imgWidth || fadedImage
980                       .getHeight() != image.getHeight()))
981       {
982         // System.err.println("redraw faded image ("+(fadedImage==null ?
983         // "null image" : "") + " lastGood="+lastImageGood+")");
984         fadedImage = new BufferedImage(imgWidth, image.getHeight(),
985                 BufferedImage.TYPE_INT_RGB);
986
987         Graphics2D fadedG = (Graphics2D) fadedImage.getGraphics();
988
989         fadedG.setColor(Color.white);
990         fadedG.fillRect(0, 0, imgWidth, image.getHeight());
991
992         fadedG.setComposite(AlphaComposite.getInstance(
993                 AlphaComposite.SRC_OVER, .3f));
994         fadedG.drawImage(image, 0, 0, this);
995
996       }
997       // make sure we don't overwrite the last good faded image until all
998       // calculations have finished
999       lastImageGood = false;
1000
1001     }
1002     else
1003     {
1004       if (fadedImage != null)
1005       {
1006         oldFaded = fadedImage;
1007       }
1008       fadedImage = null;
1009     }
1010
1011     g.setColor(Color.white);
1012     g.fillRect(0, 0, (endRes - startRes) * av.getCharWidth(), getHeight());
1013
1014     g.setFont(av.getFont());
1015     if (fm == null)
1016     {
1017       fm = g.getFontMetrics();
1018     }
1019
1020     if ((av.getAlignment().getAlignmentAnnotation() == null)
1021             || (av.getAlignment().getAlignmentAnnotation().length < 1))
1022     {
1023       g.setColor(Color.white);
1024       g.fillRect(0, 0, getWidth(), getHeight());
1025       g.setColor(Color.black);
1026       if (av.validCharWidth)
1027       {
1028         g.drawString(MessageManager
1029                 .getString("label.alignment_has_no_annotations"), 20, 15);
1030       }
1031
1032       return;
1033     }
1034     lastImageGood = renderer.drawComponent(this, av, g, activeRow,
1035             startRes, endRes);
1036     if (!lastImageGood && fadedImage == null)
1037     {
1038       fadedImage = oldFaded;
1039     }
1040   }
1041
1042   @Override
1043   public FontMetrics getFontMetrics()
1044   {
1045     return fm;
1046   }
1047
1048   @Override
1049   public Image getFadedImage()
1050   {
1051     return fadedImage;
1052   }
1053
1054   @Override
1055   public int getFadedImageWidth()
1056   {
1057     return imgWidth;
1058   }
1059
1060   private int[] bounds = new int[2];
1061
1062   @Override
1063   public int[] getVisibleVRange()
1064   {
1065     if (ap != null && ap.getAlabels() != null)
1066     {
1067       int sOffset = -ap.getAlabels().getScrollOffset();
1068       int visHeight = sOffset + ap.annotationSpaceFillerHolder.getHeight();
1069       bounds[0] = sOffset;
1070       bounds[1] = visHeight;
1071       return bounds;
1072     }
1073     else
1074     {
1075       return null;
1076     }
1077   }
1078 }