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