Merge branch 'features/JAL-845splitPaneMergeDevelop' into merge_JAL-845_JAL-1640
[jalview.git] / src / jalview / appletgui / SeqPanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.appletgui;
22
23 import jalview.commands.EditCommand;
24 import jalview.commands.EditCommand.Action;
25 import jalview.datamodel.ColumnSelection;
26 import jalview.datamodel.SearchResults;
27 import jalview.datamodel.Sequence;
28 import jalview.datamodel.SequenceFeature;
29 import jalview.datamodel.SequenceGroup;
30 import jalview.datamodel.SequenceI;
31 import jalview.schemes.ResidueProperties;
32 import jalview.structure.SelectionSource;
33 import jalview.structure.SequenceListener;
34 import jalview.structure.StructureSelectionManager;
35 import jalview.structure.VamsasSource;
36 import jalview.util.MessageManager;
37
38 import java.awt.BorderLayout;
39 import java.awt.Font;
40 import java.awt.FontMetrics;
41 import java.awt.Panel;
42 import java.awt.Point;
43 import java.awt.event.InputEvent;
44 import java.awt.event.MouseEvent;
45 import java.awt.event.MouseListener;
46 import java.awt.event.MouseMotionListener;
47 import java.util.Vector;
48
49 public class SeqPanel extends Panel implements MouseMotionListener,
50         MouseListener, SequenceListener
51 {
52
53   public SeqCanvas seqCanvas;
54
55   public AlignmentPanel ap;
56
57   protected int lastres;
58
59   protected int startseq;
60
61   protected AlignViewport av;
62
63   // if character is inserted or deleted, we will need to recalculate the
64   // conservation
65   boolean seqEditOccurred = false;
66
67   ScrollThread scrollThread = null;
68
69   boolean mouseDragging = false;
70
71   boolean editingSeqs = false;
72
73   boolean groupEditing = false;
74
75   int oldSeq = -1;
76
77   boolean changeEndSeq = false;
78
79   boolean changeStartSeq = false;
80
81   boolean changeEndRes = false;
82
83   boolean changeStartRes = false;
84
85   SequenceGroup stretchGroup = null;
86
87   StringBuffer keyboardNo1;
88
89   StringBuffer keyboardNo2;
90
91   boolean mouseWheelPressed = false;
92
93   Point lastMousePress;
94
95   EditCommand editCommand;
96
97   StructureSelectionManager ssm;
98
99   public SeqPanel(AlignViewport avp, AlignmentPanel p)
100   {
101     this.av = avp;
102
103     seqCanvas = new SeqCanvas(avp);
104     setLayout(new BorderLayout());
105     add(seqCanvas);
106
107     ap = p;
108
109     seqCanvas.addMouseMotionListener(this);
110     seqCanvas.addMouseListener(this);
111     ssm = StructureSelectionManager.getStructureSelectionManager(av.applet);
112     ssm.addStructureViewerListener(this);
113
114     seqCanvas.repaint();
115   }
116
117   void endEditing()
118   {
119     if (editCommand != null && editCommand.getSize() > 0)
120     {
121       ap.alignFrame.addHistoryItem(editCommand);
122       av.firePropertyChange("alignment", null, av.getAlignment()
123               .getSequences());
124     }
125
126     startseq = -1;
127     lastres = -1;
128     editingSeqs = false;
129     groupEditing = false;
130     keyboardNo1 = null;
131     keyboardNo2 = null;
132     editCommand = null;
133   }
134
135   void setCursorRow()
136   {
137     seqCanvas.cursorY = getKeyboardNo1() - 1;
138     scrollToVisible();
139   }
140
141   void setCursorColumn()
142   {
143     seqCanvas.cursorX = getKeyboardNo1() - 1;
144     scrollToVisible();
145   }
146
147   void setCursorRowAndColumn()
148   {
149     if (keyboardNo2 == null)
150     {
151       keyboardNo2 = new StringBuffer();
152     }
153     else
154     {
155       seqCanvas.cursorX = getKeyboardNo1() - 1;
156       seqCanvas.cursorY = getKeyboardNo2() - 1;
157       scrollToVisible();
158     }
159   }
160
161   void setCursorPosition()
162   {
163     SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
164
165     seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1()) - 1;
166     scrollToVisible();
167   }
168
169   void moveCursor(int dx, int dy)
170   {
171     seqCanvas.cursorX += dx;
172     seqCanvas.cursorY += dy;
173     if (av.hasHiddenColumns()
174             && !av.getColumnSelection().isVisible(seqCanvas.cursorX))
175     {
176       int original = seqCanvas.cursorX - dx;
177       int maxWidth = av.getAlignment().getWidth();
178
179       while (!av.getColumnSelection().isVisible(seqCanvas.cursorX)
180               && seqCanvas.cursorX < maxWidth && seqCanvas.cursorX > 0)
181       {
182         seqCanvas.cursorX += dx;
183       }
184
185       if (seqCanvas.cursorX >= maxWidth
186               || !av.getColumnSelection().isVisible(seqCanvas.cursorX))
187       {
188         seqCanvas.cursorX = original;
189       }
190     }
191     scrollToVisible();
192   }
193
194   void scrollToVisible()
195   {
196     if (seqCanvas.cursorX < 0)
197     {
198       seqCanvas.cursorX = 0;
199     }
200     else if (seqCanvas.cursorX > av.getAlignment().getWidth() - 1)
201     {
202       seqCanvas.cursorX = av.getAlignment().getWidth() - 1;
203     }
204
205     if (seqCanvas.cursorY < 0)
206     {
207       seqCanvas.cursorY = 0;
208     }
209     else if (seqCanvas.cursorY > av.getAlignment().getHeight() - 1)
210     {
211       seqCanvas.cursorY = av.getAlignment().getHeight() - 1;
212     }
213
214     endEditing();
215     if (av.getWrapAlignment())
216     {
217       ap.scrollToWrappedVisible(seqCanvas.cursorX);
218     }
219     else
220     {
221       while (seqCanvas.cursorY < av.startSeq)
222       {
223         ap.scrollUp(true);
224       }
225       while (seqCanvas.cursorY + 1 > av.endSeq)
226       {
227         ap.scrollUp(false);
228       }
229       while (seqCanvas.cursorX < av.getColumnSelection()
230               .adjustForHiddenColumns(av.startRes))
231       {
232
233         if (!ap.scrollRight(false))
234         {
235           break;
236         }
237       }
238       while (seqCanvas.cursorX > av.getColumnSelection()
239               .adjustForHiddenColumns(av.endRes))
240       {
241         if (!ap.scrollRight(true))
242         {
243           break;
244         }
245       }
246     }
247     setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY),
248             seqCanvas.cursorX, seqCanvas.cursorY);
249
250     seqCanvas.repaint();
251   }
252
253   void setSelectionAreaAtCursor(boolean topLeft)
254   {
255     SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
256
257     if (av.getSelectionGroup() != null)
258     {
259       SequenceGroup sg = av.getSelectionGroup();
260       // Find the top and bottom of this group
261       int min = av.getAlignment().getHeight(), max = 0;
262       for (int i = 0; i < sg.getSize(); i++)
263       {
264         int index = av.getAlignment().findIndex(sg.getSequenceAt(i));
265         if (index > max)
266         {
267           max = index;
268         }
269         if (index < min)
270         {
271           min = index;
272         }
273       }
274
275       max++;
276
277       if (topLeft)
278       {
279         sg.setStartRes(seqCanvas.cursorX);
280         if (sg.getEndRes() < seqCanvas.cursorX)
281         {
282           sg.setEndRes(seqCanvas.cursorX);
283         }
284
285         min = seqCanvas.cursorY;
286       }
287       else
288       {
289         sg.setEndRes(seqCanvas.cursorX);
290         if (sg.getStartRes() > seqCanvas.cursorX)
291         {
292           sg.setStartRes(seqCanvas.cursorX);
293         }
294
295         max = seqCanvas.cursorY + 1;
296       }
297
298       if (min > max)
299       {
300         // Only the user can do this
301         av.setSelectionGroup(null);
302       }
303       else
304       {
305         // Now add any sequences between min and max
306         sg.clear();
307         for (int i = min; i < max; i++)
308         {
309           sg.addSequence(av.getAlignment().getSequenceAt(i), false);
310         }
311       }
312     }
313
314     if (av.getSelectionGroup() == null)
315     {
316       SequenceGroup sg = new SequenceGroup();
317       sg.setStartRes(seqCanvas.cursorX);
318       sg.setEndRes(seqCanvas.cursorX);
319       sg.addSequence(sequence, false);
320       av.setSelectionGroup(sg);
321     }
322     ap.paintAlignment(false);
323     av.sendSelection();
324   }
325
326   void insertGapAtCursor(boolean group)
327   {
328     groupEditing = group;
329     startseq = seqCanvas.cursorY;
330     lastres = seqCanvas.cursorX;
331     editSequence(true, seqCanvas.cursorX + getKeyboardNo1());
332     endEditing();
333   }
334
335   void deleteGapAtCursor(boolean group)
336   {
337     groupEditing = group;
338     startseq = seqCanvas.cursorY;
339     lastres = seqCanvas.cursorX + getKeyboardNo1();
340     editSequence(false, seqCanvas.cursorX);
341     endEditing();
342   }
343
344   void numberPressed(char value)
345   {
346     if (keyboardNo1 == null)
347     {
348       keyboardNo1 = new StringBuffer();
349     }
350
351     if (keyboardNo2 != null)
352     {
353       keyboardNo2.append(value);
354     }
355     else
356     {
357       keyboardNo1.append(value);
358     }
359   }
360
361   int getKeyboardNo1()
362   {
363     try
364     {
365       if (keyboardNo1 != null)
366       {
367         int value = Integer.parseInt(keyboardNo1.toString());
368         keyboardNo1 = null;
369         return value;
370       }
371     } catch (Exception x)
372     {
373     }
374     keyboardNo1 = null;
375     return 1;
376   }
377
378   int getKeyboardNo2()
379   {
380     try
381     {
382       if (keyboardNo2 != null)
383       {
384         int value = Integer.parseInt(keyboardNo2.toString());
385         keyboardNo2 = null;
386         return value;
387       }
388     } catch (Exception x)
389     {
390     }
391     keyboardNo2 = null;
392     return 1;
393   }
394
395   void setStatusMessage(SequenceI sequence, int res, int seq)
396   {
397     StringBuffer text = new StringBuffer("Sequence " + (seq + 1) + " ID: "
398             + sequence.getName());
399
400     Object obj = null;
401     if (av.getAlignment().isNucleotide())
402     {
403       obj = ResidueProperties.nucleotideName.get(sequence.getCharAt(res)
404               + "");
405       if (obj != null)
406       {
407         text.append(" Nucleotide: ");
408       }
409     }
410     else
411     {
412       obj = ResidueProperties.aa2Triplet.get(sequence.getCharAt(res) + "");
413       if (obj != null)
414       {
415         text.append("  Residue: ");
416       }
417     }
418
419     if (obj != null)
420     {
421
422       if (obj != "")
423       {
424         text.append(obj + " (" + sequence.findPosition(res) + ")");
425       }
426     }
427
428     ap.alignFrame.statusBar.setText(text.toString());
429
430   }
431
432   public void mousePressed(MouseEvent evt)
433   {
434     lastMousePress = evt.getPoint();
435
436     // For now, ignore the mouseWheel font resizing on Macs
437     // As the Button2_mask always seems to be true
438     if ((evt.getModifiers() & InputEvent.BUTTON2_MASK) == InputEvent.BUTTON2_MASK
439             && !av.MAC)
440     {
441       mouseWheelPressed = true;
442       return;
443     }
444
445     if (evt.isShiftDown() || evt.isControlDown() || evt.isAltDown())
446     {
447       if (evt.isControlDown() || evt.isAltDown())
448       {
449         groupEditing = true;
450       }
451       editingSeqs = true;
452     }
453     else
454     {
455       doMousePressedDefineMode(evt);
456       return;
457     }
458
459     int seq = findSeq(evt);
460     int res = findRes(evt);
461
462     if (seq < 0 || res < 0)
463     {
464       return;
465     }
466
467     if ((seq < av.getAlignment().getHeight())
468             && (res < av.getAlignment().getSequenceAt(seq).getLength()))
469     {
470       startseq = seq;
471       lastres = res;
472     }
473     else
474     {
475       startseq = -1;
476       lastres = -1;
477     }
478
479     return;
480   }
481
482   public void mouseClicked(MouseEvent evt)
483   {
484     SequenceI sequence = av.getAlignment().getSequenceAt(findSeq(evt));
485     if (evt.getClickCount() > 1)
486     {
487       if (av.getSelectionGroup() != null
488               && av.getSelectionGroup().getSize() == 1
489               && av.getSelectionGroup().getEndRes()
490                       - av.getSelectionGroup().getStartRes() < 2)
491       {
492         av.setSelectionGroup(null);
493       }
494
495       SequenceFeature[] features = findFeaturesAtRes(sequence,
496               sequence.findPosition(findRes(evt)));
497
498       if (features != null && features.length > 0)
499       {
500         SearchResults highlight = new SearchResults();
501         highlight.addResult(sequence, features[0].getBegin(),
502                 features[0].getEnd());
503         seqCanvas.highlightSearchResults(highlight);
504       }
505       if (features != null && features.length > 0)
506       {
507         seqCanvas.getFeatureRenderer().amendFeatures(new SequenceI[]
508         { sequence }, features, false, ap);
509
510         seqCanvas.highlightSearchResults(null);
511       }
512     }
513   }
514
515   public void mouseReleased(MouseEvent evt)
516   {
517     mouseDragging = false;
518     mouseWheelPressed = false;
519     ap.paintAlignment(true);
520
521     if (!editingSeqs)
522     {
523       doMouseReleasedDefineMode(evt);
524       return;
525     }
526
527     endEditing();
528
529   }
530
531   int startWrapBlock = -1;
532
533   int wrappedBlock = -1;
534
535   int findRes(MouseEvent evt)
536   {
537     int res = 0;
538     int x = evt.getX();
539
540     if (av.getWrapAlignment())
541     {
542
543       int hgap = av.getCharHeight();
544       if (av.getScaleAboveWrapped())
545       {
546         hgap += av.getCharHeight();
547       }
548
549       int cHeight = av.getAlignment().getHeight() * av.getCharHeight()
550               + hgap
551               + seqCanvas.getAnnotationHeight();
552
553       int y = evt.getY();
554       y -= hgap;
555       x -= seqCanvas.LABEL_WEST;
556
557       int cwidth = seqCanvas.getWrappedCanvasWidth(getSize().width);
558       if (cwidth < 1)
559       {
560         return 0;
561       }
562
563       wrappedBlock = y / cHeight;
564       wrappedBlock += av.getStartRes() / cwidth;
565
566       res = wrappedBlock * cwidth + x / av.getCharWidth();
567
568     }
569     else
570     {
571       res = (x / av.getCharWidth()) + av.getStartRes();
572     }
573
574     if (av.hasHiddenColumns())
575     {
576       res = av.getColumnSelection().adjustForHiddenColumns(res);
577     }
578
579     return res;
580
581   }
582
583   int findSeq(MouseEvent evt)
584   {
585     final int sqnum = findAlRow(evt);
586     return (sqnum < 0) ? 0 : sqnum;
587   }
588
589   /**
590    * 
591    * @param evt
592    * @return row in alignment that was selected (or -1 for column selection)
593    */
594   private int findAlRow(MouseEvent evt)
595   {
596     int seq = 0;
597     int y = evt.getY();
598
599     if (av.getWrapAlignment())
600     {
601       int hgap = av.getCharHeight();
602       if (av.getScaleAboveWrapped())
603       {
604         hgap += av.getCharHeight();
605       }
606
607       int cHeight = av.getAlignment().getHeight() * av.getCharHeight()
608               + 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     StringBuilder text = new StringBuilder();
727     text.append("Sequence ").append(Integer.toString(seq + 1))
728             .append(" ID: ").append(sequence.getName());
729
730     String obj = null;
731     final String ch = String.valueOf(sequence.getCharAt(res));
732     if (av.getAlignment().isNucleotide())
733     {
734       obj = ResidueProperties.nucleotideName.get(ch);
735       if (obj != null)
736       {
737         text.append(" Nucleotide: ").append(obj);
738       }
739     }
740     else
741     {
742       obj = "X".equalsIgnoreCase(ch) ? "STOP"
743               : ResidueProperties.aa2Triplet.get(ch);
744       if (obj != null)
745       {
746         text.append(" Residue: ").append(obj);
747       }
748     }
749
750     if (obj != null)
751     {
752       text.append(" (").append(Integer.toString(respos)).append(")");
753     }
754
755     ap.alignFrame.statusBar.setText(text.toString());
756
757     StringBuilder tooltipText = new StringBuilder();
758     SequenceGroup[] groups = av.getAlignment().findAllGroups(sequence);
759     if (groups != null)
760     {
761       for (int g = 0; g < groups.length; g++)
762       {
763         if (groups[g].getStartRes() <= res && groups[g].getEndRes() >= res)
764         {
765           if (!groups[g].getName().startsWith("JTreeGroup")
766                   && !groups[g].getName().startsWith("JGroup"))
767           {
768             tooltipText.append(groups[g].getName()).append(" ");
769           }
770           if (groups[g].getDescription() != null)
771           {
772             tooltipText.append(groups[g].getDescription());
773           }
774           tooltipText.append("\n");
775         }
776       }
777     }
778
779     // use aa to see if the mouse pointer is on a
780     SequenceFeature[] allFeatures = findFeaturesAtRes(sequence,
781             sequence.findPosition(res));
782
783     int index = 0;
784     while (index < allFeatures.length)
785     {
786       SequenceFeature sf = allFeatures[index];
787
788       tooltipText.append(sf.getType() + " " + sf.begin + ":" + sf.end);
789
790       if (sf.getDescription() != null)
791       {
792         tooltipText.append(" " + sf.getDescription());
793       }
794
795       if (sf.getValue("status") != null)
796       {
797         String status = sf.getValue("status").toString();
798         if (status.length() > 0)
799         {
800           tooltipText.append(" (" + sf.getValue("status") + ")");
801         }
802       }
803       tooltipText.append("\n");
804
805       index++;
806     }
807
808     if (tooltip == null)
809     {
810       tooltip = new Tooltip(tooltipText.toString(), seqCanvas);
811     }
812     else
813     {
814       tooltip.setTip(tooltipText.toString());
815     }
816   }
817
818   SequenceFeature[] findFeaturesAtRes(SequenceI sequence, int res)
819   {
820     Vector tmp = new Vector();
821     SequenceFeature[] features = sequence.getSequenceFeatures();
822     if (features != null)
823     {
824       for (int i = 0; i < features.length; i++)
825       {
826         if (av.getFeaturesDisplayed() == null
827                 || !av.getFeaturesDisplayed().isVisible(features[i].getType()))
828         {
829           continue;
830         }
831
832         if (features[i].featureGroup != null
833                 && !seqCanvas.fr.checkGroupVisibility(features[i].featureGroup,false))
834         {
835           continue;
836         }
837
838         if ((features[i].getBegin() <= res)
839                 && (features[i].getEnd() >= res))
840         {
841           tmp.addElement(features[i]);
842         }
843       }
844     }
845
846     features = new SequenceFeature[tmp.size()];
847     tmp.copyInto(features);
848
849     return features;
850   }
851
852   Tooltip tooltip;
853
854   public void mouseDragged(MouseEvent evt)
855   {
856     if (mouseWheelPressed)
857     {
858       int oldWidth = av.getCharWidth();
859
860       // Which is bigger, left-right or up-down?
861       if (Math.abs(evt.getY() - lastMousePress.y) > Math.abs(evt.getX()
862               - lastMousePress.x))
863       {
864         int fontSize = av.font.getSize();
865
866         if (evt.getY() < lastMousePress.y && av.getCharHeight() > 1)
867         {
868           fontSize--;
869         }
870         else if (evt.getY() > lastMousePress.y)
871         {
872           fontSize++;
873         }
874
875         if (fontSize < 1)
876         {
877           fontSize = 1;
878         }
879
880         av.setFont(new Font(av.font.getName(), av.font.getStyle(), fontSize));
881         av.setCharWidth(oldWidth);
882       }
883       else
884       {
885         if (evt.getX() < lastMousePress.x && av.getCharWidth() > 1)
886         {
887           av.setCharWidth(av.getCharWidth() - 1);
888         }
889         else if (evt.getX() > lastMousePress.x)
890         {
891           av.setCharWidth(av.getCharWidth() + 1);
892         }
893
894         if (av.getCharWidth() < 1)
895         {
896           av.setCharWidth(1);
897         }
898       }
899
900       ap.fontChanged();
901
902       FontMetrics fm = getFontMetrics(av.getFont());
903       av.validCharWidth = fm.charWidth('M') <= av.getCharWidth();
904
905       lastMousePress = evt.getPoint();
906
907       ap.paintAlignment(false);
908       ap.annotationPanel.image = null;
909       return;
910     }
911
912     if (!editingSeqs)
913     {
914       doMouseDraggedDefineMode(evt);
915       return;
916     }
917
918     int res = findRes(evt);
919
920     if (res < 0)
921     {
922       res = 0;
923     }
924
925     if ((lastres == -1) || (lastres == res))
926     {
927       return;
928     }
929
930     if ((res < av.getAlignment().getWidth()) && (res < lastres))
931     {
932       // dragLeft, delete gap
933       editSequence(false, res);
934     }
935     else
936     {
937       editSequence(true, res);
938     }
939
940     mouseDragging = true;
941     if (scrollThread != null)
942     {
943       scrollThread.setEvent(evt);
944     }
945
946   }
947
948   synchronized void editSequence(boolean insertGap, int startres)
949   {
950     int fixedLeft = -1;
951     int fixedRight = -1;
952     boolean fixedColumns = false;
953     SequenceGroup sg = av.getSelectionGroup();
954
955     SequenceI seq = av.getAlignment().getSequenceAt(startseq);
956
957     if (!groupEditing && av.hasHiddenRows())
958     {
959       if (av.isHiddenRepSequence(seq))
960       {
961         sg = av.getRepresentedSequences(seq);
962         groupEditing = true;
963       }
964     }
965
966     StringBuffer message = new StringBuffer();
967     if (groupEditing)
968     {
969       message.append(MessageManager.getString("action.edit_group")).append(
970               ":");
971       if (editCommand == null)
972       {
973         editCommand = new EditCommand(
974                 MessageManager.getString("action.edit_group"));
975       }
976     }
977     else
978     {
979       message.append(MessageManager.getString("label.edit_sequence"))
980               .append(" " + seq.getName());
981       String label = seq.getName();
982       if (label.length() > 10)
983       {
984         label = label.substring(0, 10);
985       }
986       if (editCommand == null)
987       {
988         editCommand = new EditCommand(MessageManager.formatMessage(
989                 "label.edit_params", new String[]
990                 { 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, startres,
1193                   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, startres,
1209                   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, av.getAlignment(),
1305             true);
1306
1307   }
1308
1309   void deleteChar(int j, SequenceI[] seq, int fixedColumn)
1310   {
1311
1312     editCommand.appendEdit(Action.DELETE_GAP, seq, j, 1, av.getAlignment(),
1313             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 }