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