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