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