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