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