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