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