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