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