updated to jalview 2.1 and begun ArchiveClient/VamsasClient/VamsasStore updates.
[jalview.git] / src / jalview / appletgui / SeqPanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer
3  * Copyright (C) 2006 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19
20 package jalview.appletgui;
21
22 import java.awt.*;
23 import java.awt.event.*;
24
25 import jalview.datamodel.*;
26 import jalview.schemes.*;
27
28 import java.util.Vector;
29
30 public class SeqPanel
31     extends Panel implements MouseMotionListener, MouseListener
32 {
33
34   public SeqCanvas seqCanvas;
35   public AlignmentPanel ap;
36
37   protected int lastres;
38   protected int startseq;
39
40   protected AlignViewport av;
41
42   // if character is inserted or deleted, we will need to recalculate the conservation
43   boolean seqEditOccurred = false;
44
45   ScrollThread scrollThread = null;
46   boolean mouseDragging = false;
47   boolean editingSeqs = false;
48   boolean groupEditing = false;
49
50   int oldSeq = -1;
51   boolean changeEndSeq = false;
52   boolean changeStartSeq = false;
53   boolean changeEndRes = false;
54   boolean changeStartRes = false;
55   SequenceGroup stretchGroup = null;
56
57   StringBuffer keyboardNo1;
58   StringBuffer keyboardNo2;
59
60   boolean mouseWheelPressed = false;
61   Point lastMousePress;
62
63   public SeqPanel(AlignViewport avp, AlignmentPanel p)
64   {
65     this.av = avp;
66
67     seqCanvas = new SeqCanvas(avp);
68     setLayout(new BorderLayout());
69     add(seqCanvas);
70
71     ap = p;
72
73     seqCanvas.addMouseMotionListener(this);
74     seqCanvas.addMouseListener(this);
75
76     seqCanvas.repaint();
77   }
78
79   void endEditing()
80   {
81     startseq = -1;
82     lastres = -1;
83     seqEditOccurred = false;
84     editingSeqs = false;
85     groupEditing = false;
86     keyboardNo1 = null;
87     keyboardNo2 = null;
88    }
89
90    void setCursorRow()
91    {
92      seqCanvas.cursorY = getKeyboardNo(keyboardNo1)-1;
93      scrollToVisible();
94    }
95
96    void setCursorColumn()
97    {
98      seqCanvas.cursorX = getKeyboardNo(keyboardNo1)-1;
99      scrollToVisible();
100    }
101
102    void setCursorRowAndColumn()
103    {
104      if(keyboardNo2==null)
105      {
106        keyboardNo2 = new StringBuffer();
107      }
108      else
109      {
110        seqCanvas.cursorX = getKeyboardNo(keyboardNo1) - 1;
111        seqCanvas.cursorY = getKeyboardNo(keyboardNo2) - 1;
112        scrollToVisible();
113      }
114    }
115
116    void setCursorPosition()
117    {
118      SequenceI sequence =
119          (Sequence) av.getAlignment().getSequenceAt(seqCanvas.cursorY);
120
121      seqCanvas.cursorX = sequence.findIndex(
122          getKeyboardNo(keyboardNo1)-1
123          );
124      scrollToVisible();
125    }
126
127    void moveCursor(int dx, int dy)
128    {
129      seqCanvas.cursorX += dx;
130      seqCanvas.cursorY += dy;
131      scrollToVisible();
132    }
133
134    void scrollToVisible()
135    {
136      if (seqCanvas.cursorX < 0)
137        seqCanvas.cursorX = 0;
138      else if (seqCanvas.cursorX > av.alignment.getWidth() - 1)
139        seqCanvas.cursorX = av.alignment.getWidth() - 1;
140
141      if (seqCanvas.cursorY < 0)
142        seqCanvas.cursorY = 0;
143      else if (seqCanvas.cursorY > av.alignment.getHeight() - 1)
144        seqCanvas.cursorY = av.alignment.getHeight() - 1;
145
146
147      endEditing();
148      if (av.wrapAlignment)
149      {
150        ap.scrollToWrappedVisible(seqCanvas.cursorX);
151      }
152      else
153      {
154        while (seqCanvas.cursorY < av.startSeq)
155        {
156          ap.scrollUp(true);
157        }
158        while (seqCanvas.cursorY + 1 > av.endSeq)
159        {
160          ap.scrollUp(false);
161        }
162        while (seqCanvas.cursorX < av.startRes)
163        {
164
165          if (!ap.scrollRight(false))
166            break;
167        }
168        while (seqCanvas.cursorX > av.endRes)
169        {
170          if (!ap.scrollRight(true))
171            break;
172        }
173      }
174      setStatusMessage(av.alignment.getSequenceAt(seqCanvas.cursorY),
175                       seqCanvas.cursorX, seqCanvas.cursorY);
176
177      seqCanvas.repaint();
178    }
179
180    void setSelectionAreaAtCursor(boolean topLeft)
181    {
182      SequenceI sequence =
183          (Sequence) av.getAlignment().getSequenceAt(seqCanvas.cursorY);
184
185      if(av.getSelectionGroup()!=null)
186      {
187        SequenceGroup sg = av.selectionGroup;
188        //Find the top and bottom of this group
189        int min = av.alignment.getHeight(), max = 0;
190        for(int i=0; i<sg.getSize(false); i++)
191        {
192          int index = av.alignment.findIndex( sg.getSequenceAt(i) );
193          if(index > max)
194            max = index;
195          if(index < min)
196            min = index;
197        }
198
199        max ++;
200
201        if(topLeft)
202        {
203          sg.setStartRes(seqCanvas.cursorX);
204          if(sg.getEndRes()<seqCanvas.cursorX)
205            sg.setEndRes(seqCanvas.cursorX);
206
207          min = seqCanvas.cursorY;
208        }
209        else
210        {
211          sg.setEndRes(seqCanvas.cursorX);
212          if(sg.getStartRes()>seqCanvas.cursorX)
213            sg.setStartRes(seqCanvas.cursorX);
214
215          max = seqCanvas.cursorY+1;
216        }
217
218        if(min>max)
219        {
220          // Only the user can do this
221          av.setSelectionGroup(null);
222        }
223        else
224        {
225          // Now add any sequences between min and max
226          sg.getSequences(false).removeAllElements();
227          for (int i = min; i < max; i++)
228          {
229            sg.addSequence(av.alignment.getSequenceAt(i), false);
230          }
231        }
232      }
233
234      if (av.getSelectionGroup() == null)
235      {
236        SequenceGroup sg = new SequenceGroup();
237        sg.setStartRes(seqCanvas.cursorX);
238        sg.setEndRes(seqCanvas.cursorX);
239        sg.addSequence(sequence, false);
240        av.setSelectionGroup(sg);
241      }
242
243
244      ap.repaint();
245    }
246
247    void insertGapAtCursor(boolean group)
248    {
249      ap.alignFrame.addHistoryItem(new HistoryItem("Edit Sequence",
250                                                   av.alignment, HistoryItem.EDIT));
251      groupEditing = group;
252      startseq = seqCanvas.cursorY;
253      lastres = seqCanvas.cursorX;
254      editSequence(true, seqCanvas.cursorX+getKeyboardNo(keyboardNo1));
255      editOccurred();
256    }
257
258    void deleteGapAtCursor(boolean group)
259    {
260      ap.alignFrame.addHistoryItem(new HistoryItem("Edit Sequence",
261                                                   av.alignment, HistoryItem.EDIT));
262      groupEditing = group;
263      startseq = seqCanvas.cursorY;
264      lastres = seqCanvas.cursorX+getKeyboardNo(keyboardNo1);
265      editSequence(false, seqCanvas.cursorX);
266      editOccurred();
267    }
268
269    void numberPressed(char value)
270    {
271      if(keyboardNo1==null)
272        keyboardNo1 = new StringBuffer();
273
274      if(keyboardNo2!=null)
275        keyboardNo2.append(value);
276      else
277        keyboardNo1.append(value);
278    }
279
280    int getKeyboardNo(StringBuffer kb)
281    {
282      if(kb==null)
283        return 1;
284      else
285        return Integer.parseInt(kb.toString());
286    }
287
288    void setStatusMessage(SequenceI sequence, int res, int seq)
289    {
290      StringBuffer text = new StringBuffer("Sequence " + (seq + 1) + " ID: " +
291                                           sequence.getName());
292
293      Object obj = null;
294      if (av.alignment.isNucleotide())
295      {
296        obj = ResidueProperties.nucleotideName.get(sequence.getCharAt(res) +
297                                                   "");
298        if (obj != null)
299          text.append(" Nucleotide: ");
300      }
301      else
302      {
303        obj = ResidueProperties.aa2Triplet.get(sequence.getCharAt(res) + "");
304        if (obj != null)
305          text.append("  Residue: ");
306      }
307
308      if (obj != null)
309      {
310
311        if (obj != "")
312        {
313          text.append(obj + " (" + sequence.findPosition(res) +
314                      ")");
315        }
316      }
317
318      ap.alignFrame.statusBar.setText(text.toString());
319
320     }
321      public void mousePressed(MouseEvent evt)
322      {
323        lastMousePress = evt.getPoint();
324
325        if ( (evt.getModifiers() & InputEvent.BUTTON2_MASK) ==
326          InputEvent.BUTTON2_MASK && !av.MAC)
327        {
328          mouseWheelPressed = true;
329          return;
330        }
331
332        if (evt.isShiftDown() || evt.isControlDown())
333        {
334          if (evt.isControlDown())
335          {
336            groupEditing = true;
337          }
338          editingSeqs = true;
339        }
340        else
341        {
342          doMousePressedDefineMode(evt);
343          return;
344        }
345
346
347        int seq = findSeq(evt);
348        int res = findRes(evt);
349
350        if(seq<0 || res<0)
351          return;
352
353        ap.alignFrame.addHistoryItem(new HistoryItem("Edit Sequence",
354                                                           av.alignment, HistoryItem.EDIT));
355
356          if ((seq < av.getAlignment().getHeight()) &&
357                  (res < av.getAlignment().getSequenceAt(seq).getLength()))
358          {
359              startseq = seq;
360              lastres = res;
361          }
362          else
363          {
364              startseq = -1;
365              lastres = -1;
366          }
367
368         return;
369      }
370
371      public void mouseClicked(MouseEvent evt){}
372
373
374   public void mouseReleased(MouseEvent evt)
375   {
376     mouseDragging = false;
377     mouseWheelPressed = false;
378
379     if (!editingSeqs)
380     {
381        doMouseReleasedDefineMode(evt);
382        return;
383     }
384
385      editOccurred();
386
387      endEditing();
388      ap.repaint();
389   }
390
391   int startWrapBlock=-1;
392   int wrappedBlock=-1;
393   int findRes(MouseEvent evt)
394  {
395    int res = 0;
396    int x = evt.getX();
397
398   if (av.wrapAlignment)
399   {
400
401     int hgap = av.charHeight;
402     if (av.scaleAboveWrapped)
403       hgap += av.charHeight;
404
405     int cHeight = av.getAlignment().getHeight() * av.charHeight
406         + hgap + seqCanvas.getAnnotationHeight();
407
408       int y = evt.getY();
409       y -= hgap;
410       x -= seqCanvas.LABEL_WEST;
411
412
413       int cwidth = seqCanvas.getWrappedCanvasWidth(getSize().width);
414
415       wrappedBlock = y / cHeight;
416       wrappedBlock += av.getStartRes() / cwidth;
417
418       res = wrappedBlock * cwidth + x / av.getCharWidth();
419
420   }
421   else
422   {
423       res = (x / av.getCharWidth()) + av.getStartRes();
424   }
425
426   if(av.hasHiddenColumns)
427           res = av.getColumnSelection().adjustForHiddenColumns(res);
428
429   return res;
430
431  }
432
433  int findSeq(MouseEvent evt)
434  {
435
436    int seq = 0;
437    int y = evt.getY();
438
439    if (av.wrapAlignment)
440    {
441      int hgap = av.charHeight;
442      if (av.scaleAboveWrapped)
443        hgap += av.charHeight;
444
445      int cHeight = av.getAlignment().getHeight() * av.charHeight
446          + hgap + seqCanvas.getAnnotationHeight();
447
448        y -= hgap;
449
450      seq = Math.min( (y % cHeight) / av.getCharHeight(),
451                      av.alignment.getHeight() -1);
452      if(seq<0)
453        seq = 0;
454    }
455    else
456    {
457      seq = Math.min( (y / av.getCharHeight()) + av.getStartSeq(),
458                      av.alignment.getHeight() -1);
459      if(seq<0)
460        seq = 0;
461    }
462
463    return seq;
464  }
465
466
467   public void doMousePressed(MouseEvent evt)
468   {
469     ap.alignFrame.addHistoryItem(new HistoryItem(
470         "Edit Sequence", av.alignment, HistoryItem.EDIT));
471
472     int seq = findSeq(evt);
473     int res = findRes(evt);
474
475     if (seq < av.getAlignment().getHeight() &&
476         res < av.getAlignment().getSequenceAt(seq).getLength())
477     {
478       //char resstr = align.getSequenceAt(seq).getSequence().charAt(res);
479       // Find the residue's position in the sequence (res is the position
480       // in the alignment
481
482       startseq = seq;
483       lastres = res;
484     }
485     else
486     {
487       startseq = -1;
488       lastres = -1;
489     }
490
491     return;
492   }
493
494   public void mouseMoved(MouseEvent evt)
495   {
496     int res = findRes(evt);
497     int seq = findSeq(evt);
498
499     if (seq >= av.getAlignment().getHeight() || seq<0 || res<0)
500     {
501       return;
502     }
503
504     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
505     if (res > sequence.getLength())
506     {
507       return;
508     }
509
510     StringBuffer text = new StringBuffer("Sequence " + (seq + 1) + " ID: " +
511             sequence.getName());
512
513     Object obj = null;
514     if (av.alignment.isNucleotide())
515     {
516       obj = ResidueProperties.nucleotideName.get(sequence.getCharAt(res) +
517           "");
518       if(obj!=null)
519         text.append(" Nucleotide: ");
520     }
521     else
522     {
523       obj = ResidueProperties.aa2Triplet.get(sequence.getCharAt(res) + "");
524       if(obj!=null)
525         text.append("  Residue: ");
526     }
527
528     if (obj != null)
529     {
530       if (obj != "")
531       {
532         text.append(obj + " (" + sequence.findPosition(res) + ")");
533       }
534     }
535
536     if(seqCanvas.pdbCanvas!=null && sequence==seqCanvas.pdbCanvas.sequence)
537     {
538       seqCanvas.pdbCanvas.highlightRes(sequence.findPosition(res));
539     }
540
541     ap.alignFrame.statusBar.setText(text.toString());
542
543
544     // use aa to see if the mouse pointer is on a
545     if (av.showSequenceFeatures
546         && sequence.getSequenceFeatures()!=null
547         && av.featuresDisplayed!=null)
548     {
549       StringBuffer featureText = new StringBuffer();
550       Vector allFeatures = getAllFeaturesAtRes(sequence, sequence.findPosition(res));
551
552       int index = 0;
553       while (index < allFeatures.size())
554       {
555         SequenceFeature sf = (SequenceFeature) allFeatures.elementAt(index);
556
557         featureText.append(sf.getType()+" "+sf.begin+":"+sf.end);
558
559         if (sf.getDescription() != null)
560           featureText.append(" " + sf.getDescription());
561
562         if (sf.getValue("status") != null )
563         {
564           featureText.append(" (" + sf.getValue("status") + ")");
565         }
566         featureText.append("\n");
567
568         index++;
569       }
570
571
572         if (tooltip == null)
573           tooltip = new Tooltip(featureText.toString(), seqCanvas);
574         else
575           tooltip.setTip(featureText.toString());
576
577         tooltip.repaint();
578
579     }
580   }
581
582   Vector getAllFeaturesAtRes(SequenceI seq, int res)
583   {
584     Vector allFeatures = new Vector();
585     int index = 0;
586     if(seq.getSequenceFeatures()!=null)
587     {
588       while (index < seq.getSequenceFeatures().length)
589       {
590         SequenceFeature sf = seq.getSequenceFeatures()[index];
591         if (sf.getBegin() <= res &&
592             sf.getEnd() >= res)
593         {
594           if (av.featuresDisplayed.containsKey(sf.getType()))
595           {
596             allFeatures.addElement(sf);
597           }
598         }
599         index++;
600       }
601     }
602     return allFeatures;
603   }
604
605   Tooltip tooltip;
606
607   public void mouseDragged(MouseEvent evt)
608   {
609     if (mouseWheelPressed)
610     {
611       int oldWidth = av.charWidth;
612
613       //Which is bigger, left-right or up-down?
614       if (Math.abs(evt.getY() - lastMousePress.y)
615           > Math.abs(evt.getX() - lastMousePress.x))
616       {
617         int fontSize = av.font.getSize();
618
619         if (evt.getY() < lastMousePress.y && av.charHeight > 1)
620         {
621           fontSize--;
622         }
623         else if (evt.getY() > lastMousePress.y)
624         {
625           fontSize++;
626         }
627
628
629         if(fontSize<1)
630           fontSize = 1;
631
632         av.setFont(new Font(av.font.getName(), av.font.getStyle(), fontSize));
633         av.charWidth = oldWidth;
634       }
635       else
636       {
637         if (evt.getX() < lastMousePress.x && av.charWidth > 1)
638         {
639           av.charWidth--;
640         }
641         else if (evt.getX() > lastMousePress.x)
642         {
643           av.charWidth++;
644         }
645
646         if(av.charWidth<1)
647         {
648           av.charWidth = 1;
649         }
650       }
651
652       ap.fontChanged();
653
654       FontMetrics fm = getFontMetrics(av.getFont());
655       av.validCharWidth = fm.charWidth('M') <= av.charWidth;
656
657       lastMousePress = evt.getPoint();
658
659       ap.repaint();
660       ap.annotationPanel.image = null;
661       return;
662       }
663
664       if (!editingSeqs)
665       {
666         doMouseDraggedDefineMode(evt);
667         return;
668       }
669
670   int res = findRes(evt);
671
672   if (res < 0)
673   {
674       res = 0;
675   }
676
677   if ((lastres == -1) || (lastres == res))
678   {
679       return;
680   }
681
682   if ( (res < av.getAlignment().getWidth()) && (res < lastres))
683   {
684     // dragLeft, delete gap
685     editSequence(false, res);
686   }
687   else
688     editSequence(true, res);
689
690   mouseDragging = true;
691   if(scrollThread!=null)
692     scrollThread.setEvent(evt);
693
694   }
695
696   synchronized void editSequence(boolean insertGap, int startres)
697   {
698     int fixedLeft = -1;
699     int fixedRight = -1;
700     boolean fixedColumns = false;
701     SequenceGroup sg = av.getSelectionGroup();
702
703
704       if (!groupEditing && av.hasHiddenRows)
705       {
706         if (av.alignment.getSequenceAt(startseq).getHiddenSequences() != null)
707         {
708           groupEditing = true;
709         }
710       }
711
712       //No group, but the sequence may represent a group
713       if (groupEditing
714           && sg == null
715           && av.alignment.getSequenceAt(startseq).getHiddenSequences() == null)
716       {
717         groupEditing = false;
718       }
719
720       SequenceI seq = av.alignment.getSequenceAt(startseq);
721       StringBuffer message = new StringBuffer();
722       if (groupEditing)
723          message.append("Edit group:");
724       else
725          message.append("Edit sequence: "+seq.getName());
726
727      if(insertGap)
728        message.append(" insert ");
729      else
730        message.append(" delete ");
731
732      message.append(Math.abs(startres-lastres)+" gaps.");
733      ap.alignFrame.statusBar.setText(message.toString());
734
735
736       //Are we editing within a selection group?
737       if (groupEditing
738           || (sg != null && sg.getSequences(true).contains(seq)))
739       {
740         fixedColumns = true;
741
742         //sg might be null as the user may only see 1 sequence,
743         //but the sequence represents a group
744         if (sg == null)
745         {
746           sg = new SequenceGroup(null, null, false, false, false, 0,
747                                  av.alignment.getWidth()-1);
748           sg.addSequence(av.alignment.getSequenceAt(startseq), false);
749         }
750
751         fixedLeft = sg.getStartRes();
752         fixedRight = sg.getEndRes();
753
754         if (   (startres < fixedLeft && lastres >= fixedLeft)
755             || (startres >= fixedLeft && lastres < fixedLeft)
756             || (startres > fixedRight && lastres <=fixedRight)
757             || (startres <= fixedRight && lastres > fixedRight))
758         {
759           endEditing();
760           return;
761         }
762
763         if (fixedLeft > startres)
764         {
765           fixedRight = fixedLeft - 1;
766           fixedLeft = 0;
767         }
768         else if (fixedRight < startres)
769         {
770           fixedLeft = fixedRight;
771           fixedRight = -1;
772         }
773       }
774
775
776       if(av.hasHiddenColumns )
777       {
778           fixedColumns = true;
779           int y1 = av.getColumnSelection().getHiddenBoundaryLeft(startres);
780           int y2 = av.getColumnSelection().getHiddenBoundaryRight(startres);
781
782           if ( (insertGap && startres > y1 && lastres < y1)
783               || (!insertGap && startres < y2 && lastres > y2))
784           {
785             endEditing();
786             return;
787           }
788
789           //System.out.print(y1+" "+y2+" "+fixedLeft+" "+fixedRight+"~~");
790           //Selection spans a hidden region
791           if(fixedLeft<y1 && (fixedRight>y2 || fixedRight==-1))
792           {
793             if(startres>=y2)
794             {
795               fixedLeft = y2;
796             }
797             else
798             {
799              fixedRight = y2 - 1;
800            }
801           }
802       }
803
804
805       if (groupEditing)
806       {
807         // drag to right
808         if (insertGap)
809         {
810             //If the user has selected the whole sequence, and is dragging to
811             // the right, we can still extend the alignment and selectionGroup
812             if(   sg.getStartRes() == 0
813                   && sg.getEndRes() == fixedRight
814                   && sg.getEndRes() == av.alignment.getWidth()-1
815                )
816             {
817               sg.setEndRes(av.alignment.getWidth() + startres - lastres);
818               fixedRight = sg.getEndRes();
819             }
820
821           // Is it valid with fixed columns??
822           // Find the next gap before the end
823           // of the visible region boundary
824           boolean blank = false;
825           for (fixedRight = fixedRight;
826                fixedRight > lastres;
827                fixedRight--)
828           {
829             blank = true;
830             for (int s = 0; s < sg.getSize(true); s++)
831             {
832               seq = (SequenceI)sg.getSequences(true).elementAt(s);
833               for (int j = 0; j < startres - lastres; j++)
834               {
835                 if (!jalview.util.Comparison.isGap(
836                     seq.getCharAt(fixedRight - j)))
837                 {
838                   blank = false;
839                   break;
840                 }
841               }
842             }
843             if (blank)
844               break;
845           }
846
847           if (!blank)
848           {
849             if(sg.getSize(false) == av.alignment.getHeight()  )
850             {
851               if((av.hasHiddenColumns
852                   && startres<av.getColumnSelection().getHiddenBoundaryRight(startres)))
853               {
854                 endEditing();
855                 return;
856               }
857
858               int alWidth = av.alignment.getWidth();
859               if(av.hasHiddenRows)
860               {
861                 int hwidth = av.alignment.getHiddenSequences().getWidth();
862                 if(hwidth>alWidth)
863                   alWidth = hwidth;
864               }
865               //We can still insert gaps if the selectionGroup
866               //contains all the sequences
867               sg.setEndRes(sg.getEndRes()+startres-lastres);
868               fixedRight = alWidth+startres-lastres;
869             }
870             else
871             {
872               endEditing();
873               return;
874             }
875           }
876         }
877
878
879         // drag to left
880         else if(!insertGap)
881         {
882           /// Are we able to delete?
883           // ie are all columns blank?
884
885           for (int s = 0; s < sg.getSize(true); s++)
886           {
887             seq = (SequenceI)sg.getSequences(true).elementAt(s);
888
889             for (int j = startres; j < lastres; j++)
890             {
891               if (seq.getSequence().length() <= j)
892               {
893                 continue;
894               }
895
896               if (!jalview.util.Comparison.isGap(
897                   seq.getSequence().charAt(j)))
898               {
899                 // Not a gap, block edit not valid
900                 endEditing();
901                 return;
902               }
903             }
904           }
905         }
906
907
908         for (int i = 0; i < sg.getSize(true); i++)
909         {
910           seq = (SequenceI) sg.getSequences(true).elementAt(i);
911
912           if (insertGap)
913           {
914             // dragging to the right
915             for (int j = lastres; j < startres; j++)
916             {
917               if (fixedColumns && fixedRight != -1)
918               {
919                 insertChar(j, seq, fixedRight);
920               }
921               else
922                 insertChar(j, seq);
923             }
924           }
925           else
926           {
927             // dragging to the left
928             for (int j = lastres; j > startres; j--)
929             {
930               if (fixedColumns && fixedRight != -1)
931               {
932                 deleteChar(startres, seq, fixedRight);
933               }
934               else
935               {
936                 deleteChar(startres, seq);
937               }
938             }
939           }
940         }
941       }
942       else /////Editing a single sequence///////////
943       {
944         if (insertGap)
945         {
946           // dragging to the right
947           for (int j = lastres; j < startres; j++)
948           {
949             if (fixedColumns && fixedRight != -1)
950             {
951                 insertChar(j, seq, fixedRight);
952             }
953             else
954               insertChar(j, seq);
955           }
956         }
957         else
958         {
959           // dragging to the left
960           for (int j = lastres; j > startres; j--)
961           {
962             if (fixedColumns && fixedRight != -1)
963             {
964               deleteChar(startres, seq, fixedRight);
965             }
966             else
967             {
968               deleteChar(startres, seq);
969             }
970           }
971         }
972       }
973
974       lastres = startres;
975       seqCanvas.repaint();
976   }
977
978
979     /**
980      * DOCUMENT ME!
981      *
982      * @param j DOCUMENT ME!
983      * @param seq DOCUMENT ME!
984      */
985     void insertChar(int j, SequenceI seq)
986     {
987         seq.insertCharAt(j, av.getGapCharacter());
988         seqEditOccurred = true;
989     }
990
991     void insertChar(int j, SequenceI seq, int fixedColumn)
992     {
993       //Find the next gap before the end of the visible region boundary
994       //If lastCol > j, theres a boundary after the gap insertion
995       int blankColumn = fixedColumn;
996       for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)
997       {
998         if (jalview.util.Comparison.isGap(seq.getCharAt(blankColumn)))
999         {
1000           //Theres a space, so break and insert the gap
1001           break;
1002         }
1003       }
1004
1005       if (blankColumn <= j)
1006       {
1007         endEditing();
1008         return;
1009       }
1010
1011       if (!jalview.util.Comparison.isGap(seq.getCharAt(blankColumn)))
1012       {
1013         //Just Checking
1014         System.out.println("Tried removing residue (INSERT)"+seq.getCharAt(fixedColumn));
1015         return;
1016       }
1017
1018       seq.deleteCharAt(blankColumn);
1019       seq.insertCharAt(j, av.getGapCharacter());
1020       seqEditOccurred = true;
1021     }
1022
1023     void deleteChar(int j, SequenceI seq, int fixedColumn)
1024     {
1025       if (!jalview.util.Comparison.isGap(seq.getCharAt(j)))
1026       {
1027         ap.alignFrame.statusBar.setText(
1028             "End editing: Tried removing residue " + seq.getCharAt(j));
1029         return;
1030       }
1031
1032       seq.deleteCharAt(j);
1033       seq.insertCharAt(fixedColumn, av.getGapCharacter());
1034       seqEditOccurred = true;
1035     }
1036
1037     /**
1038      * DOCUMENT ME!
1039      *
1040      * @param j DOCUMENT ME!
1041      * @param seq DOCUMENT ME!
1042      */
1043     void deleteChar(int j, SequenceI seq)
1044     {
1045       if (!jalview.util.Comparison.isGap(seq.getCharAt(j)))
1046       {
1047         ap.alignFrame.statusBar.setText(
1048             "End editing: Tried removing residue " + seq.getCharAt(j));
1049         return;
1050       }
1051
1052         seq.deleteCharAt(j);
1053         seqEditOccurred = true;
1054         seqCanvas.repaint();
1055     }
1056
1057     /**
1058      * DOCUMENT ME!
1059      *
1060      * @param i DOCUMENT ME!
1061      */
1062     void editOccurred()
1063     {
1064       if (!seqEditOccurred)
1065       {
1066         ap.alignFrame.historyList.pop();
1067         ap.alignFrame.updateEditMenuBar();
1068       }
1069
1070       endEditing();
1071
1072       av.firePropertyChange("alignment", null,av.getAlignment().getSequences());
1073
1074     }
1075
1076 //////////////////////////////////////////
1077 /////Everything below this is for defining the boundary of the rubberband
1078 //////////////////////////////////////////
1079   public void doMousePressedDefineMode(MouseEvent evt)
1080   {
1081
1082
1083     if (scrollThread != null)
1084     {
1085       scrollThread.running = false;
1086       scrollThread = null;
1087     }
1088
1089     int res = findRes(evt);
1090     int seq = findSeq(evt);
1091     oldSeq = seq;
1092     startWrapBlock=wrappedBlock;
1093
1094     if(seq==-1)
1095       return;
1096
1097     SequenceI sequence = (Sequence) av.getAlignment().getSequenceAt(seq);
1098
1099     if (sequence == null || res > sequence.getLength())
1100     {
1101       return;
1102     }
1103
1104     stretchGroup = av.getSelectionGroup();
1105
1106     if (stretchGroup == null)
1107     {
1108       stretchGroup = av.alignment.findGroup(sequence);
1109       if (stretchGroup != null && res > stretchGroup.getStartRes() &&
1110           res < stretchGroup.getEndRes())
1111       {
1112         av.setSelectionGroup(stretchGroup);
1113       }
1114       else
1115       {
1116         stretchGroup = null;
1117       }
1118     }
1119
1120     else if (!stretchGroup.getSequences(false).contains(sequence)
1121              || stretchGroup.getStartRes() > res
1122              || stretchGroup.getEndRes() < res)
1123     {
1124       stretchGroup = null;
1125
1126       SequenceGroup[] allGroups = av.alignment.findAllGroups(sequence);
1127
1128       if (allGroups != null)
1129       {
1130         for (int i = 0; i < allGroups.length; i++)
1131         {
1132           if (allGroups[i].getStartRes() <= res &&
1133               allGroups[i].getEndRes() >= res)
1134           {
1135             stretchGroup = allGroups[i];
1136             break;
1137           }
1138         }
1139       }
1140       av.setSelectionGroup(stretchGroup);
1141     }
1142
1143
1144     // DETECT RIGHT MOUSE BUTTON IN AWT
1145     if ( (evt.getModifiers() & InputEvent.BUTTON3_MASK) ==
1146              InputEvent.BUTTON3_MASK)
1147     {
1148       Vector allFeatures = getAllFeaturesAtRes(sequence,
1149                                                sequence.findPosition(res));
1150
1151       Vector links = null;
1152       if(allFeatures!=null)
1153       {
1154         for (int i = 0; i < allFeatures.size(); i++)
1155         {
1156           SequenceFeature sf = (SequenceFeature) allFeatures.elementAt(i);
1157           if (sf.links != null)
1158           {
1159             links = new Vector();
1160             for (int j = 0; j < sf.links.size(); j++)
1161             {
1162               links.addElement(sf.links.elementAt(j));
1163             }
1164           }
1165         }
1166       }
1167       APopupMenu popup = new APopupMenu(ap, null, links);
1168       this.add(popup);
1169       popup.show(this, evt.getX(), evt.getY());
1170       ap.repaint();
1171       return;
1172     }
1173
1174     if (av.cursorMode)
1175     {
1176       seqCanvas.cursorX = findRes(evt);
1177       seqCanvas.cursorY = findSeq(evt);
1178       seqCanvas.repaint();
1179       return;
1180     }
1181
1182       //Only if left mouse button do we want to change group sizes
1183
1184       if (stretchGroup == null)
1185       {
1186         // define a new group here
1187         SequenceGroup sg = new SequenceGroup();
1188         sg.setStartRes(res);
1189         sg.setEndRes(res);
1190         sg.addSequence(sequence, false);
1191         av.setSelectionGroup(sg);
1192         stretchGroup = sg;
1193
1194         if (av.getConservationSelected())
1195         {
1196           SliderPanel.setConservationSlider(ap, av.getGlobalColourScheme(),
1197                                             "Background");
1198         }
1199         if (av.getAbovePIDThreshold())
1200         {
1201           SliderPanel.setPIDSliderSource(ap, av.getGlobalColourScheme(),
1202                                          "Background");
1203         }
1204
1205       }
1206   }
1207
1208   public void doMouseReleasedDefineMode(MouseEvent evt)
1209   {
1210     if (stretchGroup == null)
1211     {
1212         return;
1213     }
1214
1215     if(stretchGroup.cs!=null)
1216     {
1217       if (stretchGroup.cs instanceof ClustalxColourScheme)
1218       {
1219         ( (ClustalxColourScheme) stretchGroup.cs).resetClustalX(
1220             stretchGroup.getSequences(true),
1221             stretchGroup.getWidth());
1222       }
1223
1224       if (stretchGroup.cs.conservationApplied())
1225       {
1226         SliderPanel.setConservationSlider(ap, stretchGroup.cs,
1227                                           stretchGroup.getName());
1228         stretchGroup.recalcConservation();
1229       }
1230       else
1231       {
1232         SliderPanel.setPIDSliderSource(ap, stretchGroup.cs,
1233                                        stretchGroup.getName());
1234       }
1235     }
1236     changeEndRes = false;
1237     changeStartRes = false;
1238     stretchGroup = null;
1239     PaintRefresher.Refresh(av.alignment);
1240     ap.repaint();
1241   }
1242
1243   public void doMouseDraggedDefineMode(MouseEvent evt)
1244   {
1245     int res = findRes(evt);
1246     int y = findSeq(evt);
1247
1248     if(wrappedBlock!=startWrapBlock)
1249       return;
1250
1251      if (stretchGroup == null)
1252      {
1253           return;
1254      }
1255
1256      mouseDragging = true;
1257
1258
1259       if(y > av.alignment.getHeight())
1260       {
1261         y = av.alignment.getHeight() -1;
1262       }
1263
1264       if(res>av.alignment.getWidth())
1265         res = av.alignment.getWidth()-1;
1266
1267       if (stretchGroup.getEndRes() == res)
1268       {
1269           // Edit end res position of selected group
1270           changeEndRes = true;
1271       }
1272       else if (stretchGroup.getStartRes() == res)
1273       {
1274           // Edit start res position of selected group
1275           changeStartRes = true;
1276       }
1277
1278       if (res < 0)
1279       {
1280           res = 0;
1281       }
1282
1283       if (changeEndRes)
1284       {
1285           if (res > (stretchGroup.getStartRes() - 1))
1286           {
1287               stretchGroup.setEndRes(res);
1288           }
1289       }
1290       else if (changeStartRes)
1291       {
1292           if (res < (stretchGroup.getEndRes() + 1))
1293           {
1294               stretchGroup.setStartRes(res);
1295           }
1296       }
1297
1298       int dragDirection = 0;
1299
1300       if (y > oldSeq)
1301       {
1302           dragDirection = 1;
1303       }
1304       else if (y < oldSeq)
1305       {
1306           dragDirection = -1;
1307       }
1308
1309
1310       while ((y != oldSeq) && (oldSeq > -1) && (y < av.alignment.getHeight()))
1311       {
1312           // This routine ensures we don't skip any sequences, as the
1313           // selection is quite slow.
1314           Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1315
1316           oldSeq += dragDirection;
1317
1318           if(oldSeq<0)
1319             break;
1320
1321           Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1322
1323           if (stretchGroup.getSequences(false).contains(nextSeq))
1324           {
1325               stretchGroup.deleteSequence(seq, false);
1326           }
1327           else
1328           {
1329               if (seq != null)
1330               {
1331                   stretchGroup.addSequence(seq, false);
1332               }
1333
1334               stretchGroup.addSequence(nextSeq, false);
1335           }
1336       }
1337
1338       if(oldSeq < 0)
1339         oldSeq = -1;
1340
1341
1342       if(res>av.endRes || res<av.startRes
1343           || y<av.startSeq || y>av.endSeq)
1344       {
1345         mouseExited(evt);
1346       }
1347
1348       if (scrollThread != null)
1349       {
1350         scrollThread.setEvent(evt);
1351       }
1352
1353       seqCanvas.repaint();
1354     }
1355
1356     public void mouseEntered(MouseEvent e)
1357     {
1358       if (oldSeq < 0)
1359         oldSeq = 0;
1360
1361       if (scrollThread != null)
1362       {
1363         scrollThread.running = false;
1364         scrollThread = null;
1365       }
1366     }
1367
1368   public void mouseExited(MouseEvent e)
1369   {
1370     if (av.getWrapAlignment())
1371     {
1372       return;
1373     }
1374
1375     if (mouseDragging && scrollThread==null)
1376     {
1377       scrollThread = new ScrollThread();
1378     }
1379   }
1380
1381   void scrollCanvas(MouseEvent evt)
1382   {
1383     if(evt==null)
1384     {
1385       if(scrollThread!=null)
1386       {
1387         scrollThread.running = false;
1388         scrollThread = null;
1389       }
1390       mouseDragging = false;
1391     }
1392     else
1393     {
1394       if (scrollThread == null)
1395         scrollThread = new ScrollThread();
1396
1397       mouseDragging = true;
1398       scrollThread.setEvent(evt);
1399     }
1400
1401     }
1402
1403   // this class allows scrolling off the bottom of the visible alignment
1404   class ScrollThread
1405       extends Thread
1406   {
1407     MouseEvent evt;
1408     boolean running = false;
1409     public ScrollThread()
1410     {
1411       start();
1412     }
1413
1414     public void setEvent(MouseEvent e)
1415     {
1416       evt = e;
1417     }
1418
1419     public void stopScrolling()
1420     {
1421       running = false;
1422     }
1423
1424     public void run()
1425     {
1426       running = true;
1427       while (running)
1428       {
1429
1430         if (evt != null)
1431         {
1432
1433           if (mouseDragging && evt.getY() < 0 && av.getStartSeq() > 0)
1434           {
1435             running = ap.scrollUp(true);
1436           }
1437
1438           if (mouseDragging && evt.getY() >= getSize().height &&
1439               av.alignment.getHeight() > av.getEndSeq())
1440           {
1441             running = ap.scrollUp(false);
1442           }
1443
1444           if (mouseDragging && evt.getX() < 0)
1445           {
1446             running = ap.scrollRight(false);
1447           }
1448
1449           else if (mouseDragging && evt.getX() >= getSize().width)
1450           {
1451             running = ap.scrollRight(true);
1452           }
1453         }
1454
1455         try
1456         {
1457           Thread.sleep(75);
1458         }
1459         catch (Exception ex)
1460         {}
1461       }
1462     }
1463   }
1464
1465 }