3bf915a42614bc36c72689cb0ef8d6daedd5f206
[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           ap.seqPanel.ap.idPanel.highlightSearchResults(aa[selectedRow].groupRef.getSequences(null));
436           if (evt.getClickCount()>=2)
437           {
438             // todo: make the ap scroll to the selection
439             ap.av.setSelectionGroup(new SequenceGroup(aa[selectedRow].groupRef));
440             ap.paintAlignment(false);
441           }
442           return;
443         } else 
444         if (aa[selectedRow].sequenceRef!=null){
445           Vector sr = new Vector();
446           sr.addElement(aa[selectedRow].sequenceRef);
447           if (evt.getClickCount()==1)
448           {
449           ap.seqPanel.ap.idPanel.highlightSearchResults(sr);
450           } else
451             if (evt.getClickCount()>=2)
452           {
453               ap.seqPanel.ap.idPanel.highlightSearchResults(null);
454               SequenceGroup sg = new SequenceGroup();
455             sg.addSequence(aa[selectedRow].sequenceRef,false);
456             ap.av.setSelectionGroup(sg);
457             ap.paintAlignment(false);
458             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
459           }
460           
461         }
462       }
463     }
464     if (!SwingUtilities.isRightMouseButton(evt))
465     {
466       return;
467     }
468
469
470     JPopupMenu pop = new JPopupMenu("Annotations");
471     JMenuItem item = new JMenuItem(ADDNEW);
472     item.addActionListener(this);
473
474     if ((aa == null) || (aa.length == 0))
475     {
476       item = new JMenuItem(SHOWALL);
477       item.addActionListener(this);
478       pop.add(item);
479       pop.show(this, evt.getX(), evt.getY());
480       return;
481     }
482
483     pop.add(item);
484     item = new JMenuItem(EDITNAME);
485     item.addActionListener(this);
486     pop.add(item);
487     item = new JMenuItem(HIDE);
488     item.addActionListener(this);
489     pop.add(item);
490     item = new JMenuItem(DELETE);
491     item.addActionListener(this);
492     pop.add(item);
493     item = new JMenuItem(SHOWALL);
494     item.addActionListener(this);
495     pop.add(item);
496     item = new JMenuItem(OUTPUT_TEXT);
497     item.addActionListener(this);
498     pop.add(item);
499     // TODO: annotation object should be typed for autocalculated/derived property methods
500     if (selectedRow < aa.length)
501     {
502       if (!aa[selectedRow].autoCalculated) {
503         if (aa[selectedRow].graph==AlignmentAnnotation.NO_GRAPH)
504         {
505           // display formatting settings for this row.
506           pop.addSeparator();
507           // av and sequencegroup need to implement same interface for
508           item = new JCheckBoxMenuItem(TOGGLE_LABELSCALE,
509                 aa[selectedRow].scaleColLabel);
510           item.addActionListener(this);
511           pop.add(item);
512         }
513       }
514       else if (aa[selectedRow].label.indexOf("Consensus") > -1)
515       {
516         pop.addSeparator();
517         // av and sequencegroup need to implement same interface for
518         final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
519                 "Ignore Gaps In Consensus",
520                 (aa[selectedRow].groupRef != null) ? aa[selectedRow].groupRef
521                         .getIgnoreGapsConsensus()
522                         : ap.av.getIgnoreGapsConsensus());
523         final AlignmentAnnotation aaa = aa[selectedRow];
524         cbmi.addActionListener(new ActionListener()
525         {
526           public void actionPerformed(ActionEvent e)
527           {
528             if (aaa.groupRef != null)
529             {
530            // TODO: pass on reference to ap so the view can be updated.
531               aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState()); 
532               ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
533             }
534             else
535             {
536               ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
537             }
538           }
539         });
540         pop.add(cbmi);
541         // av and sequencegroup need to implement same interface for
542         if (aaa.groupRef != null)
543         {
544           final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
545                   "Show Full Profile", aa[selectedRow].groupRef
546                           .isIncludeAllConsSymbols());
547           cprof.addActionListener(new ActionListener()
548           {
549             public void actionPerformed(ActionEvent e)
550             {
551               // TODO: pass on reference
552               // to ap
553               // so the
554               // view
555               // can be
556               // updated.
557               aaa.groupRef.setIncludeAllConsSymbols(cprof.getState());
558               ap.repaint();
559               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
560             }
561           });
562           pop.add(cprof);
563         } else {
564 /*          final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
565                   "Show Full Profile", aa[selectedRow].groupRef
566                           .isIncludeAllConsSymbols());
567           cprof.addActionListener(new ActionListener()
568           {
569             public void actionPerformed(ActionEvent e)
570             {
571               // TODO: pass on reference
572               // to ap
573               // so the
574               // view
575               // can be
576               // updated.
577               aaa.groupRef.setIncludeAllConsSymbols(cprof.getState());
578               ap.repaint();
579               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
580             }
581           });
582           pop.add(cprof);       */   
583         }
584         final JMenuItem consclipbrd = new JMenuItem(COPYCONS_SEQ);
585         consclipbrd.addActionListener(this);
586         pop.add(consclipbrd);
587       }
588     }
589     pop.show(this, evt.getX(), evt.getY());
590   }
591
592   /**
593    * do a single sequence copy to jalview and the system clipboard
594    * 
595    * @param sq
596    *                sequence to be copied to clipboard
597    */
598   protected void copy_annotseqtoclipboard(SequenceI sq)
599   {
600     SequenceI[] seqs = new SequenceI[]
601     { sq };
602     String[] omitHidden = null;
603     SequenceI[] dseqs = new SequenceI[]
604     { sq.getDatasetSequence() };
605     if (dseqs[0] == null)
606     {
607       dseqs[0] = new Sequence(sq);
608       dseqs[0].setSequence(jalview.analysis.AlignSeq.extractGaps(
609               jalview.util.Comparison.GapChars, sq.getSequenceAsString()));
610
611       sq.setDatasetSequence(dseqs[0]);
612     }
613     Alignment ds = new Alignment(dseqs);
614     if (av.hasHiddenColumns)
615     {
616       omitHidden = av.getColumnSelection().getVisibleSequenceStrings(0,
617               sq.getLength(), seqs);
618     }
619
620     String output = new FormatAdapter().formatSequences("Fasta", seqs,
621             omitHidden);
622
623     Toolkit.getDefaultToolkit().getSystemClipboard().setContents(
624             new StringSelection(output), Desktop.instance);
625
626     Vector hiddenColumns = null;
627     if (av.hasHiddenColumns)
628     {
629       hiddenColumns = new Vector();
630       for (int i = 0; i < av.getColumnSelection().getHiddenColumns().size(); i++)
631       {
632         int[] region = (int[]) av.getColumnSelection().getHiddenColumns()
633                 .elementAt(i);
634
635         hiddenColumns.addElement(new int[]
636         { region[0], region[1] });
637       }
638     }
639
640     Desktop.jalviewClipboard = new Object[]
641     { seqs, ds, // what is the dataset of a consensus sequence ? need to flag
642                 // sequence as special.
643         hiddenColumns };
644   }
645
646   /**
647    * DOCUMENT ME!
648    * 
649    * @param g1
650    *                DOCUMENT ME!
651    */
652   public void paintComponent(Graphics g)
653   {
654
655     int width = getWidth();
656     if (width == 0)
657     {
658       width = ap.calculateIdWidth().width + 4;
659     }
660
661     Graphics2D g2 = (Graphics2D) g;
662     if (av.antiAlias)
663     {
664       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
665               RenderingHints.VALUE_ANTIALIAS_ON);
666     }
667
668     drawComponent(g2, width);
669
670   }
671
672   /**
673    * DOCUMENT ME!
674    * 
675    * @param g
676    *                DOCUMENT ME!
677    */
678   public void drawComponent(Graphics g, int width)
679   {
680     if (av.getFont().getSize() < 10)
681     {
682       g.setFont(font);
683     }
684     else
685     {
686       g.setFont(av.getFont());
687     }
688
689     FontMetrics fm = g.getFontMetrics(g.getFont());
690     g.setColor(Color.white);
691     g.fillRect(0, 0, getWidth(), getHeight());
692
693     g.translate(0, scrollOffset);
694     g.setColor(Color.black);
695
696     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
697     int fontHeight = g.getFont().getSize();
698     int y = 0;
699     int x = 0;
700     int graphExtras = 0;
701     int offset = 0;
702
703     if (aa != null)
704     {
705       for (int i = 0; i < aa.length; i++)
706       {
707         g.setColor(Color.black);
708
709         if (!aa[i].visible)
710         {
711           continue;
712         }
713
714         y += aa[i].height;
715
716         offset = -aa[i].height / 2;
717
718         if (aa[i].hasText)
719         {
720           offset += fm.getHeight() / 2;
721           offset -= fm.getDescent();
722         }
723         else
724           offset += fm.getDescent();
725
726         x = width - fm.stringWidth(aa[i].label) - 3;
727
728         if (aa[i].graphGroup > -1)
729         {
730           int groupSize = 0;
731           for (int gg = 0; gg < aa.length; gg++)
732           {
733             if (aa[gg].graphGroup == aa[i].graphGroup)
734             {
735               groupSize++;
736             }
737           }
738
739           if (groupSize * (fontHeight + 8) < aa[i].height)
740           {
741             graphExtras = (aa[i].height - (groupSize * (fontHeight + 8))) / 2;
742           }
743
744           for (int gg = 0; gg < aa.length; gg++)
745           {
746             if (aa[gg].graphGroup == aa[i].graphGroup)
747             {
748               x = width - fm.stringWidth(aa[gg].label) - 3;
749               g.drawString(aa[gg].label, x, y - graphExtras);
750               if (aa[gg].annotations[0] != null)
751               {
752                 g.setColor(aa[gg].annotations[0].colour);
753               }
754
755               g.drawLine(x, y - graphExtras - 3, x
756                       + fm.stringWidth(aa[gg].label), y - graphExtras - 3);
757
758               g.setColor(Color.black);
759               graphExtras += fontHeight + 8;
760             }
761           }
762         }
763         else
764         {
765           g.drawString(aa[i].label, x, y + offset);
766         }
767       }
768     }
769
770     if (resizePanel)
771     {
772       g.drawImage(image, 2, 0 - scrollOffset, this);
773     }
774     else if (dragEvent != null && aa != null)
775     {
776       g.setColor(Color.lightGray);
777       g.drawString(aa[selectedRow].label, dragEvent.getX(), dragEvent
778               .getY()
779               - scrollOffset);
780     }
781
782     if ((aa == null) || (aa.length < 1))
783     {
784       g.drawString("Right click", 2, 8);
785       g.drawString("to add annotation", 2, 18);
786     }
787   }
788 }