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