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