JAL-2100 triangles shown below in jvdesktop unwrapped ruler
[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     long pSessionId = System.currentTimeMillis();
1273     headless = (System.getProperty("java.awt.headless") != null && System
1274             .getProperty("java.awt.headless").equals("true"));
1275     if (alignFrame != null && !headless)
1276     {
1277       if (file != null)
1278       {
1279         alignFrame.setProgressBar(MessageManager.formatMessage(
1280               "status.saving_file", new Object[] { type.getLabel() }),
1281                 pSessionId);
1282       }
1283     }
1284     try
1285     {
1286       AlignmentDimension aDimension = getAlignmentDimension();
1287       try
1288       {
1289         jalview.util.ImageMaker im;
1290         final String imageAction, imageTitle;
1291         if (type == jalview.util.ImageMaker.TYPE.PNG)
1292         {
1293           imageAction = "Create PNG image from alignment";
1294           imageTitle = null;
1295         }
1296         else if (type == jalview.util.ImageMaker.TYPE.EPS)
1297         {
1298           imageAction = "Create EPS file from alignment";
1299           imageTitle = alignFrame.getTitle();
1300         }
1301         else
1302         {
1303           imageAction = "Create SVG file from alignment";
1304           imageTitle = alignFrame.getTitle();
1305         }
1306
1307         im = new jalview.util.ImageMaker(this, type, imageAction,
1308                 aDimension.getWidth(), aDimension.getHeight(), file,
1309                 imageTitle, alignFrame, pSessionId, headless);
1310         if (av.getWrapAlignment())
1311         {
1312           if (im.getGraphics() != null)
1313           {
1314             printWrappedAlignment(im.getGraphics(), aDimension.getWidth(),
1315                     aDimension.getHeight(), 0);
1316             im.writeImage();
1317           }
1318         }
1319         else
1320         {
1321           if (im.getGraphics() != null)
1322           {
1323             printUnwrapped(im.getGraphics(), aDimension.getWidth(),
1324                     aDimension.getHeight(), 0);
1325             im.writeImage();
1326           }
1327         }
1328
1329       } catch (OutOfMemoryError err)
1330       {
1331         // Be noisy here.
1332         System.out.println("########################\n" + "OUT OF MEMORY "
1333                 + file + "\n" + "########################");
1334         new OOMWarning("Creating Image for " + file, err);
1335         // System.out.println("Create IMAGE: " + err);
1336       } catch (Exception ex)
1337       {
1338         ex.printStackTrace();
1339       }
1340     } finally
1341     {
1342
1343     }
1344   }
1345
1346   public AlignmentDimension getAlignmentDimension()
1347   {
1348     int maxwidth = av.getAlignment().getWidth();
1349     if (av.hasHiddenColumns())
1350     {
1351       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth);
1352     }
1353
1354     int height = ((av.getAlignment().getHeight() + 1) * av.getCharHeight())
1355             + getScalePanel().getHeight();
1356     int width = getVisibleIdWidth(false) + (maxwidth * av.getCharWidth());
1357
1358     if (av.getWrapAlignment())
1359     {
1360       height = getWrappedHeight();
1361       if (headless)
1362       {
1363         // need to obtain default alignment width and then add in any
1364         // additional allowance for id margin
1365         // this duplicates the calculation in getWrappedHeight but adjusts for
1366         // offscreen idWith
1367         width = alignFrame.getWidth() - vscroll.getPreferredSize().width
1368                 - alignFrame.getInsets().left
1369                 - alignFrame.getInsets().right - getVisibleIdWidth()
1370                 + getVisibleIdWidth(false);
1371       }
1372       else
1373       {
1374         width = getSeqPanel().getWidth() + getVisibleIdWidth(false);
1375       }
1376
1377     }
1378     else if (av.isShowAnnotation())
1379     {
1380       height += getAnnotationPanel().adjustPanelHeight() + 3;
1381     }
1382     return new AlignmentDimension(width, height);
1383
1384   }
1385
1386   /**
1387    * DOCUMENT ME!
1388    */
1389   public void makeEPS(File epsFile)
1390   {
1391     makeAlignmentImage(jalview.util.ImageMaker.TYPE.EPS, epsFile);
1392   }
1393
1394   /**
1395    * DOCUMENT ME!
1396    */
1397   public void makePNG(File pngFile)
1398   {
1399     makeAlignmentImage(jalview.util.ImageMaker.TYPE.PNG, pngFile);
1400   }
1401
1402   public void makeSVG(File svgFile)
1403   {
1404     makeAlignmentImage(jalview.util.ImageMaker.TYPE.SVG, svgFile);
1405   }
1406
1407   public void makePNGImageMap(File imgMapFile, String imageName)
1408   {
1409     // /////ONLY WORKS WITH NONE WRAPPED ALIGNMENTS
1410     // ////////////////////////////////////////////
1411     int idWidth = getVisibleIdWidth(false);
1412     FontMetrics fm = getFontMetrics(av.getFont());
1413     int scaleHeight = av.getCharHeight() + fm.getDescent();
1414
1415     // Gen image map
1416     // ////////////////////////////////
1417     if (imgMapFile != null)
1418     {
1419       try
1420       {
1421         int s, sSize = av.getAlignment().getHeight(), res, alwidth = av
1422                 .getAlignment().getWidth(), g, gSize, f, fSize, sy;
1423         StringBuffer text = new StringBuffer();
1424         PrintWriter out = new PrintWriter(new FileWriter(imgMapFile));
1425         out.println(jalview.io.HTMLOutput.getImageMapHTML());
1426         out.println("<img src=\"" + imageName
1427                 + "\" border=\"0\" usemap=\"#Map\" >"
1428                 + "<map name=\"Map\">");
1429
1430         for (s = 0; s < sSize; s++)
1431         {
1432           sy = s * av.getCharHeight() + scaleHeight;
1433
1434           SequenceI seq = av.getAlignment().getSequenceAt(s);
1435           SequenceFeature[] features = seq.getSequenceFeatures();
1436           SequenceGroup[] groups = av.getAlignment().findAllGroups(seq);
1437           for (res = 0; res < alwidth; res++)
1438           {
1439             text = new StringBuffer();
1440             String triplet = null;
1441             if (av.getAlignment().isNucleotide())
1442             {
1443               triplet = ResidueProperties.nucleotideName.get(seq
1444                       .getCharAt(res) + "");
1445             }
1446             else
1447             {
1448               triplet = ResidueProperties.aa2Triplet.get(seq.getCharAt(res)
1449                       + "");
1450             }
1451
1452             if (triplet == null)
1453             {
1454               continue;
1455             }
1456
1457             int alIndex = seq.findPosition(res);
1458             gSize = groups.length;
1459             for (g = 0; g < gSize; g++)
1460             {
1461               if (text.length() < 1)
1462               {
1463                 text.append("<area shape=\"rect\" coords=\""
1464                         + (idWidth + res * av.getCharWidth()) + "," + sy
1465                         + "," + (idWidth + (res + 1) * av.getCharWidth())
1466                         + "," + (av.getCharHeight() + sy) + "\""
1467                         + " onMouseOver=\"toolTip('" + alIndex + " "
1468                         + triplet);
1469               }
1470
1471               if (groups[g].getStartRes() < res
1472                       && groups[g].getEndRes() > res)
1473               {
1474                 text.append("<br><em>" + groups[g].getName() + "</em>");
1475               }
1476             }
1477
1478             if (features != null)
1479             {
1480               if (text.length() < 1)
1481               {
1482                 text.append("<area shape=\"rect\" coords=\""
1483                         + (idWidth + res * av.getCharWidth()) + "," + sy
1484                         + "," + (idWidth + (res + 1) * av.getCharWidth())
1485                         + "," + (av.getCharHeight() + sy) + "\""
1486                         + " onMouseOver=\"toolTip('" + alIndex + " "
1487                         + triplet);
1488               }
1489               fSize = features.length;
1490               for (f = 0; f < fSize; f++)
1491               {
1492
1493                 if ((features[f].getBegin() <= seq.findPosition(res))
1494                         && (features[f].getEnd() >= seq.findPosition(res)))
1495                 {
1496                   if (features[f].getType().equals("disulfide bond"))
1497                   {
1498                     if (features[f].getBegin() == seq.findPosition(res)
1499                             || features[f].getEnd() == seq
1500                                     .findPosition(res))
1501                     {
1502                       text.append("<br>disulfide bond "
1503                               + features[f].getBegin() + ":"
1504                               + features[f].getEnd());
1505                     }
1506                   }
1507                   else
1508                   {
1509                     text.append("<br>");
1510                     text.append(features[f].getType());
1511                     if (features[f].getDescription() != null
1512                             && !features[f].getType().equals(
1513                                     features[f].getDescription()))
1514                     {
1515                       text.append(" " + features[f].getDescription());
1516                     }
1517
1518                     if (features[f].getValue("status") != null)
1519                     {
1520                       text.append(" (" + features[f].getValue("status")
1521                               + ")");
1522                     }
1523                   }
1524                 }
1525
1526               }
1527             }
1528             if (text.length() > 1)
1529             {
1530               text.append("')\"; onMouseOut=\"toolTip()\";  href=\"#\">");
1531               out.println(text.toString());
1532             }
1533           }
1534         }
1535         out.println("</map></body></html>");
1536         out.close();
1537
1538       } catch (Exception ex)
1539       {
1540         ex.printStackTrace();
1541       }
1542     } // /////////END OF IMAGE MAP
1543
1544   }
1545
1546   int getWrappedHeight()
1547   {
1548     int seqPanelWidth = getSeqPanel().seqCanvas.getWidth();
1549
1550     if (System.getProperty("java.awt.headless") != null
1551             && System.getProperty("java.awt.headless").equals("true"))
1552     {
1553       seqPanelWidth = alignFrame.getWidth() - getVisibleIdWidth()
1554               - vscroll.getPreferredSize().width
1555               - alignFrame.getInsets().left - alignFrame.getInsets().right;
1556     }
1557
1558     int chunkWidth = getSeqPanel().seqCanvas
1559             .getWrappedCanvasWidth(seqPanelWidth);
1560
1561     int hgap = av.getCharHeight();
1562     if (av.getScaleAboveWrapped())
1563     {
1564       hgap += av.getCharHeight();
1565     }
1566
1567     int annotationHeight = 0;
1568     if (av.isShowAnnotation())
1569     {
1570       annotationHeight = getAnnotationPanel().adjustPanelHeight();
1571     }
1572
1573     int cHeight = av.getAlignment().getHeight() * av.getCharHeight() + hgap
1574             + annotationHeight;
1575
1576     int maxwidth = av.getAlignment().getWidth();
1577     if (av.hasHiddenColumns())
1578     {
1579       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
1580     }
1581
1582     int height = ((maxwidth / chunkWidth) + 1) * cHeight;
1583
1584     return height;
1585   }
1586
1587   /**
1588    * close the panel - deregisters all listeners and nulls any references to
1589    * alignment data.
1590    */
1591   public void closePanel()
1592   {
1593     PaintRefresher.RemoveComponent(getSeqPanel().seqCanvas);
1594     PaintRefresher.RemoveComponent(getIdPanel().getIdCanvas());
1595     PaintRefresher.RemoveComponent(this);
1596     if (av != null)
1597     {
1598       jalview.structure.StructureSelectionManager ssm = av
1599               .getStructureSelectionManager();
1600       ssm.removeStructureViewerListener(getSeqPanel(), null);
1601       ssm.removeSelectionListener(getSeqPanel());
1602       ssm.removeCommandListener(av);
1603       ssm.removeStructureViewerListener(getSeqPanel(), null);
1604       ssm.removeSelectionListener(getSeqPanel());
1605       av.setAlignment(null);
1606       av = null;
1607     }
1608     else
1609     {
1610       if (Cache.log.isDebugEnabled())
1611       {
1612         Cache.log.warn("Closing alignment panel which is already closed.");
1613       }
1614     }
1615   }
1616
1617   /**
1618    * hides or shows dynamic annotation rows based on groups and av state flags
1619    */
1620   public void updateAnnotation()
1621   {
1622     updateAnnotation(false, false);
1623   }
1624
1625   public void updateAnnotation(boolean applyGlobalSettings)
1626   {
1627     updateAnnotation(applyGlobalSettings, false);
1628   }
1629
1630   public void updateAnnotation(boolean applyGlobalSettings,
1631           boolean preserveNewGroupSettings)
1632   {
1633     av.updateGroupAnnotationSettings(applyGlobalSettings,
1634             preserveNewGroupSettings);
1635     adjustAnnotationHeight();
1636   }
1637
1638   @Override
1639   public AlignmentI getAlignment()
1640   {
1641     return av == null ? null : av.getAlignment();
1642   }
1643
1644   @Override
1645   public String getViewName()
1646   {
1647     return av.viewName;
1648   }
1649
1650   /**
1651    * Make/Unmake this alignment panel the current input focus
1652    * 
1653    * @param b
1654    */
1655   public void setSelected(boolean b)
1656   {
1657     try
1658     {
1659       if (alignFrame.getSplitViewContainer() != null)
1660       {
1661         /*
1662          * bring enclosing SplitFrame to front first if there is one
1663          */
1664         ((SplitFrame) alignFrame.getSplitViewContainer()).setSelected(b);
1665       }
1666       alignFrame.setSelected(b);
1667     } catch (Exception ex)
1668     {
1669     }
1670
1671     if (b)
1672     {
1673       alignFrame.setDisplayedView(this);
1674     }
1675   }
1676
1677   @Override
1678   public StructureSelectionManager getStructureSelectionManager()
1679   {
1680     return av.getStructureSelectionManager();
1681   }
1682
1683   @Override
1684   public void raiseOOMWarning(String string, OutOfMemoryError error)
1685   {
1686     new OOMWarning(string, error, this);
1687   }
1688
1689   @Override
1690   public jalview.api.FeatureRenderer cloneFeatureRenderer()
1691   {
1692
1693     return new FeatureRenderer(this);
1694   }
1695
1696   @Override
1697   public jalview.api.FeatureRenderer getFeatureRenderer()
1698   {
1699     return seqPanel.seqCanvas.getFeatureRenderer();
1700   }
1701
1702   public void updateFeatureRenderer(
1703           jalview.renderer.seqfeatures.FeatureRenderer fr)
1704   {
1705     fr.transferSettings(getSeqPanel().seqCanvas.getFeatureRenderer());
1706   }
1707
1708   public void updateFeatureRendererFrom(jalview.api.FeatureRenderer fr)
1709   {
1710     if (getSeqPanel().seqCanvas.getFeatureRenderer() != null)
1711     {
1712       getSeqPanel().seqCanvas.getFeatureRenderer().transferSettings(fr);
1713     }
1714   }
1715
1716   public ScalePanel getScalePanel()
1717   {
1718     return scalePanel;
1719   }
1720
1721   public void setScalePanel(ScalePanel scalePanel)
1722   {
1723     this.scalePanel = scalePanel;
1724   }
1725
1726   public SeqPanel getSeqPanel()
1727   {
1728     return seqPanel;
1729   }
1730
1731   public void setSeqPanel(SeqPanel seqPanel)
1732   {
1733     this.seqPanel = seqPanel;
1734   }
1735
1736   public AnnotationPanel getAnnotationPanel()
1737   {
1738     return annotationPanel;
1739   }
1740
1741   public void setAnnotationPanel(AnnotationPanel annotationPanel)
1742   {
1743     this.annotationPanel = annotationPanel;
1744   }
1745
1746   public AnnotationLabels getAlabels()
1747   {
1748     return alabels;
1749   }
1750
1751   public void setAlabels(AnnotationLabels alabels)
1752   {
1753     this.alabels = alabels;
1754   }
1755
1756   public IdPanel getIdPanel()
1757   {
1758     return idPanel;
1759   }
1760
1761   public void setIdPanel(IdPanel idPanel)
1762   {
1763     this.idPanel = idPanel;
1764   }
1765
1766   /**
1767    * Follow a scrolling change in the (cDNA/Protein) complementary alignment.
1768    * The aim is to keep the two alignments 'lined up' on their centre columns.
1769    * 
1770    * @param sr
1771    *          holds mapped region(s) of this alignment that we are scrolling
1772    *          'to'; may be modified for sequence offset by this method
1773    * @param verticalOffset
1774    *          the number of visible sequences to show above the mapped region
1775    */
1776   public void scrollToCentre(SearchResults sr, int verticalOffset)
1777   {
1778     /*
1779      * To avoid jumpy vertical scrolling (if some sequences are gapped or not
1780      * mapped), we can make the scroll-to location a sequence above the one
1781      * actually mapped.
1782      */
1783     SequenceI mappedTo = sr.getResultSequence(0);
1784     List<SequenceI> seqs = av.getAlignment().getSequences();
1785
1786     /*
1787      * This is like AlignmentI.findIndex(seq) but here we are matching the
1788      * dataset sequence not the aligned sequence
1789      */
1790     boolean matched = false;
1791     for (SequenceI seq : seqs)
1792     {
1793       if (mappedTo == seq.getDatasetSequence())
1794       {
1795         matched = true;
1796         break;
1797       }
1798     }
1799     if (!matched)
1800     {
1801       return; // failsafe, shouldn't happen
1802     }
1803
1804     /*
1805      * Scroll to position but centring the target residue.
1806      */
1807     scrollToPosition(sr, verticalOffset, true, true);
1808   }
1809
1810   /**
1811    * Set a flag to say do not scroll any (cDNA/protein) complement.
1812    * 
1813    * @param b
1814    */
1815   protected void setDontScrollComplement(boolean b)
1816   {
1817     this.dontScrollComplement = b;
1818   }
1819
1820   protected boolean isDontScrollComplement()
1821   {
1822     return this.dontScrollComplement;
1823   }
1824 }