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