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