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