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