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