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