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