JAL-2728 limit divider to ensure seqs visible (wip!)
[jalview.git] / src / jalview / gui / AlignmentPanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ 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.AlignViewportI;
25 import jalview.api.AlignmentViewPanel;
26 import jalview.bin.Cache;
27 import jalview.datamodel.AlignmentI;
28 import jalview.datamodel.HiddenColumns;
29 import jalview.datamodel.SearchResultsI;
30 import jalview.datamodel.SequenceFeature;
31 import jalview.datamodel.SequenceGroup;
32 import jalview.datamodel.SequenceI;
33 import jalview.io.HTMLOutput;
34 import jalview.jbgui.GAlignmentPanel;
35 import jalview.math.AlignmentDimension;
36 import jalview.schemes.ResidueProperties;
37 import jalview.structure.StructureSelectionManager;
38 import jalview.util.Comparison;
39 import jalview.util.MessageManager;
40 import jalview.util.Platform;
41 import jalview.viewmodel.ViewportListenerI;
42 import jalview.viewmodel.ViewportRanges;
43
44 import java.awt.BorderLayout;
45 import java.awt.Color;
46 import java.awt.Container;
47 import java.awt.Dimension;
48 import java.awt.Font;
49 import java.awt.FontMetrics;
50 import java.awt.Graphics;
51 import java.awt.Insets;
52 import java.awt.event.AdjustmentEvent;
53 import java.awt.event.AdjustmentListener;
54 import java.awt.event.ComponentAdapter;
55 import java.awt.event.ComponentEvent;
56 import java.awt.print.PageFormat;
57 import java.awt.print.Printable;
58 import java.awt.print.PrinterException;
59 import java.beans.PropertyChangeEvent;
60 import java.beans.PropertyChangeListener;
61 import java.io.File;
62 import java.io.FileWriter;
63 import java.io.PrintWriter;
64 import java.util.List;
65
66 import javax.swing.SwingUtilities;
67
68 /**
69  * DOCUMENT ME!
70  * 
71  * @author $author$
72  * @version $Revision: 1.161 $
73  */
74 public class AlignmentPanel extends GAlignmentPanel implements
75         AdjustmentListener, Printable, AlignmentViewPanel, ViewportListenerI
76 {
77   public AlignViewport av;
78
79   ViewportRanges vpRanges;
80
81   OverviewPanel overviewPanel;
82
83   private SeqPanel seqPanel;
84
85   private IdPanel idPanel;
86
87   private boolean headless;
88
89   IdwidthAdjuster idwidthAdjuster;
90
91   /** DOCUMENT ME!! */
92   public AlignFrame alignFrame;
93
94   private ScalePanel scalePanel;
95
96   private AnnotationPanel annotationPanel;
97
98   private AnnotationLabels alabels;
99
100   // this value is set false when selection area being dragged
101   boolean fastPaint = true;
102
103   private int hextent = 0;
104
105   private int vextent = 0;
106
107   /*
108    * Flag set while scrolling to follow complementary cDNA/protein scroll. When
109    * false, suppresses invoking the same method recursively.
110    */
111   private boolean scrollComplementaryPanel = true;
112
113   private PropertyChangeListener propertyChangeListener;
114
115   private CalculationChooser calculationDialog;
116
117   /**
118    * Creates a new AlignmentPanel object.
119    * 
120    * @param af
121    * @param av
122    */
123   public AlignmentPanel(AlignFrame af, final AlignViewport av)
124   {
125     alignFrame = af;
126     this.av = av;
127     vpRanges = av.getRanges();
128     setSeqPanel(new SeqPanel(av, this));
129     setIdPanel(new IdPanel(av, this));
130
131     setScalePanel(new ScalePanel(av, this));
132
133     idPanelHolder.add(getIdPanel(), BorderLayout.CENTER);
134     idwidthAdjuster = new IdwidthAdjuster(this);
135     idSpaceFillerPanel1.add(idwidthAdjuster, BorderLayout.CENTER);
136
137     setAnnotationPanel(new AnnotationPanel(this));
138     setAlabels(new AnnotationLabels(this));
139
140     annotationScroller.setViewportView(getAnnotationPanel());
141     annotationSpaceFillerHolder.add(getAlabels(), BorderLayout.CENTER);
142
143     scalePanelHolder.add(getScalePanel(), BorderLayout.CENTER);
144     seqPanelHolder.add(getSeqPanel(), BorderLayout.CENTER);
145
146     setScrollValues(0, 0);
147
148     hscroll.addAdjustmentListener(this);
149     vscroll.addAdjustmentListener(this);
150
151     addComponentListener(new ComponentAdapter()
152     {
153       @Override
154       public void componentResized(ComponentEvent evt)
155       {
156         // reset the viewport ranges when the alignment panel is resized
157         // in particular, this initialises the end residue value when Jalview
158         // is initialised
159         if (av.getWrapAlignment())
160         {
161           int widthInRes = getSeqPanel().seqCanvas.getWrappedCanvasWidth(
162                   getSeqPanel().seqCanvas.getWidth());
163           vpRanges.setViewportWidth(widthInRes);
164         }
165         else
166         {
167           int widthInRes = getSeqPanel().seqCanvas.getWidth()
168                   / av.getCharWidth();
169           int heightInSeq = getSeqPanel().seqCanvas.getHeight()
170                   / av.getCharHeight();
171
172           vpRanges.setViewportWidth(widthInRes);
173           vpRanges.setViewportHeight(heightInSeq);
174         }
175       }
176
177     });
178
179     final AlignmentPanel ap = this;
180     propertyChangeListener = new PropertyChangeListener()
181     {
182       @Override
183       public void propertyChange(PropertyChangeEvent evt)
184       {
185         if (evt.getPropertyName().equals("alignment"))
186         {
187           PaintRefresher.Refresh(ap, av.getSequenceSetId(), true, true);
188           alignmentChanged();
189         }
190       }
191     };
192     av.addPropertyChangeListener(propertyChangeListener);
193
194     av.getRanges().addPropertyChangeListener(this);
195     fontChanged();
196     adjustAnnotationHeight();
197     updateLayout();
198   }
199
200   @Override
201   public AlignViewportI getAlignViewport()
202   {
203     return av;
204   }
205
206   public void alignmentChanged()
207   {
208     av.alignmentChanged(this);
209
210     if (getCalculationDialog() != null)
211     {
212       getCalculationDialog().validateCalcTypes();
213     }
214
215     alignFrame.updateEditMenuBar();
216
217     paintAlignment(true);
218
219   }
220
221   /**
222    * DOCUMENT ME!
223    */
224   public void fontChanged()
225   {
226     // set idCanvas bufferedImage to null
227     // to prevent drawing old image
228     FontMetrics fm = getFontMetrics(av.getFont());
229
230     scalePanelHolder.setPreferredSize(
231             new Dimension(10, av.getCharHeight() + fm.getDescent()));
232     idSpaceFillerPanel1.setPreferredSize(
233             new Dimension(10, av.getCharHeight() + fm.getDescent()));
234     idwidthAdjuster.invalidate();
235     scalePanelHolder.invalidate();
236     getIdPanel().getIdCanvas().gg = null;
237     getSeqPanel().seqCanvas.img = null;
238     getAnnotationPanel().adjustPanelHeight();
239
240     Dimension d = calculateIdWidth();
241
242     d.setSize(d.width + 4, d.height);
243     getIdPanel().getIdCanvas().setPreferredSize(d);
244     hscrollFillerPanel.setPreferredSize(d);
245
246     repaint();
247   }
248
249   /**
250    * Calculate the width of the alignment labels based on the displayed names
251    * and any bounds on label width set in preferences.
252    * 
253    * @return Dimension giving the maximum width of the alignment label panel
254    *         that should be used.
255    */
256   public Dimension calculateIdWidth()
257   {
258     // calculate sensible default width when no preference is available
259     Dimension r = null;
260     if (av.getIdWidth() < 0)
261     {
262       int afwidth = (alignFrame != null ? alignFrame.getWidth() : 300);
263       int maxwidth = Math.max(20, Math.min(afwidth - 200, 2 * afwidth / 3));
264       r = calculateIdWidth(maxwidth);
265       av.setIdWidth(r.width);
266     }
267     else
268     {
269       r = new Dimension();
270       r.width = av.getIdWidth();
271       r.height = 0;
272     }
273     return r;
274   }
275
276   /**
277    * Calculate the width of the alignment labels based on the displayed names
278    * and any bounds on label width set in preferences.
279    * 
280    * @param maxwidth
281    *          -1 or maximum width allowed for IdWidth
282    * @return Dimension giving the maximum width of the alignment label panel
283    *         that should be used.
284    */
285   public Dimension calculateIdWidth(int maxwidth)
286   {
287     Container c = new Container();
288
289     FontMetrics fm = c.getFontMetrics(
290             new Font(av.font.getName(), Font.ITALIC, av.font.getSize()));
291
292     AlignmentI al = av.getAlignment();
293     int i = 0;
294     int idWidth = 0;
295     String id;
296
297     while ((i < al.getHeight()) && (al.getSequenceAt(i) != null))
298     {
299       SequenceI s = al.getSequenceAt(i);
300
301       id = s.getDisplayId(av.getShowJVSuffix());
302
303       if (fm.stringWidth(id) > idWidth)
304       {
305         idWidth = fm.stringWidth(id);
306       }
307
308       i++;
309     }
310
311     // Also check annotation label widths
312     i = 0;
313
314     if (al.getAlignmentAnnotation() != null)
315     {
316       fm = c.getFontMetrics(getAlabels().getFont());
317
318       while (i < al.getAlignmentAnnotation().length)
319       {
320         String label = al.getAlignmentAnnotation()[i].label;
321
322         if (fm.stringWidth(label) > idWidth)
323         {
324           idWidth = fm.stringWidth(label);
325         }
326
327         i++;
328       }
329     }
330
331     return new Dimension(
332             maxwidth < 0 ? idWidth : Math.min(maxwidth, idWidth), 12);
333   }
334
335   /**
336    * Highlight the given results on the alignment.
337    * 
338    */
339   public void highlightSearchResults(SearchResultsI results)
340   {
341     boolean scrolled = scrollToPosition(results, 0, true, false);
342
343     boolean noFastPaint = scrolled && av.getWrapAlignment();
344
345     getSeqPanel().seqCanvas.highlightSearchResults(results, noFastPaint);
346   }
347
348   /**
349    * Scroll the view to show the position of the highlighted region in results
350    * (if any)
351    * 
352    * @param searchResults
353    * @param redrawOverview
354    * @return
355    */
356   public boolean scrollToPosition(SearchResultsI searchResults,
357           boolean redrawOverview)
358   {
359     return scrollToPosition(searchResults, 0, redrawOverview, false);
360   }
361
362   /**
363    * Scrolls the view (if necessary) to show the position of the first
364    * highlighted region in results (if any). Answers true if the view was
365    * scrolled, or false if no matched region was found, or it is already
366    * visible.
367    * 
368    * @param results
369    * @param verticalOffset
370    *          if greater than zero, allows scrolling to a position below the
371    *          first displayed sequence
372    * @param redrawOverview
373    *          - when set, the overview will be recalculated (takes longer)
374    * @param centre
375    *          if true, try to centre the search results horizontally in the view
376    * @return
377    */
378   protected boolean scrollToPosition(SearchResultsI results,
379           int verticalOffset, boolean redrawOverview, boolean centre)
380   {
381     int startv, endv, starts, ends;
382
383     if (results == null || results.isEmpty() || av == null
384             || av.getAlignment() == null)
385     {
386       return false;
387     }
388     int seqIndex = av.getAlignment().findIndex(results);
389     if (seqIndex == -1)
390     {
391       return false;
392     }
393     SequenceI seq = av.getAlignment().getSequenceAt(seqIndex);
394
395     int[] r = results.getResults(seq, 0, av.getAlignment().getWidth());
396     if (r == null)
397     {
398       return false;
399     }
400     int start = r[0];
401     int end = r[1];
402
403     /*
404      * To centre results, scroll to positions half the visible width
405      * left/right of the start/end positions
406      */
407     if (centre)
408     {
409       int offset = (vpRanges.getEndRes() - vpRanges.getStartRes() + 1) / 2 - 1;
410       start = Math.max(start - offset, 0);
411       end = end + offset - 1;
412     }
413     if (start < 0)
414     {
415       return false;
416     }
417     if (end == seq.getEnd())
418     {
419       return false;
420     }
421
422     if (av.hasHiddenColumns())
423     {
424       HiddenColumns hidden = av.getAlignment().getHiddenColumns();
425       start = hidden.findColumnPosition(start);
426       end = hidden.findColumnPosition(end);
427       if (start == end)
428       {
429         if (!hidden.isVisible(r[0]))
430         {
431           // don't scroll - position isn't visible
432           return false;
433         }
434       }
435     }
436
437     /*
438      * allow for offset of target sequence (actually scroll to one above it)
439      */
440     seqIndex = Math.max(0, seqIndex - verticalOffset);
441     boolean scrollNeeded = true;
442
443     if (!av.getWrapAlignment())
444     {
445       if ((startv = vpRanges.getStartRes()) >= start)
446       {
447         /*
448          * Scroll left to make start of search results visible
449          */
450         setScrollValues(start, seqIndex);
451       }
452       else if ((endv = vpRanges.getEndRes()) <= end)
453       {
454         /*
455          * Scroll right to make end of search results visible
456          */
457         setScrollValues(startv + end - endv, seqIndex);
458       }
459       else if ((starts = vpRanges.getStartSeq()) > seqIndex)
460       {
461         /*
462          * Scroll up to make start of search results visible
463          */
464         setScrollValues(vpRanges.getStartRes(), seqIndex);
465       }
466       else if ((ends = vpRanges.getEndSeq()) <= seqIndex)
467       {
468         /*
469          * Scroll down to make end of search results visible
470          */
471         setScrollValues(vpRanges.getStartRes(), starts + seqIndex - ends
472                 + 1);
473       }
474       /*
475        * Else results are already visible - no need to scroll
476        */
477       scrollNeeded = false;
478     }
479     else
480     {
481       scrollNeeded = vpRanges.scrollToWrappedVisible(start);
482     }
483
484     paintAlignment(redrawOverview);
485
486     return scrollNeeded;
487   }
488
489   /**
490    * DOCUMENT ME!
491    * 
492    * @return DOCUMENT ME!
493    */
494   public OverviewPanel getOverviewPanel()
495   {
496     return overviewPanel;
497   }
498
499   /**
500    * DOCUMENT ME!
501    * 
502    * @param op
503    *          DOCUMENT ME!
504    */
505   public void setOverviewPanel(OverviewPanel op)
506   {
507     overviewPanel = op;
508   }
509
510   /**
511    * 
512    * @param b
513    *          Hide or show annotation panel
514    * 
515    */
516   public void setAnnotationVisible(boolean b)
517   {
518     if (!av.getWrapAlignment())
519     {
520       annotationSpaceFillerHolder.setVisible(b);
521       annotationScroller.setVisible(b);
522     }
523     repaint();
524   }
525
526   /**
527    * automatically adjust annotation panel height for new annotation whilst
528    * ensuring the alignment is still visible.
529    */
530   @Override
531   public void adjustAnnotationHeight()
532   {
533     // TODO: display vertical annotation scrollbar if necessary
534     // this is called after loading new annotation onto alignment
535     if (alignFrame.getHeight() == 0)
536     {
537       System.out.println("NEEDS FIXING");
538     }
539     validateAnnotationDimensions(true);
540     addNotify();
541     paintAlignment(true);
542   }
543
544   /**
545    * calculate the annotation dimensions and refresh slider values accordingly.
546    * need to do repaints/notifys afterwards.
547    */
548   protected void validateAnnotationDimensions(boolean adjustPanelHeight)
549   {
550     int annotationHeight = getAnnotationPanel().adjustPanelHeight();
551
552     if (adjustPanelHeight)
553     {
554       int rowHeight = av.getCharHeight();
555       int alignmentHeight = rowHeight * av.getAlignment().getHeight();
556
557       /*
558        * Estimate available height in the AlignFrame for alignment +
559        * annotations. Deduct an estimate for title bar, menu bar, scale panel,
560        * hscroll, status bar (as these are not laid out we can't inspect their
561        * actual heights). Insets gives frame borders.
562        */
563       int stuff = Platform.isAMac() ? 80 : 100;
564       Insets insets = alignFrame.getInsets();
565       int availableHeight = alignFrame.getHeight() - stuff - insets.top
566               - insets.bottom;
567
568       /*
569        * If not enough vertical space, maximize annotation height while keeping
570        * at least two rows of alignment visible
571        */
572       if (annotationHeight + alignmentHeight > availableHeight)
573       {
574         annotationHeight = Math.min(annotationHeight,
575                 availableHeight - 2 * rowHeight);
576       }
577     }
578     else
579     {
580       // maintain same window layout whilst updating sliders
581       annotationHeight = annotationScroller.getSize().height;
582     }
583     hscroll.addNotify();
584
585     annotationScroller.setPreferredSize(
586             new Dimension(annotationScroller.getWidth(), annotationHeight));
587
588     Dimension e = idPanel.getSize();
589     alabels.setSize(new Dimension(e.width, annotationHeight));
590
591     annotationSpaceFillerHolder.setPreferredSize(new Dimension(
592             annotationSpaceFillerHolder.getWidth(), annotationHeight));
593     annotationScroller.validate();
594     annotationScroller.addNotify();
595   }
596
597   /**
598    * update alignment layout for viewport settings
599    * 
600    * @param wrap
601    *          DOCUMENT ME!
602    */
603   public void updateLayout()
604   {
605     fontChanged();
606     setAnnotationVisible(av.isShowAnnotation());
607     boolean wrap = av.getWrapAlignment();
608     vpRanges.setStartSeq(0);
609     scalePanelHolder.setVisible(!wrap);
610     hscroll.setVisible(!wrap);
611     idwidthAdjuster.setVisible(!wrap);
612
613     if (wrap)
614     {
615       annotationScroller.setVisible(false);
616       annotationSpaceFillerHolder.setVisible(false);
617     }
618     else if (av.isShowAnnotation())
619     {
620       annotationScroller.setVisible(true);
621       annotationSpaceFillerHolder.setVisible(true);
622     }
623
624     int canvasWidth = getSeqPanel().seqCanvas.getWidth();
625     if (canvasWidth > 0)
626     { // may not yet be laid out
627       if (wrap)
628       {
629         int widthInRes = getSeqPanel().seqCanvas
630                 .getWrappedCanvasWidth(canvasWidth);
631         vpRanges.setViewportWidth(widthInRes);
632       }
633       else
634       {
635         int widthInRes = (canvasWidth / av.getCharWidth()) - 1;
636         int heightInSeq = (getSeqPanel().seqCanvas.getHeight()
637                 / av.getCharHeight()) - 1;
638
639         vpRanges.setViewportWidth(widthInRes);
640         vpRanges.setViewportHeight(heightInSeq);
641       }
642     }
643
644     idSpaceFillerPanel1.setVisible(!wrap);
645
646     repaint();
647   }
648
649   /**
650    * Adjust row/column scrollers to show a visible position in the alignment.
651    * 
652    * @param x
653    *          visible column to scroll to
654    * @param y
655    *          visible row to scroll to
656    * 
657    */
658   public void setScrollValues(int xpos, int ypos)
659   {
660     int x = xpos;
661     int y = ypos;
662
663     if (av == null || av.getAlignment() == null)
664     {
665       return;
666     }
667
668     if (av.getWrapAlignment())
669     {
670       setScrollingForWrappedPanel(x);
671     }
672     else
673     {
674       int width = av.getAlignment().getWidth();
675       int height = av.getAlignment().getHeight();
676
677       if (av.hasHiddenColumns())
678       {
679         // reset the width to exclude hidden columns
680         width = av.getAlignment().getHiddenColumns()
681                 .findColumnPosition(width);
682       }
683
684       hextent = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
685       vextent = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight();
686
687       if (hextent > width)
688       {
689         hextent = width;
690       }
691
692       if (vextent > height)
693       {
694         vextent = height;
695       }
696
697       if ((hextent + x) > width)
698       {
699         x = width - hextent;
700       }
701
702       if ((vextent + y) > height)
703       {
704         y = height - vextent;
705       }
706
707       if (y < 0)
708       {
709         y = 0;
710       }
711
712       if (x < 0)
713       {
714         x = 0;
715       }
716
717       // update the scroll values
718       hscroll.setValues(x, hextent, 0, width);
719       vscroll.setValues(y, vextent, 0, height);
720     }
721   }
722
723   /**
724    * Respond to adjustment event when horizontal or vertical scrollbar is
725    * changed
726    * 
727    * @param evt
728    *          adjustment event encoding whether hscroll or vscroll changed
729    */
730   @Override
731   public void adjustmentValueChanged(AdjustmentEvent evt)
732   {
733     if (av.getWrapAlignment())
734     {
735       adjustScrollingWrapped(evt);
736       return;
737     }
738
739     if (evt.getSource() == hscroll)
740     {
741       int oldX = vpRanges.getStartRes();
742       int oldwidth = vpRanges.getViewportWidth();
743       int x = hscroll.getValue();
744       int width = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
745
746       // if we're scrolling to the position we're already at, stop
747       // this prevents infinite recursion of events when the scroll/viewport
748       // ranges values are the same
749       if ((x == oldX) && (width == oldwidth))
750       {
751         return;
752       }
753       vpRanges.setViewportStartAndWidth(x, width);
754     }
755     else if (evt.getSource() == vscroll)
756     {
757       int oldY = vpRanges.getStartSeq();
758       int oldheight = vpRanges.getViewportHeight();
759       int y = vscroll.getValue();
760       int height = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight();
761
762       // if we're scrolling to the position we're already at, stop
763       // this prevents infinite recursion of events when the scroll/viewport
764       // ranges values are the same
765       if ((y == oldY) && (height == oldheight))
766       {
767         return;
768       }
769       vpRanges.setViewportStartAndHeight(y, height);
770     }
771     if (!fastPaint)
772     {
773       repaint();
774     }
775   }
776
777   /**
778    * Responds to a scroll change by setting the start position of the viewport.
779    * Does
780    * 
781    * @param evt
782    */
783   protected void adjustScrollingWrapped(AdjustmentEvent evt)
784   {
785     if (evt.getSource() == hscroll)
786     {
787       return; // no horizontal scroll when wrapped
788     }
789     if (evt.getSource() == vscroll)
790     {
791       int newY = vscroll.getValue();
792
793       /*
794        * if we're scrolling to the position we're already at, stop
795        * this prevents infinite recursion of events when the scroll/viewport
796        * ranges values are the same
797        */
798       int oldX = vpRanges.getStartRes();
799       int oldY = vpRanges.getWrappedScrollPosition(oldX);
800       if (oldY == newY)
801       {
802         return;
803       }
804       if (newY > -1)
805       {
806         /*
807          * limit page up/down to one width's worth of positions
808          */
809         int rowSize = vpRanges.getViewportWidth();
810         int newX = newY > oldY ? oldX + rowSize : oldX - rowSize;
811         vpRanges.setViewportStartAndWidth(Math.max(0, newX), rowSize);
812       }
813     }
814     else
815     {
816       // This is only called if file loaded is a jar file that
817       // was wrapped when saved and user has wrap alignment true
818       // as preference setting
819       SwingUtilities.invokeLater(new Runnable()
820       {
821         @Override
822         public void run()
823         {
824           // When updating scrolling to use ViewportChange events, this code
825           // could not be validated and it is not clear if it is now being
826           // called. Log warning here in case it is called and unforeseen
827           // problems occur
828           Cache.log.warn(
829                   "Unexpected path through code: Wrapped jar file opened with wrap alignment set in preferences");
830
831           // scroll to start of panel
832           vpRanges.setStartRes(0);
833           vpRanges.setStartSeq(0);
834         }
835       });
836     }
837     repaint();
838   }
839
840   /**
841    * Repaint the alignment including the annotations and overview panels (if
842    * shown).
843    */
844   @Override
845   public void paintAlignment(boolean updateOverview)
846   {
847     final AnnotationSorter sorter = new AnnotationSorter(getAlignment(),
848             av.isShowAutocalculatedAbove());
849     sorter.sort(getAlignment().getAlignmentAnnotation(),
850             av.getSortAnnotationsBy());
851     repaint();
852
853     if (updateOverview)
854     {
855       // TODO: determine if this paintAlignment changed structure colours
856       av.getStructureSelectionManager().sequenceColoursChanged(this);
857
858       if (overviewPanel != null)
859       {
860         overviewPanel.updateOverviewImage();
861       }
862     }
863   }
864
865   /**
866    * DOCUMENT ME!
867    * 
868    * @param g
869    *          DOCUMENT ME!
870    */
871   @Override
872   public void paintComponent(Graphics g)
873   {
874     invalidate();
875
876     Dimension d = getIdPanel().getIdCanvas().getPreferredSize();
877     idPanelHolder.setPreferredSize(d);
878     hscrollFillerPanel.setPreferredSize(new Dimension(d.width, 12));
879     validate();
880
881     /*
882      * set scroll bar positions
883      */
884     setScrollValues(vpRanges.getStartRes(), vpRanges.getStartSeq());
885   }
886
887   /**
888    * Set vertical scroll bar position, and number of increments, for wrapped
889    * panel
890    * 
891    * @param topLeftColumn
892    *          the column position at top left (0..)
893    */
894   private void setScrollingForWrappedPanel(int topLeftColumn)
895   {
896     int scrollPosition = vpRanges.getWrappedScrollPosition(topLeftColumn);
897     int maxScroll = vpRanges.getWrappedMaxScroll(topLeftColumn);
898
899     /*
900      * a scrollbar's value can be set to at most (maximum-extent)
901      * so we add extent (1) to the maxScroll value
902      */
903     vscroll.setUnitIncrement(1);
904     vscroll.setValues(scrollPosition, 1, 0, maxScroll + 1);
905   }
906
907   /**
908    * DOCUMENT ME!
909    * 
910    * @param pg
911    *          DOCUMENT ME!
912    * @param pf
913    *          DOCUMENT ME!
914    * @param pi
915    *          DOCUMENT ME!
916    * 
917    * @return DOCUMENT ME!
918    * 
919    * @throws PrinterException
920    *           DOCUMENT ME!
921    */
922   @Override
923   public int print(Graphics pg, PageFormat pf, int pi)
924           throws PrinterException
925   {
926     pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
927
928     int pwidth = (int) pf.getImageableWidth();
929     int pheight = (int) pf.getImageableHeight();
930
931     if (av.getWrapAlignment())
932     {
933       return printWrappedAlignment(pwidth, pheight, pi, pg);
934     }
935     else
936     {
937       return printUnwrapped(pwidth, pheight, pi, pg, pg);
938     }
939   }
940
941   /**
942    * DOCUMENT ME!
943    * 
944    * @param pg
945    *          DOCUMENT ME!
946    * @param pwidth
947    *          DOCUMENT ME!
948    * @param pheight
949    *          DOCUMENT ME!
950    * @param pi
951    *          DOCUMENT ME!
952    * 
953    * @return DOCUMENT ME!
954    * 
955    * @throws PrinterException
956    *           DOCUMENT ME!
957    */
958   /**
959    * Draws the alignment image, including sequence ids, sequences, and
960    * annotation labels and annotations if shown, on either one or two Graphics
961    * context.
962    * 
963    * @param pageWidth
964    * @param pageHeight
965    * @param pi
966    * @param idGraphics
967    *          the graphics context for sequence ids and annotation labels
968    * @param alignmentGraphics
969    *          the graphics context for sequences and annotations (may or may not
970    *          be the same context as idGraphics)
971    * @return
972    * @throws PrinterException
973    */
974   public int printUnwrapped(int pageWidth, int pageHeight, int pi,
975           Graphics idGraphics, Graphics alignmentGraphics)
976           throws PrinterException
977   {
978     final int idWidth = getVisibleIdWidth(false);
979
980     /*
981      * Get the horizontal offset to where we draw the sequences.
982      * This is idWidth if using a single Graphics context, else zero.
983      */
984     final int alignmentGraphicsOffset = idGraphics != alignmentGraphics ? 0
985             : idWidth;
986
987     FontMetrics fm = getFontMetrics(av.getFont());
988     int charHeight = av.getCharHeight();
989     int scaleHeight = charHeight + fm.getDescent();
990
991     idGraphics.setColor(Color.white);
992     idGraphics.fillRect(0, 0, pageWidth, pageHeight);
993     idGraphics.setFont(av.getFont());
994
995     /*
996      * How many sequences and residues can we fit on a printable page?
997      */
998     int totalRes = (pageWidth - idWidth) / av.getCharWidth();
999
1000     int totalSeq = (pageHeight - scaleHeight) / charHeight - 1;
1001
1002     int alignmentWidth = av.getAlignment().getWidth();
1003     int pagesWide = (alignmentWidth / totalRes) + 1;
1004
1005     final int startRes = (pi % pagesWide) * totalRes;
1006     int endRes = (startRes + totalRes) - 1;
1007
1008     if (endRes > (alignmentWidth - 1))
1009     {
1010       endRes = alignmentWidth - 1;
1011     }
1012
1013     final int startSeq = (pi / pagesWide) * totalSeq;
1014     int endSeq = startSeq + totalSeq;
1015
1016     int alignmentHeight = av.getAlignment().getHeight();
1017     if (endSeq > alignmentHeight)
1018     {
1019       endSeq = alignmentHeight;
1020     }
1021
1022     int pagesHigh = ((alignmentHeight / totalSeq) + 1) * pageHeight;
1023
1024     if (av.isShowAnnotation())
1025     {
1026       pagesHigh += getAnnotationPanel().adjustPanelHeight() + 3;
1027     }
1028
1029     pagesHigh /= pageHeight;
1030
1031     if (pi >= (pagesWide * pagesHigh))
1032     {
1033       return Printable.NO_SUCH_PAGE;
1034     }
1035     final int alignmentDrawnHeight = (endSeq - startSeq) * charHeight + 3;
1036
1037     /*
1038      * draw the Scale at horizontal offset, then reset to top left (0, 0)
1039      */
1040     alignmentGraphics.translate(alignmentGraphicsOffset, 0);
1041     getScalePanel().drawScale(alignmentGraphics, startRes, endRes,
1042             pageWidth - idWidth, scaleHeight);
1043     alignmentGraphics.translate(-alignmentGraphicsOffset, 0);
1044
1045     /*
1046      * Draw the sequence ids, offset for scale height,
1047      * then reset to top left (0, 0)
1048      */
1049     idGraphics.translate(0, scaleHeight);
1050     idGraphics.setFont(getIdPanel().getIdCanvas().getIdfont());
1051     Color currentColor = null;
1052     Color currentTextColor = null;
1053
1054     SequenceI seq;
1055     for (int i = startSeq; i < endSeq; i++)
1056     {
1057       seq = av.getAlignment().getSequenceAt(i);
1058       if ((av.getSelectionGroup() != null)
1059               && av.getSelectionGroup().getSequences(null).contains(seq))
1060       {
1061         /*
1062          * gray out ids of sequences in selection group (if any)
1063          */
1064         currentColor = Color.gray;
1065         currentTextColor = Color.black;
1066       }
1067       else
1068       {
1069         currentColor = av.getSequenceColour(seq);
1070         currentTextColor = Color.black;
1071       }
1072
1073       idGraphics.setColor(currentColor);
1074       idGraphics.fillRect(0, (i - startSeq) * charHeight, idWidth,
1075               charHeight);
1076
1077       idGraphics.setColor(currentTextColor);
1078
1079       int xPos = 0;
1080       String displayId = seq.getDisplayId(av.getShowJVSuffix());
1081       if (av.isRightAlignIds())
1082       {
1083         fm = idGraphics.getFontMetrics();
1084         xPos = idWidth - fm.stringWidth(displayId) - 4;
1085       }
1086
1087       idGraphics.drawString(displayId, xPos,
1088               (((i - startSeq) * charHeight) + charHeight)
1089                       - (charHeight / 5));
1090     }
1091     idGraphics.setFont(av.getFont());
1092     idGraphics.translate(0, -scaleHeight);
1093
1094     /*
1095      * draw the sequences, offset for scale height, and id width (if using a
1096      * single graphics context), then reset to (0, scale height)
1097      */
1098     alignmentGraphics.translate(alignmentGraphicsOffset, scaleHeight);
1099     getSeqPanel().seqCanvas.drawPanelForPrinting(alignmentGraphics, startRes,
1100             endRes, startSeq, endSeq);
1101     alignmentGraphics.translate(-alignmentGraphicsOffset, 0);
1102
1103     if (av.isShowAnnotation() && (endSeq == alignmentHeight))
1104     {
1105       /*
1106        * draw annotation labels; drawComponent() translates by
1107        * getScrollOffset(), so compensate for that first;
1108        * then reset to (0, scale height)
1109        */
1110       int offset = getAlabels().getScrollOffset();
1111       idGraphics.translate(0, -offset);
1112       idGraphics.translate(0, alignmentDrawnHeight);
1113       getAlabels().drawComponent(idGraphics, idWidth);
1114       idGraphics.translate(0, -alignmentDrawnHeight);
1115
1116       /*
1117        * draw the annotations starting at 
1118        * (idOffset, alignmentHeight) from (0, scaleHeight)
1119        */
1120       alignmentGraphics.translate(alignmentGraphicsOffset,
1121               alignmentDrawnHeight);
1122       getAnnotationPanel().renderer.drawComponent(getAnnotationPanel(), av,
1123               alignmentGraphics, -1, startRes, endRes + 1);
1124     }
1125
1126     return Printable.PAGE_EXISTS;
1127   }
1128
1129   /**
1130    * DOCUMENT ME!
1131    * 
1132    * @param pg
1133    *          DOCUMENT ME!
1134    * @param pwidth
1135    *          DOCUMENT ME!
1136    * @param pheight
1137    *          DOCUMENT ME!
1138    * @param pi
1139    *          DOCUMENT ME!
1140    * 
1141    * @return DOCUMENT ME!
1142    * 
1143    * @throws PrinterException
1144    *           DOCUMENT ME!
1145    */
1146   public int printWrappedAlignment(int pwidth, int pheight, int pi,
1147           Graphics pg) throws PrinterException
1148   {
1149     int annotationHeight = 0;
1150     AnnotationLabels labels = null;
1151     if (av.isShowAnnotation())
1152     {
1153       annotationHeight = getAnnotationPanel().adjustPanelHeight();
1154       labels = new AnnotationLabels(av);
1155     }
1156
1157     int hgap = av.getCharHeight();
1158     if (av.getScaleAboveWrapped())
1159     {
1160       hgap += av.getCharHeight();
1161     }
1162
1163     int cHeight = av.getAlignment().getHeight() * av.getCharHeight() + hgap
1164             + annotationHeight;
1165
1166     int idWidth = getVisibleIdWidth(false);
1167
1168     int maxwidth = av.getAlignment().getWidth();
1169     if (av.hasHiddenColumns())
1170     {
1171       maxwidth = av.getAlignment().getHiddenColumns()
1172               .findColumnPosition(maxwidth) - 1;
1173     }
1174
1175     int resWidth = getSeqPanel().seqCanvas
1176             .getWrappedCanvasWidth(pwidth - idWidth);
1177
1178     int totalHeight = cHeight * (maxwidth / resWidth + 1);
1179
1180     pg.setColor(Color.white);
1181     pg.fillRect(0, 0, pwidth, pheight);
1182     pg.setFont(av.getFont());
1183
1184     // //////////////
1185     // Draw the ids
1186     pg.setColor(Color.black);
1187
1188     pg.translate(0, -pi * pheight);
1189
1190     pg.setClip(0, pi * pheight, pwidth, pheight);
1191
1192     int ypos = hgap;
1193
1194     do
1195     {
1196       for (int i = 0; i < av.getAlignment().getHeight(); i++)
1197       {
1198         pg.setFont(getIdPanel().getIdCanvas().getIdfont());
1199         SequenceI s = av.getAlignment().getSequenceAt(i);
1200         String string = s.getDisplayId(av.getShowJVSuffix());
1201         int xPos = 0;
1202         if (av.isRightAlignIds())
1203         {
1204           FontMetrics fm = pg.getFontMetrics();
1205           xPos = idWidth - fm.stringWidth(string) - 4;
1206         }
1207         pg.drawString(string, xPos,
1208                 ((i * av.getCharHeight()) + ypos + av.getCharHeight())
1209                         - (av.getCharHeight() / 5));
1210       }
1211       if (labels != null)
1212       {
1213         pg.translate(-3, ypos
1214                 + (av.getAlignment().getHeight() * av.getCharHeight()));
1215
1216         pg.setFont(av.getFont());
1217         labels.drawComponent(pg, idWidth);
1218         pg.translate(+3, -ypos
1219                 - (av.getAlignment().getHeight() * av.getCharHeight()));
1220       }
1221
1222       ypos += cHeight;
1223     } while (ypos < totalHeight);
1224
1225     pg.translate(idWidth, 0);
1226
1227     getSeqPanel().seqCanvas.drawWrappedPanelForPrinting(pg, pwidth - idWidth,
1228             totalHeight, 0);
1229
1230     if ((pi * pheight) < totalHeight)
1231     {
1232       return Printable.PAGE_EXISTS;
1233
1234     }
1235     else
1236     {
1237       return Printable.NO_SUCH_PAGE;
1238     }
1239   }
1240
1241   /**
1242    * get current sequence ID panel width, or nominal value if panel were to be
1243    * displayed using default settings
1244    * 
1245    * @return
1246    */
1247   public int getVisibleIdWidth()
1248   {
1249     return getVisibleIdWidth(true);
1250   }
1251
1252   /**
1253    * get current sequence ID panel width, or nominal value if panel were to be
1254    * displayed using default settings
1255    * 
1256    * @param onscreen
1257    *          indicate if the Id width for onscreen or offscreen display should
1258    *          be returned
1259    * @return
1260    */
1261   public int getVisibleIdWidth(boolean onscreen)
1262   {
1263     // see if rendering offscreen - check preferences and calc width accordingly
1264     if (!onscreen && Cache.getDefault("FIGURE_AUTOIDWIDTH", false))
1265     {
1266       return calculateIdWidth(-1).width + 4;
1267     }
1268     Integer idwidth = null;
1269     if (onscreen || (idwidth = Cache
1270             .getIntegerProperty("FIGURE_FIXEDIDWIDTH")) == null)
1271     {
1272       int w = getIdPanel().getWidth();
1273       return (w > 0 ? w : calculateIdWidth().width + 4);
1274     }
1275     return idwidth.intValue() + 4;
1276   }
1277
1278   void makeAlignmentImage(jalview.util.ImageMaker.TYPE type, File file)
1279   {
1280     int boarderBottomOffset = 5;
1281     long pSessionId = System.currentTimeMillis();
1282     headless = (System.getProperty("java.awt.headless") != null
1283             && System.getProperty("java.awt.headless").equals("true"));
1284     if (alignFrame != null && !headless)
1285     {
1286       if (file != null)
1287       {
1288         alignFrame.setProgressBar(MessageManager
1289                 .formatMessage("status.saving_file", new Object[]
1290                 { type.getLabel() }), pSessionId);
1291       }
1292     }
1293     try
1294     {
1295       AlignmentDimension aDimension = getAlignmentDimension();
1296       try
1297       {
1298         jalview.util.ImageMaker im;
1299         final String imageAction, imageTitle;
1300         if (type == jalview.util.ImageMaker.TYPE.PNG)
1301         {
1302           imageAction = "Create PNG image from alignment";
1303           imageTitle = null;
1304         }
1305         else if (type == jalview.util.ImageMaker.TYPE.EPS)
1306         {
1307           imageAction = "Create EPS file from alignment";
1308           imageTitle = alignFrame.getTitle();
1309         }
1310         else
1311         {
1312           imageAction = "Create SVG file from alignment";
1313           imageTitle = alignFrame.getTitle();
1314         }
1315
1316         im = new jalview.util.ImageMaker(this, type, imageAction,
1317                 aDimension.getWidth(),
1318                 aDimension.getHeight() + boarderBottomOffset, file,
1319                 imageTitle, alignFrame, pSessionId, headless);
1320         Graphics graphics = im.getGraphics();
1321         if (av.getWrapAlignment())
1322         {
1323           if (graphics != null)
1324           {
1325             printWrappedAlignment(aDimension.getWidth(),
1326                     aDimension.getHeight() + boarderBottomOffset, 0,
1327                     graphics);
1328             im.writeImage();
1329           }
1330         }
1331         else
1332         {
1333           if (graphics != null)
1334           {
1335             printUnwrapped(aDimension.getWidth(), aDimension.getHeight(), 0,
1336                     graphics, graphics);
1337             im.writeImage();
1338           }
1339         }
1340
1341       } catch (OutOfMemoryError err)
1342       {
1343         // Be noisy here.
1344         System.out.println("########################\n" + "OUT OF MEMORY "
1345                 + file + "\n" + "########################");
1346         new OOMWarning("Creating Image for " + file, err);
1347         // System.out.println("Create IMAGE: " + err);
1348       } catch (Exception ex)
1349       {
1350         ex.printStackTrace();
1351       }
1352     } finally
1353     {
1354
1355     }
1356   }
1357
1358   public AlignmentDimension getAlignmentDimension()
1359   {
1360     int maxwidth = av.getAlignment().getWidth();
1361     if (av.hasHiddenColumns())
1362     {
1363       maxwidth = av.getAlignment().getHiddenColumns()
1364               .findColumnPosition(maxwidth);
1365     }
1366
1367     int height = ((av.getAlignment().getHeight() + 1) * av.getCharHeight())
1368             + getScalePanel().getHeight();
1369     int width = getVisibleIdWidth(false) + (maxwidth * av.getCharWidth());
1370
1371     if (av.getWrapAlignment())
1372     {
1373       height = getWrappedHeight();
1374       if (headless)
1375       {
1376         // need to obtain default alignment width and then add in any
1377         // additional allowance for id margin
1378         // this duplicates the calculation in getWrappedHeight but adjusts for
1379         // offscreen idWith
1380         width = alignFrame.getWidth() - vscroll.getPreferredSize().width
1381                 - alignFrame.getInsets().left - alignFrame.getInsets().right
1382                 - getVisibleIdWidth() + getVisibleIdWidth(false);
1383       }
1384       else
1385       {
1386         width = getSeqPanel().getWidth() + getVisibleIdWidth(false);
1387       }
1388
1389     }
1390     else if (av.isShowAnnotation())
1391     {
1392       height += getAnnotationPanel().adjustPanelHeight() + 3;
1393     }
1394     return new AlignmentDimension(width, height);
1395
1396   }
1397
1398   /**
1399    * DOCUMENT ME!
1400    */
1401   public void makeEPS(File epsFile)
1402   {
1403     makeAlignmentImage(jalview.util.ImageMaker.TYPE.EPS, epsFile);
1404   }
1405
1406   /**
1407    * DOCUMENT ME!
1408    */
1409   public void makePNG(File pngFile)
1410   {
1411     makeAlignmentImage(jalview.util.ImageMaker.TYPE.PNG, pngFile);
1412   }
1413
1414   public void makeSVG(File svgFile)
1415   {
1416     makeAlignmentImage(jalview.util.ImageMaker.TYPE.SVG, svgFile);
1417   }
1418
1419   public void makePNGImageMap(File imgMapFile, String imageName)
1420   {
1421     // /////ONLY WORKS WITH NON WRAPPED ALIGNMENTS
1422     // ////////////////////////////////////////////
1423     int idWidth = getVisibleIdWidth(false);
1424     FontMetrics fm = getFontMetrics(av.getFont());
1425     int scaleHeight = av.getCharHeight() + fm.getDescent();
1426
1427     // Gen image map
1428     // ////////////////////////////////
1429     if (imgMapFile != null)
1430     {
1431       try
1432       {
1433         int sSize = av.getAlignment().getHeight();
1434         int alwidth = av.getAlignment().getWidth();
1435         PrintWriter out = new PrintWriter(new FileWriter(imgMapFile));
1436         out.println(HTMLOutput.getImageMapHTML());
1437         out.println("<img src=\"" + imageName
1438                 + "\" border=\"0\" usemap=\"#Map\" >"
1439                 + "<map name=\"Map\">");
1440
1441         for (int s = 0; s < sSize; s++)
1442         {
1443           int sy = s * av.getCharHeight() + scaleHeight;
1444
1445           SequenceI seq = av.getAlignment().getSequenceAt(s);
1446           SequenceGroup[] groups = av.getAlignment().findAllGroups(seq);
1447           for (int column = 0; column < alwidth; column++)
1448           {
1449             StringBuilder text = new StringBuilder(512);
1450             String triplet = null;
1451             if (av.getAlignment().isNucleotide())
1452             {
1453               triplet = ResidueProperties.nucleotideName.get(seq
1454                       .getCharAt(column) + "");
1455             }
1456             else
1457             {
1458               triplet = ResidueProperties.aa2Triplet.get(seq.getCharAt(column)
1459                       + "");
1460             }
1461
1462             if (triplet == null)
1463             {
1464               continue;
1465             }
1466
1467             int seqPos = seq.findPosition(column);
1468             int gSize = groups.length;
1469             for (int g = 0; g < gSize; g++)
1470             {
1471               if (text.length() < 1)
1472               {
1473                 text.append("<area shape=\"rect\" coords=\"")
1474                         .append((idWidth + column * av.getCharWidth()))
1475                         .append(",").append(sy).append(",")
1476                         .append((idWidth + (column + 1) * av.getCharWidth()))
1477                         .append(",").append((av.getCharHeight() + sy))
1478                         .append("\"").append(" onMouseOver=\"toolTip('")
1479                         .append(seqPos).append(" ").append(triplet);
1480               }
1481
1482               if (groups[g].getStartRes() < column
1483                       && groups[g].getEndRes() > column)
1484               {
1485                 text.append("<br><em>").append(groups[g].getName())
1486                         .append("</em>");
1487               }
1488             }
1489
1490             if (text.length() < 1)
1491             {
1492               text.append("<area shape=\"rect\" coords=\"")
1493                       .append((idWidth + column * av.getCharWidth()))
1494                       .append(",").append(sy).append(",")
1495                       .append((idWidth + (column + 1) * av.getCharWidth()))
1496                       .append(",").append((av.getCharHeight() + sy))
1497                       .append("\"").append(" onMouseOver=\"toolTip('")
1498                       .append(seqPos).append(" ").append(triplet);
1499             }
1500             if (!Comparison.isGap(seq.getCharAt(column)))
1501             {
1502               List<SequenceFeature> features = seq.findFeatures(column, column);
1503               for (SequenceFeature sf : features)
1504               {
1505                 if (sf.isContactFeature())
1506                 {
1507                   text.append("<br>").append(sf.getType()).append(" ")
1508                           .append(sf.getBegin()).append(":")
1509                           .append(sf.getEnd());
1510                 }
1511                 else
1512                 {
1513                   text.append("<br>");
1514                   text.append(sf.getType());
1515                   String description = sf.getDescription();
1516                   if (description != null
1517                           && !sf.getType().equals(description))
1518                   {
1519                     description = description.replace("\"", "&quot;");
1520                     text.append(" ").append(description);
1521                   }
1522                 }
1523                 String status = sf.getStatus();
1524                 if (status != null && !"".equals(status))
1525                 {
1526                   text.append(" (").append(status).append(")");
1527                 }
1528               }
1529               if (text.length() > 1)
1530               {
1531                 text.append("')\"; onMouseOut=\"toolTip()\";  href=\"#\">");
1532                 out.println(text.toString());
1533               }
1534             }
1535           }
1536         }
1537         out.println("</map></body></html>");
1538         out.close();
1539
1540       } catch (Exception ex)
1541       {
1542         ex.printStackTrace();
1543       }
1544     } // /////////END OF IMAGE MAP
1545
1546   }
1547
1548   int getWrappedHeight()
1549   {
1550     int seqPanelWidth = getSeqPanel().seqCanvas.getWidth();
1551
1552     if (System.getProperty("java.awt.headless") != null
1553             && System.getProperty("java.awt.headless").equals("true"))
1554     {
1555       seqPanelWidth = alignFrame.getWidth() - getVisibleIdWidth()
1556               - vscroll.getPreferredSize().width
1557               - alignFrame.getInsets().left - alignFrame.getInsets().right;
1558     }
1559
1560     int chunkWidth = getSeqPanel().seqCanvas
1561             .getWrappedCanvasWidth(seqPanelWidth);
1562
1563     int hgap = av.getCharHeight();
1564     if (av.getScaleAboveWrapped())
1565     {
1566       hgap += av.getCharHeight();
1567     }
1568
1569     int annotationHeight = 0;
1570     if (av.isShowAnnotation())
1571     {
1572       annotationHeight = getAnnotationPanel().adjustPanelHeight();
1573     }
1574
1575     int cHeight = av.getAlignment().getHeight() * av.getCharHeight() + hgap
1576             + annotationHeight;
1577
1578     int maxwidth = av.getAlignment().getWidth();
1579     if (av.hasHiddenColumns())
1580     {
1581       maxwidth = av.getAlignment().getHiddenColumns()
1582               .findColumnPosition(maxwidth) - 1;
1583     }
1584
1585     int height = ((maxwidth / chunkWidth) + 1) * cHeight;
1586
1587     return height;
1588   }
1589
1590   /**
1591    * close the panel - deregisters all listeners and nulls any references to
1592    * alignment data.
1593    */
1594   public void closePanel()
1595   {
1596     PaintRefresher.RemoveComponent(getSeqPanel().seqCanvas);
1597     PaintRefresher.RemoveComponent(getIdPanel().getIdCanvas());
1598     PaintRefresher.RemoveComponent(this);
1599
1600     closeChildFrames();
1601
1602     /*
1603      * try to ensure references are nulled
1604      */
1605     if (annotationPanel != null)
1606     {
1607       annotationPanel.dispose();
1608     }
1609
1610     if (av != null)
1611     {
1612       av.removePropertyChangeListener(propertyChangeListener);
1613       jalview.structure.StructureSelectionManager ssm = av
1614               .getStructureSelectionManager();
1615       ssm.removeStructureViewerListener(getSeqPanel(), null);
1616       ssm.removeSelectionListener(getSeqPanel());
1617       ssm.removeCommandListener(av);
1618       ssm.removeStructureViewerListener(getSeqPanel(), null);
1619       ssm.removeSelectionListener(getSeqPanel());
1620       av.dispose();
1621       av = null;
1622     }
1623     else
1624     {
1625       if (Cache.log.isDebugEnabled())
1626       {
1627         Cache.log.warn("Closing alignment panel which is already closed.");
1628       }
1629     }
1630   }
1631
1632   /**
1633    * Close any open dialogs that would be orphaned when this one is closed
1634    */
1635   protected void closeChildFrames()
1636   {
1637     if (calculationDialog != null)
1638     {
1639       calculationDialog.closeFrame();
1640     }
1641   }
1642
1643   /**
1644    * hides or shows dynamic annotation rows based on groups and av state flags
1645    */
1646   public void updateAnnotation()
1647   {
1648     updateAnnotation(false, false);
1649   }
1650
1651   public void updateAnnotation(boolean applyGlobalSettings)
1652   {
1653     updateAnnotation(applyGlobalSettings, false);
1654   }
1655
1656   public void updateAnnotation(boolean applyGlobalSettings,
1657           boolean preserveNewGroupSettings)
1658   {
1659     av.updateGroupAnnotationSettings(applyGlobalSettings,
1660             preserveNewGroupSettings);
1661     adjustAnnotationHeight();
1662   }
1663
1664   @Override
1665   public AlignmentI getAlignment()
1666   {
1667     return av == null ? null : av.getAlignment();
1668   }
1669
1670   @Override
1671   public String getViewName()
1672   {
1673     return av.viewName;
1674   }
1675
1676   /**
1677    * Make/Unmake this alignment panel the current input focus
1678    * 
1679    * @param b
1680    */
1681   public void setSelected(boolean b)
1682   {
1683     try
1684     {
1685       if (alignFrame.getSplitViewContainer() != null)
1686       {
1687         /*
1688          * bring enclosing SplitFrame to front first if there is one
1689          */
1690         ((SplitFrame) alignFrame.getSplitViewContainer()).setSelected(b);
1691       }
1692       alignFrame.setSelected(b);
1693     } catch (Exception ex)
1694     {
1695     }
1696
1697     if (b)
1698     {
1699       alignFrame.setDisplayedView(this);
1700     }
1701   }
1702
1703   @Override
1704   public StructureSelectionManager getStructureSelectionManager()
1705   {
1706     return av.getStructureSelectionManager();
1707   }
1708
1709   @Override
1710   public void raiseOOMWarning(String string, OutOfMemoryError error)
1711   {
1712     new OOMWarning(string, error, this);
1713   }
1714
1715   @Override
1716   public jalview.api.FeatureRenderer cloneFeatureRenderer()
1717   {
1718
1719     return new FeatureRenderer(this);
1720   }
1721
1722   @Override
1723   public jalview.api.FeatureRenderer getFeatureRenderer()
1724   {
1725     return seqPanel.seqCanvas.getFeatureRenderer();
1726   }
1727
1728   public void updateFeatureRenderer(
1729           jalview.renderer.seqfeatures.FeatureRenderer fr)
1730   {
1731     fr.transferSettings(getSeqPanel().seqCanvas.getFeatureRenderer());
1732   }
1733
1734   public void updateFeatureRendererFrom(jalview.api.FeatureRenderer fr)
1735   {
1736     if (getSeqPanel().seqCanvas.getFeatureRenderer() != null)
1737     {
1738       getSeqPanel().seqCanvas.getFeatureRenderer().transferSettings(fr);
1739     }
1740   }
1741
1742   public ScalePanel getScalePanel()
1743   {
1744     return scalePanel;
1745   }
1746
1747   public void setScalePanel(ScalePanel scalePanel)
1748   {
1749     this.scalePanel = scalePanel;
1750   }
1751
1752   public SeqPanel getSeqPanel()
1753   {
1754     return seqPanel;
1755   }
1756
1757   public void setSeqPanel(SeqPanel seqPanel)
1758   {
1759     this.seqPanel = seqPanel;
1760   }
1761
1762   public AnnotationPanel getAnnotationPanel()
1763   {
1764     return annotationPanel;
1765   }
1766
1767   public void setAnnotationPanel(AnnotationPanel annotationPanel)
1768   {
1769     this.annotationPanel = annotationPanel;
1770   }
1771
1772   public AnnotationLabels getAlabels()
1773   {
1774     return alabels;
1775   }
1776
1777   public void setAlabels(AnnotationLabels alabels)
1778   {
1779     this.alabels = alabels;
1780   }
1781
1782   public IdPanel getIdPanel()
1783   {
1784     return idPanel;
1785   }
1786
1787   public void setIdPanel(IdPanel idPanel)
1788   {
1789     this.idPanel = idPanel;
1790   }
1791
1792   /**
1793    * Follow a scrolling change in the (cDNA/Protein) complementary alignment.
1794    * The aim is to keep the two alignments 'lined up' on their centre columns.
1795    * 
1796    * @param sr
1797    *          holds mapped region(s) of this alignment that we are scrolling
1798    *          'to'; may be modified for sequence offset by this method
1799    * @param verticalOffset
1800    *          the number of visible sequences to show above the mapped region
1801    */
1802   protected void scrollToCentre(SearchResultsI sr, int verticalOffset)
1803   {
1804     /*
1805      * To avoid jumpy vertical scrolling (if some sequences are gapped or not
1806      * mapped), we can make the scroll-to location a sequence above the one
1807      * actually mapped.
1808      */
1809     SequenceI mappedTo = sr.getResults().get(0).getSequence();
1810     List<SequenceI> seqs = av.getAlignment().getSequences();
1811
1812     /*
1813      * This is like AlignmentI.findIndex(seq) but here we are matching the
1814      * dataset sequence not the aligned sequence
1815      */
1816     boolean matched = false;
1817     for (SequenceI seq : seqs)
1818     {
1819       if (mappedTo == seq.getDatasetSequence())
1820       {
1821         matched = true;
1822         break;
1823       }
1824     }
1825     if (!matched)
1826     {
1827       return; // failsafe, shouldn't happen
1828     }
1829
1830     /*
1831      * Scroll to position but centring the target residue.
1832      */
1833     scrollToPosition(sr, verticalOffset, true, true);
1834   }
1835
1836   /**
1837    * Set a flag to say do not scroll any (cDNA/protein) complement.
1838    * 
1839    * @param b
1840    */
1841   protected void setToScrollComplementPanel(boolean b)
1842   {
1843     this.scrollComplementaryPanel = b;
1844   }
1845
1846   /**
1847    * Get whether to scroll complement panel
1848    * 
1849    * @return true if cDNA/protein complement panels should be scrolled
1850    */
1851   protected boolean isSetToScrollComplementPanel()
1852   {
1853     return this.scrollComplementaryPanel;
1854   }
1855
1856   /**
1857    * Redraw sensibly.
1858    * 
1859    * @adjustHeight if true, try to recalculate panel height for visible
1860    *               annotations
1861    */
1862   protected void refresh(boolean adjustHeight)
1863   {
1864     validateAnnotationDimensions(adjustHeight);
1865     addNotify();
1866     if (adjustHeight)
1867     {
1868       // sort, repaint, update overview
1869       paintAlignment(true);
1870     }
1871     else
1872     {
1873       // lightweight repaint
1874       repaint();
1875     }
1876   }
1877
1878   @Override
1879   /**
1880    * Property change event fired when a change is made to the viewport ranges
1881    * object associated with this alignment panel's viewport
1882    */
1883   public void propertyChange(PropertyChangeEvent evt)
1884   {
1885     // update this panel's scroll values based on the new viewport ranges values
1886     int x = vpRanges.getStartRes();
1887     int y = vpRanges.getStartSeq();
1888     setScrollValues(x, y);
1889
1890     // now update any complementary alignment (its viewport ranges object
1891     // is different so does not get automatically updated)
1892     if (isSetToScrollComplementPanel())
1893     {
1894       setToScrollComplementPanel(false);
1895       av.scrollComplementaryAlignment();
1896       setToScrollComplementPanel(true);
1897     }
1898   }
1899
1900   /**
1901    * Set the reference to the PCA/Tree chooser dialog for this panel. This
1902    * reference should be nulled when the dialog is closed.
1903    * 
1904    * @param calculationChooser
1905    */
1906   public void setCalculationDialog(CalculationChooser calculationChooser)
1907   {
1908     calculationDialog = calculationChooser;
1909   }
1910
1911   /**
1912    * Returns the reference to the PCA/Tree chooser dialog for this panel (null
1913    * if none is open)
1914    */
1915   public CalculationChooser getCalculationDialog()
1916   {
1917     return calculationDialog;
1918   }
1919 }