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