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