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