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