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