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