JAL-2388 Hidden cols separated from column selection (almost complete)
[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       AlignmentI al = av.getAlignment();
423       start = al.getHiddenColumns().findColumnPosition(ostart);
424       end = al.getHiddenColumns().findColumnPosition(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(), starts + seqIndex - ends
504                 + 1);
505       }
506       /*
507        * Else results are already visible - no need to scroll
508        */
509     }
510     else
511     {
512       scrollToWrappedVisible(start);
513     }
514     if (redrawOverview && overviewPanel != null)
515     {
516       overviewPanel.setBoxPosition();
517     }
518     paintAlignment(redrawOverview);
519     return true;
520   }
521
522   void scrollToWrappedVisible(int res)
523   {
524     int cwidth = seqPanel.seqCanvas
525             .getWrappedCanvasWidth(seqPanel.seqCanvas.getSize().width);
526     if (res <= vpRanges.getStartRes()
527             || res >= (vpRanges.getStartRes() + cwidth))
528     {
529       vscroll.setValue(res / cwidth);
530       vpRanges.setStartRes(vscroll.getValue() * cwidth);
531     }
532   }
533
534   public OverviewPanel getOverviewPanel()
535   {
536     return overviewPanel;
537   }
538
539   public void setOverviewPanel(OverviewPanel op)
540   {
541     overviewPanel = op;
542   }
543
544   public void setAnnotationVisible(boolean b)
545   {
546     if (!av.getWrapAlignment())
547     {
548       annotationSpaceFillerHolder.setVisible(b);
549       annotationPanelHolder.setVisible(b);
550     }
551     else
552     {
553       annotationSpaceFillerHolder.setVisible(false);
554       annotationPanelHolder.setVisible(false);
555     }
556     validate();
557     repaint();
558   }
559
560   /**
561    * automatically adjust annotation panel height for new annotation whilst
562    * ensuring the alignment is still visible.
563    */
564   @Override
565   public void adjustAnnotationHeight()
566   {
567     // TODO: display vertical annotation scrollbar if necessary
568     // this is called after loading new annotation onto alignment
569     if (alignFrame.getSize().height == 0)
570     {
571       System.out
572               .println("adjustAnnotationHeight frame size zero NEEDS FIXING");
573     }
574     fontChanged();
575     validateAnnotationDimensions(true);
576     apvscroll.addNotify();
577     hscroll.addNotify();
578     validate();
579     paintAlignment(true);
580   }
581
582   /**
583    * Calculate the annotation dimensions and refresh slider values accordingly.
584    * Need to do repaints/notifys afterwards.
585    */
586   protected void validateAnnotationDimensions(boolean adjustPanelHeight)
587   {
588     int rowHeight = av.getCharHeight();
589     int alignmentHeight = rowHeight * av.getAlignment().getHeight();
590     int annotationHeight = av.calcPanelHeight();
591
592     int mheight = annotationHeight;
593     Dimension d = sequenceHolderPanel.getSize();
594
595     int availableHeight = d.height - scalePanelHolder.getHeight();
596
597     if (adjustPanelHeight)
598     {
599       /*
600        * If not enough vertical space, maximize annotation height while keeping
601        * at least two rows of alignment visible
602        */
603       if (annotationHeight + alignmentHeight > availableHeight)
604       {
605         annotationHeight = Math.min(annotationHeight, availableHeight - 2
606                 * rowHeight);
607       }
608     }
609     else
610     {
611       // maintain same window layout whilst updating sliders
612       annotationHeight = annotationPanelHolder.getSize().height;
613     }
614
615     if (availableHeight - annotationHeight < 5)
616     {
617       annotationHeight = availableHeight;
618     }
619
620     annotationPanel.setSize(new Dimension(d.width, annotationHeight));
621     annotationPanelHolder.setSize(new Dimension(d.width, annotationHeight));
622     // seqPanelHolder.setSize(d.width, seqandannot - height);
623     seqPanel.seqCanvas
624             .setSize(d.width, seqPanel.seqCanvas.getSize().height);
625
626     Dimension e = idPanel.getSize();
627     alabels.setSize(new Dimension(e.width, annotationHeight));
628     annotationSpaceFillerHolder.setSize(new Dimension(e.width,
629             annotationHeight));
630
631     int s = apvscroll.getValue();
632     if (s > mheight - annotationHeight)
633     {
634       s = 0;
635     }
636     apvscroll.setValues(s, annotationHeight, 0, mheight);
637     annotationPanel.setScrollOffset(apvscroll.getValue(), false);
638     alabels.setScrollOffset(apvscroll.getValue(), false);
639   }
640
641   public void setWrapAlignment(boolean wrap)
642   {
643     vpRanges.setStartSeq(0);
644     vpRanges.setStartRes(0);
645     scalePanelHolder.setVisible(!wrap);
646
647     hscroll.setVisible(!wrap);
648     idwidthAdjuster.setVisible(!wrap);
649
650     if (wrap)
651     {
652       annotationPanelHolder.setVisible(false);
653       annotationSpaceFillerHolder.setVisible(false);
654     }
655     else if (av.isShowAnnotation())
656     {
657       annotationPanelHolder.setVisible(true);
658       annotationSpaceFillerHolder.setVisible(true);
659     }
660
661     idSpaceFillerPanel1.setVisible(!wrap);
662
663     fontChanged(); // This is so that the scalePanel is resized correctly
664
665     validate();
666     sequenceHolderPanel.validate();
667     repaint();
668
669   }
670
671   int hextent = 0;
672
673   int vextent = 0;
674
675   // return value is true if the scroll is valid
676   public boolean scrollUp(boolean up)
677   {
678     if (up)
679     {
680       if (vscroll.getValue() < 1)
681       {
682         return false;
683       }
684       setScrollValues(hscroll.getValue(), vscroll.getValue() - 1);
685     }
686     else
687     {
688       if (vextent + vscroll.getValue() >= av.getAlignment().getHeight())
689       {
690         return false;
691       }
692       setScrollValues(hscroll.getValue(), vscroll.getValue() + 1);
693     }
694
695     repaint();
696     return true;
697   }
698
699   public boolean scrollRight(boolean right)
700   {
701     if (!right)
702     {
703       if (hscroll.getValue() < 1)
704       {
705         return false;
706       }
707       setScrollValues(hscroll.getValue() - 1, vscroll.getValue());
708     }
709     else
710     {
711       if (hextent + hscroll.getValue() >= av.getAlignment().getWidth())
712       {
713         return false;
714       }
715       setScrollValues(hscroll.getValue() + 1, vscroll.getValue());
716     }
717
718     repaint();
719     return true;
720   }
721
722   public void setScrollValues(int x, int y)
723   {
724     int width = av.getAlignment().getWidth();
725     int height = av.getAlignment().getHeight();
726
727     if (av.hasHiddenColumns())
728     {
729       width = av.getAlignment().getHiddenColumns()
730               .findColumnPosition(width);
731     }
732     if (x < 0)
733     {
734       x = 0;
735     }
736
737
738     hextent = seqPanel.seqCanvas.getSize().width / av.getCharWidth();
739     vextent = seqPanel.seqCanvas.getSize().height / av.getCharHeight();
740
741     if (hextent > width)
742     {
743       hextent = width;
744     }
745
746     if (vextent > height)
747     {
748       vextent = height;
749     }
750
751     if ((hextent + x) > width)
752     {
753       System.err.println("hextent was " + hextent + " and x was " + x);
754
755       x = width - hextent;
756     }
757
758     if ((vextent + y) > height)
759     {
760       y = height - vextent;
761     }
762
763     if (y < 0)
764     {
765       y = 0;
766     }
767
768     if (x < 0)
769     {
770       System.err.println("x was " + x);
771       x = 0;
772     }
773
774     vpRanges.setStartSeq(y);
775     vpRanges.setEndSeq(y + vextent);
776     vpRanges.setStartRes(x);
777     vpRanges.setEndRes((x + (seqPanel.seqCanvas.getSize().width / av
778             .getCharWidth())) - 1);
779
780     hscroll.setValues(x, hextent, 0, width);
781     vscroll.setValues(y, vextent, 0, height);
782
783     if (overviewPanel != null)
784     {
785       overviewPanel.setBoxPosition();
786     }
787     sendViewPosition();
788
789   }
790
791   @Override
792   public void adjustmentValueChanged(AdjustmentEvent evt)
793   {
794     int oldX = vpRanges.getStartRes();
795     int oldY = vpRanges.getStartSeq();
796
797     if (evt == null || evt.getSource() == apvscroll)
798     {
799       annotationPanel.setScrollOffset(apvscroll.getValue(), false);
800       alabels.setScrollOffset(apvscroll.getValue(), false);
801       // annotationPanel.image=null;
802       // alabels.image=null;
803       // alabels.repaint();
804       // annotationPanel.repaint();
805     }
806     if (evt == null || evt.getSource() == hscroll)
807     {
808       int x = hscroll.getValue();
809       vpRanges.setStartRes(x);
810       vpRanges.setEndRes(x + seqPanel.seqCanvas.getSize().width
811               / av.getCharWidth() - 1);
812     }
813
814     if (evt == null || evt.getSource() == vscroll)
815     {
816       int offy = vscroll.getValue();
817       if (av.getWrapAlignment())
818       {
819         int rowSize = seqPanel.seqCanvas
820                 .getWrappedCanvasWidth(seqPanel.seqCanvas.getSize().width);
821         vpRanges.setStartRes(vscroll.getValue() * rowSize);
822         vpRanges.setEndRes((vscroll.getValue() + 1) * rowSize);
823       }
824       else
825       {
826         vpRanges.setStartSeq(offy);
827         vpRanges.setEndSeq(offy + seqPanel.seqCanvas.getSize().height
828                 / av.getCharHeight() - 1);
829       }
830     }
831
832     if (overviewPanel != null)
833     {
834       overviewPanel.setBoxPosition();
835     }
836
837     int scrollX = vpRanges.getStartRes() - oldX;
838     int scrollY = vpRanges.getStartSeq() - oldY;
839
840     if (av.getWrapAlignment() || !fastPaint || av.MAC)
841     {
842       repaint();
843     }
844     else
845     {
846       // Make sure we're not trying to draw a panel
847       // larger than the visible window
848       if (scrollX > vpRanges.getEndRes() - vpRanges.getStartRes())
849       {
850         scrollX = vpRanges.getEndRes() - vpRanges.getStartRes();
851       }
852       else if (scrollX < vpRanges.getStartRes() - vpRanges.getEndRes())
853       {
854         scrollX = vpRanges.getStartRes() - vpRanges.getEndRes();
855       }
856
857       idPanel.idCanvas.fastPaint(scrollY);
858       seqPanel.seqCanvas.fastPaint(scrollX, scrollY);
859
860       scalePanel.repaint();
861       if (av.isShowAnnotation())
862       {
863         annotationPanel.fastPaint(vpRanges.getStartRes() - oldX);
864       }
865     }
866     sendViewPosition();
867
868     /*
869      * If there is one, scroll the (Protein/cDNA) complementary alignment to
870      * match, unless we are ourselves doing that.
871      */
872     if (isFollowingComplementScroll())
873     {
874       setFollowingComplementScroll(false);
875     }
876     else
877     {
878       AlignmentPanel ap = getComplementPanel();
879       av.scrollComplementaryAlignment(ap);
880     }
881
882   }
883
884   /**
885    * A helper method to return the AlignmentPanel in the other (complementary)
886    * half of a SplitFrame view. Returns null if not in a SplitFrame.
887    * 
888    * @return
889    */
890   private AlignmentPanel getComplementPanel()
891   {
892     AlignmentPanel ap = null;
893     if (alignFrame != null)
894     {
895       SplitFrame sf = alignFrame.getSplitFrame();
896       if (sf != null)
897       {
898         AlignFrame other = sf.getComplement(alignFrame);
899         if (other != null)
900         {
901           ap = other.alignPanel;
902         }
903       }
904     }
905     return ap;
906   }
907
908   /**
909    * Follow a scrolling change in the (cDNA/Protein) complementary alignment.
910    * The aim is to keep the two alignments 'lined up' on their centre columns.
911    * 
912    * @param sr
913    *          holds mapped region(s) of this alignment that we are scrolling
914    *          'to'; may be modified for sequence offset by this method
915    * @param seqOffset
916    *          the number of visible sequences to show above the mapped region
917    */
918   protected void scrollToCentre(SearchResultsI sr, int seqOffset)
919   {
920     /*
921      * To avoid jumpy vertical scrolling (if some sequences are gapped or not
922      * mapped), we can make the scroll-to location a sequence above the one
923      * actually mapped.
924      */
925     SequenceI mappedTo = sr.getResults().get(0).getSequence();
926     List<SequenceI> seqs = av.getAlignment().getSequences();
927
928     /*
929      * This is like AlignmentI.findIndex(seq) but here we are matching the
930      * dataset sequence not the aligned sequence
931      */
932     int sequenceIndex = 0;
933     boolean matched = false;
934     for (SequenceI seq : seqs)
935     {
936       if (mappedTo == seq.getDatasetSequence())
937       {
938         matched = true;
939         break;
940       }
941       sequenceIndex++;
942     }
943     if (!matched)
944     {
945       return; // failsafe, shouldn't happen
946     }
947
948     /*
949      * Scroll to position but centring the target residue. Also set a state flag
950      * to prevent adjustmentValueChanged performing this recursively.
951      */
952     setFollowingComplementScroll(true);
953     // this should be scrollToPosition(sr,verticalOffset,
954     scrollToPosition(sr, seqOffset, true, true);
955   }
956
957   private void sendViewPosition()
958   {
959     StructureSelectionManager.getStructureSelectionManager(av.applet)
960             .sendViewPosition(this, vpRanges.getStartRes(),
961                     vpRanges.getEndRes(), vpRanges.getStartSeq(),
962                     vpRanges.getEndSeq());
963   }
964
965   /**
966    * Repaint the alignment and annotations, and, optionally, any overview window
967    */
968   @Override
969   public void paintAlignment(boolean updateOverview)
970   {
971     final AnnotationSorter sorter = new AnnotationSorter(getAlignment(),
972             av.isShowAutocalculatedAbove());
973     sorter.sort(getAlignment().getAlignmentAnnotation(),
974             av.getSortAnnotationsBy());
975     repaint();
976
977     if (updateOverview)
978     {
979       // TODO: determine if this paintAlignment changed structure colours
980       jalview.structure.StructureSelectionManager
981               .getStructureSelectionManager(av.applet)
982               .sequenceColoursChanged(this);
983
984       if (overviewPanel != null)
985       {
986         overviewPanel.updateOverviewImage();
987       }
988     }
989   }
990
991   @Override
992   public void update(Graphics g)
993   {
994     paint(g);
995   }
996
997   @Override
998   public void paint(Graphics g)
999   {
1000     invalidate();
1001     Dimension d = idPanel.idCanvas.getSize();
1002     final int canvasHeight = seqPanel.seqCanvas.getSize().height;
1003     if (canvasHeight != d.height)
1004     {
1005       idPanel.idCanvas.setSize(d.width, canvasHeight);
1006     }
1007
1008     if (av.getWrapAlignment())
1009     {
1010       int maxwidth = av.getAlignment().getWidth();
1011
1012       if (av.hasHiddenColumns())
1013       {
1014         maxwidth = av.getAlignment().getHiddenColumns()
1015                 .findColumnPosition(maxwidth) - 1;
1016       }
1017
1018       int canvasWidth = seqPanel.seqCanvas
1019               .getWrappedCanvasWidth(seqPanel.seqCanvas.getSize().width);
1020
1021       if (canvasWidth > 0)
1022       {
1023         int max = maxwidth / canvasWidth;
1024         vscroll.setMaximum(1 + max);
1025         vscroll.setUnitIncrement(1);
1026         vscroll.setVisibleAmount(1);
1027       }
1028     }
1029     else
1030     {
1031       setScrollValues(vpRanges.getStartRes(), vpRanges.getStartSeq());
1032     }
1033
1034     seqPanel.seqCanvas.repaint();
1035     idPanel.idCanvas.repaint();
1036     if (!av.getWrapAlignment())
1037     {
1038       if (av.isShowAnnotation())
1039       {
1040         alabels.repaint();
1041         annotationPanel.repaint();
1042       }
1043       scalePanel.repaint();
1044     }
1045
1046   }
1047
1048   protected Panel sequenceHolderPanel = new Panel();
1049
1050   protected Scrollbar vscroll = new Scrollbar();
1051
1052   protected Scrollbar hscroll = new Scrollbar();
1053
1054   protected Panel seqPanelHolder = new Panel();
1055
1056   protected Panel scalePanelHolder = new Panel();
1057
1058   protected Panel idPanelHolder = new Panel();
1059
1060   protected Panel idSpaceFillerPanel1 = new Panel();
1061
1062   public Panel annotationSpaceFillerHolder = new Panel();
1063
1064   protected Panel hscrollFillerPanel = new Panel();
1065
1066   Panel annotationPanelHolder = new Panel();
1067
1068   protected Scrollbar apvscroll = new Scrollbar();
1069
1070   /*
1071    * Flag set while scrolling to follow complementary cDNA/protein scroll. When
1072    * true, suppresses invoking the same method recursively.
1073    */
1074   private boolean followingComplementScroll;
1075
1076   private void jbInit() throws Exception
1077   {
1078     // idPanelHolder.setPreferredSize(new Dimension(70, 10));
1079     this.setLayout(new BorderLayout());
1080
1081     // sequenceHolderPanel.setPreferredSize(new Dimension(150, 150));
1082     sequenceHolderPanel.setLayout(new BorderLayout());
1083     seqPanelHolder.setLayout(new BorderLayout());
1084     scalePanelHolder.setBackground(Color.white);
1085
1086     // scalePanelHolder.setPreferredSize(new Dimension(10, 30));
1087     scalePanelHolder.setLayout(new BorderLayout());
1088     idPanelHolder.setLayout(new BorderLayout());
1089     idSpaceFillerPanel1.setBackground(Color.white);
1090
1091     // idSpaceFillerPanel1.setPreferredSize(new Dimension(10, 30));
1092     idSpaceFillerPanel1.setLayout(new BorderLayout());
1093     annotationSpaceFillerHolder.setBackground(Color.white);
1094
1095     // annotationSpaceFillerHolder.setPreferredSize(new Dimension(10, 80));
1096     annotationSpaceFillerHolder.setLayout(new BorderLayout());
1097     hscroll.setOrientation(Scrollbar.HORIZONTAL);
1098
1099     Panel hscrollHolder = new Panel();
1100     hscrollHolder.setLayout(new BorderLayout());
1101     hscrollFillerPanel.setBackground(Color.white);
1102     apvscroll.setOrientation(Scrollbar.VERTICAL);
1103     apvscroll.setVisible(true);
1104     apvscroll.addAdjustmentListener(this);
1105
1106     annotationPanelHolder.setBackground(Color.white);
1107     annotationPanelHolder.setLayout(new BorderLayout());
1108     annotationPanelHolder.add(apvscroll, BorderLayout.EAST);
1109     // hscrollFillerPanel.setPreferredSize(new Dimension(70, 10));
1110     hscrollHolder.setBackground(Color.white);
1111
1112     // annotationScroller.setPreferredSize(new Dimension(10, 80));
1113     // this.setPreferredSize(new Dimension(220, 166));
1114     seqPanelHolder.setBackground(Color.white);
1115     idPanelHolder.setBackground(Color.white);
1116     sequenceHolderPanel.add(scalePanelHolder, BorderLayout.NORTH);
1117     sequenceHolderPanel.add(seqPanelHolder, BorderLayout.CENTER);
1118     seqPanelHolder.add(vscroll, BorderLayout.EAST);
1119
1120     // Panel3.add(secondaryPanelHolder, BorderLayout.SOUTH);
1121     this.add(idPanelHolder, BorderLayout.WEST);
1122     idPanelHolder.add(idSpaceFillerPanel1, BorderLayout.NORTH);
1123     idPanelHolder.add(annotationSpaceFillerHolder, BorderLayout.SOUTH);
1124     this.add(hscrollHolder, BorderLayout.SOUTH);
1125     hscrollHolder.add(hscroll, BorderLayout.CENTER);
1126     hscrollHolder.add(hscrollFillerPanel, BorderLayout.WEST);
1127     this.add(sequenceHolderPanel, BorderLayout.CENTER);
1128   }
1129
1130   /**
1131    * hides or shows dynamic annotation rows based on groups and av state flags
1132    */
1133   public void updateAnnotation()
1134   {
1135     updateAnnotation(false);
1136   }
1137
1138   public void updateAnnotation(boolean applyGlobalSettings)
1139   {
1140     updateAnnotation(applyGlobalSettings, false);
1141   }
1142
1143   public void updateAnnotation(boolean applyGlobalSettings,
1144           boolean preserveNewGroupSettings)
1145   {
1146     av.updateGroupAnnotationSettings(applyGlobalSettings,
1147             preserveNewGroupSettings);
1148     adjustAnnotationHeight();
1149   }
1150
1151   @Override
1152   public AlignmentI getAlignment()
1153   {
1154     return av.getAlignment();
1155   }
1156
1157   @Override
1158   public String getViewName()
1159   {
1160     return getName();
1161   }
1162
1163   @Override
1164   public StructureSelectionManager getStructureSelectionManager()
1165   {
1166     return StructureSelectionManager
1167             .getStructureSelectionManager(av.applet);
1168   }
1169
1170   @Override
1171   public void raiseOOMWarning(String string, OutOfMemoryError error)
1172   {
1173     // TODO: JAL-960
1174     System.err.println("Out of memory whilst '" + string + "'");
1175     error.printStackTrace();
1176   }
1177
1178   /**
1179    * Set a flag to say we are scrolling to follow a (cDNA/protein) complement.
1180    * 
1181    * @param b
1182    */
1183   protected void setFollowingComplementScroll(boolean b)
1184   {
1185     this.followingComplementScroll = b;
1186   }
1187
1188   protected boolean isFollowingComplementScroll()
1189   {
1190     return this.followingComplementScroll;
1191   }
1192
1193 }