JAL-3210 Improvements to eclipse detection. New src tree and SwingJS updated from...
[jalview.git] / src / jalview / appletgui / 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.appletgui;
22
23 import jalview.analysis.AnnotationSorter;
24 import jalview.api.AlignViewportI;
25 import jalview.api.AlignmentViewPanel;
26 import jalview.bin.JalviewLite;
27 import jalview.datamodel.AlignmentI;
28 import jalview.datamodel.SearchResultsI;
29 import jalview.datamodel.SequenceI;
30 import jalview.structure.StructureSelectionManager;
31 import jalview.viewmodel.ViewportListenerI;
32 import jalview.viewmodel.ViewportRanges;
33
34 import java.awt.BorderLayout;
35 import java.awt.Color;
36 import java.awt.Dimension;
37 import java.awt.FontMetrics;
38 import java.awt.Frame;
39 import java.awt.Graphics;
40 import java.awt.Panel;
41 import java.awt.Scrollbar;
42 import java.awt.event.AdjustmentEvent;
43 import java.awt.event.AdjustmentListener;
44 import java.awt.event.ComponentAdapter;
45 import java.awt.event.ComponentEvent;
46 import java.awt.image.BufferedImage;
47 import java.beans.PropertyChangeEvent;
48 import java.util.List;
49
50 @SuppressWarnings("serial")
51 public class AlignmentPanel extends Panel
52         implements AdjustmentListener, AlignmentViewPanel, ViewportListenerI
53 {
54
55   public AlignViewport av;
56
57   OverviewPanel overviewPanel;
58
59   SeqPanel seqPanel;
60
61   IdPanel idPanel;
62
63   IdwidthAdjuster idwidthAdjuster;
64
65   public AlignFrame alignFrame;
66
67   ScalePanel scalePanel;
68
69   AnnotationPanel annotationPanel;
70
71   AnnotationLabels alabels;
72
73   ViewportRanges vpRanges;
74
75   // this value is set false when selection area being dragged
76   boolean fastPaint = true;
77
78   public AlignmentPanel(AlignFrame af, final AlignViewport av)
79   {
80     try
81     {
82       jbInit();
83     } catch (Exception e)
84     {
85       e.printStackTrace();
86     }
87
88     alignFrame = af;
89     this.av = av;
90     vpRanges = av.getRanges();
91     seqPanel = new SeqPanel(av, this);
92     idPanel = new IdPanel(av, this);
93     scalePanel = new ScalePanel(av, this);
94     idwidthAdjuster = new IdwidthAdjuster(this);
95     annotationPanel = new AnnotationPanel(this);
96     annotationPanelHolder.add(annotationPanel, BorderLayout.CENTER);
97
98     sequenceHolderPanel.add(annotationPanelHolder, BorderLayout.SOUTH);
99     alabels = new AnnotationLabels(this);
100
101     setAnnotationVisible(av.isShowAnnotation());
102
103     idPanelHolder.add(idPanel, BorderLayout.CENTER);
104     idSpaceFillerPanel1.add(idwidthAdjuster, BorderLayout.CENTER);
105     annotationSpaceFillerHolder.add(alabels, BorderLayout.CENTER);
106     scalePanelHolder.add(scalePanel, BorderLayout.CENTER);
107     seqPanelHolder.add(seqPanel, BorderLayout.CENTER);
108
109     fontChanged();
110     setScrollValues(0, 0);
111
112     apvscroll.addAdjustmentListener(this);
113     hscroll.addAdjustmentListener(this);
114     vscroll.addAdjustmentListener(this);
115
116     addComponentListener(new ComponentAdapter()
117     {
118       @Override
119       public void componentResized(ComponentEvent evt)
120       {
121         // reset the viewport ranges when the alignment panel is resized
122         // in particular, this initialises the end residue value when Jalview
123         // is initialised
124         if (av.getWrapAlignment())
125         {
126           int widthInRes = seqPanel.seqCanvas
127                   .getWrappedCanvasWidth(seqPanel.seqCanvas.getWidth());
128           vpRanges.setViewportWidth(widthInRes);
129         }
130         else
131         {
132           int widthInRes = seqPanel.seqCanvas.getWidth()
133                   / av.getCharWidth();
134           int heightInSeq = seqPanel.seqCanvas.getHeight()
135                   / av.getCharHeight();
136
137           vpRanges.setViewportWidth(widthInRes);
138           vpRanges.setViewportHeight(heightInSeq);
139         }
140         // setScrollValues(vpRanges.getStartRes(), vpRanges.getStartSeq());
141         if (getSize().height > 0
142                 && annotationPanelHolder.getSize().height > 0)
143         {
144           validateAnnotationDimensions(false);
145         }
146         repaint();
147       }
148
149     });
150
151     Dimension d = calculateIdWidth();
152     idPanel.idCanvas.setSize(d);
153
154     hscrollFillerPanel.setSize(d.width, annotationPanel.getSize().height);
155
156     idPanel.idCanvas.setSize(d.width, seqPanel.seqCanvas.getSize().height);
157     annotationSpaceFillerHolder.setSize(d.width,
158             annotationPanel.getSize().height);
159     alabels.setSize(d.width, annotationPanel.getSize().height);
160     final AlignmentPanel ap = this;
161     av.addPropertyChangeListener(new java.beans.PropertyChangeListener()
162     {
163       @Override
164       public void propertyChange(java.beans.PropertyChangeEvent evt)
165       {
166         if (evt.getPropertyName().equals("alignment"))
167         {
168           PaintRefresher.Refresh(ap, av.getSequenceSetId(), true, true);
169           alignmentChanged();
170         }
171       }
172     });
173     av.getRanges().addPropertyChangeListener(this);
174   }
175
176   @Override
177   public AlignViewportI getAlignViewport()
178   {
179     return av;
180   }
181
182   @Override
183   public SequenceRenderer getSequenceRenderer()
184   {
185     return seqPanel.seqCanvas.sr;
186   }
187
188   @Override
189   public jalview.api.FeatureRenderer getFeatureRenderer()
190   {
191     return seqPanel.seqCanvas.fr;
192   }
193
194   @Override
195   public jalview.api.FeatureRenderer cloneFeatureRenderer()
196   {
197     FeatureRenderer nfr = new FeatureRenderer(av);
198     nfr.transferSettings(seqPanel.seqCanvas.fr);
199     return nfr;
200   }
201
202   public void alignmentChanged()
203   {
204     av.alignmentChanged(this);
205
206     if (overviewPanel != null)
207     {
208       overviewPanel.updateOverviewImage();
209     }
210
211     alignFrame.updateEditMenuBar();
212
213     repaint();
214   }
215
216   public void fontChanged()
217   {
218     // set idCanvas bufferedImage to null
219     // to prevent drawing old image
220     idPanel.idCanvas.image = null;
221     FontMetrics fm = getFontMetrics(av.getFont());
222
223     scalePanel.setSize(
224             new Dimension(10, av.getCharHeight() + fm.getDescent()));
225     idwidthAdjuster.setSize(
226             new Dimension(10, av.getCharHeight() + fm.getDescent()));
227     av.updateSequenceIdColours();
228     annotationPanel.image = null;
229     int ap = annotationPanel.adjustPanelHeight(false);
230     Dimension d = calculateIdWidth();
231     d.setSize(d.width + 4, seqPanel.seqCanvas.getSize().height);
232     alabels.setSize(d.width + 4, ap);
233
234     idPanel.idCanvas.setSize(d);
235     hscrollFillerPanel.setSize(d);
236
237     validateAnnotationDimensions(false);
238     annotationPanel.repaint();
239     validate();
240     repaint();
241   }
242
243   public void setIdWidth(int w, int h)
244   {
245     idPanel.idCanvas.setSize(w, h);
246     idPanelHolder.setSize(w, idPanelHolder.getSize().height);
247     annotationSpaceFillerHolder.setSize(w,
248             annotationSpaceFillerHolder.getSize().height);
249     alabels.setSize(w, alabels.getSize().height);
250     validate();
251   }
252
253   Dimension calculateIdWidth()
254   {
255     if (av.nullFrame == null)
256     {
257       av.nullFrame = new Frame();
258       av.nullFrame.addNotify();
259     }
260
261     Graphics g = av.nullFrame.getGraphics();
262
263     FontMetrics fm = g.getFontMetrics(av.font);
264     AlignmentI al = av.getAlignment();
265
266     int i = 0;
267     int idWidth = 0;
268     String id;
269     while (i < al.getHeight() && al.getSequenceAt(i) != null)
270     {
271       SequenceI s = al.getSequenceAt(i);
272       id = s.getDisplayId(av.getShowJVSuffix());
273
274       if (fm.stringWidth(id) > idWidth)
275       {
276         idWidth = fm.stringWidth(id);
277       }
278       i++;
279     }
280
281     // Also check annotation label widths
282     i = 0;
283     if (al.getAlignmentAnnotation() != null)
284     {
285       fm = g.getFontMetrics(av.nullFrame.getFont());
286       while (i < al.getAlignmentAnnotation().length)
287       {
288         String label = al.getAlignmentAnnotation()[i].label;
289         if (fm.stringWidth(label) > idWidth)
290         {
291           idWidth = fm.stringWidth(label);
292         }
293         i++;
294       }
295     }
296
297     return new Dimension(idWidth, idPanel.idCanvas.getSize().height);
298   }
299
300   /**
301    * Highlight the given results on the alignment.
302    * 
303    */
304   public void highlightSearchResults(SearchResultsI results)
305   {
306     scrollToPosition(results);
307     seqPanel.seqCanvas.highlightSearchResults(results);
308   }
309
310   /**
311    * scroll the view to show the position of the highlighted region in results
312    * (if any) and redraw the overview
313    * 
314    * @param results
315    * @return false if results were not found
316    */
317   public boolean scrollToPosition(SearchResultsI results)
318   {
319     return scrollToPosition(results, true);
320   }
321
322   /**
323    * scroll the view to show the position of the highlighted region in results
324    * (if any)
325    * 
326    * @param results
327    * @param redrawOverview
328    *          - when set, the overview will be recalculated (takes longer)
329    * @return false if results were not found
330    */
331   public boolean scrollToPosition(SearchResultsI results,
332           boolean redrawOverview)
333   {
334     return scrollToPosition(results, 0, redrawOverview, false);
335   }
336
337   /**
338    * scroll the view to show the position of the highlighted region in results
339    * (if any)
340    * 
341    * @param results
342    * @param redrawOverview
343    *          - when set, the overview will be recalculated (takes longer)
344    * @return false if results were not found
345    */
346   public boolean scrollToPosition(SearchResultsI results,
347           int verticalOffset, boolean redrawOverview, boolean centre)
348   {
349     // do we need to scroll the panel?
350     if (results != null && results.getSize() > 0)
351     {
352       AlignmentI alignment = av.getAlignment();
353       int seqIndex = alignment.findIndex(results);
354       if (seqIndex == -1)
355       {
356         return false;
357       }
358       /*
359        * allow for offset of target sequence (actually scroll to one above it)
360        */
361
362       SequenceI seq = alignment.getSequenceAt(seqIndex);
363       int[] r = results.getResults(seq, 0, alignment.getWidth());
364       if (r == null)
365       {
366         if (JalviewLite.debug)
367         {// DEBUG
368           System.out.println(
369                   "DEBUG: scroll didn't happen - results not within alignment : "
370                           + seq.getStart() + "," + seq.getEnd());
371         }
372         return false;
373       }
374       if (JalviewLite.debug)
375       {
376         // DEBUG
377         /*
378          * System.out.println("DEBUG: scroll: start=" + r[0] +
379          * " av.getStartRes()=" + av.getStartRes() + " end=" + r[1] +
380          * " seq.end=" + seq.getEnd() + " av.getEndRes()=" + av.getEndRes() +
381          * " hextent=" + hextent);
382          */
383       }
384       int start = r[0];
385       int end = r[1];
386
387       /*
388        * To centre results, scroll to positions half the visible width
389        * left/right of the start/end positions
390        */
391       if (centre)
392       {
393         int offset = (vpRanges.getEndRes() - vpRanges.getStartRes() + 1) / 2
394                 - 1;
395         start = Math.max(start - offset, 0);
396         end = end + offset - 1;
397         // end = Math.min(end + offset, seq.getEnd() - 1);
398       }
399
400       if (start < 0)
401       {
402         return false;
403       }
404       if (end == seq.getEnd())
405       {
406         return false;
407       }
408
409       /*
410        * allow for offset of target sequence (actually scroll to one above it)
411        */
412       seqIndex = Math.max(0, seqIndex - verticalOffset);
413       return scrollTo(start, end, seqIndex, false, redrawOverview);
414     }
415     return true;
416   }
417
418   public boolean scrollTo(int ostart, int end, int seqIndex,
419           boolean scrollToNearest, boolean redrawOverview)
420   {
421     int startv, endv, starts, ends;//, width;
422
423     int start = -1;
424     if (av.hasHiddenColumns())
425     {
426       AlignmentI al = av.getAlignment();
427       start = al.getHiddenColumns().absoluteToVisibleColumn(ostart);
428       end = al.getHiddenColumns().absoluteToVisibleColumn(end);
429       if (start == end)
430       {
431         if (!scrollToNearest && !al.getHiddenColumns().isVisible(ostart))
432         {
433           // don't scroll - position isn't visible
434           return false;
435         }
436       }
437     }
438     else
439     {
440       start = ostart;
441     }
442
443     if (!av.getWrapAlignment())
444     {
445       /*
446        * int spos=av.getStartRes(),sqpos=av.getStartSeq(); if ((startv =
447        * av.getStartRes()) >= start) { spos=start-1; // seqIn //
448        * setScrollValues(start - 1, seqIndex); } else if ((endv =
449        * av.getEndRes()) <= end) { // setScrollValues(spos=startv + 1 + end -
450        * endv, seqIndex); spos=startv + 1 + end - endv; } else if ((starts =
451        * av.getStartSeq()) > seqIndex) { setScrollValues(av.getStartRes(),
452        * seqIndex); } else if ((ends = av.getEndSeq()) <= seqIndex) {
453        * setScrollValues(av.getStartRes(), starts + seqIndex - ends + 1); }
454        */
455
456       // below is scrolling logic up to Jalview 2.8.2
457       // if ((av.getStartRes() > end)
458       // || (av.getEndRes() < start)
459       // || ((av.getStartSeq() > seqIndex) || (av.getEndSeq() < seqIndex)))
460       // {
461       // if (start > av.getAlignment().getWidth() - hextent)
462       // {
463       // start = av.getAlignment().getWidth() - hextent;
464       // if (start < 0)
465       // {
466       // start = 0;
467       // }
468       //
469       // }
470       // if (seqIndex > av.getAlignment().getHeight() - vextent)
471       // {
472       // seqIndex = av.getAlignment().getHeight() - vextent;
473       // if (seqIndex < 0)
474       // {
475       // seqIndex = 0;
476       // }
477       // }
478       // setScrollValues(start, seqIndex);
479       // }
480       // logic copied from jalview.gui.AlignmentPanel:
481       if ((startv = vpRanges.getStartRes()) >= start)
482       {
483         /*
484          * Scroll left to make start of search results visible
485          */
486         setScrollValues(start - 1, seqIndex);
487       }
488       else if ((endv = vpRanges.getEndRes()) <= end)
489       {
490         /*
491          * Scroll right to make end of search results visible
492          */
493         setScrollValues(startv + 1 + end - endv, seqIndex);
494       }
495       else if ((starts = vpRanges.getStartSeq()) > seqIndex)
496       {
497         /*
498          * Scroll up to make start of search results visible
499          */
500         setScrollValues(vpRanges.getStartRes(), seqIndex);
501       }
502       else if ((ends = vpRanges.getEndSeq()) <= seqIndex)
503       {
504         /*
505          * Scroll down to make end of search results visible
506          */
507         setScrollValues(vpRanges.getStartRes(),
508                 starts + seqIndex - ends + 1);
509       }
510       /*
511        * Else results are already visible - no need to scroll
512        */
513     }
514     else
515     {
516       vpRanges.scrollToWrappedVisible(start);
517     }
518
519     paintAlignment(redrawOverview, false);
520     return true;
521   }
522
523   public OverviewPanel getOverviewPanel()
524   {
525     return overviewPanel;
526   }
527
528   public void setOverviewPanel(OverviewPanel op)
529   {
530     overviewPanel = op;
531   }
532
533   public void setAnnotationVisible(boolean b)
534   {
535     if (!av.getWrapAlignment())
536     {
537       annotationSpaceFillerHolder.setVisible(b);
538       annotationPanelHolder.setVisible(b);
539     }
540     else
541     {
542       annotationSpaceFillerHolder.setVisible(false);
543       annotationPanelHolder.setVisible(false);
544     }
545     validate();
546     repaint();
547   }
548
549   /**
550    * automatically adjust annotation panel height for new annotation whilst
551    * ensuring the alignment is still visible.
552    */
553   @Override
554   public void adjustAnnotationHeight()
555   {
556     // TODO: display vertical annotation scrollbar if necessary
557     // this is called after loading new annotation onto alignment
558     if (alignFrame.getSize().height == 0)
559     {
560       System.out.println(
561               "adjustAnnotationHeight frame size zero NEEDS FIXING");
562     }
563     fontChanged();
564     validateAnnotationDimensions(true);
565     apvscroll.addNotify();
566     hscroll.addNotify();
567     validate();
568     paintAlignment(true, false);
569   }
570
571   /**
572    * Calculate the annotation dimensions and refresh slider values accordingly.
573    * Need to do repaints/notifys afterwards.
574    */
575   protected void validateAnnotationDimensions(boolean adjustPanelHeight)
576   {
577     int rowHeight = av.getCharHeight();
578     int alignmentHeight = rowHeight * av.getAlignment().getHeight();
579     int annotationHeight = av.calcPanelHeight();
580
581     int mheight = annotationHeight;
582     Dimension d = sequenceHolderPanel.getSize();
583
584     int availableHeight = d.height - scalePanelHolder.getHeight();
585
586     if (adjustPanelHeight)
587     {
588       /*
589        * If not enough vertical space, maximize annotation height while keeping
590        * at least two rows of alignment visible
591        */
592       if (annotationHeight + alignmentHeight > availableHeight)
593       {
594         annotationHeight = Math.min(annotationHeight,
595                 availableHeight - 2 * rowHeight);
596       }
597     }
598     else
599     {
600       // maintain same window layout whilst updating sliders
601       annotationHeight = annotationPanelHolder.getSize().height;
602     }
603
604     if (availableHeight - annotationHeight < 5)
605     {
606       annotationHeight = availableHeight;
607     }
608
609     annotationPanel.setSize(new Dimension(d.width, annotationHeight));
610     annotationPanelHolder.setSize(new Dimension(d.width, annotationHeight));
611     // seqPanelHolder.setSize(d.width, seqandannot - height);
612     seqPanel.seqCanvas.setSize(d.width,
613             seqPanel.seqCanvas.getSize().height);
614
615     Dimension e = idPanel.getSize();
616     alabels.setSize(new Dimension(e.width, annotationHeight));
617     annotationSpaceFillerHolder
618             .setSize(new Dimension(e.width, annotationHeight));
619
620     int s = apvscroll.getValue();
621     if (s > mheight - annotationHeight)
622     {
623       s = 0;
624     }
625     apvscroll.setValues(s, annotationHeight, 0, mheight);
626     annotationPanel.setScrollOffset(apvscroll.getValue(), false);
627     alabels.setScrollOffset(apvscroll.getValue(), false);
628   }
629
630   public void setWrapAlignment(boolean wrap)
631   {
632     vpRanges.setStartEndSeq(0, vpRanges.getVisibleAlignmentHeight());
633     vpRanges.setStartRes(0);
634     scalePanelHolder.setVisible(!wrap);
635
636     hscroll.setVisible(!wrap);
637     idwidthAdjuster.setVisible(!wrap);
638
639     if (wrap)
640     {
641       annotationPanelHolder.setVisible(false);
642       annotationSpaceFillerHolder.setVisible(false);
643     }
644     else if (av.isShowAnnotation())
645     {
646       annotationPanelHolder.setVisible(true);
647       annotationSpaceFillerHolder.setVisible(true);
648     }
649
650     idSpaceFillerPanel1.setVisible(!wrap);
651
652     fontChanged(); // This is so that the scalePanel is resized correctly
653
654     validate();
655     sequenceHolderPanel.validate();
656     repaint();
657
658   }
659
660   int hextent = 0;
661
662   int vextent = 0;
663
664   public void setScrollValues(int xpos, int ypos)
665   {
666     int x = xpos;
667     int y = ypos;
668
669     if (av.getWrapAlignment())
670     {
671       setScrollingForWrappedPanel(x);
672     }
673     else
674     {
675       int width = av.getAlignment().getVisibleWidth();
676       int height = av.getAlignment().getHeight();
677
678       if (x < 0)
679       {
680         x = 0;
681       }
682
683       hextent = seqPanel.seqCanvas.getSize().width / av.getCharWidth();
684       vextent = seqPanel.seqCanvas.getSize().height / av.getCharHeight();
685
686       if (hextent > width)
687       {
688         hextent = width;
689       }
690
691       if (vextent > height)
692       {
693         vextent = height;
694       }
695
696       if ((hextent + x) > width)
697       {
698         System.err.println("hextent was " + hextent + " and x was " + x);
699
700         x = width - hextent;
701       }
702
703       if ((vextent + y) > height)
704       {
705         y = height - vextent;
706       }
707
708       if (y < 0)
709       {
710         y = 0;
711       }
712
713       if (x < 0)
714       {
715         System.err.println("x was " + x);
716         x = 0;
717       }
718
719       hscroll.setValues(x, hextent, 0, width);
720       vscroll.setValues(y, vextent, 0, height);
721
722       // AWT scrollbar does not fire adjustmentValueChanged for setValues
723       // so also call adjustment code!
724       adjustHorizontal(x);
725       adjustVertical(y);
726
727       sendViewPosition();
728     }
729   }
730
731   /**
732    * Respond to adjustment event when horizontal or vertical scrollbar is
733    * changed
734    * 
735    * @param evt
736    *          adjustment event encoding whether apvscroll, hscroll or vscroll
737    *          changed
738    */
739   @Override
740   public void adjustmentValueChanged(AdjustmentEvent evt)
741   {
742     // Note that this event is NOT fired by the AWT scrollbar when setValues is
743     // called. Instead manually call adjustHorizontal and adjustVertical
744     // directly.
745     if (evt == null || evt.getSource() == apvscroll)
746     {
747       annotationPanel.setScrollOffset(apvscroll.getValue(), false);
748       alabels.setScrollOffset(apvscroll.getValue(), false);
749     }
750     if (evt == null || evt.getSource() == hscroll)
751     {
752       int x = hscroll.getValue();
753       adjustHorizontal(x);
754     }
755
756     if (evt == null || evt.getSource() == vscroll)
757     {
758       int offy = vscroll.getValue();
759       adjustVertical(offy);
760     }
761
762   }
763
764   private void adjustHorizontal(int x)
765   {
766     int oldX = vpRanges.getStartRes();
767     int oldwidth = vpRanges.getViewportWidth();
768     int width = seqPanel.seqCanvas.getWidth() / av.getCharWidth();
769
770     // if we're scrolling to the position we're already at, stop
771     // this prevents infinite recursion of events when the scroll/viewport
772     // ranges values are the same
773     if ((x == oldX) && (width == oldwidth))
774     {
775       return;
776     }
777     vpRanges.setViewportStartAndWidth(x, width);
778
779     if (av.getWrapAlignment() || !fastPaint)
780     {
781       repaint();
782     }
783     sendViewPosition();
784   }
785
786   private void adjustVertical(int newY)
787   {
788     if (av.getWrapAlignment())
789     {
790       /*
791        * if we're scrolling to the position we're already at, stop
792        * this prevents infinite recursion of events when the scroll/viewport
793        * ranges values are the same
794        */
795       int oldX = vpRanges.getStartRes();
796       int oldY = vpRanges.getWrappedScrollPosition(oldX);
797       if (oldY == newY)
798       {
799         return;
800       }
801       if (newY > -1)
802       {
803         /*
804          * limit page up/down to one width's worth of positions
805          */
806         int rowSize = vpRanges.getViewportWidth();
807         int newX = newY > oldY ? oldX + rowSize : oldX - rowSize;
808         vpRanges.setViewportStartAndWidth(Math.max(0, newX), rowSize);
809       }
810     }
811     else
812     {
813       int height = seqPanel.seqCanvas.getHeight() / av.getCharHeight();
814       int oldY = vpRanges.getStartSeq();
815       int oldheight = vpRanges.getViewportHeight();
816
817       // if we're scrolling to the position we're already at, stop
818       // this prevents infinite recursion of events when the scroll/viewport
819       // ranges values are the same
820       if ((newY == oldY) && (height == oldheight))
821       {
822         return;
823       }
824       vpRanges.setViewportStartAndHeight(newY, height);
825     }
826     if (av.getWrapAlignment() || !fastPaint)
827     {
828       repaint();
829     }
830     sendViewPosition();
831   }
832
833   /**
834    * A helper method to return the AlignmentPanel in the other (complementary)
835    * half of a SplitFrame view. Returns null if not in a SplitFrame.
836    * 
837    * @return
838    */
839   private AlignmentPanel getComplementPanel()
840   {
841     AlignmentPanel ap = null;
842     if (alignFrame != null)
843     {
844       SplitFrame sf = alignFrame.getSplitFrame();
845       if (sf != null)
846       {
847         AlignFrame other = sf.getComplement(alignFrame);
848         if (other != null)
849         {
850           ap = other.alignPanel;
851         }
852       }
853     }
854     return ap;
855   }
856
857   /**
858    * Follow a scrolling change in the (cDNA/Protein) complementary alignment.
859    * The aim is to keep the two alignments 'lined up' on their centre columns.
860    * 
861    * @param sr
862    *          holds mapped region(s) of this alignment that we are scrolling
863    *          'to'; may be modified for sequence offset by this method
864    * @param seqOffset
865    *          the number of visible sequences to show above the mapped region
866    */
867   protected void scrollToCentre(SearchResultsI sr, int seqOffset)
868   {
869     /*
870      * To avoid jumpy vertical scrolling (if some sequences are gapped or not
871      * mapped), we can make the scroll-to location a sequence above the one
872      * actually mapped.
873      */
874     SequenceI mappedTo = sr.getResults().get(0).getSequence();
875     List<SequenceI> seqs = av.getAlignment().getSequences();
876
877     /*
878      * This is like AlignmentI.findIndex(seq) but here we are matching the
879      * dataset sequence not the aligned sequence
880      */
881     boolean matched = false;
882     for (SequenceI seq : seqs)
883     {
884       if (mappedTo == seq.getDatasetSequence())
885       {
886         matched = true;
887         break;
888       }
889     }
890     if (!matched)
891     {
892       return; // failsafe, shouldn't happen
893     }
894
895     /*
896      * Scroll to position but centring the target residue. Also set a state flag
897      * to prevent adjustmentValueChanged performing this recursively.
898      */
899     scrollToPosition(sr, seqOffset, true, true);
900   }
901
902   private void sendViewPosition()
903   {
904     StructureSelectionManager.getStructureSelectionManager(av.applet)
905             .sendViewPosition(this, vpRanges.getStartRes(),
906                     vpRanges.getEndRes(), vpRanges.getStartSeq(),
907                     vpRanges.getEndSeq());
908   }
909
910   /**
911    * Repaint the alignment and annotations, and, optionally, any overview window
912    */
913   @Override
914   public void paintAlignment(boolean updateOverview,
915           boolean updateStructures)
916   {
917     final AnnotationSorter sorter = new AnnotationSorter(getAlignment(),
918             av.isShowAutocalculatedAbove());
919     sorter.sort(getAlignment().getAlignmentAnnotation(),
920             av.getSortAnnotationsBy());
921     repaint();
922
923     if (updateStructures)
924     {
925       jalview.structure.StructureSelectionManager
926               .getStructureSelectionManager(av.applet)
927               .sequenceColoursChanged(this);
928     }
929     if (updateOverview)
930     {
931       if (overviewPanel != null)
932       {
933         overviewPanel.updateOverviewImage();
934       }
935     }
936   }
937
938   @Override
939   public void update(Graphics g)
940   {
941     paint(g);
942   }
943
944   @Override
945   public void paint(Graphics g)
946   {
947     invalidate();
948     Dimension d = idPanel.idCanvas.getSize();
949     final int canvasHeight = seqPanel.seqCanvas.getSize().height;
950     if (canvasHeight != d.height)
951     {
952       idPanel.idCanvas.setSize(d.width, canvasHeight);
953     }
954
955     setScrollValues(vpRanges.getStartRes(), vpRanges.getStartSeq());
956
957     seqPanel.seqCanvas.repaint();
958     idPanel.idCanvas.repaint();
959     if (!av.getWrapAlignment())
960     {
961       if (av.isShowAnnotation())
962       {
963         alabels.repaint();
964         annotationPanel.repaint();
965       }
966       scalePanel.repaint();
967     }
968
969   }
970
971   /**
972    * Set vertical scroll bar parameters for wrapped panel
973    * 
974    * @param topLeftColumn
975    *          the column position at top left (0..)
976    */
977   private void setScrollingForWrappedPanel(int topLeftColumn)
978   {
979     int scrollPosition = vpRanges.getWrappedScrollPosition(topLeftColumn);
980     int maxScroll = vpRanges.getWrappedMaxScroll(topLeftColumn);
981
982     /*
983      * a scrollbar's value can be set to at most (maximum-extent)
984      * so we add extent (1) to the maxScroll value
985      */
986     vscroll.setUnitIncrement(1);
987     vscroll.setValues(scrollPosition, 1, 0, maxScroll + 1);
988   }
989
990   protected Panel sequenceHolderPanel = new Panel();
991
992   protected Scrollbar vscroll = new Scrollbar();
993
994   protected Scrollbar hscroll = new Scrollbar();
995
996   protected Panel seqPanelHolder = new Panel();
997
998   protected Panel scalePanelHolder = new Panel();
999
1000   protected Panel idPanelHolder = new Panel();
1001
1002   protected Panel idSpaceFillerPanel1 = new Panel();
1003
1004   public Panel annotationSpaceFillerHolder = new Panel();
1005
1006   protected Panel hscrollFillerPanel = new Panel();
1007
1008   Panel annotationPanelHolder = new Panel();
1009
1010   protected Scrollbar apvscroll = new Scrollbar();
1011
1012   /*
1013    * Flag set while scrolling to follow complementary cDNA/protein scroll. When
1014    * false, suppresses invoking the same method recursively.
1015    */
1016   private boolean scrollComplementaryPanel = true;
1017
1018   private void jbInit() throws Exception
1019   {
1020     // idPanelHolder.setPreferredSize(new Dimension(70, 10));
1021     this.setLayout(new BorderLayout());
1022
1023     // sequenceHolderPanel.setPreferredSize(new Dimension(150, 150));
1024     sequenceHolderPanel.setLayout(new BorderLayout());
1025     seqPanelHolder.setLayout(new BorderLayout());
1026     scalePanelHolder.setBackground(Color.white);
1027
1028     // scalePanelHolder.setPreferredSize(new Dimension(10, 30));
1029     scalePanelHolder.setLayout(new BorderLayout());
1030     idPanelHolder.setLayout(new BorderLayout());
1031     idSpaceFillerPanel1.setBackground(Color.white);
1032
1033     // idSpaceFillerPanel1.setPreferredSize(new Dimension(10, 30));
1034     idSpaceFillerPanel1.setLayout(new BorderLayout());
1035     annotationSpaceFillerHolder.setBackground(Color.white);
1036
1037     // annotationSpaceFillerHolder.setPreferredSize(new Dimension(10, 80));
1038     annotationSpaceFillerHolder.setLayout(new BorderLayout());
1039     hscroll.setOrientation(Scrollbar.HORIZONTAL);
1040
1041     Panel hscrollHolder = new Panel();
1042     hscrollHolder.setLayout(new BorderLayout());
1043     hscrollFillerPanel.setBackground(Color.white);
1044     apvscroll.setOrientation(Scrollbar.VERTICAL);
1045     apvscroll.setVisible(true);
1046     apvscroll.addAdjustmentListener(this);
1047
1048     annotationPanelHolder.setBackground(Color.white);
1049     annotationPanelHolder.setLayout(new BorderLayout());
1050     annotationPanelHolder.add(apvscroll, BorderLayout.EAST);
1051     // hscrollFillerPanel.setPreferredSize(new Dimension(70, 10));
1052     hscrollHolder.setBackground(Color.white);
1053
1054     // annotationScroller.setPreferredSize(new Dimension(10, 80));
1055     // this.setPreferredSize(new Dimension(220, 166));
1056     seqPanelHolder.setBackground(Color.white);
1057     idPanelHolder.setBackground(Color.white);
1058     sequenceHolderPanel.add(scalePanelHolder, BorderLayout.NORTH);
1059     sequenceHolderPanel.add(seqPanelHolder, BorderLayout.CENTER);
1060     seqPanelHolder.add(vscroll, BorderLayout.EAST);
1061
1062     // Panel3.add(secondaryPanelHolder, BorderLayout.SOUTH);
1063     this.add(idPanelHolder, BorderLayout.WEST);
1064     idPanelHolder.add(idSpaceFillerPanel1, BorderLayout.NORTH);
1065     idPanelHolder.add(annotationSpaceFillerHolder, BorderLayout.SOUTH);
1066     this.add(hscrollHolder, BorderLayout.SOUTH);
1067     hscrollHolder.add(hscroll, BorderLayout.CENTER);
1068     hscrollHolder.add(hscrollFillerPanel, BorderLayout.WEST);
1069     this.add(sequenceHolderPanel, BorderLayout.CENTER);
1070   }
1071
1072   /**
1073    * hides or shows dynamic annotation rows based on groups and av state flags
1074    */
1075   public void updateAnnotation()
1076   {
1077     updateAnnotation(false);
1078   }
1079
1080   public void updateAnnotation(boolean applyGlobalSettings)
1081   {
1082     updateAnnotation(applyGlobalSettings, false);
1083   }
1084
1085   public void updateAnnotation(boolean applyGlobalSettings,
1086           boolean preserveNewGroupSettings)
1087   {
1088     av.updateGroupAnnotationSettings(applyGlobalSettings,
1089             preserveNewGroupSettings);
1090     adjustAnnotationHeight();
1091   }
1092
1093   @Override
1094   public AlignmentI getAlignment()
1095   {
1096     return av.getAlignment();
1097   }
1098
1099   @Override
1100   public String getViewName()
1101   {
1102     return getName();
1103   }
1104
1105   @Override
1106   public StructureSelectionManager getStructureSelectionManager()
1107   {
1108     return StructureSelectionManager
1109             .getStructureSelectionManager(av.applet);
1110   }
1111
1112   @Override
1113   public void raiseOOMWarning(String string, OutOfMemoryError error)
1114   {
1115     // TODO: JAL-960
1116     System.err.println("Out of memory whilst '" + string + "'");
1117     error.printStackTrace();
1118   }
1119
1120   /**
1121    * Set a flag to say we are scrolling to follow a (cDNA/protein) complement.
1122    * 
1123    * @param b
1124    */
1125   protected void setToScrollComplementPanel(boolean b)
1126   {
1127     this.scrollComplementaryPanel = b;
1128   }
1129
1130   /**
1131    * Get whether to scroll complement panel
1132    * 
1133    * @return true if cDNA/protein complement panels should be scrolled
1134    */
1135   protected boolean isSetToScrollComplementPanel()
1136   {
1137     return this.scrollComplementaryPanel;
1138   }
1139
1140   @Override
1141   /**
1142    * Property change event fired when a change is made to the viewport ranges
1143    * object associated with this alignment panel's viewport
1144    */
1145   public void propertyChange(PropertyChangeEvent evt)
1146   {
1147     // update this panel's scroll values based on the new viewport ranges values
1148     int x = vpRanges.getStartRes();
1149     int y = vpRanges.getStartSeq();
1150     setScrollValues(x, y);
1151
1152     // now update any complementary alignment (its viewport ranges object
1153     // is different so does not get automatically updated)
1154     if (isSetToScrollComplementPanel())
1155     {
1156       setToScrollComplementPanel(false);
1157       av.scrollComplementaryAlignment(getComplementPanel());
1158       setToScrollComplementPanel(true);
1159     }
1160
1161   }
1162
1163   @Override
1164   public void overviewDone(BufferedImage miniMe)
1165   {
1166     overviewPanel.canvas.finalizeDraw(miniMe);
1167   }
1168
1169 }