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