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