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