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