extend alignPanel api with OOMHandler call to raise OOM events appropriately in GUI
[jalview.git] / src / jalview / gui / AlignmentPanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
3  * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.gui;
19
20 import java.beans.*;
21 import java.io.*;
22 import java.util.Hashtable;
23 import java.util.Vector;
24
25 import java.awt.*;
26 import java.awt.event.*;
27 import java.awt.print.*;
28 import javax.swing.*;
29
30 import jalview.api.AlignmentViewPanel;
31 import jalview.bin.Cache;
32 import jalview.datamodel.*;
33 import jalview.jbgui.*;
34 import jalview.schemes.*;
35 import jalview.structure.StructureSelectionManager;
36
37 /**
38  * DOCUMENT ME!
39  * 
40  * @author $author$
41  * @version $Revision: 1.161 $
42  */
43 public class AlignmentPanel extends GAlignmentPanel implements
44         AdjustmentListener, Printable, AlignmentViewPanel
45 {
46   public AlignViewport av;
47
48   OverviewPanel overviewPanel;
49
50   SeqPanel seqPanel;
51
52   IdPanel idPanel;
53
54   IdwidthAdjuster idwidthAdjuster;
55
56   /** DOCUMENT ME!! */
57   public AlignFrame alignFrame;
58
59   ScalePanel scalePanel;
60
61   AnnotationPanel annotationPanel;
62
63   AnnotationLabels alabels;
64
65   // this value is set false when selection area being dragged
66   boolean fastPaint = true;
67
68   int hextent = 0;
69
70   int vextent = 0;
71
72   /**
73    * Creates a new AlignmentPanel object.
74    * 
75    * @param af
76    *          DOCUMENT ME!
77    * @param av
78    *          DOCUMENT ME!
79    */
80   public AlignmentPanel(AlignFrame af, final AlignViewport av)
81   {
82     alignFrame = af;
83     this.av = av;
84     seqPanel = new SeqPanel(av, this);
85     idPanel = new IdPanel(av, this);
86
87     scalePanel = new ScalePanel(av, this);
88
89     idPanelHolder.add(idPanel, BorderLayout.CENTER);
90     idwidthAdjuster = new IdwidthAdjuster(this);
91     idSpaceFillerPanel1.add(idwidthAdjuster, BorderLayout.CENTER);
92
93     annotationPanel = new AnnotationPanel(this);
94     alabels = new AnnotationLabels(this);
95
96     annotationScroller.setViewportView(annotationPanel);
97     annotationSpaceFillerHolder.add(alabels, BorderLayout.CENTER);
98
99     scalePanelHolder.add(scalePanel, BorderLayout.CENTER);
100     seqPanelHolder.add(seqPanel, BorderLayout.CENTER);
101
102     setScrollValues(0, 0);
103
104     setAnnotationVisible(av.getShowAnnotation());
105
106     hscroll.addAdjustmentListener(this);
107     vscroll.addAdjustmentListener(this);
108
109     final AlignmentPanel ap = this;
110     av.addPropertyChangeListener(new PropertyChangeListener()
111     {
112       public void propertyChange(PropertyChangeEvent evt)
113       {
114         if (evt.getPropertyName().equals("alignment"))
115         {
116           PaintRefresher.Refresh(ap, av.getSequenceSetId(), true, true);
117           alignmentChanged();
118         }
119       }
120     });
121     fontChanged();
122     adjustAnnotationHeight();
123
124   }
125
126   public void alignmentChanged()
127   {
128     av.alignmentChanged(this);
129
130     alignFrame.updateEditMenuBar();
131
132     paintAlignment(true);
133
134   }
135
136   /**
137    * DOCUMENT ME!
138    */
139   public void fontChanged()
140   {
141     // set idCanvas bufferedImage to null
142     // to prevent drawing old image
143     FontMetrics fm = getFontMetrics(av.getFont());
144
145     scalePanelHolder.setPreferredSize(new Dimension(10, av.charHeight
146             + fm.getDescent()));
147     idSpaceFillerPanel1.setPreferredSize(new Dimension(10, av.charHeight
148             + fm.getDescent()));
149
150     idPanel.idCanvas.gg = null;
151     seqPanel.seqCanvas.img = null;
152     annotationPanel.adjustPanelHeight();
153
154     Dimension d = calculateIdWidth();
155     d.setSize(d.width + 4, d.height);
156     idPanel.idCanvas.setPreferredSize(d);
157     hscrollFillerPanel.setPreferredSize(d);
158
159     if (overviewPanel != null)
160     {
161       overviewPanel.setBoxPosition();
162     }
163
164     repaint();
165   }
166
167   /**
168    * Calculate the width of the alignment labels based on the displayed names
169    * and any bounds on label width set in preferences.
170    * 
171    * @return Dimension giving the maximum width of the alignment label panel
172    *         that should be used.
173    */
174   public Dimension calculateIdWidth()
175   {
176     Container c = new Container();
177
178     FontMetrics fm = c.getFontMetrics(new Font(av.font.getName(),
179             Font.ITALIC, av.font.getSize()));
180
181     AlignmentI al = av.getAlignment();
182     int afwidth = (alignFrame != null ? alignFrame.getWidth() : 300);
183     int maxwidth = Math.max(20,
184             Math.min(afwidth - 200, (int) 2 * afwidth / 3));
185     int i = 0;
186     int idWidth = 0;
187     String id;
188
189     while ((i < al.getHeight()) && (al.getSequenceAt(i) != null))
190     {
191       SequenceI s = al.getSequenceAt(i);
192
193       id = s.getDisplayId(av.getShowJVSuffix());
194
195       if (fm.stringWidth(id) > idWidth)
196       {
197         idWidth = fm.stringWidth(id);
198       }
199
200       i++;
201     }
202
203     // Also check annotation label widths
204     i = 0;
205
206     if (al.getAlignmentAnnotation() != null)
207     {
208       fm = c.getFontMetrics(alabels.getFont());
209
210       while (i < al.getAlignmentAnnotation().length)
211       {
212         String label = al.getAlignmentAnnotation()[i].label;
213
214         if (fm.stringWidth(label) > idWidth)
215         {
216           idWidth = fm.stringWidth(label);
217         }
218
219         i++;
220       }
221     }
222
223     return new Dimension(Math.min(maxwidth, idWidth), 12);
224   }
225
226   /**
227    * Highlight the given results on the alignment.
228    * 
229    */
230   public void highlightSearchResults(SearchResults results)
231   {
232     scrollToPosition(results);
233     seqPanel.seqCanvas.highlightSearchResults(results);
234   }
235
236   /**
237    * scroll the view to show the position of the highlighted region in results
238    * (if any) and redraw the overview
239    * 
240    * @param results
241    */
242   public boolean scrollToPosition(SearchResults results)
243   {
244     return scrollToPosition(results, true);
245   }
246
247   /**
248    * scroll the view to show the position of the highlighted region in results
249    * (if any)
250    * 
251    * @param results
252    * @param redrawOverview
253    *          - when set, the overview will be recalculated (takes longer)
254    * @return false if results were not found
255    */
256   public boolean scrollToPosition(SearchResults results,
257           boolean redrawOverview)
258   {
259     int startv, endv, starts, ends, width;
260     // TODO: properly locate search results in view when large numbers of hidden
261     // columns exist before highlighted region
262     // do we need to scroll the panel?
263     // TODO: tons of nullpointereexceptions raised here.
264     if (results != null && results.getSize() > 0 && av != null
265             && av.alignment != null)
266     {
267       int seqIndex = av.alignment.findIndex(results);
268       if (seqIndex == -1)
269       {
270         return false;
271       }
272       SequenceI seq = av.alignment.getSequenceAt(seqIndex);
273       
274       int[] r=results.getResults(seq, 0, av.alignment.getWidth());
275       if (r == null)
276       {
277         return false;
278       }
279       int start = r[0];
280       int end = r[1];
281       // System.err.println("Seq : "+seqIndex+" Scroll to "+start+","+end); //
282       // DEBUG
283       if (start < 0)
284       {
285         return false;
286       }
287       if (end == seq.getEnd())
288       {
289         return false;
290       }
291       if (av.hasHiddenColumns)
292       {
293         start = av.getColumnSelection().findColumnPosition(start);
294         end = av.getColumnSelection().findColumnPosition(end);
295         if (start==end)
296         {
297           if (!av.colSel.isVisible(r[0]))
298           {
299             // don't scroll - position isn't visible
300             return false;
301           }
302         }
303       }
304       if (!av.wrapAlignment)
305       {
306         if ((startv = av.getStartRes()) >= start)
307         {
308           setScrollValues(start - 1, seqIndex);
309         }
310         else if ((endv = av.getEndRes()) <= end)
311         {
312           setScrollValues(startv + 1 + end - endv, seqIndex);
313         }
314         else if ((starts = av.getStartSeq()) > seqIndex)
315         {
316           setScrollValues(av.getStartRes(), seqIndex);
317         }
318         else if ((ends = av.getEndSeq()) <= seqIndex)
319         {
320           setScrollValues(av.getStartRes(), starts + seqIndex - ends + 1);
321         }
322       }
323       else
324       {
325         scrollToWrappedVisible(start);
326       }
327     }
328     if (redrawOverview && overviewPanel != null)
329     {
330       overviewPanel.setBoxPosition();
331     }
332     paintAlignment(redrawOverview);
333     return true;
334   }
335
336   void scrollToWrappedVisible(int res)
337   {
338     int cwidth = seqPanel.seqCanvas
339             .getWrappedCanvasWidth(seqPanel.seqCanvas.getWidth());
340     if (res < av.getStartRes() || res >= (av.getStartRes() + cwidth))
341     {
342       vscroll.setValue((res / cwidth));
343       av.startRes = vscroll.getValue() * cwidth;
344     }
345
346   }
347
348   /**
349    * DOCUMENT ME!
350    * 
351    * @return DOCUMENT ME!
352    */
353   public OverviewPanel getOverviewPanel()
354   {
355     return overviewPanel;
356   }
357
358   /**
359    * DOCUMENT ME!
360    * 
361    * @param op
362    *          DOCUMENT ME!
363    */
364   public void setOverviewPanel(OverviewPanel op)
365   {
366     overviewPanel = op;
367   }
368
369   /**
370    * 
371    * @param b Hide or show annotation panel
372    *          
373    */
374   public void setAnnotationVisible(boolean b)
375   {
376     if (!av.wrapAlignment)
377     {
378       annotationSpaceFillerHolder.setVisible(b);
379       annotationScroller.setVisible(b);
380     }
381     repaint();
382   }
383
384   /**
385    * automatically adjust annotation panel height for new annotation
386    * whilst ensuring the alignment is still visible.
387    */
388   public void adjustAnnotationHeight()
389   {
390     // TODO: display vertical annotation scrollbar if necessary
391     // this is called after loading new annotation onto alignment
392     if (alignFrame.getHeight() == 0)
393     {
394       System.out.println("NEEDS FIXING");
395     }
396     validateAnnotationDimensions(true);
397     addNotify();
398     paintAlignment(true);
399   }
400   /**
401    * calculate the annotation dimensions and refresh slider values accordingly.
402    * need to do repaints/notifys afterwards. 
403    */
404   protected void validateAnnotationDimensions(boolean adjustPanelHeight) {
405     int height = annotationPanel.adjustPanelHeight();
406     
407     if (hscroll.isVisible())
408     {
409       height += hscroll.getPreferredSize().height;
410     }
411     if (height > alignFrame.getHeight() / 2)
412     {
413       height = alignFrame.getHeight() / 2;
414     }
415     if (!adjustPanelHeight)
416     {
417       // maintain same window layout whilst updating sliders
418       height=annotationScroller.getSize().height;
419     }
420     hscroll.addNotify();
421     
422     annotationScroller.setPreferredSize(new Dimension(annotationScroller
423             .getWidth(), height));
424
425     annotationSpaceFillerHolder.setPreferredSize(new Dimension(
426             annotationSpaceFillerHolder.getWidth(), height));
427     annotationScroller.validate();// repaint();
428     annotationScroller.addNotify();
429   }
430
431   /**
432    * DOCUMENT ME!
433    * 
434    * @param wrap
435    *          DOCUMENT ME!
436    */
437   public void setWrapAlignment(boolean wrap)
438   {
439     av.startSeq = 0;
440     scalePanelHolder.setVisible(!wrap);
441     hscroll.setVisible(!wrap);
442     idwidthAdjuster.setVisible(!wrap);
443
444     if (wrap)
445     {
446       annotationScroller.setVisible(false);
447       annotationSpaceFillerHolder.setVisible(false);
448     }
449     else if (av.showAnnotation)
450     {
451       annotationScroller.setVisible(true);
452       annotationSpaceFillerHolder.setVisible(true);
453     }
454
455     idSpaceFillerPanel1.setVisible(!wrap);
456
457     repaint();
458   }
459
460   // return value is true if the scroll is valid
461   public boolean scrollUp(boolean up)
462   {
463     if (up)
464     {
465       if (vscroll.getValue() < 1)
466       {
467         return false;
468       }
469
470       fastPaint = false;
471       vscroll.setValue(vscroll.getValue() - 1);
472     }
473     else
474     {
475       if ((vextent + vscroll.getValue()) >= av.getAlignment().getHeight())
476       {
477         return false;
478       }
479
480       fastPaint = false;
481       vscroll.setValue(vscroll.getValue() + 1);
482     }
483
484     fastPaint = true;
485
486     return true;
487   }
488
489   /**
490    * DOCUMENT ME!
491    * 
492    * @param right
493    *          DOCUMENT ME!
494    * 
495    * @return DOCUMENT ME!
496    */
497   public boolean scrollRight(boolean right)
498   {
499     if (!right)
500     {
501       if (hscroll.getValue() < 1)
502       {
503         return false;
504       }
505
506       fastPaint = false;
507       hscroll.setValue(hscroll.getValue() - 1);
508     }
509     else
510     {
511       if ((hextent + hscroll.getValue()) >= av.getAlignment().getWidth())
512       {
513         return false;
514       }
515
516       fastPaint = false;
517       hscroll.setValue(hscroll.getValue() + 1);
518     }
519
520     fastPaint = true;
521
522     return true;
523   }
524
525   /**
526    * Adjust row/column scrollers to show a visible position in the alignment.
527    * 
528    * @param x visible column to scroll to
529    *          DOCUMENT ME!
530    * @param y visible row to scroll to
531    *          
532    */
533   public void setScrollValues(int x, int y)
534   {
535     // System.err.println("Scroll to "+x+","+y);
536     if (av == null || av.alignment == null)
537     {
538       return;
539     }
540     int width = av.alignment.getWidth();
541     int height = av.alignment.getHeight();
542
543     if (av.hasHiddenColumns)
544     {
545       width = av.getColumnSelection().findColumnPosition(width);
546     }
547
548     av.setEndRes((x + (seqPanel.seqCanvas.getWidth() / av.charWidth)) - 1);
549
550     hextent = seqPanel.seqCanvas.getWidth() / av.charWidth;
551     vextent = seqPanel.seqCanvas.getHeight() / av.charHeight;
552
553     if (hextent > width)
554     {
555       hextent = width;
556     }
557
558     if (vextent > height)
559     {
560       vextent = height;
561     }
562
563     if ((hextent + x) > width)
564     {
565       x = width - hextent;
566     }
567
568     if ((vextent + y) > height)
569     {
570       y = height - vextent;
571     }
572
573     if (y < 0)
574     {
575       y = 0;
576     }
577
578     if (x < 0)
579     {
580       x = 0;
581     }
582
583     hscroll.setValues(x, hextent, 0, width);
584     vscroll.setValues(y, vextent, 0, height);
585   }
586
587   /**
588    * DOCUMENT ME!
589    * 
590    * @param evt
591    *          DOCUMENT ME!
592    */
593   public void adjustmentValueChanged(AdjustmentEvent evt)
594   {
595
596     int oldX = av.getStartRes();
597     int oldY = av.getStartSeq();
598
599     if (evt.getSource() == hscroll)
600     {
601       int x = hscroll.getValue();
602       av.setStartRes(x);
603       av.setEndRes((x + (seqPanel.seqCanvas.getWidth() / av.getCharWidth())) - 1);
604     }
605
606     if (evt.getSource() == vscroll)
607     {
608       int offy = vscroll.getValue();
609
610       if (av.getWrapAlignment())
611       {
612         if (offy > -1)
613         {
614           int rowSize = seqPanel.seqCanvas
615                   .getWrappedCanvasWidth(seqPanel.seqCanvas.getWidth());
616           av.setStartRes(offy * rowSize);
617           av.setEndRes((offy + 1) * rowSize);
618         }
619         else
620         {
621           // This is only called if file loaded is a jar file that
622           // was wrapped when saved and user has wrap alignment true
623           // as preference setting
624           SwingUtilities.invokeLater(new Runnable()
625           {
626             public void run()
627             {
628               setScrollValues(av.getStartRes(), av.getStartSeq());
629             }
630           });
631         }
632       }
633       else
634       {
635         av.setStartSeq(offy);
636         av.setEndSeq(offy
637                 + (seqPanel.seqCanvas.getHeight() / av.getCharHeight()));
638       }
639     }
640
641     if (overviewPanel != null)
642     {
643       overviewPanel.setBoxPosition();
644     }
645
646     int scrollX = av.startRes - oldX;
647     int scrollY = av.startSeq - oldY;
648
649     if (av.getWrapAlignment() || !fastPaint)
650     {
651       repaint();
652     }
653     else
654     {
655       // Make sure we're not trying to draw a panel
656       // larger than the visible window
657       if (scrollX > av.endRes - av.startRes)
658       {
659         scrollX = av.endRes - av.startRes;
660       }
661       else if (scrollX < av.startRes - av.endRes)
662       {
663         scrollX = av.startRes - av.endRes;
664       }
665
666       if (scrollX != 0 || scrollY != 0)
667       {
668         idPanel.idCanvas.fastPaint(scrollY);
669         seqPanel.seqCanvas.fastPaint(scrollX, scrollY);
670         scalePanel.repaint();
671
672         if (av.getShowAnnotation())
673         {
674           annotationPanel.fastPaint(scrollX);
675         }
676       }
677     }
678   }
679
680   public void paintAlignment(boolean updateOverview)
681   {
682     repaint();
683
684     if (updateOverview)
685     {
686       av.getStructureSelectionManager().sequenceColoursChanged(this);
687
688       if (overviewPanel != null)
689       {
690         overviewPanel.updateOverviewImage();
691       }
692     }
693   }
694
695   /**
696    * DOCUMENT ME!
697    * 
698    * @param g
699    *          DOCUMENT ME!
700    */
701   public void paintComponent(Graphics g)
702   {
703     invalidate();
704
705     Dimension d = idPanel.idCanvas.getPreferredSize();
706     idPanelHolder.setPreferredSize(d);
707     hscrollFillerPanel.setPreferredSize(new Dimension(d.width, 12));
708     validate();
709
710     if (av.getWrapAlignment())
711     {
712       int maxwidth = av.alignment.getWidth();
713
714       if (av.hasHiddenColumns)
715       {
716         maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
717       }
718
719       int canvasWidth = seqPanel.seqCanvas
720               .getWrappedCanvasWidth(seqPanel.seqCanvas.getWidth());
721       if (canvasWidth > 0)
722       {
723         int max = maxwidth
724                 / seqPanel.seqCanvas
725                         .getWrappedCanvasWidth(seqPanel.seqCanvas
726                                 .getWidth()) + 1;
727         vscroll.setMaximum(max);
728         vscroll.setUnitIncrement(1);
729         vscroll.setVisibleAmount(1);
730       }
731     }
732     else
733     {
734       setScrollValues(av.getStartRes(), av.getStartSeq());
735     }
736   }
737
738   /**
739    * DOCUMENT ME!
740    * 
741    * @param pg
742    *          DOCUMENT ME!
743    * @param pf
744    *          DOCUMENT ME!
745    * @param pi
746    *          DOCUMENT ME!
747    * 
748    * @return DOCUMENT ME!
749    * 
750    * @throws PrinterException
751    *           DOCUMENT ME!
752    */
753   public int print(Graphics pg, PageFormat pf, int pi)
754           throws PrinterException
755   {
756     pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
757
758     int pwidth = (int) pf.getImageableWidth();
759     int pheight = (int) pf.getImageableHeight();
760
761     if (av.getWrapAlignment())
762     {
763       return printWrappedAlignment(pg, pwidth, pheight, pi);
764     }
765     else
766     {
767       return printUnwrapped(pg, pwidth, pheight, pi);
768     }
769   }
770
771   /**
772    * DOCUMENT ME!
773    * 
774    * @param pg
775    *          DOCUMENT ME!
776    * @param pwidth
777    *          DOCUMENT ME!
778    * @param pheight
779    *          DOCUMENT ME!
780    * @param pi
781    *          DOCUMENT ME!
782    * 
783    * @return DOCUMENT ME!
784    * 
785    * @throws PrinterException
786    *           DOCUMENT ME!
787    */
788   public int printUnwrapped(Graphics pg, int pwidth, int pheight, int pi)
789           throws PrinterException
790   {
791     int idWidth = getVisibleIdWidth();
792     FontMetrics fm = getFontMetrics(av.getFont());
793     int scaleHeight = av.charHeight + fm.getDescent();
794
795     pg.setColor(Color.white);
796     pg.fillRect(0, 0, pwidth, pheight);
797     pg.setFont(av.getFont());
798
799     // //////////////////////////////////
800     // / How many sequences and residues can we fit on a printable page?
801     int totalRes = (pwidth - idWidth) / av.getCharWidth();
802
803     int totalSeq = (int) ((pheight - scaleHeight) / av.getCharHeight()) - 1;
804
805     int pagesWide = (av.getAlignment().getWidth() / totalRes) + 1;
806
807     // ///////////////////////////
808     // / Only print these sequences and residues on this page
809     int startRes;
810
811     // ///////////////////////////
812     // / Only print these sequences and residues on this page
813     int endRes;
814
815     // ///////////////////////////
816     // / Only print these sequences and residues on this page
817     int startSeq;
818
819     // ///////////////////////////
820     // / Only print these sequences and residues on this page
821     int endSeq;
822     startRes = (pi % pagesWide) * totalRes;
823     endRes = (startRes + totalRes) - 1;
824
825     if (endRes > (av.getAlignment().getWidth() - 1))
826     {
827       endRes = av.getAlignment().getWidth() - 1;
828     }
829
830     startSeq = (pi / pagesWide) * totalSeq;
831     endSeq = startSeq + totalSeq;
832
833     if (endSeq > av.getAlignment().getHeight())
834     {
835       endSeq = av.getAlignment().getHeight();
836     }
837
838     int pagesHigh = ((av.alignment.getHeight() / totalSeq) + 1) * pheight;
839
840     if (av.showAnnotation)
841     {
842       pagesHigh += annotationPanel.adjustPanelHeight() + 3;
843     }
844
845     pagesHigh /= pheight;
846
847     if (pi >= (pagesWide * pagesHigh))
848     {
849       return Printable.NO_SUCH_PAGE;
850     }
851
852     // draw Scale
853     pg.translate(idWidth, 0);
854     scalePanel.drawScale(pg, startRes, endRes, pwidth - idWidth,
855             scaleHeight);
856     pg.translate(-idWidth, scaleHeight);
857
858     // //////////////
859     // Draw the ids
860     Color currentColor = null;
861     Color currentTextColor = null;
862
863     pg.setFont(idPanel.idCanvas.idfont);
864
865     SequenceI seq;
866     for (int i = startSeq; i < endSeq; i++)
867     {
868       seq = av.getAlignment().getSequenceAt(i);
869       if ((av.getSelectionGroup() != null)
870               && av.getSelectionGroup().getSequences(null).contains(seq))
871       {
872         currentColor = Color.gray;
873         currentTextColor = Color.black;
874       }
875       else
876       {
877         currentColor = av.getSequenceColour(seq);
878         currentTextColor = Color.black;
879       }
880
881       pg.setColor(currentColor);
882       pg.fillRect(0, (i - startSeq) * av.charHeight, idWidth,
883               av.getCharHeight());
884
885       pg.setColor(currentTextColor);
886
887       int xPos = 0;
888       if (av.rightAlignIds)
889       {
890         fm = pg.getFontMetrics();
891         xPos = idWidth
892                 - fm.stringWidth(seq.getDisplayId(av.getShowJVSuffix()))
893                 - 4;
894       }
895
896       pg.drawString(
897               seq.getDisplayId(av.getShowJVSuffix()),
898               xPos,
899               (((i - startSeq) * av.charHeight) + av.getCharHeight())
900                       - (av.getCharHeight() / 5));
901     }
902
903     pg.setFont(av.getFont());
904
905     // draw main sequence panel
906     pg.translate(idWidth, 0);
907     seqPanel.seqCanvas.drawPanel(pg, startRes, endRes, startSeq, endSeq, 0);
908
909     if (av.showAnnotation && (endSeq == av.alignment.getHeight()))
910     {
911       pg.translate(-idWidth - 3, (endSeq - startSeq) * av.charHeight + 3);
912       alabels.drawComponent((Graphics2D) pg, idWidth);
913       pg.translate(idWidth + 3, 0);
914       annotationPanel.renderer.drawComponent(annotationPanel, av, (Graphics2D) pg, -1, startRes, endRes + 1);
915     }
916
917     return Printable.PAGE_EXISTS;
918   }
919
920   /**
921    * DOCUMENT ME!
922    * 
923    * @param pg
924    *          DOCUMENT ME!
925    * @param pwidth
926    *          DOCUMENT ME!
927    * @param pheight
928    *          DOCUMENT ME!
929    * @param pi
930    *          DOCUMENT ME!
931    * 
932    * @return DOCUMENT ME!
933    * 
934    * @throws PrinterException
935    *           DOCUMENT ME!
936    */
937   public int printWrappedAlignment(Graphics pg, int pwidth, int pheight,
938           int pi) throws PrinterException
939   {
940
941     int annotationHeight = 0;
942     AnnotationLabels labels = null;
943     if (av.showAnnotation)
944     {
945       annotationHeight = annotationPanel.adjustPanelHeight();
946       labels = new AnnotationLabels(av);
947     }
948
949     int hgap = av.charHeight;
950     if (av.scaleAboveWrapped)
951     {
952       hgap += av.charHeight;
953     }
954
955     int cHeight = av.getAlignment().getHeight() * av.charHeight + hgap
956             + annotationHeight;
957
958     int idWidth = getVisibleIdWidth();
959
960     int maxwidth = av.alignment.getWidth();
961     if (av.hasHiddenColumns)
962     {
963       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
964     }
965
966     int resWidth = seqPanel.seqCanvas.getWrappedCanvasWidth(pwidth
967             - idWidth);
968
969     int totalHeight = cHeight * (maxwidth / resWidth + 1);
970
971     pg.setColor(Color.white);
972     pg.fillRect(0, 0, pwidth, pheight);
973     pg.setFont(av.getFont());
974
975     // //////////////
976     // Draw the ids
977     pg.setColor(Color.black);
978
979     pg.translate(0, -pi * pheight);
980
981     pg.setClip(0, pi * pheight, pwidth, pheight);
982
983     int ypos = hgap;
984
985     do
986     {
987       for (int i = 0; i < av.alignment.getHeight(); i++)
988       {
989         pg.setFont(idPanel.idCanvas.idfont);
990         SequenceI s = av.alignment.getSequenceAt(i);
991         String string = s.getDisplayId(av.getShowJVSuffix());
992         int xPos = 0;
993         if (av.rightAlignIds)
994         {
995           FontMetrics fm = pg.getFontMetrics();
996           xPos = idWidth - fm.stringWidth(string) - 4;
997         }
998         pg.drawString(string, xPos,
999                 ((i * av.charHeight) + ypos + av.charHeight)
1000                         - (av.charHeight / 5));
1001       }
1002       if (labels != null)
1003       {
1004         pg.translate(-3, ypos
1005                 + (av.getAlignment().getHeight() * av.charHeight));
1006
1007         pg.setFont(av.getFont());
1008         labels.drawComponent(pg, idWidth);
1009         pg.translate(+3, -ypos
1010                 - (av.getAlignment().getHeight() * av.charHeight));
1011       }
1012
1013       ypos += cHeight;
1014     } while (ypos < totalHeight);
1015
1016     pg.translate(idWidth, 0);
1017
1018     seqPanel.seqCanvas.drawWrappedPanel(pg, pwidth - idWidth, totalHeight,
1019             0);
1020
1021     if ((pi * pheight) < totalHeight)
1022     {
1023       return Printable.PAGE_EXISTS;
1024
1025     }
1026     else
1027     {
1028       return Printable.NO_SUCH_PAGE;
1029     }
1030   }
1031
1032   int getVisibleIdWidth()
1033   {
1034     return idPanel.getWidth() > 0 ? idPanel.getWidth()
1035             : calculateIdWidth().width + 4;
1036   }
1037
1038   void makeAlignmentImage(int type, File file)
1039   {
1040     int maxwidth = av.alignment.getWidth();
1041     if (av.hasHiddenColumns)
1042     {
1043       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth);
1044     }
1045
1046     int height = ((av.alignment.getHeight() + 1) * av.charHeight)
1047             + scalePanel.getHeight();
1048     int width = getVisibleIdWidth() + (maxwidth * av.charWidth);
1049
1050     if (av.getWrapAlignment())
1051     {
1052       height = getWrappedHeight();
1053       if (System.getProperty("java.awt.headless") != null
1054               && System.getProperty("java.awt.headless").equals("true"))
1055       {
1056         // TODO: JAL-244
1057         width = alignFrame.getWidth() - vscroll.getPreferredSize().width
1058                 - alignFrame.getInsets().left
1059                 - alignFrame.getInsets().right;
1060       }
1061       else
1062       {
1063         width = seqPanel.getWidth() + getVisibleIdWidth();
1064       }
1065
1066     }
1067     else if (av.getShowAnnotation())
1068     {
1069       height += annotationPanel.adjustPanelHeight() + 3;
1070     }
1071
1072     try
1073     {
1074
1075       jalview.util.ImageMaker im;
1076       if (type == jalview.util.ImageMaker.PNG)
1077       {
1078         im = new jalview.util.ImageMaker(this, jalview.util.ImageMaker.PNG,
1079                 "Create PNG image from alignment", width, height, file,
1080                 null);
1081       }
1082       else
1083       {
1084         im = new jalview.util.ImageMaker(this, jalview.util.ImageMaker.EPS,
1085                 "Create EPS file from alignment", width, height, file,
1086                 alignFrame.getTitle());
1087       }
1088
1089       if (av.getWrapAlignment())
1090       {
1091         if (im.getGraphics() != null)
1092         {
1093           printWrappedAlignment(im.getGraphics(), width, height, 0);
1094           im.writeImage();
1095         }
1096       }
1097       else
1098       {
1099         if (im.getGraphics() != null)
1100         {
1101           printUnwrapped(im.getGraphics(), width, height, 0);
1102           im.writeImage();
1103         }
1104       }
1105     } catch (OutOfMemoryError err)
1106     {
1107       // Be noisy here.
1108       System.out.println("########################\n" + "OUT OF MEMORY "
1109               + file + "\n" + "########################");
1110       new OOMWarning("Creating Image for " + file, err);
1111       // System.out.println("Create IMAGE: " + err);
1112     } catch (Exception ex)
1113     {
1114       ex.printStackTrace();
1115     }
1116   }
1117
1118   /**
1119    * DOCUMENT ME!
1120    */
1121   public void makeEPS(File epsFile)
1122   {
1123     makeAlignmentImage(jalview.util.ImageMaker.EPS, epsFile);
1124   }
1125
1126   /**
1127    * DOCUMENT ME!
1128    */
1129   public void makePNG(File pngFile)
1130   {
1131     makeAlignmentImage(jalview.util.ImageMaker.PNG, pngFile);
1132   }
1133
1134   public void makePNGImageMap(File imgMapFile, String imageName)
1135   {
1136     // /////ONLY WORKS WITH NONE WRAPPED ALIGNMENTS
1137     // ////////////////////////////////////////////
1138     int idWidth = getVisibleIdWidth();
1139     FontMetrics fm = getFontMetrics(av.getFont());
1140     int scaleHeight = av.charHeight + fm.getDescent();
1141
1142     // Gen image map
1143     // ////////////////////////////////
1144     if (imgMapFile != null)
1145     {
1146       try
1147       {
1148         int s, sSize = av.alignment.getHeight(), res, alwidth = av.alignment
1149                 .getWidth(), g, gSize, f, fSize, sy;
1150         StringBuffer text = new StringBuffer();
1151         PrintWriter out = new PrintWriter(new FileWriter(imgMapFile));
1152         out.println(jalview.io.HTMLOutput.getImageMapHTML());
1153         out.println("<img src=\"" + imageName
1154                 + "\" border=\"0\" usemap=\"#Map\" >"
1155                 + "<map name=\"Map\">");
1156
1157         for (s = 0; s < sSize; s++)
1158         {
1159           sy = s * av.charHeight + scaleHeight;
1160
1161           SequenceI seq = av.alignment.getSequenceAt(s);
1162           SequenceFeature[] features = seq.getDatasetSequence()
1163                   .getSequenceFeatures();
1164           SequenceGroup[] groups = av.alignment.findAllGroups(seq);
1165           for (res = 0; res < alwidth; res++)
1166           {
1167             text = new StringBuffer();
1168             Object obj = null;
1169             if (av.alignment.isNucleotide())
1170             {
1171               obj = ResidueProperties.nucleotideName.get(seq.getCharAt(res)
1172                       + "");
1173             }
1174             else
1175             {
1176               obj = ResidueProperties.aa2Triplet.get(seq.getCharAt(res)
1177                       + "");
1178             }
1179
1180             if (obj == null)
1181             {
1182               continue;
1183             }
1184
1185             String triplet = obj.toString();
1186             int alIndex = seq.findPosition(res);
1187             gSize = groups.length;
1188             for (g = 0; g < gSize; g++)
1189             {
1190               if (text.length() < 1)
1191               {
1192                 text.append("<area shape=\"rect\" coords=\""
1193                         + (idWidth + res * av.charWidth) + "," + sy + ","
1194                         + (idWidth + (res + 1) * av.charWidth) + ","
1195                         + (av.charHeight + sy) + "\""
1196                         + " onMouseOver=\"toolTip('" + alIndex + " "
1197                         + triplet);
1198               }
1199
1200               if (groups[g].getStartRes() < res
1201                       && groups[g].getEndRes() > res)
1202               {
1203                 text.append("<br><em>" + groups[g].getName() + "</em>");
1204               }
1205             }
1206
1207             if (features != null)
1208             {
1209               if (text.length() < 1)
1210               {
1211                 text.append("<area shape=\"rect\" coords=\""
1212                         + (idWidth + res * av.charWidth) + "," + sy + ","
1213                         + (idWidth + (res + 1) * av.charWidth) + ","
1214                         + (av.charHeight + sy) + "\""
1215                         + " onMouseOver=\"toolTip('" + alIndex + " "
1216                         + triplet);
1217               }
1218               fSize = features.length;
1219               for (f = 0; f < fSize; f++)
1220               {
1221
1222                 if ((features[f].getBegin() <= seq.findPosition(res))
1223                         && (features[f].getEnd() >= seq.findPosition(res)))
1224                 {
1225                   if (features[f].getType().equals("disulfide bond"))
1226                   {
1227                     if (features[f].getBegin() == seq.findPosition(res)
1228                             || features[f].getEnd() == seq
1229                                     .findPosition(res))
1230                     {
1231                       text.append("<br>disulfide bond "
1232                               + features[f].getBegin() + ":"
1233                               + features[f].getEnd());
1234                     }
1235                   }
1236                   else
1237                   {
1238                     text.append("<br>");
1239                     text.append(features[f].getType());
1240                     if (features[f].getDescription() != null
1241                             && !features[f].getType().equals(
1242                                     features[f].getDescription()))
1243                     {
1244                       text.append(" " + features[f].getDescription());
1245                     }
1246
1247                     if (features[f].getValue("status") != null)
1248                     {
1249                       text.append(" (" + features[f].getValue("status")
1250                               + ")");
1251                     }
1252                   }
1253                 }
1254
1255               }
1256             }
1257             if (text.length() > 1)
1258             {
1259               text.append("')\"; onMouseOut=\"toolTip()\";  href=\"#\">");
1260               out.println(text.toString());
1261             }
1262           }
1263         }
1264         out.println("</map></body></html>");
1265         out.close();
1266
1267       } catch (Exception ex)
1268       {
1269         ex.printStackTrace();
1270       }
1271     } // /////////END OF IMAGE MAP
1272
1273   }
1274
1275   int getWrappedHeight()
1276   {
1277     int seqPanelWidth = seqPanel.seqCanvas.getWidth();
1278
1279     if (System.getProperty("java.awt.headless") != null
1280             && System.getProperty("java.awt.headless").equals("true"))
1281     {
1282       seqPanelWidth = alignFrame.getWidth() - getVisibleIdWidth()
1283               - vscroll.getPreferredSize().width
1284               - alignFrame.getInsets().left - alignFrame.getInsets().right;
1285     }
1286
1287     int chunkWidth = seqPanel.seqCanvas
1288             .getWrappedCanvasWidth(seqPanelWidth);
1289
1290     int hgap = av.charHeight;
1291     if (av.scaleAboveWrapped)
1292     {
1293       hgap += av.charHeight;
1294     }
1295
1296     int annotationHeight = 0;
1297     if (av.showAnnotation)
1298     {
1299       annotationHeight = annotationPanel.adjustPanelHeight();
1300     }
1301
1302     int cHeight = av.getAlignment().getHeight() * av.charHeight + hgap
1303             + annotationHeight;
1304
1305     int maxwidth = av.alignment.getWidth();
1306     if (av.hasHiddenColumns)
1307     {
1308       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
1309     }
1310
1311     int height = ((maxwidth / chunkWidth) + 1) * cHeight;
1312
1313     return height;
1314   }
1315
1316   /**
1317    * close the panel - deregisters all listeners and nulls any references to
1318    * alignment data.
1319    */
1320   public void closePanel()
1321   {
1322     PaintRefresher.RemoveComponent(seqPanel.seqCanvas);
1323     PaintRefresher.RemoveComponent(idPanel.idCanvas);
1324     PaintRefresher.RemoveComponent(this);
1325     if (av != null)
1326     {
1327       jalview.structure.StructureSelectionManager ssm = av.getStructureSelectionManager();
1328       ssm.removeStructureViewerListener(seqPanel, null);
1329       ssm.removeSelectionListener(seqPanel);
1330       av.alignment = null;
1331       av = null;
1332     }
1333     else
1334     {
1335       if (Cache.log.isDebugEnabled())
1336       {
1337         Cache.log.warn("Closing alignment panel which is already closed.");
1338       }
1339     }
1340   }
1341
1342   /**
1343    * hides or shows dynamic annotation rows based on groups and av state flags
1344    */
1345   public void updateAnnotation()
1346   {
1347     updateAnnotation(false);
1348   }
1349
1350   public void updateAnnotation(boolean applyGlobalSettings)
1351   {
1352     // TODO: this should be merged with other annotation update stuff - that
1353     // sits on AlignViewport
1354     boolean updateCalcs = false;
1355     boolean conv = av.isShowGroupConservation();
1356     boolean cons = av.isShowGroupConsensus();
1357     boolean showprf = av.isShowSequenceLogo();
1358     boolean showConsHist = av.isShowConsensusHistogram();
1359     boolean normLogo = av.isNormaliseSequenceLogo();
1360
1361     boolean sortg = true;
1362
1363     // remove old automatic annotation
1364     // add any new annotation
1365
1366     Vector gr = av.alignment.getGroups(); // OrderedBy(av.alignment.getSequencesArray());
1367     // intersect alignment annotation with alignment groups
1368
1369     AlignmentAnnotation[] aan = av.alignment.getAlignmentAnnotation();
1370     Hashtable oldrfs = new Hashtable();
1371     if (aan != null)
1372     {
1373       for (int an = 0; an < aan.length; an++)
1374       {
1375         if (aan[an].autoCalculated && aan[an].groupRef != null)
1376         {
1377           oldrfs.put(aan[an].groupRef, aan[an].groupRef);
1378           av.alignment.deleteAnnotation(aan[an]);
1379           aan[an] = null;
1380         }
1381       }
1382     }
1383     SequenceGroup sg;
1384     if (gr != null)
1385     {
1386       for (int g = 0; g < gr.size(); g++)
1387       {
1388         updateCalcs = false;
1389         sg = (SequenceGroup) gr.elementAt(g);
1390         if (applyGlobalSettings || !oldrfs.containsKey(sg))
1391         {
1392           // set defaults for this group's conservation/consensus
1393           sg.setshowSequenceLogo(showprf);
1394           sg.setShowConsensusHistogram(showConsHist);
1395           sg.setNormaliseSequenceLogo(normLogo);
1396         }
1397         if (conv)
1398         {
1399           updateCalcs = true;
1400           av.alignment.addAnnotation(sg.getConservationRow(), 0);
1401         }
1402         if (cons)
1403         {
1404           updateCalcs = true;
1405           av.alignment.addAnnotation(sg.getConsensus(), 0);
1406         }
1407         // refresh the annotation rows
1408         if (updateCalcs)
1409         {
1410           sg.recalcConservation();
1411         }
1412       }
1413     }
1414     oldrfs.clear();
1415     adjustAnnotationHeight();
1416   }
1417
1418   @Override
1419   public AlignmentI getAlignment()
1420   {
1421     return av.alignment;
1422   }
1423
1424   /**
1425    * get the name for this view
1426    * @return 
1427    */
1428   public String getViewName()
1429   {
1430     return av.viewName;
1431   }
1432
1433   /**
1434    * Make/Unmake this alignment panel the current input focus
1435    * @param b
1436    */
1437   public void setSelected(boolean b)
1438   {
1439     try {
1440       alignFrame.setSelected(b);
1441       } catch (Exception ex) {};
1442       
1443     if (b)
1444     {
1445       alignFrame.setDisplayedView(this);
1446     } 
1447   }
1448
1449   @Override
1450   public StructureSelectionManager getStructureSelectionManager()
1451   {
1452     return av.getStructureSelectionManager();
1453   }
1454
1455   @Override
1456   public void raiseOOMWarning(String string, OutOfMemoryError error)
1457   {
1458     new OOMWarning(string,  error, this);
1459   }
1460 }