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