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