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