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