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