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