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