JAL-3253-applet JAL-3383
[jalview.git] / src / jalview / gui / AlignmentPanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import jalview.analysis.AnnotationSorter;
24 import jalview.api.AlignViewportI;
25 import jalview.api.AlignmentViewPanel;
26 import jalview.api.SequenceRenderer;
27 import jalview.bin.Cache;
28 import jalview.bin.Jalview;
29 import jalview.datamodel.AlignmentI;
30 import jalview.datamodel.HiddenColumns;
31 import jalview.datamodel.SearchResultsI;
32 import jalview.datamodel.SequenceFeature;
33 import jalview.datamodel.SequenceGroup;
34 import jalview.datamodel.SequenceI;
35 import jalview.gui.ImageExporter.ImageWriterI;
36 import jalview.io.HTMLOutput;
37 import jalview.jbgui.GAlignmentPanel;
38 import jalview.math.AlignmentDimension;
39 import jalview.schemes.ResidueProperties;
40 import jalview.structure.StructureSelectionManager;
41 import jalview.util.Comparison;
42 import jalview.util.ImageMaker;
43 import jalview.util.MessageManager;
44 import jalview.viewmodel.ViewportListenerI;
45 import jalview.viewmodel.ViewportRanges;
46
47 import java.awt.BorderLayout;
48 import java.awt.Color;
49 import java.awt.Container;
50 import java.awt.Dimension;
51 import java.awt.Font;
52 import java.awt.FontMetrics;
53 import java.awt.Graphics;
54 import java.awt.Graphics2D;
55 import java.awt.event.AdjustmentEvent;
56 import java.awt.event.AdjustmentListener;
57 import java.awt.event.ComponentAdapter;
58 import java.awt.event.ComponentEvent;
59 import java.awt.image.BufferedImage;
60 import java.awt.print.PageFormat;
61 import java.awt.print.Printable;
62 import java.awt.print.PrinterException;
63 import java.beans.PropertyChangeEvent;
64 import java.beans.PropertyChangeListener;
65 import java.io.File;
66 import java.io.FileWriter;
67 import java.io.PrintWriter;
68 import java.util.List;
69
70 import javax.swing.SwingUtilities;
71
72 /**
73  * The main panel of an AlignFrame, containing holders for the IdPanel,
74  * SeqPanel, AnnotationLabels (a JPanel), and AnnotationPanel.
75  * 
76  * Additional holders contain an IdPanelWidthAdjuster space above the idPanel,
77  * AnnotationScroller (JScrollPane for AnnotationPanel), and vertical and
78  * horizontal scrollbars.
79  * 
80  * 
81  * 
82  * @author $author$
83  * @version $Revision: 1.161 $
84  */
85 @SuppressWarnings("serial")
86 public class AlignmentPanel extends GAlignmentPanel implements
87         AdjustmentListener, Printable, AlignmentViewPanel, ViewportListenerI
88 {
89   public AlignViewport av;
90
91   OverviewPanel overviewPanel;
92
93   private SeqPanel seqPanel;
94
95   private IdPanel idPanel;
96
97   IdwidthAdjuster idwidthAdjuster;
98
99   public AlignFrame alignFrame;
100
101   private ScalePanel scalePanel;
102
103   private AnnotationPanel annotationPanel;
104
105   private AnnotationLabels alabels;
106
107   private int hextent = 0;
108
109   private int vextent = 0;
110
111   /*
112    * Flag set while scrolling to follow complementary cDNA/protein scroll. When
113    * false, suppresses invoking the same method recursively.
114    */
115   private boolean scrollComplementaryPanel = true;
116
117   private PropertyChangeListener propertyChangeListener;
118
119   private CalculationChooser calculationDialog;
120
121   /**
122    * Creates a new AlignmentPanel object.
123    * 
124    * @param af
125    * @param av
126    */
127   public AlignmentPanel(AlignFrame af, final AlignViewport av)
128   {
129     setBackground(Color.white); // BH 2019
130     setOpaque(true);
131     alignFrame = af;
132     this.av = av;
133     setSeqPanel(new SeqPanel(av, this));
134     setIdPanel(new IdPanel(av, this));
135
136     setScalePanel(new ScalePanel(av, this));
137
138     idPanelHolder.add(getIdPanel(), BorderLayout.CENTER);
139     idwidthAdjuster = new IdwidthAdjuster(this);
140     idSpaceFillerPanel1.add(idwidthAdjuster, BorderLayout.CENTER);
141
142     setAnnotationPanel(new AnnotationPanel(this));
143     setAlabels(new AnnotationLabels(this));
144
145     annotationScroller.setViewportView(getAnnotationPanel());
146     annotationSpaceFillerHolder.add(getAlabels(), BorderLayout.CENTER);
147     if (!av.isShowAnnotation())
148     {
149       annotationScroller.setVisible(false);
150       annotationSpaceFillerHolder.setVisible(false);
151     }
152     scalePanelHolder.add(getScalePanel(), BorderLayout.CENTER);
153     seqPanelHolder.add(getSeqPanel(), BorderLayout.CENTER);
154
155     setScrollValues(0, 0);
156
157     hscroll.addAdjustmentListener(this);
158     vscroll.addAdjustmentListener(this);
159
160     addComponentListener(new ComponentAdapter()
161     {
162       @Override
163       public void componentResized(ComponentEvent evt)
164       {
165         // reset the viewport ranges when the alignment panel is resized
166         // in particular, this initialises the end residue value when Jalview
167         // is initialised
168         ViewportRanges ranges = av.getRanges();
169         if (av.getWrapAlignment())
170         {
171           int widthInRes = getSeqPanel().seqCanvas.getWrappedCanvasWidth(
172                   getSeqPanel().seqCanvas.getWidth());
173           ranges.setViewportWidth(widthInRes);
174         }
175         else
176         {
177           int widthInRes = getSeqPanel().seqCanvas.getWidth()
178                   / av.getCharWidth();
179           int heightInSeq = getSeqPanel().seqCanvas.getHeight()
180                   / av.getCharHeight();
181
182           ranges.setViewportWidth(widthInRes);
183           ranges.setViewportHeight(heightInSeq);
184         }
185       }
186
187     });
188
189     final AlignmentPanel ap = this;
190     propertyChangeListener = new PropertyChangeListener()
191     {
192       @Override
193       public void propertyChange(PropertyChangeEvent evt)
194       {
195         if (evt.getPropertyName().equals("alignment"))
196         {
197           PaintRefresher.Refresh(ap, av.getSequenceSetId(), true, true);
198           alignmentChanged();
199         }
200       }
201     };
202     av.addPropertyChangeListener(propertyChangeListener);
203
204     av.getRanges().addPropertyChangeListener(this);
205     fontChanged();
206     adjustAnnotationHeight();
207     updateLayout();
208   }
209
210   @Override
211   public AlignViewportI getAlignViewport()
212   {
213     return av;
214   }
215
216   public void alignmentChanged()
217   {
218     av.alignmentChanged(this);
219
220     if (getCalculationDialog() != null)
221     {
222       getCalculationDialog().validateCalcTypes();
223     }
224
225     alignFrame.updateEditMenuBar();
226
227     // no idea if we need to update structure
228     paintAlignment(true, true);
229
230   }
231
232   /**
233    * DOCUMENT ME!
234    */
235   public void fontChanged()
236   {
237     // set idCanvas bufferedImage to null
238     // to prevent drawing old image
239     FontMetrics fm = getFontMetrics(av.getFont());
240
241     scalePanelHolder.setPreferredSize(
242             new Dimension(10, av.getCharHeight() + fm.getDescent()));
243     idSpaceFillerPanel1.setPreferredSize(
244             new Dimension(10, av.getCharHeight() + fm.getDescent()));
245     idwidthAdjuster.invalidate();
246     scalePanelHolder.invalidate();
247     // BH 2018 getIdPanel().getIdCanvas().gg = null;
248     getSeqPanel().seqCanvas.img = null;
249     getAnnotationPanel().adjustPanelHeight();
250
251     Dimension d = calculateIdWidth();
252
253     d.setSize(d.width + 4, d.height);
254     getIdPanel().getIdCanvas().setPreferredSize(d);
255     hscrollFillerPanel.setPreferredSize(d);
256
257     repaint();
258   }
259
260   /**
261    * Calculate the width of the alignment labels based on the displayed names
262    * and any bounds on label width set in preferences.
263    * 
264    * @return Dimension giving the maximum width of the alignment label panel
265    *         that should be used.
266    */
267   public Dimension calculateIdWidth()
268   {
269     // calculate sensible default width when no preference is available
270     Dimension r = null;
271     if (av.getIdWidth() < 0)
272     {
273       int afwidth = (alignFrame != null ? alignFrame.getWidth() : 300);
274       int maxwidth = Math.max(20, Math.min(afwidth - 200, 2 * afwidth / 3));
275       r = calculateIdWidth(maxwidth);
276       av.setIdWidth(r.width);
277     }
278     else
279     {
280       r = new Dimension();
281       r.width = av.getIdWidth();
282       r.height = 0;
283     }
284     return r;
285   }
286
287   /**
288    * Calculate the width of the alignment labels based on the displayed names
289    * and any bounds on label width set in preferences.
290    * 
291    * @param maxwidth
292    *          -1 or maximum width allowed for IdWidth
293    * @return Dimension giving the maximum width of the alignment label panel
294    *         that should be used.
295    */
296   public Dimension calculateIdWidth(int maxwidth)
297   {
298     Container c = new Container();
299
300     FontMetrics fm = c.getFontMetrics(
301             new Font(av.font.getName(), Font.ITALIC, av.font.getSize()));
302
303     AlignmentI al = av.getAlignment();
304     int i = 0;
305     int idWidth = 0;
306     String id;
307
308     while ((i < al.getHeight()) && (al.getSequenceAt(i) != null))
309     {
310       SequenceI s = al.getSequenceAt(i);
311
312       id = s.getDisplayId(av.getShowJVSuffix());
313
314       if (fm.stringWidth(id) > idWidth)
315       {
316         idWidth = fm.stringWidth(id);
317       }
318
319       i++;
320     }
321
322     // Also check annotation label widths
323     i = 0;
324
325     if (al.getAlignmentAnnotation() != null)
326     {
327       fm = c.getFontMetrics(getAlabels().getFont());
328
329       while (i < al.getAlignmentAnnotation().length)
330       {
331         String label = al.getAlignmentAnnotation()[i].label;
332
333         if (fm.stringWidth(label) > idWidth)
334         {
335           idWidth = fm.stringWidth(label);
336         }
337
338         i++;
339       }
340     }
341
342     return new Dimension(
343             maxwidth < 0 ? idWidth : Math.min(maxwidth, idWidth), 12);
344   }
345
346   /**
347    * Highlight the given results on the alignment
348    * 
349    */
350   public void highlightSearchResults(SearchResultsI results)
351   {
352     boolean scrolled = scrollToPosition(results, 0, false);
353
354     boolean fastPaint = !(scrolled && av.getWrapAlignment());
355
356     getSeqPanel().seqCanvas.highlightSearchResults(results, fastPaint);
357   }
358
359   /**
360    * Scroll the view to show the position of the highlighted region in results
361    * (if any)
362    * 
363    * @param searchResults
364    * @return
365    */
366   public boolean scrollToPosition(SearchResultsI searchResults)
367   {
368     return scrollToPosition(searchResults, 0, false);
369   }
370
371   /**
372    * Scrolls the view (if necessary) to show the position of the first
373    * highlighted region in results (if any). Answers true if the view was
374    * scrolled, or false if no matched region was found, or it is already
375    * visible.
376    * 
377    * @param results
378    * @param verticalOffset
379    *          if greater than zero, allows scrolling to a position below the
380    *          first displayed sequence
381    * @param centre
382    *          if true, try to centre the search results horizontally in the view
383    * @return
384    */
385   protected boolean scrollToPosition(SearchResultsI results,
386           int verticalOffset, boolean centre)
387   {
388     int startv, endv, starts, ends;
389     ViewportRanges ranges = av.getRanges();
390
391     if (results == null || results.isEmpty() || av == null
392             || av.getAlignment() == null)
393     {
394       return false;
395     }
396     int seqIndex = av.getAlignment().findIndex(results);
397     if (seqIndex == -1)
398     {
399       return false;
400     }
401     SequenceI seq = av.getAlignment().getSequenceAt(seqIndex);
402
403     int[] r = results.getResults(seq, 0, av.getAlignment().getWidth());
404     if (r == null)
405     {
406       return false;
407     }
408     int start = r[0];
409     int end = r[1];
410
411     /*
412      * To centre results, scroll to positions half the visible width
413      * left/right of the start/end positions
414      */
415     if (centre)
416     {
417       int offset = (ranges.getEndRes() - ranges.getStartRes() + 1) / 2 - 1;
418       start = Math.max(start - offset, 0);
419       end = end + offset - 1;
420     }
421     if (start < 0)
422     {
423       return false;
424     }
425     if (end == seq.getEnd())
426     {
427       return false;
428     }
429
430     if (av.hasHiddenColumns())
431     {
432       HiddenColumns hidden = av.getAlignment().getHiddenColumns();
433       start = hidden.absoluteToVisibleColumn(start);
434       end = hidden.absoluteToVisibleColumn(end);
435       if (start == end)
436       {
437         if (!hidden.isVisible(r[0]))
438         {
439           // don't scroll - position isn't visible
440           return false;
441         }
442       }
443     }
444
445     /*
446      * allow for offset of target sequence (actually scroll to one above it)
447      */
448     seqIndex = Math.max(0, seqIndex - verticalOffset);
449     boolean scrollNeeded = true;
450
451     if (!av.getWrapAlignment())
452     {
453       if ((startv = ranges.getStartRes()) >= start)
454       {
455         /*
456          * Scroll left to make start of search results visible
457          */
458         setScrollValues(start, seqIndex);
459       }
460       else if ((endv = ranges.getEndRes()) <= end)
461       {
462         /*
463          * Scroll right to make end of search results visible
464          */
465         setScrollValues(startv + end - endv, seqIndex);
466       }
467       else if ((starts = ranges.getStartSeq()) > seqIndex)
468       {
469         /*
470          * Scroll up to make start of search results visible
471          */
472         setScrollValues(ranges.getStartRes(), seqIndex);
473       }
474       else if ((ends = ranges.getEndSeq()) <= seqIndex)
475       {
476         /*
477          * Scroll down to make end of search results visible
478          */
479         setScrollValues(ranges.getStartRes(), starts + seqIndex - ends
480                 + 1);
481       }
482       /*
483        * Else results are already visible - no need to scroll
484        */
485       scrollNeeded = false;
486     }
487     else
488     {
489       scrollNeeded = ranges.scrollToWrappedVisible(start);
490     }
491
492     paintAlignment(false, false);
493
494     return scrollNeeded;
495   }
496
497   /**
498    * DOCUMENT ME!
499    * 
500    * @return DOCUMENT ME!
501    */
502   public OverviewPanel getOverviewPanel()
503   {
504     return overviewPanel;
505   }
506
507   /**
508    * DOCUMENT ME!
509    * 
510    * @param op
511    *          DOCUMENT ME!
512    */
513   public void setOverviewPanel(OverviewPanel op)
514   {
515     overviewPanel = op;
516   }
517
518   /**
519    * 
520    * @param b
521    *          Hide or show annotation panel
522    * 
523    */
524   public void setAnnotationVisible(boolean b)
525   {
526     if (!av.getWrapAlignment())
527     {
528       annotationSpaceFillerHolder.setVisible(b);
529       annotationScroller.setVisible(b);
530     }
531     repaint();
532   }
533
534   /**
535    * automatically adjust annotation panel height for new annotation whilst
536    * ensuring the alignment is still visible.
537    */
538   @Override
539   public void adjustAnnotationHeight()
540   {
541     // TODO: display vertical annotation scrollbar if necessary
542     // this is called after loading new annotation onto alignment
543     if (alignFrame.getHeight() == 0)
544     {
545       System.out.println("NEEDS FIXING");
546     }
547     validateAnnotationDimensions(true);
548     addNotify();
549     // TODO: many places call this method and also paintAlignment with various
550     // different settings. this means multiple redraws are triggered...
551     paintAlignment(true, av.needToUpdateStructureViews());
552   }
553
554   /**
555    * calculate the annotation dimensions and refresh slider values accordingly.
556    * need to do repaints/notifys afterwards.
557    */
558   protected void validateAnnotationDimensions(boolean adjustPanelHeight)
559   {
560     // BH 2018.04.18 comment: addNotify() is not appropriate here. We
561     // are not changing ancestors, and keyboard action listeners do
562     // not need to be reset. addNotify() is a very expensive operation,
563     // requiring a full re-layout of all parents and children.
564     // Note in JComponent:
565     // This method is called by the toolkit internally and should
566     // not be called directly by programs.
567     // I note that addNotify() is called in several areas of Jalview.
568
569     int annotationHeight = getAnnotationPanel().adjustPanelHeight();
570     annotationHeight = getAnnotationPanel()
571             .adjustForAlignFrame(adjustPanelHeight, annotationHeight);
572
573     hscroll.addNotify();
574     annotationScroller.setPreferredSize(
575             new Dimension(annotationScroller.getWidth(), annotationHeight));
576
577     Dimension e = idPanel.getSize();
578     alabels.setSize(new Dimension(e.width, annotationHeight));
579
580
581     annotationSpaceFillerHolder.setPreferredSize(new Dimension(
582             annotationSpaceFillerHolder.getWidth(), annotationHeight));
583     annotationScroller.validate();
584     annotationScroller.addNotify();
585   }
586
587   /**
588    * update alignment layout for viewport settings
589    * 
590    * @param wrap
591    *          DOCUMENT ME!
592    */
593   public void updateLayout()
594   {
595     fontChanged();
596     setAnnotationVisible(av.isShowAnnotation());
597     boolean wrap = av.getWrapAlignment();
598     ViewportRanges ranges = av.getRanges();
599     ranges.setStartSeq(0);
600     scalePanelHolder.setVisible(!wrap);
601     hscroll.setVisible(!wrap);
602     idwidthAdjuster.setVisible(!wrap);
603
604     if (wrap)
605     {
606       annotationScroller.setVisible(false);
607       annotationSpaceFillerHolder.setVisible(false);
608     }
609     else if (av.isShowAnnotation())
610     {
611       annotationScroller.setVisible(true);
612       annotationSpaceFillerHolder.setVisible(true);
613       validateAnnotationDimensions(false);
614     }
615
616     int canvasWidth = getSeqPanel().seqCanvas.getWidth();
617     if (canvasWidth > 0)
618     { // may not yet be laid out
619       if (wrap)
620       {
621         int widthInRes = getSeqPanel().seqCanvas
622                 .getWrappedCanvasWidth(canvasWidth);
623         ranges.setViewportWidth(widthInRes);
624       }
625       else
626       {
627         int widthInRes = (canvasWidth / av.getCharWidth());
628         int heightInSeq = (getSeqPanel().seqCanvas.getHeight()
629                 / av.getCharHeight());
630
631         ranges.setViewportWidth(widthInRes);
632         ranges.setViewportHeight(heightInSeq);
633       }
634     }
635
636     idSpaceFillerPanel1.setVisible(!wrap);
637
638     repaint();
639   }
640
641   /**
642    * Adjust row/column scrollers to show a visible position in the alignment.
643    * 
644    * @param x
645    *          visible column to scroll to
646    * @param y
647    *          visible row to scroll to
648    * 
649    */
650   public void setScrollValues(int xpos, int ypos)
651   {
652     int x = xpos;
653     int y = ypos;
654
655     if (av == null || av.getAlignment() == null)
656     {
657       return;
658     }
659
660     if (av.getWrapAlignment())
661     {
662       setScrollingForWrappedPanel(x);
663     }
664     else
665     {
666       int width = av.getAlignment().getVisibleWidth();
667       int height = av.getAlignment().getHeight();
668
669       hextent = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
670       vextent = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight();
671
672       if (hextent > width)
673       {
674         hextent = width;
675       }
676
677       if (vextent > height)
678       {
679         vextent = height;
680       }
681
682       if ((hextent + x) > width)
683       {
684         x = width - hextent;
685       }
686
687       if ((vextent + y) > height)
688       {
689         y = height - vextent;
690       }
691
692       if (y < 0)
693       {
694         y = 0;
695       }
696
697       if (x < 0)
698       {
699         x = 0;
700       }
701
702       // update the scroll values
703       hscroll.setValues(x, hextent, 0, width);
704       vscroll.setValues(y, vextent, 0, height);
705     }
706   }
707
708   /**
709    * Respond to adjustment event when horizontal or vertical scrollbar is
710    * changed
711    * 
712    * @param evt
713    *          adjustment event encoding whether hscroll or vscroll changed
714    */
715   @Override
716   public void adjustmentValueChanged(AdjustmentEvent evt)
717   {
718     if (av.getWrapAlignment())
719     {
720       adjustScrollingWrapped(evt);
721       return;
722     }
723
724     ViewportRanges ranges = av.getRanges();
725
726     if (evt.getSource() == hscroll)
727     {
728       int oldX = ranges.getStartRes();
729       int oldwidth = ranges.getViewportWidth();
730       int x = hscroll.getValue();
731       int width = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
732
733       // if we're scrolling to the position we're already at, stop
734       // this prevents infinite recursion of events when the scroll/viewport
735       // ranges values are the same
736       if ((x == oldX) && (width == oldwidth))
737       {
738         return;
739       }
740       ranges.setViewportStartAndWidth(x, width);
741     }
742     else if (evt.getSource() == vscroll)
743     {
744       int oldY = ranges.getStartSeq();
745       int oldheight = ranges.getViewportHeight();
746       int y = vscroll.getValue();
747       int height = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight();
748
749       // if we're scrolling to the position we're already at, stop
750       // this prevents infinite recursion of events when the scroll/viewport
751       // ranges values are the same
752       if ((y == oldY) && (height == oldheight))
753       {
754         return;
755       }
756       ranges.setViewportStartAndHeight(y, height);
757     }
758     repaint();
759   }
760
761   /**
762    * Responds to a scroll change by setting the start position of the viewport.
763    * Does
764    * 
765    * @param evt
766    */
767   protected void adjustScrollingWrapped(AdjustmentEvent evt)
768   {
769     if (evt.getSource() == hscroll)
770     {
771       return; // no horizontal scroll when wrapped
772     }
773     final ViewportRanges ranges = av.getRanges();
774
775     if (evt.getSource() == vscroll)
776     {
777       int newY = vscroll.getValue();
778
779       /*
780        * if we're scrolling to the position we're already at, stop
781        * this prevents infinite recursion of events when the scroll/viewport
782        * ranges values are the same
783        */
784       int oldX = ranges.getStartRes();
785       int oldY = ranges.getWrappedScrollPosition(oldX);
786       if (oldY == newY)
787       {
788         return;
789       }
790       if (newY > -1)
791       {
792         /*
793          * limit page up/down to one width's worth of positions
794          */
795         int rowSize = ranges.getViewportWidth();
796         int newX = newY > oldY ? oldX + rowSize : oldX - rowSize;
797         ranges.setViewportStartAndWidth(Math.max(0, newX), rowSize);
798       }
799     }
800     else
801     {
802       // This is only called if file loaded is a jar file that
803       // was wrapped when saved and user has wrap alignment true
804       // as preference setting
805       SwingUtilities.invokeLater(new Runnable()
806       {
807         @Override
808         public void run()
809         {
810           // When updating scrolling to use ViewportChange events, this code
811           // could not be validated and it is not clear if it is now being
812           // called. Log warning here in case it is called and unforeseen
813           // problems occur
814           Cache.log.warn(
815                   "Unexpected path through code: Wrapped jar file opened with wrap alignment set in preferences");
816
817           // scroll to start of panel
818           ranges.setStartRes(0);
819           ranges.setStartSeq(0);
820         }
821       });
822     }
823     repaint();
824   }
825
826   /* (non-Javadoc)
827    * @see jalview.api.AlignmentViewPanel#paintAlignment(boolean)
828    */
829   @Override
830   public void paintAlignment(boolean updateOverview,
831           boolean updateStructures)
832   {
833     final AnnotationSorter sorter = new AnnotationSorter(getAlignment(),
834             av.isShowAutocalculatedAbove());
835     sorter.sort(getAlignment().getAlignmentAnnotation(),
836             av.getSortAnnotationsBy());
837     repaint();
838
839     if (updateStructures)
840     {
841       av.getStructureSelectionManager().sequenceColoursChanged(this);
842     }
843     if (updateOverview)
844     {
845
846       if (overviewPanel != null)
847       {
848         getAlignment().resetColors();
849         overviewPanel.updateOverviewImage();
850       }
851     }
852   }
853
854   /**
855    * DOCUMENT ME!
856    * 
857    * @param g
858    *          DOCUMENT ME!
859    */
860   @Override
861   public void paintComponent(Graphics g)
862   {
863     invalidate(); // needed so that the id width adjuster works correctly
864
865     Dimension d = getIdPanel().getIdCanvas().getPreferredSize();
866     idPanelHolder.setPreferredSize(d);
867     hscrollFillerPanel.setPreferredSize(new Dimension(d.width, 12));
868
869     validate(); // needed so that the id width adjuster works correctly
870
871     /*
872      * set scroll bar positions - tried to remove but necessary for split panel to resize correctly
873      * though I still think this call should be elsewhere.
874      */
875     ViewportRanges ranges = av.getRanges();
876     setScrollValues(ranges.getStartRes(), ranges.getStartSeq());
877     super.paintComponent(g);
878   }
879
880   /**
881    * Set vertical scroll bar position, and number of increments, for wrapped
882    * panel
883    * 
884    * @param topLeftColumn
885    *          the column position at top left (0..)
886    */
887   private void setScrollingForWrappedPanel(int topLeftColumn)
888   {
889     ViewportRanges ranges = av.getRanges();
890     int scrollPosition = ranges.getWrappedScrollPosition(topLeftColumn);
891     int maxScroll = ranges.getWrappedMaxScroll(topLeftColumn);
892
893     /*
894      * a scrollbar's value can be set to at most (maximum-extent)
895      * so we add extent (1) to the maxScroll value
896      */
897     vscroll.setUnitIncrement(1);
898     vscroll.setValues(scrollPosition, 1, 0, maxScroll + 1);
899   }
900
901   /**
902    * DOCUMENT ME!
903    * 
904    * @param pg
905    *          DOCUMENT ME!
906    * @param pf
907    *          DOCUMENT ME!
908    * @param pi
909    *          DOCUMENT ME!
910    * 
911    * @return DOCUMENT ME!
912    * 
913    * @throws PrinterException
914    *           DOCUMENT ME!
915    */
916   @Override
917   public int print(Graphics pg, PageFormat pf, int pi)
918           throws PrinterException
919   {
920     pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
921
922     int pwidth = (int) pf.getImageableWidth();
923     int pheight = (int) pf.getImageableHeight();
924
925     if (av.getWrapAlignment())
926     {
927       return printWrappedAlignment(pwidth, pheight, pi, pg);
928     }
929     else
930     {
931       return printUnwrapped(pwidth, pheight, pi, pg, pg);
932     }
933   }
934
935   /**
936    * Draws the alignment image, including sequence ids, sequences, and
937    * annotation labels and annotations if shown, on either one or two Graphics
938    * contexts.
939    * 
940    * @param pageWidth
941    *          in pixels
942    * @param pageHeight
943    *          in pixels
944    * @param pageIndex
945    *          (0, 1, ...)
946    * @param idGraphics
947    *          the graphics context for sequence ids and annotation labels
948    * @param alignmentGraphics
949    *          the graphics context for sequences and annotations (may or may not
950    *          be the same context as idGraphics)
951    * @return
952    * @throws PrinterException
953    */
954   public int printUnwrapped(int pageWidth, int pageHeight, int pageIndex,
955           Graphics idGraphics, Graphics alignmentGraphics)
956           throws PrinterException
957   {
958     final int idWidth = getVisibleIdWidth(false);
959
960     /*
961      * Get the horizontal offset to where we draw the sequences.
962      * This is idWidth if using a single Graphics context, else zero.
963      */
964     final int alignmentGraphicsOffset = idGraphics != alignmentGraphics ? 0
965             : idWidth;
966
967     FontMetrics fm = getFontMetrics(av.getFont());
968     final int charHeight = av.getCharHeight();
969     final int scaleHeight = charHeight + fm.getDescent();
970
971     idGraphics.setColor(Color.white);
972     idGraphics.fillRect(0, 0, pageWidth, pageHeight);
973     idGraphics.setFont(av.getFont());
974
975     /*
976      * How many sequences and residues can we fit on a printable page?
977      */
978     final int totalRes = (pageWidth - idWidth) / av.getCharWidth();
979
980     final int totalSeq = (pageHeight - scaleHeight) / charHeight - 1;
981
982     final int alignmentWidth = av.getAlignment().getVisibleWidth();
983     int pagesWide = (alignmentWidth / totalRes) + 1;
984
985     final int startRes = (pageIndex % pagesWide) * totalRes;
986     final int endRes = Math.min(startRes + totalRes - 1,
987             alignmentWidth - 1);
988
989     final int startSeq = (pageIndex / pagesWide) * totalSeq;
990     final int alignmentHeight = av.getAlignment().getHeight();
991     final int endSeq = Math.min(startSeq + totalSeq, alignmentHeight);
992
993     int pagesHigh = ((alignmentHeight / totalSeq) + 1) * pageHeight;
994
995     if (av.isShowAnnotation())
996     {
997       pagesHigh += getAnnotationPanel().adjustPanelHeight() + 3;
998     }
999
1000     pagesHigh /= pageHeight;
1001
1002     if (pageIndex >= (pagesWide * pagesHigh))
1003     {
1004       return Printable.NO_SUCH_PAGE;
1005     }
1006     final int alignmentDrawnHeight = (endSeq - startSeq) * charHeight + 3;
1007
1008     /*
1009      * draw the Scale at horizontal offset, then reset to top left (0, 0)
1010      */
1011     alignmentGraphics.translate(alignmentGraphicsOffset, 0);
1012     getScalePanel().drawScale(alignmentGraphics, startRes, endRes,
1013             pageWidth - idWidth, scaleHeight);
1014     alignmentGraphics.translate(-alignmentGraphicsOffset, 0);
1015
1016     /*
1017      * Draw the sequence ids, offset for scale height,
1018      * then reset to top left (0, 0)
1019      */
1020     idGraphics.translate(0, scaleHeight);
1021     IdCanvas idCanvas = getIdPanel().getIdCanvas();
1022     List<SequenceI> selection = av.getSelectionGroup() == null ? null
1023             : av.getSelectionGroup().getSequences(null);
1024     idCanvas.drawIds((Graphics2D) idGraphics, av, startSeq, endSeq - 1,
1025             selection);
1026
1027     idGraphics.setFont(av.getFont());
1028     idGraphics.translate(0, -scaleHeight);
1029
1030     /*
1031      * draw the sequences, offset for scale height, and id width (if using a
1032      * single graphics context), then reset to (0, scale height)
1033      */
1034     alignmentGraphics.translate(alignmentGraphicsOffset, scaleHeight);
1035     getSeqPanel().seqCanvas.drawPanelForPrinting(alignmentGraphics, startRes,
1036             endRes, startSeq, endSeq - 1);
1037     alignmentGraphics.translate(-alignmentGraphicsOffset, 0);
1038
1039     if (av.isShowAnnotation() && (endSeq == alignmentHeight))
1040     {
1041       /*
1042        * draw annotation labels; drawComponent() translates by
1043        * getScrollOffset(), so compensate for that first;
1044        * then reset to (0, scale height)
1045        */
1046       int offset = getAlabels().getScrollOffset();
1047       idGraphics.translate(0, -offset);
1048       idGraphics.translate(0, alignmentDrawnHeight);
1049       getAlabels().drawComponent(idGraphics, idWidth);
1050       idGraphics.translate(0, -alignmentDrawnHeight);
1051
1052       /*
1053        * draw the annotations starting at 
1054        * (idOffset, alignmentHeight) from (0, scaleHeight)
1055        */
1056       alignmentGraphics.translate(alignmentGraphicsOffset,
1057               alignmentDrawnHeight);
1058       getAnnotationPanel().renderer.drawComponent(getAnnotationPanel(), av,
1059               alignmentGraphics, -1, startRes, endRes + 1);
1060     }
1061
1062     return Printable.PAGE_EXISTS;
1063   }
1064
1065   /**
1066    * Prints one page of an alignment in wrapped mode. Returns
1067    * Printable.PAGE_EXISTS (0) if a page was drawn, or Printable.NO_SUCH_PAGE if
1068    * no page could be drawn (page number out of range).
1069    * 
1070    * @param pageWidth
1071    * @param pageHeight
1072    * @param pageNumber
1073    *          (0, 1, ...)
1074    * @param g
1075    * 
1076    * @return
1077    * 
1078    * @throws PrinterException
1079    */
1080   public int printWrappedAlignment(int pageWidth, int pageHeight, int pageNumber,
1081           Graphics g) throws PrinterException
1082   {
1083     int annotationHeight = 0;
1084     if (av.isShowAnnotation())
1085     {
1086       annotationHeight = getAnnotationPanel().adjustPanelHeight();
1087     }
1088
1089     int hgap = av.getCharHeight();
1090     if (av.getScaleAboveWrapped())
1091     {
1092       hgap += av.getCharHeight();
1093     }
1094
1095     int cHeight = av.getAlignment().getHeight() * av.getCharHeight() + hgap
1096             + annotationHeight;
1097
1098     int idWidth = getVisibleIdWidth(false);
1099
1100     int maxwidth = av.getAlignment().getVisibleWidth();
1101
1102     int resWidth = getSeqPanel().seqCanvas
1103             .getWrappedCanvasWidth(pageWidth - idWidth);
1104
1105     int totalHeight = cHeight * (maxwidth / resWidth + 1);
1106
1107     g.setColor(Color.white);
1108     g.fillRect(0, 0, pageWidth, pageHeight);
1109     g.setFont(av.getFont());
1110     g.setColor(Color.black);
1111
1112     /*
1113      * method: print the whole wrapped alignment, but with a clip region that
1114      * is restricted to the requested page; this supports selective print of 
1115      * single  pages or ranges, (at the cost of some repeated processing in 
1116      * the 'normal' case, when all pages are printed)
1117      */
1118     g.translate(0, -pageNumber * pageHeight);
1119
1120     g.setClip(0, pageNumber * pageHeight, pageWidth, pageHeight);
1121
1122     /*
1123      * draw sequence ids and annotation labels (if shown)
1124      */
1125     IdCanvas idCanvas = getIdPanel().getIdCanvas();
1126     idCanvas.drawIdsWrapped((Graphics2D) g, av, 0, totalHeight);
1127
1128     g.translate(idWidth, 0);
1129
1130     getSeqPanel().seqCanvas.drawWrappedPanelForPrinting(g, pageWidth - idWidth,
1131             totalHeight, 0);
1132
1133     if ((pageNumber * pageHeight) < totalHeight)
1134     {
1135       return Printable.PAGE_EXISTS;
1136     }
1137     else
1138     {
1139       return Printable.NO_SUCH_PAGE;
1140     }
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    * @return
1148    */
1149   public int getVisibleIdWidth()
1150   {
1151     return getVisibleIdWidth(true);
1152   }
1153
1154   /**
1155    * get current sequence ID panel width, or nominal value if panel were to be
1156    * displayed using default settings
1157    * 
1158    * @param onscreen
1159    *          indicate if the Id width for onscreen or offscreen display should
1160    *          be returned
1161    * @return
1162    */
1163   public int getVisibleIdWidth(boolean onscreen)
1164   {
1165     // see if rendering offscreen - check preferences and calc width accordingly
1166     if (!onscreen && Cache.getDefault(Preferences.FIGURE_AUTOIDWIDTH, false))
1167     {
1168       return calculateIdWidth(-1).width + 4;
1169     }
1170     Integer idwidth = null;
1171     if (onscreen || (idwidth = Cache
1172             .getIntegerProperty(Preferences.FIGURE_FIXEDIDWIDTH)) == null)
1173     {
1174       int w = getIdPanel().getWidth();
1175       return (w > 0 ? w : calculateIdWidth().width + 4);
1176     }
1177     return idwidth.intValue() + 4;
1178   }
1179
1180   /**
1181    * Builds an image of the alignment of the specified type (EPS/PNG/SVG) and
1182    * writes it to the specified file
1183    * 
1184    * @param type
1185    * @param file
1186    */
1187   void makeAlignmentImage(ImageMaker.TYPE type, File file)
1188   {
1189     final int borderBottomOffset = 5;
1190
1191     AlignmentDimension aDimension = getAlignmentDimension();
1192     // todo use a lambda function in place of callback here?
1193     ImageWriterI writer = new ImageWriterI()
1194     {
1195       @Override
1196       public void exportImage(Graphics graphics) throws Exception
1197       {
1198         if (av.getWrapAlignment())
1199         {
1200           printWrappedAlignment(aDimension.getWidth(),
1201                   aDimension.getHeight() + borderBottomOffset, 0, graphics);
1202         }
1203         else
1204         {
1205           printUnwrapped(aDimension.getWidth(), aDimension.getHeight(), 0,
1206                   graphics, graphics);
1207         }
1208       }
1209     };
1210
1211     String fileTitle = alignFrame.getTitle();
1212     ImageExporter exporter = new ImageExporter(writer, alignFrame, type,
1213             fileTitle);
1214     int imageWidth = aDimension.getWidth();
1215     int imageHeight = aDimension.getHeight() + borderBottomOffset;
1216     String of = MessageManager.getString("label.alignment");
1217     exporter.doExport(file, this, imageWidth, imageHeight, of);
1218   }
1219
1220   /**
1221    * Calculates and returns a suitable width and height (in pixels) for an
1222    * exported image
1223    * 
1224    * @return
1225    */
1226   public AlignmentDimension getAlignmentDimension()
1227   {
1228     int maxwidth = av.getAlignment().getVisibleWidth();
1229
1230     int height = ((av.getAlignment().getHeight() + 1) * av.getCharHeight())
1231             + getScalePanel().getHeight();
1232     int width = getVisibleIdWidth(false) + (maxwidth * av.getCharWidth());
1233
1234     if (av.getWrapAlignment())
1235     {
1236       height = getWrappedHeight();
1237       if (Jalview.isHeadlessMode())
1238       {
1239         // need to obtain default alignment width and then add in any
1240         // additional allowance for id margin
1241         // this duplicates the calculation in getWrappedHeight but adjusts for
1242         // offscreen idWith
1243         width = alignFrame.getWidth() - vscroll.getPreferredSize().width
1244                 - alignFrame.getInsets().left - alignFrame.getInsets().right
1245                 - getVisibleIdWidth() + getVisibleIdWidth(false);
1246       }
1247       else
1248       {
1249         width = getSeqPanel().getWidth() + getVisibleIdWidth(false);
1250       }
1251
1252     }
1253     else if (av.isShowAnnotation())
1254     {
1255       height += getAnnotationPanel().adjustPanelHeight() + 3;
1256     }
1257     return new AlignmentDimension(width, height);
1258
1259   }
1260
1261   public void makePNGImageMap(File imgMapFile, String imageName)
1262   {
1263     // /////ONLY WORKS WITH NON WRAPPED ALIGNMENTS
1264     // ////////////////////////////////////////////
1265     int idWidth = getVisibleIdWidth(false);
1266     FontMetrics fm = getFontMetrics(av.getFont());
1267     int scaleHeight = av.getCharHeight() + fm.getDescent();
1268
1269     // Gen image map
1270     // ////////////////////////////////
1271     if (imgMapFile != null)
1272     {
1273       try
1274       {
1275         int sSize = av.getAlignment().getHeight();
1276         int alwidth = av.getAlignment().getWidth();
1277         PrintWriter out = new PrintWriter(new FileWriter(imgMapFile));
1278         out.println(HTMLOutput.getImageMapHTML());
1279         out.println("<img src=\"" + imageName
1280                 + "\" border=\"0\" usemap=\"#Map\" >"
1281                 + "<map name=\"Map\">");
1282
1283         for (int s = 0; s < sSize; s++)
1284         {
1285           int sy = s * av.getCharHeight() + scaleHeight;
1286
1287           SequenceI seq = av.getAlignment().getSequenceAt(s);
1288           SequenceGroup[] groups = av.getAlignment().findAllGroups(seq);
1289           for (int column = 0; column < alwidth; column++)
1290           {
1291             StringBuilder text = new StringBuilder(512);
1292             String triplet = null;
1293             if (av.getAlignment().isNucleotide())
1294             {
1295               triplet = ResidueProperties.nucleotideName.get(seq
1296                       .getCharAt(column) + "");
1297             }
1298             else
1299             {
1300               triplet = ResidueProperties.aa2Triplet.get(seq.getCharAt(column)
1301                       + "");
1302             }
1303
1304             if (triplet == null)
1305             {
1306               continue;
1307             }
1308
1309             int seqPos = seq.findPosition(column);
1310             int gSize = groups.length;
1311             for (int g = 0; g < gSize; g++)
1312             {
1313               if (text.length() < 1)
1314               {
1315                 text.append("<area shape=\"rect\" coords=\"")
1316                         .append((idWidth + column * av.getCharWidth()))
1317                         .append(",").append(sy).append(",")
1318                         .append((idWidth + (column + 1) * av.getCharWidth()))
1319                         .append(",").append((av.getCharHeight() + sy))
1320                         .append("\"").append(" onMouseOver=\"toolTip('")
1321                         .append(seqPos).append(" ").append(triplet);
1322               }
1323
1324               if (groups[g].getStartRes() < column
1325                       && groups[g].getEndRes() > column)
1326               {
1327                 text.append("<br><em>").append(groups[g].getName())
1328                         .append("</em>");
1329               }
1330             }
1331
1332             if (text.length() < 1)
1333             {
1334               text.append("<area shape=\"rect\" coords=\"")
1335                       .append((idWidth + column * av.getCharWidth()))
1336                       .append(",").append(sy).append(",")
1337                       .append((idWidth + (column + 1) * av.getCharWidth()))
1338                       .append(",").append((av.getCharHeight() + sy))
1339                       .append("\"").append(" onMouseOver=\"toolTip('")
1340                       .append(seqPos).append(" ").append(triplet);
1341             }
1342             if (!Comparison.isGap(seq.getCharAt(column)))
1343             {
1344               List<SequenceFeature> features = seq.findFeatures(column, column);
1345               for (SequenceFeature sf : features)
1346               {
1347                 if (sf.isContactFeature())
1348                 {
1349                   text.append("<br>").append(sf.getType()).append(" ")
1350                           .append(sf.getBegin()).append(":")
1351                           .append(sf.getEnd());
1352                 }
1353                 else
1354                 {
1355                   text.append("<br>");
1356                   text.append(sf.getType());
1357                   String description = sf.getDescription();
1358                   if (description != null
1359                           && !sf.getType().equals(description))
1360                   {
1361                     description = description.replace("\"", "&quot;");
1362                     text.append(" ").append(description);
1363                   }
1364                 }
1365                 String status = sf.getStatus();
1366                 if (status != null && !"".equals(status))
1367                 {
1368                   text.append(" (").append(status).append(")");
1369                 }
1370               }
1371               if (text.length() > 1)
1372               {
1373                 text.append("')\"; onMouseOut=\"toolTip()\";  href=\"#\">");
1374                 out.println(text.toString());
1375               }
1376             }
1377           }
1378         }
1379         out.println("</map></body></html>");
1380         out.close();
1381
1382       } catch (Exception ex)
1383       {
1384         ex.printStackTrace();
1385       }
1386     } // /////////END OF IMAGE MAP
1387
1388   }
1389
1390   int getWrappedHeight()
1391   {
1392     int seqPanelWidth = getSeqPanel().seqCanvas.getWidth();
1393
1394     if (Jalview.isHeadlessMode())
1395     {
1396       seqPanelWidth = alignFrame.getWidth() - getVisibleIdWidth()
1397               - vscroll.getPreferredSize().width
1398               - alignFrame.getInsets().left - alignFrame.getInsets().right;
1399     }
1400
1401     int chunkWidth = getSeqPanel().seqCanvas
1402             .getWrappedCanvasWidth(seqPanelWidth);
1403
1404     int hgap = av.getCharHeight();
1405     if (av.getScaleAboveWrapped())
1406     {
1407       hgap += av.getCharHeight();
1408     }
1409
1410     int annotationHeight = 0;
1411     if (av.isShowAnnotation())
1412     {
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   @Override
1741   public SequenceRenderer getSequenceRenderer()
1742   {
1743     return seqPanel.seqCanvas.getSequenceRenderer();
1744   }
1745
1746   public boolean scrollTo(int ostart, int end, int seqIndex,
1747           boolean scrollToNearest, boolean redrawOverview)
1748   {
1749     int startv, endv, starts, ends;// , width;
1750
1751     int start = -1;
1752     if (av.hasHiddenColumns())
1753     {
1754       AlignmentI al = av.getAlignment();
1755       start = al.getHiddenColumns().absoluteToVisibleColumn(ostart);
1756       end = al.getHiddenColumns().absoluteToVisibleColumn(end);
1757       if (start == end)
1758       {
1759         if (!scrollToNearest && !al.getHiddenColumns().isVisible(ostart))
1760         {
1761           // don't scroll - position isn't visible
1762           return false;
1763         }
1764       }
1765     }
1766     else
1767     {
1768       start = ostart;
1769     }
1770
1771     ViewportRanges ranges = av.getRanges();
1772     if (!av.getWrapAlignment())
1773     {
1774       /*
1775        * int spos=av.getStartRes(),sqpos=av.getStartSeq(); if ((startv =
1776        * av.getStartRes()) >= start) { spos=start-1; // seqIn //
1777        * setScrollValues(start - 1, seqIndex); } else if ((endv =
1778        * av.getEndRes()) <= end) { // setScrollValues(spos=startv + 1 + end -
1779        * endv, seqIndex); spos=startv + 1 + end - endv; } else if ((starts =
1780        * av.getStartSeq()) > seqIndex) { setScrollValues(av.getStartRes(),
1781        * seqIndex); } else if ((ends = av.getEndSeq()) <= seqIndex) {
1782        * setScrollValues(av.getStartRes(), starts + seqIndex - ends + 1); }
1783        */
1784
1785       // below is scrolling logic up to Jalview 2.8.2
1786       // if ((av.getStartRes() > end)
1787       // || (av.getEndRes() < start)
1788       // || ((av.getStartSeq() > seqIndex) || (av.getEndSeq() < seqIndex)))
1789       // {
1790       // if (start > av.getAlignment().getWidth() - hextent)
1791       // {
1792       // start = av.getAlignment().getWidth() - hextent;
1793       // if (start < 0)
1794       // {
1795       // start = 0;
1796       // }
1797       //
1798       // }
1799       // if (seqIndex > av.getAlignment().getHeight() - vextent)
1800       // {
1801       // seqIndex = av.getAlignment().getHeight() - vextent;
1802       // if (seqIndex < 0)
1803       // {
1804       // seqIndex = 0;
1805       // }
1806       // }
1807       // setScrollValues(start, seqIndex);
1808       // }
1809       // logic copied from jalview.gui.AlignmentPanel:
1810       if ((startv = ranges.getStartRes()) >= start)
1811       {
1812         /*
1813          * Scroll left to make start of search results visible
1814          */
1815         setScrollValues(start - 1, seqIndex);
1816       }
1817       else if ((endv = ranges.getEndRes()) <= end)
1818       {
1819         /*
1820          * Scroll right to make end of search results visible
1821          */
1822         setScrollValues(startv + 1 + end - endv, seqIndex);
1823       }
1824       else if ((starts = ranges.getStartSeq()) > seqIndex)
1825       {
1826         /*
1827          * Scroll up to make start of search results visible
1828          */
1829         setScrollValues(ranges.getStartRes(), seqIndex);
1830       }
1831       else if ((ends = ranges.getEndSeq()) <= seqIndex)
1832       {
1833         /*
1834          * Scroll down to make end of search results visible
1835          */
1836         setScrollValues(ranges.getStartRes(), starts + seqIndex - ends + 1);
1837       }
1838       /*
1839        * Else results are already visible - no need to scroll
1840        */
1841     }
1842     else
1843     {
1844       ranges.scrollToWrappedVisible(start);
1845     }
1846
1847     paintAlignment(redrawOverview, false);
1848     return true;
1849   }
1850
1851   @Override
1852   public void overviewDone(BufferedImage miniMe)
1853   {
1854     overviewPanel.canvas.finalizeDraw(miniMe);
1855   }
1856
1857 }