JAL-1517 source formatting
[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()
429             * (av.getAlignment().getHeight() + (!av.hasHiddenRows() ? 0
430                     : av.getAlignment().getHiddenSequences().getSize()));
431     float sscaling = (float) (theight / (1.0 * theight + height));
432     float ascaling = (float) (height * 1.0 / alignFrame.getHeight());
433     int rheight = alignFrame.getHeight() - height - av.getCharHeight();
434     if (adjustPanelHeight)
435     {
436       // NOTE: this logic is different in the applet. Need a better algorithm to
437       // define behaviour
438       // try and set height according to alignment
439       if (ascaling > 0 && sscaling < 0.5)
440       {
441         // if the alignment is too big then
442         // default is 0.5 split
443         height = alignFrame.getHeight() / 2;
444       }
445       else
446       {
447         // if space for more than one sequence row left when annotation is fully
448         // displayed then set height to annotation height
449         // otherwise, leave at least two lines of sequence shown.
450         height = (rheight > av.getCharHeight()) ? height
451                 : (-av.getCharHeight() * 3 + (int) (alignFrame.getHeight() * (1 - sscaling)));
452       }
453     }
454     else
455     {
456       // maintain same window layout whilst updating sliders
457       height = annotationScroller.getSize().height;
458     }
459     hscroll.addNotify();
460
461     annotationScroller.setPreferredSize(new Dimension(annotationScroller
462             .getWidth(), height));
463
464     annotationSpaceFillerHolder.setPreferredSize(new Dimension(
465             annotationSpaceFillerHolder.getWidth(), height));
466     annotationScroller.validate();// repaint();
467     annotationScroller.addNotify();
468   }
469
470   /**
471    * DOCUMENT ME!
472    * 
473    * @param wrap
474    *          DOCUMENT ME!
475    */
476   public void setWrapAlignment(boolean wrap)
477   {
478     av.startSeq = 0;
479     scalePanelHolder.setVisible(!wrap);
480     hscroll.setVisible(!wrap);
481     idwidthAdjuster.setVisible(!wrap);
482
483     if (wrap)
484     {
485       annotationScroller.setVisible(false);
486       annotationSpaceFillerHolder.setVisible(false);
487     }
488     else if (av.showAnnotation)
489     {
490       annotationScroller.setVisible(true);
491       annotationSpaceFillerHolder.setVisible(true);
492     }
493
494     idSpaceFillerPanel1.setVisible(!wrap);
495
496     repaint();
497   }
498
499   // return value is true if the scroll is valid
500   public boolean scrollUp(boolean up)
501   {
502     if (up)
503     {
504       if (vscroll.getValue() < 1)
505       {
506         return false;
507       }
508
509       fastPaint = false;
510       vscroll.setValue(vscroll.getValue() - 1);
511     }
512     else
513     {
514       if ((vextent + vscroll.getValue()) >= av.getAlignment().getHeight())
515       {
516         return false;
517       }
518
519       fastPaint = false;
520       vscroll.setValue(vscroll.getValue() + 1);
521     }
522
523     fastPaint = true;
524
525     return true;
526   }
527
528   /**
529    * DOCUMENT ME!
530    * 
531    * @param right
532    *          DOCUMENT ME!
533    * 
534    * @return DOCUMENT ME!
535    */
536   public boolean scrollRight(boolean right)
537   {
538     if (!right)
539     {
540       if (hscroll.getValue() < 1)
541       {
542         return false;
543       }
544
545       fastPaint = false;
546       hscroll.setValue(hscroll.getValue() - 1);
547     }
548     else
549     {
550       if ((hextent + hscroll.getValue()) >= av.getAlignment().getWidth())
551       {
552         return false;
553       }
554
555       fastPaint = false;
556       hscroll.setValue(hscroll.getValue() + 1);
557     }
558
559     fastPaint = true;
560
561     return true;
562   }
563
564   /**
565    * Adjust row/column scrollers to show a visible position in the alignment.
566    * 
567    * @param x
568    *          visible column to scroll to DOCUMENT ME!
569    * @param y
570    *          visible row to scroll to
571    * 
572    */
573   public void setScrollValues(int x, int y)
574   {
575     // System.err.println("Scroll to "+x+","+y);
576     if (av == null || av.getAlignment() == null)
577     {
578       return;
579     }
580     int width = av.getAlignment().getWidth();
581     int height = av.getAlignment().getHeight();
582
583     if (av.hasHiddenColumns())
584     {
585       width = av.getColumnSelection().findColumnPosition(width);
586     }
587
588     av.setEndRes((x + (seqPanel.seqCanvas.getWidth() / av.charWidth)) - 1);
589
590     hextent = seqPanel.seqCanvas.getWidth() / av.charWidth;
591     vextent = seqPanel.seqCanvas.getHeight() / av.charHeight;
592
593     if (hextent > width)
594     {
595       hextent = width;
596     }
597
598     if (vextent > height)
599     {
600       vextent = height;
601     }
602
603     if ((hextent + x) > width)
604     {
605       x = width - hextent;
606     }
607
608     if ((vextent + y) > height)
609     {
610       y = height - vextent;
611     }
612
613     if (y < 0)
614     {
615       y = 0;
616     }
617
618     if (x < 0)
619     {
620       x = 0;
621     }
622
623     hscroll.setValues(x, hextent, 0, width);
624     vscroll.setValues(y, vextent, 0, height);
625   }
626
627   /**
628    * DOCUMENT ME!
629    * 
630    * @param evt
631    *          DOCUMENT ME!
632    */
633   public void adjustmentValueChanged(AdjustmentEvent evt)
634   {
635
636     int oldX = av.getStartRes();
637     int oldY = av.getStartSeq();
638
639     if (evt.getSource() == hscroll)
640     {
641       int x = hscroll.getValue();
642       av.setStartRes(x);
643       av.setEndRes((x + (seqPanel.seqCanvas.getWidth() / av.getCharWidth())) - 1);
644     }
645
646     if (evt.getSource() == vscroll)
647     {
648       int offy = vscroll.getValue();
649
650       if (av.getWrapAlignment())
651       {
652         if (offy > -1)
653         {
654           int rowSize = seqPanel.seqCanvas
655                   .getWrappedCanvasWidth(seqPanel.seqCanvas.getWidth());
656           av.setStartRes(offy * rowSize);
657           av.setEndRes((offy + 1) * rowSize);
658         }
659         else
660         {
661           // This is only called if file loaded is a jar file that
662           // was wrapped when saved and user has wrap alignment true
663           // as preference setting
664           SwingUtilities.invokeLater(new Runnable()
665           {
666             public void run()
667             {
668               setScrollValues(av.getStartRes(), av.getStartSeq());
669             }
670           });
671         }
672       }
673       else
674       {
675         av.setStartSeq(offy);
676         av.setEndSeq(offy
677                 + (seqPanel.seqCanvas.getHeight() / av.getCharHeight()));
678       }
679     }
680
681     if (overviewPanel != null)
682     {
683       overviewPanel.setBoxPosition();
684     }
685
686     int scrollX = av.startRes - oldX;
687     int scrollY = av.startSeq - oldY;
688
689     if (av.getWrapAlignment() || !fastPaint)
690     {
691       repaint();
692     }
693     else
694     {
695       // Make sure we're not trying to draw a panel
696       // larger than the visible window
697       if (scrollX > av.endRes - av.startRes)
698       {
699         scrollX = av.endRes - av.startRes;
700       }
701       else if (scrollX < av.startRes - av.endRes)
702       {
703         scrollX = av.startRes - av.endRes;
704       }
705
706       if (scrollX != 0 || scrollY != 0)
707       {
708         idPanel.idCanvas.fastPaint(scrollY);
709         seqPanel.seqCanvas.fastPaint(scrollX, scrollY);
710         scalePanel.repaint();
711
712         if (av.getShowAnnotation() && scrollX != 0)
713         {
714           annotationPanel.fastPaint(scrollX);
715         }
716       }
717     }
718   }
719
720   public void paintAlignment(boolean updateOverview)
721   {
722     repaint();
723
724     if (updateOverview)
725     {
726       av.getStructureSelectionManager().sequenceColoursChanged(this);
727
728       if (overviewPanel != null)
729       {
730         overviewPanel.updateOverviewImage();
731       }
732     }
733   }
734
735   /**
736    * DOCUMENT ME!
737    * 
738    * @param g
739    *          DOCUMENT ME!
740    */
741   public void paintComponent(Graphics g)
742   {
743     invalidate();
744
745     Dimension d = idPanel.idCanvas.getPreferredSize();
746     idPanelHolder.setPreferredSize(d);
747     hscrollFillerPanel.setPreferredSize(new Dimension(d.width, 12));
748     validate();
749
750     if (av.getWrapAlignment())
751     {
752       int maxwidth = av.getAlignment().getWidth();
753
754       if (av.hasHiddenColumns())
755       {
756         maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
757       }
758
759       int canvasWidth = seqPanel.seqCanvas
760               .getWrappedCanvasWidth(seqPanel.seqCanvas.getWidth());
761       if (canvasWidth > 0)
762       {
763         int max = maxwidth
764                 / seqPanel.seqCanvas
765                         .getWrappedCanvasWidth(seqPanel.seqCanvas
766                                 .getWidth()) + 1;
767         vscroll.setMaximum(max);
768         vscroll.setUnitIncrement(1);
769         vscroll.setVisibleAmount(1);
770       }
771     }
772     else
773     {
774       setScrollValues(av.getStartRes(), av.getStartSeq());
775     }
776   }
777
778   /**
779    * DOCUMENT ME!
780    * 
781    * @param pg
782    *          DOCUMENT ME!
783    * @param pf
784    *          DOCUMENT ME!
785    * @param pi
786    *          DOCUMENT ME!
787    * 
788    * @return DOCUMENT ME!
789    * 
790    * @throws PrinterException
791    *           DOCUMENT ME!
792    */
793   public int print(Graphics pg, PageFormat pf, int pi)
794           throws PrinterException
795   {
796     pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
797
798     int pwidth = (int) pf.getImageableWidth();
799     int pheight = (int) pf.getImageableHeight();
800
801     if (av.getWrapAlignment())
802     {
803       return printWrappedAlignment(pg, pwidth, pheight, pi);
804     }
805     else
806     {
807       return printUnwrapped(pg, pwidth, pheight, pi);
808     }
809   }
810
811   /**
812    * DOCUMENT ME!
813    * 
814    * @param pg
815    *          DOCUMENT ME!
816    * @param pwidth
817    *          DOCUMENT ME!
818    * @param pheight
819    *          DOCUMENT ME!
820    * @param pi
821    *          DOCUMENT ME!
822    * 
823    * @return DOCUMENT ME!
824    * 
825    * @throws PrinterException
826    *           DOCUMENT ME!
827    */
828   public int printUnwrapped(Graphics pg, int pwidth, int pheight, int pi)
829           throws PrinterException
830   {
831     int idWidth = getVisibleIdWidth(false);
832     FontMetrics fm = getFontMetrics(av.getFont());
833     int scaleHeight = av.charHeight + fm.getDescent();
834
835     pg.setColor(Color.white);
836     pg.fillRect(0, 0, pwidth, pheight);
837     pg.setFont(av.getFont());
838
839     // //////////////////////////////////
840     // / How many sequences and residues can we fit on a printable page?
841     int totalRes = (pwidth - idWidth) / av.getCharWidth();
842
843     int totalSeq = (int) ((pheight - scaleHeight) / av.getCharHeight()) - 1;
844
845     int pagesWide = (av.getAlignment().getWidth() / totalRes) + 1;
846
847     // ///////////////////////////
848     // / Only print these sequences and residues on this page
849     int startRes;
850
851     // ///////////////////////////
852     // / Only print these sequences and residues on this page
853     int endRes;
854
855     // ///////////////////////////
856     // / Only print these sequences and residues on this page
857     int startSeq;
858
859     // ///////////////////////////
860     // / Only print these sequences and residues on this page
861     int endSeq;
862     startRes = (pi % pagesWide) * totalRes;
863     endRes = (startRes + totalRes) - 1;
864
865     if (endRes > (av.getAlignment().getWidth() - 1))
866     {
867       endRes = av.getAlignment().getWidth() - 1;
868     }
869
870     startSeq = (pi / pagesWide) * totalSeq;
871     endSeq = startSeq + totalSeq;
872
873     if (endSeq > av.getAlignment().getHeight())
874     {
875       endSeq = av.getAlignment().getHeight();
876     }
877
878     int pagesHigh = ((av.getAlignment().getHeight() / totalSeq) + 1)
879             * pheight;
880
881     if (av.showAnnotation)
882     {
883       pagesHigh += annotationPanel.adjustPanelHeight() + 3;
884     }
885
886     pagesHigh /= pheight;
887
888     if (pi >= (pagesWide * pagesHigh))
889     {
890       return Printable.NO_SUCH_PAGE;
891     }
892
893     // draw Scale
894     pg.translate(idWidth, 0);
895     scalePanel.drawScale(pg, startRes, endRes, pwidth - idWidth,
896             scaleHeight);
897     pg.translate(-idWidth, scaleHeight);
898
899     // //////////////
900     // Draw the ids
901     Color currentColor = null;
902     Color currentTextColor = null;
903
904     pg.setFont(idPanel.idCanvas.idfont);
905
906     SequenceI seq;
907     for (int i = startSeq; i < endSeq; i++)
908     {
909       seq = av.getAlignment().getSequenceAt(i);
910       if ((av.getSelectionGroup() != null)
911               && av.getSelectionGroup().getSequences(null).contains(seq))
912       {
913         currentColor = Color.gray;
914         currentTextColor = Color.black;
915       }
916       else
917       {
918         currentColor = av.getSequenceColour(seq);
919         currentTextColor = Color.black;
920       }
921
922       pg.setColor(currentColor);
923       pg.fillRect(0, (i - startSeq) * av.charHeight, idWidth,
924               av.getCharHeight());
925
926       pg.setColor(currentTextColor);
927
928       int xPos = 0;
929       if (av.rightAlignIds)
930       {
931         fm = pg.getFontMetrics();
932         xPos = idWidth
933                 - fm.stringWidth(seq.getDisplayId(av.getShowJVSuffix()))
934                 - 4;
935       }
936
937       pg.drawString(
938               seq.getDisplayId(av.getShowJVSuffix()),
939               xPos,
940               (((i - startSeq) * av.charHeight) + av.getCharHeight())
941                       - (av.getCharHeight() / 5));
942     }
943
944     pg.setFont(av.getFont());
945
946     // draw main sequence panel
947     pg.translate(idWidth, 0);
948     seqPanel.seqCanvas.drawPanel(pg, startRes, endRes, startSeq, endSeq, 0);
949
950     if (av.showAnnotation && (endSeq == av.getAlignment().getHeight()))
951     {
952       // draw annotation - need to offset for current scroll position
953       int offset = -alabels.scrollOffset;
954       pg.translate(0, offset);
955       pg.translate(-idWidth - 3, (endSeq - startSeq) * av.charHeight + 3);
956       alabels.drawComponent((Graphics2D) pg, idWidth);
957       pg.translate(idWidth + 3, 0);
958       annotationPanel.renderer.drawComponent(annotationPanel, av,
959               (Graphics2D) pg, -1, startRes, endRes + 1);
960       pg.translate(0, -offset);
961     }
962
963     return Printable.PAGE_EXISTS;
964   }
965
966   /**
967    * DOCUMENT ME!
968    * 
969    * @param pg
970    *          DOCUMENT ME!
971    * @param pwidth
972    *          DOCUMENT ME!
973    * @param pheight
974    *          DOCUMENT ME!
975    * @param pi
976    *          DOCUMENT ME!
977    * 
978    * @return DOCUMENT ME!
979    * 
980    * @throws PrinterException
981    *           DOCUMENT ME!
982    */
983   public int printWrappedAlignment(Graphics pg, int pwidth, int pheight,
984           int pi) throws PrinterException
985   {
986
987     int annotationHeight = 0;
988     AnnotationLabels labels = null;
989     if (av.showAnnotation)
990     {
991       annotationHeight = annotationPanel.adjustPanelHeight();
992       labels = new AnnotationLabels(av);
993     }
994
995     int hgap = av.charHeight;
996     if (av.scaleAboveWrapped)
997     {
998       hgap += av.charHeight;
999     }
1000
1001     int cHeight = av.getAlignment().getHeight() * av.charHeight + hgap
1002             + annotationHeight;
1003
1004     int idWidth = getVisibleIdWidth(false);
1005
1006     int maxwidth = av.getAlignment().getWidth();
1007     if (av.hasHiddenColumns())
1008     {
1009       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
1010     }
1011
1012     int resWidth = seqPanel.seqCanvas.getWrappedCanvasWidth(pwidth
1013             - idWidth);
1014
1015     int totalHeight = cHeight * (maxwidth / resWidth + 1);
1016
1017     pg.setColor(Color.white);
1018     pg.fillRect(0, 0, pwidth, pheight);
1019     pg.setFont(av.getFont());
1020
1021     // //////////////
1022     // Draw the ids
1023     pg.setColor(Color.black);
1024
1025     pg.translate(0, -pi * pheight);
1026
1027     pg.setClip(0, pi * pheight, pwidth, pheight);
1028
1029     int ypos = hgap;
1030
1031     do
1032     {
1033       for (int i = 0; i < av.getAlignment().getHeight(); i++)
1034       {
1035         pg.setFont(idPanel.idCanvas.idfont);
1036         SequenceI s = av.getAlignment().getSequenceAt(i);
1037         String string = s.getDisplayId(av.getShowJVSuffix());
1038         int xPos = 0;
1039         if (av.rightAlignIds)
1040         {
1041           FontMetrics fm = pg.getFontMetrics();
1042           xPos = idWidth - fm.stringWidth(string) - 4;
1043         }
1044         pg.drawString(string, xPos,
1045                 ((i * av.charHeight) + ypos + av.charHeight)
1046                         - (av.charHeight / 5));
1047       }
1048       if (labels != null)
1049       {
1050         pg.translate(-3, ypos
1051                 + (av.getAlignment().getHeight() * av.charHeight));
1052
1053         pg.setFont(av.getFont());
1054         labels.drawComponent(pg, idWidth);
1055         pg.translate(+3, -ypos
1056                 - (av.getAlignment().getHeight() * av.charHeight));
1057       }
1058
1059       ypos += cHeight;
1060     } while (ypos < totalHeight);
1061
1062     pg.translate(idWidth, 0);
1063
1064     seqPanel.seqCanvas.drawWrappedPanel(pg, pwidth - idWidth, totalHeight,
1065             0);
1066
1067     if ((pi * pheight) < totalHeight)
1068     {
1069       return Printable.PAGE_EXISTS;
1070
1071     }
1072     else
1073     {
1074       return Printable.NO_SUCH_PAGE;
1075     }
1076   }
1077
1078   /**
1079    * get current sequence ID panel width, or nominal value if panel were to be
1080    * displayed using default settings
1081    * 
1082    * @return
1083    */
1084   int getVisibleIdWidth()
1085   {
1086     return getVisibleIdWidth(true);
1087   }
1088
1089   /**
1090    * get current sequence ID panel width, or nominal value if panel were to be
1091    * displayed using default settings
1092    * 
1093    * @param onscreen
1094    *          indicate if the Id width for onscreen or offscreen display should
1095    *          be returned
1096    * @return
1097    */
1098   int getVisibleIdWidth(boolean onscreen)
1099   {
1100     // see if rendering offscreen - check preferences and calc width accordingly
1101     if (!onscreen && Cache.getDefault("FIGURE_AUTOIDWIDTH", false))
1102     {
1103       return calculateIdWidth(-1).width + 4;
1104     }
1105     Integer idwidth = null;
1106     if (onscreen
1107             || (idwidth = Cache.getIntegerProperty("FIGURE_FIXEDIDWIDTH")) == null)
1108     {
1109       return (idPanel.getWidth() > 0 ? idPanel.getWidth()
1110               : calculateIdWidth().width + 4);
1111     }
1112     return idwidth.intValue() + 4;
1113   }
1114
1115   void makeAlignmentImage(int type, File file)
1116   {
1117     long progress = System.currentTimeMillis();
1118     boolean headless = (System.getProperty("java.awt.headless") != null && System
1119             .getProperty("java.awt.headless").equals("true"));
1120     if (alignFrame != null && !headless)
1121     {
1122       alignFrame.setProgressBar("Saving "
1123               + (type == jalview.util.ImageMaker.PNG ? "PNG image"
1124                       : "EPS file"), progress);
1125     }
1126     try
1127     {
1128       int maxwidth = av.getAlignment().getWidth();
1129       if (av.hasHiddenColumns())
1130       {
1131         maxwidth = av.getColumnSelection().findColumnPosition(maxwidth);
1132       }
1133
1134       int height = ((av.getAlignment().getHeight() + 1) * av.charHeight)
1135               + scalePanel.getHeight();
1136       int width = getVisibleIdWidth(false) + (maxwidth * av.charWidth);
1137
1138       if (av.getWrapAlignment())
1139       {
1140         height = getWrappedHeight();
1141         if (headless)
1142         {
1143           // need to obtain default alignment width and then add in any
1144           // additional allowance for id margin
1145           // this duplicates the calculation in getWrappedHeight but adjusts for
1146           // offscreen idWith
1147           width = alignFrame.getWidth() - vscroll.getPreferredSize().width
1148                   - alignFrame.getInsets().left
1149                   - alignFrame.getInsets().right - getVisibleIdWidth()
1150                   + getVisibleIdWidth(false);
1151         }
1152         else
1153         {
1154           width = seqPanel.getWidth() + getVisibleIdWidth(false);
1155         }
1156
1157       }
1158       else if (av.getShowAnnotation())
1159       {
1160         height += annotationPanel.adjustPanelHeight() + 3;
1161       }
1162
1163       try
1164       {
1165
1166         jalview.util.ImageMaker im;
1167         final String imageAction, imageTitle;
1168         if (type == jalview.util.ImageMaker.PNG)
1169         {
1170           imageAction = "Create PNG image from alignment";
1171           imageTitle = null;
1172         }
1173         else
1174         {
1175           imageAction = "Create EPS file from alignment";
1176           imageTitle = alignFrame.getTitle();
1177         }
1178         im = new jalview.util.ImageMaker(this, type, imageAction, width,
1179                 height, file, imageTitle);
1180         if (av.getWrapAlignment())
1181         {
1182           if (im.getGraphics() != null)
1183           {
1184             printWrappedAlignment(im.getGraphics(), width, height, 0);
1185             im.writeImage();
1186           }
1187         }
1188         else
1189         {
1190           if (im.getGraphics() != null)
1191           {
1192             printUnwrapped(im.getGraphics(), width, height, 0);
1193             im.writeImage();
1194           }
1195         }
1196       } catch (OutOfMemoryError err)
1197       {
1198         // Be noisy here.
1199         System.out.println("########################\n" + "OUT OF MEMORY "
1200                 + file + "\n" + "########################");
1201         new OOMWarning("Creating Image for " + file, err);
1202         // System.out.println("Create IMAGE: " + err);
1203       } catch (Exception ex)
1204       {
1205         ex.printStackTrace();
1206       }
1207     } finally
1208     {
1209       if (alignFrame != null && !headless)
1210       {
1211         alignFrame.setProgressBar("Export complete.", progress);
1212       }
1213     }
1214   }
1215
1216   /**
1217    * DOCUMENT ME!
1218    */
1219   public void makeEPS(File epsFile)
1220   {
1221     makeAlignmentImage(jalview.util.ImageMaker.EPS, epsFile);
1222   }
1223
1224   /**
1225    * DOCUMENT ME!
1226    */
1227   public void makePNG(File pngFile)
1228   {
1229     makeAlignmentImage(jalview.util.ImageMaker.PNG, pngFile);
1230   }
1231
1232   public void makePNGImageMap(File imgMapFile, String imageName)
1233   {
1234     // /////ONLY WORKS WITH NONE WRAPPED ALIGNMENTS
1235     // ////////////////////////////////////////////
1236     int idWidth = getVisibleIdWidth(false);
1237     FontMetrics fm = getFontMetrics(av.getFont());
1238     int scaleHeight = av.charHeight + fm.getDescent();
1239
1240     // Gen image map
1241     // ////////////////////////////////
1242     if (imgMapFile != null)
1243     {
1244       try
1245       {
1246         int s, sSize = av.getAlignment().getHeight(), res, alwidth = av
1247                 .getAlignment().getWidth(), g, gSize, f, fSize, sy;
1248         StringBuffer text = new StringBuffer();
1249         PrintWriter out = new PrintWriter(new FileWriter(imgMapFile));
1250         out.println(jalview.io.HTMLOutput.getImageMapHTML());
1251         out.println("<img src=\"" + imageName
1252                 + "\" border=\"0\" usemap=\"#Map\" >"
1253                 + "<map name=\"Map\">");
1254
1255         for (s = 0; s < sSize; s++)
1256         {
1257           sy = s * av.charHeight + scaleHeight;
1258
1259           SequenceI seq = av.getAlignment().getSequenceAt(s);
1260           SequenceFeature[] features = seq.getDatasetSequence()
1261                   .getSequenceFeatures();
1262           SequenceGroup[] groups = av.getAlignment().findAllGroups(seq);
1263           for (res = 0; res < alwidth; res++)
1264           {
1265             text = new StringBuffer();
1266             Object obj = null;
1267             if (av.getAlignment().isNucleotide())
1268             {
1269               obj = ResidueProperties.nucleotideName.get(seq.getCharAt(res)
1270                       + "");
1271             }
1272             else
1273             {
1274               obj = ResidueProperties.aa2Triplet.get(seq.getCharAt(res)
1275                       + "");
1276             }
1277
1278             if (obj == null)
1279             {
1280               continue;
1281             }
1282
1283             String triplet = obj.toString();
1284             int alIndex = seq.findPosition(res);
1285             gSize = groups.length;
1286             for (g = 0; g < gSize; g++)
1287             {
1288               if (text.length() < 1)
1289               {
1290                 text.append("<area shape=\"rect\" coords=\""
1291                         + (idWidth + res * av.charWidth) + "," + sy + ","
1292                         + (idWidth + (res + 1) * av.charWidth) + ","
1293                         + (av.charHeight + sy) + "\""
1294                         + " onMouseOver=\"toolTip('" + alIndex + " "
1295                         + triplet);
1296               }
1297
1298               if (groups[g].getStartRes() < res
1299                       && groups[g].getEndRes() > res)
1300               {
1301                 text.append("<br><em>" + groups[g].getName() + "</em>");
1302               }
1303             }
1304
1305             if (features != null)
1306             {
1307               if (text.length() < 1)
1308               {
1309                 text.append("<area shape=\"rect\" coords=\""
1310                         + (idWidth + res * av.charWidth) + "," + sy + ","
1311                         + (idWidth + (res + 1) * av.charWidth) + ","
1312                         + (av.charHeight + sy) + "\""
1313                         + " onMouseOver=\"toolTip('" + alIndex + " "
1314                         + triplet);
1315               }
1316               fSize = features.length;
1317               for (f = 0; f < fSize; f++)
1318               {
1319
1320                 if ((features[f].getBegin() <= seq.findPosition(res))
1321                         && (features[f].getEnd() >= seq.findPosition(res)))
1322                 {
1323                   if (features[f].getType().equals("disulfide bond"))
1324                   {
1325                     if (features[f].getBegin() == seq.findPosition(res)
1326                             || features[f].getEnd() == seq
1327                                     .findPosition(res))
1328                     {
1329                       text.append("<br>disulfide bond "
1330                               + features[f].getBegin() + ":"
1331                               + features[f].getEnd());
1332                     }
1333                   }
1334                   else
1335                   {
1336                     text.append("<br>");
1337                     text.append(features[f].getType());
1338                     if (features[f].getDescription() != null
1339                             && !features[f].getType().equals(
1340                                     features[f].getDescription()))
1341                     {
1342                       text.append(" " + features[f].getDescription());
1343                     }
1344
1345                     if (features[f].getValue("status") != null)
1346                     {
1347                       text.append(" (" + features[f].getValue("status")
1348                               + ")");
1349                     }
1350                   }
1351                 }
1352
1353               }
1354             }
1355             if (text.length() > 1)
1356             {
1357               text.append("')\"; onMouseOut=\"toolTip()\";  href=\"#\">");
1358               out.println(text.toString());
1359             }
1360           }
1361         }
1362         out.println("</map></body></html>");
1363         out.close();
1364
1365       } catch (Exception ex)
1366       {
1367         ex.printStackTrace();
1368       }
1369     } // /////////END OF IMAGE MAP
1370
1371   }
1372
1373   int getWrappedHeight()
1374   {
1375     int seqPanelWidth = seqPanel.seqCanvas.getWidth();
1376
1377     if (System.getProperty("java.awt.headless") != null
1378             && System.getProperty("java.awt.headless").equals("true"))
1379     {
1380       seqPanelWidth = alignFrame.getWidth() - getVisibleIdWidth()
1381               - vscroll.getPreferredSize().width
1382               - alignFrame.getInsets().left - alignFrame.getInsets().right;
1383     }
1384
1385     int chunkWidth = seqPanel.seqCanvas
1386             .getWrappedCanvasWidth(seqPanelWidth);
1387
1388     int hgap = av.charHeight;
1389     if (av.scaleAboveWrapped)
1390     {
1391       hgap += av.charHeight;
1392     }
1393
1394     int annotationHeight = 0;
1395     if (av.showAnnotation)
1396     {
1397       annotationHeight = annotationPanel.adjustPanelHeight();
1398     }
1399
1400     int cHeight = av.getAlignment().getHeight() * av.charHeight + hgap
1401             + annotationHeight;
1402
1403     int maxwidth = av.getAlignment().getWidth();
1404     if (av.hasHiddenColumns())
1405     {
1406       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
1407     }
1408
1409     int height = ((maxwidth / chunkWidth) + 1) * cHeight;
1410
1411     return height;
1412   }
1413
1414   /**
1415    * close the panel - deregisters all listeners and nulls any references to
1416    * alignment data.
1417    */
1418   public void closePanel()
1419   {
1420     PaintRefresher.RemoveComponent(seqPanel.seqCanvas);
1421     PaintRefresher.RemoveComponent(idPanel.idCanvas);
1422     PaintRefresher.RemoveComponent(this);
1423     if (av != null)
1424     {
1425       jalview.structure.StructureSelectionManager ssm = av
1426               .getStructureSelectionManager();
1427       ssm.removeStructureViewerListener(seqPanel, null);
1428       ssm.removeSelectionListener(seqPanel);
1429       av.setAlignment(null);
1430       av = null;
1431     }
1432     else
1433     {
1434       if (Cache.log.isDebugEnabled())
1435       {
1436         Cache.log.warn("Closing alignment panel which is already closed.");
1437       }
1438     }
1439   }
1440
1441   /**
1442    * hides or shows dynamic annotation rows based on groups and av state flags
1443    */
1444   public void updateAnnotation()
1445   {
1446     updateAnnotation(false, false);
1447   }
1448
1449   public void updateAnnotation(boolean applyGlobalSettings)
1450   {
1451     updateAnnotation(applyGlobalSettings, false);
1452   }
1453
1454   public void updateAnnotation(boolean applyGlobalSettings,
1455           boolean preserveNewGroupSettings)
1456   {
1457     av.updateGroupAnnotationSettings(applyGlobalSettings,
1458             preserveNewGroupSettings);
1459     adjustAnnotationHeight();
1460   }
1461
1462   @Override
1463   public AlignmentI getAlignment()
1464   {
1465     return av.getAlignment();
1466   }
1467
1468   /**
1469    * get the name for this view
1470    * 
1471    * @return
1472    */
1473   public String getViewName()
1474   {
1475     return av.viewName;
1476   }
1477
1478   /**
1479    * Make/Unmake this alignment panel the current input focus
1480    * 
1481    * @param b
1482    */
1483   public void setSelected(boolean b)
1484   {
1485     try
1486     {
1487       alignFrame.setSelected(b);
1488     } catch (Exception ex)
1489     {
1490     }
1491     ;
1492
1493     if (b)
1494     {
1495       alignFrame.setDisplayedView(this);
1496     }
1497   }
1498
1499   @Override
1500   public StructureSelectionManager getStructureSelectionManager()
1501   {
1502     return av.getStructureSelectionManager();
1503   }
1504
1505   @Override
1506   public void raiseOOMWarning(String string, OutOfMemoryError error)
1507   {
1508     new OOMWarning(string, error, this);
1509   }
1510
1511   public FeatureRenderer cloneFeatureRenderer()
1512   {
1513
1514     return new FeatureRenderer(this);
1515   }
1516
1517   public void updateFeatureRenderer(FeatureRenderer fr)
1518   {
1519     fr.transferSettings(seqPanel.seqCanvas.getFeatureRenderer());
1520   }
1521
1522   public void updateFeatureRendererFrom(FeatureRenderer fr)
1523   {
1524     if (seqPanel.seqCanvas.getFeatureRenderer() != null)
1525     {
1526       seqPanel.seqCanvas.getFeatureRenderer().transferSettings(fr);
1527     }
1528   }
1529 }