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