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