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