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