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