3ea788b785fd69aa78846e3aa6b751bf0019613a
[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       {
504         // display settings for this row.
505         pop.addSeparator();
506         // av and sequencegroup need to implement same interface for
507         item = new JCheckBoxMenuItem(TOGGLE_LABELSCALE,
508                 aa[selectedRow].scaleColLabel);
509         item.addActionListener(this);
510         pop.add(item);
511       }
512       else if (aa[selectedRow].label.indexOf("Consensus") > -1)
513       {
514         pop.addSeparator();
515         // av and sequencegroup need to implement same interface for
516         final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
517                 "Ignore Gaps In Consensus",
518                 (aa[selectedRow].groupRef != null) ? aa[selectedRow].groupRef
519                         .getIgnoreGapsConsensus()
520                         : ap.av.getIgnoreGapsConsensus());
521         final AlignmentAnnotation aaa = aa[selectedRow];
522         cbmi.addActionListener(new ActionListener()
523         {
524           public void actionPerformed(ActionEvent e)
525           {
526             if (aaa.groupRef != null)
527             {
528            // TODO: pass on reference to ap so the view can be updated.
529               aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState()); 
530               ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
531             }
532             else
533             {
534               ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
535             }
536           }
537         });
538         pop.add(cbmi);
539         // av and sequencegroup need to implement same interface for
540         if (aaa.groupRef != null)
541         {
542           final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
543                   "Show Full Profile", aa[selectedRow].groupRef
544                           .isIncludeAllConsSymbols());
545           cprof.addActionListener(new ActionListener()
546           {
547             public void actionPerformed(ActionEvent e)
548             {
549               // TODO: pass on reference
550               // to ap
551               // so the
552               // view
553               // can be
554               // updated.
555               aaa.groupRef.setIncludeAllConsSymbols(cprof.getState());
556               ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
557             }
558           });
559           pop.add(cprof);
560         }
561         final JMenuItem consclipbrd = new JMenuItem(COPYCONS_SEQ);
562         consclipbrd.addActionListener(this);
563         pop.add(consclipbrd);
564       }
565     }
566     pop.show(this, evt.getX(), evt.getY());
567   }
568
569   /**
570    * do a single sequence copy to jalview and the system clipboard
571    * 
572    * @param sq
573    *                sequence to be copied to clipboard
574    */
575   protected void copy_annotseqtoclipboard(SequenceI sq)
576   {
577     SequenceI[] seqs = new SequenceI[]
578     { sq };
579     String[] omitHidden = null;
580     SequenceI[] dseqs = new SequenceI[]
581     { sq.getDatasetSequence() };
582     if (dseqs[0] == null)
583     {
584       dseqs[0] = new Sequence(sq);
585       dseqs[0].setSequence(jalview.analysis.AlignSeq.extractGaps(
586               jalview.util.Comparison.GapChars, sq.getSequenceAsString()));
587
588       sq.setDatasetSequence(dseqs[0]);
589     }
590     Alignment ds = new Alignment(dseqs);
591     if (av.hasHiddenColumns)
592     {
593       omitHidden = av.getColumnSelection().getVisibleSequenceStrings(0,
594               sq.getLength(), seqs);
595     }
596
597     String output = new FormatAdapter().formatSequences("Fasta", seqs,
598             omitHidden);
599
600     Toolkit.getDefaultToolkit().getSystemClipboard().setContents(
601             new StringSelection(output), Desktop.instance);
602
603     Vector hiddenColumns = null;
604     if (av.hasHiddenColumns)
605     {
606       hiddenColumns = new Vector();
607       for (int i = 0; i < av.getColumnSelection().getHiddenColumns().size(); i++)
608       {
609         int[] region = (int[]) av.getColumnSelection().getHiddenColumns()
610                 .elementAt(i);
611
612         hiddenColumns.addElement(new int[]
613         { region[0], region[1] });
614       }
615     }
616
617     Desktop.jalviewClipboard = new Object[]
618     { seqs, ds, // what is the dataset of a consensus sequence ? need to flag
619                 // sequence as special.
620         hiddenColumns };
621   }
622
623   /**
624    * DOCUMENT ME!
625    * 
626    * @param g1
627    *                DOCUMENT ME!
628    */
629   public void paintComponent(Graphics g)
630   {
631
632     int width = getWidth();
633     if (width == 0)
634     {
635       width = ap.calculateIdWidth().width + 4;
636     }
637
638     Graphics2D g2 = (Graphics2D) g;
639     if (av.antiAlias)
640     {
641       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
642               RenderingHints.VALUE_ANTIALIAS_ON);
643     }
644
645     drawComponent(g2, width);
646
647   }
648
649   /**
650    * DOCUMENT ME!
651    * 
652    * @param g
653    *                DOCUMENT ME!
654    */
655   public void drawComponent(Graphics g, int width)
656   {
657     if (av.getFont().getSize() < 10)
658     {
659       g.setFont(font);
660     }
661     else
662     {
663       g.setFont(av.getFont());
664     }
665
666     FontMetrics fm = g.getFontMetrics(g.getFont());
667     g.setColor(Color.white);
668     g.fillRect(0, 0, getWidth(), getHeight());
669
670     g.translate(0, scrollOffset);
671     g.setColor(Color.black);
672
673     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
674     int fontHeight = g.getFont().getSize();
675     int y = 0;
676     int x = 0;
677     int graphExtras = 0;
678     int offset = 0;
679
680     if (aa != null)
681     {
682       for (int i = 0; i < aa.length; i++)
683       {
684         g.setColor(Color.black);
685
686         if (!aa[i].visible)
687         {
688           continue;
689         }
690
691         y += aa[i].height;
692
693         offset = -aa[i].height / 2;
694
695         if (aa[i].hasText)
696         {
697           offset += fm.getHeight() / 2;
698           offset -= fm.getDescent();
699         }
700         else
701           offset += fm.getDescent();
702
703         x = width - fm.stringWidth(aa[i].label) - 3;
704
705         if (aa[i].graphGroup > -1)
706         {
707           int groupSize = 0;
708           for (int gg = 0; gg < aa.length; gg++)
709           {
710             if (aa[gg].graphGroup == aa[i].graphGroup)
711             {
712               groupSize++;
713             }
714           }
715
716           if (groupSize * (fontHeight + 8) < aa[i].height)
717           {
718             graphExtras = (aa[i].height - (groupSize * (fontHeight + 8))) / 2;
719           }
720
721           for (int gg = 0; gg < aa.length; gg++)
722           {
723             if (aa[gg].graphGroup == aa[i].graphGroup)
724             {
725               x = width - fm.stringWidth(aa[gg].label) - 3;
726               g.drawString(aa[gg].label, x, y - graphExtras);
727               if (aa[gg].annotations[0] != null)
728               {
729                 g.setColor(aa[gg].annotations[0].colour);
730               }
731
732               g.drawLine(x, y - graphExtras - 3, x
733                       + fm.stringWidth(aa[gg].label), y - graphExtras - 3);
734
735               g.setColor(Color.black);
736               graphExtras += fontHeight + 8;
737             }
738           }
739         }
740         else
741         {
742           g.drawString(aa[i].label, x, y + offset);
743         }
744       }
745     }
746
747     if (resizePanel)
748     {
749       g.drawImage(image, 2, 0 - scrollOffset, this);
750     }
751     else if (dragEvent != null && aa != null)
752     {
753       g.setColor(Color.lightGray);
754       g.drawString(aa[selectedRow].label, dragEvent.getX(), dragEvent
755               .getY()
756               - scrollOffset);
757     }
758
759     if ((aa == null) || (aa.length < 1))
760     {
761       g.drawString("Right click", 2, 8);
762       g.drawString("to add annotation", 2, 18);
763     }
764   }
765 }