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