JAL-1432 updated copyright notices
[jalview.git] / src / jalview / appletgui / AnnotationLabels.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.0b1)
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 of the License, or (at your option) any later version.
10  *  
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  * The Jalview Authors are detailed in the 'AUTHORS' file.
18  */
19 package jalview.appletgui;
20
21 import java.util.*;
22
23 import java.awt.*;
24 import java.awt.event.*;
25
26 import jalview.datamodel.*;
27 import jalview.util.ParseHtmlBodyAndLinks;
28
29 public class AnnotationLabels extends Panel implements ActionListener,
30         MouseListener, MouseMotionListener
31 {
32   Image image;
33
34   boolean active = false;
35
36   AlignmentPanel ap;
37
38   AlignViewport av;
39
40   boolean resizing = false;
41
42   int oldY, mouseX;
43
44   static String ADDNEW = "Add New Row";
45
46   static String EDITNAME = "Edit Label/Description";
47
48   static String HIDE = "Hide This Row";
49
50   static String SHOWALL = "Show All Hidden Rows";
51
52   static String OUTPUT_TEXT = "Show Values In Textbox";
53
54   static String COPYCONS_SEQ = "Copy Consensus Sequence";
55
56   int scrollOffset = 0;
57
58   int selectedRow = -1;
59
60   Tooltip tooltip;
61
62   private boolean hasHiddenRows;
63
64   public AnnotationLabels(AlignmentPanel ap)
65   {
66     this.ap = ap;
67     this.av = ap.av;
68     setLayout(null);
69
70     /**
71      * this retrieves the adjustable height glyph from resources. we don't use
72      * it at the moment. java.net.URL url =
73      * getClass().getResource("/images/idwidth.gif"); Image temp = null;
74      * 
75      * if (url != null) { temp =
76      * java.awt.Toolkit.getDefaultToolkit().createImage(url); }
77      * 
78      * try { MediaTracker mt = new MediaTracker(this); mt.addImage(temp, 0);
79      * mt.waitForID(0); } catch (Exception ex) { }
80      * 
81      * BufferedImage bi = new BufferedImage(temp.getHeight(this),
82      * temp.getWidth(this), BufferedImage.TYPE_INT_RGB); Graphics2D g =
83      * (Graphics2D) bi.getGraphics(); g.rotate(Math.toRadians(90));
84      * g.drawImage(temp, 0, -bi.getWidth(this), this); image = (Image) bi;
85      */
86     addMouseListener(this);
87     addMouseMotionListener(this);
88   }
89
90   public AnnotationLabels(AlignViewport av)
91   {
92     this.av = av;
93   }
94
95   public void setScrollOffset(int y)
96   {
97     scrollOffset = y;
98     repaint();
99   }
100
101   /**
102    * 
103    * @param y
104    * @return -2 if no rows are visible at all, -1 if no visible rows were
105    *         selected
106    */
107   int getSelectedRow(int y)
108   {
109     int row = -2;
110     AlignmentAnnotation[] aa = ap.av.getAlignment()
111             .getAlignmentAnnotation();
112
113     if (aa == null)
114     {
115       return row;
116     }
117     int height = 0;
118     for (int i = 0; i < aa.length; i++)
119     {
120       row = -1;
121       if (!aa[i].visible)
122       {
123         continue;
124       }
125       height += aa[i].height;
126       if (y < height)
127       {
128         row = i;
129         break;
130       }
131     }
132
133     return row;
134   }
135
136   public void actionPerformed(ActionEvent evt)
137   {
138     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
139
140     if (evt.getActionCommand().equals(ADDNEW))
141     {
142       AlignmentAnnotation newAnnotation = new AlignmentAnnotation("", null,
143               new Annotation[ap.av.getAlignment().getWidth()]);
144
145       if (!editLabelDescription(newAnnotation))
146       {
147         return;
148       }
149
150       ap.av.getAlignment().addAnnotation(newAnnotation);
151       ap.av.getAlignment().setAnnotationIndex(newAnnotation, 0);
152     }
153     else if (evt.getActionCommand().equals(EDITNAME))
154     {
155       editLabelDescription(aa[selectedRow]);
156     }
157     else if (evt.getActionCommand().equals(HIDE))
158     {
159       aa[selectedRow].visible = false;
160     }
161     else if (evt.getActionCommand().equals(SHOWALL))
162     {
163       for (int i = 0; i < aa.length; i++)
164       {
165         aa[i].visible = (aa[i].annotations == null) ? false : true;
166       }
167     }
168     else if (evt.getActionCommand().equals(OUTPUT_TEXT))
169     {
170       CutAndPasteTransfer cap = new CutAndPasteTransfer(false,
171               ap.alignFrame);
172       Frame frame = new Frame();
173       frame.add(cap);
174       jalview.bin.JalviewLite.addFrame(frame, ap.alignFrame.getTitle()
175               + " - " + aa[selectedRow].label, 500, 100);
176       cap.setText(aa[selectedRow].toString());
177     }
178     else if (evt.getActionCommand().equals(COPYCONS_SEQ))
179     {
180       SequenceI cons = av.getConsensusSeq();
181       if (cons != null)
182       {
183         copy_annotseqtoclipboard(cons);
184       }
185
186     }
187     ap.annotationPanel.adjustPanelHeight();
188     setSize(getSize().width, ap.annotationPanel.getSize().height);
189     ap.validate();
190     ap.paintAlignment(true);
191   }
192
193   boolean editLabelDescription(AlignmentAnnotation annotation)
194   {
195     Checkbox padGaps = new Checkbox("Fill Empty Gaps With \""
196             + ap.av.getGapCharacter() + "\"", annotation.padGaps);
197
198     EditNameDialog dialog = new EditNameDialog(annotation.label,
199             annotation.description, "      Annotation Label",
200             "Annotation Description", ap.alignFrame,
201             "Edit Annotation Name / Description", 500, 180, false);
202
203     Panel empty = new Panel(new FlowLayout());
204     empty.add(padGaps);
205     dialog.add(empty);
206     dialog.pack();
207
208     dialog.setVisible(true);
209
210     if (dialog.accept)
211     {
212       annotation.label = dialog.getName();
213       annotation.description = dialog.getDescription();
214       annotation.setPadGaps(padGaps.getState(), av.getGapCharacter());
215       repaint();
216       return true;
217     }
218     else
219       return false;
220
221   }
222
223   boolean resizePanel = false;
224
225   public void mouseMoved(MouseEvent evt)
226   {
227     resizePanel = evt.getY() < 10 && evt.getX() < 14;
228     int row = getSelectedRow(evt.getY() + scrollOffset);
229
230     if (row > -1)
231     {
232       ParseHtmlBodyAndLinks phb = new ParseHtmlBodyAndLinks(
233               av.getAlignment().getAlignmentAnnotation()[row]
234                       .getDescription(true),
235               true, "\n");
236       if (tooltip == null)
237       {
238         tooltip = new Tooltip(phb.getNonHtmlContent(), this);
239       }
240       else
241       {
242         tooltip.setTip(phb.getNonHtmlContent());
243       }
244     }
245     else if (tooltip != null)
246     {
247       tooltip.setTip("");
248     }
249   }
250
251   /**
252    * curent drag position
253    */
254   MouseEvent dragEvent = null;
255
256   /**
257    * flag to indicate drag events should be ignored
258    */
259   private boolean dragCancelled = false;
260
261   /**
262    * clear any drag events in progress
263    */
264   public void cancelDrag()
265   {
266     dragEvent = null;
267     dragCancelled = true;
268   }
269
270   public void mouseDragged(MouseEvent evt)
271   {
272     if (dragCancelled)
273     {
274       return;
275     }
276     ;
277     dragEvent = evt;
278
279     if (resizePanel)
280     {
281       Dimension d = ap.annotationPanelHolder.getSize(), e = ap.annotationSpaceFillerHolder
282               .getSize(), f = ap.seqPanelHolder.getSize();
283       int dif = evt.getY() - oldY;
284
285       dif /= ap.av.charHeight;
286       dif *= ap.av.charHeight;
287
288       if ((d.height - dif) > 20 && (f.height + dif) > 20)
289       {
290         ap.annotationPanel.setSize(d.width, d.height - dif);
291         setSize(new Dimension(e.width, d.height - dif));
292         ap.annotationSpaceFillerHolder.setSize(new Dimension(e.width,
293                 d.height - dif));
294         ap.annotationPanelHolder.setSize(new Dimension(d.width, d.height
295                 - dif));
296         ap.apvscroll.setValues(ap.apvscroll.getValue(), d.height - dif, 0,
297                 av.calcPanelHeight());
298         f.height += dif;
299         ap.seqPanelHolder.setPreferredSize(f);
300         ap.setScrollValues(av.getStartRes(), av.getStartSeq());
301         ap.validate();
302         // ap.paintAlignment(true);
303         ap.addNotify();
304       }
305
306     }
307     else
308     {
309       int diff;
310       if ((diff = 6 - evt.getY()) > 0)
311       {
312         // nudge scroll up
313         ap.apvscroll.setValue(ap.apvscroll.getValue() - diff);
314         ap.adjustmentValueChanged(null);
315
316       }
317       else if ((0 < (diff = 6
318               - ap.annotationSpaceFillerHolder.getSize().height
319               + evt.getY())))
320       {
321         // nudge scroll down
322         ap.apvscroll.setValue(ap.apvscroll.getValue() + diff);
323         ap.adjustmentValueChanged(null);
324       }
325       repaint();
326     }
327   }
328
329   public void mouseClicked(MouseEvent evt)
330   {
331   }
332
333   public void mouseReleased(MouseEvent evt)
334   {
335     if (!resizePanel && !dragCancelled)
336     {
337       int start = selectedRow;
338
339       int end = getSelectedRow(evt.getY() + scrollOffset);
340
341       if (start > -1 && start != end)
342       {
343         // Swap these annotations
344         AlignmentAnnotation startAA = ap.av.getAlignment()
345                 .getAlignmentAnnotation()[start];
346         if (end == -1)
347         {
348           end = ap.av.getAlignment().getAlignmentAnnotation().length - 1;
349         }
350         AlignmentAnnotation endAA = ap.av.getAlignment()
351                 .getAlignmentAnnotation()[end];
352
353         ap.av.getAlignment().getAlignmentAnnotation()[end] = startAA;
354         ap.av.getAlignment().getAlignmentAnnotation()[start] = endAA;
355       }
356     }
357     resizePanel = false;
358     dragEvent = null;
359     dragCancelled = false;
360     repaint();
361     ap.annotationPanel.repaint();
362   }
363
364   public void mouseEntered(MouseEvent evt)
365   {
366     if (evt.getY() < 10 && evt.getX() < 14)
367     {
368       resizePanel = true;
369       repaint();
370     }
371   }
372
373   public void mouseExited(MouseEvent evt)
374   {
375     dragCancelled = false;
376
377     if (dragEvent == null)
378     {
379       resizePanel = false;
380     }
381     else
382     {
383       if (!resizePanel)
384       {
385         dragEvent = null;
386       }
387     }
388     repaint();
389   }
390
391   public void mousePressed(MouseEvent evt)
392   {
393     oldY = evt.getY();
394     if (resizePanel)
395     {
396       return;
397     }
398     dragCancelled = false;
399     // todo: move below to mouseClicked ?
400     selectedRow = getSelectedRow(evt.getY() + scrollOffset);
401
402     AlignmentAnnotation[] aa = ap.av.getAlignment()
403             .getAlignmentAnnotation();
404
405     // DETECT RIGHT MOUSE BUTTON IN AWT
406     if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
407     {
408
409       PopupMenu popup = new PopupMenu("Annotations");
410
411       MenuItem item = new MenuItem(ADDNEW);
412       item.addActionListener(this);
413       popup.add(item);
414       if (selectedRow < 0)
415       {
416         // this never happens at moment: - see comment on JAL-563
417         if (hasHiddenRows)
418         {
419           item = new MenuItem(SHOWALL);
420           item.addActionListener(this);
421           popup.add(item);
422         }
423         this.add(popup);
424         popup.show(this, evt.getX(), evt.getY());
425         return;
426       }
427       // add the rest if there are actually rows to show
428       item = new MenuItem(EDITNAME);
429       item.addActionListener(this);
430       popup.add(item);
431       item = new MenuItem(HIDE);
432       item.addActionListener(this);
433       popup.add(item);
434       if (hasHiddenRows)
435       {
436         item = new MenuItem(SHOWALL);
437         item.addActionListener(this);
438         popup.add(item);
439       }
440       this.add(popup);
441       item = new MenuItem(OUTPUT_TEXT);
442       item.addActionListener(this);
443       popup.add(item);
444       if (selectedRow < aa.length)
445       {
446         if (aa[selectedRow].autoCalculated)
447         {
448           if (aa[selectedRow].label.indexOf("Consensus") > -1)
449           {
450             popup.addSeparator();
451             final CheckboxMenuItem cbmi = new CheckboxMenuItem(
452                     "Ignore Gaps In Consensus",
453                     (aa[selectedRow].groupRef != null) ? aa[selectedRow].groupRef
454                             .getIgnoreGapsConsensus() : ap.av
455                             .getIgnoreGapsConsensus());
456             final AlignmentAnnotation aaa = aa[selectedRow];
457             cbmi.addItemListener(new ItemListener()
458             {
459               public void itemStateChanged(ItemEvent e)
460               {
461                 if (aaa.groupRef != null)
462                 {
463                   // TODO: pass on reference to ap so the view can be updated.
464                   aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState());
465                 }
466                 else
467                 {
468                   ap.av.setIgnoreGapsConsensus(cbmi.getState());
469                 }
470                 ap.paintAlignment(true);
471               }
472             });
473             popup.add(cbmi);
474             if (aaa.groupRef != null)
475             {
476               final CheckboxMenuItem chist = new CheckboxMenuItem(
477                       "Show Group Histogram",
478                       aa[selectedRow].groupRef.isShowConsensusHistogram());
479               chist.addItemListener(new ItemListener()
480               {
481                 public void itemStateChanged(ItemEvent e)
482                 {
483                   // TODO: pass on reference
484                   // to ap
485                   // so the
486                   // view
487                   // can be
488                   // updated.
489                   aaa.groupRef.setShowConsensusHistogram(chist.getState());
490                   ap.repaint();
491                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
492                 }
493               });
494               popup.add(chist);
495               final CheckboxMenuItem cprofl = new CheckboxMenuItem(
496                       "Show Group Logo",
497                       aa[selectedRow].groupRef.isShowSequenceLogo());
498               cprofl.addItemListener(new ItemListener()
499               {
500                 public void itemStateChanged(ItemEvent e)
501                 {
502                   // TODO: pass on reference
503                   // to ap
504                   // so the
505                   // view
506                   // can be
507                   // updated.
508                   aaa.groupRef.setshowSequenceLogo(cprofl.getState());
509                   ap.repaint();
510                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
511                 }
512               });
513
514               popup.add(cprofl);
515               final CheckboxMenuItem cprofn = new CheckboxMenuItem(
516                       "Normalise Group Logo",
517                       aa[selectedRow].groupRef.isNormaliseSequenceLogo());
518               cprofn.addItemListener(new ItemListener()
519               {
520                 public void itemStateChanged(ItemEvent e)
521                 {
522                   // TODO: pass on reference
523                   // to ap
524                   // so the
525                   // view
526                   // can be
527                   // updated.
528                   aaa.groupRef.setshowSequenceLogo(true);
529                   aaa.groupRef.setNormaliseSequenceLogo(cprofn.getState());
530                   ap.repaint();
531                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
532                 }
533               });
534               popup.add(cprofn);
535             }
536             else
537             {
538               final CheckboxMenuItem chist = new CheckboxMenuItem(
539                       "Show Histogram", av.isShowConsensusHistogram());
540               chist.addItemListener(new ItemListener()
541               {
542                 public void itemStateChanged(ItemEvent e)
543                 {
544                   // TODO: pass on reference
545                   // to ap
546                   // so the
547                   // view
548                   // can be
549                   // updated.
550                   av.setShowConsensusHistogram(chist.getState());
551                   ap.alignFrame.showConsensusHistogram.setState(chist
552                           .getState()); // TODO: implement
553                                         // ap.updateGUI()/alignFrame.updateGUI
554                                         // for applet
555                   ap.repaint();
556                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
557                 }
558               });
559               popup.add(chist);
560               final CheckboxMenuItem cprof = new CheckboxMenuItem(
561                       "Show Logo", av.isShowSequenceLogo());
562               cprof.addItemListener(new ItemListener()
563               {
564                 public void itemStateChanged(ItemEvent e)
565                 {
566                   // TODO: pass on reference
567                   // to ap
568                   // so the
569                   // view
570                   // can be
571                   // updated.
572                   av.setShowSequenceLogo(cprof.getState());
573                   ap.alignFrame.showSequenceLogo.setState(cprof.getState()); // TODO:
574                                                                              // implement
575                                                                              // ap.updateGUI()/alignFrame.updateGUI
576                                                                              // for
577                                                                              // applet
578                   ap.repaint();
579                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
580                 }
581               });
582               popup.add(cprof);
583               final CheckboxMenuItem cprofn = new CheckboxMenuItem(
584                       "Normalise Logo", av.isNormaliseSequenceLogo());
585               cprofn.addItemListener(new ItemListener()
586               {
587                 public void itemStateChanged(ItemEvent e)
588                 {
589                   // TODO: pass on reference
590                   // to ap
591                   // so the
592                   // view
593                   // can be
594                   // updated.
595                   av.setShowSequenceLogo(true);
596                   ap.alignFrame.normSequenceLogo.setState(cprofn.getState()); // TODO:
597                                                                               // implement
598                                                                               // ap.updateGUI()/alignFrame.updateGUI
599                                                                               // for
600                                                                               // applet
601                   av.setNormaliseSequenceLogo(cprofn.getState());
602                   ap.repaint();
603                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
604                 }
605               });
606               popup.add(cprofn);
607             }
608
609             item = new MenuItem(COPYCONS_SEQ);
610             item.addActionListener(this);
611             popup.add(item);
612           }
613         }
614       }
615       popup.show(this, evt.getX(), evt.getY());
616     }
617     else
618     {
619       // selection action.
620       if (selectedRow > -1 && selectedRow < aa.length)
621       {
622         if (aa[selectedRow].groupRef != null)
623         {
624           if (evt.getClickCount() >= 2)
625           {
626             // todo: make the ap scroll to the selection - not necessary, first
627             // click highlights/scrolls, second selects
628             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
629             ap.av.setSelectionGroup(// new SequenceGroup(
630             aa[selectedRow].groupRef); // );
631             ap.av.sendSelection();
632             ap.paintAlignment(false);
633             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
634           }
635           else
636           {
637             ap.seqPanel.ap.idPanel
638                     .highlightSearchResults(aa[selectedRow].groupRef
639                             .getSequences(null));
640           }
641           return;
642         }
643         else if (aa[selectedRow].sequenceRef != null)
644         {
645           Vector sr = new Vector();
646           sr.addElement(aa[selectedRow].sequenceRef);
647           if (evt.getClickCount() == 1)
648           {
649             ap.seqPanel.ap.idPanel.highlightSearchResults(sr);
650           }
651           else if (evt.getClickCount() >= 2)
652           {
653             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
654             SequenceGroup sg = new SequenceGroup();
655             sg.addSequence(aa[selectedRow].sequenceRef, false);
656             ap.av.setSelectionGroup(sg);
657             ap.paintAlignment(false);
658             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
659             ap.av.sendSelection();
660           }
661
662         }
663       }
664
665     }
666   }
667
668   /**
669    * DOCUMENT ME!
670    * 
671    * @param e
672    *          DOCUMENT ME!
673    */
674   protected void copy_annotseqtoclipboard(SequenceI sq)
675   {
676     if (sq == null || sq.getLength() < 1)
677     {
678       return;
679     }
680     jalview.appletgui.AlignFrame.copiedSequences = new StringBuffer();
681     jalview.appletgui.AlignFrame.copiedSequences.append(sq.getName() + "\t"
682             + sq.getStart() + "\t" + sq.getEnd() + "\t"
683             + sq.getSequenceAsString() + "\n");
684     if (av.hasHiddenColumns())
685     {
686       jalview.appletgui.AlignFrame.copiedHiddenColumns = new Vector();
687       for (int i = 0; i < av.getColumnSelection().getHiddenColumns().size(); i++)
688       {
689         int[] region = (int[]) av.getColumnSelection().getHiddenColumns()
690                 .elementAt(i);
691
692         jalview.appletgui.AlignFrame.copiedHiddenColumns
693                 .addElement(new int[]
694                 { region[0], region[1] });
695       }
696     }
697   }
698
699   public void update(Graphics g)
700   {
701     paint(g);
702   }
703
704   public void paint(Graphics g)
705   {
706     int w = getSize().width;
707     int h = getSize().height;
708     if (image == null || w != image.getWidth(this)
709             || h != image.getHeight(this))
710     {
711       image = createImage(w, ap.annotationPanel.getSize().height);
712     }
713
714     drawComponent(image.getGraphics(), w);
715     g.drawImage(image, 0, 0, this);
716   }
717
718   public void drawComponent(Graphics g, int width)
719   {
720     g.setFont(av.getFont());
721     FontMetrics fm = g.getFontMetrics(av.getFont());
722     g.setColor(Color.white);
723     g.fillRect(0, 0, getSize().width, getSize().height);
724
725     g.translate(0, -scrollOffset);
726     g.setColor(Color.black);
727
728     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
729     int y = 0, fy = g.getFont().getSize();
730     int x = 0, offset;
731
732     if (aa != null)
733     {
734       hasHiddenRows = false;
735       for (int i = 0; i < aa.length; i++)
736       {
737         if (!aa[i].visible)
738         {
739           hasHiddenRows = true;
740           continue;
741         }
742
743         x = width - fm.stringWidth(aa[i].label) - 3;
744
745         y += aa[i].height;
746         offset = -(aa[i].height - fy) / 2;
747
748         g.drawString(aa[i].label, x, y + offset);
749       }
750     }
751     g.translate(0, +scrollOffset);
752     if (resizePanel)
753     {
754       g.setColor(Color.red);
755       g.setPaintMode();
756       g.drawLine(2, 8, 5, 2);
757       g.drawLine(5, 2, 8, 8);
758     }
759     else if (!dragCancelled && dragEvent != null && aa != null)
760     {
761       g.setColor(Color.lightGray);
762       g.drawString(aa[selectedRow].label, dragEvent.getX(),
763               dragEvent.getY());
764     }
765
766     if (!av.wrapAlignment && ((aa == null) || (aa.length < 1)))
767     {
768       g.setColor(Color.black);
769       g.drawString("Right click", 2, 8);
770       g.drawString("to add annotation", 2, 18);
771     }
772   }
773 }