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