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