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