adjustable annotation panel height and vertical scrollbar JAL-516,JAL-338,JAL-306
[jalview.git] / src / jalview / appletgui / AnnotationLabels.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.6)
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.appletgui;
19
20 import java.util.*;
21
22 import java.awt.*;
23 import java.awt.event.*;
24 import java.awt.image.BufferedImage;
25
26 import jalview.datamodel.*;
27
28 public class AnnotationLabels extends Panel implements ActionListener,
29         MouseListener, MouseMotionListener
30 {
31   Image image;
32
33   boolean active = false;
34
35   AlignmentPanel ap;
36
37   AlignViewport av;
38
39   boolean resizing = false;
40
41   int oldY, mouseX;
42
43   static String ADDNEW = "Add New Row";
44
45   static String EDITNAME = "Edit Label/Description";
46
47   static String HIDE = "Hide This Row";
48
49   static String SHOWALL = "Show All Hidden Rows";
50
51   static String OUTPUT_TEXT = "Show Values In Textbox";
52
53   static String COPYCONS_SEQ = "Copy Consensus Sequence";
54
55   int scrollOffset = 0;
56
57   int selectedRow = -1;
58
59   Tooltip tooltip;
60
61   private boolean hasHiddenRows;
62
63   public AnnotationLabels(AlignmentPanel ap)
64   {
65     this.ap = ap;
66     this.av = ap.av;
67     setLayout(null);
68
69     java.net.URL url = getClass().getResource("/images/idwidth.gif");
70     Image temp = null;
71
72     if (url != null)
73     {
74       temp = java.awt.Toolkit.getDefaultToolkit().createImage(url);
75     }
76
77     try
78     {
79       MediaTracker mt = new MediaTracker(this);
80       mt.addImage(temp, 0);
81       mt.waitForID(0);
82     } catch (Exception ex)
83     {
84     }
85
86     BufferedImage bi = new BufferedImage(temp.getHeight(this),
87             temp.getWidth(this), BufferedImage.TYPE_INT_RGB);
88     Graphics2D g = (Graphics2D) bi.getGraphics();
89     g.rotate(Math.toRadians(90));
90     g.drawImage(temp, 0, -bi.getWidth(this), this);
91     image = (Image) bi;
92
93     addMouseListener(this);
94     addMouseMotionListener(this);
95   }
96
97   public AnnotationLabels(AlignViewport av)
98   {
99     this.av = av;
100   }
101
102   public void setScrollOffset(int y)
103   {
104     scrollOffset = y;
105     repaint();
106   }
107
108   /**
109    * 
110    * @param y
111    * @return -2 if no rows are visible at all, -1 if no visible rows were
112    *         selected
113    */
114   int getSelectedRow(int y)
115   {
116     int row = -2;
117     AlignmentAnnotation[] aa = ap.av.alignment.getAlignmentAnnotation();
118
119     if (aa == null)
120     {
121       return row;
122     }
123     int height = 0;
124     for (int i = 0; i < aa.length; i++)
125     {
126       row = -1;
127       if (!aa[i].visible)
128       {
129         continue;
130       }
131       height += aa[i].height;
132       if (y < height)
133       {
134         row = i;
135         break;
136       }
137     }
138
139     return row;
140   }
141
142   public void actionPerformed(ActionEvent evt)
143   {
144     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
145
146     if (evt.getActionCommand().equals(ADDNEW))
147     {
148       AlignmentAnnotation newAnnotation = new AlignmentAnnotation("", null,
149               new Annotation[ap.av.alignment.getWidth()]);
150
151       if (!editLabelDescription(newAnnotation))
152       {
153         return;
154       }
155
156       ap.av.alignment.addAnnotation(newAnnotation);
157       ap.av.alignment.setAnnotationIndex(newAnnotation, 0);
158     }
159     else if (evt.getActionCommand().equals(EDITNAME))
160     {
161       editLabelDescription(aa[selectedRow]);
162     }
163     else if (evt.getActionCommand().equals(HIDE))
164     {
165       aa[selectedRow].visible = false;
166     }
167     else if (evt.getActionCommand().equals(SHOWALL))
168     {
169       for (int i = 0; i < aa.length; i++)
170       {
171         aa[i].visible = (aa[i].annotations == null) ? false : true;
172       }
173     }
174     else if (evt.getActionCommand().equals(OUTPUT_TEXT))
175     {
176       CutAndPasteTransfer cap = new CutAndPasteTransfer(false,
177               ap.alignFrame);
178       Frame frame = new Frame();
179       frame.add(cap);
180       jalview.bin.JalviewLite.addFrame(frame, ap.alignFrame.getTitle()
181               + " - " + aa[selectedRow].label, 500, 100);
182       cap.setText(aa[selectedRow].toString());
183     }
184     else if (evt.getActionCommand().equals(COPYCONS_SEQ))
185     {
186       SequenceI cons = av.getConsensusSeq();
187       if (cons != null)
188       {
189         copy_annotseqtoclipboard(cons);
190       }
191
192     }
193     ap.annotationPanel.adjustPanelHeight();
194     setSize(getSize().width, ap.annotationPanel.getSize().height);
195     ap.validate();
196     ap.paintAlignment(true);
197   }
198
199   boolean editLabelDescription(AlignmentAnnotation annotation)
200   {
201     Checkbox padGaps = new Checkbox("Fill Empty Gaps With \""
202             + ap.av.getGapCharacter() + "\"", annotation.padGaps);
203
204     EditNameDialog dialog = new EditNameDialog(annotation.label,
205             annotation.description, "      Annotation Label",
206             "Annotation Description", ap.alignFrame,
207             "Edit Annotation Name / Description", 500, 180, false);
208
209     Panel empty = new Panel(new FlowLayout());
210     empty.add(padGaps);
211     dialog.add(empty);
212     dialog.pack();
213
214     dialog.setVisible(true);
215
216     if (dialog.accept)
217     {
218       annotation.label = dialog.getName();
219       annotation.description = dialog.getDescription();
220       annotation.setPadGaps(padGaps.getState(), av.getGapCharacter());
221       repaint();
222       return true;
223     }
224     else
225       return false;
226
227   }
228
229   boolean resizePanel = false;
230
231   public void mouseMoved(MouseEvent evt)
232   {
233     resizePanel = evt.getY() < 10;
234
235     int row = getSelectedRow(evt.getY() + scrollOffset);
236
237     if (row > -1)
238     {
239       if (tooltip == null)
240       {
241         tooltip = new Tooltip(
242                 ap.av.alignment.getAlignmentAnnotation()[row]
243                         .getDescription(true),
244                 this);
245       }
246       else
247       {
248         tooltip.setTip(ap.av.alignment.getAlignmentAnnotation()[row]
249                 .getDescription(true));
250       }
251     }
252     else if (tooltip != null)
253     {
254       tooltip.setTip("");
255     }
256
257   }
258
259   MouseEvent dragEvent = null;
260
261   public void mouseDragged(MouseEvent evt)
262   {
263     dragEvent = evt;
264
265     if (resizePanel)
266     {
267       Dimension d = ap.annotationPanelHolder.getSize(),e = ap.annotationSpaceFillerHolder.getSize();;
268       int dif = evt.getY() - oldY;
269
270       dif /= ap.av.charHeight;
271       dif *= ap.av.charHeight;
272
273       if ((d.height - dif) > 20)
274       {
275         
276         setPreferredSize(new Dimension(e.width,d.height-dif));
277         ap.annotationSpaceFillerHolder.setPreferredSize(new Dimension(e.width, d.height - dif));
278         ap.annotationPanelHolder.setPreferredSize(new Dimension(d.width, d.height - dif));
279         ap.apvscroll.setValues(ap.apvscroll.getValue(), d.height-dif, 0, ap.annotationPanel.adjustPanelHeight(false));
280         
281         ap.validate();
282         //ap.paintAlignment(true);
283       }
284
285       ap.addNotify();
286     }
287     else
288     {
289       repaint();
290     }
291   }
292
293   public void mouseClicked(MouseEvent evt)
294   {
295   }
296
297   public void mouseReleased(MouseEvent evt)
298   {
299     resizePanel = false;
300     dragEvent = null;
301     repaint();
302     ap.annotationPanel.repaint();
303   }
304
305   public void mouseEntered(MouseEvent evt)
306   {
307     if (evt.getY() < 10)
308     {
309       resizePanel = true;
310       repaint();
311     }
312   }
313
314   public void mouseExited(MouseEvent evt)
315   {
316
317     if (dragEvent == null)
318     {
319       resizePanel = false;
320     }
321     else
322     {
323       if (!resizePanel)
324       {
325         dragEvent = null;
326       }
327     }
328     repaint();
329   }
330
331   public void mousePressed(MouseEvent evt)
332   {
333     oldY = evt.getY();
334     // todo: move below to mouseClicked ?
335     selectedRow = getSelectedRow(evt.getY() + scrollOffset);
336
337     AlignmentAnnotation[] aa = ap.av.alignment.getAlignmentAnnotation();
338
339     // DETECT RIGHT MOUSE BUTTON IN AWT
340     if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
341     {
342
343       PopupMenu popup = new PopupMenu("Annotations");
344
345       MenuItem item = new MenuItem(ADDNEW);
346       item.addActionListener(this);
347       popup.add(item);
348       if (selectedRow < 0)
349       {
350         // this never happens at moment: - see comment on JAL-563
351         if (hasHiddenRows)
352         {
353           item = new MenuItem(SHOWALL);
354           item.addActionListener(this);
355           popup.add(item);
356         }
357         this.add(popup);
358         popup.show(this, evt.getX(), evt.getY());
359         return;
360       }
361       // add the rest if there are actually rows to show
362       item = new MenuItem(EDITNAME);
363       item.addActionListener(this);
364       popup.add(item);
365       item = new MenuItem(HIDE);
366       item.addActionListener(this);
367       popup.add(item);
368       if (hasHiddenRows)
369       {
370         item = new MenuItem(SHOWALL);
371         item.addActionListener(this);
372         popup.add(item);
373       }
374       this.add(popup);
375       item = new MenuItem(OUTPUT_TEXT);
376       item.addActionListener(this);
377       popup.add(item);
378
379       if (aa[selectedRow] == ap.av.consensus)
380       {
381         popup.addSeparator();
382         final CheckboxMenuItem cbmi = new CheckboxMenuItem(
383                 "Ignore Gaps In Consensus", ap.av.getIgnoreGapsConsensus());
384
385         cbmi.addItemListener(new ItemListener()
386         {
387           public void itemStateChanged(ItemEvent e)
388           {
389             ap.av.setIgnoreGapsConsensus(cbmi.getState());
390             ap.paintAlignment(true);
391           }
392         });
393         popup.add(cbmi);
394         item = new MenuItem(COPYCONS_SEQ);
395         item.addActionListener(this);
396         popup.add(item);
397       }
398
399       popup.show(this, evt.getX(), evt.getY());
400     }
401     else
402     {
403       // selection action.
404       if (selectedRow > -1 && selectedRow < aa.length)
405       {
406         if (aa[selectedRow].groupRef != null)
407         {
408           if (evt.getClickCount() >= 2)
409           {
410             // todo: make the ap scroll to the selection - not necessary, first
411             // click highlights/scrolls, second selects
412             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
413             ap.av.setSelectionGroup(// new SequenceGroup(
414             aa[selectedRow].groupRef); // );
415             ap.av.sendSelection();
416             ap.paintAlignment(false);
417             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
418           }
419           else
420           {
421             ap.seqPanel.ap.idPanel
422                     .highlightSearchResults(aa[selectedRow].groupRef
423                             .getSequences(null));
424           }
425           return;
426         }
427         else if (aa[selectedRow].sequenceRef != null)
428         {
429           Vector sr = new Vector();
430           sr.addElement(aa[selectedRow].sequenceRef);
431           if (evt.getClickCount() == 1)
432           {
433             ap.seqPanel.ap.idPanel.highlightSearchResults(sr);
434           }
435           else if (evt.getClickCount() >= 2)
436           {
437             ap.seqPanel.ap.idPanel.highlightSearchResults(null);
438             SequenceGroup sg = new SequenceGroup();
439             sg.addSequence(aa[selectedRow].sequenceRef, false);
440             ap.av.setSelectionGroup(sg);
441             ap.paintAlignment(false);
442             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
443             ap.av.sendSelection();
444           }
445
446         }
447       }
448
449     }
450   }
451
452   /**
453    * DOCUMENT ME!
454    * 
455    * @param e
456    *          DOCUMENT ME!
457    */
458   protected void copy_annotseqtoclipboard(SequenceI sq)
459   {
460     if (sq == null || sq.getLength() < 1)
461     {
462       return;
463     }
464     jalview.appletgui.AlignFrame.copiedSequences = new StringBuffer();
465     jalview.appletgui.AlignFrame.copiedSequences.append(sq.getName() + "\t"
466             + sq.getStart() + "\t" + sq.getEnd() + "\t"
467             + sq.getSequenceAsString() + "\n");
468     if (av.hasHiddenColumns)
469     {
470       jalview.appletgui.AlignFrame.copiedHiddenColumns = new Vector();
471       for (int i = 0; i < av.getColumnSelection().getHiddenColumns().size(); i++)
472       {
473         int[] region = (int[]) av.getColumnSelection().getHiddenColumns()
474                 .elementAt(i);
475
476         jalview.appletgui.AlignFrame.copiedHiddenColumns
477                 .addElement(new int[]
478                 { region[0], region[1] });
479       }
480     }
481   }
482
483   public void update(Graphics g)
484   {
485     paint(g);
486   }
487
488   public void paint(Graphics g)
489   {
490     int w = getSize().width;
491     if (image == null || w != image.getWidth(this))
492     {
493       image = createImage(w, ap.annotationPanel.getSize().height);
494     }
495
496     drawComponent(image.getGraphics(), w);
497     g.drawImage(image, 0, 0, this);
498   }
499
500   public void drawComponent(Graphics g, int width)
501   {
502     g.setFont(av.getFont());
503     FontMetrics fm = g.getFontMetrics(av.getFont());
504     g.setColor(Color.white);
505     g.fillRect(0, 0, getSize().width, getSize().height);
506
507     g.translate(0, -scrollOffset);
508     g.setColor(Color.black);
509
510     AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation();
511     int y = 0, fy = g.getFont().getSize();
512     int x = 0, offset;
513
514     if (aa != null)
515     {
516       hasHiddenRows = false;
517       for (int i = 0; i < aa.length; i++)
518       {
519         if (!aa[i].visible)
520         {
521           hasHiddenRows = true;
522           continue;
523         }
524
525         x = width - fm.stringWidth(aa[i].label) - 3;
526
527         y += aa[i].height;
528         offset = -(aa[i].height - fy) / 2;
529
530         g.drawString(aa[i].label, x, y + offset);
531       }
532     }
533     g.translate(0, +scrollOffset);
534     if (resizePanel)
535     {
536       g.setColor(Color.red);
537       g.setPaintMode();
538       g.drawLine(2, 8, 5, 2);
539       g.drawLine(5, 2, 8, 8);
540     }
541     else if (dragEvent != null && aa != null)
542     {
543       g.setColor(Color.lightGray);
544       g.drawString(aa[selectedRow].label, dragEvent.getX(),
545               dragEvent.getY());
546     }
547
548     if ((aa == null) || (aa.length < 1))
549     {
550       g.setColor(Color.black);
551       g.drawString("Right click", 2, 8);
552       g.drawString("to add annotation", 2, 18);
553     }
554   }
555
556 }