merge from 2_4_Release branch
[jalview.git] / src / jalview / gui / AnnotationLabels.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.4)
3  * Copyright (C) 2008 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 = av.getConsensusSeq();
218       if (cons != null)
219       {
220         copy_annotseqtoclipboard(cons);
221       }
222
223     }
224
225     ap.annotationPanel.adjustPanelHeight();
226     ap.annotationScroller.validate();
227     ap.paintAlignment(true);
228   }
229
230   /**
231    * DOCUMENT ME!
232    * 
233    * @param e
234    *                DOCUMENT ME!
235    */
236   boolean editLabelDescription(AlignmentAnnotation annotation)
237   {
238     EditNameDialog dialog = new EditNameDialog(annotation.label,
239             annotation.description, "       Annotation Name ",
240             "Annotation Description ", "Edit Annotation Name/Description");
241
242     if (!dialog.accept)
243     {
244       return false;
245     }
246
247     annotation.label = dialog.getName();
248
249     String text = dialog.getDescription();
250     if (text != null && text.length() == 0)
251     {
252       text = null;
253     }
254     annotation.description = text;
255
256     return true;
257   }
258
259   /**
260    * DOCUMENT ME!
261    * 
262    * @param evt
263    *                DOCUMENT ME!
264    */
265   public void mousePressed(MouseEvent evt)
266   {
267     getSelectedRow(evt.getY() - scrollOffset);
268     oldY = evt.getY();
269   }
270
271   /**
272    * DOCUMENT ME!
273    * 
274    * @param evt
275    *                DOCUMENT ME!
276    */
277   public void mouseReleased(MouseEvent evt)
278   {
279     int start = selectedRow;
280     getSelectedRow(evt.getY() - scrollOffset);
281     int end = selectedRow;
282
283     if (start != end)
284     {
285       // Swap these annotations
286       AlignmentAnnotation startAA = ap.av.alignment
287               .getAlignmentAnnotation()[start];
288       AlignmentAnnotation endAA = ap.av.alignment.getAlignmentAnnotation()[end];
289
290       ap.av.alignment.getAlignmentAnnotation()[end] = startAA;
291       ap.av.alignment.getAlignmentAnnotation()[start] = endAA;
292     }
293
294     resizePanel = false;
295     dragEvent = null;
296     repaint();
297     ap.annotationPanel.repaint();
298   }
299
300   /**
301    * DOCUMENT ME!
302    * 
303    * @param evt
304    *                DOCUMENT ME!
305    */
306   public void mouseEntered(MouseEvent evt)
307   {
308     if (evt.getY() < 10)
309     {
310       resizePanel = true;
311       repaint();
312     }
313   }
314
315   /**
316    * DOCUMENT ME!
317    * 
318    * @param evt
319    *                DOCUMENT ME!
320    */
321   public void mouseExited(MouseEvent evt)
322   {
323     if (dragEvent == null)
324     {
325       resizePanel = false;
326       repaint();
327     }
328   }
329
330   /**
331    * DOCUMENT ME!
332    * 
333    * @param evt
334    *                DOCUMENT ME!
335    */
336   public void mouseDragged(MouseEvent evt)
337   {
338     dragEvent = evt;
339
340     if (resizePanel)
341     {
342       Dimension d = ap.annotationScroller.getPreferredSize();
343       int dif = evt.getY() - oldY;
344
345       dif /= ap.av.charHeight;
346       dif *= ap.av.charHeight;
347
348       if ((d.height - dif) > 20)
349       {
350         ap.annotationScroller.setPreferredSize(new Dimension(d.width,
351                 d.height - dif));
352         d = ap.annotationSpaceFillerHolder.getPreferredSize();
353         ap.annotationSpaceFillerHolder.setPreferredSize(new Dimension(
354                 d.width, d.height - dif));
355         ap.paintAlignment(true);
356       }
357
358       ap.addNotify();
359     }
360     else
361     {
362       repaint();
363     }
364   }
365
366   /**
367    * DOCUMENT ME!
368    * 
369    * @param evt
370    *                DOCUMENT ME!
371    */
372   public void mouseMoved(MouseEvent evt)
373   {
374     resizePanel = evt.getY() < 10;
375
376     getSelectedRow(evt.getY() - scrollOffset);
377
378     if (selectedRow > -1
379             && ap.av.alignment.getAlignmentAnnotation().length > selectedRow)
380     {
381       AlignmentAnnotation aa = ap.av.alignment.getAlignmentAnnotation()[selectedRow];
382
383       StringBuffer desc = new StringBuffer("<html>");
384
385       if (aa.description != null
386               && !aa.description.equals("New description"))
387       {
388         desc.append(aa.getDescription(true));
389         if (aa.hasScore)
390           desc.append("<br>");
391       }
392       if (aa.hasScore())
393       {
394         desc.append("Score: " + aa.score);
395       }
396
397       if (desc.length() != 6)
398       {
399         desc.append("</html>");
400         this.setToolTipText(desc.toString());
401       }
402       else
403         this.setToolTipText(null);
404     }
405
406   }
407
408   /**
409    * DOCUMENT ME!
410    * 
411    * @param evt
412    *                DOCUMENT ME!
413    */
414   public void mouseClicked(MouseEvent evt)
415   {
416     if (!SwingUtilities.isRightMouseButton(evt))
417     {
418       return;
419     }
420
421     AlignmentAnnotation[] aa = ap.av.alignment.getAlignmentAnnotation();
422
423     JPopupMenu pop = new JPopupMenu("Annotations");
424     JMenuItem item = new JMenuItem(ADDNEW);
425     item.addActionListener(this);
426
427     if ((aa == null) || (aa.length == 0))
428     {
429       item = new JMenuItem(SHOWALL);
430       item.addActionListener(this);
431       pop.add(item);
432       pop.show(this, evt.getX(), evt.getY());
433       return;
434     }
435
436     pop.add(item);
437     item = new JMenuItem(EDITNAME);
438     item.addActionListener(this);
439     pop.add(item);
440     item = new JMenuItem(HIDE);
441     item.addActionListener(this);
442     pop.add(item);
443     item = new JMenuItem(DELETE);
444     item.addActionListener(this);
445     pop.add(item);
446     item = new JMenuItem(SHOWALL);
447     item.addActionListener(this);
448     pop.add(item);
449     item = new JMenuItem(OUTPUT_TEXT);
450     item.addActionListener(this);
451     pop.add(item);
452     // annotation object should be typed
453     if (selectedRow < aa.length && aa[selectedRow] == ap.av.consensus)
454     {
455       pop.addSeparator();
456       final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
457               "Ignore Gaps In Consensus", ap.av.getIgnoreGapsConsensus());
458       cbmi.addActionListener(new ActionListener()
459       {
460         public void actionPerformed(ActionEvent e)
461         {
462           ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
463         }
464       });
465       pop.add(cbmi);
466       final JMenuItem consclipbrd = new JMenuItem(COPYCONS_SEQ);
467       consclipbrd.addActionListener(this);
468       pop.add(consclipbrd);
469     }
470
471     pop.show(this, evt.getX(), evt.getY());
472   }
473
474   /**
475    * do a single sequence copy to jalview and the system clipboard
476    * 
477    * @param sq
478    *                sequence to be copied to clipboard
479    */
480   protected void copy_annotseqtoclipboard(SequenceI sq)
481   {
482     SequenceI[] seqs = new SequenceI[]
483     { sq };
484     String[] omitHidden = null;
485     SequenceI[] dseqs = new SequenceI[]
486     { sq.getDatasetSequence() };
487     if (dseqs[0] == null)
488     {
489       dseqs[0] = new Sequence(sq);
490       dseqs[0].setSequence(jalview.analysis.AlignSeq.extractGaps(
491               jalview.util.Comparison.GapChars, sq.getSequenceAsString()));
492
493       sq.setDatasetSequence(dseqs[0]);
494     }
495     Alignment ds = new Alignment(dseqs);
496     if (av.hasHiddenColumns)
497     {
498       omitHidden = av.getColumnSelection().getVisibleSequenceStrings(0,
499               sq.getLength(), seqs);
500     }
501
502     String output = new FormatAdapter().formatSequences("Fasta", seqs,
503             omitHidden);
504
505     Toolkit.getDefaultToolkit().getSystemClipboard().setContents(
506             new StringSelection(output), Desktop.instance);
507
508     Vector hiddenColumns = null;
509     if (av.hasHiddenColumns)
510     {
511       hiddenColumns = new Vector();
512       for (int i = 0; i < av.getColumnSelection().getHiddenColumns().size(); i++)
513       {
514         int[] region = (int[]) av.getColumnSelection().getHiddenColumns()
515                 .elementAt(i);
516
517         hiddenColumns.addElement(new int[]
518         { region[0], region[1] });
519       }
520     }
521
522     Desktop.jalviewClipboard = new Object[]
523     { seqs, ds, // what is the dataset of a consensus sequence ? need to flag
524                 // sequence as special.
525         hiddenColumns };
526   }
527
528   /**
529    * DOCUMENT ME!
530    * 
531    * @param g1
532    *                DOCUMENT ME!
533    */
534   public void paintComponent(Graphics g)
535   {
536
537     int width = getWidth();
538     if (width == 0)
539     {
540       width = ap.calculateIdWidth().width + 4;
541     }
542
543     Graphics2D g2 = (Graphics2D) g;
544     if (av.antiAlias)
545     {
546       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
547               RenderingHints.VALUE_ANTIALIAS_ON);
548     }
549
550     drawComponent(g2, width);
551
552   }
553
554   /**
555    * DOCUMENT ME!
556    * 
557    * @param g
558    *                DOCUMENT ME!
559    */
560   public void drawComponent(Graphics g, int width)
561   {
562     if (av.getFont().getSize() < 10)
563     {
564       g.setFont(font);
565     }
566     else
567     {
568       g.setFont(av.getFont());
569     }
570
571     FontMetrics fm = g.getFontMetrics(g.getFont());
572     g.setColor(Color.white);
573     g.fillRect(0, 0, getWidth(), getHeight());
574
575     g.translate(0, scrollOffset);
576     g.setColor(Color.black);
577
578     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
579     int fontHeight = g.getFont().getSize();
580     int y = 0;
581     int x = 0;
582     int graphExtras = 0;
583     int offset = 0;
584
585     if (aa != null)
586     {
587       for (int i = 0; i < aa.length; i++)
588       {
589         g.setColor(Color.black);
590
591         if (!aa[i].visible)
592         {
593           continue;
594         }
595
596         y += aa[i].height;
597
598         offset = -aa[i].height / 2;
599
600         if (aa[i].hasText)
601         {
602           offset += fm.getHeight() / 2;
603           offset -= fm.getDescent();
604         }
605         else
606           offset += fm.getDescent();
607
608         x = width - fm.stringWidth(aa[i].label) - 3;
609
610         if (aa[i].graphGroup > -1)
611         {
612           int groupSize = 0;
613           for (int gg = 0; gg < aa.length; gg++)
614           {
615             if (aa[gg].graphGroup == aa[i].graphGroup)
616             {
617               groupSize++;
618             }
619           }
620
621           if (groupSize * (fontHeight + 8) < aa[i].height)
622           {
623             graphExtras = (aa[i].height - (groupSize * (fontHeight + 8))) / 2;
624           }
625
626           for (int gg = 0; gg < aa.length; gg++)
627           {
628             if (aa[gg].graphGroup == aa[i].graphGroup)
629             {
630               x = width - fm.stringWidth(aa[gg].label) - 3;
631               g.drawString(aa[gg].label, x, y - graphExtras);
632               if (aa[gg].annotations[0] != null)
633               {
634                 g.setColor(aa[gg].annotations[0].colour);
635               }
636
637               g.drawLine(x, y - graphExtras - 3, x
638                       + fm.stringWidth(aa[gg].label), y - graphExtras - 3);
639
640               g.setColor(Color.black);
641               graphExtras += fontHeight + 8;
642             }
643           }
644         }
645         else
646         {
647           g.drawString(aa[i].label, x, y + offset);
648         }
649       }
650     }
651
652     if (resizePanel)
653     {
654       g.drawImage(image, 2, 0 - scrollOffset, this);
655     }
656     else if (dragEvent != null && aa != null)
657     {
658       g.setColor(Color.lightGray);
659       g.drawString(aa[selectedRow].label, dragEvent.getX(), dragEvent
660               .getY()
661               - scrollOffset);
662     }
663
664     if ((aa == null) || (aa.length < 1))
665     {
666       g.drawString("Right click", 2, 8);
667       g.drawString("to add annotation", 2, 18);
668     }
669   }
670 }