javatidy
[jalview.git] / src / jalview / appletgui / AnnotationPanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
3  * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, G Barton, M Clamp, S Searle
4  *
5  * This file is part of Jalview.
6  *
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
10  *
11  * Jalview is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
14  * PURPOSE.  See the GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.appletgui;
19
20 import java.util.*;
21
22 import java.awt.*;
23 import java.awt.event.*;
24 import java.awt.image.BufferedImage;
25
26 import jalview.datamodel.*;
27 import jalview.renderer.AnnotationRenderer;
28 import jalview.renderer.AwtRenderPanelI;
29
30 public class AnnotationPanel extends Panel implements AwtRenderPanelI, AdjustmentListener,
31         ActionListener, MouseListener, MouseMotionListener
32 {
33   AlignViewport av;
34
35   AlignmentPanel ap;
36
37   int activeRow = -1;
38
39   Vector activeRes;
40
41   final String HELIX = "Helix";
42
43   final String SHEET = "Sheet";
44
45   /**
46    * For RNA secondary structure "stems" aka helices
47    */
48   final String STEM = "RNA Helix";
49
50   final String LABEL = "Label";
51
52   final String REMOVE = "Remove Annotation";
53
54   final String COLOUR = "Colour";
55
56   final Color HELIX_COLOUR = Color.red.darker();
57
58   final Color SHEET_COLOUR = Color.green.darker().darker();
59
60   Image image;
61
62   Graphics gg;
63
64   FontMetrics fm;
65
66   int imgWidth = 0;
67
68   boolean fastPaint = false;
69
70   // Used For mouse Dragging and resizing graphs
71   int graphStretch = -1;
72
73   int graphStretchY = -1;
74
75   boolean mouseDragging = false;
76
77   public static int GRAPH_HEIGHT = 40;
78
79   boolean MAC = false;
80
81   public final AnnotationRenderer renderer;
82
83   public AnnotationPanel(AlignmentPanel ap)
84   {
85     MAC = new jalview.util.Platform().isAMac();
86     this.ap = ap;
87     av = ap.av;
88     setLayout(null);
89     int height = adjustPanelHeight();
90     ap.apvscroll.setValues(0, getSize().height, 0, height);
91
92     addMouseMotionListener(this);
93
94     addMouseListener(this);
95
96     // ap.annotationScroller.getVAdjustable().addAdjustmentListener( this );
97     renderer = new AnnotationRenderer();
98   }
99
100   public AnnotationPanel(AlignViewport av)
101   {
102     this.av = av;
103     renderer = new AnnotationRenderer();
104   }
105
106   @Override
107   public void adjustmentValueChanged(AdjustmentEvent evt)
108   {
109   }
110
111   /**
112    * DOCUMENT ME!
113    *
114    * @param evt
115    *          DOCUMENT ME!
116    */
117   @Override
118   public void actionPerformed(ActionEvent evt)
119   {
120     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
121     if (aa == null)
122     {
123       return;
124     }
125     Annotation[] anot = aa[activeRow].annotations;
126
127     if (anot.length < av.getColumnSelection().getMax())
128     {
129       Annotation[] temp = new Annotation[av.getColumnSelection().getMax() + 2];
130       System.arraycopy(anot, 0, temp, 0, anot.length);
131       anot = temp;
132       aa[activeRow].annotations = anot;
133     }
134
135     String label = "";
136     if (av.getColumnSelection() != null && av.getColumnSelection().size() > 0
137             && anot[av.getColumnSelection().getMin()] != null)
138       label = anot[av.getColumnSelection().getMin()].displayCharacter;
139
140     if (evt.getActionCommand().equals(REMOVE))
141     {
142       for (int i = 0; i < av.getColumnSelection().size(); i++)
143       {
144         anot[av.getColumnSelection().columnAt(i)] = null;
145       }
146     }
147     else if (evt.getActionCommand().equals(LABEL))
148     {
149       label = enterLabel(label, "Enter Label");
150
151       if (label == null)
152       {
153         return;
154       }
155
156       if ((label.length() > 0) && !aa[activeRow].hasText)
157       {
158         aa[activeRow].hasText = true;
159       }
160
161       for (int i = 0; i < av.getColumnSelection().size(); i++)
162       {
163         int index = av.getColumnSelection().columnAt(i);
164
165         if (!av.getColumnSelection().isVisible(index))
166           continue;
167
168         if (anot[index] == null)
169         {
170           anot[index] = new Annotation(label, "", ' ', 0);
171         }
172
173         anot[index].displayCharacter = label;
174       }
175     }
176     else if (evt.getActionCommand().equals(COLOUR))
177     {
178       UserDefinedColours udc = new UserDefinedColours(this, Color.black,
179               ap.alignFrame);
180
181       Color col = udc.getColor();
182
183       for (int i = 0; i < av.getColumnSelection().size(); i++)
184       {
185         int index = av.getColumnSelection().columnAt(i);
186
187         if (!av.getColumnSelection().isVisible(index))
188           continue;
189
190         if (anot[index] == null)
191         {
192           anot[index] = new Annotation("", "", ' ', 0);
193         }
194
195         anot[index].colour = col;
196       }
197     }
198     else
199     // HELIX OR SHEET
200     {
201       char type = 0;
202       String symbol = "\u03B1";
203
204       if (evt.getActionCommand().equals(HELIX))
205       {
206         type = 'H';
207       }
208       else if (evt.getActionCommand().equals(SHEET))
209       {
210         type = 'E';
211         symbol = "\u03B2";
212       }
213
214       // Added by LML to color stems
215       else if (evt.getActionCommand().equals(STEM))
216       {
217         type = 'S';
218         symbol = "\u03C3";
219       }
220
221       if (!aa[activeRow].hasIcons)
222       {
223         aa[activeRow].hasIcons = true;
224       }
225
226       label = enterLabel(symbol, "Enter Label");
227
228       if (label == null)
229       {
230         return;
231       }
232
233       if ((label.length() > 0) && !aa[activeRow].hasText)
234       {
235         aa[activeRow].hasText = true;
236       }
237
238       for (int i = 0; i < av.getColumnSelection().size(); i++)
239       {
240         int index = av.getColumnSelection().columnAt(i);
241
242         if (!av.getColumnSelection().isVisible(index))
243           continue;
244
245         if (anot[index] == null)
246         {
247           anot[index] = new Annotation(label, "", type, 0);
248         }
249
250         anot[index].secondaryStructure = type;
251         anot[index].displayCharacter = label;
252       }
253     }
254
255     aa[activeRow].validateRangeAndDisplay();
256
257     adjustPanelHeight();
258     repaint();
259
260     return;
261   }
262
263   String enterLabel(String text, String label)
264   {
265     EditNameDialog dialog = new EditNameDialog(text, null, label, null,
266             ap.alignFrame, "Enter Label", 400, 200, true);
267
268     if (dialog.accept)
269       return dialog.getName();
270     else
271       return null;
272   }
273
274   @Override
275   public void mousePressed(MouseEvent evt)
276   {
277     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
278     if (aa == null)
279     {
280       return;
281     }
282
283     int height = -scrollOffset;
284     activeRow = -1;
285
286     for (int i = 0; i < aa.length; i++)
287     {
288       if (aa[i].visible)
289       {
290         height += aa[i].height;
291       }
292
293       if (evt.getY() < height)
294       {
295         if (aa[i].editable)
296         {
297           activeRow = i;
298         }
299         else if (aa[i].graph > 0)
300         {
301           // Stretch Graph
302           graphStretch = i;
303           graphStretchY = evt.getY();
304         }
305
306         break;
307       }
308     }
309
310     if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK
311             && activeRow != -1)
312     {
313       if (av.getColumnSelection() == null)
314       {
315         return;
316       }
317
318       PopupMenu pop = new PopupMenu("Structure type");
319       MenuItem item;
320       /*
321        * Just display the needed structure options
322        */
323       if (av.getAlignment().isNucleotide() == true)
324       {
325         item = new MenuItem(STEM);
326         item.addActionListener(this);
327         pop.add(item);
328       } else {
329         item = new MenuItem(HELIX);
330         item.addActionListener(this);
331         pop.add(item);
332         item = new MenuItem(SHEET);
333         item.addActionListener(this);
334         pop.add(item);
335       }
336       item = new MenuItem(LABEL);
337       item.addActionListener(this);
338       pop.add(item);
339       item = new MenuItem(COLOUR);
340       item.addActionListener(this);
341       pop.add(item);
342       item = new MenuItem(REMOVE);
343       item.addActionListener(this);
344       pop.add(item);
345       ap.alignFrame.add(pop);
346       pop.show(this, evt.getX(), evt.getY());
347
348       return;
349     }
350
351     if (aa == null)
352     {
353       return;
354     }
355
356     ap.scalePanel.mousePressed(evt);
357   }
358
359   @Override
360   public void mouseReleased(MouseEvent evt)
361   {
362     graphStretch = -1;
363     graphStretchY = -1;
364     mouseDragging = false;
365     if (needValidating)
366     {
367       ap.validate();
368       needValidating = false;
369     }
370     ap.scalePanel.mouseReleased(evt);
371   }
372
373   @Override
374   public void mouseClicked(MouseEvent evt)
375   {
376   }
377
378   boolean needValidating = false;
379
380   @Override
381   public void mouseDragged(MouseEvent evt)
382   {
383     if (graphStretch > -1)
384     {
385       av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight += graphStretchY
386               - evt.getY();
387       if (av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight < 0)
388       {
389         av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight = 0;
390       }
391       graphStretchY = evt.getY();
392       calcPanelHeight();
393       needValidating = true;
394       ap.paintAlignment(true);
395     }
396     else
397     {
398       ap.scalePanel.mouseDragged(evt);
399     }
400   }
401
402   @Override
403   public void mouseMoved(MouseEvent evt)
404   {
405     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
406     if (aa == null)
407     {
408       return;
409     }
410
411     int row = -1;
412     int height = -scrollOffset;
413     for (int i = 0; i < aa.length; i++)
414     {
415
416       if (aa[i].visible)
417       {
418         height += aa[i].height;
419       }
420
421       if (evt.getY() < height)
422       {
423         row = i;
424         break;
425       }
426     }
427
428     int res = evt.getX() / av.getCharWidth() + av.getStartRes();
429
430     if (av.hasHiddenColumns())
431     {
432       res = av.getColumnSelection().adjustForHiddenColumns(res);
433     }
434
435     if (row > -1 && res < aa[row].annotations.length
436             && aa[row].annotations[res] != null)
437     {
438       StringBuffer text = new StringBuffer("Sequence position " + (res + 1));
439       if (aa[row].annotations[res].description != null)
440       {
441         text.append("  " + aa[row].annotations[res].description);
442       }
443       ap.alignFrame.statusBar.setText(text.toString());
444     }
445   }
446
447   @Override
448   public void mouseEntered(MouseEvent evt)
449   {
450     ap.scalePanel.mouseEntered(evt);
451   }
452
453   @Override
454   public void mouseExited(MouseEvent evt)
455   {
456     ap.scalePanel.mouseExited(evt);
457   }
458
459   public int adjustPanelHeight()
460   {
461     return adjustPanelHeight(true);
462   }
463
464   public int adjustPanelHeight(boolean repaint)
465   {
466     int height = calcPanelHeight();
467     this.setSize(new Dimension(getSize().width, height));
468     if (repaint)
469     {
470       repaint();
471     }
472     return height;
473   }
474   /**
475    * calculate the height for visible annotation, revalidating bounds where necessary
476    * ABSTRACT GUI METHOD
477    * @return total height of annotation
478    */
479   public int calcPanelHeight()
480   {
481     // setHeight of panels
482     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
483     int height = 0;
484
485     if (aa != null)
486     {
487       for (int i = 0; i < aa.length; i++)
488       {
489         if (!aa[i].visible)
490         {
491           continue;
492         }
493
494         aa[i].height = 0;
495
496         if (aa[i].hasText)
497         {
498           aa[i].height += av.charHeight;
499         }
500
501         if (aa[i].hasIcons)
502         {
503           aa[i].height += 16;
504         }
505
506         if (aa[i].graph > 0)
507         {
508           aa[i].height += aa[i].graphHeight;
509         }
510
511         if (aa[i].height == 0)
512         {
513           aa[i].height = 20;
514         }
515
516         height += aa[i].height;
517       }
518     }
519     if (height == 0)
520     {
521       height = 20;
522     }
523
524     return height;
525
526   }
527
528   public void addEditableColumn(int i)
529   {
530     if (activeRow == -1)
531     {
532       AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
533       if (aa == null)
534       {
535         return;
536       }
537
538       for (int j = 0; j < aa.length; j++)
539       {
540         if (aa[j].editable)
541         {
542           activeRow = j;
543           break;
544         }
545       }
546     }
547
548     if (activeRes == null)
549     {
550       activeRes = new Vector();
551       activeRes.addElement(String.valueOf(i));
552       return;
553     }
554
555     activeRes.addElement(String.valueOf(i));
556   }
557
558   @Override
559   public void update(Graphics g)
560   {
561     paint(g);
562   }
563
564   @Override
565   public void paint(Graphics g)
566   {
567     Dimension d = getSize();
568     imgWidth = d.width;
569     // (av.endRes - av.startRes + 1) * av.charWidth;
570     if (imgWidth<1 || d.height<1)
571     {
572       return;
573     }
574     if (image == null || imgWidth != image.getWidth(this) || d.height != image.getHeight(this))
575     {
576       image = createImage(imgWidth, d.height);
577       gg = image.getGraphics();
578       gg.setFont(av.getFont());
579       fm = gg.getFontMetrics();
580       fastPaint = false;
581     }
582
583     if (fastPaint)
584     {
585       g.drawImage(image, 0, 0, this);
586       fastPaint = false;
587       return;
588     }
589
590     gg.setColor(Color.white);
591     gg.fillRect(0, 0, getSize().width, getSize().height);
592     drawComponent(gg, av.startRes, av.endRes + 1);
593
594     g.drawImage(image, 0, 0, this);
595   }
596
597   public void fastPaint(int horizontal)
598   {
599     if (horizontal == 0 || av.getAlignment().getAlignmentAnnotation() == null
600             || av.getAlignment().getAlignmentAnnotation().length < 1)
601     {
602       repaint();
603       return;
604     }
605
606     gg.copyArea(0, 0, imgWidth, getSize().height, -horizontal
607             * av.charWidth, 0);
608     int sr = av.startRes, er = av.endRes + 1, transX = 0;
609
610     if (horizontal > 0) // scrollbar pulled right, image to the left
611     {
612       transX = (er - sr - horizontal) * av.charWidth;
613       sr = er - horizontal;
614     }
615     else if (horizontal < 0)
616     {
617       er = sr - horizontal;
618     }
619
620     gg.translate(transX, 0);
621
622     drawComponent(gg, sr, er);
623
624     gg.translate(-transX, 0);
625
626     fastPaint = true;
627     repaint();
628   }
629
630   /**
631    * DOCUMENT ME!
632    *
633    * @param g
634    *          DOCUMENT ME!
635    * @param startRes
636    *          DOCUMENT ME!
637    * @param endRes
638    *          DOCUMENT ME!
639    */
640   public void drawComponent(Graphics g, int startRes, int endRes)
641   {
642     Font ofont = av.getFont();
643     g.setFont(ofont);
644
645     g.setColor(Color.white);
646     g.fillRect(0, 0, (endRes - startRes) * av.charWidth, getSize().height);
647
648     if (fm == null)
649     {
650       fm = g.getFontMetrics();
651     }
652
653     if ((av.getAlignment().getAlignmentAnnotation() == null)
654             || (av.getAlignment().getAlignmentAnnotation().length < 1))
655     {
656       g.setColor(Color.white);
657       g.fillRect(0, 0, getSize().width, getSize().height);
658       g.setColor(Color.black);
659       if (av.validCharWidth)
660       {
661         g.drawString("Alignment has no annotations", 20, 15);
662       }
663
664       return;
665     }
666     g.translate(0, -scrollOffset);
667     renderer.drawComponent(this, av, g, activeRow, startRes, endRes);
668     g.translate(0, +scrollOffset);
669   }
670
671   int scrollOffset = 0;
672
673   public void setScrollOffset(int value)
674   {
675     scrollOffset = value;
676     repaint();
677   }
678
679   @Override
680   public FontMetrics getFontMetrics()
681   {
682     return fm;
683   }
684
685   @Override
686   public Image getFadedImage()
687   {
688     return image;
689   }
690
691   @Override
692   public int getFadedImageWidth()
693   {
694     return imgWidth;
695   }
696 }