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