Take scrollOffset into account
[jalview.git] / src / jalview / gui / AnnotationLabels.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer
3  * Copyright (C) 2006 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 jalview.datamodel.*;
22 import jalview.io.FormatAdapter;
23
24 import java.awt.*;
25 import java.awt.datatransfer.StringSelection;
26 import java.awt.event.*;
27 import java.awt.image.*;
28 import java.util.Vector;
29
30 import javax.swing.*;
31
32
33 /**
34  * DOCUMENT ME!
35  *
36  * @author $author$
37  * @version $Revision$
38  */
39 public class AnnotationLabels extends JPanel implements MouseListener,
40     MouseMotionListener, ActionListener
41 {
42     static String ADDNEW = "Add New Row";
43     static String HIDE = "Hide This Row";
44     static String DELETE = "Delete This Row";
45     static String SHOWALL = "Show All Hidden Rows";
46     static String OUTPUT_TEXT = "Export Annotation";
47     static String COPYCONS_SEQ = "Copy Consensus Sequence";
48     boolean resizePanel = false;
49     Image image;
50     AlignmentPanel ap;
51     AlignViewport av;
52     boolean resizing = false;
53     MouseEvent dragEvent;
54     int oldY;
55     int selectedRow;
56     int scrollOffset = 0;
57     Font font = new Font("Arial", Font.PLAIN, 11);
58
59
60     /**
61      * Creates a new AnnotationLabels object.
62      *
63      * @param ap DOCUMENT ME!
64      */
65     public AnnotationLabels(AlignmentPanel ap)
66     {
67         this.ap = ap;
68         av = ap.av;
69         ToolTipManager.sharedInstance().registerComponent(this);
70
71         java.net.URL url = getClass().getResource("/images/idwidth.gif");
72         Image temp = null;
73
74         if (url != null)
75         {
76             temp = java.awt.Toolkit.getDefaultToolkit().createImage(url);
77         }
78
79         try
80         {
81             MediaTracker mt = new MediaTracker(this);
82             mt.addImage(temp, 0);
83             mt.waitForID(0);
84         }
85         catch (Exception ex)
86         {
87         }
88
89         BufferedImage bi = new BufferedImage(temp.getHeight(this),
90                 temp.getWidth(this), BufferedImage.TYPE_INT_RGB);
91         Graphics2D g = (Graphics2D) bi.getGraphics();
92         g.rotate(Math.toRadians(90));
93         g.drawImage(temp, 0, -bi.getWidth(this), this);
94         image = (Image) bi;
95
96         addMouseListener(this);
97         addMouseMotionListener(this);
98     }
99
100     public AnnotationLabels(AlignViewport av)
101     {
102       this.av = av;
103     }
104
105     /**
106      * DOCUMENT ME!
107      *
108      * @param y DOCUMENT ME!
109      */
110     public void setScrollOffset(int y)
111     {
112         scrollOffset = y;
113         repaint();
114     }
115
116     void getSelectedRow(int y)
117     {
118       int height = 0;
119       AlignmentAnnotation[] aa = ap.av.alignment.getAlignmentAnnotation();
120
121       if(aa!=null)
122       {
123         for (int i = 0; i < aa.length; i++)
124         {
125           if (!aa[i].visible)
126           {
127             continue;
128           }
129
130           height += aa[i].height;
131
132           if (y < height)
133           {
134             selectedRow = i;
135
136             break;
137           }
138         }
139       }
140     }
141
142     /**
143      * DOCUMENT ME!
144      *
145      * @param evt DOCUMENT ME!
146      */
147     public void actionPerformed(ActionEvent evt)
148     {
149         int dif = 0;
150         AlignmentAnnotation[] aa = ap.av.alignment.getAlignmentAnnotation();
151
152         if (evt.getActionCommand().equals(ADDNEW))
153         {
154             String label = JOptionPane.showInputDialog(this,
155                     "Label for annotation");
156
157             if (label == null)
158             {
159                 label = "";
160             }
161
162             AlignmentAnnotation newAnnotation = new AlignmentAnnotation(label,
163                     "New description",
164                     new Annotation[ap.av.alignment.getWidth()]);
165
166             ap.av.alignment.addAnnotation(newAnnotation);
167             ap.av.alignment.setAnnotationIndex(newAnnotation, 0);
168             if (aa != null)
169               dif = aa[aa.length - 1].height;
170         }
171         else if (evt.getActionCommand().equals(HIDE))
172         {
173             aa[selectedRow].visible = false;
174
175             if (aa[selectedRow].label.equals("Quality"))
176             {
177                 ap.av.quality = null;
178             }
179
180             dif = aa[selectedRow].height * -1;
181         }
182         else if (evt.getActionCommand().equals(DELETE))
183         {
184             ap.av.alignment.deleteAnnotation(aa[selectedRow]);
185             dif = aa[selectedRow].height * -1;
186         }
187         else if (evt.getActionCommand().equals(SHOWALL))
188         {
189             for (int i = 0; i < aa.length; i++)
190             {
191                 if (!aa[i].visible)
192                 {
193                     dif += aa[i].height;
194                     aa[i].visible = true;
195                 }
196             }
197         }
198         else if (evt.getActionCommand().equals(OUTPUT_TEXT))
199         {
200           new AnnotationExporter().exportAnnotations(
201               ap,
202               new AlignmentAnnotation[]
203               {aa[selectedRow]}
204               );
205         }
206         else if (evt.getActionCommand().equals(COPYCONS_SEQ))
207         {
208           SequenceI cons=av.getConsensusSeq();
209           if (cons!=null)
210             copy_annotseqtoclipboard(cons);
211
212         }
213
214         ap.annotationPanel.adjustPanelHeight();
215         ap.annotationScroller.validate();
216         ap.repaint();
217     }
218
219     /**
220      * DOCUMENT ME!
221      *
222      * @param evt DOCUMENT ME!
223      */
224     public void mousePressed(MouseEvent evt)
225     {
226         getSelectedRow(evt.getY() - scrollOffset);
227         oldY = evt.getY();
228     }
229
230     /**
231      * DOCUMENT ME!
232      *
233      * @param evt DOCUMENT ME!
234      */
235     public void mouseReleased(MouseEvent evt)
236     {
237         int start = selectedRow;
238         getSelectedRow(evt.getY() - scrollOffset);
239         int end = selectedRow;
240
241         if(start!=end)
242         {
243           //Swap these annotations
244           AlignmentAnnotation startAA = ap.av.alignment.getAlignmentAnnotation()[start];
245           AlignmentAnnotation endAA = ap.av.alignment.getAlignmentAnnotation()[end];
246
247
248           ap.av.alignment.getAlignmentAnnotation()[end] = startAA;
249           ap.av.alignment.getAlignmentAnnotation()[start] = endAA;
250         }
251
252         resizePanel = false;
253         dragEvent = null;
254         repaint();
255         ap.annotationPanel.repaint();
256     }
257
258     /**
259      * DOCUMENT ME!
260      *
261      * @param evt DOCUMENT ME!
262      */
263     public void mouseEntered(MouseEvent evt)
264     {
265       if(evt.getY()<10)
266       {
267         resizePanel = true;
268         repaint();
269       }
270     }
271
272     /**
273      * DOCUMENT ME!
274      *
275      * @param evt DOCUMENT ME!
276      */
277     public void mouseExited(MouseEvent evt)
278     {
279       if(dragEvent == null)
280       {
281         resizePanel = false;
282         repaint();
283       }
284     }
285
286     /**
287      * DOCUMENT ME!
288      *
289      * @param evt DOCUMENT ME!
290      */
291     public void mouseDragged(MouseEvent evt)
292     {
293       dragEvent = evt;
294
295       if (resizePanel)
296       {
297         Dimension d = ap.annotationScroller.getPreferredSize();
298         int dif = evt.getY() - oldY;
299
300         dif /= ap.av.charHeight;
301         dif *= ap.av.charHeight;
302
303         if ( (d.height - dif) > 20)
304         {
305           ap.annotationScroller.setPreferredSize(new Dimension(d.width,
306               d.height - dif));
307           d = ap.annotationSpaceFillerHolder.getPreferredSize();
308           ap.annotationSpaceFillerHolder.setPreferredSize(new Dimension(
309               d.width, d.height - dif));
310           ap.repaint();
311         }
312
313         ap.addNotify();
314       }
315       else
316         repaint();
317     }
318
319     /**
320      * DOCUMENT ME!
321      *
322      * @param evt DOCUMENT ME!
323      */
324     public void mouseMoved(MouseEvent evt)
325     {
326       resizePanel = evt.getY()<10;
327
328       getSelectedRow(evt.getY() - scrollOffset);
329
330       if(selectedRow>-1)
331       {
332         setToolTipText(ap.av.alignment.
333             getAlignmentAnnotation()[selectedRow].description);
334
335       }
336       repaint();
337     }
338
339     /**
340      * DOCUMENT ME!
341      *
342      * @param evt DOCUMENT ME!
343      */
344     public void mouseClicked(MouseEvent evt)
345     {
346        if(!SwingUtilities.isRightMouseButton(evt))
347          return;
348
349         AlignmentAnnotation[] aa = ap.av.alignment.getAlignmentAnnotation();
350
351         if ((aa == null) || (aa.length == 0))
352         {
353             JPopupMenu pop = new JPopupMenu("Annotations");
354             JMenuItem item = new JMenuItem(ADDNEW);
355             item.addActionListener(this);
356             pop.add(item);
357             pop.show(this, evt.getX(), evt.getY());
358
359             return;
360         }
361
362
363         JPopupMenu pop = new JPopupMenu("Annotations");
364         JMenuItem item = new JMenuItem(ADDNEW);
365         item.addActionListener(this);
366         pop.add(item);
367         item = new JMenuItem(HIDE);
368         item.addActionListener(this);
369         pop.add(item);
370         item = new JMenuItem(DELETE);
371         item.addActionListener(this);
372         pop.add(item);
373         item = new JMenuItem(SHOWALL);
374         item.addActionListener(this);
375         pop.add(item);
376         item = new JMenuItem(OUTPUT_TEXT);
377         item.addActionListener(this);
378         pop.add(item);
379         // annotation object should be typed
380         if (aa[selectedRow].label.equals("Consensus"))
381         {
382           pop.addSeparator();
383           final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
384               "Ignore Gaps In Consensus",
385               ap.av.getIgnoreGapsConsensus());
386           cbmi.addActionListener(new ActionListener()
387               {public void actionPerformed(ActionEvent e)
388                {
389                  ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
390                }
391               });
392           pop.add(cbmi);
393           final JMenuItem consclipbrd = new JMenuItem(COPYCONS_SEQ);
394           consclipbrd.addActionListener(this);
395           pop.add(consclipbrd);
396         }
397
398         pop.show(this, evt.getX(), evt.getY());
399     }
400     /**
401      * do a single sequence copy to jalview and the system clipboard
402      *
403      * @param sq sequence to be copied to clipboard
404      */
405     protected void copy_annotseqtoclipboard(SequenceI sq)
406     {
407       SequenceI [] seqs = new SequenceI[] { sq };
408       String[] omitHidden = null;
409       SequenceI [] dseqs=new SequenceI[] { sq.getDatasetSequence()};
410       if (dseqs[0]==null) {
411         dseqs[0] = new Sequence(sq);
412         dseqs[0].setSequence(
413             jalview.analysis.AlignSeq.extractGaps(
414                 jalview.util.Comparison.GapChars,
415                 sq.getSequenceAsString()));
416
417         sq.setDatasetSequence(dseqs[0]);
418       }
419       Alignment ds=new Alignment(dseqs);
420       if (av.hasHiddenColumns)
421       {
422         omitHidden = av.getColumnSelection().getVisibleSequenceStrings(0, sq.getLength(), seqs);
423       }
424
425       String output = new FormatAdapter().formatSequences(
426           "Fasta",
427           seqs,
428           omitHidden);
429
430
431       Toolkit.getDefaultToolkit().getSystemClipboard()
432           .setContents(new StringSelection(output), Desktop.instance);
433
434       Vector hiddenColumns = null;
435       if(av.hasHiddenColumns)
436       {
437         hiddenColumns =new Vector();
438         for(int i=0; i<av.getColumnSelection().getHiddenColumns().size(); i++)
439         {
440           int[] region = (int[])
441               av.getColumnSelection().getHiddenColumns().elementAt(i);
442
443           hiddenColumns.addElement(new int[]{region[0],
444                             region[1]});
445         }
446       }
447
448       Desktop.jalviewClipboard = new Object[]{ seqs,
449           ds, // what is the dataset of a consensus sequence ? need to flag sequence as special.
450           hiddenColumns};
451     }
452     /**
453      * DOCUMENT ME!
454      *
455      * @param g1 DOCUMENT ME!
456      */
457     public void paintComponent(Graphics g)
458     {
459
460         int width = getWidth();
461         if(width==0)
462          width = ap.calculateIdWidth().width + 4;
463
464        Graphics2D g2 = (Graphics2D) g;
465        if(av.antiAlias)
466         g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
467             RenderingHints.VALUE_ANTIALIAS_ON);
468
469        drawComponent(g2, width);
470
471     }
472
473     /**
474      * DOCUMENT ME!
475      *
476      * @param g DOCUMENT ME!
477      */
478     public void drawComponent(Graphics g, int width)
479     {
480         if(av.getFont().getSize()<10)
481           g.setFont(font);
482         else
483           g.setFont(av.getFont());
484
485         FontMetrics fm = g.getFontMetrics(g.getFont());
486         g.setColor(Color.white);
487         g.fillRect(0, 0, getWidth(), getHeight());
488
489         g.translate(0, scrollOffset);
490         g.setColor(Color.black);
491
492         AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
493         int fontHeight = g.getFont().getSize();
494         int y = fontHeight;
495         int x = 0;
496         int graphExtras = 0;
497
498
499
500         if (aa != null)
501         {
502             for (int i = 0; i < aa.length; i++)
503             {
504                 if (!aa[i].visible)
505                 {
506                     continue;
507                 }
508
509                 x = width - fm.stringWidth(aa[i].label) - 3;
510
511                 if (aa[i].graph>0 && aa[i].graphHeight>0)
512                 {
513                     graphExtras = y;
514
515                     y += (aa[i].height / 3);
516
517                     if(aa[i].graphGroup<0)
518                         graphExtras = y + fontHeight;
519                 }
520
521                 if(aa[i].graphGroup>-1)
522                 {
523                   int groupSize = 0;
524                   for (int gg = 0; gg < aa.length; gg++)
525                   {
526                     if (aa[gg].graphGroup == aa[i].graphGroup)
527                       groupSize++;
528                   }
529
530                   if(groupSize * (fontHeight+8) < aa[i].height)
531                     graphExtras += (aa[i].height -( groupSize * (fontHeight+8)) )/2;
532
533                  for(int gg=0; gg<aa.length; gg++)
534                  {
535                    if(aa[gg].graphGroup==aa[i].graphGroup)
536                    {
537                      x = width - fm.stringWidth(aa[gg].label) - 3;
538                      g.drawString(aa[gg].label, x, graphExtras );
539                      if(aa[gg].annotations[0]!=null)
540                        g.setColor(aa[gg].annotations[0].colour);
541
542                      g.drawLine( x, graphExtras+3,
543                                  x+fm.stringWidth(aa[gg].label),
544                                  graphExtras+3);
545
546                      g.setColor(Color.black);
547                      graphExtras += fontHeight+8;
548                    }
549                  }
550                 }
551                 else
552                   g.drawString(aa[i].label, x, y);
553
554                 if (aa[i].graph>0)
555                 {
556                 /*  if (aa[i].graphLines != null)
557                   {
558                     for (int gl = 0; gl < aa[i].graphLines.size(); gl++)
559                     {
560                        x = width - fm.stringWidth(aa[i].getGraphLine(gl).label) - 3;
561                       g.drawString(aa[i].getGraphLine(gl).label, x, graphExtras);
562                       g.setColor(aa[i].getGraphLine(gl).colour);
563                       Graphics2D g2 = (Graphics2D) g;
564                       g2.setStroke(new BasicStroke(1,
565                           BasicStroke.CAP_SQUARE,
566                           BasicStroke.JOIN_ROUND, 3f,
567                           new float[]
568                           {5f, 3f}, 0f));
569
570                       graphExtras += 3;
571
572                       g.drawLine(x, graphExtras,
573                                  x+fm.stringWidth(aa[i].label),
574                                  graphExtras);
575                       g2.setStroke(new BasicStroke());
576                     }
577                   }*/
578                     y += ((2 * aa[i].height) / 3);
579                 }
580                 else
581                 {
582                     y += aa[i].height;
583                 }
584             }
585         }
586
587         if (resizePanel)
588         {
589           g.drawImage(image, 2, 0 - scrollOffset, this);
590         }
591         else if (dragEvent != null && aa!=null)
592         {
593           g.setColor(Color.lightGray);
594           g.drawString(aa[selectedRow].label, dragEvent.getX(), dragEvent.getY() - scrollOffset);
595         }
596
597
598         if ((aa == null) || (aa.length < 1))
599         {
600             g.drawString("Right click", 2, 8);
601             g.drawString("to add annotation", 2, 18);
602         }
603     }
604 }