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