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