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