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