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