JAL-2403 redundant 'implements' removed
[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.startRes = 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.startSeq = 0;
636     av.startRes = 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
767     int endSeq = y + vextent;
768     if (endSeq > av.getAlignment().getHeight())
769     {
770       endSeq = av.getAlignment().getHeight();
771     }
772
773     av.setEndSeq(endSeq);
774     av.setStartRes(x);
775     av.setEndRes((x + (seqPanel.seqCanvas.getSize().width / av
776             .getCharWidth())) - 1);
777
778     hscroll.setValues(x, hextent, 0, width);
779     vscroll.setValues(y, vextent, 0, height);
780
781     if (overviewPanel != null)
782     {
783       overviewPanel.setBoxPosition();
784     }
785     sendViewPosition();
786
787   }
788
789   @Override
790   public void adjustmentValueChanged(AdjustmentEvent evt)
791   {
792     int oldX = av.getStartRes();
793     int oldY = av.getStartSeq();
794
795     if (evt == null || evt.getSource() == apvscroll)
796     {
797       annotationPanel.setScrollOffset(apvscroll.getValue(), false);
798       alabels.setScrollOffset(apvscroll.getValue(), false);
799       // annotationPanel.image=null;
800       // alabels.image=null;
801       // alabels.repaint();
802       // annotationPanel.repaint();
803     }
804     if (evt == null || evt.getSource() == hscroll)
805     {
806       int x = hscroll.getValue();
807       av.setStartRes(x);
808       av.setEndRes(x + seqPanel.seqCanvas.getSize().width
809               / av.getCharWidth() - 1);
810     }
811
812     if (evt == null || evt.getSource() == vscroll)
813     {
814       int offy = vscroll.getValue();
815       if (av.getWrapAlignment())
816       {
817         int rowSize = seqPanel.seqCanvas
818                 .getWrappedCanvasWidth(seqPanel.seqCanvas.getSize().width);
819         av.setStartRes(vscroll.getValue() * rowSize);
820         av.setEndRes((vscroll.getValue() + 1) * rowSize);
821       }
822       else
823       {
824         av.setStartSeq(offy);
825         av.setEndSeq(offy + seqPanel.seqCanvas.getSize().height
826                 / av.getCharHeight());
827       }
828     }
829
830     if (overviewPanel != null)
831     {
832       overviewPanel.setBoxPosition();
833     }
834
835     int scrollX = av.startRes - oldX;
836     int scrollY = av.startSeq - oldY;
837
838     if (av.getWrapAlignment() || !fastPaint || av.MAC)
839     {
840       repaint();
841     }
842     else
843     {
844       // Make sure we're not trying to draw a panel
845       // larger than the visible window
846       if (scrollX > av.endRes - av.startRes)
847       {
848         scrollX = av.endRes - av.startRes;
849       }
850       else if (scrollX < av.startRes - av.endRes)
851       {
852         scrollX = av.startRes - av.endRes;
853       }
854
855       idPanel.idCanvas.fastPaint(scrollY);
856       seqPanel.seqCanvas.fastPaint(scrollX, scrollY);
857
858       scalePanel.repaint();
859       if (av.isShowAnnotation())
860       {
861         annotationPanel.fastPaint(av.getStartRes() - oldX);
862       }
863     }
864     sendViewPosition();
865
866     /*
867      * If there is one, scroll the (Protein/cDNA) complementary alignment to
868      * match, unless we are ourselves doing that.
869      */
870     if (isFollowingComplementScroll())
871     {
872       setFollowingComplementScroll(false);
873     }
874     else
875     {
876       AlignmentPanel ap = getComplementPanel();
877       av.scrollComplementaryAlignment(ap);
878     }
879
880   }
881
882   /**
883    * A helper method to return the AlignmentPanel in the other (complementary)
884    * half of a SplitFrame view. Returns null if not in a SplitFrame.
885    * 
886    * @return
887    */
888   private AlignmentPanel getComplementPanel()
889   {
890     AlignmentPanel ap = null;
891     if (alignFrame != null)
892     {
893       SplitFrame sf = alignFrame.getSplitFrame();
894       if (sf != null)
895       {
896         AlignFrame other = sf.getComplement(alignFrame);
897         if (other != null)
898         {
899           ap = other.alignPanel;
900         }
901       }
902     }
903     return ap;
904   }
905
906   /**
907    * Follow a scrolling change in the (cDNA/Protein) complementary alignment.
908    * The aim is to keep the two alignments 'lined up' on their centre columns.
909    * 
910    * @param sr
911    *          holds mapped region(s) of this alignment that we are scrolling
912    *          'to'; may be modified for sequence offset by this method
913    * @param seqOffset
914    *          the number of visible sequences to show above the mapped region
915    */
916   protected void scrollToCentre(SearchResultsI sr, int seqOffset)
917   {
918     /*
919      * To avoid jumpy vertical scrolling (if some sequences are gapped or not
920      * mapped), we can make the scroll-to location a sequence above the one
921      * actually mapped.
922      */
923     SequenceI mappedTo = sr.getResults().get(0).getSequence();
924     List<SequenceI> seqs = av.getAlignment().getSequences();
925
926     /*
927      * This is like AlignmentI.findIndex(seq) but here we are matching the
928      * dataset sequence not the aligned sequence
929      */
930     int sequenceIndex = 0;
931     boolean matched = false;
932     for (SequenceI seq : seqs)
933     {
934       if (mappedTo == seq.getDatasetSequence())
935       {
936         matched = true;
937         break;
938       }
939       sequenceIndex++;
940     }
941     if (!matched)
942     {
943       return; // failsafe, shouldn't happen
944     }
945
946     /*
947      * Scroll to position but centring the target residue. Also set a state flag
948      * to prevent adjustmentValueChanged performing this recursively.
949      */
950     setFollowingComplementScroll(true);
951     // this should be scrollToPosition(sr,verticalOffset,
952     scrollToPosition(sr, seqOffset, true, true);
953   }
954
955   private void sendViewPosition()
956   {
957     StructureSelectionManager.getStructureSelectionManager(av.applet)
958             .sendViewPosition(this, av.startRes, av.endRes, av.startSeq,
959                     av.endSeq);
960   }
961
962   /**
963    * Repaint the alignment and annotations, and, optionally, any overview window
964    */
965   @Override
966   public void paintAlignment(boolean updateOverview)
967   {
968     final AnnotationSorter sorter = new AnnotationSorter(getAlignment(),
969             av.isShowAutocalculatedAbove());
970     sorter.sort(getAlignment().getAlignmentAnnotation(),
971             av.getSortAnnotationsBy());
972     repaint();
973
974     if (updateOverview)
975     {
976       // TODO: determine if this paintAlignment changed structure colours
977       jalview.structure.StructureSelectionManager
978               .getStructureSelectionManager(av.applet)
979               .sequenceColoursChanged(this);
980
981       if (overviewPanel != null)
982       {
983         overviewPanel.updateOverviewImage();
984       }
985     }
986   }
987
988   @Override
989   public void update(Graphics g)
990   {
991     paint(g);
992   }
993
994   @Override
995   public void paint(Graphics g)
996   {
997     invalidate();
998     Dimension d = idPanel.idCanvas.getSize();
999     final int canvasHeight = seqPanel.seqCanvas.getSize().height;
1000     if (canvasHeight != d.height)
1001     {
1002       idPanel.idCanvas.setSize(d.width, canvasHeight);
1003     }
1004
1005     if (av.getWrapAlignment())
1006     {
1007       int maxwidth = av.getAlignment().getWidth();
1008
1009       if (av.hasHiddenColumns())
1010       {
1011         maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
1012       }
1013
1014       int canvasWidth = seqPanel.seqCanvas
1015               .getWrappedCanvasWidth(seqPanel.seqCanvas.getSize().width);
1016
1017       if (canvasWidth > 0)
1018       {
1019         int max = maxwidth / canvasWidth;
1020         vscroll.setMaximum(1 + max);
1021         vscroll.setUnitIncrement(1);
1022         vscroll.setVisibleAmount(1);
1023       }
1024     }
1025     else
1026     {
1027       setScrollValues(av.getStartRes(), av.getStartSeq());
1028     }
1029
1030     seqPanel.seqCanvas.repaint();
1031     idPanel.idCanvas.repaint();
1032     if (!av.getWrapAlignment())
1033     {
1034       if (av.isShowAnnotation())
1035       {
1036         alabels.repaint();
1037         annotationPanel.repaint();
1038       }
1039       scalePanel.repaint();
1040     }
1041
1042   }
1043
1044   protected Panel sequenceHolderPanel = new Panel();
1045
1046   protected Scrollbar vscroll = new Scrollbar();
1047
1048   protected Scrollbar hscroll = new Scrollbar();
1049
1050   protected Panel seqPanelHolder = new Panel();
1051
1052   protected Panel scalePanelHolder = new Panel();
1053
1054   protected Panel idPanelHolder = new Panel();
1055
1056   protected Panel idSpaceFillerPanel1 = new Panel();
1057
1058   public Panel annotationSpaceFillerHolder = new Panel();
1059
1060   protected Panel hscrollFillerPanel = new Panel();
1061
1062   Panel annotationPanelHolder = new Panel();
1063
1064   protected Scrollbar apvscroll = new Scrollbar();
1065
1066   /*
1067    * Flag set while scrolling to follow complementary cDNA/protein scroll. When
1068    * true, suppresses invoking the same method recursively.
1069    */
1070   private boolean followingComplementScroll;
1071
1072   private void jbInit() throws Exception
1073   {
1074     // idPanelHolder.setPreferredSize(new Dimension(70, 10));
1075     this.setLayout(new BorderLayout());
1076
1077     // sequenceHolderPanel.setPreferredSize(new Dimension(150, 150));
1078     sequenceHolderPanel.setLayout(new BorderLayout());
1079     seqPanelHolder.setLayout(new BorderLayout());
1080     scalePanelHolder.setBackground(Color.white);
1081
1082     // scalePanelHolder.setPreferredSize(new Dimension(10, 30));
1083     scalePanelHolder.setLayout(new BorderLayout());
1084     idPanelHolder.setLayout(new BorderLayout());
1085     idSpaceFillerPanel1.setBackground(Color.white);
1086
1087     // idSpaceFillerPanel1.setPreferredSize(new Dimension(10, 30));
1088     idSpaceFillerPanel1.setLayout(new BorderLayout());
1089     annotationSpaceFillerHolder.setBackground(Color.white);
1090
1091     // annotationSpaceFillerHolder.setPreferredSize(new Dimension(10, 80));
1092     annotationSpaceFillerHolder.setLayout(new BorderLayout());
1093     hscroll.setOrientation(Scrollbar.HORIZONTAL);
1094
1095     Panel hscrollHolder = new Panel();
1096     hscrollHolder.setLayout(new BorderLayout());
1097     hscrollFillerPanel.setBackground(Color.white);
1098     apvscroll.setOrientation(Scrollbar.VERTICAL);
1099     apvscroll.setVisible(true);
1100     apvscroll.addAdjustmentListener(this);
1101
1102     annotationPanelHolder.setBackground(Color.white);
1103     annotationPanelHolder.setLayout(new BorderLayout());
1104     annotationPanelHolder.add(apvscroll, BorderLayout.EAST);
1105     // hscrollFillerPanel.setPreferredSize(new Dimension(70, 10));
1106     hscrollHolder.setBackground(Color.white);
1107
1108     // annotationScroller.setPreferredSize(new Dimension(10, 80));
1109     // this.setPreferredSize(new Dimension(220, 166));
1110     seqPanelHolder.setBackground(Color.white);
1111     idPanelHolder.setBackground(Color.white);
1112     sequenceHolderPanel.add(scalePanelHolder, BorderLayout.NORTH);
1113     sequenceHolderPanel.add(seqPanelHolder, BorderLayout.CENTER);
1114     seqPanelHolder.add(vscroll, BorderLayout.EAST);
1115
1116     // Panel3.add(secondaryPanelHolder, BorderLayout.SOUTH);
1117     this.add(idPanelHolder, BorderLayout.WEST);
1118     idPanelHolder.add(idSpaceFillerPanel1, BorderLayout.NORTH);
1119     idPanelHolder.add(annotationSpaceFillerHolder, BorderLayout.SOUTH);
1120     this.add(hscrollHolder, BorderLayout.SOUTH);
1121     hscrollHolder.add(hscroll, BorderLayout.CENTER);
1122     hscrollHolder.add(hscrollFillerPanel, BorderLayout.WEST);
1123     this.add(sequenceHolderPanel, BorderLayout.CENTER);
1124   }
1125
1126   /**
1127    * hides or shows dynamic annotation rows based on groups and av state flags
1128    */
1129   public void updateAnnotation()
1130   {
1131     updateAnnotation(false);
1132   }
1133
1134   public void updateAnnotation(boolean applyGlobalSettings)
1135   {
1136     updateAnnotation(applyGlobalSettings, false);
1137   }
1138
1139   public void updateAnnotation(boolean applyGlobalSettings,
1140           boolean preserveNewGroupSettings)
1141   {
1142     av.updateGroupAnnotationSettings(applyGlobalSettings,
1143             preserveNewGroupSettings);
1144     adjustAnnotationHeight();
1145   }
1146
1147   @Override
1148   public AlignmentI getAlignment()
1149   {
1150     return av.getAlignment();
1151   }
1152
1153   @Override
1154   public String getViewName()
1155   {
1156     return getName();
1157   }
1158
1159   @Override
1160   public StructureSelectionManager getStructureSelectionManager()
1161   {
1162     return StructureSelectionManager
1163             .getStructureSelectionManager(av.applet);
1164   }
1165
1166   @Override
1167   public void raiseOOMWarning(String string, OutOfMemoryError error)
1168   {
1169     // TODO: JAL-960
1170     System.err.println("Out of memory whilst '" + string + "'");
1171     error.printStackTrace();
1172   }
1173
1174   /**
1175    * Set a flag to say we are scrolling to follow a (cDNA/protein) complement.
1176    * 
1177    * @param b
1178    */
1179   protected void setFollowingComplementScroll(boolean b)
1180   {
1181     this.followingComplementScroll = b;
1182   }
1183
1184   protected boolean isFollowingComplementScroll()
1185   {
1186     return this.followingComplementScroll;
1187   }
1188
1189 }