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