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