JAL-1015 refactor tooltip html generation code to jalview.io.SequenceAnnotationReport
[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       ap.scrollUp(false);
1379     }
1380     else
1381     {
1382       ap.scrollUp(true);
1383     }
1384     // TODO Update tooltip for new position.
1385   }
1386
1387   /**
1388    * DOCUMENT ME!
1389    *
1390    * @param evt
1391    *          DOCUMENT ME!
1392    */
1393   public void doMousePressedDefineMode(MouseEvent evt)
1394   {
1395     int res = findRes(evt);
1396     int seq = findSeq(evt);
1397     oldSeq = seq;
1398
1399     startWrapBlock = wrappedBlock;
1400
1401     if (av.wrapAlignment && seq > av.getAlignment().getHeight())
1402     {
1403       JOptionPane.showInternalMessageDialog(Desktop.desktop,
1404               "Cannot edit annotations in wrapped view.",
1405               "Wrapped view - no edit", JOptionPane.WARNING_MESSAGE);
1406       return;
1407     }
1408
1409     if (seq < 0 || res < 0)
1410     {
1411       return;
1412     }
1413
1414     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
1415
1416     if ((sequence == null) || (res > sequence.getLength()))
1417     {
1418       return;
1419     }
1420
1421     stretchGroup = av.getSelectionGroup();
1422
1423     if (stretchGroup == null)
1424     {
1425       stretchGroup = av.getAlignment().findGroup(sequence);
1426
1427       if ((stretchGroup != null) && (res > stretchGroup.getStartRes())
1428               && (res < stretchGroup.getEndRes()))
1429       {
1430         av.setSelectionGroup(stretchGroup);
1431       }
1432       else
1433       {
1434         stretchGroup = null;
1435       }
1436     }
1437     else if (!stretchGroup.getSequences(null).contains(sequence)
1438             || (stretchGroup.getStartRes() > res)
1439             || (stretchGroup.getEndRes() < res))
1440     {
1441       stretchGroup = null;
1442
1443       SequenceGroup[] allGroups = av.getAlignment().findAllGroups(sequence);
1444
1445       if (allGroups != null)
1446       {
1447         for (int i = 0; i < allGroups.length; i++)
1448         {
1449           if ((allGroups[i].getStartRes() <= res)
1450                   && (allGroups[i].getEndRes() >= res))
1451           {
1452             stretchGroup = allGroups[i];
1453             break;
1454           }
1455         }
1456       }
1457
1458       av.setSelectionGroup(stretchGroup);
1459
1460     }
1461
1462     if (javax.swing.SwingUtilities.isRightMouseButton(evt))
1463     {
1464       SequenceFeature[] allFeatures = findFeaturesAtRes(
1465               sequence.getDatasetSequence(), sequence.findPosition(res));
1466       Vector links = new Vector();
1467       for (int i = 0; i < allFeatures.length; i++)
1468       {
1469         if (allFeatures[i].links != null)
1470         {
1471           for (int j = 0; j < allFeatures[i].links.size(); j++)
1472           {
1473             links.addElement(allFeatures[i].links.elementAt(j));
1474           }
1475         }
1476       }
1477
1478       jalview.gui.PopupMenu pop = new jalview.gui.PopupMenu(ap, null, links);
1479       pop.show(this, evt.getX(), evt.getY());
1480       return;
1481     }
1482
1483     if (av.cursorMode)
1484     {
1485       seqCanvas.cursorX = findRes(evt);
1486       seqCanvas.cursorY = findSeq(evt);
1487       seqCanvas.repaint();
1488       return;
1489     }
1490
1491     if (stretchGroup == null)
1492     {
1493       // Only if left mouse button do we want to change group sizes
1494
1495       // define a new group here
1496       SequenceGroup sg = new SequenceGroup();
1497       sg.setStartRes(res);
1498       sg.setEndRes(res);
1499       sg.addSequence(sequence, false);
1500       av.setSelectionGroup(sg);
1501
1502       stretchGroup = sg;
1503
1504       if (av.getConservationSelected())
1505       {
1506         SliderPanel.setConservationSlider(ap, av.getGlobalColourScheme(),
1507                 "Background");
1508       }
1509
1510       if (av.getAbovePIDThreshold())
1511       {
1512         SliderPanel.setPIDSliderSource(ap, av.getGlobalColourScheme(),
1513                 "Background");
1514       }
1515       if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))
1516       {
1517         // Edit end res position of selected group
1518         changeEndRes = true;
1519       }
1520       else if ((stretchGroup != null)
1521               && (stretchGroup.getStartRes() == res))
1522       {
1523         // Edit end res position of selected group
1524         changeStartRes = true;
1525       }
1526       stretchGroup.getWidth();
1527     }
1528
1529     seqCanvas.repaint();
1530   }
1531
1532   /**
1533    * DOCUMENT ME!
1534    *
1535    * @param evt
1536    *          DOCUMENT ME!
1537    */
1538   public void doMouseReleasedDefineMode(MouseEvent evt)
1539   {
1540     if (stretchGroup == null)
1541     {
1542       return;
1543     }
1544
1545     stretchGroup.recalcConservation(); // always do this - annotation has own
1546                                        // state
1547     if (stretchGroup.cs != null)
1548     {
1549       if (stretchGroup.cs instanceof ClustalxColourScheme)
1550       {
1551         ((ClustalxColourScheme) stretchGroup.cs).alignmentChanged(
1552                 stretchGroup,av.getHiddenRepSequences());
1553       }
1554
1555       if (stretchGroup.cs.conservationApplied())
1556       {
1557         SliderPanel.setConservationSlider(ap, stretchGroup.cs,
1558                 stretchGroup.getName());
1559       }
1560       else
1561       {
1562         SliderPanel.setPIDSliderSource(ap, stretchGroup.cs,
1563                 stretchGroup.getName());
1564       }
1565     }
1566     PaintRefresher.Refresh(this, av.getSequenceSetId());
1567     ap.paintAlignment(true);
1568
1569     changeEndRes = false;
1570     changeStartRes = false;
1571     stretchGroup = null;
1572     av.sendSelection();
1573   }
1574
1575   /**
1576    * DOCUMENT ME!
1577    *
1578    * @param evt
1579    *          DOCUMENT ME!
1580    */
1581   public void doMouseDraggedDefineMode(MouseEvent evt)
1582   {
1583     int res = findRes(evt);
1584     int y = findSeq(evt);
1585
1586     if (wrappedBlock != startWrapBlock)
1587     {
1588       return;
1589     }
1590
1591     if (stretchGroup == null)
1592     {
1593       return;
1594     }
1595
1596     if (res >= av.getAlignment().getWidth())
1597     {
1598       res = av.getAlignment().getWidth() - 1;
1599     }
1600
1601     if (stretchGroup.getEndRes() == res)
1602     {
1603       // Edit end res position of selected group
1604       changeEndRes = true;
1605     }
1606     else if (stretchGroup.getStartRes() == res)
1607     {
1608       // Edit start res position of selected group
1609       changeStartRes = true;
1610     }
1611
1612     if (res < av.getStartRes())
1613     {
1614       res = av.getStartRes();
1615     }
1616
1617     if (changeEndRes)
1618     {
1619       if (res > (stretchGroup.getStartRes() - 1))
1620       {
1621         stretchGroup.setEndRes(res);
1622       }
1623     }
1624     else if (changeStartRes)
1625     {
1626       if (res < (stretchGroup.getEndRes() + 1))
1627       {
1628         stretchGroup.setStartRes(res);
1629       }
1630     }
1631
1632     int dragDirection = 0;
1633
1634     if (y > oldSeq)
1635     {
1636       dragDirection = 1;
1637     }
1638     else if (y < oldSeq)
1639     {
1640       dragDirection = -1;
1641     }
1642
1643     while ((y != oldSeq) && (oldSeq > -1) && (y < av.getAlignment().getHeight()))
1644     {
1645       // This routine ensures we don't skip any sequences, as the
1646       // selection is quite slow.
1647       Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1648
1649       oldSeq += dragDirection;
1650
1651       if (oldSeq < 0)
1652       {
1653         break;
1654       }
1655
1656       Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1657
1658       if (stretchGroup.getSequences(null).contains(nextSeq))
1659       {
1660         stretchGroup.deleteSequence(seq, false);
1661       }
1662       else
1663       {
1664         if (seq != null)
1665         {
1666           stretchGroup.addSequence(seq, false);
1667         }
1668
1669         stretchGroup.addSequence(nextSeq, false);
1670       }
1671     }
1672
1673     if (oldSeq < 0)
1674     {
1675       oldSeq = -1;
1676     }
1677
1678     mouseDragging = true;
1679
1680     if (scrollThread != null)
1681     {
1682       scrollThread.setEvent(evt);
1683     }
1684
1685     seqCanvas.repaint();
1686   }
1687
1688   void scrollCanvas(MouseEvent evt)
1689   {
1690     if (evt == null)
1691     {
1692       if (scrollThread != null)
1693       {
1694         scrollThread.running = false;
1695         scrollThread = null;
1696       }
1697       mouseDragging = false;
1698     }
1699     else
1700     {
1701       if (scrollThread == null)
1702       {
1703         scrollThread = new ScrollThread();
1704       }
1705
1706       mouseDragging = true;
1707       scrollThread.setEvent(evt);
1708     }
1709
1710   }
1711
1712   // this class allows scrolling off the bottom of the visible alignment
1713   class ScrollThread extends Thread
1714   {
1715     MouseEvent evt;
1716
1717     boolean running = false;
1718
1719     public ScrollThread()
1720     {
1721       start();
1722     }
1723
1724     public void setEvent(MouseEvent e)
1725     {
1726       evt = e;
1727     }
1728
1729     public void stopScrolling()
1730     {
1731       running = false;
1732     }
1733
1734     @Override
1735     public void run()
1736     {
1737       running = true;
1738
1739       while (running)
1740       {
1741         if (evt != null)
1742         {
1743           if (mouseDragging && (evt.getY() < 0) && (av.getStartSeq() > 0))
1744           {
1745             running = ap.scrollUp(true);
1746           }
1747
1748           if (mouseDragging && (evt.getY() >= getHeight())
1749                   && (av.getAlignment().getHeight() > av.getEndSeq()))
1750           {
1751             running = ap.scrollUp(false);
1752           }
1753
1754           if (mouseDragging && (evt.getX() < 0))
1755           {
1756             running = ap.scrollRight(false);
1757           }
1758           else if (mouseDragging && (evt.getX() >= getWidth()))
1759           {
1760             running = ap.scrollRight(true);
1761           }
1762         }
1763
1764         try
1765         {
1766           Thread.sleep(20);
1767         } catch (Exception ex)
1768         {
1769         }
1770       }
1771     }
1772   }
1773
1774   /**
1775    * modify current selection according to a received message.
1776    */
1777   @Override
1778   public void selection(SequenceGroup seqsel, ColumnSelection colsel,
1779           SelectionSource source)
1780   {
1781     // TODO: fix this hack - source of messages is align viewport, but SeqPanel
1782     // handles selection messages...
1783     // TODO: extend config options to allow user to control if selections may be
1784     // shared between viewports.
1785     if (av == source
1786             || !av.followSelection
1787             || (av.isSelectionGroupChanged(false) || av.isColSelChanged(false))
1788             || (source instanceof AlignViewport && ((AlignViewport) source)
1789                     .getSequenceSetId().equals(av.getSequenceSetId())))
1790     {
1791       return;
1792     }
1793     // do we want to thread this ? (contention with seqsel and colsel locks, I
1794     // suspect)
1795     // rules are: colsel is copied if there is a real intersection between
1796     // sequence selection
1797     boolean repaint = false, copycolsel = true;
1798     // if (!av.isSelectionGroupChanged(false))
1799     {
1800       SequenceGroup sgroup = null;
1801       if (seqsel != null && seqsel.getSize()>0)
1802       {
1803         if (av.getAlignment() == null)
1804         {
1805           jalview.bin.Cache.log.warn("alignviewport av SeqSetId="
1806                   + av.getSequenceSetId() + " ViewId=" + av.getViewId()
1807                   + " 's alignment is NULL! returning immediatly.");
1808           return;
1809         }
1810         sgroup = seqsel.intersect(av.getAlignment(),
1811                 (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
1812         if ((sgroup == null || sgroup.getSize() == 0)
1813                 || (colsel == null || colsel.size() == 0))
1814         {
1815           // don't copy columns if the region didn't intersect.
1816           copycolsel = false;
1817         }
1818       }
1819       if (sgroup != null && sgroup.getSize() > 0)
1820       {
1821         av.setSelectionGroup(sgroup);
1822       }
1823       else
1824       {
1825         av.setSelectionGroup(null);
1826       }
1827       av.isSelectionGroupChanged(true);
1828       repaint = true;
1829     }
1830     if (copycolsel)
1831     {
1832       // the current selection is unset or from a previous message
1833       // so import the new colsel.
1834       if (colsel == null || colsel.size() == 0)
1835       {
1836         if (av.getColumnSelection() != null)
1837         {
1838           av.getColumnSelection().clear();
1839           repaint=true;
1840         }
1841       }
1842       else
1843       {
1844         // TODO: shift colSel according to the intersecting sequences
1845         if (av.getColumnSelection() == null)
1846         {
1847           av.setColumnSelection(new ColumnSelection(colsel));
1848         }
1849         else
1850         {
1851           av.getColumnSelection().setElementsFrom(colsel);
1852         }
1853       }
1854       av.isColSelChanged(true);
1855       repaint = true;
1856     }
1857     if (copycolsel && av.hasHiddenColumns()
1858             && (av.getColumnSelection() == null || av.getColumnSelection().getHiddenColumns() == null))
1859     {
1860       System.err.println("Bad things");
1861     }
1862     if (repaint)
1863     {
1864       // probably finessing with multiple redraws here
1865       PaintRefresher.Refresh(this, av.getSequenceSetId());
1866       // ap.paintAlignment(false);
1867     }
1868   }
1869 }