ToolTip updated
[jalview.git] / src / jalview / gui / AnnotationPanel.java
1 /*\r
2  * Jalview - A Sequence Alignment Editor and Viewer\r
3  * Copyright (C) 2005 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 java.awt.image.*;\r
26 import javax.swing.*;\r
27 \r
28 import jalview.datamodel.*;\r
29 \r
30 public class AnnotationPanel\r
31     extends JPanel implements MouseListener,\r
32     MouseMotionListener, ActionListener, AdjustmentListener\r
33 {\r
34   static String HELIX = "Helix";\r
35   static String SHEET = "Sheet";\r
36   static String LABEL = "Label";\r
37   static String REMOVE = "Remove Annotation";\r
38   static String COLOUR = "Colour";\r
39   static Color HELIX_COLOUR = Color.red.darker();\r
40   static Color SHEET_COLOUR = Color.green.darker().darker();\r
41 \r
42   public static int GRAPH_HEIGHT = 40;\r
43   AlignViewport av;\r
44   AlignmentPanel ap;\r
45   int activeRow = -1;\r
46   ArrayList activeRes;\r
47   BufferedImage image;\r
48   Graphics2D gg;\r
49   FontMetrics fm;\r
50   int imgWidth = 0;\r
51   boolean fastPaint = false;\r
52 \r
53   public AnnotationPanel(AlignmentPanel ap)\r
54   {\r
55     ToolTipManager.sharedInstance().registerComponent(this);\r
56     ToolTipManager.sharedInstance().setInitialDelay(0);\r
57     ToolTipManager.sharedInstance().setDismissDelay(10000);\r
58     this.ap = ap;\r
59     av = ap.av;\r
60     this.setLayout(null);\r
61     addMouseListener(this);\r
62     addMouseMotionListener(this);\r
63     ap.annotationScroller.getVerticalScrollBar().addAdjustmentListener(this);\r
64   }\r
65 \r
66   public void adjustmentValueChanged(AdjustmentEvent evt)\r
67   {\r
68     ap.alabels.setScrollOffset( -evt.getValue());\r
69   }\r
70 \r
71   public void adjustPanelHeight()\r
72   {\r
73     // setHeight of panels\r
74     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();\r
75     int height = 0;\r
76 \r
77     if (aa != null)\r
78     {\r
79       for (int i = 0; i < aa.length; i++)\r
80       {\r
81         if (!aa[i].visible)\r
82         {\r
83           continue;\r
84         }\r
85 \r
86         aa[i].height = 0;\r
87 \r
88         if (aa[i].hasText)\r
89         {\r
90           aa[i].height += av.charHeight;\r
91         }\r
92 \r
93         if (aa[i].hasIcons)\r
94         {\r
95           aa[i].height += 16;\r
96         }\r
97 \r
98         if (aa[i].isGraph)\r
99         {\r
100           aa[i].height += GRAPH_HEIGHT;\r
101         }\r
102 \r
103         if (aa[i].height == 0)\r
104         {\r
105           aa[i].height = 20;\r
106         }\r
107 \r
108         height += aa[i].height;\r
109       }\r
110     }\r
111     else\r
112     {\r
113       height = 20;\r
114     }\r
115 \r
116     this.setPreferredSize(new Dimension(1, height));\r
117   }\r
118 \r
119   public void addEditableColumn(int i)\r
120   {\r
121     if (activeRow == -1)\r
122     {\r
123       AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();\r
124 \r
125       for (int j = 0; j < aa.length; j++)\r
126       {\r
127         if (aa[j].editable)\r
128         {\r
129           activeRow = j;\r
130 \r
131           break;\r
132         }\r
133       }\r
134     }\r
135 \r
136     if (activeRes == null)\r
137     {\r
138       activeRes = new ArrayList();\r
139       activeRes.add(String.valueOf(i));\r
140 \r
141       return;\r
142     }\r
143 \r
144     activeRes.add(String.valueOf(i));\r
145   }\r
146 \r
147   public void actionPerformed(ActionEvent evt)\r
148   {\r
149     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();\r
150     Annotation[] anot = aa[activeRow].annotations;\r
151 \r
152     if (evt.getActionCommand().equals(REMOVE))\r
153     {\r
154       for (int i = 0; i < activeRes.size(); i++)\r
155       {\r
156         anot[Integer.parseInt(activeRes.get(i).toString())] = null;\r
157         anot[Integer.parseInt(activeRes.get(i).toString())] = null;\r
158       }\r
159     }\r
160     else if (evt.getActionCommand().equals(LABEL))\r
161     {\r
162       String label = JOptionPane.showInputDialog(this, "Enter Label ",\r
163                                                  "Enter label",\r
164                                                  JOptionPane.QUESTION_MESSAGE);\r
165 \r
166       if (label == null)\r
167       {\r
168         label = "";\r
169       }\r
170 \r
171       if ( (label.length() > 0) && !aa[activeRow].hasText)\r
172       {\r
173         aa[activeRow].hasText = true;\r
174       }\r
175 \r
176       for (int i = 0; i < activeRes.size(); i++)\r
177       {\r
178         int index = Integer.parseInt(activeRes.get(i).toString());\r
179 \r
180         if (anot[index] == null)\r
181         {\r
182           anot[index] = new Annotation(label, "", ' ', 0);\r
183         }\r
184 \r
185         anot[index].displayCharacter = label;\r
186       }\r
187     }\r
188     else if (evt.getActionCommand().equals(COLOUR))\r
189     {\r
190       Color col = JColorChooser.showDialog(this,\r
191                                            "Choose foreground colour",\r
192                                            Color.black);\r
193 \r
194       for (int i = 0; i < activeRes.size(); i++)\r
195       {\r
196         int index = Integer.parseInt(activeRes.get(i).toString());\r
197 \r
198         if (anot[index] == null)\r
199         {\r
200           anot[index] = new Annotation("", "", ' ', 0);\r
201         }\r
202 \r
203         anot[index].colour = col;\r
204       }\r
205     }\r
206     else // HELIX OR SHEET\r
207     {\r
208       char type = 0;\r
209       String symbol = "\u03B1";\r
210 \r
211       if (evt.getActionCommand().equals(HELIX))\r
212       {\r
213         type = 'H';\r
214       }\r
215       else if (evt.getActionCommand().equals(SHEET))\r
216       {\r
217         type = 'E';\r
218         symbol = "\u03B2";\r
219       }\r
220 \r
221       if (!aa[activeRow].hasIcons)\r
222       {\r
223         aa[activeRow].hasIcons = true;\r
224       }\r
225 \r
226       String label = JOptionPane.showInputDialog(\r
227           "Enter a label for the structure?",\r
228           symbol);\r
229 \r
230       if (label == null)\r
231       {\r
232         label = "";\r
233       }\r
234 \r
235       if ( (label.length() > 0) && !aa[activeRow].hasText)\r
236       {\r
237         aa[activeRow].hasText = true;\r
238       }\r
239 \r
240       for (int i = 0; i < activeRes.size(); i++)\r
241       {\r
242         int index = Integer.parseInt(activeRes.get(i).toString());\r
243 \r
244         if (anot[index] == null)\r
245         {\r
246           anot[index] = new Annotation(label, "", type, 0);\r
247         }\r
248 \r
249         anot[index].secondaryStructure = type;\r
250         anot[index].displayCharacter = label;\r
251       }\r
252     }\r
253 \r
254     adjustPanelHeight();\r
255     activeRes = null;\r
256     repaint();\r
257 \r
258     return;\r
259   }\r
260 \r
261   public void mousePressed(MouseEvent evt)\r
262   {\r
263     int height = 0;\r
264     activeRow = -1;\r
265     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();\r
266 \r
267     for (int i = 0; i < aa.length; i++)\r
268     {\r
269       height += aa[i].height;\r
270 \r
271       if (evt.getY() < height)\r
272       {\r
273         if (aa[i].editable)\r
274            activeRow = i;\r
275          else\r
276            activeRes = null;\r
277 \r
278         break;\r
279       }\r
280     }\r
281 \r
282 \r
283     if (SwingUtilities.isRightMouseButton(evt))\r
284     {\r
285       if (activeRes == null)\r
286       {\r
287         return;\r
288       }\r
289 \r
290 \r
291       JPopupMenu pop = new JPopupMenu("Structure type");\r
292       JMenuItem item = new JMenuItem(HELIX);\r
293       item.addActionListener(this);\r
294       pop.add(item);\r
295       item = new JMenuItem(SHEET);\r
296       item.addActionListener(this);\r
297       pop.add(item);\r
298       item = new JMenuItem(LABEL);\r
299       item.addActionListener(this);\r
300       pop.add(item);\r
301       item = new JMenuItem(COLOUR);\r
302       item.addActionListener(this);\r
303       pop.add(item);\r
304       item = new JMenuItem(REMOVE);\r
305       item.addActionListener(this);\r
306       pop.add(item);\r
307       pop.show(this, evt.getX(), evt.getY());\r
308 \r
309       return;\r
310     }\r
311 \r
312 \r
313     if (aa == null)\r
314     {\r
315       return;\r
316     }\r
317 \r
318 \r
319     int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();\r
320 \r
321     if (evt.isControlDown() || evt.isAltDown())\r
322     {\r
323       addEditableColumn(res);\r
324     }\r
325     else if (evt.isShiftDown())\r
326     {\r
327       if (activeRes == null)\r
328       {\r
329         activeRes = new ArrayList();\r
330       }\r
331       else\r
332       {\r
333         int start = Integer.parseInt(activeRes.get(activeRes.size() -\r
334             1).toString());\r
335         int end = res;\r
336 \r
337         if (end < start)\r
338         {\r
339           int temp = end;\r
340           end = start;\r
341           start = temp;\r
342         }\r
343 \r
344         for (int n = start; n <= end; n++)\r
345         {\r
346           addEditableColumn(n);\r
347         }\r
348       }\r
349     }\r
350     else\r
351     {\r
352       activeRes = new ArrayList();\r
353       activeRes.add(String.valueOf(res));\r
354     }\r
355 \r
356     repaint();\r
357   }\r
358 \r
359   public void mouseReleased(MouseEvent evt)\r
360   {\r
361   }\r
362 \r
363   public void mouseEntered(MouseEvent evt)\r
364   {\r
365   }\r
366 \r
367   public void mouseExited(MouseEvent evt)\r
368   {\r
369   }\r
370 \r
371   public void mouseDragged(MouseEvent evt)\r
372   {\r
373   }\r
374 \r
375   public void mouseMoved(MouseEvent evt)\r
376   {\r
377     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();\r
378 \r
379     if (aa == null)\r
380     {\r
381       return;\r
382     }\r
383 \r
384     int row = -1;\r
385     int height = 0;\r
386 \r
387     for (int i = 0; i < aa.length; i++)\r
388     {\r
389       if (aa[i].visible)\r
390       {\r
391         height += aa[i].height;\r
392       }\r
393 \r
394       if (evt.getY() < height)\r
395       {\r
396         row = i;\r
397 \r
398         break;\r
399       }\r
400     }\r
401 \r
402     int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();\r
403 \r
404     if ( (row > -1) && (res < aa[row].annotations.length) &&\r
405         (aa[row].annotations[res] != null))\r
406     {\r
407       this.setToolTipText(aa[row].annotations[res].description);\r
408 \r
409       StringBuffer text = new StringBuffer("Sequence position " +\r
410                                            (res + 1) + "  " +\r
411                                            aa[row].annotations[res].description);\r
412       ap.alignFrame.statusBar.setText(text.toString());\r
413     }\r
414   }\r
415 \r
416   public void mouseClicked(MouseEvent evt)\r
417   {\r
418   }\r
419 \r
420   public void paintComponent(Graphics g)\r
421   {\r
422     g.setColor(Color.white);\r
423     g.fillRect(0, 0, getWidth(), getHeight());\r
424 \r
425     if (fastPaint)\r
426     {\r
427       g.drawImage(image, 0, 0, this);\r
428       fastPaint = false;\r
429 \r
430       return;\r
431     }\r
432 \r
433     imgWidth = (av.endRes - av.startRes + 1) * av.charWidth;\r
434 \r
435     image = new BufferedImage(imgWidth, ap.annotationPanel.getHeight(),\r
436                               BufferedImage.TYPE_INT_RGB);\r
437     gg = (Graphics2D) image.getGraphics();\r
438     gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,\r
439                         RenderingHints.VALUE_ANTIALIAS_ON);\r
440 \r
441     gg.setFont(av.getFont());\r
442     fm = gg.getFontMetrics();\r
443 \r
444     drawComponent(gg, av.startRes, av.endRes + 1);\r
445     g.drawImage(image, 0, 0, this);\r
446   }\r
447 \r
448   public void fastPaint(int horizontal)\r
449   {\r
450     if ( (horizontal == 0) ||\r
451         (av.alignment.getAlignmentAnnotation() == null) ||\r
452         (av.alignment.getAlignmentAnnotation().length < 1))\r
453     {\r
454       repaint();\r
455 \r
456       return;\r
457     }\r
458 \r
459     gg.copyArea(0, 0, imgWidth, getHeight(), -horizontal * av.charWidth, 0);\r
460 \r
461     int sr = av.startRes;\r
462     int er = av.endRes + 1;\r
463     int transX = 0;\r
464 \r
465     if (horizontal > 0) // scrollbar pulled right, image to the left\r
466     {\r
467       transX = (er - sr - horizontal) * av.charWidth;\r
468       sr = er - horizontal;\r
469     }\r
470     else if (horizontal < 0)\r
471     {\r
472       er = sr - horizontal;\r
473     }\r
474 \r
475     gg.translate(transX, 0);\r
476 \r
477     drawComponent(gg, sr, er);\r
478 \r
479     gg.translate( -transX, 0);\r
480 \r
481     fastPaint = true;\r
482     repaint();\r
483   }\r
484 \r
485   public void drawComponent(Graphics2D g, int startRes, int endRes)\r
486   {\r
487     g.setColor(Color.white);\r
488     g.fillRect(0, 0, (endRes - startRes) * av.charWidth, getHeight());\r
489 \r
490     if ( (av.alignment.getAlignmentAnnotation() == null) ||\r
491         (av.alignment.getAlignmentAnnotation().length < 1))\r
492     {\r
493       g.setColor(Color.white);\r
494       g.fillRect(0, 0, getWidth(), getHeight());\r
495       g.setColor(Color.black);\r
496       g.drawString("Alignment has no annotations", 20, 15);\r
497 \r
498       return;\r
499     }\r
500 \r
501     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();\r
502 \r
503     int j;\r
504     int x = 0;\r
505     int y = 0;\r
506     char[] lastSS = new char[aa.length];\r
507     int[] lastSSX = new int[aa.length];\r
508     int iconOffset = av.charHeight / 2;\r
509     boolean validRes = false;\r
510 \r
511     //\u03B2 \u03B1\r
512     for (int i = 0; i < aa.length; i++)\r
513     {\r
514       AlignmentAnnotation row = aa[i];\r
515 \r
516       if (!row.visible)\r
517       {\r
518         continue;\r
519       }\r
520 \r
521       if (row.isGraph)\r
522       {\r
523         // this is so that we draw the characters below the graph\r
524         y += row.height;\r
525 \r
526         if (row.hasText)\r
527         {\r
528           y -= av.charHeight;\r
529         }\r
530       }\r
531 \r
532       if (row.hasText)\r
533       {\r
534         iconOffset = av.charHeight / 2;\r
535       }\r
536       else\r
537       {\r
538         iconOffset = 0;\r
539       }\r
540 \r
541       for (j = startRes; j < endRes; j++)\r
542       {\r
543         if ( (row.annotations.length <= j) ||\r
544             (row.annotations[j] == null))\r
545         {\r
546           validRes = false;\r
547         }\r
548         else\r
549         {\r
550           validRes = true;\r
551         }\r
552 \r
553         x = (j - startRes) * av.charWidth;\r
554 \r
555         if (activeRow == i)\r
556         {\r
557           g.setColor(Color.red);\r
558 \r
559           if (activeRes != null)\r
560           {\r
561             for (int n = 0; n < activeRes.size(); n++)\r
562             {\r
563               int v = Integer.parseInt(activeRes.get(n).toString());\r
564 \r
565               if (v == j)\r
566               {\r
567                 g.fillRect( (j - startRes) * av.charWidth, y,\r
568                            av.charWidth, row.height);\r
569               }\r
570             }\r
571           }\r
572         }\r
573 \r
574         if (validRes &&\r
575             (row.annotations[j].displayCharacter.length() > 0))\r
576         {\r
577           int charOffset = (av.charWidth -\r
578                             fm.charWidth(row.annotations[j].displayCharacter.\r
579                                          charAt(\r
580                                              0))) / 2;\r
581           g.setColor(row.annotations[j].colour);\r
582 \r
583           if (j == 0)\r
584           {\r
585             if ( (row.annotations[0].secondaryStructure == 'H') ||\r
586                 (row.annotations[0].secondaryStructure == 'E'))\r
587             {\r
588               g.drawString(row.annotations[j].displayCharacter,\r
589                            x, y + iconOffset + 2);\r
590             }\r
591           }\r
592           else if ( ( (row.annotations[j].secondaryStructure == 'H') ||\r
593                      (row.annotations[j].secondaryStructure == 'E')) &&\r
594                    ( (row.annotations[j - 1] == null) ||\r
595                     (row.annotations[j].secondaryStructure != row.annotations[j -\r
596                      1].secondaryStructure)))\r
597           {\r
598             g.drawString(row.annotations[j].displayCharacter, x,\r
599                          y + iconOffset + 2);\r
600           }\r
601 \r
602           if (!row.hasIcons)\r
603           {\r
604             g.drawString(row.annotations[j].displayCharacter,\r
605                          x + charOffset, y + iconOffset + 2);\r
606           }\r
607         }\r
608 \r
609         if (row.hasIcons)\r
610         {\r
611           if (!validRes ||\r
612               (row.annotations[j].secondaryStructure != lastSS[i]))\r
613           {\r
614             switch (lastSS[i])\r
615             {\r
616               case 'H':\r
617                 g.setColor(HELIX_COLOUR);\r
618                 g.fillRoundRect(lastSSX[i], y + 4 + iconOffset,\r
619                                 x - lastSSX[i], 7, 8, 8);\r
620 \r
621                 break;\r
622 \r
623               case 'E':\r
624                 g.setColor(SHEET_COLOUR);\r
625                 g.fillRect(lastSSX[i], y + 4 + iconOffset,\r
626                            x - lastSSX[i] - 4, 7);\r
627                 g.fillPolygon(new int[]\r
628                               {x - 4, x - 4, x},\r
629                               new int[]\r
630                               {\r
631                               y + iconOffset, y + 14 + iconOffset,\r
632                               y + 8 + iconOffset\r
633                 }, 3);\r
634 \r
635                 break;\r
636 \r
637               case 'C':\r
638                 break;\r
639 \r
640               default:\r
641                 g.setColor(Color.gray);\r
642                 g.fillRect(lastSSX[i], y + 6 + iconOffset,\r
643                            x - lastSSX[i], 2);\r
644 \r
645                 break;\r
646             }\r
647 \r
648             if (validRes)\r
649             {\r
650               lastSS[i] = row.annotations[j].secondaryStructure;\r
651             }\r
652             else\r
653             {\r
654               lastSS[i] = ' ';\r
655             }\r
656 \r
657             lastSSX[i] = x;\r
658           }\r
659         }\r
660 \r
661         if (validRes && row.isGraph)\r
662         {\r
663           g.setColor(new Color(0, 0, 180));\r
664 \r
665           int height = (int) ( (row.annotations[j].value / row.graphMax) *\r
666                               GRAPH_HEIGHT);\r
667 \r
668           if (row.windowLength > 1)\r
669           {\r
670             int total = 0;\r
671 \r
672             for (int i2 = j - (row.windowLength / 2);\r
673                  i2 < (j + (row.windowLength / 2)); i2++)\r
674             {\r
675               if ( (i2 < 0) || (i2 >= av.alignment.getWidth()))\r
676               {\r
677                 continue;\r
678               }\r
679 \r
680               total += row.annotations[i2].value;\r
681             }\r
682 \r
683             total /= row.windowLength;\r
684             height = (int) ( (total / row.graphMax) * GRAPH_HEIGHT);\r
685           }\r
686 \r
687           g.setColor(row.annotations[j].colour);\r
688           g.fillRect(x, y - height, av.charWidth, height);\r
689         }\r
690       }\r
691 \r
692       x += av.charWidth;\r
693 \r
694       if (row.hasIcons)\r
695       {\r
696         switch (lastSS[i])\r
697         {\r
698           case 'H':\r
699             g.setColor(HELIX_COLOUR);\r
700             g.fillRoundRect(lastSSX[i], y + 4 + iconOffset,\r
701                             x - lastSSX[i], 7, 8, 8);\r
702 \r
703             break;\r
704 \r
705           case 'E':\r
706             g.setColor(SHEET_COLOUR);\r
707             g.fillRect(lastSSX[i], y + 4 + iconOffset,\r
708                        x - lastSSX[i] - 4, 7);\r
709             g.fillPolygon(new int[]\r
710                           {x - 4, x - 4, x},\r
711                           new int[]\r
712                           {\r
713                           y + iconOffset, y + 14 + iconOffset,\r
714                           y + 7 + iconOffset\r
715             }, 3);\r
716 \r
717             break;\r
718 \r
719           case 'C':\r
720             break;\r
721 \r
722           default:\r
723             g.setColor(Color.gray);\r
724             g.fillRect(lastSSX[i], y + 6 + iconOffset, x - lastSSX[i], 2);\r
725 \r
726             break;\r
727         }\r
728       }\r
729 \r
730       if (row.isGraph && row.hasText)\r
731       {\r
732         y += av.charHeight;\r
733       }\r
734 \r
735       if (!row.isGraph)\r
736       {\r
737         y += aa[i].height;\r
738       }\r
739     }\r
740   }\r
741 \r
742   // used by overview window\r
743   public void drawGraph(Graphics g, AlignmentAnnotation aa, int width, int y)\r
744   {\r
745     g.setColor(Color.white);\r
746     g.fillRect(0, 0, width, y);\r
747     g.setColor(new Color(0, 0, 180));\r
748 \r
749     int x = 0;\r
750 \r
751     for (int j = 0; j < aa.annotations.length; j++)\r
752     {\r
753       g.setColor(new Color(0, 0, 180));\r
754 \r
755       int height = (int) ( (aa.annotations[j].value / aa.graphMax) *\r
756                           GRAPH_HEIGHT);\r
757       g.fillRect(x, y - height, av.charWidth, height);\r
758       x += av.charWidth;\r
759     }\r
760   }\r
761 }\r