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