get consensus sequence and set ignore gaps flag for group consensus row
[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     if (!SwingUtilities.isRightMouseButton(evt))
423     {
424       return;
425     }
426
427     AlignmentAnnotation[] aa = ap.av.alignment.getAlignmentAnnotation();
428
429     JPopupMenu pop = new JPopupMenu("Annotations");
430     JMenuItem item = new JMenuItem(ADDNEW);
431     item.addActionListener(this);
432
433     if ((aa == null) || (aa.length == 0))
434     {
435       item = new JMenuItem(SHOWALL);
436       item.addActionListener(this);
437       pop.add(item);
438       pop.show(this, evt.getX(), evt.getY());
439       return;
440     }
441
442     pop.add(item);
443     item = new JMenuItem(EDITNAME);
444     item.addActionListener(this);
445     pop.add(item);
446     item = new JMenuItem(HIDE);
447     item.addActionListener(this);
448     pop.add(item);
449     item = new JMenuItem(DELETE);
450     item.addActionListener(this);
451     pop.add(item);
452     item = new JMenuItem(SHOWALL);
453     item.addActionListener(this);
454     pop.add(item);
455     item = new JMenuItem(OUTPUT_TEXT);
456     item.addActionListener(this);
457     pop.add(item);
458     // TODO: annotation object should be typed for autocalculated/derived property methods
459     if (selectedRow < aa.length && aa[selectedRow].autoCalculated && aa[selectedRow].label.indexOf("Consensus")>-1)
460     {
461       pop.addSeparator();
462       // av and sequencegroup need to implement same interface for 
463       final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
464               "Ignore Gaps In Consensus", ap.av.getIgnoreGapsConsensus());
465       final AlignmentAnnotation aaa = aa[selectedRow];
466       cbmi.addActionListener(new ActionListener()
467       {
468         public void actionPerformed(ActionEvent e)
469         {
470           if (aaa.groupRef!=null)
471           {
472             aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState());
473           }
474           ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
475         }
476       });
477       pop.add(cbmi);
478       final JMenuItem consclipbrd = new JMenuItem(COPYCONS_SEQ);
479       consclipbrd.addActionListener(this);
480       pop.add(consclipbrd);
481     }
482
483     pop.show(this, evt.getX(), evt.getY());
484   }
485
486   /**
487    * do a single sequence copy to jalview and the system clipboard
488    * 
489    * @param sq
490    *                sequence to be copied to clipboard
491    */
492   protected void copy_annotseqtoclipboard(SequenceI sq)
493   {
494     SequenceI[] seqs = new SequenceI[]
495     { sq };
496     String[] omitHidden = null;
497     SequenceI[] dseqs = new SequenceI[]
498     { sq.getDatasetSequence() };
499     if (dseqs[0] == null)
500     {
501       dseqs[0] = new Sequence(sq);
502       dseqs[0].setSequence(jalview.analysis.AlignSeq.extractGaps(
503               jalview.util.Comparison.GapChars, sq.getSequenceAsString()));
504
505       sq.setDatasetSequence(dseqs[0]);
506     }
507     Alignment ds = new Alignment(dseqs);
508     if (av.hasHiddenColumns)
509     {
510       omitHidden = av.getColumnSelection().getVisibleSequenceStrings(0,
511               sq.getLength(), seqs);
512     }
513
514     String output = new FormatAdapter().formatSequences("Fasta", seqs,
515             omitHidden);
516
517     Toolkit.getDefaultToolkit().getSystemClipboard().setContents(
518             new StringSelection(output), Desktop.instance);
519
520     Vector hiddenColumns = null;
521     if (av.hasHiddenColumns)
522     {
523       hiddenColumns = new Vector();
524       for (int i = 0; i < av.getColumnSelection().getHiddenColumns().size(); i++)
525       {
526         int[] region = (int[]) av.getColumnSelection().getHiddenColumns()
527                 .elementAt(i);
528
529         hiddenColumns.addElement(new int[]
530         { region[0], region[1] });
531       }
532     }
533
534     Desktop.jalviewClipboard = new Object[]
535     { seqs, ds, // what is the dataset of a consensus sequence ? need to flag
536                 // sequence as special.
537         hiddenColumns };
538   }
539
540   /**
541    * DOCUMENT ME!
542    * 
543    * @param g1
544    *                DOCUMENT ME!
545    */
546   public void paintComponent(Graphics g)
547   {
548
549     int width = getWidth();
550     if (width == 0)
551     {
552       width = ap.calculateIdWidth().width + 4;
553     }
554
555     Graphics2D g2 = (Graphics2D) g;
556     if (av.antiAlias)
557     {
558       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
559               RenderingHints.VALUE_ANTIALIAS_ON);
560     }
561
562     drawComponent(g2, width);
563
564   }
565
566   /**
567    * DOCUMENT ME!
568    * 
569    * @param g
570    *                DOCUMENT ME!
571    */
572   public void drawComponent(Graphics g, int width)
573   {
574     if (av.getFont().getSize() < 10)
575     {
576       g.setFont(font);
577     }
578     else
579     {
580       g.setFont(av.getFont());
581     }
582
583     FontMetrics fm = g.getFontMetrics(g.getFont());
584     g.setColor(Color.white);
585     g.fillRect(0, 0, getWidth(), getHeight());
586
587     g.translate(0, scrollOffset);
588     g.setColor(Color.black);
589
590     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
591     int fontHeight = g.getFont().getSize();
592     int y = 0;
593     int x = 0;
594     int graphExtras = 0;
595     int offset = 0;
596
597     if (aa != null)
598     {
599       for (int i = 0; i < aa.length; i++)
600       {
601         g.setColor(Color.black);
602
603         if (!aa[i].visible)
604         {
605           continue;
606         }
607
608         y += aa[i].height;
609
610         offset = -aa[i].height / 2;
611
612         if (aa[i].hasText)
613         {
614           offset += fm.getHeight() / 2;
615           offset -= fm.getDescent();
616         }
617         else
618           offset += fm.getDescent();
619
620         x = width - fm.stringWidth(aa[i].label) - 3;
621
622         if (aa[i].graphGroup > -1)
623         {
624           int groupSize = 0;
625           for (int gg = 0; gg < aa.length; gg++)
626           {
627             if (aa[gg].graphGroup == aa[i].graphGroup)
628             {
629               groupSize++;
630             }
631           }
632
633           if (groupSize * (fontHeight + 8) < aa[i].height)
634           {
635             graphExtras = (aa[i].height - (groupSize * (fontHeight + 8))) / 2;
636           }
637
638           for (int gg = 0; gg < aa.length; gg++)
639           {
640             if (aa[gg].graphGroup == aa[i].graphGroup)
641             {
642               x = width - fm.stringWidth(aa[gg].label) - 3;
643               g.drawString(aa[gg].label, x, y - graphExtras);
644               if (aa[gg].annotations[0] != null)
645               {
646                 g.setColor(aa[gg].annotations[0].colour);
647               }
648
649               g.drawLine(x, y - graphExtras - 3, x
650                       + fm.stringWidth(aa[gg].label), y - graphExtras - 3);
651
652               g.setColor(Color.black);
653               graphExtras += fontHeight + 8;
654             }
655           }
656         }
657         else
658         {
659           g.drawString(aa[i].label, x, y + offset);
660         }
661       }
662     }
663
664     if (resizePanel)
665     {
666       g.drawImage(image, 2, 0 - scrollOffset, this);
667     }
668     else if (dragEvent != null && aa != null)
669     {
670       g.setColor(Color.lightGray);
671       g.drawString(aa[selectedRow].label, dragEvent.getX(), dragEvent
672               .getY()
673               - scrollOffset);
674     }
675
676     if ((aa == null) || (aa.length < 1))
677     {
678       g.drawString("Right click", 2, 8);
679       g.drawString("to add annotation", 2, 18);
680     }
681   }
682 }