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