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