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