0e12703b0bb7360e9e8583165c7fff48e38c2877
[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);
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(new Font(av.font.getName(), av.font.getStyle(), fontSize));
963         av.setCharWidth(oldWidth);
964       }
965       else
966       {
967         if (evt.getX() < lastMousePress.x && av.getCharWidth() > 1)
968         {
969           av.setCharWidth(av.getCharWidth() - 1);
970         }
971         else if (evt.getX() > lastMousePress.x)
972         {
973           av.setCharWidth(av.getCharWidth() + 1);
974         }
975
976         if (av.getCharWidth() < 1)
977         {
978           av.setCharWidth(1);
979         }
980       }
981
982       ap.fontChanged();
983
984       FontMetrics fm = getFontMetrics(av.getFont());
985       av.validCharWidth = fm.charWidth('M') <= av.getCharWidth();
986
987       lastMousePress = evt.getPoint();
988
989       ap.paintAlignment(false);
990       ap.annotationPanel.image = null;
991       return;
992     }
993
994     if (!editingSeqs)
995     {
996       doMouseDraggedDefineMode(evt);
997       return;
998     }
999
1000     int res = findRes(evt);
1001
1002     if (res < 0)
1003     {
1004       res = 0;
1005     }
1006
1007     if ((lastres == -1) || (lastres == res))
1008     {
1009       return;
1010     }
1011
1012     if ((res < av.getAlignment().getWidth()) && (res < lastres))
1013     {
1014       // dragLeft, delete gap
1015       editSequence(false, res);
1016     }
1017     else
1018     {
1019       editSequence(true, res);
1020     }
1021
1022     mouseDragging = true;
1023     if (scrollThread != null)
1024     {
1025       scrollThread.setEvent(evt);
1026     }
1027
1028   }
1029
1030   synchronized void editSequence(boolean insertGap, int startres)
1031   {
1032     int fixedLeft = -1;
1033     int fixedRight = -1;
1034     boolean fixedColumns = false;
1035     SequenceGroup sg = av.getSelectionGroup();
1036
1037     SequenceI seq = av.getAlignment().getSequenceAt(startseq);
1038
1039     if (!groupEditing && av.hasHiddenRows())
1040     {
1041       if (av.isHiddenRepSequence(seq))
1042       {
1043         sg = av.getRepresentedSequences(seq);
1044         groupEditing = true;
1045       }
1046     }
1047
1048     StringBuffer message = new StringBuffer();
1049     if (groupEditing)
1050     {
1051       message.append(MessageManager.getString("action.edit_group")).append(
1052               ":");
1053       if (editCommand == null)
1054       {
1055         editCommand = new EditCommand(
1056                 MessageManager.getString("action.edit_group"));
1057       }
1058     }
1059     else
1060     {
1061       message.append(MessageManager.getString("label.edit_sequence"))
1062               .append(" " + seq.getName());
1063       String label = seq.getName();
1064       if (label.length() > 10)
1065       {
1066         label = label.substring(0, 10);
1067       }
1068       if (editCommand == null)
1069       {
1070         editCommand = new EditCommand(MessageManager.formatMessage(
1071                 "label.edit_params", new String[] { label }));
1072       }
1073     }
1074
1075     if (insertGap)
1076     {
1077       message.append(" insert ");
1078     }
1079     else
1080     {
1081       message.append(" delete ");
1082     }
1083
1084     message.append(Math.abs(startres - lastres) + " gaps.");
1085     ap.alignFrame.statusBar.setText(message.toString());
1086
1087     // Are we editing within a selection group?
1088     if (groupEditing
1089             || (sg != null && sg.getSequences(av.getHiddenRepSequences())
1090                     .contains(seq)))
1091     {
1092       fixedColumns = true;
1093
1094       // sg might be null as the user may only see 1 sequence,
1095       // but the sequence represents a group
1096       if (sg == null)
1097       {
1098         if (!av.isHiddenRepSequence(seq))
1099         {
1100           endEditing();
1101           return;
1102         }
1103
1104         sg = av.getRepresentedSequences(seq);
1105       }
1106
1107       fixedLeft = sg.getStartRes();
1108       fixedRight = sg.getEndRes();
1109
1110       if ((startres < fixedLeft && lastres >= fixedLeft)
1111               || (startres >= fixedLeft && lastres < fixedLeft)
1112               || (startres > fixedRight && lastres <= fixedRight)
1113               || (startres <= fixedRight && lastres > fixedRight))
1114       {
1115         endEditing();
1116         return;
1117       }
1118
1119       if (fixedLeft > startres)
1120       {
1121         fixedRight = fixedLeft - 1;
1122         fixedLeft = 0;
1123       }
1124       else if (fixedRight < startres)
1125       {
1126         fixedLeft = fixedRight;
1127         fixedRight = -1;
1128       }
1129     }
1130
1131     if (av.hasHiddenColumns())
1132     {
1133       fixedColumns = true;
1134       int y1 = av.getColumnSelection().getHiddenBoundaryLeft(startres);
1135       int y2 = av.getColumnSelection().getHiddenBoundaryRight(startres);
1136
1137       if ((insertGap && startres > y1 && lastres < y1)
1138               || (!insertGap && startres < y2 && lastres > y2))
1139       {
1140         endEditing();
1141         return;
1142       }
1143
1144       // System.out.print(y1+" "+y2+" "+fixedLeft+" "+fixedRight+"~~");
1145       // Selection spans a hidden region
1146       if (fixedLeft < y1 && (fixedRight > y2 || fixedRight == -1))
1147       {
1148         if (startres >= y2)
1149         {
1150           fixedLeft = y2;
1151         }
1152         else
1153         {
1154           fixedRight = y2 - 1;
1155         }
1156       }
1157     }
1158
1159     if (groupEditing)
1160     {
1161       SequenceI[] groupSeqs = sg.getSequences(av.getHiddenRepSequences())
1162               .toArray(new SequenceI[0]);
1163
1164       // drag to right
1165       if (insertGap)
1166       {
1167         // If the user has selected the whole sequence, and is dragging to
1168         // the right, we can still extend the alignment and selectionGroup
1169         if (sg.getStartRes() == 0 && sg.getEndRes() == fixedRight
1170                 && sg.getEndRes() == av.getAlignment().getWidth() - 1)
1171         {
1172           sg.setEndRes(av.getAlignment().getWidth() + startres - lastres);
1173           fixedRight = sg.getEndRes();
1174         }
1175
1176         // Is it valid with fixed columns??
1177         // Find the next gap before the end
1178         // of the visible region boundary
1179         boolean blank = false;
1180         for (fixedRight = fixedRight; fixedRight > lastres; fixedRight--)
1181         {
1182           blank = true;
1183
1184           for (SequenceI gs : groupSeqs)
1185           {
1186             for (int j = 0; j < startres - lastres; j++)
1187             {
1188               if (!jalview.util.Comparison.isGap(gs.getCharAt(fixedRight
1189                       - j)))
1190               {
1191                 blank = false;
1192                 break;
1193               }
1194             }
1195           }
1196           if (blank)
1197           {
1198             break;
1199           }
1200         }
1201
1202         if (!blank)
1203         {
1204           if (sg.getSize() == av.getAlignment().getHeight())
1205           {
1206             if ((av.hasHiddenColumns() && startres < av
1207                     .getColumnSelection().getHiddenBoundaryRight(startres)))
1208             {
1209               endEditing();
1210               return;
1211             }
1212
1213             int alWidth = av.getAlignment().getWidth();
1214             if (av.hasHiddenRows())
1215             {
1216               int hwidth = av.getAlignment().getHiddenSequences()
1217                       .getWidth();
1218               if (hwidth > alWidth)
1219               {
1220                 alWidth = hwidth;
1221               }
1222             }
1223             // We can still insert gaps if the selectionGroup
1224             // contains all the sequences
1225             sg.setEndRes(sg.getEndRes() + startres - lastres);
1226             fixedRight = alWidth + startres - lastres;
1227           }
1228           else
1229           {
1230             endEditing();
1231             return;
1232           }
1233         }
1234       }
1235
1236       // drag to left
1237       else if (!insertGap)
1238       {
1239         // / Are we able to delete?
1240         // ie are all columns blank?
1241
1242         for (SequenceI gs : groupSeqs)
1243         {
1244           for (int j = startres; j < lastres; j++)
1245           {
1246             if (gs.getLength() <= j)
1247             {
1248               continue;
1249             }
1250
1251             if (!jalview.util.Comparison.isGap(gs.getCharAt(j)))
1252             {
1253               // Not a gap, block edit not valid
1254               endEditing();
1255               return;
1256             }
1257           }
1258         }
1259       }
1260
1261       if (insertGap)
1262       {
1263         // dragging to the right
1264         if (fixedColumns && fixedRight != -1)
1265         {
1266           for (int j = lastres; j < startres; j++)
1267           {
1268             insertChar(j, groupSeqs, fixedRight);
1269           }
1270         }
1271         else
1272         {
1273           editCommand.appendEdit(Action.INSERT_GAP, groupSeqs, startres,
1274                   startres - lastres, av.getAlignment(), true);
1275         }
1276       }
1277       else
1278       {
1279         // dragging to the left
1280         if (fixedColumns && fixedRight != -1)
1281         {
1282           for (int j = lastres; j > startres; j--)
1283           {
1284             deleteChar(startres, groupSeqs, fixedRight);
1285           }
1286         }
1287         else
1288         {
1289           editCommand.appendEdit(Action.DELETE_GAP, groupSeqs, startres,
1290                   lastres - startres, av.getAlignment(), true);
1291         }
1292
1293       }
1294     }
1295     else
1296     // ///Editing a single sequence///////////
1297     {
1298       if (insertGap)
1299       {
1300         // dragging to the right
1301         if (fixedColumns && fixedRight != -1)
1302         {
1303           for (int j = lastres; j < startres; j++)
1304           {
1305             insertChar(j, new SequenceI[] { seq }, fixedRight);
1306           }
1307         }
1308         else
1309         {
1310           editCommand.appendEdit(Action.INSERT_GAP,
1311                   new SequenceI[] { seq }, lastres, startres - lastres,
1312                   av.getAlignment(), true);
1313         }
1314       }
1315       else
1316       {
1317         // dragging to the left
1318         if (fixedColumns && fixedRight != -1)
1319         {
1320           for (int j = lastres; j > startres; j--)
1321           {
1322             if (!jalview.util.Comparison.isGap(seq.getCharAt(startres)))
1323             {
1324               endEditing();
1325               break;
1326             }
1327             deleteChar(startres, new SequenceI[] { seq }, fixedRight);
1328           }
1329         }
1330         else
1331         {
1332           // could be a keyboard edit trying to delete none gaps
1333           int max = 0;
1334           for (int m = startres; m < lastres; m++)
1335           {
1336             if (!jalview.util.Comparison.isGap(seq.getCharAt(m)))
1337             {
1338               break;
1339             }
1340             max++;
1341           }
1342
1343           if (max > 0)
1344           {
1345             editCommand.appendEdit(Action.DELETE_GAP,
1346                     new SequenceI[] { seq }, startres, max,
1347                     av.getAlignment(), true);
1348           }
1349         }
1350       }
1351     }
1352
1353     lastres = startres;
1354     seqCanvas.repaint();
1355   }
1356
1357   void insertChar(int j, SequenceI[] seq, int fixedColumn)
1358   {
1359     int blankColumn = fixedColumn;
1360     for (int s = 0; s < seq.length; s++)
1361     {
1362       // Find the next gap before the end of the visible region boundary
1363       // If lastCol > j, theres a boundary after the gap insertion
1364
1365       for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)
1366       {
1367         if (jalview.util.Comparison.isGap(seq[s].getCharAt(blankColumn)))
1368         {
1369           // Theres a space, so break and insert the gap
1370           break;
1371         }
1372       }
1373
1374       if (blankColumn <= j)
1375       {
1376         blankColumn = fixedColumn;
1377         endEditing();
1378         return;
1379       }
1380     }
1381
1382     editCommand.appendEdit(Action.DELETE_GAP, seq, blankColumn, 1,
1383             av.getAlignment(), true);
1384
1385     editCommand.appendEdit(Action.INSERT_GAP, seq, j, 1, av.getAlignment(),
1386             true);
1387
1388   }
1389
1390   void deleteChar(int j, SequenceI[] seq, int fixedColumn)
1391   {
1392
1393     editCommand.appendEdit(Action.DELETE_GAP, seq, j, 1, av.getAlignment(),
1394             true);
1395
1396     editCommand.appendEdit(Action.INSERT_GAP, seq, fixedColumn, 1,
1397             av.getAlignment(), true);
1398   }
1399
1400   // ////////////////////////////////////////
1401   // ///Everything below this is for defining the boundary of the rubberband
1402   // ////////////////////////////////////////
1403   public void doMousePressedDefineMode(MouseEvent evt)
1404   {
1405     if (scrollThread != null)
1406     {
1407       scrollThread.running = false;
1408       scrollThread = null;
1409     }
1410
1411     int res = findRes(evt);
1412     int seq = findSeq(evt);
1413     oldSeq = seq;
1414     startWrapBlock = wrappedBlock;
1415
1416     if (seq == -1)
1417     {
1418       return;
1419     }
1420
1421     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
1422
1423     if (sequence == null || res > sequence.getLength())
1424     {
1425       return;
1426     }
1427
1428     stretchGroup = av.getSelectionGroup();
1429
1430     if (stretchGroup == null || !stretchGroup.contains(sequence, res))
1431     {
1432       stretchGroup = av.getAlignment().findGroup(sequence, res);
1433       if (stretchGroup != null)
1434       {
1435         // only update the current selection if the popup menu has a group to
1436         // focus on
1437         av.setSelectionGroup(stretchGroup);
1438       }
1439     }
1440
1441     // DETECT RIGHT MOUSE BUTTON IN AWT
1442     if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
1443     {
1444       SequenceFeature[] allFeatures = findFeaturesAtRes(sequence,
1445               sequence.findPosition(res));
1446
1447       Vector<String> links = null;
1448       if (allFeatures != null)
1449       {
1450         for (int i = 0; i < allFeatures.length; i++)
1451         {
1452           if (allFeatures[i].links != null)
1453           {
1454             if (links == null)
1455             {
1456               links = new Vector<String>();
1457             }
1458             for (int j = 0; j < allFeatures[i].links.size(); j++)
1459             {
1460               links.addElement(allFeatures[i].links.elementAt(j));
1461             }
1462           }
1463         }
1464       }
1465       APopupMenu popup = new APopupMenu(ap, null, links);
1466       this.add(popup);
1467       popup.show(this, evt.getX(), evt.getY());
1468       return;
1469     }
1470
1471     if (av.cursorMode)
1472     {
1473       seqCanvas.cursorX = findRes(evt);
1474       seqCanvas.cursorY = findSeq(evt);
1475       seqCanvas.repaint();
1476       return;
1477     }
1478
1479     // Only if left mouse button do we want to change group sizes
1480
1481     if (stretchGroup == null)
1482     {
1483       // define a new group here
1484       SequenceGroup sg = new SequenceGroup();
1485       sg.setStartRes(res);
1486       sg.setEndRes(res);
1487       sg.addSequence(sequence, false);
1488       av.setSelectionGroup(sg);
1489       stretchGroup = sg;
1490
1491       if (av.getConservationSelected())
1492       {
1493         SliderPanel.setConservationSlider(ap, av.getResidueShading(),
1494                 ap.getViewName());
1495       }
1496       if (av.getAbovePIDThreshold())
1497       {
1498         SliderPanel.setPIDSliderSource(ap, av.getResidueShading(),
1499                 ap.getViewName());
1500       }
1501
1502     }
1503   }
1504
1505   public void doMouseReleasedDefineMode(MouseEvent evt)
1506   {
1507     if (stretchGroup == null)
1508     {
1509       return;
1510     }
1511     // always do this - annotation has own state
1512     // but defer colourscheme update until hidden sequences are passed in
1513     boolean vischange = stretchGroup.recalcConservation(true);
1514     // here we rely on stretchGroup == av.getSelection()
1515     needOverviewUpdate |= vischange && av.isSelectionDefinedGroup();
1516     if (stretchGroup.cs != null)
1517     {
1518       stretchGroup.cs.alignmentChanged(stretchGroup,
1519               av.getHiddenRepSequences());
1520
1521       if (stretchGroup.cs.conservationApplied())
1522       {
1523         SliderPanel.setConservationSlider(ap, stretchGroup.cs,
1524                 stretchGroup.getName());
1525       }
1526       if (stretchGroup.cs.getThreshold() > 0)
1527       {
1528         SliderPanel.setPIDSliderSource(ap, stretchGroup.cs,
1529                 stretchGroup.getName());
1530       }
1531     }
1532     PaintRefresher.Refresh(ap, av.getSequenceSetId());
1533     ap.paintAlignment(needOverviewUpdate);
1534     needOverviewUpdate = false;
1535     changeEndRes = false;
1536     changeStartRes = false;
1537     stretchGroup = null;
1538     av.sendSelection();
1539   }
1540
1541   public void doMouseDraggedDefineMode(MouseEvent evt)
1542   {
1543     int res = findRes(evt);
1544     int y = findSeq(evt);
1545
1546     if (wrappedBlock != startWrapBlock)
1547     {
1548       return;
1549     }
1550
1551     if (stretchGroup == null)
1552     {
1553       return;
1554     }
1555
1556     mouseDragging = true;
1557
1558     if (y > av.getAlignment().getHeight())
1559     {
1560       y = av.getAlignment().getHeight() - 1;
1561     }
1562
1563     if (res >= av.getAlignment().getWidth())
1564     {
1565       res = av.getAlignment().getWidth() - 1;
1566     }
1567
1568     if (stretchGroup.getEndRes() == res)
1569     {
1570       // Edit end res position of selected group
1571       changeEndRes = true;
1572     }
1573     else if (stretchGroup.getStartRes() == res)
1574     {
1575       // Edit start res position of selected group
1576       changeStartRes = true;
1577     }
1578
1579     if (res < 0)
1580     {
1581       res = 0;
1582     }
1583
1584     if (changeEndRes)
1585     {
1586       if (res > (stretchGroup.getStartRes() - 1))
1587       {
1588         stretchGroup.setEndRes(res);
1589         needOverviewUpdate |= av.isSelectionDefinedGroup();
1590       }
1591     }
1592     else if (changeStartRes)
1593     {
1594       if (res < (stretchGroup.getEndRes() + 1))
1595       {
1596         stretchGroup.setStartRes(res);
1597         needOverviewUpdate |= av.isSelectionDefinedGroup();
1598       }
1599     }
1600
1601     int dragDirection = 0;
1602
1603     if (y > oldSeq)
1604     {
1605       dragDirection = 1;
1606     }
1607     else if (y < oldSeq)
1608     {
1609       dragDirection = -1;
1610     }
1611
1612     while ((y != oldSeq) && (oldSeq > -1)
1613             && (y < av.getAlignment().getHeight()))
1614     {
1615       // This routine ensures we don't skip any sequences, as the
1616       // selection is quite slow.
1617       Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1618
1619       oldSeq += dragDirection;
1620
1621       if (oldSeq < 0)
1622       {
1623         break;
1624       }
1625
1626       Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1627
1628       if (stretchGroup.getSequences(null).contains(nextSeq))
1629       {
1630         stretchGroup.deleteSequence(seq, false);
1631         needOverviewUpdate |= av.isSelectionDefinedGroup();
1632       }
1633       else
1634       {
1635         if (seq != null)
1636         {
1637           stretchGroup.addSequence(seq, false);
1638         }
1639
1640         stretchGroup.addSequence(nextSeq, false);
1641         needOverviewUpdate |= av.isSelectionDefinedGroup();
1642       }
1643     }
1644
1645     if (oldSeq < 0)
1646     {
1647       oldSeq = -1;
1648     }
1649
1650     if (res > av.getRanges().getEndRes()
1651             || res < av.getRanges().getStartRes()
1652             || y < av.getRanges().getStartSeq()
1653             || y > av.getRanges().getEndSeq())
1654     {
1655       mouseExited(evt);
1656     }
1657
1658     if (scrollThread != null)
1659     {
1660       scrollThread.setEvent(evt);
1661     }
1662
1663     seqCanvas.repaint();
1664   }
1665
1666   @Override
1667   public void mouseEntered(MouseEvent e)
1668   {
1669     if (oldSeq < 0)
1670     {
1671       oldSeq = 0;
1672     }
1673
1674     if (scrollThread != null)
1675     {
1676       scrollThread.running = false;
1677       scrollThread = null;
1678     }
1679   }
1680
1681   @Override
1682   public void mouseExited(MouseEvent e)
1683   {
1684     if (av.getWrapAlignment())
1685     {
1686       return;
1687     }
1688
1689     if (mouseDragging && scrollThread == null)
1690     {
1691       scrollThread = new ScrollThread();
1692     }
1693   }
1694
1695   void scrollCanvas(MouseEvent evt)
1696   {
1697     if (evt == null)
1698     {
1699       if (scrollThread != null)
1700       {
1701         scrollThread.running = false;
1702         scrollThread = null;
1703       }
1704       mouseDragging = false;
1705     }
1706     else
1707     {
1708       if (scrollThread == null)
1709       {
1710         scrollThread = new ScrollThread();
1711       }
1712
1713       mouseDragging = true;
1714       scrollThread.setEvent(evt);
1715     }
1716
1717   }
1718
1719   // this class allows scrolling off the bottom of the visible alignment
1720   class ScrollThread extends Thread
1721   {
1722     MouseEvent evt;
1723
1724     boolean running = false;
1725
1726     public ScrollThread()
1727     {
1728       start();
1729     }
1730
1731     public void setEvent(MouseEvent e)
1732     {
1733       evt = e;
1734     }
1735
1736     public void stopScrolling()
1737     {
1738       running = false;
1739     }
1740
1741     @Override
1742     public void run()
1743     {
1744       running = true;
1745       while (running)
1746       {
1747
1748         if (evt != null)
1749         {
1750
1751           if (mouseDragging && evt.getY() < 0
1752                   && av.getRanges().getStartSeq() > 0)
1753           {
1754             running = ap.scrollUp(true);
1755           }
1756
1757           if (mouseDragging && evt.getY() >= getSize().height
1758                   && av.getAlignment().getHeight() > av.getRanges()
1759                           .getEndSeq())
1760           {
1761             running = ap.scrollUp(false);
1762           }
1763
1764           if (mouseDragging && evt.getX() < 0)
1765           {
1766             running = ap.scrollRight(false);
1767           }
1768
1769           else if (mouseDragging && evt.getX() >= getSize().width)
1770           {
1771             running = ap.scrollRight(true);
1772           }
1773         }
1774
1775         try
1776         {
1777           Thread.sleep(75);
1778         } catch (Exception ex)
1779         {
1780         }
1781       }
1782     }
1783   }
1784
1785   /**
1786    * modify current selection according to a received message.
1787    */
1788   @Override
1789   public void selection(SequenceGroup seqsel, ColumnSelection colsel,
1790           SelectionSource source)
1791   {
1792     // TODO: fix this hack - source of messages is align viewport, but SeqPanel
1793     // handles selection messages...
1794     // TODO: extend config options to allow user to control if selections may be
1795     // shared between viewports.
1796     if (av != null
1797             && (av == source || !av.followSelection || (source instanceof AlignViewport && ((AlignmentViewport) source)
1798                     .getSequenceSetId().equals(av.getSequenceSetId()))))
1799     {
1800       return;
1801     }
1802
1803     /*
1804      * Check for selection in a view of which this one is a dna/protein
1805      * complement.
1806      */
1807     if (selectionFromTranslation(seqsel, colsel, source))
1808     {
1809       return;
1810     }
1811
1812     // do we want to thread this ? (contention with seqsel and colsel locks, I
1813     // suspect)
1814     /*
1815      * only copy colsel if there is a real intersection between
1816      * sequence selection and this panel's alignment
1817      */
1818     boolean repaint = false;
1819     boolean copycolsel = false;
1820     if (av.getSelectionGroup() == null || !av.isSelectionGroupChanged(true))
1821     {
1822       SequenceGroup sgroup = null;
1823       if (seqsel != null && seqsel.getSize() > 0)
1824       {
1825         if (av.getAlignment() == null)
1826         {
1827           System.out
1828                   .println("Selection message: alignviewport av SeqSetId="
1829                           + av.getSequenceSetId() + " ViewId="
1830                           + av.getViewId()
1831                           + " 's alignment is NULL! returning immediatly.");
1832           return;
1833         }
1834         sgroup = seqsel.intersect(av.getAlignment(),
1835                 (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
1836         if ((sgroup != null && sgroup.getSize() > 0))
1837         {
1838           copycolsel = true;
1839         }
1840       }
1841       if (sgroup != null && sgroup.getSize() > 0)
1842       {
1843         av.setSelectionGroup(sgroup);
1844       }
1845       else
1846       {
1847         av.setSelectionGroup(null);
1848       }
1849       repaint = av.isSelectionGroupChanged(true);
1850     }
1851     if (copycolsel
1852             && (av.getColumnSelection() == null || !av
1853                     .isColSelChanged(true)))
1854     {
1855       // the current selection is unset or from a previous message
1856       // so import the new colsel.
1857       if (colsel == null || colsel.isEmpty())
1858       {
1859         if (av.getColumnSelection() != null)
1860         {
1861           av.getColumnSelection().clear();
1862         }
1863       }
1864       else
1865       {
1866         // TODO: shift colSel according to the intersecting sequences
1867         if (av.getColumnSelection() == null)
1868         {
1869           av.setColumnSelection(new ColumnSelection(colsel));
1870         }
1871         else
1872         {
1873           av.getColumnSelection().setElementsFrom(colsel);
1874         }
1875       }
1876       repaint |= av.isColSelChanged(true);
1877     }
1878     if (copycolsel
1879             && av.hasHiddenColumns()
1880             && (av.getColumnSelection() == null || av.getColumnSelection()
1881                     .getHiddenColumns() == null))
1882     {
1883       System.err.println("Bad things");
1884     }
1885     if (repaint)
1886     {
1887       ap.scalePanelHolder.repaint();
1888       ap.repaint();
1889     }
1890   }
1891
1892   /**
1893    * scroll to the given row/column - or nearest visible location
1894    * 
1895    * @param row
1896    * @param column
1897    */
1898   public void scrollTo(int row, int column)
1899   {
1900
1901     row = row < 0 ? ap.av.getRanges().getStartSeq() : row;
1902     column = column < 0 ? ap.av.getRanges().getStartRes() : column;
1903     ap.scrollTo(column, column, row, true, true);
1904   }
1905
1906   /**
1907    * scroll to the given row - or nearest visible location
1908    * 
1909    * @param row
1910    */
1911   public void scrollToRow(int row)
1912   {
1913
1914     row = row < 0 ? ap.av.getRanges().getStartSeq() : row;
1915     ap.scrollTo(ap.av.getRanges().getStartRes(), ap.av.getRanges()
1916             .getStartRes(), row, true, true);
1917   }
1918
1919   /**
1920    * scroll to the given column - or nearest visible location
1921    * 
1922    * @param column
1923    */
1924   public void scrollToColumn(int column)
1925   {
1926
1927     column = column < 0 ? ap.av.getRanges().getStartRes() : column;
1928     ap.scrollTo(column, column, ap.av.getRanges().getStartSeq(), true, true);
1929   }
1930
1931   /**
1932    * If this panel is a cdna/protein translation view of the selection source,
1933    * tries to map the source selection to a local one, and returns true. Else
1934    * returns false.
1935    * 
1936    * @param seqsel
1937    * @param colsel
1938    * @param source
1939    */
1940   protected boolean selectionFromTranslation(SequenceGroup seqsel,
1941           ColumnSelection colsel, SelectionSource source)
1942   {
1943     if (!(source instanceof AlignViewportI))
1944     {
1945       return false;
1946     }
1947     final AlignViewportI sourceAv = (AlignViewportI) source;
1948     if (sourceAv.getCodingComplement() != av
1949             && av.getCodingComplement() != sourceAv)
1950     {
1951       return false;
1952     }
1953
1954     /*
1955      * Map sequence selection
1956      */
1957     SequenceGroup sg = MappingUtils.mapSequenceGroup(seqsel, sourceAv, av);
1958     av.setSelectionGroup(sg);
1959     av.isSelectionGroupChanged(true);
1960
1961     /*
1962      * Map column selection
1963      */
1964     ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv,
1965             av);
1966     av.setColumnSelection(cs);
1967
1968     ap.scalePanelHolder.repaint();
1969     ap.repaint();
1970
1971     return true;
1972   }
1973
1974 }