JAL-1432 updated copyright notices
[jalview.git] / src / jalview / appletgui / SeqPanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.0b1)
3  * Copyright (C) 2014 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 of the License, or (at your option) any later version.
10  *  
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  * The Jalview Authors are detailed in the 'AUTHORS' file.
18  */
19 package jalview.appletgui;
20
21 import java.util.*;
22
23 import java.awt.*;
24 import java.awt.event.*;
25
26 import jalview.commands.*;
27 import jalview.datamodel.*;
28 import jalview.schemes.*;
29 import jalview.structure.SelectionSource;
30 import jalview.structure.SequenceListener;
31 import jalview.structure.StructureSelectionManager;
32
33 public class SeqPanel extends Panel implements MouseMotionListener,
34         MouseListener, SequenceListener
35 {
36
37   public SeqCanvas seqCanvas;
38
39   public AlignmentPanel ap;
40
41   protected int lastres;
42
43   protected int startseq;
44
45   protected AlignViewport av;
46
47   // if character is inserted or deleted, we will need to recalculate the
48   // conservation
49   boolean seqEditOccurred = false;
50
51   ScrollThread scrollThread = null;
52
53   boolean mouseDragging = false;
54
55   boolean editingSeqs = false;
56
57   boolean groupEditing = false;
58
59   int oldSeq = -1;
60
61   boolean changeEndSeq = false;
62
63   boolean changeStartSeq = false;
64
65   boolean changeEndRes = false;
66
67   boolean changeStartRes = false;
68
69   SequenceGroup stretchGroup = null;
70
71   StringBuffer keyboardNo1;
72
73   StringBuffer keyboardNo2;
74
75   boolean mouseWheelPressed = false;
76
77   Point lastMousePress;
78
79   EditCommand editCommand;
80
81   StructureSelectionManager ssm;
82
83   public SeqPanel(AlignViewport avp, AlignmentPanel p)
84   {
85     this.av = avp;
86
87     seqCanvas = new SeqCanvas(avp);
88     setLayout(new BorderLayout());
89     add(seqCanvas);
90
91     ap = p;
92
93     seqCanvas.addMouseMotionListener(this);
94     seqCanvas.addMouseListener(this);
95     ssm = StructureSelectionManager.getStructureSelectionManager(av.applet);
96     ssm.addStructureViewerListener(this);
97
98     seqCanvas.repaint();
99   }
100
101   void endEditing()
102   {
103     if (editCommand != null && editCommand.getSize() > 0)
104     {
105       ap.alignFrame.addHistoryItem(editCommand);
106       av.firePropertyChange("alignment", null, av.getAlignment()
107               .getSequences());
108     }
109
110     startseq = -1;
111     lastres = -1;
112     editingSeqs = false;
113     groupEditing = false;
114     keyboardNo1 = null;
115     keyboardNo2 = null;
116     editCommand = null;
117   }
118
119   void setCursorRow()
120   {
121     seqCanvas.cursorY = getKeyboardNo1() - 1;
122     scrollToVisible();
123   }
124
125   void setCursorColumn()
126   {
127     seqCanvas.cursorX = getKeyboardNo1() - 1;
128     scrollToVisible();
129   }
130
131   void setCursorRowAndColumn()
132   {
133     if (keyboardNo2 == null)
134     {
135       keyboardNo2 = new StringBuffer();
136     }
137     else
138     {
139       seqCanvas.cursorX = getKeyboardNo1() - 1;
140       seqCanvas.cursorY = getKeyboardNo2() - 1;
141       scrollToVisible();
142     }
143   }
144
145   void setCursorPosition()
146   {
147     SequenceI sequence = (Sequence) av.getAlignment().getSequenceAt(
148             seqCanvas.cursorY);
149
150     seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1()) - 1;
151     scrollToVisible();
152   }
153
154   void moveCursor(int dx, int dy)
155   {
156     seqCanvas.cursorX += dx;
157     seqCanvas.cursorY += dy;
158     if (av.hasHiddenColumns()
159             && !av.getColumnSelection().isVisible(seqCanvas.cursorX))
160     {
161       int original = seqCanvas.cursorX - dx;
162       int maxWidth = av.getAlignment().getWidth();
163
164       while (!av.getColumnSelection().isVisible(seqCanvas.cursorX)
165               && seqCanvas.cursorX < maxWidth && seqCanvas.cursorX > 0)
166       {
167         seqCanvas.cursorX += dx;
168       }
169
170       if (seqCanvas.cursorX >= maxWidth
171               || !av.getColumnSelection().isVisible(seqCanvas.cursorX))
172       {
173         seqCanvas.cursorX = original;
174       }
175     }
176     scrollToVisible();
177   }
178
179   void scrollToVisible()
180   {
181     if (seqCanvas.cursorX < 0)
182     {
183       seqCanvas.cursorX = 0;
184     }
185     else if (seqCanvas.cursorX > av.getAlignment().getWidth() - 1)
186     {
187       seqCanvas.cursorX = av.getAlignment().getWidth() - 1;
188     }
189
190     if (seqCanvas.cursorY < 0)
191     {
192       seqCanvas.cursorY = 0;
193     }
194     else if (seqCanvas.cursorY > av.getAlignment().getHeight() - 1)
195     {
196       seqCanvas.cursorY = av.getAlignment().getHeight() - 1;
197     }
198
199     endEditing();
200     if (av.wrapAlignment)
201     {
202       ap.scrollToWrappedVisible(seqCanvas.cursorX);
203     }
204     else
205     {
206       while (seqCanvas.cursorY < av.startSeq)
207       {
208         ap.scrollUp(true);
209       }
210       while (seqCanvas.cursorY + 1 > av.endSeq)
211       {
212         ap.scrollUp(false);
213       }
214       while (seqCanvas.cursorX < av.getColumnSelection()
215               .adjustForHiddenColumns(av.startRes))
216       {
217
218         if (!ap.scrollRight(false))
219         {
220           break;
221         }
222       }
223       while (seqCanvas.cursorX > av.getColumnSelection()
224               .adjustForHiddenColumns(av.endRes))
225       {
226         if (!ap.scrollRight(true))
227         {
228           break;
229         }
230       }
231     }
232     setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY),
233             seqCanvas.cursorX, seqCanvas.cursorY);
234
235     seqCanvas.repaint();
236   }
237
238   void setSelectionAreaAtCursor(boolean topLeft)
239   {
240     SequenceI sequence = (Sequence) av.getAlignment().getSequenceAt(
241             seqCanvas.cursorY);
242
243     if (av.getSelectionGroup() != null)
244     {
245       SequenceGroup sg = av.getSelectionGroup();
246       // Find the top and bottom of this group
247       int min = av.getAlignment().getHeight(), max = 0;
248       for (int i = 0; i < sg.getSize(); i++)
249       {
250         int index = av.getAlignment().findIndex(sg.getSequenceAt(i));
251         if (index > max)
252         {
253           max = index;
254         }
255         if (index < min)
256         {
257           min = index;
258         }
259       }
260
261       max++;
262
263       if (topLeft)
264       {
265         sg.setStartRes(seqCanvas.cursorX);
266         if (sg.getEndRes() < seqCanvas.cursorX)
267         {
268           sg.setEndRes(seqCanvas.cursorX);
269         }
270
271         min = seqCanvas.cursorY;
272       }
273       else
274       {
275         sg.setEndRes(seqCanvas.cursorX);
276         if (sg.getStartRes() > seqCanvas.cursorX)
277         {
278           sg.setStartRes(seqCanvas.cursorX);
279         }
280
281         max = seqCanvas.cursorY + 1;
282       }
283
284       if (min > max)
285       {
286         // Only the user can do this
287         av.setSelectionGroup(null);
288       }
289       else
290       {
291         // Now add any sequences between min and max
292         sg.clear();
293         for (int i = min; i < max; i++)
294         {
295           sg.addSequence(av.getAlignment().getSequenceAt(i), false);
296         }
297       }
298     }
299
300     if (av.getSelectionGroup() == null)
301     {
302       SequenceGroup sg = new SequenceGroup();
303       sg.setStartRes(seqCanvas.cursorX);
304       sg.setEndRes(seqCanvas.cursorX);
305       sg.addSequence(sequence, false);
306       av.setSelectionGroup(sg);
307     }
308     ap.paintAlignment(false);
309     av.sendSelection();
310   }
311
312   void insertGapAtCursor(boolean group)
313   {
314     groupEditing = group;
315     startseq = seqCanvas.cursorY;
316     lastres = seqCanvas.cursorX;
317     editSequence(true, seqCanvas.cursorX + getKeyboardNo1());
318     endEditing();
319   }
320
321   void deleteGapAtCursor(boolean group)
322   {
323     groupEditing = group;
324     startseq = seqCanvas.cursorY;
325     lastres = seqCanvas.cursorX + getKeyboardNo1();
326     editSequence(false, seqCanvas.cursorX);
327     endEditing();
328   }
329
330   void numberPressed(char value)
331   {
332     if (keyboardNo1 == null)
333     {
334       keyboardNo1 = new StringBuffer();
335     }
336
337     if (keyboardNo2 != null)
338     {
339       keyboardNo2.append(value);
340     }
341     else
342     {
343       keyboardNo1.append(value);
344     }
345   }
346   int getKeyboardNo1()
347   {
348     try {
349     if (keyboardNo1 != null) 
350     {
351       int value = Integer.parseInt(keyboardNo1.toString());
352       keyboardNo1 = null;
353       return value;
354     }
355     } catch (Exception x)
356     {}
357     keyboardNo1 = null;
358     return 1;
359   }
360
361   int getKeyboardNo2()
362   {
363     try {
364     if (keyboardNo2!=null){
365       int value = Integer.parseInt(keyboardNo2.toString());
366       keyboardNo2 = null;
367       return value;
368     }
369     } catch (Exception x)
370     {}
371     keyboardNo2 = null;
372     return 1;
373   }
374   
375   void setStatusMessage(SequenceI sequence, int res, int seq)
376   {
377     StringBuffer text = new StringBuffer("Sequence " + (seq + 1) + " ID: "
378             + sequence.getName());
379
380     Object obj = null;
381     if (av.getAlignment().isNucleotide())
382     {
383       obj = ResidueProperties.nucleotideName.get(sequence.getCharAt(res)
384               + "");
385       if (obj != null)
386       {
387         text.append(" Nucleotide: ");
388       }
389     }
390     else
391     {
392       obj = ResidueProperties.aa2Triplet.get(sequence.getCharAt(res) + "");
393       if (obj != null)
394       {
395         text.append("  Residue: ");
396       }
397     }
398
399     if (obj != null)
400     {
401
402       if (obj != "")
403       {
404         text.append(obj + " (" + sequence.findPosition(res) + ")");
405       }
406     }
407
408     ap.alignFrame.statusBar.setText(text.toString());
409
410   }
411
412   public void mousePressed(MouseEvent evt)
413   {
414     lastMousePress = evt.getPoint();
415
416     // For now, ignore the mouseWheel font resizing on Macs
417     // As the Button2_mask always seems to be true
418     if ((evt.getModifiers() & InputEvent.BUTTON2_MASK) == InputEvent.BUTTON2_MASK
419             && !av.MAC)
420     {
421       mouseWheelPressed = true;
422       return;
423     }
424
425     if (evt.isShiftDown() || evt.isControlDown() || evt.isAltDown())
426     {
427       if (evt.isControlDown() || evt.isAltDown())
428       {
429         groupEditing = true;
430       }
431       editingSeqs = true;
432     }
433     else
434     {
435       doMousePressedDefineMode(evt);
436       return;
437     }
438
439     int seq = findSeq(evt);
440     int res = findRes(evt);
441
442     if (seq < 0 || res < 0)
443     {
444       return;
445     }
446
447     if ((seq < av.getAlignment().getHeight())
448             && (res < av.getAlignment().getSequenceAt(seq).getLength()))
449     {
450       startseq = seq;
451       lastres = res;
452     }
453     else
454     {
455       startseq = -1;
456       lastres = -1;
457     }
458
459     return;
460   }
461
462   public void mouseClicked(MouseEvent evt)
463   {
464     SequenceI sequence = av.getAlignment().getSequenceAt(findSeq(evt));
465     if (evt.getClickCount() > 1)
466     {
467       if (av.getSelectionGroup() != null
468               && av.getSelectionGroup().getSize() == 1
469               && av.getSelectionGroup().getEndRes()
470                       - av.getSelectionGroup().getStartRes() < 2)
471       {
472         av.setSelectionGroup(null);
473       }
474
475       SequenceFeature[] features = findFeaturesAtRes(sequence,
476               sequence.findPosition(findRes(evt)));
477
478       if (features != null && features.length > 0)
479       {
480         SearchResults highlight = new SearchResults();
481         highlight.addResult(sequence, features[0].getBegin(),
482                 features[0].getEnd());
483         seqCanvas.highlightSearchResults(highlight);
484       }
485       if (features != null && features.length > 0)
486       {
487         seqCanvas.getFeatureRenderer().amendFeatures(new SequenceI[]
488         { sequence }, features, false, ap);
489
490         seqCanvas.highlightSearchResults(null);
491       }
492     }
493   }
494
495   public void mouseReleased(MouseEvent evt)
496   {
497     mouseDragging = false;
498     mouseWheelPressed = false;
499     ap.paintAlignment(true);
500
501     if (!editingSeqs)
502     {
503       doMouseReleasedDefineMode(evt);
504       return;
505     }
506
507     endEditing();
508
509   }
510
511   int startWrapBlock = -1;
512
513   int wrappedBlock = -1;
514
515   int findRes(MouseEvent evt)
516   {
517     int res = 0;
518     int x = evt.getX();
519
520     if (av.wrapAlignment)
521     {
522
523       int hgap = av.charHeight;
524       if (av.scaleAboveWrapped)
525       {
526         hgap += av.charHeight;
527       }
528
529       int cHeight = av.getAlignment().getHeight() * av.charHeight + hgap
530               + seqCanvas.getAnnotationHeight();
531
532       int y = evt.getY();
533       y -= hgap;
534       x -= seqCanvas.LABEL_WEST;
535
536       int cwidth = seqCanvas.getWrappedCanvasWidth(getSize().width);
537       if (cwidth < 1)
538       {
539         return 0;
540       }
541
542       wrappedBlock = y / cHeight;
543       wrappedBlock += av.getStartRes() / cwidth;
544
545       res = wrappedBlock * cwidth + x / av.getCharWidth();
546
547     }
548     else
549     {
550       res = (x / av.getCharWidth()) + av.getStartRes();
551     }
552
553     if (av.hasHiddenColumns())
554     {
555       res = av.getColumnSelection().adjustForHiddenColumns(res);
556     }
557
558     return res;
559
560   }
561
562   int findSeq(MouseEvent evt)
563   {
564     final int sqnum = findAlRow(evt);
565     return (sqnum < 0) ? 0 : sqnum;
566   }
567
568   /**
569    * 
570    * @param evt
571    * @return row in alignment that was selected (or -1 for column selection)
572    */
573   private int findAlRow(MouseEvent evt)
574   {
575     int seq = 0;
576     int y = evt.getY();
577
578     if (av.wrapAlignment)
579     {
580       int hgap = av.charHeight;
581       if (av.scaleAboveWrapped)
582       {
583         hgap += av.charHeight;
584       }
585
586       int cHeight = av.getAlignment().getHeight() * av.charHeight + hgap
587               + seqCanvas.getAnnotationHeight();
588
589       y -= hgap;
590
591       seq = Math.min((y % cHeight) / av.getCharHeight(), av.getAlignment()
592               .getHeight() - 1);
593       if (seq < 0)
594       {
595         seq = -1;
596       }
597     }
598     else
599     {
600       seq = Math.min((y / av.getCharHeight()) + av.getStartSeq(), av
601               .getAlignment().getHeight() - 1);
602       if (seq < 0)
603       {
604         seq = -1;
605       }
606     }
607
608     return seq;
609   }
610
611   public void doMousePressed(MouseEvent evt)
612   {
613
614     int seq = findSeq(evt);
615     int res = findRes(evt);
616
617     if (seq < av.getAlignment().getHeight()
618             && res < av.getAlignment().getSequenceAt(seq).getLength())
619     {
620       // char resstr = align.getSequenceAt(seq).getSequence().charAt(res);
621       // Find the residue's position in the sequence (res is the position
622       // in the alignment
623
624       startseq = seq;
625       lastres = res;
626     }
627     else
628     {
629       startseq = -1;
630       lastres = -1;
631     }
632
633     return;
634   }
635
636   String lastMessage;
637
638   public void mouseOverSequence(SequenceI sequence, int index, int pos)
639   {
640     String tmp = sequence.hashCode() + index + "";
641     if (lastMessage == null || !lastMessage.equals(tmp))
642       ssm.mouseOverSequence(sequence, index, pos, av);
643
644     lastMessage = tmp;
645   }
646
647   public void highlightSequence(SearchResults results)
648   {
649     if (av.followHighlight)
650     {
651       if (ap.scrollToPosition(results, true))
652       {
653         ap.alignFrame.repaint();
654       }
655     }
656     seqCanvas.highlightSearchResults(results);
657
658   }
659
660   public void updateColours(SequenceI seq, int index)
661   {
662     System.out.println("update the seqPanel colours");
663     // repaint();
664   }
665
666   public void mouseMoved(MouseEvent evt)
667   {
668     int res = findRes(evt);
669     int seq = findSeq(evt);
670
671     if (seq >= av.getAlignment().getHeight() || seq < 0 || res < 0)
672     {
673       if (tooltip != null)
674       {
675         tooltip.setTip("");
676       }
677       return;
678     }
679
680     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
681     if (res > sequence.getLength())
682     {
683       if (tooltip != null)
684       {
685         tooltip.setTip("");
686       }
687       return;
688     }
689
690     int respos = sequence.findPosition(res);
691     if (ssm != null)
692       mouseOverSequence(sequence, res, respos);
693
694     StringBuffer text = new StringBuffer("Sequence " + (seq + 1) + " ID: "
695             + sequence.getName());
696
697     Object obj = null;
698     if (av.getAlignment().isNucleotide())
699     {
700       obj = ResidueProperties.nucleotideName.get(sequence.getCharAt(res)
701               + "");
702       if (obj != null)
703       {
704         text.append(" Nucleotide: ");
705       }
706     }
707     else
708     {
709       obj = ResidueProperties.aa2Triplet.get(sequence.getCharAt(res) + "");
710       if (obj != null)
711       {
712         text.append("  Residue: ");
713       }
714     }
715
716     if (obj != null)
717     {
718       if (obj != "")
719       {
720         text.append(obj + " (" + respos + ")");
721       }
722     }
723
724     ap.alignFrame.statusBar.setText(text.toString());
725
726     StringBuffer tooltipText = new StringBuffer();
727     SequenceGroup[] groups = av.getAlignment().findAllGroups(sequence);
728     if (groups != null)
729     {
730       for (int g = 0; g < groups.length; g++)
731       {
732         if (groups[g].getStartRes() <= res && groups[g].getEndRes() >= res)
733         {
734           if (!groups[g].getName().startsWith("JTreeGroup")
735                   && !groups[g].getName().startsWith("JGroup"))
736           {
737             tooltipText.append(groups[g].getName() + " ");
738           }
739           if (groups[g].getDescription() != null)
740           {
741             tooltipText.append(groups[g].getDescription());
742           }
743           tooltipText.append("\n");
744         }
745       }
746     }
747
748     // use aa to see if the mouse pointer is on a
749     SequenceFeature[] allFeatures = findFeaturesAtRes(sequence,
750             sequence.findPosition(res));
751
752     int index = 0;
753     while (index < allFeatures.length)
754     {
755       SequenceFeature sf = allFeatures[index];
756
757       tooltipText.append(sf.getType() + " " + sf.begin + ":" + sf.end);
758
759       if (sf.getDescription() != null)
760       {
761         tooltipText.append(" " + sf.getDescription());
762       }
763
764       if (sf.getValue("status") != null)
765       {
766         String status = sf.getValue("status").toString();
767         if (status.length() > 0)
768         {
769           tooltipText.append(" (" + sf.getValue("status") + ")");
770         }
771       }
772       tooltipText.append("\n");
773
774       index++;
775     }
776
777     if (tooltip == null)
778     {
779       tooltip = new Tooltip(tooltipText.toString(), seqCanvas);
780     }
781     else
782     {
783       tooltip.setTip(tooltipText.toString());
784     }
785   }
786
787   SequenceFeature[] findFeaturesAtRes(SequenceI sequence, int res)
788   {
789     Vector tmp = new Vector();
790     SequenceFeature[] features = sequence.getSequenceFeatures();
791     if (features != null)
792     {
793       for (int i = 0; i < features.length; i++)
794       {
795         if (av.featuresDisplayed == null
796                 || !av.featuresDisplayed.containsKey(features[i].getType()))
797         {
798           continue;
799         }
800
801         if (features[i].featureGroup != null
802                 && seqCanvas.fr.featureGroups != null
803                 && seqCanvas.fr.featureGroups
804                         .containsKey(features[i].featureGroup)
805                 && !((Boolean) seqCanvas.fr.featureGroups
806                         .get(features[i].featureGroup)).booleanValue())
807           continue;
808
809         if ((features[i].getBegin() <= res)
810                 && (features[i].getEnd() >= res))
811         {
812           tmp.addElement(features[i]);
813         }
814       }
815     }
816
817     features = new SequenceFeature[tmp.size()];
818     tmp.copyInto(features);
819
820     return features;
821   }
822
823   Tooltip tooltip;
824
825   public void mouseDragged(MouseEvent evt)
826   {
827     if (mouseWheelPressed)
828     {
829       int oldWidth = av.charWidth;
830
831       // Which is bigger, left-right or up-down?
832       if (Math.abs(evt.getY() - lastMousePress.y) > Math.abs(evt.getX()
833               - lastMousePress.x))
834       {
835         int fontSize = av.font.getSize();
836
837         if (evt.getY() < lastMousePress.y && av.charHeight > 1)
838         {
839           fontSize--;
840         }
841         else if (evt.getY() > lastMousePress.y)
842         {
843           fontSize++;
844         }
845
846         if (fontSize < 1)
847         {
848           fontSize = 1;
849         }
850
851         av.setFont(new Font(av.font.getName(), av.font.getStyle(), fontSize));
852         av.charWidth = oldWidth;
853       }
854       else
855       {
856         if (evt.getX() < lastMousePress.x && av.charWidth > 1)
857         {
858           av.charWidth--;
859         }
860         else if (evt.getX() > lastMousePress.x)
861         {
862           av.charWidth++;
863         }
864
865         if (av.charWidth < 1)
866         {
867           av.charWidth = 1;
868         }
869       }
870
871       ap.fontChanged();
872
873       FontMetrics fm = getFontMetrics(av.getFont());
874       av.validCharWidth = fm.charWidth('M') <= av.charWidth;
875
876       lastMousePress = evt.getPoint();
877
878       ap.paintAlignment(false);
879       ap.annotationPanel.image = null;
880       return;
881     }
882
883     if (!editingSeqs)
884     {
885       doMouseDraggedDefineMode(evt);
886       return;
887     }
888
889     int res = findRes(evt);
890
891     if (res < 0)
892     {
893       res = 0;
894     }
895
896     if ((lastres == -1) || (lastres == res))
897     {
898       return;
899     }
900
901     if ((res < av.getAlignment().getWidth()) && (res < lastres))
902     {
903       // dragLeft, delete gap
904       editSequence(false, res);
905     }
906     else
907     {
908       editSequence(true, res);
909     }
910
911     mouseDragging = true;
912     if (scrollThread != null)
913     {
914       scrollThread.setEvent(evt);
915     }
916
917   }
918
919   synchronized void editSequence(boolean insertGap, int startres)
920   {
921     int fixedLeft = -1;
922     int fixedRight = -1;
923     boolean fixedColumns = false;
924     SequenceGroup sg = av.getSelectionGroup();
925
926     SequenceI seq = av.getAlignment().getSequenceAt(startseq);
927
928     if (!groupEditing && av.hasHiddenRows())
929     {
930       if (av.isHiddenRepSequence(seq))
931       {
932         sg = (SequenceGroup) av.getRepresentedSequences(seq);
933         groupEditing = true;
934       }
935     }
936
937     StringBuffer message = new StringBuffer();
938     if (groupEditing)
939     {
940       message.append("Edit group:");
941       if (editCommand == null)
942       {
943         editCommand = new EditCommand("Edit Group");
944       }
945     }
946     else
947     {
948       message.append("Edit sequence: " + seq.getName());
949       String label = seq.getName();
950       if (label.length() > 10)
951       {
952         label = label.substring(0, 10);
953       }
954       if (editCommand == null)
955       {
956         editCommand = new EditCommand("Edit " + label);
957       }
958     }
959
960     if (insertGap)
961     {
962       message.append(" insert ");
963     }
964     else
965     {
966       message.append(" delete ");
967     }
968
969     message.append(Math.abs(startres - lastres) + " gaps.");
970     ap.alignFrame.statusBar.setText(message.toString());
971
972     // Are we editing within a selection group?
973     if (groupEditing
974             || (sg != null && sg.getSequences(av.getHiddenRepSequences())
975                     .contains(seq)))
976     {
977       fixedColumns = true;
978
979       // sg might be null as the user may only see 1 sequence,
980       // but the sequence represents a group
981       if (sg == null)
982       {
983         if (!av.isHiddenRepSequence(seq))
984         {
985           endEditing();
986           return;
987         }
988
989         sg = av.getRepresentedSequences(seq);
990       }
991
992       fixedLeft = sg.getStartRes();
993       fixedRight = sg.getEndRes();
994
995       if ((startres < fixedLeft && lastres >= fixedLeft)
996               || (startres >= fixedLeft && lastres < fixedLeft)
997               || (startres > fixedRight && lastres <= fixedRight)
998               || (startres <= fixedRight && lastres > fixedRight))
999       {
1000         endEditing();
1001         return;
1002       }
1003
1004       if (fixedLeft > startres)
1005       {
1006         fixedRight = fixedLeft - 1;
1007         fixedLeft = 0;
1008       }
1009       else if (fixedRight < startres)
1010       {
1011         fixedLeft = fixedRight;
1012         fixedRight = -1;
1013       }
1014     }
1015
1016     if (av.hasHiddenColumns())
1017     {
1018       fixedColumns = true;
1019       int y1 = av.getColumnSelection().getHiddenBoundaryLeft(startres);
1020       int y2 = av.getColumnSelection().getHiddenBoundaryRight(startres);
1021
1022       if ((insertGap && startres > y1 && lastres < y1)
1023               || (!insertGap && startres < y2 && lastres > y2))
1024       {
1025         endEditing();
1026         return;
1027       }
1028
1029       // System.out.print(y1+" "+y2+" "+fixedLeft+" "+fixedRight+"~~");
1030       // Selection spans a hidden region
1031       if (fixedLeft < y1 && (fixedRight > y2 || fixedRight == -1))
1032       {
1033         if (startres >= y2)
1034         {
1035           fixedLeft = y2;
1036         }
1037         else
1038         {
1039           fixedRight = y2 - 1;
1040         }
1041       }
1042     }
1043
1044     if (groupEditing)
1045     {
1046       SequenceI[] groupSeqs = sg.getSequences(av.getHiddenRepSequences())
1047               .toArray(new SequenceI[0]);
1048
1049       // drag to right
1050       if (insertGap)
1051       {
1052         // If the user has selected the whole sequence, and is dragging to
1053         // the right, we can still extend the alignment and selectionGroup
1054         if (sg.getStartRes() == 0 && sg.getEndRes() == fixedRight
1055                 && sg.getEndRes() == av.getAlignment().getWidth() - 1)
1056         {
1057           sg.setEndRes(av.getAlignment().getWidth() + startres - lastres);
1058           fixedRight = sg.getEndRes();
1059         }
1060
1061         // Is it valid with fixed columns??
1062         // Find the next gap before the end
1063         // of the visible region boundary
1064         boolean blank = false;
1065         for (fixedRight = fixedRight; fixedRight > lastres; fixedRight--)
1066         {
1067           blank = true;
1068
1069           for (SequenceI gs : groupSeqs)
1070           {
1071             for (int j = 0; j < startres - lastres; j++)
1072             {
1073               if (!jalview.util.Comparison.isGap(gs.getCharAt(fixedRight
1074                       - j)))
1075               {
1076                 blank = false;
1077                 break;
1078               }
1079             }
1080           }
1081           if (blank)
1082           {
1083             break;
1084           }
1085         }
1086
1087         if (!blank)
1088         {
1089           if (sg.getSize() == av.getAlignment().getHeight())
1090           {
1091             if ((av.hasHiddenColumns() && startres < av
1092                     .getColumnSelection().getHiddenBoundaryRight(startres)))
1093             {
1094               endEditing();
1095               return;
1096             }
1097
1098             int alWidth = av.getAlignment().getWidth();
1099             if (av.hasHiddenRows())
1100             {
1101               int hwidth = av.getAlignment().getHiddenSequences()
1102                       .getWidth();
1103               if (hwidth > alWidth)
1104               {
1105                 alWidth = hwidth;
1106               }
1107             }
1108             // We can still insert gaps if the selectionGroup
1109             // contains all the sequences
1110             sg.setEndRes(sg.getEndRes() + startres - lastres);
1111             fixedRight = alWidth + startres - lastres;
1112           }
1113           else
1114           {
1115             endEditing();
1116             return;
1117           }
1118         }
1119       }
1120
1121       // drag to left
1122       else if (!insertGap)
1123       {
1124         // / Are we able to delete?
1125         // ie are all columns blank?
1126
1127         for (SequenceI gs : groupSeqs)
1128         {
1129           for (int j = startres; j < lastres; j++)
1130           {
1131             if (gs.getLength() <= j)
1132             {
1133               continue;
1134             }
1135
1136             if (!jalview.util.Comparison.isGap(gs.getCharAt(j)))
1137             {
1138               // Not a gap, block edit not valid
1139               endEditing();
1140               return;
1141             }
1142           }
1143         }
1144       }
1145
1146       if (insertGap)
1147       {
1148         // dragging to the right
1149         if (fixedColumns && fixedRight != -1)
1150         {
1151           for (int j = lastres; j < startres; j++)
1152           {
1153             insertChar(j, groupSeqs, fixedRight);
1154           }
1155         }
1156         else
1157         {
1158           editCommand.appendEdit(EditCommand.INSERT_GAP, groupSeqs,
1159                   startres, startres - lastres, av.getAlignment(), true);
1160         }
1161       }
1162       else
1163       {
1164         // dragging to the left
1165         if (fixedColumns && fixedRight != -1)
1166         {
1167           for (int j = lastres; j > startres; j--)
1168           {
1169             deleteChar(startres, groupSeqs, fixedRight);
1170           }
1171         }
1172         else
1173         {
1174           editCommand.appendEdit(EditCommand.DELETE_GAP, groupSeqs,
1175                   startres, lastres - startres, av.getAlignment(), true);
1176         }
1177
1178       }
1179     }
1180     else
1181     // ///Editing a single sequence///////////
1182     {
1183       if (insertGap)
1184       {
1185         // dragging to the right
1186         if (fixedColumns && fixedRight != -1)
1187         {
1188           for (int j = lastres; j < startres; j++)
1189           {
1190             insertChar(j, new SequenceI[]
1191             { seq }, fixedRight);
1192           }
1193         }
1194         else
1195         {
1196           editCommand.appendEdit(EditCommand.INSERT_GAP, new SequenceI[]
1197           { seq }, lastres, startres - lastres, av.getAlignment(), true);
1198         }
1199       }
1200       else
1201       {
1202         // dragging to the left
1203         if (fixedColumns && fixedRight != -1)
1204         {
1205           for (int j = lastres; j > startres; j--)
1206           {
1207             if (!jalview.util.Comparison.isGap(seq.getCharAt(startres)))
1208             {
1209               endEditing();
1210               break;
1211             }
1212             deleteChar(startres, new SequenceI[]
1213             { seq }, fixedRight);
1214           }
1215         }
1216         else
1217         {
1218           // could be a keyboard edit trying to delete none gaps
1219           int max = 0;
1220           for (int m = startres; m < lastres; m++)
1221           {
1222             if (!jalview.util.Comparison.isGap(seq.getCharAt(m)))
1223             {
1224               break;
1225             }
1226             max++;
1227           }
1228
1229           if (max > 0)
1230           {
1231             editCommand.appendEdit(EditCommand.DELETE_GAP, new SequenceI[]
1232             { seq }, startres, max, av.getAlignment(), true);
1233           }
1234         }
1235       }
1236     }
1237
1238     lastres = startres;
1239     seqCanvas.repaint();
1240   }
1241
1242   void insertChar(int j, SequenceI[] seq, int fixedColumn)
1243   {
1244     int blankColumn = fixedColumn;
1245     for (int s = 0; s < seq.length; s++)
1246     {
1247       // Find the next gap before the end of the visible region boundary
1248       // If lastCol > j, theres a boundary after the gap insertion
1249
1250       for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)
1251       {
1252         if (jalview.util.Comparison.isGap(seq[s].getCharAt(blankColumn)))
1253         {
1254           // Theres a space, so break and insert the gap
1255           break;
1256         }
1257       }
1258
1259       if (blankColumn <= j)
1260       {
1261         blankColumn = fixedColumn;
1262         endEditing();
1263         return;
1264       }
1265     }
1266
1267     editCommand.appendEdit(EditCommand.DELETE_GAP, seq, blankColumn, 1,
1268             av.getAlignment(), true);
1269
1270     editCommand.appendEdit(EditCommand.INSERT_GAP, seq, j, 1,
1271             av.getAlignment(), true);
1272
1273   }
1274
1275   void deleteChar(int j, SequenceI[] seq, int fixedColumn)
1276   {
1277
1278     editCommand.appendEdit(EditCommand.DELETE_GAP, seq, j, 1,
1279             av.getAlignment(), true);
1280
1281     editCommand.appendEdit(EditCommand.INSERT_GAP, seq, fixedColumn, 1,
1282             av.getAlignment(), true);
1283   }
1284
1285   // ////////////////////////////////////////
1286   // ///Everything below this is for defining the boundary of the rubberband
1287   // ////////////////////////////////////////
1288   public void doMousePressedDefineMode(MouseEvent evt)
1289   {
1290     if (scrollThread != null)
1291     {
1292       scrollThread.running = false;
1293       scrollThread = null;
1294     }
1295
1296     int res = findRes(evt);
1297     int seq = findSeq(evt);
1298     oldSeq = seq;
1299     startWrapBlock = wrappedBlock;
1300
1301     if (seq == -1)
1302     {
1303       return;
1304     }
1305
1306     SequenceI sequence = (Sequence) av.getAlignment().getSequenceAt(seq);
1307
1308     if (sequence == null || res > sequence.getLength())
1309     {
1310       return;
1311     }
1312
1313     stretchGroup = av.getSelectionGroup();
1314
1315     if (stretchGroup == null)
1316     {
1317       stretchGroup = av.getAlignment().findGroup(sequence);
1318       if (stretchGroup != null && res > stretchGroup.getStartRes()
1319               && res < stretchGroup.getEndRes())
1320       {
1321         av.setSelectionGroup(stretchGroup);
1322       }
1323       else
1324       {
1325         stretchGroup = null;
1326       }
1327     }
1328
1329     else if (!stretchGroup.getSequences(null).contains(sequence)
1330             || stretchGroup.getStartRes() > res
1331             || stretchGroup.getEndRes() < res)
1332     {
1333       stretchGroup = null;
1334
1335       SequenceGroup[] allGroups = av.getAlignment().findAllGroups(sequence);
1336
1337       if (allGroups != null)
1338       {
1339         for (int i = 0; i < allGroups.length; i++)
1340         {
1341           if (allGroups[i].getStartRes() <= res
1342                   && allGroups[i].getEndRes() >= res)
1343           {
1344             stretchGroup = allGroups[i];
1345             break;
1346           }
1347         }
1348       }
1349       av.setSelectionGroup(stretchGroup);
1350     }
1351
1352     // DETECT RIGHT MOUSE BUTTON IN AWT
1353     if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
1354     {
1355       SequenceFeature[] allFeatures = findFeaturesAtRes(sequence,
1356               sequence.findPosition(res));
1357
1358       Vector links = null;
1359       if (allFeatures != null)
1360       {
1361         for (int i = 0; i < allFeatures.length; i++)
1362         {
1363           if (allFeatures[i].links != null)
1364           {
1365             if (links == null)
1366             {
1367               links = new Vector();
1368             }
1369             for (int j = 0; j < allFeatures[i].links.size(); j++)
1370             {
1371               links.addElement(allFeatures[i].links.elementAt(j));
1372             }
1373           }
1374         }
1375       }
1376       APopupMenu popup = new APopupMenu(ap, null, links);
1377       this.add(popup);
1378       popup.show(this, evt.getX(), evt.getY());
1379       return;
1380     }
1381
1382     if (av.cursorMode)
1383     {
1384       seqCanvas.cursorX = findRes(evt);
1385       seqCanvas.cursorY = findSeq(evt);
1386       seqCanvas.repaint();
1387       return;
1388     }
1389
1390     // Only if left mouse button do we want to change group sizes
1391
1392     if (stretchGroup == null)
1393     {
1394       // define a new group here
1395       SequenceGroup sg = new SequenceGroup();
1396       sg.setStartRes(res);
1397       sg.setEndRes(res);
1398       sg.addSequence(sequence, false);
1399       av.setSelectionGroup(sg);
1400       stretchGroup = sg;
1401
1402       if (av.getConservationSelected())
1403       {
1404         SliderPanel.setConservationSlider(ap, av.getGlobalColourScheme(),
1405                 "Background");
1406       }
1407       if (av.getAbovePIDThreshold())
1408       {
1409         SliderPanel.setPIDSliderSource(ap, av.getGlobalColourScheme(),
1410                 "Background");
1411       }
1412
1413     }
1414   }
1415
1416   public void doMouseReleasedDefineMode(MouseEvent evt)
1417   {
1418     if (stretchGroup == null)
1419     {
1420       return;
1421     }
1422
1423     stretchGroup.recalcConservation(); // always do this - annotation has own
1424                                        // state
1425     if (stretchGroup.cs != null)
1426     {
1427       stretchGroup.cs.alignmentChanged(stretchGroup,
1428               av.getHiddenRepSequences());
1429
1430       if (stretchGroup.cs.conservationApplied())
1431       {
1432         SliderPanel.setConservationSlider(ap, stretchGroup.cs,
1433                 stretchGroup.getName());
1434       }
1435       else
1436       {
1437         SliderPanel.setPIDSliderSource(ap, stretchGroup.cs,
1438                 stretchGroup.getName());
1439       }
1440     }
1441     changeEndRes = false;
1442     changeStartRes = false;
1443     stretchGroup = null;
1444     PaintRefresher.Refresh(ap, av.getSequenceSetId());
1445     ap.paintAlignment(true);
1446     av.sendSelection();
1447   }
1448
1449   public void doMouseDraggedDefineMode(MouseEvent evt)
1450   {
1451     int res = findRes(evt);
1452     int y = findSeq(evt);
1453
1454     if (wrappedBlock != startWrapBlock)
1455     {
1456       return;
1457     }
1458
1459     if (stretchGroup == null)
1460     {
1461       return;
1462     }
1463
1464     mouseDragging = true;
1465
1466     if (y > av.getAlignment().getHeight())
1467     {
1468       y = av.getAlignment().getHeight() - 1;
1469     }
1470
1471     if (res >= av.getAlignment().getWidth())
1472     {
1473       res = av.getAlignment().getWidth() - 1;
1474     }
1475
1476     if (stretchGroup.getEndRes() == res)
1477     {
1478       // Edit end res position of selected group
1479       changeEndRes = true;
1480     }
1481     else if (stretchGroup.getStartRes() == res)
1482     {
1483       // Edit start res position of selected group
1484       changeStartRes = true;
1485     }
1486
1487     if (res < 0)
1488     {
1489       res = 0;
1490     }
1491
1492     if (changeEndRes)
1493     {
1494       if (res > (stretchGroup.getStartRes() - 1))
1495       {
1496         stretchGroup.setEndRes(res);
1497       }
1498     }
1499     else if (changeStartRes)
1500     {
1501       if (res < (stretchGroup.getEndRes() + 1))
1502       {
1503         stretchGroup.setStartRes(res);
1504       }
1505     }
1506
1507     int dragDirection = 0;
1508
1509     if (y > oldSeq)
1510     {
1511       dragDirection = 1;
1512     }
1513     else if (y < oldSeq)
1514     {
1515       dragDirection = -1;
1516     }
1517
1518     while ((y != oldSeq) && (oldSeq > -1)
1519             && (y < av.getAlignment().getHeight()))
1520     {
1521       // This routine ensures we don't skip any sequences, as the
1522       // selection is quite slow.
1523       Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1524
1525       oldSeq += dragDirection;
1526
1527       if (oldSeq < 0)
1528       {
1529         break;
1530       }
1531
1532       Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1533
1534       if (stretchGroup.getSequences(null).contains(nextSeq))
1535       {
1536         stretchGroup.deleteSequence(seq, false);
1537       }
1538       else
1539       {
1540         if (seq != null)
1541         {
1542           stretchGroup.addSequence(seq, false);
1543         }
1544
1545         stretchGroup.addSequence(nextSeq, false);
1546       }
1547     }
1548
1549     if (oldSeq < 0)
1550     {
1551       oldSeq = -1;
1552     }
1553
1554     if (res > av.endRes || res < av.startRes || y < av.startSeq
1555             || y > av.endSeq)
1556     {
1557       mouseExited(evt);
1558     }
1559
1560     if (scrollThread != null)
1561     {
1562       scrollThread.setEvent(evt);
1563     }
1564
1565     seqCanvas.repaint();
1566   }
1567
1568   public void mouseEntered(MouseEvent e)
1569   {
1570     if (oldSeq < 0)
1571     {
1572       oldSeq = 0;
1573     }
1574
1575     if (scrollThread != null)
1576     {
1577       scrollThread.running = false;
1578       scrollThread = null;
1579     }
1580   }
1581
1582   public void mouseExited(MouseEvent e)
1583   {
1584     if (av.getWrapAlignment())
1585     {
1586       return;
1587     }
1588
1589     if (mouseDragging && scrollThread == null)
1590     {
1591       scrollThread = new ScrollThread();
1592     }
1593   }
1594
1595   void scrollCanvas(MouseEvent evt)
1596   {
1597     if (evt == null)
1598     {
1599       if (scrollThread != null)
1600       {
1601         scrollThread.running = false;
1602         scrollThread = null;
1603       }
1604       mouseDragging = false;
1605     }
1606     else
1607     {
1608       if (scrollThread == null)
1609       {
1610         scrollThread = new ScrollThread();
1611       }
1612
1613       mouseDragging = true;
1614       scrollThread.setEvent(evt);
1615     }
1616
1617   }
1618
1619   // this class allows scrolling off the bottom of the visible alignment
1620   class ScrollThread extends Thread
1621   {
1622     MouseEvent evt;
1623
1624     boolean running = false;
1625
1626     public ScrollThread()
1627     {
1628       start();
1629     }
1630
1631     public void setEvent(MouseEvent e)
1632     {
1633       evt = e;
1634     }
1635
1636     public void stopScrolling()
1637     {
1638       running = false;
1639     }
1640
1641     public void run()
1642     {
1643       running = true;
1644       while (running)
1645       {
1646
1647         if (evt != null)
1648         {
1649
1650           if (mouseDragging && evt.getY() < 0 && av.getStartSeq() > 0)
1651           {
1652             running = ap.scrollUp(true);
1653           }
1654
1655           if (mouseDragging && evt.getY() >= getSize().height
1656                   && av.getAlignment().getHeight() > av.getEndSeq())
1657           {
1658             running = ap.scrollUp(false);
1659           }
1660
1661           if (mouseDragging && evt.getX() < 0)
1662           {
1663             running = ap.scrollRight(false);
1664           }
1665
1666           else if (mouseDragging && evt.getX() >= getSize().width)
1667           {
1668             running = ap.scrollRight(true);
1669           }
1670         }
1671
1672         try
1673         {
1674           Thread.sleep(75);
1675         } catch (Exception ex)
1676         {
1677         }
1678       }
1679     }
1680   }
1681
1682   /**
1683    * modify current selection according to a received message.
1684    */
1685   public void selection(SequenceGroup seqsel, ColumnSelection colsel,
1686           SelectionSource source)
1687   {
1688     // TODO: fix this hack - source of messages is align viewport, but SeqPanel
1689     // handles selection messages...
1690     // TODO: extend config options to allow user to control if selections may be
1691     // shared between viewports.
1692     if (av != null
1693             && (av == source || !av.followSelection || (source instanceof AlignViewport && ((AlignViewport) source)
1694                     .getSequenceSetId().equals(av.getSequenceSetId()))))
1695     {
1696       return;
1697     }
1698     // do we want to thread this ? (contention with seqsel and colsel locks, I
1699     // suspect)
1700     // rules are: colsel is copied if there is a real intersection between
1701     // sequence selection
1702     boolean repaint = false, copycolsel = true;
1703     if (av.getSelectionGroup() == null || !av.isSelectionGroupChanged(true))
1704     {
1705       SequenceGroup sgroup = null;
1706       if (seqsel != null && seqsel.getSize() > 0)
1707       {
1708         if (av.getAlignment() == null)
1709         {
1710           System.out
1711                   .println("Selection message: alignviewport av SeqSetId="
1712                           + av.getSequenceSetId() + " ViewId="
1713                           + av.getViewId()
1714                           + " 's alignment is NULL! returning immediatly.");
1715           return;
1716         }
1717         sgroup = seqsel.intersect(av.getAlignment(),
1718                 (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
1719         if ((sgroup == null || sgroup.getSize() == 0)
1720                 && (colsel == null || colsel.size() == 0))
1721         {
1722           // don't copy columns if the region didn't intersect.
1723           copycolsel = false;
1724         }
1725       }
1726       if (sgroup != null && sgroup.getSize() > 0)
1727       {
1728         av.setSelectionGroup(sgroup);
1729       }
1730       else
1731       {
1732         av.setSelectionGroup(null);
1733       }
1734       repaint = av.isSelectionGroupChanged(true);
1735     }
1736     if (copycolsel
1737             && (av.getColumnSelection() == null || !av
1738                     .isColSelChanged(true)))
1739     {
1740       // the current selection is unset or from a previous message
1741       // so import the new colsel.
1742       if (colsel == null || colsel.size() == 0)
1743       {
1744         if (av.getColumnSelection() != null)
1745         {
1746           av.getColumnSelection().clear();
1747         }
1748       }
1749       else
1750       {
1751         // TODO: shift colSel according to the intersecting sequences
1752         if (av.getColumnSelection() == null)
1753         {
1754           av.setColumnSelection(new ColumnSelection(colsel));
1755         }
1756         else
1757         {
1758           av.getColumnSelection().setElementsFrom(colsel);
1759         }
1760       }
1761       repaint |= av.isColSelChanged(true);
1762     }
1763     if (copycolsel
1764             && av.hasHiddenColumns()
1765             && (av.getColumnSelection() == null || av.getColumnSelection()
1766                     .getHiddenColumns() == null))
1767     {
1768       System.err.println("Bad things");
1769     }
1770     if (repaint)
1771     {
1772       ap.scalePanelHolder.repaint();
1773       ap.repaint();
1774     }
1775   }
1776
1777   /**
1778    * scroll to the given row/column - or nearest visible location
1779    * 
1780    * @param row
1781    * @param column
1782    */
1783   public void scrollTo(int row, int column)
1784   {
1785
1786     row = row < 0 ? ap.av.startSeq : row;
1787     column = column < 0 ? ap.av.startRes : column;
1788     ap.scrollTo(column, column, row, true, true);
1789   }
1790
1791   /**
1792    * scroll to the given row - or nearest visible location
1793    * 
1794    * @param row
1795    */
1796   public void scrollToRow(int row)
1797   {
1798
1799     row = row < 0 ? ap.av.startSeq : row;
1800     ap.scrollTo(ap.av.startRes, ap.av.startRes, row, true, true);
1801   }
1802
1803   /**
1804    * scroll to the given column - or nearest visible location
1805    * 
1806    * @param column
1807    */
1808   public void scrollToColumn(int column)
1809   {
1810
1811     column = column < 0 ? ap.av.startRes : column;
1812     ap.scrollTo(column, column, ap.av.startSeq, true, true);
1813   }
1814
1815 }