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