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