a286e0685004b7fc3885a83da4fedbf3239d7761
[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(
416               MessageManager.getString("label.annotations"));
417
418       MenuItem item = new MenuItem(ADDNEW);
419       item.addActionListener(this);
420       popup.add(item);
421       if (selectedRow < 0)
422       {
423         // this never happens at moment: - see comment on JAL-563
424         if (hasHiddenRows)
425         {
426           item = new MenuItem(SHOWALL);
427           item.addActionListener(this);
428           popup.add(item);
429         }
430         this.add(popup);
431         popup.show(this, evt.getX(), evt.getY());
432         return;
433       }
434       // add the rest if there are actually rows to show
435       item = new MenuItem(EDITNAME);
436       item.addActionListener(this);
437       popup.add(item);
438       item = new MenuItem(HIDE);
439       item.addActionListener(this);
440       popup.add(item);
441       if (hasHiddenRows)
442       {
443         item = new MenuItem(SHOWALL);
444         item.addActionListener(this);
445         popup.add(item);
446       }
447       this.add(popup);
448       item = new MenuItem(OUTPUT_TEXT);
449       item.addActionListener(this);
450       popup.add(item);
451       if (selectedRow < aa.length)
452       {
453         if (aa[selectedRow].autoCalculated)
454         {
455           if (aa[selectedRow].label.indexOf("Consensus") > -1)
456           {
457             popup.addSeparator();
458             final CheckboxMenuItem cbmi = new CheckboxMenuItem(
459                     "Ignore Gaps In Consensus",
460                     (aa[selectedRow].groupRef != null) ? aa[selectedRow].groupRef
461                             .getIgnoreGapsConsensus() : ap.av
462                             .getIgnoreGapsConsensus());
463             final AlignmentAnnotation aaa = aa[selectedRow];
464             cbmi.addItemListener(new ItemListener()
465             {
466               public void itemStateChanged(ItemEvent e)
467               {
468                 if (aaa.groupRef != null)
469                 {
470                   // TODO: pass on reference to ap so the view can be updated.
471                   aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState());
472                 }
473                 else
474                 {
475                   ap.av.setIgnoreGapsConsensus(cbmi.getState());
476                 }
477                 ap.paintAlignment(true);
478               }
479             });
480             popup.add(cbmi);
481             if (aaa.groupRef != null)
482             {
483               final CheckboxMenuItem chist = new CheckboxMenuItem(
484                       "Show Group Histogram",
485                       aa[selectedRow].groupRef.isShowConsensusHistogram());
486               chist.addItemListener(new ItemListener()
487               {
488                 public void itemStateChanged(ItemEvent e)
489                 {
490                   // TODO: pass on reference
491                   // to ap
492                   // so the
493                   // view
494                   // can be
495                   // updated.
496                   aaa.groupRef.setShowConsensusHistogram(chist.getState());
497                   ap.repaint();
498                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
499                 }
500               });
501               popup.add(chist);
502               final CheckboxMenuItem cprofl = new CheckboxMenuItem(
503                       "Show Group Logo",
504                       aa[selectedRow].groupRef.isShowSequenceLogo());
505               cprofl.addItemListener(new ItemListener()
506               {
507                 public void itemStateChanged(ItemEvent e)
508                 {
509                   // TODO: pass on reference
510                   // to ap
511                   // so the
512                   // view
513                   // can be
514                   // updated.
515                   aaa.groupRef.setshowSequenceLogo(cprofl.getState());
516                   ap.repaint();
517                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
518                 }
519               });
520
521               popup.add(cprofl);
522               final CheckboxMenuItem cprofn = new CheckboxMenuItem(
523                       "Normalise Group Logo",
524                       aa[selectedRow].groupRef.isNormaliseSequenceLogo());
525               cprofn.addItemListener(new ItemListener()
526               {
527                 public void itemStateChanged(ItemEvent e)
528                 {
529                   // TODO: pass on reference
530                   // to ap
531                   // so the
532                   // view
533                   // can be
534                   // updated.
535                   aaa.groupRef.setshowSequenceLogo(true);
536                   aaa.groupRef.setNormaliseSequenceLogo(cprofn.getState());
537                   ap.repaint();
538                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
539                 }
540               });
541               popup.add(cprofn);
542             }
543             else
544             {
545               final CheckboxMenuItem chist = new CheckboxMenuItem(
546                       "Show Histogram", av.isShowConsensusHistogram());
547               chist.addItemListener(new ItemListener()
548               {
549                 public void itemStateChanged(ItemEvent e)
550                 {
551                   // TODO: pass on reference
552                   // to ap
553                   // so the
554                   // view
555                   // can be
556                   // updated.
557                   av.setShowConsensusHistogram(chist.getState());
558                   ap.alignFrame.showConsensusHistogram.setState(chist
559                           .getState()); // TODO: implement
560                                         // ap.updateGUI()/alignFrame.updateGUI
561                                         // for applet
562                   ap.repaint();
563                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
564                 }
565               });
566               popup.add(chist);
567               final CheckboxMenuItem cprof = new CheckboxMenuItem(
568                       "Show Logo", av.isShowSequenceLogo());
569               cprof.addItemListener(new ItemListener()
570               {
571                 public void itemStateChanged(ItemEvent e)
572                 {
573                   // TODO: pass on reference
574                   // to ap
575                   // so the
576                   // view
577                   // can be
578                   // updated.
579                   av.setShowSequenceLogo(cprof.getState());
580                   ap.alignFrame.showSequenceLogo.setState(cprof.getState()); // TODO:
581                                                                              // implement
582                                                                              // ap.updateGUI()/alignFrame.updateGUI
583                                                                              // for
584                                                                              // applet
585                   ap.repaint();
586                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
587                 }
588               });
589               popup.add(cprof);
590               final CheckboxMenuItem cprofn = new CheckboxMenuItem(
591                       "Normalise Logo", av.isNormaliseSequenceLogo());
592               cprofn.addItemListener(new ItemListener()
593               {
594                 public void itemStateChanged(ItemEvent e)
595                 {
596                   // TODO: pass on reference
597                   // to ap
598                   // so the
599                   // view
600                   // can be
601                   // updated.
602                   av.setShowSequenceLogo(true);
603                   ap.alignFrame.normSequenceLogo.setState(cprofn.getState()); // TODO:
604                                                                               // implement
605                                                                               // ap.updateGUI()/alignFrame.updateGUI
606                                                                               // for
607                                                                               // applet
608                   av.setNormaliseSequenceLogo(cprofn.getState());
609                   ap.repaint();
610                   // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
611                 }
612               });
613               popup.add(cprofn);
614             }
615
616             item = new MenuItem(COPYCONS_SEQ);
617             item.addActionListener(this);
618             popup.add(item);
619           }
620         }
621       }
622       popup.show(this, evt.getX(), evt.getY());
623     }
624     else
625     {
626       // selection action.
627       if (selectedRow > -1 && selectedRow < aa.length)
628       {
629         if (aa[selectedRow].groupRef != null)
630         {
631           if (evt.getClickCount() >= 2)
632           {
633             // todo: make the ap scroll to the selection - not necessary, first
634             // click highlights/scrolls, second selects
635             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
636             ap.av.setSelectionGroup(// new SequenceGroup(
637             aa[selectedRow].groupRef); // );
638             ap.av.sendSelection();
639             ap.paintAlignment(false);
640             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
641           }
642           else
643           {
644             ap.seqPanel.ap.idPanel
645                     .highlightSearchResults(aa[selectedRow].groupRef
646                             .getSequences(null));
647           }
648           return;
649         }
650         else if (aa[selectedRow].sequenceRef != null)
651         {
652           Vector sr = new Vector();
653           sr.addElement(aa[selectedRow].sequenceRef);
654           if (evt.getClickCount() == 1)
655           {
656             ap.seqPanel.ap.idPanel.highlightSearchResults(sr);
657           }
658           else if (evt.getClickCount() >= 2)
659           {
660             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
661             SequenceGroup sg = new SequenceGroup();
662             sg.addSequence(aa[selectedRow].sequenceRef, false);
663             ap.av.setSelectionGroup(sg);
664             ap.paintAlignment(false);
665             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
666             ap.av.sendSelection();
667           }
668
669         }
670       }
671
672     }
673   }
674
675   /**
676    * DOCUMENT ME!
677    * 
678    * @param e
679    *          DOCUMENT ME!
680    */
681   protected void copy_annotseqtoclipboard(SequenceI sq)
682   {
683     if (sq == null || sq.getLength() < 1)
684     {
685       return;
686     }
687     jalview.appletgui.AlignFrame.copiedSequences = new StringBuffer();
688     jalview.appletgui.AlignFrame.copiedSequences.append(sq.getName() + "\t"
689             + sq.getStart() + "\t" + sq.getEnd() + "\t"
690             + sq.getSequenceAsString() + "\n");
691     if (av.hasHiddenColumns())
692     {
693       jalview.appletgui.AlignFrame.copiedHiddenColumns = new Vector();
694       for (int i = 0; i < av.getColumnSelection().getHiddenColumns().size(); i++)
695       {
696         int[] region = (int[]) av.getColumnSelection().getHiddenColumns()
697                 .elementAt(i);
698
699         jalview.appletgui.AlignFrame.copiedHiddenColumns
700                 .addElement(new int[]
701                 { region[0], region[1] });
702       }
703     }
704   }
705
706   public void update(Graphics g)
707   {
708     paint(g);
709   }
710
711   public void paint(Graphics g)
712   {
713     int w = getSize().width;
714     int h = getSize().height;
715     if (image == null || w != image.getWidth(this)
716             || h != image.getHeight(this))
717     {
718       image = createImage(w, ap.annotationPanel.getSize().height);
719     }
720
721     drawComponent(image.getGraphics(), w);
722     g.drawImage(image, 0, 0, this);
723   }
724
725   public void drawComponent(Graphics g, int width)
726   {
727     g.setFont(av.getFont());
728     FontMetrics fm = g.getFontMetrics(av.getFont());
729     g.setColor(Color.white);
730     g.fillRect(0, 0, getSize().width, getSize().height);
731
732     g.translate(0, -scrollOffset);
733     g.setColor(Color.black);
734
735     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
736     int y = 0, fy = g.getFont().getSize();
737     int x = 0, offset;
738
739     if (aa != null)
740     {
741       hasHiddenRows = false;
742       for (int i = 0; i < aa.length; i++)
743       {
744         if (!aa[i].visible)
745         {
746           hasHiddenRows = true;
747           continue;
748         }
749
750         x = width - fm.stringWidth(aa[i].label) - 3;
751
752         y += aa[i].height;
753         offset = -(aa[i].height - fy) / 2;
754
755         g.drawString(aa[i].label, x, y + offset);
756       }
757     }
758     g.translate(0, +scrollOffset);
759     if (resizePanel)
760     {
761       g.setColor(Color.red);
762       g.setPaintMode();
763       g.drawLine(2, 8, 5, 2);
764       g.drawLine(5, 2, 8, 8);
765     }
766     else if (!dragCancelled && dragEvent != null && aa != null)
767     {
768       g.setColor(Color.lightGray);
769       g.drawString(aa[selectedRow].label, dragEvent.getX(),
770               dragEvent.getY());
771     }
772
773     if (!av.wrapAlignment && ((aa == null) || (aa.length < 1)))
774     {
775       g.setColor(Color.black);
776       g.drawString(MessageManager.getString("label.right_click"), 2, 8);
777       g.drawString(MessageManager.getString("label.to_add_annotation"), 2,
778               18);
779     }
780   }
781 }