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