patches for # JAL-254 and JAL-548
[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     } else {
157       selectedRow = -1;
158     }
159   }
160
161   /**
162    * DOCUMENT ME!
163    * 
164    * @param evt
165    *          DOCUMENT ME!
166    */
167   public void actionPerformed(ActionEvent evt)
168   {
169     AlignmentAnnotation[] aa = ap.av.alignment.getAlignmentAnnotation();
170
171     if (evt.getActionCommand().equals(ADDNEW))
172     {
173       AlignmentAnnotation newAnnotation = new AlignmentAnnotation(null,
174               null, new Annotation[ap.av.alignment.getWidth()]);
175
176       if (!editLabelDescription(newAnnotation))
177       {
178         return;
179       }
180
181       ap.av.alignment.addAnnotation(newAnnotation);
182       ap.av.alignment.setAnnotationIndex(newAnnotation, 0);
183     }
184     else if (evt.getActionCommand().equals(EDITNAME))
185     {
186       editLabelDescription(aa[selectedRow]);
187       repaint();
188     }
189     else if (evt.getActionCommand().equals(HIDE))
190     {
191       aa[selectedRow].visible = false;
192
193       if (aa[selectedRow].label.equals("Quality"))
194       {
195         ap.av.quality = null;
196       }
197     }
198     else if (evt.getActionCommand().equals(DELETE))
199     {
200       ap.av.alignment.deleteAnnotation(aa[selectedRow]);
201     }
202     else if (evt.getActionCommand().equals(SHOWALL))
203     {
204       for (int i = 0; i < aa.length; i++)
205       {
206         if (!aa[i].visible && aa[i].annotations != null)
207         {
208           aa[i].visible = true;
209         }
210       }
211     }
212     else if (evt.getActionCommand().equals(OUTPUT_TEXT))
213     {
214       new AnnotationExporter().exportAnnotations(ap,
215               new AlignmentAnnotation[]
216               { aa[selectedRow] }, null, null);
217     }
218     else if (evt.getActionCommand().equals(COPYCONS_SEQ))
219     {
220       SequenceI cons = null;
221       if (aa[selectedRow].groupRef != null)
222       {
223         cons = aa[selectedRow].groupRef.getConsensusSeq();
224       }
225       else
226       {
227         cons = av.getConsensusSeq();
228       }
229       if (cons != null)
230       {
231         copy_annotseqtoclipboard(cons);
232       }
233
234     }
235     else if (evt.getActionCommand().equals(TOGGLE_LABELSCALE))
236     {
237       aa[selectedRow].scaleColLabel = !aa[selectedRow].scaleColLabel;
238     }
239
240     ap.annotationPanel.adjustPanelHeight();
241     ap.annotationScroller.validate();
242     ap.paintAlignment(true);
243   }
244
245   /**
246    * DOCUMENT ME!
247    * 
248    * @param e
249    *          DOCUMENT ME!
250    */
251   boolean editLabelDescription(AlignmentAnnotation annotation)
252   {
253     EditNameDialog dialog = new EditNameDialog(annotation.label,
254             annotation.description, "       Annotation Name ",
255             "Annotation Description ", "Edit Annotation Name/Description");
256
257     if (!dialog.accept)
258     {
259       return false;
260     }
261
262     annotation.label = dialog.getName();
263
264     String text = dialog.getDescription();
265     if (text != null && text.length() == 0)
266     {
267       text = null;
268     }
269     annotation.description = text;
270
271     return true;
272   }
273
274   /**
275    * DOCUMENT ME!
276    * 
277    * @param evt
278    *          DOCUMENT ME!
279    */
280   public void mousePressed(MouseEvent evt)
281   {
282     getSelectedRow(evt.getY() - scrollOffset);
283     oldY = evt.getY();
284   }
285
286   /**
287    * DOCUMENT ME!
288    * 
289    * @param evt
290    *          DOCUMENT ME!
291    */
292   public void mouseReleased(MouseEvent evt)
293   {
294     int start = selectedRow;
295     getSelectedRow(evt.getY() - scrollOffset);
296     int end = selectedRow;
297
298     if (start != end)
299     {
300       // Swap these annotations
301       AlignmentAnnotation startAA = ap.av.alignment
302               .getAlignmentAnnotation()[start];
303       AlignmentAnnotation endAA = ap.av.alignment.getAlignmentAnnotation()[end];
304
305       ap.av.alignment.getAlignmentAnnotation()[end] = startAA;
306       ap.av.alignment.getAlignmentAnnotation()[start] = endAA;
307     }
308
309     resizePanel = false;
310     dragEvent = null;
311     repaint();
312     ap.annotationPanel.repaint();
313   }
314
315   /**
316    * DOCUMENT ME!
317    * 
318    * @param evt
319    *          DOCUMENT ME!
320    */
321   public void mouseEntered(MouseEvent evt)
322   {
323     if (evt.getY() < 10)
324     {
325       resizePanel = true;
326       repaint();
327     }
328   }
329
330   /**
331    * DOCUMENT ME!
332    * 
333    * @param evt
334    *          DOCUMENT ME!
335    */
336   public void mouseExited(MouseEvent evt)
337   {
338     if (dragEvent == null)
339     {
340       resizePanel = false;
341       repaint();
342     }
343   }
344
345   /**
346    * DOCUMENT ME!
347    * 
348    * @param evt
349    *          DOCUMENT ME!
350    */
351   public void mouseDragged(MouseEvent evt)
352   {
353     dragEvent = evt;
354
355     if (resizePanel)
356     {
357       Dimension d = ap.annotationScroller.getPreferredSize();
358       int dif = evt.getY() - oldY;
359
360       dif /= ap.av.charHeight;
361       dif *= ap.av.charHeight;
362
363       if ((d.height - dif) > 20)
364       {
365         ap.annotationScroller.setPreferredSize(new Dimension(d.width,
366                 d.height - dif));
367         d = ap.annotationSpaceFillerHolder.getPreferredSize();
368         ap.annotationSpaceFillerHolder.setPreferredSize(new Dimension(
369                 d.width, d.height - dif));
370         ap.paintAlignment(true);
371       }
372
373       ap.addNotify();
374     }
375     else
376     {
377       repaint();
378     }
379   }
380
381   /**
382    * DOCUMENT ME!
383    * 
384    * @param evt
385    *          DOCUMENT ME!
386    */
387   public void mouseMoved(MouseEvent evt)
388   {
389     resizePanel = evt.getY() < 10;
390
391     getSelectedRow(evt.getY() - scrollOffset);
392
393     if (selectedRow > -1
394             && ap.av.alignment.getAlignmentAnnotation().length > selectedRow)
395     {
396       AlignmentAnnotation aa = ap.av.alignment.getAlignmentAnnotation()[selectedRow];
397
398       StringBuffer desc = new StringBuffer("<html>");
399
400       if (aa.description != null
401               && !aa.description.equals("New description"))
402       {
403         desc.append(aa.getDescription(true));
404         if (aa.hasScore)
405           desc.append("<br>");
406       }
407       if (aa.hasScore())
408       {
409         desc.append("Score: " + aa.score);
410       }
411
412       if (desc.length() != 6)
413       {
414         desc.append("</html>");
415         this.setToolTipText(desc.toString());
416       }
417       else
418         this.setToolTipText(null);
419     }
420
421   }
422
423   /**
424    * DOCUMENT ME!
425    * 
426    * @param evt
427    *          DOCUMENT ME!
428    */
429   public void mouseClicked(MouseEvent evt)
430   {
431     AlignmentAnnotation[] aa = ap.av.alignment.getAlignmentAnnotation();
432     if (SwingUtilities.isLeftMouseButton(evt))
433     {
434       if (aa!=null && selectedRow < aa.length)
435       {
436         if (aa[selectedRow].groupRef != null)
437         {
438           if (evt.getClickCount() >= 2)
439           {
440             // todo: make the ap scroll to the selection
441             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
442             ap.av.setSelectionGroup(// new SequenceGroup(
443                     aa[selectedRow].groupRef); // );
444             ap.paintAlignment(false);
445           }
446           else
447           {
448             ap.seqPanel.ap.idPanel
449                     .highlightSearchResults(aa[selectedRow].groupRef
450                             .getSequences(null));
451           }
452           return;
453         }
454         else if (aa[selectedRow].sequenceRef != null)
455         {
456           Vector sr = new Vector();
457           sr.addElement(aa[selectedRow].sequenceRef);
458           if (evt.getClickCount() == 1)
459           {
460             ap.seqPanel.ap.idPanel.highlightSearchResults(sr);
461           }
462           else if (evt.getClickCount() >= 2)
463           {
464             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
465             SequenceGroup sg = new SequenceGroup();
466             sg.addSequence(aa[selectedRow].sequenceRef, false);
467             ap.av.setSelectionGroup(sg);
468             ap.paintAlignment(false);
469             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
470           }
471
472         }
473       }
474     }
475     if (!SwingUtilities.isRightMouseButton(evt))
476     {
477       return;
478     }
479
480     JPopupMenu pop = new JPopupMenu("Annotations");
481     JMenuItem item = new JMenuItem(ADDNEW);
482     item.addActionListener(this);
483
484     if ((aa == null) || (aa.length == 0))
485     {
486       item = new JMenuItem(SHOWALL);
487       item.addActionListener(this);
488       pop.add(item);
489       pop.show(this, evt.getX(), evt.getY());
490       return;
491     }
492
493     pop.add(item);
494     item = new JMenuItem(EDITNAME);
495     item.addActionListener(this);
496     pop.add(item);
497     item = new JMenuItem(HIDE);
498     item.addActionListener(this);
499     pop.add(item);
500     item = new JMenuItem(DELETE);
501     item.addActionListener(this);
502     pop.add(item);
503     item = new JMenuItem(SHOWALL);
504     item.addActionListener(this);
505     pop.add(item);
506     item = new JMenuItem(OUTPUT_TEXT);
507     item.addActionListener(this);
508     pop.add(item);
509     // TODO: annotation object should be typed for autocalculated/derived
510     // property methods
511     if (selectedRow < aa.length)
512     {
513       if (!aa[selectedRow].autoCalculated)
514       {
515         if (aa[selectedRow].graph == AlignmentAnnotation.NO_GRAPH)
516         {
517           // display formatting settings for this row.
518           pop.addSeparator();
519           // av and sequencegroup need to implement same interface for
520           item = new JCheckBoxMenuItem(TOGGLE_LABELSCALE,
521                   aa[selectedRow].scaleColLabel);
522           item.addActionListener(this);
523           pop.add(item);
524         }
525       }
526       else if (aa[selectedRow].label.indexOf("Consensus") > -1)
527       {
528         pop.addSeparator();
529         // av and sequencegroup need to implement same interface for
530         final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
531                 "Ignore Gaps In Consensus",
532                 (aa[selectedRow].groupRef != null) ? aa[selectedRow].groupRef
533                         .getIgnoreGapsConsensus()
534                         : ap.av.getIgnoreGapsConsensus());
535         final AlignmentAnnotation aaa = aa[selectedRow];
536         cbmi.addActionListener(new ActionListener()
537         {
538           public void actionPerformed(ActionEvent e)
539           {
540             if (aaa.groupRef != null)
541             {
542               // TODO: pass on reference to ap so the view can be updated.
543               aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState());
544               ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
545             }
546             else
547             {
548               ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
549             }
550           }
551         });
552         pop.add(cbmi);
553         // av and sequencegroup need to implement same interface for
554         if (aaa.groupRef != null)
555         {
556           final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
557                   "Show Group Histogram", aa[selectedRow].groupRef
558                           .isShowConsensusHistogram());
559           chist.addActionListener(new ActionListener()
560           {
561             public void actionPerformed(ActionEvent e)
562             {
563               // TODO: pass on reference
564               // to ap
565               // so the
566               // view
567               // can be
568               // updated.
569               aaa.groupRef.setShowConsensusHistogram(chist.getState());
570               ap.repaint();
571               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
572             }
573           });
574           pop.add(chist);
575           final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
576                   "Show Group Logo", aa[selectedRow].groupRef
577                           .isShowSequenceLogo());
578           cprof.addActionListener(new ActionListener()
579           {
580             public void actionPerformed(ActionEvent e)
581             {
582               // TODO: pass on reference
583               // to ap
584               // so the
585               // view
586               // can be
587               // updated.
588               aaa.groupRef.setIncludeAllConsSymbols(cprof.getState());
589               ap.repaint();
590               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
591             }
592           });
593           pop.add(cprof);
594         }
595         else
596         {
597           final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
598                   "Show Histogram", av.isShowConsensusHistogram());
599           chist.addActionListener(new ActionListener()
600           {
601             public void actionPerformed(ActionEvent e)
602             {
603               // TODO: pass on reference
604               // to ap
605               // so the
606               // view
607               // can be
608               // updated.
609               av.setShowConsensusHistogram(chist.getState());
610               ap.repaint();
611               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
612             }
613           });
614           pop.add(chist);
615           final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
616                   "Show Logo", av.isShowSequenceLogo());
617           cprof.addActionListener(new ActionListener()
618           {
619             public void actionPerformed(ActionEvent e)
620             {
621               // TODO: pass on reference
622               // to ap
623               // so the
624               // view
625               // can be
626               // updated.
627               av.setShowSequenceLogo(cprof.getState());
628               ap.repaint();
629               // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
630             }
631           });
632           pop.add(cprof);
633         }
634         final JMenuItem consclipbrd = new JMenuItem(COPYCONS_SEQ);
635         consclipbrd.addActionListener(this);
636         pop.add(consclipbrd);
637       }
638     }
639     pop.show(this, evt.getX(), evt.getY());
640   }
641
642   /**
643    * do a single sequence copy to jalview and the system clipboard
644    * 
645    * @param sq
646    *          sequence to be copied to clipboard
647    */
648   protected void copy_annotseqtoclipboard(SequenceI sq)
649   {
650     SequenceI[] seqs = new SequenceI[]
651     { sq };
652     String[] omitHidden = null;
653     SequenceI[] dseqs = new SequenceI[]
654     { sq.getDatasetSequence() };
655     if (dseqs[0] == null)
656     {
657       dseqs[0] = new Sequence(sq);
658       dseqs[0].setSequence(jalview.analysis.AlignSeq.extractGaps(
659               jalview.util.Comparison.GapChars, sq.getSequenceAsString()));
660
661       sq.setDatasetSequence(dseqs[0]);
662     }
663     Alignment ds = new Alignment(dseqs);
664     if (av.hasHiddenColumns)
665     {
666       omitHidden = av.getColumnSelection().getVisibleSequenceStrings(0,
667               sq.getLength(), seqs);
668     }
669
670     String output = new FormatAdapter().formatSequences("Fasta", seqs,
671             omitHidden);
672
673     Toolkit.getDefaultToolkit().getSystemClipboard().setContents(
674             new StringSelection(output), Desktop.instance);
675
676     Vector hiddenColumns = null;
677     if (av.hasHiddenColumns)
678     {
679       hiddenColumns = new Vector();
680       for (int i = 0; i < av.getColumnSelection().getHiddenColumns().size(); i++)
681       {
682         int[] region = (int[]) av.getColumnSelection().getHiddenColumns()
683                 .elementAt(i);
684
685         hiddenColumns.addElement(new int[]
686         { region[0], region[1] });
687       }
688     }
689
690     Desktop.jalviewClipboard = new Object[]
691     { seqs, ds, // what is the dataset of a consensus sequence ? need to flag
692         // sequence as special.
693         hiddenColumns };
694   }
695
696   /**
697    * DOCUMENT ME!
698    * 
699    * @param g1
700    *          DOCUMENT ME!
701    */
702   public void paintComponent(Graphics g)
703   {
704
705     int width = getWidth();
706     if (width == 0)
707     {
708       width = ap.calculateIdWidth().width + 4;
709     }
710
711     Graphics2D g2 = (Graphics2D) g;
712     if (av.antiAlias)
713     {
714       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
715               RenderingHints.VALUE_ANTIALIAS_ON);
716     }
717
718     drawComponent(g2, width);
719
720   }
721
722   /**
723    * DOCUMENT ME!
724    * 
725    * @param g
726    *          DOCUMENT ME!
727    */
728   public void drawComponent(Graphics g, int width)
729   {
730     if (av.getFont().getSize() < 10)
731     {
732       g.setFont(font);
733     }
734     else
735     {
736       g.setFont(av.getFont());
737     }
738
739     FontMetrics fm = g.getFontMetrics(g.getFont());
740     g.setColor(Color.white);
741     g.fillRect(0, 0, getWidth(), getHeight());
742
743     g.translate(0, scrollOffset);
744     g.setColor(Color.black);
745
746     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
747     int fontHeight = g.getFont().getSize();
748     int y = 0;
749     int x = 0;
750     int graphExtras = 0;
751     int offset = 0;
752
753     if (aa != null)
754     {
755       for (int i = 0; i < aa.length; i++)
756       {
757         g.setColor(Color.black);
758
759         if (!aa[i].visible)
760         {
761           continue;
762         }
763
764         y += aa[i].height;
765
766         offset = -aa[i].height / 2;
767
768         if (aa[i].hasText)
769         {
770           offset += fm.getHeight() / 2;
771           offset -= fm.getDescent();
772         }
773         else
774           offset += fm.getDescent();
775
776         x = width - fm.stringWidth(aa[i].label) - 3;
777
778         if (aa[i].graphGroup > -1)
779         {
780           int groupSize = 0;
781           for (int gg = 0; gg < aa.length; gg++)
782           {
783             if (aa[gg].graphGroup == aa[i].graphGroup)
784             {
785               groupSize++;
786             }
787           }
788
789           if (groupSize * (fontHeight + 8) < aa[i].height)
790           {
791             graphExtras = (aa[i].height - (groupSize * (fontHeight + 8))) / 2;
792           }
793
794           for (int gg = 0; gg < aa.length; gg++)
795           {
796             if (aa[gg].graphGroup == aa[i].graphGroup)
797             {
798               x = width - fm.stringWidth(aa[gg].label) - 3;
799               g.drawString(aa[gg].label, x, y - graphExtras);
800               if (aa[gg].annotations[0] != null)
801               {
802                 g.setColor(aa[gg].annotations[0].colour);
803               }
804
805               g.drawLine(x, y - graphExtras - 3, x
806                       + fm.stringWidth(aa[gg].label), y - graphExtras - 3);
807
808               g.setColor(Color.black);
809               graphExtras += fontHeight + 8;
810             }
811           }
812         }
813         else
814         {
815           g.drawString(aa[i].label, x, y + offset);
816         }
817       }
818     }
819
820     if (resizePanel)
821     {
822       g.drawImage(image, 2, 0 - scrollOffset, this);
823     }
824     else if (dragEvent != null && aa != null)
825     {
826       g.setColor(Color.lightGray);
827       g.drawString(aa[selectedRow].label, dragEvent.getX(), dragEvent
828               .getY()
829               - scrollOffset);
830     }
831
832     if ((aa == null) || (aa.length < 1))
833     {
834       g.drawString("Right click", 2, 8);
835       g.drawString("to add annotation", 2, 18);
836     }
837   }
838 }