JAL-1834 removed quotes around truncated strings with ellipsis
[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       av.getStructureSelectionManager().sequenceColoursChanged(this);
855
856       if (overviewPanel != null)
857       {
858         overviewPanel.updateOverviewImage();
859       }
860     }
861   }
862
863   /**
864    * DOCUMENT ME!
865    * 
866    * @param g
867    *          DOCUMENT ME!
868    */
869   public void paintComponent(Graphics g)
870   {
871     invalidate();
872
873     Dimension d = getIdPanel().getIdCanvas().getPreferredSize();
874     idPanelHolder.setPreferredSize(d);
875     hscrollFillerPanel.setPreferredSize(new Dimension(d.width, 12));
876     validate();
877
878     if (av.getWrapAlignment())
879     {
880       int maxwidth = av.getAlignment().getWidth();
881
882       if (av.hasHiddenColumns())
883       {
884         maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
885       }
886
887       int canvasWidth = getSeqPanel().seqCanvas
888               .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
889       if (canvasWidth > 0)
890       {
891         int max = maxwidth
892                 / getSeqPanel().seqCanvas
893                         .getWrappedCanvasWidth(getSeqPanel().seqCanvas
894                                 .getWidth()) + 1;
895         vscroll.setMaximum(max);
896         vscroll.setUnitIncrement(1);
897         vscroll.setVisibleAmount(1);
898       }
899     }
900     else
901     {
902       setScrollValues(av.getStartRes(), av.getStartSeq());
903     }
904   }
905
906   /**
907    * DOCUMENT ME!
908    * 
909    * @param pg
910    *          DOCUMENT ME!
911    * @param pf
912    *          DOCUMENT ME!
913    * @param pi
914    *          DOCUMENT ME!
915    * 
916    * @return DOCUMENT ME!
917    * 
918    * @throws PrinterException
919    *           DOCUMENT ME!
920    */
921   public int print(Graphics pg, PageFormat pf, int pi)
922           throws PrinterException
923   {
924     pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
925
926     int pwidth = (int) pf.getImageableWidth();
927     int pheight = (int) pf.getImageableHeight();
928
929     if (av.getWrapAlignment())
930     {
931       return printWrappedAlignment(pg, pwidth, pheight, pi);
932     }
933     else
934     {
935       return printUnwrapped(pg, pwidth, pheight, pi);
936     }
937   }
938
939   /**
940    * DOCUMENT ME!
941    * 
942    * @param pg
943    *          DOCUMENT ME!
944    * @param pwidth
945    *          DOCUMENT ME!
946    * @param pheight
947    *          DOCUMENT ME!
948    * @param pi
949    *          DOCUMENT ME!
950    * 
951    * @return DOCUMENT ME!
952    * 
953    * @throws PrinterException
954    *           DOCUMENT ME!
955    */
956   public int printUnwrapped(Graphics pg, int pwidth, int pheight, int pi)
957           throws PrinterException
958   {
959     int idWidth = getVisibleIdWidth(false);
960     FontMetrics fm = getFontMetrics(av.getFont());
961     int scaleHeight = av.getCharHeight() + fm.getDescent();
962
963     pg.setColor(Color.white);
964     pg.fillRect(0, 0, pwidth, pheight);
965     pg.setFont(av.getFont());
966
967     // //////////////////////////////////
968     // / How many sequences and residues can we fit on a printable page?
969     int totalRes = (pwidth - idWidth) / av.getCharWidth();
970
971     int totalSeq = (pheight - scaleHeight) / av.getCharHeight() - 1;
972
973     int pagesWide = (av.getAlignment().getWidth() / totalRes) + 1;
974
975     // ///////////////////////////
976     // / Only print these sequences and residues on this page
977     int startRes;
978
979     // ///////////////////////////
980     // / Only print these sequences and residues on this page
981     int endRes;
982
983     // ///////////////////////////
984     // / Only print these sequences and residues on this page
985     int startSeq;
986
987     // ///////////////////////////
988     // / Only print these sequences and residues on this page
989     int endSeq;
990     startRes = (pi % pagesWide) * totalRes;
991     endRes = (startRes + totalRes) - 1;
992
993     if (endRes > (av.getAlignment().getWidth() - 1))
994     {
995       endRes = av.getAlignment().getWidth() - 1;
996     }
997
998     startSeq = (pi / pagesWide) * totalSeq;
999     endSeq = startSeq + totalSeq;
1000
1001     if (endSeq > av.getAlignment().getHeight())
1002     {
1003       endSeq = av.getAlignment().getHeight();
1004     }
1005
1006     int pagesHigh = ((av.getAlignment().getHeight() / totalSeq) + 1)
1007             * pheight;
1008
1009     if (av.isShowAnnotation())
1010     {
1011       pagesHigh += getAnnotationPanel().adjustPanelHeight() + 3;
1012     }
1013
1014     pagesHigh /= pheight;
1015
1016     if (pi >= (pagesWide * pagesHigh))
1017     {
1018       return Printable.NO_SUCH_PAGE;
1019     }
1020
1021     // draw Scale
1022     pg.translate(idWidth, 0);
1023     getScalePanel().drawScale(pg, startRes, endRes, pwidth - idWidth,
1024             scaleHeight);
1025     pg.translate(-idWidth, scaleHeight);
1026
1027     // //////////////
1028     // Draw the ids
1029     Color currentColor = null;
1030     Color currentTextColor = null;
1031
1032     pg.setFont(getIdPanel().getIdCanvas().getIdfont());
1033
1034     SequenceI seq;
1035     for (int i = startSeq; i < endSeq; i++)
1036     {
1037       seq = av.getAlignment().getSequenceAt(i);
1038       if ((av.getSelectionGroup() != null)
1039               && av.getSelectionGroup().getSequences(null).contains(seq))
1040       {
1041         currentColor = Color.gray;
1042         currentTextColor = Color.black;
1043       }
1044       else
1045       {
1046         currentColor = av.getSequenceColour(seq);
1047         currentTextColor = Color.black;
1048       }
1049
1050       pg.setColor(currentColor);
1051       pg.fillRect(0, (i - startSeq) * av.getCharHeight(), idWidth,
1052               av.getCharHeight());
1053
1054       pg.setColor(currentTextColor);
1055
1056       int xPos = 0;
1057       if (av.isRightAlignIds())
1058       {
1059         fm = pg.getFontMetrics();
1060         xPos = idWidth
1061                 - fm.stringWidth(seq.getDisplayId(av.getShowJVSuffix()))
1062                 - 4;
1063       }
1064
1065       pg.drawString(
1066               seq.getDisplayId(av.getShowJVSuffix()),
1067               xPos,
1068               (((i - startSeq) * av.getCharHeight()) + av.getCharHeight())
1069                       - (av.getCharHeight() / 5));
1070     }
1071
1072     pg.setFont(av.getFont());
1073
1074     // draw main sequence panel
1075     pg.translate(idWidth, 0);
1076     getSeqPanel().seqCanvas.drawPanel(pg, startRes, endRes, startSeq, endSeq, 0);
1077
1078     if (av.isShowAnnotation() && (endSeq == av.getAlignment().getHeight()))
1079     {
1080       // draw annotation - need to offset for current scroll position
1081       int offset = -getAlabels().getScrollOffset();
1082       pg.translate(0, offset);
1083       pg.translate(-idWidth - 3, (endSeq - startSeq) * av.getCharHeight()
1084               + 3);
1085       getAlabels().drawComponent(pg, idWidth);
1086       pg.translate(idWidth + 3, 0);
1087       getAnnotationPanel().renderer.drawComponent(getAnnotationPanel(), av,
1088               pg, -1, startRes, endRes + 1);
1089       pg.translate(0, -offset);
1090     }
1091
1092     return Printable.PAGE_EXISTS;
1093   }
1094
1095   /**
1096    * DOCUMENT ME!
1097    * 
1098    * @param pg
1099    *          DOCUMENT ME!
1100    * @param pwidth
1101    *          DOCUMENT ME!
1102    * @param pheight
1103    *          DOCUMENT ME!
1104    * @param pi
1105    *          DOCUMENT ME!
1106    * 
1107    * @return DOCUMENT ME!
1108    * 
1109    * @throws PrinterException
1110    *           DOCUMENT ME!
1111    */
1112   public int printWrappedAlignment(Graphics pg, int pwidth, int pheight,
1113           int pi) throws PrinterException
1114   {
1115     int annotationHeight = 0;
1116     AnnotationLabels labels = null;
1117     if (av.isShowAnnotation())
1118     {
1119       annotationHeight = getAnnotationPanel().adjustPanelHeight();
1120       labels = new AnnotationLabels(av);
1121     }
1122
1123     int hgap = av.getCharHeight();
1124     if (av.getScaleAboveWrapped())
1125     {
1126       hgap += av.getCharHeight();
1127     }
1128
1129     int cHeight = av.getAlignment().getHeight() * av.getCharHeight() + hgap
1130             + annotationHeight;
1131
1132     int idWidth = getVisibleIdWidth(false);
1133
1134     int maxwidth = av.getAlignment().getWidth();
1135     if (av.hasHiddenColumns())
1136     {
1137       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
1138     }
1139
1140     int resWidth = getSeqPanel().seqCanvas.getWrappedCanvasWidth(pwidth
1141             - idWidth);
1142
1143     int totalHeight = cHeight * (maxwidth / resWidth + 1);
1144
1145     pg.setColor(Color.white);
1146     pg.fillRect(0, 0, pwidth, pheight);
1147     pg.setFont(av.getFont());
1148
1149     // //////////////
1150     // Draw the ids
1151     pg.setColor(Color.black);
1152
1153     pg.translate(0, -pi * pheight);
1154
1155     pg.setClip(0, pi * pheight, pwidth, pheight);
1156
1157     int ypos = hgap;
1158
1159     do
1160     {
1161       for (int i = 0; i < av.getAlignment().getHeight(); i++)
1162       {
1163         pg.setFont(getIdPanel().getIdCanvas().getIdfont());
1164         SequenceI s = av.getAlignment().getSequenceAt(i);
1165         String string = s.getDisplayId(av.getShowJVSuffix());
1166         int xPos = 0;
1167         if (av.isRightAlignIds())
1168         {
1169           FontMetrics fm = pg.getFontMetrics();
1170           xPos = idWidth - fm.stringWidth(string) - 4;
1171         }
1172         pg.drawString(string, xPos,
1173                 ((i * av.getCharHeight()) + ypos + av.getCharHeight())
1174                         - (av.getCharHeight() / 5));
1175       }
1176       if (labels != null)
1177       {
1178         pg.translate(-3, ypos
1179  + (av.getAlignment().getHeight() * av.getCharHeight()));
1180
1181         pg.setFont(av.getFont());
1182         labels.drawComponent(pg, idWidth);
1183         pg.translate(+3, -ypos
1184                         - (av.getAlignment().getHeight() * av
1185                                 .getCharHeight()));
1186       }
1187
1188       ypos += cHeight;
1189     } while (ypos < totalHeight);
1190
1191     pg.translate(idWidth, 0);
1192
1193     getSeqPanel().seqCanvas.drawWrappedPanel(pg, pwidth - idWidth, totalHeight,
1194             0);
1195
1196     if ((pi * pheight) < totalHeight)
1197     {
1198       return Printable.PAGE_EXISTS;
1199
1200     }
1201     else
1202     {
1203       return Printable.NO_SUCH_PAGE;
1204     }
1205   }
1206
1207   /**
1208    * get current sequence ID panel width, or nominal value if panel were to be
1209    * displayed using default settings
1210    * 
1211    * @return
1212    */
1213   public int getVisibleIdWidth()
1214   {
1215     return getVisibleIdWidth(true);
1216   }
1217
1218   /**
1219    * get current sequence ID panel width, or nominal value if panel were to be
1220    * displayed using default settings
1221    * 
1222    * @param onscreen
1223    *          indicate if the Id width for onscreen or offscreen display should
1224    *          be returned
1225    * @return
1226    */
1227   public int getVisibleIdWidth(boolean onscreen)
1228   {
1229     // see if rendering offscreen - check preferences and calc width accordingly
1230     if (!onscreen && Cache.getDefault("FIGURE_AUTOIDWIDTH", false))
1231     {
1232       return calculateIdWidth(-1).width + 4;
1233     }
1234     Integer idwidth = null;
1235     if (onscreen
1236             || (idwidth = Cache.getIntegerProperty("FIGURE_FIXEDIDWIDTH")) == null)
1237     {
1238       return (getIdPanel().getWidth() > 0 ? getIdPanel().getWidth()
1239               : calculateIdWidth().width + 4);
1240     }
1241     return idwidth.intValue() + 4;
1242   }
1243
1244   void makeAlignmentImage(jalview.util.ImageMaker.TYPE type, File file)
1245   {
1246     long progress = System.currentTimeMillis();
1247     headless = (System.getProperty("java.awt.headless") != null && System
1248             .getProperty("java.awt.headless").equals("true"));
1249     if (alignFrame != null && !headless)
1250     {
1251       alignFrame.setProgressBar(MessageManager.formatMessage(
1252               "status.saving_file", new Object[]
1253               { type.getLabel() }), progress);
1254     }
1255     try
1256     {
1257       AlignmentDimension aDimension = getAlignmentDimension();
1258       try
1259       {
1260         jalview.util.ImageMaker im;
1261         final String imageAction, imageTitle;
1262         if (type == jalview.util.ImageMaker.TYPE.PNG)
1263         {
1264           imageAction = "Create PNG image from alignment";
1265           imageTitle = null;
1266         }
1267         else if (type == jalview.util.ImageMaker.TYPE.EPS)
1268         {
1269           imageAction = "Create EPS file from alignment";
1270           imageTitle = alignFrame.getTitle();
1271         }
1272         else
1273         {
1274           imageAction = "Create SVG file from alignment";
1275           imageTitle = alignFrame.getTitle();
1276         }
1277
1278         im = new jalview.util.ImageMaker(this, type, imageAction,
1279                 aDimension.getWidth(), aDimension.getHeight(), file,
1280                 imageTitle);
1281         if (av.getWrapAlignment())
1282         {
1283           if (im.getGraphics() != null)
1284           {
1285             printWrappedAlignment(im.getGraphics(), aDimension.getWidth(),
1286                     aDimension.getHeight(), 0);
1287             im.writeImage();
1288           }
1289         }
1290         else
1291         {
1292           if (im.getGraphics() != null)
1293           {
1294             printUnwrapped(im.getGraphics(), aDimension.getWidth(),
1295                     aDimension.getHeight(), 0);
1296             im.writeImage();
1297           }
1298         }
1299       } catch (OutOfMemoryError err)
1300       {
1301         // Be noisy here.
1302         System.out.println("########################\n" + "OUT OF MEMORY "
1303                 + file + "\n" + "########################");
1304         new OOMWarning("Creating Image for " + file, err);
1305         // System.out.println("Create IMAGE: " + err);
1306       } catch (Exception ex)
1307       {
1308         ex.printStackTrace();
1309       }
1310     } finally
1311     {
1312       if (alignFrame != null && !headless)
1313       {
1314         alignFrame.setProgressBar(MessageManager.getString("status.export_complete"), progress);
1315       }
1316     }
1317   }
1318
1319   public AlignmentDimension getAlignmentDimension()
1320   {
1321     int maxwidth = av.getAlignment().getWidth();
1322     if (av.hasHiddenColumns())
1323     {
1324       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth);
1325     }
1326
1327     int height = ((av.getAlignment().getHeight() + 1) * av.getCharHeight())
1328             + getScalePanel().getHeight();
1329     int width = getVisibleIdWidth(false) + (maxwidth * av.getCharWidth());
1330
1331     if (av.getWrapAlignment())
1332     {
1333       height = getWrappedHeight();
1334       if (headless)
1335       {
1336         // need to obtain default alignment width and then add in any
1337         // additional allowance for id margin
1338         // this duplicates the calculation in getWrappedHeight but adjusts for
1339         // offscreen idWith
1340         width = alignFrame.getWidth() - vscroll.getPreferredSize().width
1341                 - alignFrame.getInsets().left
1342                 - alignFrame.getInsets().right - getVisibleIdWidth()
1343                 + getVisibleIdWidth(false);
1344       }
1345       else
1346       {
1347         width = getSeqPanel().getWidth() + getVisibleIdWidth(false);
1348       }
1349
1350     }
1351     else if (av.isShowAnnotation())
1352     {
1353       height += getAnnotationPanel().adjustPanelHeight() + 3;
1354     }
1355     return new AlignmentDimension(width, height);
1356
1357   }
1358
1359   /**
1360    * DOCUMENT ME!
1361    */
1362   public void makeEPS(File epsFile)
1363   {
1364     makeAlignmentImage(jalview.util.ImageMaker.TYPE.EPS, epsFile);
1365   }
1366
1367   /**
1368    * DOCUMENT ME!
1369    */
1370   public void makePNG(File pngFile)
1371   {
1372     makeAlignmentImage(jalview.util.ImageMaker.TYPE.PNG, pngFile);
1373   }
1374
1375   public void makeSVG(File svgFile)
1376   {
1377     makeAlignmentImage(jalview.util.ImageMaker.TYPE.SVG, svgFile);
1378   }
1379   public void makePNGImageMap(File imgMapFile, String imageName)
1380   {
1381     // /////ONLY WORKS WITH NONE WRAPPED ALIGNMENTS
1382     // ////////////////////////////////////////////
1383     int idWidth = getVisibleIdWidth(false);
1384     FontMetrics fm = getFontMetrics(av.getFont());
1385     int scaleHeight = av.getCharHeight() + fm.getDescent();
1386
1387     // Gen image map
1388     // ////////////////////////////////
1389     if (imgMapFile != null)
1390     {
1391       try
1392       {
1393         int s, sSize = av.getAlignment().getHeight(), res, alwidth = av
1394                 .getAlignment().getWidth(), g, gSize, f, fSize, sy;
1395         StringBuffer text = new StringBuffer();
1396         PrintWriter out = new PrintWriter(new FileWriter(imgMapFile));
1397         out.println(jalview.io.HTMLOutput.getImageMapHTML());
1398         out.println("<img src=\"" + imageName
1399                 + "\" border=\"0\" usemap=\"#Map\" >"
1400                 + "<map name=\"Map\">");
1401
1402         for (s = 0; s < sSize; s++)
1403         {
1404           sy = s * av.getCharHeight() + scaleHeight;
1405
1406           SequenceI seq = av.getAlignment().getSequenceAt(s);
1407           SequenceFeature[] features = seq.getSequenceFeatures();
1408           SequenceGroup[] groups = av.getAlignment().findAllGroups(seq);
1409           for (res = 0; res < alwidth; res++)
1410           {
1411             text = new StringBuffer();
1412             String triplet = null;
1413             if (av.getAlignment().isNucleotide())
1414             {
1415               triplet = ResidueProperties.nucleotideName.get(seq
1416                       .getCharAt(res)
1417                       + "");
1418             }
1419             else
1420             {
1421               triplet = ResidueProperties.aa2Triplet.get(seq.getCharAt(res)
1422                       + "");
1423             }
1424
1425             if (triplet == null)
1426             {
1427               continue;
1428             }
1429
1430             int alIndex = seq.findPosition(res);
1431             gSize = groups.length;
1432             for (g = 0; g < gSize; g++)
1433             {
1434               if (text.length() < 1)
1435               {
1436                 text.append("<area shape=\"rect\" coords=\""
1437                         + (idWidth + res * av.getCharWidth()) + "," + sy
1438                         + "," + (idWidth + (res + 1) * av.getCharWidth())
1439                         + ","
1440                         + (av.getCharHeight() + sy) + "\""
1441                         + " onMouseOver=\"toolTip('" + alIndex + " "
1442                         + triplet);
1443               }
1444
1445               if (groups[g].getStartRes() < res
1446                       && groups[g].getEndRes() > res)
1447               {
1448                 text.append("<br><em>" + groups[g].getName() + "</em>");
1449               }
1450             }
1451
1452             if (features != null)
1453             {
1454               if (text.length() < 1)
1455               {
1456                 text.append("<area shape=\"rect\" coords=\""
1457                         + (idWidth + res * av.getCharWidth()) + "," + sy
1458                         + "," + (idWidth + (res + 1) * av.getCharWidth())
1459                         + ","
1460                         + (av.getCharHeight() + sy) + "\""
1461                         + " onMouseOver=\"toolTip('" + alIndex + " "
1462                         + triplet);
1463               }
1464               fSize = features.length;
1465               for (f = 0; f < fSize; f++)
1466               {
1467
1468                 if ((features[f].getBegin() <= seq.findPosition(res))
1469                         && (features[f].getEnd() >= seq.findPosition(res)))
1470                 {
1471                   if (features[f].getType().equals("disulfide bond"))
1472                   {
1473                     if (features[f].getBegin() == seq.findPosition(res)
1474                             || features[f].getEnd() == seq
1475                                     .findPosition(res))
1476                     {
1477                       text.append("<br>disulfide bond "
1478                               + features[f].getBegin() + ":"
1479                               + features[f].getEnd());
1480                     }
1481                   }
1482                   else
1483                   {
1484                     text.append("<br>");
1485                     text.append(features[f].getType());
1486                     if (features[f].getDescription() != null
1487                             && !features[f].getType().equals(
1488                                     features[f].getDescription()))
1489                     {
1490                       text.append(" " + features[f].getDescription());
1491                     }
1492
1493                     if (features[f].getValue("status") != null)
1494                     {
1495                       text.append(" (" + features[f].getValue("status")
1496                               + ")");
1497                     }
1498                   }
1499                 }
1500
1501               }
1502             }
1503             if (text.length() > 1)
1504             {
1505               text.append("')\"; onMouseOut=\"toolTip()\";  href=\"#\">");
1506               out.println(text.toString());
1507             }
1508           }
1509         }
1510         out.println("</map></body></html>");
1511         out.close();
1512
1513       } catch (Exception ex)
1514       {
1515         ex.printStackTrace();
1516       }
1517     } // /////////END OF IMAGE MAP
1518
1519   }
1520
1521   int getWrappedHeight()
1522   {
1523     int seqPanelWidth = getSeqPanel().seqCanvas.getWidth();
1524
1525     if (System.getProperty("java.awt.headless") != null
1526             && System.getProperty("java.awt.headless").equals("true"))
1527     {
1528       seqPanelWidth = alignFrame.getWidth() - getVisibleIdWidth()
1529               - vscroll.getPreferredSize().width
1530               - alignFrame.getInsets().left - alignFrame.getInsets().right;
1531     }
1532
1533     int chunkWidth = getSeqPanel().seqCanvas
1534             .getWrappedCanvasWidth(seqPanelWidth);
1535
1536     int hgap = av.getCharHeight();
1537     if (av.getScaleAboveWrapped())
1538     {
1539       hgap += av.getCharHeight();
1540     }
1541
1542     int annotationHeight = 0;
1543     if (av.isShowAnnotation())
1544     {
1545       annotationHeight = getAnnotationPanel().adjustPanelHeight();
1546     }
1547
1548     int cHeight = av.getAlignment().getHeight() * av.getCharHeight() + hgap
1549             + annotationHeight;
1550
1551     int maxwidth = av.getAlignment().getWidth();
1552     if (av.hasHiddenColumns())
1553     {
1554       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
1555     }
1556
1557     int height = ((maxwidth / chunkWidth) + 1) * cHeight;
1558
1559     return height;
1560   }
1561
1562   /**
1563    * close the panel - deregisters all listeners and nulls any references to
1564    * alignment data.
1565    */
1566   public void closePanel()
1567   {
1568     PaintRefresher.RemoveComponent(getSeqPanel().seqCanvas);
1569     PaintRefresher.RemoveComponent(getIdPanel().getIdCanvas());
1570     PaintRefresher.RemoveComponent(this);
1571     if (av != null)
1572     {
1573       jalview.structure.StructureSelectionManager ssm = av
1574               .getStructureSelectionManager();
1575       ssm.removeStructureViewerListener(getSeqPanel(), null);
1576       ssm.removeSelectionListener(getSeqPanel());
1577       ssm.removeCommandListener(av);
1578       ssm.removeStructureViewerListener(getSeqPanel(), null);
1579       ssm.removeSelectionListener(getSeqPanel());
1580       av.setAlignment(null);
1581       av = null;
1582     }
1583     else
1584     {
1585       if (Cache.log.isDebugEnabled())
1586       {
1587         Cache.log.warn("Closing alignment panel which is already closed.");
1588       }
1589     }
1590   }
1591
1592   /**
1593    * hides or shows dynamic annotation rows based on groups and av state flags
1594    */
1595   public void updateAnnotation()
1596   {
1597     updateAnnotation(false, false);
1598   }
1599
1600   public void updateAnnotation(boolean applyGlobalSettings)
1601   {
1602     updateAnnotation(applyGlobalSettings, false);
1603   }
1604
1605   public void updateAnnotation(boolean applyGlobalSettings,
1606           boolean preserveNewGroupSettings)
1607   {
1608     av.updateGroupAnnotationSettings(applyGlobalSettings,
1609             preserveNewGroupSettings);
1610     adjustAnnotationHeight();
1611   }
1612
1613   @Override
1614   public AlignmentI getAlignment()
1615   {
1616     return av.getAlignment();
1617   }
1618
1619
1620   @Override
1621   public String getViewName()
1622   {
1623     return av.viewName;
1624   }
1625
1626   /**
1627    * Make/Unmake this alignment panel the current input focus
1628    * 
1629    * @param b
1630    */
1631   public void setSelected(boolean b)
1632   {
1633     try
1634     {
1635       if (alignFrame.getSplitViewContainer() != null)
1636       {
1637         /*
1638          * bring enclosing SplitFrame to front first if there is one
1639          */
1640         ((SplitFrame) alignFrame.getSplitViewContainer()).setSelected(b);
1641       }
1642       alignFrame.setSelected(b);
1643     } catch (Exception ex)
1644     {
1645     }
1646
1647     if (b)
1648     {
1649       alignFrame.setDisplayedView(this);
1650     }
1651   }
1652
1653   @Override
1654   public StructureSelectionManager getStructureSelectionManager()
1655   {
1656     return av.getStructureSelectionManager();
1657   }
1658
1659   @Override
1660   public void raiseOOMWarning(String string, OutOfMemoryError error)
1661   {
1662     new OOMWarning(string, error, this);
1663   }
1664
1665   @Override
1666   public jalview.api.FeatureRenderer cloneFeatureRenderer()
1667   {
1668
1669     return new FeatureRenderer(this);
1670   }
1671   @Override 
1672   public jalview.api.FeatureRenderer getFeatureRenderer()
1673   {
1674     return seqPanel.seqCanvas.getFeatureRenderer();
1675   }
1676   public void updateFeatureRenderer(jalview.renderer.seqfeatures.FeatureRenderer fr)
1677   {
1678     fr.transferSettings(getSeqPanel().seqCanvas.getFeatureRenderer());
1679   }
1680
1681   public void updateFeatureRendererFrom(jalview.api.FeatureRenderer fr)
1682   {
1683     if (getSeqPanel().seqCanvas.getFeatureRenderer() != null)
1684     {
1685       getSeqPanel().seqCanvas.getFeatureRenderer().transferSettings(fr);
1686     }
1687   }
1688
1689   public ScalePanel getScalePanel()
1690   {
1691     return scalePanel;
1692   }
1693
1694   public void setScalePanel(ScalePanel scalePanel)
1695   {
1696     this.scalePanel = scalePanel;
1697   }
1698
1699   public SeqPanel getSeqPanel()
1700   {
1701     return seqPanel;
1702   }
1703
1704   public void setSeqPanel(SeqPanel seqPanel)
1705   {
1706     this.seqPanel = seqPanel;
1707   }
1708
1709   public AnnotationPanel getAnnotationPanel()
1710   {
1711     return annotationPanel;
1712   }
1713
1714   public void setAnnotationPanel(AnnotationPanel annotationPanel)
1715   {
1716     this.annotationPanel = annotationPanel;
1717   }
1718
1719   public AnnotationLabels getAlabels()
1720   {
1721     return alabels;
1722   }
1723
1724   public void setAlabels(AnnotationLabels alabels)
1725   {
1726     this.alabels = alabels;
1727   }
1728
1729   public IdPanel getIdPanel()
1730   {
1731     return idPanel;
1732   }
1733
1734   public void setIdPanel(IdPanel idPanel)
1735   {
1736     this.idPanel = idPanel;
1737   }
1738
1739   /**
1740    * Follow a scrolling change in the (cDNA/Protein) complementary alignment.
1741    * The aim is to keep the two alignments 'lined up' on their centre columns.
1742    * 
1743    * @param sr
1744    *          holds mapped region(s) of this alignment that we are scrolling
1745    *          'to'; may be modified for sequence offset by this method
1746    * @param verticalOffset
1747    *          the number of visible sequences to show above the mapped region
1748    */
1749   public void scrollToCentre(SearchResults sr, int verticalOffset)
1750   {
1751     /*
1752      * To avoid jumpy vertical scrolling (if some sequences are gapped or not
1753      * mapped), we can make the scroll-to location a sequence above the one
1754      * actually mapped.
1755      */
1756     SequenceI mappedTo = sr.getResultSequence(0);
1757     List<SequenceI> seqs = av.getAlignment().getSequences();
1758
1759     /*
1760      * This is like AlignmentI.findIndex(seq) but here we are matching the
1761      * dataset sequence not the aligned sequence
1762      */
1763     boolean matched = false;
1764     for (SequenceI seq : seqs)
1765     {
1766       if (mappedTo == seq.getDatasetSequence())
1767       {
1768         matched = true;
1769         break;
1770       }
1771     }
1772     if (!matched)
1773     {
1774       return; // failsafe, shouldn't happen
1775     }
1776
1777     /*
1778      * Scroll to position but centring the target residue.
1779      */
1780     scrollToPosition(sr, verticalOffset, true, true);
1781   }
1782
1783   /**
1784    * Set a flag to say we are scrolling to follow a (cDNA/protein) complement.
1785    * 
1786    * @param b
1787    */
1788   protected void setFollowingComplementScroll(boolean b)
1789   {
1790     this.followingComplementScroll = b;
1791   }
1792
1793   protected boolean isFollowingComplementScroll()
1794   {
1795     return this.followingComplementScroll;
1796   }
1797 }