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