JAL-1834 removed quotes around truncated strings with ellipsis
[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().size() > 0
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 i = 0; i < av.getColumnSelection().size(); i++)
162       {
163         anot[av.getColumnSelection().columnAt(i)] = null;
164       }
165     }
166     else if (evt.getActionCommand().equals(LABEL))
167     {
168       label = enterLabel(label, "Enter Label");
169
170       if (label == null)
171       {
172         return;
173       }
174
175       if ((label.length() > 0) && !aa[activeRow].hasText)
176       {
177         aa[activeRow].hasText = true;
178       }
179
180       for (int i = 0; i < av.getColumnSelection().size(); i++)
181       {
182         int index = av.getColumnSelection().columnAt(i);
183
184         if (!av.getColumnSelection().isVisible(index))
185         {
186           continue;
187         }
188
189         if (anot[index] == null)
190         {
191           anot[index] = new Annotation(label, "", ' ', 0);
192         }
193
194         anot[index].displayCharacter = label;
195       }
196     }
197     else if (evt.getActionCommand().equals(COLOUR))
198     {
199       UserDefinedColours udc = new UserDefinedColours(this, Color.black,
200               ap.alignFrame);
201
202       Color col = udc.getColor();
203
204       for (int i = 0; i < av.getColumnSelection().size(); i++)
205       {
206         int index = av.getColumnSelection().columnAt(i);
207
208         if (!av.getColumnSelection().isVisible(index))
209         {
210           continue;
211         }
212
213         if (anot[index] == null)
214         {
215           anot[index] = new Annotation("", "", ' ', 0);
216         }
217
218         anot[index].colour = col;
219       }
220     }
221     else
222     // HELIX OR SHEET
223     {
224       char type = 0;
225       String symbol = "\u03B1";
226
227       if (evt.getActionCommand().equals(HELIX))
228       {
229         type = 'H';
230       }
231       else if (evt.getActionCommand().equals(SHEET))
232       {
233         type = 'E';
234         symbol = "\u03B2";
235       }
236
237       // Added by LML to color stems
238       else if (evt.getActionCommand().equals(STEM))
239       {
240         type = 'S';
241         symbol = "\u03C3";
242       }
243
244       if (!aa[activeRow].hasIcons)
245       {
246         aa[activeRow].hasIcons = true;
247       }
248
249       label = enterLabel(symbol, "Enter Label");
250
251       if (label == null)
252       {
253         return;
254       }
255
256       if ((label.length() > 0) && !aa[activeRow].hasText)
257       {
258         aa[activeRow].hasText = true;
259         if (evt.getActionCommand().equals(STEM))
260         {
261           aa[activeRow].showAllColLabels = true;
262         }
263       }
264
265       for (int i = 0; i < av.getColumnSelection().size(); i++)
266       {
267         int index = av.getColumnSelection().columnAt(i);
268
269         if (!av.getColumnSelection().isVisible(index))
270         {
271           continue;
272         }
273
274         if (anot[index] == null)
275         {
276           anot[index] = new Annotation(label, "", type, 0);
277         }
278
279         anot[index].secondaryStructure = type != 'S' ? type : label
280                 .length() == 0 ? ' ' : label.charAt(0);
281         anot[index].displayCharacter = label;
282       }
283     }
284
285     av.getAlignment().validateAnnotation(aa[activeRow]);
286
287     ap.alignmentChanged();
288     adjustPanelHeight();
289     repaint();
290
291     return;
292   }
293
294   String enterLabel(String text, String label)
295   {
296     EditNameDialog dialog = new EditNameDialog(text, null, label, null,
297             ap.alignFrame, "Enter Label", 400, 200, true);
298
299     if (dialog.accept)
300     {
301       return dialog.getName();
302     }
303     else
304     {
305       return null;
306     }
307   }
308
309   @Override
310   public void mousePressed(MouseEvent evt)
311   {
312     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
313     if (aa == null)
314     {
315       return;
316     }
317
318     int height = -scrollOffset;
319     activeRow = -1;
320
321     for (int i = 0; i < aa.length; i++)
322     {
323       if (aa[i].visible)
324       {
325         height += aa[i].height;
326       }
327
328       if (evt.getY() < height)
329       {
330         if (aa[i].editable)
331         {
332           activeRow = i;
333         }
334         else if (aa[i].graph > 0)
335         {
336           // Stretch Graph
337           graphStretch = i;
338           graphStretchY = evt.getY();
339         }
340
341         break;
342       }
343     }
344
345     if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK
346             && activeRow != -1)
347     {
348       if (av.getColumnSelection() == null)
349       {
350         return;
351       }
352
353       PopupMenu pop = new PopupMenu(
354               MessageManager.getString("label.structure_type"));
355       MenuItem item;
356       /*
357        * Just display the needed structure options
358        */
359       if (av.getAlignment().isNucleotide() == true)
360       {
361         item = new MenuItem(STEM);
362         item.addActionListener(this);
363         pop.add(item);
364       }
365       else
366       {
367         item = new MenuItem(HELIX);
368         item.addActionListener(this);
369         pop.add(item);
370         item = new MenuItem(SHEET);
371         item.addActionListener(this);
372         pop.add(item);
373       }
374       item = new MenuItem(LABEL);
375       item.addActionListener(this);
376       pop.add(item);
377       item = new MenuItem(COLOUR);
378       item.addActionListener(this);
379       pop.add(item);
380       item = new MenuItem(REMOVE);
381       item.addActionListener(this);
382       pop.add(item);
383       ap.alignFrame.add(pop);
384       pop.show(this, evt.getX(), evt.getY());
385
386       return;
387     }
388
389     ap.scalePanel.mousePressed(evt);
390   }
391
392   @Override
393   public void mouseReleased(MouseEvent evt)
394   {
395     graphStretch = -1;
396     graphStretchY = -1;
397     mouseDragging = false;
398     if (needValidating)
399     {
400       ap.validate();
401       needValidating = false;
402     }
403     ap.scalePanel.mouseReleased(evt);
404   }
405
406   @Override
407   public void mouseClicked(MouseEvent evt)
408   {
409   }
410
411   boolean needValidating = false;
412
413   @Override
414   public void mouseDragged(MouseEvent evt)
415   {
416     if (graphStretch > -1)
417     {
418       av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight += graphStretchY
419               - evt.getY();
420       if (av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight < 0)
421       {
422         av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight = 0;
423       }
424       graphStretchY = evt.getY();
425       av.calcPanelHeight();
426       needValidating = true;
427       ap.paintAlignment(true);
428     }
429     else
430     {
431       ap.scalePanel.mouseDragged(evt);
432     }
433   }
434
435   @Override
436   public void mouseMoved(MouseEvent evt)
437   {
438     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
439     if (aa == null)
440     {
441       return;
442     }
443
444     int row = -1;
445     int height = -scrollOffset;
446     for (int i = 0; i < aa.length; i++)
447     {
448
449       if (aa[i].visible)
450       {
451         height += aa[i].height;
452       }
453
454       if (evt.getY() < height)
455       {
456         row = i;
457         break;
458       }
459     }
460
461     int res = evt.getX() / av.getCharWidth() + av.getStartRes();
462
463     if (av.hasHiddenColumns())
464     {
465       res = av.getColumnSelection().adjustForHiddenColumns(res);
466     }
467
468     if (row > -1 && res < aa[row].annotations.length
469             && aa[row].annotations[res] != null)
470     {
471       StringBuffer text = new StringBuffer("Sequence position " + (res + 1));
472       if (aa[row].annotations[res].description != null)
473       {
474         text.append("  " + aa[row].annotations[res].description);
475       }
476       ap.alignFrame.statusBar.setText(text.toString());
477     }
478   }
479
480   @Override
481   public void mouseEntered(MouseEvent evt)
482   {
483     ap.scalePanel.mouseEntered(evt);
484   }
485
486   @Override
487   public void mouseExited(MouseEvent evt)
488   {
489     ap.scalePanel.mouseExited(evt);
490   }
491
492   public int adjustPanelHeight()
493   {
494     return adjustPanelHeight(true);
495   }
496
497   public int adjustPanelHeight(boolean repaint)
498   {
499     int height = av.calcPanelHeight();
500     this.setSize(new Dimension(getSize().width, height));
501     if (repaint)
502     {
503       repaint();
504     }
505     return height;
506   }
507
508   /**
509    * calculate the height for visible annotation, revalidating bounds where
510    * necessary ABSTRACT GUI METHOD
511    * 
512    * @return total height of annotation
513    */
514
515   public void addEditableColumn(int i)
516   {
517     if (activeRow == -1)
518     {
519       AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
520       if (aa == null)
521       {
522         return;
523       }
524
525       for (int j = 0; j < aa.length; j++)
526       {
527         if (aa[j].editable)
528         {
529           activeRow = j;
530           break;
531         }
532       }
533     }
534   }
535
536   @Override
537   public void update(Graphics g)
538   {
539     paint(g);
540   }
541
542   @Override
543   public void paint(Graphics g)
544   {
545     Dimension d = getSize();
546     imgWidth = d.width;
547     // (av.endRes - av.startRes + 1) * av.charWidth;
548     if (imgWidth < 1 || d.height < 1)
549     {
550       return;
551     }
552     if (image == null || imgWidth != image.getWidth(this)
553             || d.height != image.getHeight(this))
554     {
555       image = createImage(imgWidth, d.height);
556       gg = image.getGraphics();
557       gg.setFont(av.getFont());
558       fm = gg.getFontMetrics();
559       fastPaint = false;
560     }
561
562     if (fastPaint)
563     {
564       g.drawImage(image, 0, 0, this);
565       fastPaint = false;
566       return;
567     }
568
569     gg.setColor(Color.white);
570     gg.fillRect(0, 0, getSize().width, getSize().height);
571     drawComponent(gg, av.startRes, av.endRes + 1);
572
573     g.drawImage(image, 0, 0, this);
574   }
575
576   public void fastPaint(int horizontal)
577   {
578     if (horizontal == 0
579             || av.getAlignment().getAlignmentAnnotation() == null
580             || av.getAlignment().getAlignmentAnnotation().length < 1)
581     {
582       repaint();
583       return;
584     }
585
586     gg.copyArea(0, 0, imgWidth, getSize().height,
587             -horizontal * av.getCharWidth(), 0);
588     int sr = av.startRes, er = av.endRes + 1, transX = 0;
589
590     if (horizontal > 0) // scrollbar pulled right, image to the left
591     {
592       transX = (er - sr - horizontal) * av.getCharWidth();
593       sr = er - horizontal;
594     }
595     else if (horizontal < 0)
596     {
597       er = sr - horizontal;
598     }
599
600     gg.translate(transX, 0);
601
602     drawComponent(gg, sr, er);
603
604     gg.translate(-transX, 0);
605
606     fastPaint = true;
607     repaint();
608   }
609
610   /**
611    * DOCUMENT ME!
612    * 
613    * @param g
614    *          DOCUMENT ME!
615    * @param startRes
616    *          DOCUMENT ME!
617    * @param endRes
618    *          DOCUMENT ME!
619    */
620   public void drawComponent(Graphics g, int startRes, int endRes)
621   {
622     Font ofont = av.getFont();
623     g.setFont(ofont);
624
625     g.setColor(Color.white);
626     g.fillRect(0, 0, (endRes - startRes) * av.getCharWidth(),
627             getSize().height);
628
629     if (fm == null)
630     {
631       fm = g.getFontMetrics();
632     }
633
634     if ((av.getAlignment().getAlignmentAnnotation() == null)
635             || (av.getAlignment().getAlignmentAnnotation().length < 1))
636     {
637       g.setColor(Color.white);
638       g.fillRect(0, 0, getSize().width, getSize().height);
639       g.setColor(Color.black);
640       if (av.validCharWidth)
641       {
642         g.drawString(MessageManager
643                 .getString("label.alignment_has_no_annotations"), 20, 15);
644       }
645
646       return;
647     }
648     g.translate(0, -scrollOffset);
649     renderer.drawComponent(this, av, g, activeRow, startRes, endRes);
650     g.translate(0, +scrollOffset);
651   }
652
653   int scrollOffset = 0;
654
655   public void setScrollOffset(int value, boolean repaint)
656   {
657     scrollOffset = value;
658     if (repaint)
659     {
660       repaint();
661     }
662   }
663
664   @Override
665   public FontMetrics getFontMetrics()
666   {
667     return fm;
668   }
669
670   @Override
671   public Image getFadedImage()
672   {
673     return image;
674   }
675
676   @Override
677   public int getFadedImageWidth()
678   {
679     return imgWidth;
680   }
681
682   private int[] bounds = new int[2];
683
684   @Override
685   public int[] getVisibleVRange()
686   {
687     if (ap != null && ap.alabels != null)
688     {
689       int sOffset = -ap.alabels.scrollOffset;
690       int visHeight = sOffset + ap.annotationPanelHolder.getHeight();
691       bounds[0] = sOffset;
692       bounds[1] = visHeight;
693       return bounds;
694     }
695     else
696     {
697       return null;
698     }
699   }
700 }