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