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