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