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