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