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