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