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