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