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