apply jalview code style
[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,
468               sequence.findPosition(findRes(evt)));
469
470       if (features != null && features.length > 0)
471       {
472         SearchResults highlight = new SearchResults();
473         highlight.addResult(sequence, features[0].getBegin(),
474                 features[0].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(),
584               av.alignment.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,
738             sequence.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.setFont(new Font(av.font.getName(), av.font.getStyle(), fontSize));
840         av.charWidth = oldWidth;
841       }
842       else
843       {
844         if (evt.getX() < lastMousePress.x && av.charWidth > 1)
845         {
846           av.charWidth--;
847         }
848         else if (evt.getX() > lastMousePress.x)
849         {
850           av.charWidth++;
851         }
852
853         if (av.charWidth < 1)
854         {
855           av.charWidth = 1;
856         }
857       }
858
859       ap.fontChanged();
860
861       FontMetrics fm = getFontMetrics(av.getFont());
862       av.validCharWidth = fm.charWidth('M') <= av.charWidth;
863
864       lastMousePress = evt.getPoint();
865
866       ap.paintAlignment(false);
867       ap.annotationPanel.image = null;
868       return;
869     }
870
871     if (!editingSeqs)
872     {
873       doMouseDraggedDefineMode(evt);
874       return;
875     }
876
877     int res = findRes(evt);
878
879     if (res < 0)
880     {
881       res = 0;
882     }
883
884     if ((lastres == -1) || (lastres == res))
885     {
886       return;
887     }
888
889     if ((res < av.getAlignment().getWidth()) && (res < lastres))
890     {
891       // dragLeft, delete gap
892       editSequence(false, res);
893     }
894     else
895     {
896       editSequence(true, res);
897     }
898
899     mouseDragging = true;
900     if (scrollThread != null)
901     {
902       scrollThread.setEvent(evt);
903     }
904
905   }
906
907   synchronized void editSequence(boolean insertGap, int startres)
908   {
909     int fixedLeft = -1;
910     int fixedRight = -1;
911     boolean fixedColumns = false;
912     SequenceGroup sg = av.getSelectionGroup();
913
914     SequenceI seq = av.alignment.getSequenceAt(startseq);
915
916     if (!groupEditing && av.hasHiddenRows)
917     {
918       if (av.hiddenRepSequences != null
919               && av.hiddenRepSequences.containsKey(seq))
920       {
921         sg = (SequenceGroup) av.hiddenRepSequences.get(seq);
922         groupEditing = true;
923       }
924     }
925
926     StringBuffer message = new StringBuffer();
927     if (groupEditing)
928     {
929       message.append("Edit group:");
930       if (editCommand == null)
931       {
932         editCommand = new EditCommand("Edit Group");
933       }
934     }
935     else
936     {
937       message.append("Edit sequence: " + seq.getName());
938       String label = seq.getName();
939       if (label.length() > 10)
940       {
941         label = label.substring(0, 10);
942       }
943       if (editCommand == null)
944       {
945         editCommand = new EditCommand("Edit " + label);
946       }
947     }
948
949     if (insertGap)
950     {
951       message.append(" insert ");
952     }
953     else
954     {
955       message.append(" delete ");
956     }
957
958     message.append(Math.abs(startres - lastres) + " gaps.");
959     ap.alignFrame.statusBar.setText(message.toString());
960
961     // Are we editing within a selection group?
962     if (groupEditing
963             || (sg != null && sg.getSequences(av.hiddenRepSequences)
964                     .contains(seq)))
965     {
966       fixedColumns = true;
967
968       // sg might be null as the user may only see 1 sequence,
969       // but the sequence represents a group
970       if (sg == null)
971       {
972         if (av.hiddenRepSequences == null
973                 || !av.hiddenRepSequences.containsKey(seq))
974         {
975           endEditing();
976           return;
977         }
978
979         sg = (SequenceGroup) av.hiddenRepSequences.get(seq);
980       }
981
982       fixedLeft = sg.getStartRes();
983       fixedRight = sg.getEndRes();
984
985       if ((startres < fixedLeft && lastres >= fixedLeft)
986               || (startres >= fixedLeft && lastres < fixedLeft)
987               || (startres > fixedRight && lastres <= fixedRight)
988               || (startres <= fixedRight && lastres > fixedRight))
989       {
990         endEditing();
991         return;
992       }
993
994       if (fixedLeft > startres)
995       {
996         fixedRight = fixedLeft - 1;
997         fixedLeft = 0;
998       }
999       else if (fixedRight < startres)
1000       {
1001         fixedLeft = fixedRight;
1002         fixedRight = -1;
1003       }
1004     }
1005
1006     if (av.hasHiddenColumns)
1007     {
1008       fixedColumns = true;
1009       int y1 = av.getColumnSelection().getHiddenBoundaryLeft(startres);
1010       int y2 = av.getColumnSelection().getHiddenBoundaryRight(startres);
1011
1012       if ((insertGap && startres > y1 && lastres < y1)
1013               || (!insertGap && startres < y2 && lastres > y2))
1014       {
1015         endEditing();
1016         return;
1017       }
1018
1019       // System.out.print(y1+" "+y2+" "+fixedLeft+" "+fixedRight+"~~");
1020       // Selection spans a hidden region
1021       if (fixedLeft < y1 && (fixedRight > y2 || fixedRight == -1))
1022       {
1023         if (startres >= y2)
1024         {
1025           fixedLeft = y2;
1026         }
1027         else
1028         {
1029           fixedRight = y2 - 1;
1030         }
1031       }
1032     }
1033
1034     if (groupEditing)
1035     {
1036       Vector vseqs = sg.getSequences(av.hiddenRepSequences);
1037       int g, groupSize = vseqs.size();
1038       SequenceI[] groupSeqs = new SequenceI[groupSize];
1039       for (g = 0; g < groupSeqs.length; g++)
1040       {
1041         groupSeqs[g] = (SequenceI) vseqs.elementAt(g);
1042       }
1043
1044       // drag to right
1045       if (insertGap)
1046       {
1047         // If the user has selected the whole sequence, and is dragging to
1048         // the right, we can still extend the alignment and selectionGroup
1049         if (sg.getStartRes() == 0 && sg.getEndRes() == fixedRight
1050                 && sg.getEndRes() == av.alignment.getWidth() - 1)
1051         {
1052           sg.setEndRes(av.alignment.getWidth() + startres - lastres);
1053           fixedRight = sg.getEndRes();
1054         }
1055
1056         // Is it valid with fixed columns??
1057         // Find the next gap before the end
1058         // of the visible region boundary
1059         boolean blank = false;
1060         for (fixedRight = fixedRight; fixedRight > lastres; fixedRight--)
1061         {
1062           blank = true;
1063
1064           for (g = 0; g < groupSize; g++)
1065           {
1066             for (int j = 0; j < startres - lastres; j++)
1067             {
1068               if (!jalview.util.Comparison.isGap(groupSeqs[g]
1069                       .getCharAt(fixedRight - j)))
1070               {
1071                 blank = false;
1072                 break;
1073               }
1074             }
1075           }
1076           if (blank)
1077           {
1078             break;
1079           }
1080         }
1081
1082         if (!blank)
1083         {
1084           if (sg.getSize() == av.alignment.getHeight())
1085           {
1086             if ((av.hasHiddenColumns && startres < av.getColumnSelection()
1087                     .getHiddenBoundaryRight(startres)))
1088             {
1089               endEditing();
1090               return;
1091             }
1092
1093             int alWidth = av.alignment.getWidth();
1094             if (av.hasHiddenRows)
1095             {
1096               int hwidth = av.alignment.getHiddenSequences().getWidth();
1097               if (hwidth > alWidth)
1098               {
1099                 alWidth = hwidth;
1100               }
1101             }
1102             // We can still insert gaps if the selectionGroup
1103             // contains all the sequences
1104             sg.setEndRes(sg.getEndRes() + startres - lastres);
1105             fixedRight = alWidth + startres - lastres;
1106           }
1107           else
1108           {
1109             endEditing();
1110             return;
1111           }
1112         }
1113       }
1114
1115       // drag to left
1116       else if (!insertGap)
1117       {
1118         // / Are we able to delete?
1119         // ie are all columns blank?
1120
1121         for (g = 0; g < groupSize; g++)
1122         {
1123           for (int j = startres; j < lastres; j++)
1124           {
1125             if (groupSeqs[g].getLength() <= j)
1126             {
1127               continue;
1128             }
1129
1130             if (!jalview.util.Comparison.isGap(groupSeqs[g].getCharAt(j)))
1131             {
1132               // Not a gap, block edit not valid
1133               endEditing();
1134               return;
1135             }
1136           }
1137         }
1138       }
1139
1140       if (insertGap)
1141       {
1142         // dragging to the right
1143         if (fixedColumns && fixedRight != -1)
1144         {
1145           for (int j = lastres; j < startres; j++)
1146           {
1147             insertChar(j, groupSeqs, fixedRight);
1148           }
1149         }
1150         else
1151         {
1152           editCommand.appendEdit(EditCommand.INSERT_GAP, groupSeqs,
1153                   startres, startres - lastres, av.alignment, true);
1154         }
1155       }
1156       else
1157       {
1158         // dragging to the left
1159         if (fixedColumns && fixedRight != -1)
1160         {
1161           for (int j = lastres; j > startres; j--)
1162           {
1163             deleteChar(startres, groupSeqs, fixedRight);
1164           }
1165         }
1166         else
1167         {
1168           editCommand.appendEdit(EditCommand.DELETE_GAP, groupSeqs,
1169                   startres, lastres - startres, av.alignment, true);
1170         }
1171
1172       }
1173     }
1174     else
1175     // ///Editing a single sequence///////////
1176     {
1177       if (insertGap)
1178       {
1179         // dragging to the right
1180         if (fixedColumns && fixedRight != -1)
1181         {
1182           for (int j = lastres; j < startres; j++)
1183           {
1184             insertChar(j, new SequenceI[]
1185             { seq }, fixedRight);
1186           }
1187         }
1188         else
1189         {
1190           editCommand.appendEdit(EditCommand.INSERT_GAP, new SequenceI[]
1191           { seq }, lastres, startres - lastres, av.alignment, true);
1192         }
1193       }
1194       else
1195       {
1196         // dragging to the left
1197         if (fixedColumns && fixedRight != -1)
1198         {
1199           for (int j = lastres; j > startres; j--)
1200           {
1201             if (!jalview.util.Comparison.isGap(seq.getCharAt(startres)))
1202             {
1203               endEditing();
1204               break;
1205             }
1206             deleteChar(startres, new SequenceI[]
1207             { seq }, fixedRight);
1208           }
1209         }
1210         else
1211         {
1212           // could be a keyboard edit trying to delete none gaps
1213           int max = 0;
1214           for (int m = startres; m < lastres; m++)
1215           {
1216             if (!jalview.util.Comparison.isGap(seq.getCharAt(m)))
1217             {
1218               break;
1219             }
1220             max++;
1221           }
1222
1223           if (max > 0)
1224           {
1225             editCommand.appendEdit(EditCommand.DELETE_GAP, new SequenceI[]
1226             { seq }, startres, max, av.alignment, true);
1227           }
1228         }
1229       }
1230     }
1231
1232     lastres = startres;
1233     seqCanvas.repaint();
1234   }
1235
1236   void insertChar(int j, SequenceI[] seq, int fixedColumn)
1237   {
1238     int blankColumn = fixedColumn;
1239     for (int s = 0; s < seq.length; s++)
1240     {
1241       // Find the next gap before the end of the visible region boundary
1242       // If lastCol > j, theres a boundary after the gap insertion
1243
1244       for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)
1245       {
1246         if (jalview.util.Comparison.isGap(seq[s].getCharAt(blankColumn)))
1247         {
1248           // Theres a space, so break and insert the gap
1249           break;
1250         }
1251       }
1252
1253       if (blankColumn <= j)
1254       {
1255         blankColumn = fixedColumn;
1256         endEditing();
1257         return;
1258       }
1259     }
1260
1261     editCommand.appendEdit(EditCommand.DELETE_GAP, seq, blankColumn, 1,
1262             av.alignment, true);
1263
1264     editCommand.appendEdit(EditCommand.INSERT_GAP, seq, j, 1, av.alignment,
1265             true);
1266
1267   }
1268
1269   void deleteChar(int j, SequenceI[] seq, int fixedColumn)
1270   {
1271
1272     editCommand.appendEdit(EditCommand.DELETE_GAP, seq, j, 1, av.alignment,
1273             true);
1274
1275     editCommand.appendEdit(EditCommand.INSERT_GAP, seq, fixedColumn, 1,
1276             av.alignment, true);
1277   }
1278
1279   // ////////////////////////////////////////
1280   // ///Everything below this is for defining the boundary of the rubberband
1281   // ////////////////////////////////////////
1282   public void doMousePressedDefineMode(MouseEvent evt)
1283   {
1284     if (scrollThread != null)
1285     {
1286       scrollThread.running = false;
1287       scrollThread = null;
1288     }
1289
1290     int res = findRes(evt);
1291     int seq = findSeq(evt);
1292     oldSeq = seq;
1293     startWrapBlock = wrappedBlock;
1294
1295     if (seq == -1)
1296     {
1297       return;
1298     }
1299
1300     SequenceI sequence = (Sequence) av.getAlignment().getSequenceAt(seq);
1301
1302     if (sequence == null || res > sequence.getLength())
1303     {
1304       return;
1305     }
1306
1307     stretchGroup = av.getSelectionGroup();
1308
1309     if (stretchGroup == null)
1310     {
1311       stretchGroup = av.alignment.findGroup(sequence);
1312       if (stretchGroup != null && res > stretchGroup.getStartRes()
1313               && res < stretchGroup.getEndRes())
1314       {
1315         av.setSelectionGroup(stretchGroup);
1316       }
1317       else
1318       {
1319         stretchGroup = null;
1320       }
1321     }
1322
1323     else if (!stretchGroup.getSequences(null).contains(sequence)
1324             || stretchGroup.getStartRes() > res
1325             || stretchGroup.getEndRes() < res)
1326     {
1327       stretchGroup = null;
1328
1329       SequenceGroup[] allGroups = av.alignment.findAllGroups(sequence);
1330
1331       if (allGroups != null)
1332       {
1333         for (int i = 0; i < allGroups.length; i++)
1334         {
1335           if (allGroups[i].getStartRes() <= res
1336                   && allGroups[i].getEndRes() >= res)
1337           {
1338             stretchGroup = allGroups[i];
1339             break;
1340           }
1341         }
1342       }
1343       av.setSelectionGroup(stretchGroup);
1344     }
1345
1346     // DETECT RIGHT MOUSE BUTTON IN AWT
1347     if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
1348     {
1349       SequenceFeature[] allFeatures = findFeaturesAtRes(sequence,
1350               sequence.findPosition(res));
1351
1352       Vector links = null;
1353       if (allFeatures != null)
1354       {
1355         for (int i = 0; i < allFeatures.length; i++)
1356         {
1357           if (allFeatures[i].links != null)
1358           {
1359             if (links == null)
1360             {
1361               links = new Vector();
1362             }
1363             for (int j = 0; j < allFeatures[i].links.size(); j++)
1364             {
1365               links.addElement(allFeatures[i].links.elementAt(j));
1366             }
1367           }
1368         }
1369       }
1370       APopupMenu popup = new APopupMenu(ap, null, links);
1371       this.add(popup);
1372       popup.show(this, evt.getX(), evt.getY());
1373       return;
1374     }
1375
1376     if (av.cursorMode)
1377     {
1378       seqCanvas.cursorX = findRes(evt);
1379       seqCanvas.cursorY = findSeq(evt);
1380       seqCanvas.repaint();
1381       return;
1382     }
1383
1384     // Only if left mouse button do we want to change group sizes
1385
1386     if (stretchGroup == null)
1387     {
1388       // define a new group here
1389       SequenceGroup sg = new SequenceGroup();
1390       sg.setStartRes(res);
1391       sg.setEndRes(res);
1392       sg.addSequence(sequence, false);
1393       av.setSelectionGroup(sg);
1394       stretchGroup = sg;
1395
1396       if (av.getConservationSelected())
1397       {
1398         SliderPanel.setConservationSlider(ap, av.getGlobalColourScheme(),
1399                 "Background");
1400       }
1401       if (av.getAbovePIDThreshold())
1402       {
1403         SliderPanel.setPIDSliderSource(ap, av.getGlobalColourScheme(),
1404                 "Background");
1405       }
1406
1407     }
1408   }
1409
1410   public void doMouseReleasedDefineMode(MouseEvent evt)
1411   {
1412     if (stretchGroup == null)
1413     {
1414       return;
1415     }
1416
1417     if (stretchGroup.cs != null)
1418     {
1419       if (stretchGroup.cs instanceof ClustalxColourScheme)
1420       {
1421         ((ClustalxColourScheme) stretchGroup.cs).resetClustalX(
1422                 stretchGroup.getSequences(av.hiddenRepSequences),
1423                 stretchGroup.getWidth());
1424       }
1425
1426       if (stretchGroup.cs instanceof Blosum62ColourScheme
1427               || stretchGroup.cs instanceof PIDColourScheme
1428               || stretchGroup.cs.conservationApplied()
1429               || stretchGroup.cs.getThreshold() > 0)
1430       {
1431         stretchGroup.recalcConservation();
1432       }
1433
1434       if (stretchGroup.cs.conservationApplied())
1435       {
1436         SliderPanel.setConservationSlider(ap, stretchGroup.cs,
1437                 stretchGroup.getName());
1438         stretchGroup.recalcConservation();
1439       }
1440       else
1441       {
1442         SliderPanel.setPIDSliderSource(ap, stretchGroup.cs,
1443                 stretchGroup.getName());
1444       }
1445     }
1446     changeEndRes = false;
1447     changeStartRes = false;
1448     stretchGroup = null;
1449     PaintRefresher.Refresh(ap, av.getSequenceSetId());
1450     ap.paintAlignment(true);
1451   }
1452
1453   public void doMouseDraggedDefineMode(MouseEvent evt)
1454   {
1455     int res = findRes(evt);
1456     int y = findSeq(evt);
1457
1458     if (wrappedBlock != startWrapBlock)
1459     {
1460       return;
1461     }
1462
1463     if (stretchGroup == null)
1464     {
1465       return;
1466     }
1467
1468     mouseDragging = true;
1469
1470     if (y > av.alignment.getHeight())
1471     {
1472       y = av.alignment.getHeight() - 1;
1473     }
1474
1475     if (res >= av.alignment.getWidth())
1476     {
1477       res = av.alignment.getWidth() - 1;
1478     }
1479
1480     if (stretchGroup.getEndRes() == res)
1481     {
1482       // Edit end res position of selected group
1483       changeEndRes = true;
1484     }
1485     else if (stretchGroup.getStartRes() == res)
1486     {
1487       // Edit start res position of selected group
1488       changeStartRes = true;
1489     }
1490
1491     if (res < 0)
1492     {
1493       res = 0;
1494     }
1495
1496     if (changeEndRes)
1497     {
1498       if (res > (stretchGroup.getStartRes() - 1))
1499       {
1500         stretchGroup.setEndRes(res);
1501       }
1502     }
1503     else if (changeStartRes)
1504     {
1505       if (res < (stretchGroup.getEndRes() + 1))
1506       {
1507         stretchGroup.setStartRes(res);
1508       }
1509     }
1510
1511     int dragDirection = 0;
1512
1513     if (y > oldSeq)
1514     {
1515       dragDirection = 1;
1516     }
1517     else if (y < oldSeq)
1518     {
1519       dragDirection = -1;
1520     }
1521
1522     while ((y != oldSeq) && (oldSeq > -1) && (y < av.alignment.getHeight()))
1523     {
1524       // This routine ensures we don't skip any sequences, as the
1525       // selection is quite slow.
1526       Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1527
1528       oldSeq += dragDirection;
1529
1530       if (oldSeq < 0)
1531       {
1532         break;
1533       }
1534
1535       Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1536
1537       if (stretchGroup.getSequences(null).contains(nextSeq))
1538       {
1539         stretchGroup.deleteSequence(seq, false);
1540       }
1541       else
1542       {
1543         if (seq != null)
1544         {
1545           stretchGroup.addSequence(seq, false);
1546         }
1547
1548         stretchGroup.addSequence(nextSeq, false);
1549       }
1550     }
1551
1552     if (oldSeq < 0)
1553     {
1554       oldSeq = -1;
1555     }
1556
1557     if (res > av.endRes || res < av.startRes || y < av.startSeq
1558             || y > av.endSeq)
1559     {
1560       mouseExited(evt);
1561     }
1562
1563     if (scrollThread != null)
1564     {
1565       scrollThread.setEvent(evt);
1566     }
1567
1568     seqCanvas.repaint();
1569   }
1570
1571   public void mouseEntered(MouseEvent e)
1572   {
1573     if (oldSeq < 0)
1574     {
1575       oldSeq = 0;
1576     }
1577
1578     if (scrollThread != null)
1579     {
1580       scrollThread.running = false;
1581       scrollThread = null;
1582     }
1583   }
1584
1585   public void mouseExited(MouseEvent e)
1586   {
1587     if (av.getWrapAlignment())
1588     {
1589       return;
1590     }
1591
1592     if (mouseDragging && scrollThread == null)
1593     {
1594       scrollThread = new ScrollThread();
1595     }
1596   }
1597
1598   void scrollCanvas(MouseEvent evt)
1599   {
1600     if (evt == null)
1601     {
1602       if (scrollThread != null)
1603       {
1604         scrollThread.running = false;
1605         scrollThread = null;
1606       }
1607       mouseDragging = false;
1608     }
1609     else
1610     {
1611       if (scrollThread == null)
1612       {
1613         scrollThread = new ScrollThread();
1614       }
1615
1616       mouseDragging = true;
1617       scrollThread.setEvent(evt);
1618     }
1619
1620   }
1621
1622   // this class allows scrolling off the bottom of the visible alignment
1623   class ScrollThread extends Thread
1624   {
1625     MouseEvent evt;
1626
1627     boolean running = false;
1628
1629     public ScrollThread()
1630     {
1631       start();
1632     }
1633
1634     public void setEvent(MouseEvent e)
1635     {
1636       evt = e;
1637     }
1638
1639     public void stopScrolling()
1640     {
1641       running = false;
1642     }
1643
1644     public void run()
1645     {
1646       running = true;
1647       while (running)
1648       {
1649
1650         if (evt != null)
1651         {
1652
1653           if (mouseDragging && evt.getY() < 0 && av.getStartSeq() > 0)
1654           {
1655             running = ap.scrollUp(true);
1656           }
1657
1658           if (mouseDragging && evt.getY() >= getSize().height
1659                   && av.alignment.getHeight() > av.getEndSeq())
1660           {
1661             running = ap.scrollUp(false);
1662           }
1663
1664           if (mouseDragging && evt.getX() < 0)
1665           {
1666             running = ap.scrollRight(false);
1667           }
1668
1669           else if (mouseDragging && evt.getX() >= getSize().width)
1670           {
1671             running = ap.scrollRight(true);
1672           }
1673         }
1674
1675         try
1676         {
1677           Thread.sleep(75);
1678         } catch (Exception ex)
1679         {
1680         }
1681       }
1682     }
1683   }
1684
1685 }