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