03821d7a86e8fbeefc8a65925381f07e05cda1da
[jalview.git] / src / jalview / 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     if (keyboardNo1 != null) 
525     {
526       int value = Integer.parseInt(keyboardNo1.toString());
527       keyboardNo1 = null;
528       return value;
529     }
530     } catch (Exception x)
531     {}
532     keyboardNo1 = null;
533     return 1;
534   }
535
536   int getKeyboardNo2()
537   {
538     try {
539     if (keyboardNo2!=null){
540       int value = Integer.parseInt(keyboardNo2.toString());
541       keyboardNo2 = null;
542       return value;
543     }
544     } catch (Exception x)
545     {}
546     keyboardNo2 = null;
547     return 1;
548   }
549
550   /**
551    * DOCUMENT ME!
552    * 
553    * @param evt
554    *          DOCUMENT ME!
555    */
556   @Override
557   public void mouseReleased(MouseEvent evt)
558   {
559     mouseDragging = false;
560     mouseWheelPressed = false;
561
562     if (!editingSeqs)
563     {
564       doMouseReleasedDefineMode(evt);
565       return;
566     }
567
568     endEditing();
569   }
570
571   /**
572    * DOCUMENT ME!
573    * 
574    * @param evt
575    *          DOCUMENT ME!
576    */
577   @Override
578   public void mousePressed(MouseEvent evt)
579   {
580     lastMousePress = evt.getPoint();
581
582     if (javax.swing.SwingUtilities.isMiddleMouseButton(evt))
583     {
584       mouseWheelPressed = true;
585       return;
586     }
587
588     if (evt.isShiftDown() || evt.isAltDown() || evt.isControlDown())
589     {
590       if (evt.isAltDown() || evt.isControlDown())
591       {
592         groupEditing = true;
593       }
594       editingSeqs = true;
595     }
596     else
597     {
598       doMousePressedDefineMode(evt);
599       return;
600     }
601
602     int seq = findSeq(evt);
603     int res = findRes(evt);
604
605     if (seq < 0 || res < 0)
606     {
607       return;
608     }
609
610     if ((seq < av.getAlignment().getHeight())
611             && (res < av.getAlignment().getSequenceAt(seq).getLength()))
612     {
613       startseq = seq;
614       lastres = res;
615     }
616     else
617     {
618       startseq = -1;
619       lastres = -1;
620     }
621
622     return;
623   }
624
625   String lastMessage;
626
627   @Override
628   public void mouseOverSequence(SequenceI sequence, int index, int pos)
629   {
630     String tmp = sequence.hashCode() + " " + index + " " + pos;
631
632     if (lastMessage == null || !lastMessage.equals(tmp))
633     {
634       // System.err.println("mouseOver Sequence: "+tmp);
635       ssm.mouseOverSequence(sequence, index, pos, av);
636     }
637     lastMessage = tmp;
638   }
639
640   @Override
641   public void highlightSequence(SearchResults results)
642   {
643     if (av.followHighlight)
644     {
645       if (ap.scrollToPosition(results, false))
646       {
647         seqCanvas.revalidate();
648       }
649     }
650     seqCanvas.highlightSearchResults(results);
651   }
652
653   @Override
654   public void updateColours(SequenceI seq, int index)
655   {
656     System.out.println("update the seqPanel colours");
657     // repaint();
658   }
659
660   /**
661    * DOCUMENT ME!
662    * 
663    * @param evt
664    *          DOCUMENT ME!
665    */
666   @Override
667   public void mouseMoved(MouseEvent evt)
668   {
669     if (editingSeqs)
670     {
671       // This is because MacOSX creates a mouseMoved
672       // If control is down, other platforms will not.
673       mouseDragged(evt);
674     }
675
676     int res = findRes(evt);
677     int seq = findSeq(evt);
678     int pos;
679     if (res < 0 || seq < 0 || seq >= av.getAlignment().getHeight())
680     {
681       return;
682     }
683
684     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
685
686     if (res >= sequence.getLength())
687     {
688       return;
689     }
690
691     pos = setStatusMessage(sequence, res, seq);
692     if (ssm != null && pos > -1)
693       mouseOverSequence(sequence, res, pos);
694
695     tooltipText.setLength(6); // Cuts the buffer back to <html>
696
697     SequenceGroup[] groups = av.getAlignment().findAllGroups(sequence);
698     if (groups != null)
699     {
700       for (int g = 0; g < groups.length; g++)
701       {
702         if (groups[g].getStartRes() <= res && groups[g].getEndRes() >= res)
703         {
704           if (tooltipText.length() > 6)
705           {
706             tooltipText.append("<br>");
707           }
708
709           if (!groups[g].getName().startsWith("JTreeGroup")
710                   && !groups[g].getName().startsWith("JGroup"))
711           {
712             tooltipText.append(groups[g].getName());
713           }
714
715           if (groups[g].getDescription() != null)
716           {
717             tooltipText.append(": " + groups[g].getDescription());
718           }
719         }
720       }
721     }
722
723     // use aa to see if the mouse pointer is on a
724     if (av.showSequenceFeatures)
725     {
726       int rpos;
727       SequenceFeature[] features = findFeaturesAtRes(
728               sequence.getDatasetSequence(),
729               rpos = sequence.findPosition(res));
730       seqARep.appendFeatures(tooltipText, rpos, features,
731               this.ap.seqPanel.seqCanvas.fr.minmax);
732     }
733     if (tooltipText.length() == 6) // <html></html>
734     {
735       setToolTipText(null);
736       lastTooltip = null;
737     }
738     else
739     {
740       tooltipText.append("</html>");
741       if (lastTooltip == null
742               || !lastTooltip.equals(tooltipText.toString()))
743       {
744         setToolTipText(tooltipText.toString());
745         lastTooltip = tooltipText.toString();
746       }
747
748     }
749
750   }
751
752   private Point lastp = null;
753
754   /*
755    * (non-Javadoc)
756    * 
757    * @see javax.swing.JComponent#getToolTipLocation(java.awt.event.MouseEvent)
758    */
759   public Point getToolTipLocation(MouseEvent event)
760   {
761     int x = event.getX(), w = getWidth();
762     int wdth = (w - x < 200) ? -(w / 2) : 5; // switch sides when tooltip is too
763     // close to edge
764     Point p = lastp;
765     if (!event.isShiftDown() || p == null)
766     {
767       p = (tooltipText != null && tooltipText.length() > 6) ? new Point(
768               event.getX() + wdth, event.getY() - 20) : null;
769     }
770     /*
771      * TODO: try to modify position region is not obcured by tooltip
772      */
773     return lastp = p;
774   }
775
776   String lastTooltip;
777
778   /**
779    * Set status message in alignment panel
780    * 
781    * @param sequence
782    *          aligned sequence object
783    * @param res
784    *          alignment column
785    * @param seq
786    *          index of sequence in alignment
787    * @return position of res in sequence
788    */
789   int setStatusMessage(SequenceI sequence, int res, int seq)
790   {
791     int pos = -1;
792     StringBuffer text = new StringBuffer("Sequence " + (seq + 1) + " ID: "
793             + sequence.getName());
794
795     Object obj = null;
796     if (av.getAlignment().isNucleotide())
797     {
798       obj = ResidueProperties.nucleotideName.get(sequence.getCharAt(res)
799               + "");
800       if (obj != null)
801       {
802         text.append(" Nucleotide: ");
803       }
804     }
805     else
806     {
807       obj = ResidueProperties.aa2Triplet.get(sequence.getCharAt(res) + "");
808       if (obj != null)
809       {
810         text.append("  Residue: ");
811       }
812     }
813
814     if (obj != null)
815     {
816       pos = sequence.findPosition(res);
817       if (obj != "")
818       {
819         text.append(obj + " (" + pos + ")");
820       }
821     }
822     ap.alignFrame.statusBar.setText(text.toString());
823     return pos;
824   }
825
826   /**
827    * DOCUMENT ME!
828    * 
829    * @param evt
830    *          DOCUMENT ME!
831    */
832   @Override
833   public void mouseDragged(MouseEvent evt)
834   {
835     if (mouseWheelPressed)
836     {
837       int oldWidth = av.charWidth;
838
839       // Which is bigger, left-right or up-down?
840       if (Math.abs(evt.getY() - lastMousePress.getY()) > Math.abs(evt
841               .getX() - lastMousePress.getX()))
842       {
843         int fontSize = av.font.getSize();
844
845         if (evt.getY() < lastMousePress.getY())
846         {
847           fontSize--;
848         }
849         else if (evt.getY() > lastMousePress.getY())
850         {
851           fontSize++;
852         }
853
854         if (fontSize < 1)
855         {
856           fontSize = 1;
857         }
858
859         av.setFont(new Font(av.font.getName(), av.font.getStyle(), fontSize));
860         av.charWidth = oldWidth;
861         ap.fontChanged();
862       }
863       else
864       {
865         if (evt.getX() < lastMousePress.getX() && av.charWidth > 1)
866         {
867           av.charWidth--;
868         }
869         else if (evt.getX() > lastMousePress.getX())
870         {
871           av.charWidth++;
872         }
873
874         ap.paintAlignment(false);
875       }
876
877       FontMetrics fm = getFontMetrics(av.getFont());
878       av.validCharWidth = fm.charWidth('M') <= av.charWidth;
879
880       lastMousePress = evt.getPoint();
881
882       return;
883     }
884
885     if (!editingSeqs)
886     {
887       doMouseDraggedDefineMode(evt);
888       return;
889     }
890
891     int res = findRes(evt);
892
893     if (res < 0)
894     {
895       res = 0;
896     }
897
898     if ((lastres == -1) || (lastres == res))
899     {
900       return;
901     }
902
903     if ((res < av.getAlignment().getWidth()) && (res < lastres))
904     {
905       // dragLeft, delete gap
906       editSequence(false, false, res);
907     }
908     else
909     {
910       editSequence(true, false, res);
911     }
912
913     mouseDragging = true;
914     if (scrollThread != null)
915     {
916       scrollThread.setEvent(evt);
917     }
918   }
919
920   // TODO: Make it more clever than many booleans
921   synchronized void editSequence(boolean insertGap, boolean editSeq,
922           int startres)
923   {
924     int fixedLeft = -1;
925     int fixedRight = -1;
926     boolean fixedColumns = false;
927     SequenceGroup sg = av.getSelectionGroup();
928
929     SequenceI seq = av.getAlignment().getSequenceAt(startseq);
930
931     // No group, but the sequence may represent a group
932     if (!groupEditing && av.hasHiddenRows())
933     {
934       if (av.isHiddenRepSequence(seq))
935       {
936         sg = av.getRepresentedSequences(seq);
937         groupEditing = true;
938       }
939     }
940
941     StringBuffer message = new StringBuffer();
942     if (groupEditing)
943     {
944       message.append("Edit group:");
945       if (editCommand == null)
946       {
947         editCommand = new EditCommand("Edit Group");
948       }
949     }
950     else
951     {
952       message.append("Edit sequence: " + seq.getName());
953       String label = seq.getName();
954       if (label.length() > 10)
955       {
956         label = label.substring(0, 10);
957       }
958       if (editCommand == null)
959       {
960         editCommand = new EditCommand("Edit " + label);
961       }
962     }
963
964     if (insertGap)
965     {
966       message.append(" insert ");
967     }
968     else
969     {
970       message.append(" delete ");
971     }
972
973     message.append(Math.abs(startres - lastres) + " gaps.");
974     ap.alignFrame.statusBar.setText(message.toString());
975
976     // Are we editing within a selection group?
977     if (groupEditing
978             || (sg != null && sg.getSequences(av.getHiddenRepSequences())
979                     .contains(seq)))
980     {
981       fixedColumns = true;
982
983       // sg might be null as the user may only see 1 sequence,
984       // but the sequence represents a group
985       if (sg == null)
986       {
987         if (!av.isHiddenRepSequence(seq))
988         {
989           endEditing();
990           return;
991         }
992         sg = av.getRepresentedSequences(seq);
993       }
994
995       fixedLeft = sg.getStartRes();
996       fixedRight = sg.getEndRes();
997
998       if ((startres < fixedLeft && lastres >= fixedLeft)
999               || (startres >= fixedLeft && lastres < fixedLeft)
1000               || (startres > fixedRight && lastres <= fixedRight)
1001               || (startres <= fixedRight && lastres > fixedRight))
1002       {
1003         endEditing();
1004         return;
1005       }
1006
1007       if (fixedLeft > startres)
1008       {
1009         fixedRight = fixedLeft - 1;
1010         fixedLeft = 0;
1011       }
1012       else if (fixedRight < startres)
1013       {
1014         fixedLeft = fixedRight;
1015         fixedRight = -1;
1016       }
1017     }
1018
1019     if (av.hasHiddenColumns())
1020     {
1021       fixedColumns = true;
1022       int y1 = av.getColumnSelection().getHiddenBoundaryLeft(startres);
1023       int y2 = av.getColumnSelection().getHiddenBoundaryRight(startres);
1024
1025       if ((insertGap && startres > y1 && lastres < y1)
1026               || (!insertGap && startres < y2 && lastres > y2))
1027       {
1028         endEditing();
1029         return;
1030       }
1031
1032       // System.out.print(y1+" "+y2+" "+fixedLeft+" "+fixedRight+"~~");
1033       // Selection spans a hidden region
1034       if (fixedLeft < y1 && (fixedRight > y2 || fixedRight == -1))
1035       {
1036         if (startres >= y2)
1037         {
1038           fixedLeft = y2;
1039         }
1040         else
1041         {
1042           fixedRight = y2 - 1;
1043         }
1044       }
1045     }
1046
1047     if (groupEditing)
1048     {
1049       List<SequenceI> vseqs = sg.getSequences(av.getHiddenRepSequences());
1050       int g, groupSize = vseqs.size();
1051       SequenceI[] groupSeqs = new SequenceI[groupSize];
1052       for (g = 0; g < groupSeqs.length; g++)
1053       {
1054         groupSeqs[g] = vseqs.get(g);
1055       }
1056
1057       // drag to right
1058       if (insertGap)
1059       {
1060         // If the user has selected the whole sequence, and is dragging to
1061         // the right, we can still extend the alignment and selectionGroup
1062         if (sg.getStartRes() == 0 && sg.getEndRes() == fixedRight
1063                 && sg.getEndRes() == av.getAlignment().getWidth() - 1)
1064         {
1065           sg.setEndRes(av.getAlignment().getWidth() + startres - lastres);
1066           fixedRight = sg.getEndRes();
1067         }
1068
1069         // Is it valid with fixed columns??
1070         // Find the next gap before the end
1071         // of the visible region boundary
1072         boolean blank = false;
1073         for (fixedRight = fixedRight; fixedRight > lastres; fixedRight--)
1074         {
1075           blank = true;
1076
1077           for (g = 0; g < groupSize; g++)
1078           {
1079             for (int j = 0; j < startres - lastres; j++)
1080             {
1081               if (!jalview.util.Comparison.isGap(groupSeqs[g]
1082                       .getCharAt(fixedRight - j)))
1083               {
1084                 blank = false;
1085                 break;
1086               }
1087             }
1088           }
1089           if (blank)
1090           {
1091             break;
1092           }
1093         }
1094
1095         if (!blank)
1096         {
1097           if (sg.getSize() == av.getAlignment().getHeight())
1098           {
1099             if ((av.hasHiddenColumns() && startres < av
1100                     .getColumnSelection().getHiddenBoundaryRight(startres)))
1101             {
1102               endEditing();
1103               return;
1104             }
1105
1106             int alWidth = av.getAlignment().getWidth();
1107             if (av.hasHiddenRows())
1108             {
1109               int hwidth = av.getAlignment().getHiddenSequences()
1110                       .getWidth();
1111               if (hwidth > alWidth)
1112               {
1113                 alWidth = hwidth;
1114               }
1115             }
1116             // We can still insert gaps if the selectionGroup
1117             // contains all the sequences
1118             sg.setEndRes(sg.getEndRes() + startres - lastres);
1119             fixedRight = alWidth + startres - lastres;
1120           }
1121           else
1122           {
1123             endEditing();
1124             return;
1125           }
1126         }
1127       }
1128
1129       // drag to left
1130       else if (!insertGap)
1131       {
1132         // / Are we able to delete?
1133         // ie are all columns blank?
1134
1135         for (g = 0; g < groupSize; g++)
1136         {
1137           for (int j = startres; j < lastres; j++)
1138           {
1139             if (groupSeqs[g].getLength() <= j)
1140             {
1141               continue;
1142             }
1143
1144             if (!jalview.util.Comparison.isGap(groupSeqs[g].getCharAt(j)))
1145             {
1146               // Not a gap, block edit not valid
1147               endEditing();
1148               return;
1149             }
1150           }
1151         }
1152       }
1153
1154       if (insertGap)
1155       {
1156         // dragging to the right
1157         if (fixedColumns && fixedRight != -1)
1158         {
1159           for (int j = lastres; j < startres; j++)
1160           {
1161             insertChar(j, groupSeqs, fixedRight);
1162           }
1163         }
1164         else
1165         {
1166           editCommand.appendEdit(EditCommand.INSERT_GAP, groupSeqs,
1167                   startres, startres - lastres, av.getAlignment(), true);
1168         }
1169       }
1170       else
1171       {
1172         // dragging to the left
1173         if (fixedColumns && fixedRight != -1)
1174         {
1175           for (int j = lastres; j > startres; j--)
1176           {
1177             deleteChar(startres, groupSeqs, fixedRight);
1178           }
1179         }
1180         else
1181         {
1182           editCommand.appendEdit(EditCommand.DELETE_GAP, groupSeqs,
1183                   startres, lastres - startres, av.getAlignment(), true);
1184         }
1185
1186       }
1187     }
1188     else
1189     // ///Editing a single sequence///////////
1190     {
1191       if (insertGap)
1192       {
1193         // dragging to the right
1194         if (fixedColumns && fixedRight != -1)
1195         {
1196           for (int j = lastres; j < startres; j++)
1197           {
1198             insertChar(j, new SequenceI[]
1199             { seq }, fixedRight);
1200           }
1201         }
1202         else
1203         {
1204           editCommand.appendEdit(EditCommand.INSERT_GAP, new SequenceI[]
1205           { seq }, lastres, startres - lastres, av.getAlignment(), true);
1206         }
1207       }
1208       else
1209       {
1210         if (!editSeq)
1211         {
1212           // dragging to the left
1213           if (fixedColumns && fixedRight != -1)
1214           {
1215             for (int j = lastres; j > startres; j--)
1216             {
1217               if (!jalview.util.Comparison.isGap(seq.getCharAt(startres)))
1218               {
1219                 endEditing();
1220                 break;
1221               }
1222               deleteChar(startres, new SequenceI[]
1223               { seq }, fixedRight);
1224             }
1225           }
1226           else
1227           {
1228             // could be a keyboard edit trying to delete none gaps
1229             int max = 0;
1230             for (int m = startres; m < lastres; m++)
1231             {
1232               if (!jalview.util.Comparison.isGap(seq.getCharAt(m)))
1233               {
1234                 break;
1235               }
1236               max++;
1237             }
1238
1239             if (max > 0)
1240             {
1241               editCommand.appendEdit(EditCommand.DELETE_GAP,
1242                       new SequenceI[]
1243                       { seq }, startres, max, av.getAlignment(), true);
1244             }
1245           }
1246         }
1247         else
1248         {// insertGap==false AND editSeq==TRUE;
1249           if (fixedColumns && fixedRight != -1)
1250           {
1251             for (int j = lastres; j < startres; j++)
1252             {
1253               insertChar(j, new SequenceI[]
1254               { seq }, fixedRight);
1255             }
1256           }
1257           else
1258           {
1259             editCommand.appendEdit(EditCommand.INSERT_NUC, new SequenceI[]
1260             { seq }, lastres, startres - lastres, av.getAlignment(), true);
1261           }
1262         }
1263       }
1264     }
1265
1266     lastres = startres;
1267     seqCanvas.repaint();
1268   }
1269
1270   void insertChar(int j, SequenceI[] seq, int fixedColumn)
1271   {
1272     int blankColumn = fixedColumn;
1273     for (int s = 0; s < seq.length; s++)
1274     {
1275       // Find the next gap before the end of the visible region boundary
1276       // If lastCol > j, theres a boundary after the gap insertion
1277
1278       for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)
1279       {
1280         if (jalview.util.Comparison.isGap(seq[s].getCharAt(blankColumn)))
1281         {
1282           // Theres a space, so break and insert the gap
1283           break;
1284         }
1285       }
1286
1287       if (blankColumn <= j)
1288       {
1289         blankColumn = fixedColumn;
1290         endEditing();
1291         return;
1292       }
1293     }
1294
1295     editCommand.appendEdit(EditCommand.DELETE_GAP, seq, blankColumn, 1,
1296             av.getAlignment(), true);
1297
1298     editCommand.appendEdit(EditCommand.INSERT_GAP, seq, j, 1,
1299             av.getAlignment(), true);
1300
1301   }
1302
1303   void deleteChar(int j, SequenceI[] seq, int fixedColumn)
1304   {
1305
1306     editCommand.appendEdit(EditCommand.DELETE_GAP, seq, j, 1,
1307             av.getAlignment(), true);
1308
1309     editCommand.appendEdit(EditCommand.INSERT_GAP, seq, fixedColumn, 1,
1310             av.getAlignment(), true);
1311   }
1312
1313   /**
1314    * DOCUMENT ME!
1315    * 
1316    * @param e
1317    *          DOCUMENT ME!
1318    */
1319   @Override
1320   public void mouseEntered(MouseEvent e)
1321   {
1322     if (oldSeq < 0)
1323     {
1324       oldSeq = 0;
1325     }
1326
1327     if (scrollThread != null)
1328     {
1329       scrollThread.running = false;
1330       scrollThread = null;
1331     }
1332   }
1333
1334   /**
1335    * DOCUMENT ME!
1336    * 
1337    * @param e
1338    *          DOCUMENT ME!
1339    */
1340   @Override
1341   public void mouseExited(MouseEvent e)
1342   {
1343     if (av.getWrapAlignment())
1344     {
1345       return;
1346     }
1347
1348     if (mouseDragging)
1349     {
1350       scrollThread = new ScrollThread();
1351     }
1352   }
1353
1354   @Override
1355   public void mouseClicked(MouseEvent evt)
1356   {
1357     SequenceGroup sg = null;
1358     SequenceI sequence = av.getAlignment().getSequenceAt(findSeq(evt));
1359     if (evt.getClickCount() > 1)
1360     {
1361       sg = av.getSelectionGroup();
1362       if (sg != null && sg.getSize() == 1
1363               && sg.getEndRes() - sg.getStartRes() < 2)
1364       {
1365         av.setSelectionGroup(null);
1366       }
1367
1368       SequenceFeature[] features = findFeaturesAtRes(
1369               sequence.getDatasetSequence(),
1370               sequence.findPosition(findRes(evt)));
1371
1372       if (features != null && features.length > 0)
1373       {
1374         SearchResults highlight = new SearchResults();
1375         highlight.addResult(sequence, features[0].getBegin(),
1376                 features[0].getEnd());
1377         seqCanvas.highlightSearchResults(highlight);
1378       }
1379       if (features != null && features.length > 0)
1380       {
1381         seqCanvas.getFeatureRenderer().amendFeatures(new SequenceI[]
1382         { sequence }, features, false, ap);
1383
1384         seqCanvas.highlightSearchResults(null);
1385       }
1386     }
1387   }
1388
1389   @Override
1390   public void mouseWheelMoved(MouseWheelEvent e)
1391   {
1392     e.consume();
1393     if (e.getWheelRotation() > 0)
1394     {
1395       if (e.isShiftDown())
1396       {
1397         ap.scrollRight(true);
1398
1399       }
1400       else
1401       {
1402         ap.scrollUp(false);
1403       }
1404     }
1405     else
1406     {
1407       if (e.isShiftDown())
1408       {
1409         ap.scrollRight(false);
1410       }
1411       else
1412       {
1413         ap.scrollUp(true);
1414       }
1415     }
1416     // TODO Update tooltip for new position.
1417   }
1418
1419   /**
1420    * DOCUMENT ME!
1421    * 
1422    * @param evt
1423    *          DOCUMENT ME!
1424    */
1425   public void doMousePressedDefineMode(MouseEvent evt)
1426   {
1427     int res = findRes(evt);
1428     int seq = findSeq(evt);
1429     oldSeq = seq;
1430
1431     startWrapBlock = wrappedBlock;
1432
1433     if (av.wrapAlignment && seq > av.getAlignment().getHeight())
1434     {
1435       JOptionPane.showInternalMessageDialog(Desktop.desktop,
1436               MessageManager.getString("label.cannot_edit_annotations_in_wrapped_view"),
1437               MessageManager.getString("label.wrapped_view_no_edit"), JOptionPane.WARNING_MESSAGE);
1438       return;
1439     }
1440
1441     if (seq < 0 || res < 0)
1442     {
1443       return;
1444     }
1445
1446     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
1447
1448     if ((sequence == null) || (res > sequence.getLength()))
1449     {
1450       return;
1451     }
1452
1453     stretchGroup = av.getSelectionGroup();
1454
1455     if (stretchGroup == null)
1456     {
1457       stretchGroup = av.getAlignment().findGroup(sequence);
1458
1459       if ((stretchGroup != null) && (res > stretchGroup.getStartRes())
1460               && (res < stretchGroup.getEndRes()))
1461       {
1462         av.setSelectionGroup(stretchGroup);
1463       }
1464       else
1465       {
1466         stretchGroup = null;
1467       }
1468     }
1469     else if (!stretchGroup.getSequences(null).contains(sequence)
1470             || (stretchGroup.getStartRes() > res)
1471             || (stretchGroup.getEndRes() < res))
1472     {
1473       stretchGroup = null;
1474
1475       SequenceGroup[] allGroups = av.getAlignment().findAllGroups(sequence);
1476
1477       if (allGroups != null)
1478       {
1479         for (int i = 0; i < allGroups.length; i++)
1480         {
1481           if ((allGroups[i].getStartRes() <= res)
1482                   && (allGroups[i].getEndRes() >= res))
1483           {
1484             stretchGroup = allGroups[i];
1485             break;
1486           }
1487         }
1488       }
1489
1490       av.setSelectionGroup(stretchGroup);
1491
1492     }
1493
1494     if (javax.swing.SwingUtilities.isRightMouseButton(evt))
1495     {
1496       SequenceFeature[] allFeatures = findFeaturesAtRes(
1497               sequence.getDatasetSequence(), sequence.findPosition(res));
1498       Vector links = new Vector();
1499       for (int i = 0; i < allFeatures.length; i++)
1500       {
1501         if (allFeatures[i].links != null)
1502         {
1503           for (int j = 0; j < allFeatures[i].links.size(); j++)
1504           {
1505             links.addElement(allFeatures[i].links.elementAt(j));
1506           }
1507         }
1508       }
1509
1510       jalview.gui.PopupMenu pop = new jalview.gui.PopupMenu(ap, null, links);
1511       pop.show(this, evt.getX(), evt.getY());
1512       return;
1513     }
1514
1515     if (av.cursorMode)
1516     {
1517       seqCanvas.cursorX = findRes(evt);
1518       seqCanvas.cursorY = findSeq(evt);
1519       seqCanvas.repaint();
1520       return;
1521     }
1522
1523     if (stretchGroup == null)
1524     {
1525       // Only if left mouse button do we want to change group sizes
1526
1527       // define a new group here
1528       SequenceGroup sg = new SequenceGroup();
1529       sg.setStartRes(res);
1530       sg.setEndRes(res);
1531       sg.addSequence(sequence, false);
1532       av.setSelectionGroup(sg);
1533
1534       stretchGroup = sg;
1535
1536       if (av.getConservationSelected())
1537       {
1538         SliderPanel.setConservationSlider(ap, av.getGlobalColourScheme(),
1539                 "Background");
1540       }
1541
1542       if (av.getAbovePIDThreshold())
1543       {
1544         SliderPanel.setPIDSliderSource(ap, av.getGlobalColourScheme(),
1545                 "Background");
1546       }
1547       if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))
1548       {
1549         // Edit end res position of selected group
1550         changeEndRes = true;
1551       }
1552       else if ((stretchGroup != null)
1553               && (stretchGroup.getStartRes() == res))
1554       {
1555         // Edit end res position of selected group
1556         changeStartRes = true;
1557       }
1558       stretchGroup.getWidth();
1559     }
1560
1561     seqCanvas.repaint();
1562   }
1563
1564   /**
1565    * DOCUMENT ME!
1566    * 
1567    * @param evt
1568    *          DOCUMENT ME!
1569    */
1570   public void doMouseReleasedDefineMode(MouseEvent evt)
1571   {
1572     if (stretchGroup == null)
1573     {
1574       return;
1575     }
1576
1577     stretchGroup.recalcConservation(); // always do this - annotation has own
1578                                        // state
1579     if (stretchGroup.cs != null)
1580     {
1581       stretchGroup.cs.alignmentChanged(stretchGroup,
1582               av.getHiddenRepSequences());
1583
1584       if (stretchGroup.cs.conservationApplied())
1585       {
1586         SliderPanel.setConservationSlider(ap, stretchGroup.cs,
1587                 stretchGroup.getName());
1588       }
1589       else
1590       {
1591         SliderPanel.setPIDSliderSource(ap, stretchGroup.cs,
1592                 stretchGroup.getName());
1593       }
1594     }
1595     PaintRefresher.Refresh(this, av.getSequenceSetId());
1596     ap.paintAlignment(true);
1597
1598     changeEndRes = false;
1599     changeStartRes = false;
1600     stretchGroup = null;
1601     av.sendSelection();
1602   }
1603
1604   /**
1605    * DOCUMENT ME!
1606    * 
1607    * @param evt
1608    *          DOCUMENT ME!
1609    */
1610   public void doMouseDraggedDefineMode(MouseEvent evt)
1611   {
1612     int res = findRes(evt);
1613     int y = findSeq(evt);
1614
1615     if (wrappedBlock != startWrapBlock)
1616     {
1617       return;
1618     }
1619
1620     if (stretchGroup == null)
1621     {
1622       return;
1623     }
1624
1625     if (res >= av.getAlignment().getWidth())
1626     {
1627       res = av.getAlignment().getWidth() - 1;
1628     }
1629
1630     if (stretchGroup.getEndRes() == res)
1631     {
1632       // Edit end res position of selected group
1633       changeEndRes = true;
1634     }
1635     else if (stretchGroup.getStartRes() == res)
1636     {
1637       // Edit start res position of selected group
1638       changeStartRes = true;
1639     }
1640
1641     if (res < av.getStartRes())
1642     {
1643       res = av.getStartRes();
1644     }
1645
1646     if (changeEndRes)
1647     {
1648       if (res > (stretchGroup.getStartRes() - 1))
1649       {
1650         stretchGroup.setEndRes(res);
1651       }
1652     }
1653     else if (changeStartRes)
1654     {
1655       if (res < (stretchGroup.getEndRes() + 1))
1656       {
1657         stretchGroup.setStartRes(res);
1658       }
1659     }
1660
1661     int dragDirection = 0;
1662
1663     if (y > oldSeq)
1664     {
1665       dragDirection = 1;
1666     }
1667     else if (y < oldSeq)
1668     {
1669       dragDirection = -1;
1670     }
1671
1672     while ((y != oldSeq) && (oldSeq > -1)
1673             && (y < av.getAlignment().getHeight()))
1674     {
1675       // This routine ensures we don't skip any sequences, as the
1676       // selection is quite slow.
1677       Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1678
1679       oldSeq += dragDirection;
1680
1681       if (oldSeq < 0)
1682       {
1683         break;
1684       }
1685
1686       Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1687
1688       if (stretchGroup.getSequences(null).contains(nextSeq))
1689       {
1690         stretchGroup.deleteSequence(seq, false);
1691       }
1692       else
1693       {
1694         if (seq != null)
1695         {
1696           stretchGroup.addSequence(seq, false);
1697         }
1698
1699         stretchGroup.addSequence(nextSeq, false);
1700       }
1701     }
1702
1703     if (oldSeq < 0)
1704     {
1705       oldSeq = -1;
1706     }
1707
1708     mouseDragging = true;
1709
1710     if (scrollThread != null)
1711     {
1712       scrollThread.setEvent(evt);
1713     }
1714
1715     seqCanvas.repaint();
1716   }
1717
1718   void scrollCanvas(MouseEvent evt)
1719   {
1720     if (evt == null)
1721     {
1722       if (scrollThread != null)
1723       {
1724         scrollThread.running = false;
1725         scrollThread = null;
1726       }
1727       mouseDragging = false;
1728     }
1729     else
1730     {
1731       if (scrollThread == null)
1732       {
1733         scrollThread = new ScrollThread();
1734       }
1735
1736       mouseDragging = true;
1737       scrollThread.setEvent(evt);
1738     }
1739
1740   }
1741
1742   // this class allows scrolling off the bottom of the visible alignment
1743   class ScrollThread extends Thread
1744   {
1745     MouseEvent evt;
1746
1747     boolean running = false;
1748
1749     public ScrollThread()
1750     {
1751       start();
1752     }
1753
1754     public void setEvent(MouseEvent e)
1755     {
1756       evt = e;
1757     }
1758
1759     public void stopScrolling()
1760     {
1761       running = false;
1762     }
1763
1764     @Override
1765     public void run()
1766     {
1767       running = true;
1768
1769       while (running)
1770       {
1771         if (evt != null)
1772         {
1773           if (mouseDragging && (evt.getY() < 0) && (av.getStartSeq() > 0))
1774           {
1775             running = ap.scrollUp(true);
1776           }
1777
1778           if (mouseDragging && (evt.getY() >= getHeight())
1779                   && (av.getAlignment().getHeight() > av.getEndSeq()))
1780           {
1781             running = ap.scrollUp(false);
1782           }
1783
1784           if (mouseDragging && (evt.getX() < 0))
1785           {
1786             running = ap.scrollRight(false);
1787           }
1788           else if (mouseDragging && (evt.getX() >= getWidth()))
1789           {
1790             running = ap.scrollRight(true);
1791           }
1792         }
1793
1794         try
1795         {
1796           Thread.sleep(20);
1797         } catch (Exception ex)
1798         {
1799         }
1800       }
1801     }
1802   }
1803
1804   /**
1805    * modify current selection according to a received message.
1806    */
1807   @Override
1808   public void selection(SequenceGroup seqsel, ColumnSelection colsel,
1809           SelectionSource source)
1810   {
1811     // TODO: fix this hack - source of messages is align viewport, but SeqPanel
1812     // handles selection messages...
1813     // TODO: extend config options to allow user to control if selections may be
1814     // shared between viewports.
1815     if (av == source
1816             || !av.followSelection
1817             || (av.isSelectionGroupChanged(false) || av
1818                     .isColSelChanged(false))
1819             || (source instanceof AlignViewport && ((AlignViewport) source)
1820                     .getSequenceSetId().equals(av.getSequenceSetId())))
1821     {
1822       return;
1823     }
1824     // do we want to thread this ? (contention with seqsel and colsel locks, I
1825     // suspect)
1826     // rules are: colsel is copied if there is a real intersection between
1827     // sequence selection
1828     boolean repaint = false, copycolsel = true;
1829     // if (!av.isSelectionGroupChanged(false))
1830     {
1831       SequenceGroup sgroup = null;
1832       if (seqsel != null && seqsel.getSize() > 0)
1833       {
1834         if (av.getAlignment() == null)
1835         {
1836           jalview.bin.Cache.log.warn("alignviewport av SeqSetId="
1837                   + av.getSequenceSetId() + " ViewId=" + av.getViewId()
1838                   + " 's alignment is NULL! returning immediatly.");
1839           return;
1840         }
1841         sgroup = seqsel.intersect(av.getAlignment(),
1842                 (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
1843         if ((sgroup == null || sgroup.getSize() == 0)
1844                 || (colsel == null || colsel.size() == 0))
1845         {
1846           // don't copy columns if the region didn't intersect.
1847           copycolsel = false;
1848         }
1849       }
1850       if (sgroup != null && sgroup.getSize() > 0)
1851       {
1852         av.setSelectionGroup(sgroup);
1853       }
1854       else
1855       {
1856         av.setSelectionGroup(null);
1857       }
1858       av.isSelectionGroupChanged(true);
1859       repaint = true;
1860     }
1861     if (copycolsel)
1862     {
1863       // the current selection is unset or from a previous message
1864       // so import the new colsel.
1865       if (colsel == null || colsel.size() == 0)
1866       {
1867         if (av.getColumnSelection() != null)
1868         {
1869           av.getColumnSelection().clear();
1870           repaint = true;
1871         }
1872       }
1873       else
1874       {
1875         // TODO: shift colSel according to the intersecting sequences
1876         if (av.getColumnSelection() == null)
1877         {
1878           av.setColumnSelection(new ColumnSelection(colsel));
1879         }
1880         else
1881         {
1882           av.getColumnSelection().setElementsFrom(colsel);
1883         }
1884       }
1885       av.isColSelChanged(true);
1886       repaint = true;
1887     }
1888     if (copycolsel
1889             && av.hasHiddenColumns()
1890             && (av.getColumnSelection() == null || av.getColumnSelection()
1891                     .getHiddenColumns() == null))
1892     {
1893       System.err.println("Bad things");
1894     }
1895     if (repaint)
1896     {
1897       // probably finessing with multiple redraws here
1898       PaintRefresher.Refresh(this, av.getSequenceSetId());
1899       // ap.paintAlignment(false);
1900     }
1901   }
1902 }