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