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