ensure all annotation dependent rendering gets refreshed
[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     ap.alignmentChanged();
259     repaint();
260
261     return;
262   }
263
264   String enterLabel(String text, String label)
265   {
266     EditNameDialog dialog = new EditNameDialog(text, null, label, null,
267             ap.alignFrame, "Enter Label", 400, 200, true);
268
269     if (dialog.accept)
270       return dialog.getName();
271     else
272       return null;
273   }
274
275   @Override
276   public void mousePressed(MouseEvent evt)
277   {
278     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
279     if (aa == null)
280     {
281       return;
282     }
283
284     int height = -scrollOffset;
285     activeRow = -1;
286
287     for (int i = 0; i < aa.length; i++)
288     {
289       if (aa[i].visible)
290       {
291         height += aa[i].height;
292       }
293
294       if (evt.getY() < height)
295       {
296         if (aa[i].editable)
297         {
298           activeRow = i;
299         }
300         else if (aa[i].graph > 0)
301         {
302           // Stretch Graph
303           graphStretch = i;
304           graphStretchY = evt.getY();
305         }
306
307         break;
308       }
309     }
310
311     if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK
312             && activeRow != -1)
313     {
314       if (av.getColumnSelection() == null)
315       {
316         return;
317       }
318
319       PopupMenu pop = new PopupMenu("Structure type");
320       MenuItem item;
321       /*
322        * Just display the needed structure options
323        */
324       if (av.getAlignment().isNucleotide() == true)
325       {
326         item = new MenuItem(STEM);
327         item.addActionListener(this);
328         pop.add(item);
329       } else {
330         item = new MenuItem(HELIX);
331         item.addActionListener(this);
332         pop.add(item);
333         item = new MenuItem(SHEET);
334         item.addActionListener(this);
335         pop.add(item);
336       }
337       item = new MenuItem(LABEL);
338       item.addActionListener(this);
339       pop.add(item);
340       item = new MenuItem(COLOUR);
341       item.addActionListener(this);
342       pop.add(item);
343       item = new MenuItem(REMOVE);
344       item.addActionListener(this);
345       pop.add(item);
346       ap.alignFrame.add(pop);
347       pop.show(this, evt.getX(), evt.getY());
348
349       return;
350     }
351
352     if (aa == null)
353     {
354       return;
355     }
356
357     ap.scalePanel.mousePressed(evt);
358   }
359
360   @Override
361   public void mouseReleased(MouseEvent evt)
362   {
363     graphStretch = -1;
364     graphStretchY = -1;
365     mouseDragging = false;
366     if (needValidating)
367     {
368       ap.validate();
369       needValidating = false;
370     }
371     ap.scalePanel.mouseReleased(evt);
372   }
373
374   @Override
375   public void mouseClicked(MouseEvent evt)
376   {
377   }
378
379   boolean needValidating = false;
380
381   @Override
382   public void mouseDragged(MouseEvent evt)
383   {
384     if (graphStretch > -1)
385     {
386       av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight += graphStretchY
387               - evt.getY();
388       if (av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight < 0)
389       {
390         av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight = 0;
391       }
392       graphStretchY = evt.getY();
393       calcPanelHeight();
394       needValidating = true;
395       ap.paintAlignment(true);
396     }
397     else
398     {
399       ap.scalePanel.mouseDragged(evt);
400     }
401   }
402
403   @Override
404   public void mouseMoved(MouseEvent evt)
405   {
406     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
407     if (aa == null)
408     {
409       return;
410     }
411
412     int row = -1;
413     int height = -scrollOffset;
414     for (int i = 0; i < aa.length; i++)
415     {
416
417       if (aa[i].visible)
418       {
419         height += aa[i].height;
420       }
421
422       if (evt.getY() < height)
423       {
424         row = i;
425         break;
426       }
427     }
428
429     int res = evt.getX() / av.getCharWidth() + av.getStartRes();
430
431     if (av.hasHiddenColumns())
432     {
433       res = av.getColumnSelection().adjustForHiddenColumns(res);
434     }
435
436     if (row > -1 && res < aa[row].annotations.length
437             && aa[row].annotations[res] != null)
438     {
439       StringBuffer text = new StringBuffer("Sequence position " + (res + 1));
440       if (aa[row].annotations[res].description != null)
441       {
442         text.append("  " + aa[row].annotations[res].description);
443       }
444       ap.alignFrame.statusBar.setText(text.toString());
445     }
446   }
447
448   @Override
449   public void mouseEntered(MouseEvent evt)
450   {
451     ap.scalePanel.mouseEntered(evt);
452   }
453
454   @Override
455   public void mouseExited(MouseEvent evt)
456   {
457     ap.scalePanel.mouseExited(evt);
458   }
459
460   public int adjustPanelHeight()
461   {
462     return adjustPanelHeight(true);
463   }
464
465   public int adjustPanelHeight(boolean repaint)
466   {
467     int height = calcPanelHeight();
468     this.setSize(new Dimension(getSize().width, height));
469     if (repaint)
470     {
471       repaint();
472     }
473     return height;
474   }
475   /**
476    * calculate the height for visible annotation, revalidating bounds where necessary
477    * ABSTRACT GUI METHOD
478    * @return total height of annotation
479    */
480   public int calcPanelHeight()
481   {
482     // setHeight of panels
483     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
484     int height = 0;
485
486     if (aa != null)
487     {
488       for (int i = 0; i < aa.length; i++)
489       {
490         if (!aa[i].visible)
491         {
492           continue;
493         }
494
495         aa[i].height = 0;
496
497         if (aa[i].hasText)
498         {
499           aa[i].height += av.charHeight;
500         }
501
502         if (aa[i].hasIcons)
503         {
504           aa[i].height += 16;
505         }
506
507         if (aa[i].graph > 0)
508         {
509           aa[i].height += aa[i].graphHeight;
510         }
511
512         if (aa[i].height == 0)
513         {
514           aa[i].height = 20;
515         }
516
517         height += aa[i].height;
518       }
519     }
520     if (height == 0)
521     {
522       height = 20;
523     }
524
525     return height;
526
527   }
528
529   public void addEditableColumn(int i)
530   {
531     if (activeRow == -1)
532     {
533       AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
534       if (aa == null)
535       {
536         return;
537       }
538
539       for (int j = 0; j < aa.length; j++)
540       {
541         if (aa[j].editable)
542         {
543           activeRow = j;
544           break;
545         }
546       }
547     }
548
549     if (activeRes == null)
550     {
551       activeRes = new Vector();
552       activeRes.addElement(String.valueOf(i));
553       return;
554     }
555
556     activeRes.addElement(String.valueOf(i));
557   }
558
559   @Override
560   public void update(Graphics g)
561   {
562     paint(g);
563   }
564
565   @Override
566   public void paint(Graphics g)
567   {
568     Dimension d = getSize();
569     imgWidth = d.width;
570     // (av.endRes - av.startRes + 1) * av.charWidth;
571     if (imgWidth<1 || d.height<1)
572     {
573       return;
574     }
575     if (image == null || imgWidth != image.getWidth(this) || d.height != image.getHeight(this))
576     {
577       image = createImage(imgWidth, d.height);
578       gg = image.getGraphics();
579       gg.setFont(av.getFont());
580       fm = gg.getFontMetrics();
581       fastPaint = false;
582     }
583
584     if (fastPaint)
585     {
586       g.drawImage(image, 0, 0, this);
587       fastPaint = false;
588       return;
589     }
590
591     gg.setColor(Color.white);
592     gg.fillRect(0, 0, getSize().width, getSize().height);
593     drawComponent(gg, av.startRes, av.endRes + 1);
594
595     g.drawImage(image, 0, 0, this);
596   }
597
598   public void fastPaint(int horizontal)
599   {
600     if (horizontal == 0 || av.getAlignment().getAlignmentAnnotation() == null
601             || av.getAlignment().getAlignmentAnnotation().length < 1)
602     {
603       repaint();
604       return;
605     }
606
607     gg.copyArea(0, 0, imgWidth, getSize().height, -horizontal
608             * av.charWidth, 0);
609     int sr = av.startRes, er = av.endRes + 1, transX = 0;
610
611     if (horizontal > 0) // scrollbar pulled right, image to the left
612     {
613       transX = (er - sr - horizontal) * av.charWidth;
614       sr = er - horizontal;
615     }
616     else if (horizontal < 0)
617     {
618       er = sr - horizontal;
619     }
620
621     gg.translate(transX, 0);
622
623     drawComponent(gg, sr, er);
624
625     gg.translate(-transX, 0);
626
627     fastPaint = true;
628     repaint();
629   }
630
631   /**
632    * DOCUMENT ME!
633    *
634    * @param g
635    *          DOCUMENT ME!
636    * @param startRes
637    *          DOCUMENT ME!
638    * @param endRes
639    *          DOCUMENT ME!
640    */
641   public void drawComponent(Graphics g, int startRes, int endRes)
642   {
643     Font ofont = av.getFont();
644     g.setFont(ofont);
645
646     g.setColor(Color.white);
647     g.fillRect(0, 0, (endRes - startRes) * av.charWidth, getSize().height);
648
649     if (fm == null)
650     {
651       fm = g.getFontMetrics();
652     }
653
654     if ((av.getAlignment().getAlignmentAnnotation() == null)
655             || (av.getAlignment().getAlignmentAnnotation().length < 1))
656     {
657       g.setColor(Color.white);
658       g.fillRect(0, 0, getSize().width, getSize().height);
659       g.setColor(Color.black);
660       if (av.validCharWidth)
661       {
662         g.drawString("Alignment has no annotations", 20, 15);
663       }
664
665       return;
666     }
667     g.translate(0, -scrollOffset);
668     renderer.drawComponent(this, av, g, activeRow, startRes, endRes);
669     g.translate(0, +scrollOffset);
670   }
671
672   int scrollOffset = 0;
673
674   public void setScrollOffset(int value)
675   {
676     scrollOffset = value;
677     repaint();
678   }
679
680   @Override
681   public FontMetrics getFontMetrics()
682   {
683     return fm;
684   }
685
686   @Override
687   public Image getFadedImage()
688   {
689     return image;
690   }
691
692   @Override
693   public int getFadedImageWidth()
694   {
695     return imgWidth;
696   }
697 }