profile visualizations are now logos
[jalview.git] / src / jalview / gui / AnnotationLabels.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Development Version 2.4.1)
3  * Copyright (C) 2009 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  * 
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  * 
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.gui;
20
21 import java.util.*;
22
23 import java.awt.*;
24 import java.awt.datatransfer.*;
25 import java.awt.event.*;
26 import java.awt.image.*;
27 import javax.swing.*;
28
29 import jalview.datamodel.*;
30 import jalview.io.*;
31
32 /**
33  * DOCUMENT ME!
34  * 
35  * @author $author$
36  * @version $Revision$
37  */
38 public class AnnotationLabels extends JPanel implements MouseListener,
39         MouseMotionListener, ActionListener
40 {
41   static String TOGGLE_LABELSCALE = "Scale Label to Column";
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 DELETE = "Delete This Row";
50
51   static String SHOWALL = "Show All Hidden Rows";
52
53   static String OUTPUT_TEXT = "Export Annotation";
54
55   static String COPYCONS_SEQ = "Copy Consensus Sequence";
56
57   boolean resizePanel = false;
58
59   Image image;
60
61   AlignmentPanel ap;
62
63   AlignViewport av;
64
65   boolean resizing = false;
66
67   MouseEvent dragEvent;
68
69   int oldY;
70
71   int selectedRow;
72
73   int scrollOffset = 0;
74
75   Font font = new Font("Arial", Font.PLAIN, 11);
76
77   /**
78    * Creates a new AnnotationLabels object.
79    * 
80    * @param ap
81    *                DOCUMENT ME!
82    */
83   public AnnotationLabels(AlignmentPanel ap)
84   {
85     this.ap = ap;
86     av = ap.av;
87     ToolTipManager.sharedInstance().registerComponent(this);
88
89     java.net.URL url = getClass().getResource("/images/idwidth.gif");
90     Image temp = null;
91
92     if (url != null)
93     {
94       temp = java.awt.Toolkit.getDefaultToolkit().createImage(url);
95     }
96
97     try
98     {
99       MediaTracker mt = new MediaTracker(this);
100       mt.addImage(temp, 0);
101       mt.waitForID(0);
102     } catch (Exception ex)
103     {
104     }
105
106     BufferedImage bi = new BufferedImage(temp.getHeight(this), temp
107             .getWidth(this), BufferedImage.TYPE_INT_RGB);
108     Graphics2D g = (Graphics2D) bi.getGraphics();
109     g.rotate(Math.toRadians(90));
110     g.drawImage(temp, 0, -bi.getWidth(this), this);
111     image = (Image) bi;
112
113     addMouseListener(this);
114     addMouseMotionListener(this);
115   }
116
117   public AnnotationLabels(AlignViewport av)
118   {
119     this.av = av;
120   }
121
122   /**
123    * DOCUMENT ME!
124    * 
125    * @param y
126    *                DOCUMENT ME!
127    */
128   public void setScrollOffset(int y)
129   {
130     scrollOffset = y;
131     repaint();
132   }
133
134   void getSelectedRow(int y)
135   {
136     int height = 0;
137     AlignmentAnnotation[] aa = ap.av.alignment.getAlignmentAnnotation();
138
139     if (aa != null)
140     {
141       for (int i = 0; i < aa.length; i++)
142       {
143         if (!aa[i].visible)
144         {
145           continue;
146         }
147
148         height += aa[i].height;
149
150         if (y < height)
151         {
152           selectedRow = i;
153
154           break;
155         }
156       }
157     }
158   }
159
160   /**
161    * DOCUMENT ME!
162    * 
163    * @param evt
164    *                DOCUMENT ME!
165    */
166   public void actionPerformed(ActionEvent evt)
167   {
168     AlignmentAnnotation[] aa = ap.av.alignment.getAlignmentAnnotation();
169
170     if (evt.getActionCommand().equals(ADDNEW))
171     {
172       AlignmentAnnotation newAnnotation = new AlignmentAnnotation(null,
173               null, new Annotation[ap.av.alignment.getWidth()]);
174
175       if (!editLabelDescription(newAnnotation))
176       {
177         return;
178       }
179
180       ap.av.alignment.addAnnotation(newAnnotation);
181       ap.av.alignment.setAnnotationIndex(newAnnotation, 0);
182     }
183     else if (evt.getActionCommand().equals(EDITNAME))
184     {
185       editLabelDescription(aa[selectedRow]);
186       repaint();
187     }
188     else if (evt.getActionCommand().equals(HIDE))
189     {
190       aa[selectedRow].visible = false;
191
192       if (aa[selectedRow].label.equals("Quality"))
193       {
194         ap.av.quality = null;
195       }
196     }
197     else if (evt.getActionCommand().equals(DELETE))
198     {
199       ap.av.alignment.deleteAnnotation(aa[selectedRow]);
200     }
201     else if (evt.getActionCommand().equals(SHOWALL))
202     {
203       for (int i = 0; i < aa.length; i++)
204       {
205         if (!aa[i].visible && aa[i].annotations != null)
206         {
207           aa[i].visible = true;
208         }
209       }
210     }
211     else if (evt.getActionCommand().equals(OUTPUT_TEXT))
212     {
213       new AnnotationExporter().exportAnnotations(ap,
214               new AlignmentAnnotation[]
215               { aa[selectedRow] }, null, null);
216     }
217     else if (evt.getActionCommand().equals(COPYCONS_SEQ))
218     {
219       SequenceI cons = null;
220       if (aa[selectedRow].groupRef!=null)
221       {
222         cons = aa[selectedRow].groupRef.getConsensusSeq();
223       } else {
224         cons = av.getConsensusSeq();
225       }
226       if (cons != null)
227       {
228         copy_annotseqtoclipboard(cons);
229       }
230
231     }
232     else if (evt.getActionCommand().equals(TOGGLE_LABELSCALE))
233     {
234       aa[selectedRow].scaleColLabel = !aa[selectedRow].scaleColLabel; 
235     }
236
237     ap.annotationPanel.adjustPanelHeight();
238     ap.annotationScroller.validate();
239     ap.paintAlignment(true);
240   }
241
242   /**
243    * DOCUMENT ME!
244    * 
245    * @param e
246    *                DOCUMENT ME!
247    */
248   boolean editLabelDescription(AlignmentAnnotation annotation)
249   {
250     EditNameDialog dialog = new EditNameDialog(annotation.label,
251             annotation.description, "       Annotation Name ",
252             "Annotation Description ", "Edit Annotation Name/Description");
253
254     if (!dialog.accept)
255     {
256       return false;
257     }
258
259     annotation.label = dialog.getName();
260
261     String text = dialog.getDescription();
262     if (text != null && text.length() == 0)
263     {
264       text = null;
265     }
266     annotation.description = text;
267
268     return true;
269   }
270
271   /**
272    * DOCUMENT ME!
273    * 
274    * @param evt
275    *                DOCUMENT ME!
276    */
277   public void mousePressed(MouseEvent evt)
278   {
279     getSelectedRow(evt.getY() - scrollOffset);
280     oldY = evt.getY();
281   }
282
283   /**
284    * DOCUMENT ME!
285    * 
286    * @param evt
287    *                DOCUMENT ME!
288    */
289   public void mouseReleased(MouseEvent evt)
290   {
291     int start = selectedRow;
292     getSelectedRow(evt.getY() - scrollOffset);
293     int end = selectedRow;
294
295     if (start != end)
296     {
297       // Swap these annotations
298       AlignmentAnnotation startAA = ap.av.alignment
299               .getAlignmentAnnotation()[start];
300       AlignmentAnnotation endAA = ap.av.alignment.getAlignmentAnnotation()[end];
301
302       ap.av.alignment.getAlignmentAnnotation()[end] = startAA;
303       ap.av.alignment.getAlignmentAnnotation()[start] = endAA;
304     }
305
306     resizePanel = false;
307     dragEvent = null;
308     repaint();
309     ap.annotationPanel.repaint();
310   }
311
312   /**
313    * DOCUMENT ME!
314    * 
315    * @param evt
316    *                DOCUMENT ME!
317    */
318   public void mouseEntered(MouseEvent evt)
319   {
320     if (evt.getY() < 10)
321     {
322       resizePanel = true;
323       repaint();
324     }
325   }
326
327   /**
328    * DOCUMENT ME!
329    * 
330    * @param evt
331    *                DOCUMENT ME!
332    */
333   public void mouseExited(MouseEvent evt)
334   {
335     if (dragEvent == null)
336     {
337       resizePanel = false;
338       repaint();
339     }
340   }
341
342   /**
343    * DOCUMENT ME!
344    * 
345    * @param evt
346    *                DOCUMENT ME!
347    */
348   public void mouseDragged(MouseEvent evt)
349   {
350     dragEvent = evt;
351
352     if (resizePanel)
353     {
354       Dimension d = ap.annotationScroller.getPreferredSize();
355       int dif = evt.getY() - oldY;
356
357       dif /= ap.av.charHeight;
358       dif *= ap.av.charHeight;
359
360       if ((d.height - dif) > 20)
361       {
362         ap.annotationScroller.setPreferredSize(new Dimension(d.width,
363                 d.height - dif));
364         d = ap.annotationSpaceFillerHolder.getPreferredSize();
365         ap.annotationSpaceFillerHolder.setPreferredSize(new Dimension(
366                 d.width, d.height - dif));
367         ap.paintAlignment(true);
368       }
369
370       ap.addNotify();
371     }
372     else
373     {
374       repaint();
375     }
376   }
377
378   /**
379    * DOCUMENT ME!
380    * 
381    * @param evt
382    *                DOCUMENT ME!
383    */
384   public void mouseMoved(MouseEvent evt)
385   {
386     resizePanel = evt.getY() < 10;
387
388     getSelectedRow(evt.getY() - scrollOffset);
389
390     if (selectedRow > -1
391             && ap.av.alignment.getAlignmentAnnotation().length > selectedRow)
392     {
393       AlignmentAnnotation aa = ap.av.alignment.getAlignmentAnnotation()[selectedRow];
394
395       StringBuffer desc = new StringBuffer("<html>");
396
397       if (aa.description != null
398               && !aa.description.equals("New description"))
399       {
400         desc.append(aa.getDescription(true));
401         if (aa.hasScore)
402           desc.append("<br>");
403       }
404       if (aa.hasScore())
405       {
406         desc.append("Score: " + aa.score);
407       }
408
409       if (desc.length() != 6)
410       {
411         desc.append("</html>");
412         this.setToolTipText(desc.toString());
413       }
414       else
415         this.setToolTipText(null);
416     }
417
418   }
419
420   /**
421    * DOCUMENT ME!
422    * 
423    * @param evt
424    *                DOCUMENT ME!
425    */
426   public void mouseClicked(MouseEvent evt)
427   {
428     AlignmentAnnotation[] aa = ap.av.alignment.getAlignmentAnnotation();
429     if (SwingUtilities.isLeftMouseButton(evt))
430     {
431       if (selectedRow < aa.length)
432         {
433         if (aa[selectedRow].groupRef!=null)
434         {
435           if (evt.getClickCount()>=2)
436           {
437             // todo: make the ap scroll to the selection
438             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
439             ap.av.setSelectionGroup(//new SequenceGroup(
440                     aa[selectedRow].groupRef); // );
441             ap.paintAlignment(false);
442           } else {
443             ap.seqPanel.ap.idPanel.highlightSearchResults(aa[selectedRow].groupRef.getSequences(null));
444           }
445           return;
446         } else 
447         if (aa[selectedRow].sequenceRef!=null){
448           Vector sr = new Vector();
449           sr.addElement(aa[selectedRow].sequenceRef);
450           if (evt.getClickCount()==1)
451           {
452           ap.seqPanel.ap.idPanel.highlightSearchResults(sr);
453           } else
454             if (evt.getClickCount()>=2)
455           {
456               ap.seqPanel.ap.idPanel.highlightSearchResults(null);
457               SequenceGroup sg = new SequenceGroup();
458             sg.addSequence(aa[selectedRow].sequenceRef,false);
459             ap.av.setSelectionGroup(sg);
460             ap.paintAlignment(false);
461             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
462           }
463           
464         }
465       }
466     }
467     if (!SwingUtilities.isRightMouseButton(evt))
468     {
469       return;
470     }
471
472
473     JPopupMenu pop = new JPopupMenu("Annotations");
474     JMenuItem item = new JMenuItem(ADDNEW);
475     item.addActionListener(this);
476
477     if ((aa == null) || (aa.length == 0))
478     {
479       item = new JMenuItem(SHOWALL);
480       item.addActionListener(this);
481       pop.add(item);
482       pop.show(this, evt.getX(), evt.getY());
483       return;
484     }
485
486     pop.add(item);
487     item = new JMenuItem(EDITNAME);
488     item.addActionListener(this);
489     pop.add(item);
490     item = new JMenuItem(HIDE);
491     item.addActionListener(this);
492     pop.add(item);
493     item = new JMenuItem(DELETE);
494     item.addActionListener(this);
495     pop.add(item);
496     item = new JMenuItem(SHOWALL);
497     item.addActionListener(this);
498     pop.add(item);
499     item = new JMenuItem(OUTPUT_TEXT);
500     item.addActionListener(this);
501     pop.add(item);
502     // TODO: annotation object should be typed for autocalculated/derived property methods
503     if (selectedRow < aa.length)
504     {
505       if (!aa[selectedRow].autoCalculated) {
506         if (aa[selectedRow].graph==AlignmentAnnotation.NO_GRAPH)
507         {
508           // display formatting settings for this row.
509           pop.addSeparator();
510           // av and sequencegroup need to implement same interface for
511           item = new JCheckBoxMenuItem(TOGGLE_LABELSCALE,
512                 aa[selectedRow].scaleColLabel);
513           item.addActionListener(this);
514           pop.add(item);
515         }
516       }
517       else if (aa[selectedRow].label.indexOf("Consensus") > -1)
518       {
519         pop.addSeparator();
520         // av and sequencegroup need to implement same interface for
521         final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
522                 "Ignore Gaps In Consensus",
523                 (aa[selectedRow].groupRef != null) ? aa[selectedRow].groupRef
524                         .getIgnoreGapsConsensus()
525                         : ap.av.getIgnoreGapsConsensus());
526         final AlignmentAnnotation aaa = aa[selectedRow];
527         cbmi.addActionListener(new ActionListener()
528         {
529           public void actionPerformed(ActionEvent e)
530           {
531             if (aaa.groupRef != null)
532             {
533            // TODO: pass on reference to ap so the view can be updated.
534               aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState()); 
535               ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
536             }
537             else
538             {
539               ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
540             }
541           }
542         });
543         pop.add(cbmi);
544         // av and sequencegroup need to implement same interface for
545         if (aaa.groupRef != null)
546         {
547           final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
548                   "Show Group Histogram", aa[selectedRow].groupRef
549                           .isShowConsensusHistogram());
550           chist.addActionListener(new ActionListener()
551           {
552             public void actionPerformed(ActionEvent e)
553             {
554               // TODO: pass on reference
555               // to ap
556               // so the
557               // view
558               // can be
559               // updated.
560               aaa.groupRef.setShowConsensusHistogram(chist.getState());
561               ap.repaint();
562               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
563             }
564           });
565           pop.add(chist);
566           final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
567                   "Show Group Logo", aa[selectedRow].groupRef
568                           .isShowSequenceLogo());
569           cprof.addActionListener(new ActionListener()
570           {
571             public void actionPerformed(ActionEvent e)
572             {
573               // TODO: pass on reference
574               // to ap
575               // so the
576               // view
577               // can be
578               // updated.
579               aaa.groupRef.setIncludeAllConsSymbols(cprof.getState());
580               ap.repaint();
581               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
582             }
583           });
584           pop.add(cprof);
585         } else {
586           final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
587                   "Show Histogram", av.isShowConsensusHistogram());
588           chist.addActionListener(new ActionListener()
589           {
590             public void actionPerformed(ActionEvent e)
591             {
592               // TODO: pass on reference
593               // to ap
594               // so the
595               // view
596               // can be
597               // updated.
598               av.setShowConsensusHistogram(chist.getState());
599               ap.repaint();
600               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
601             }
602           });
603           pop.add(chist);
604           final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
605                   "Show Logo", av.isShowSequenceLogo());
606           cprof.addActionListener(new ActionListener()
607           {
608             public void actionPerformed(ActionEvent e)
609             {
610               // TODO: pass on reference
611               // to ap
612               // so the
613               // view
614               // can be
615               // updated.
616               av.setShowSequenceLogo(cprof.getState());
617               ap.repaint();
618               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
619             }
620           });
621           pop.add(cprof);   
622         }
623         final JMenuItem consclipbrd = new JMenuItem(COPYCONS_SEQ);
624         consclipbrd.addActionListener(this);
625         pop.add(consclipbrd);
626       }
627     }
628     pop.show(this, evt.getX(), evt.getY());
629   }
630
631   /**
632    * do a single sequence copy to jalview and the system clipboard
633    * 
634    * @param sq
635    *                sequence to be copied to clipboard
636    */
637   protected void copy_annotseqtoclipboard(SequenceI sq)
638   {
639     SequenceI[] seqs = new SequenceI[]
640     { sq };
641     String[] omitHidden = null;
642     SequenceI[] dseqs = new SequenceI[]
643     { sq.getDatasetSequence() };
644     if (dseqs[0] == null)
645     {
646       dseqs[0] = new Sequence(sq);
647       dseqs[0].setSequence(jalview.analysis.AlignSeq.extractGaps(
648               jalview.util.Comparison.GapChars, sq.getSequenceAsString()));
649
650       sq.setDatasetSequence(dseqs[0]);
651     }
652     Alignment ds = new Alignment(dseqs);
653     if (av.hasHiddenColumns)
654     {
655       omitHidden = av.getColumnSelection().getVisibleSequenceStrings(0,
656               sq.getLength(), seqs);
657     }
658
659     String output = new FormatAdapter().formatSequences("Fasta", seqs,
660             omitHidden);
661
662     Toolkit.getDefaultToolkit().getSystemClipboard().setContents(
663             new StringSelection(output), Desktop.instance);
664
665     Vector hiddenColumns = null;
666     if (av.hasHiddenColumns)
667     {
668       hiddenColumns = new Vector();
669       for (int i = 0; i < av.getColumnSelection().getHiddenColumns().size(); i++)
670       {
671         int[] region = (int[]) av.getColumnSelection().getHiddenColumns()
672                 .elementAt(i);
673
674         hiddenColumns.addElement(new int[]
675         { region[0], region[1] });
676       }
677     }
678
679     Desktop.jalviewClipboard = new Object[]
680     { seqs, ds, // what is the dataset of a consensus sequence ? need to flag
681                 // sequence as special.
682         hiddenColumns };
683   }
684
685   /**
686    * DOCUMENT ME!
687    * 
688    * @param g1
689    *                DOCUMENT ME!
690    */
691   public void paintComponent(Graphics g)
692   {
693
694     int width = getWidth();
695     if (width == 0)
696     {
697       width = ap.calculateIdWidth().width + 4;
698     }
699
700     Graphics2D g2 = (Graphics2D) g;
701     if (av.antiAlias)
702     {
703       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
704               RenderingHints.VALUE_ANTIALIAS_ON);
705     }
706
707     drawComponent(g2, width);
708
709   }
710
711   /**
712    * DOCUMENT ME!
713    * 
714    * @param g
715    *                DOCUMENT ME!
716    */
717   public void drawComponent(Graphics g, int width)
718   {
719     if (av.getFont().getSize() < 10)
720     {
721       g.setFont(font);
722     }
723     else
724     {
725       g.setFont(av.getFont());
726     }
727
728     FontMetrics fm = g.getFontMetrics(g.getFont());
729     g.setColor(Color.white);
730     g.fillRect(0, 0, getWidth(), getHeight());
731
732     g.translate(0, scrollOffset);
733     g.setColor(Color.black);
734
735     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
736     int fontHeight = g.getFont().getSize();
737     int y = 0;
738     int x = 0;
739     int graphExtras = 0;
740     int offset = 0;
741
742     if (aa != null)
743     {
744       for (int i = 0; i < aa.length; i++)
745       {
746         g.setColor(Color.black);
747
748         if (!aa[i].visible)
749         {
750           continue;
751         }
752
753         y += aa[i].height;
754
755         offset = -aa[i].height / 2;
756
757         if (aa[i].hasText)
758         {
759           offset += fm.getHeight() / 2;
760           offset -= fm.getDescent();
761         }
762         else
763           offset += fm.getDescent();
764
765         x = width - fm.stringWidth(aa[i].label) - 3;
766
767         if (aa[i].graphGroup > -1)
768         {
769           int groupSize = 0;
770           for (int gg = 0; gg < aa.length; gg++)
771           {
772             if (aa[gg].graphGroup == aa[i].graphGroup)
773             {
774               groupSize++;
775             }
776           }
777
778           if (groupSize * (fontHeight + 8) < aa[i].height)
779           {
780             graphExtras = (aa[i].height - (groupSize * (fontHeight + 8))) / 2;
781           }
782
783           for (int gg = 0; gg < aa.length; gg++)
784           {
785             if (aa[gg].graphGroup == aa[i].graphGroup)
786             {
787               x = width - fm.stringWidth(aa[gg].label) - 3;
788               g.drawString(aa[gg].label, x, y - graphExtras);
789               if (aa[gg].annotations[0] != null)
790               {
791                 g.setColor(aa[gg].annotations[0].colour);
792               }
793
794               g.drawLine(x, y - graphExtras - 3, x
795                       + fm.stringWidth(aa[gg].label), y - graphExtras - 3);
796
797               g.setColor(Color.black);
798               graphExtras += fontHeight + 8;
799             }
800           }
801         }
802         else
803         {
804           g.drawString(aa[i].label, x, y + offset);
805         }
806       }
807     }
808
809     if (resizePanel)
810     {
811       g.drawImage(image, 2, 0 - scrollOffset, this);
812     }
813     else if (dragEvent != null && aa != null)
814     {
815       g.setColor(Color.lightGray);
816       g.drawString(aa[selectedRow].label, dragEvent.getX(), dragEvent
817               .getY()
818               - scrollOffset);
819     }
820
821     if ((aa == null) || (aa.length < 1))
822     {
823       g.drawString("Right click", 2, 8);
824       g.drawString("to add annotation", 2, 18);
825     }
826   }
827 }