Clover icon

jalviewX

  1. Project Clover database Wed Oct 31 2018 15:13:58 GMT
  2. Package jalview.gui

File AnnotationLabels.java

 

Coverage histogram

../../img/srcFileCovDistChart2.png
51% of files have more coverage

Code metrics

180
388
29
1
1,219
887
138
0.36
13.38
29
4.76

Classes

Class Line # Actions
AnnotationLabels 72 388 138 498
0.1658291516.6%
 

Contributing tests

This file is covered by 88 tests. .

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ The Jalview Authors
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
10    * of the License, or (at your option) any later version.
11    *
12    * Jalview is distributed in the hope that it will be useful, but
13    * WITHOUT ANY WARRANTY; without even the implied warranty
14    * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15    * PURPOSE. See the GNU General Public License for more details.
16    *
17    * You should have received a copy of the GNU General Public License
18    * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19    * The Jalview Authors are detailed in the 'AUTHORS' file.
20    */
21    package jalview.gui;
22   
23    import jalview.analysis.AlignSeq;
24    import jalview.analysis.AlignmentUtils;
25    import jalview.datamodel.Alignment;
26    import jalview.datamodel.AlignmentAnnotation;
27    import jalview.datamodel.Annotation;
28    import jalview.datamodel.HiddenColumns;
29    import jalview.datamodel.Sequence;
30    import jalview.datamodel.SequenceGroup;
31    import jalview.datamodel.SequenceI;
32    import jalview.io.FileFormat;
33    import jalview.io.FormatAdapter;
34    import jalview.util.Comparison;
35    import jalview.util.MessageManager;
36    import jalview.util.Platform;
37    import jalview.util.dialogrunner.RunResponse;
38   
39    import java.awt.Color;
40    import java.awt.Cursor;
41    import java.awt.Dimension;
42    import java.awt.Font;
43    import java.awt.FontMetrics;
44    import java.awt.Graphics;
45    import java.awt.Graphics2D;
46    import java.awt.RenderingHints;
47    import java.awt.Toolkit;
48    import java.awt.datatransfer.StringSelection;
49    import java.awt.event.ActionEvent;
50    import java.awt.event.ActionListener;
51    import java.awt.event.MouseEvent;
52    import java.awt.event.MouseListener;
53    import java.awt.event.MouseMotionListener;
54    import java.awt.geom.AffineTransform;
55    import java.util.Arrays;
56    import java.util.Collections;
57    import java.util.Iterator;
58    import java.util.regex.Pattern;
59   
60    import javax.swing.JCheckBoxMenuItem;
61    import javax.swing.JMenuItem;
62    import javax.swing.JPanel;
63    import javax.swing.JPopupMenu;
64    import javax.swing.SwingUtilities;
65    import javax.swing.ToolTipManager;
66   
67    /**
68    * The panel that holds the labels for alignment annotations, providing
69    * tooltips, context menus, drag to reorder rows, and drag to adjust panel
70    * height
71    */
 
72    public class AnnotationLabels extends JPanel
73    implements MouseListener, MouseMotionListener, ActionListener
74    {
75    /**
76    * width in pixels within which height adjuster arrows are shown and active
77    */
78    private static final int HEIGHT_ADJUSTER_WIDTH = 50;
79   
80    /**
81    * height in pixels for allowing height adjuster to be active
82    */
83    private static int HEIGHT_ADJUSTER_HEIGHT = 10;
84   
85    private static final Pattern LEFT_ANGLE_BRACKET_PATTERN = Pattern
86    .compile("<");
87   
88    private static final Font font = new Font("Arial", Font.PLAIN, 11);
89   
90    private static final String TOGGLE_LABELSCALE = MessageManager
91    .getString("label.scale_label_to_column");
92   
93    private static final String ADDNEW = MessageManager
94    .getString("label.add_new_row");
95   
96    private static final String EDITNAME = MessageManager
97    .getString("label.edit_label_description");
98   
99    private static final String HIDE = MessageManager
100    .getString("label.hide_row");
101   
102    private static final String DELETE = MessageManager
103    .getString("label.delete_row");
104   
105    private static final String SHOWALL = MessageManager
106    .getString("label.show_all_hidden_rows");
107   
108    private static final String OUTPUT_TEXT = MessageManager
109    .getString("label.export_annotation");
110   
111    private static final String COPYCONS_SEQ = MessageManager
112    .getString("label.copy_consensus_sequence");
113   
114    private final boolean debugRedraw = false;
115   
116    private AlignmentPanel ap;
117   
118    AlignViewport av;
119   
120    private MouseEvent dragEvent;
121   
122    private int oldY;
123   
124    private int selectedRow;
125   
126    private int scrollOffset = 0;
127   
128    private boolean hasHiddenRows;
129   
130    private boolean resizePanel = false;
131   
132    /**
133    * Creates a new AnnotationLabels object
134    *
135    * @param ap
136    */
 
137  217 toggle public AnnotationLabels(AlignmentPanel ap)
138    {
139  217 this.ap = ap;
140  217 av = ap.av;
141  217 ToolTipManager.sharedInstance().registerComponent(this);
142   
143  217 addMouseListener(this);
144  217 addMouseMotionListener(this);
145  217 addMouseWheelListener(ap.getAnnotationPanel());
146    }
147   
 
148  174 toggle public AnnotationLabels(AlignViewport av)
149    {
150  174 this.av = av;
151    }
152   
153    /**
154    * DOCUMENT ME!
155    *
156    * @param y
157    * DOCUMENT ME!
158    */
 
159  282 toggle public void setScrollOffset(int y)
160    {
161  282 scrollOffset = y;
162  282 repaint();
163    }
164   
165    /**
166    * sets selectedRow to -2 if no annotation preset, -1 if no visible row is at
167    * y
168    *
169    * @param y
170    * coordinate position to search for a row
171    */
 
172  0 toggle void getSelectedRow(int y)
173    {
174  0 int height = 0;
175  0 AlignmentAnnotation[] aa = ap.av.getAlignment()
176    .getAlignmentAnnotation();
177  0 selectedRow = -2;
178  0 if (aa != null)
179    {
180  0 for (int i = 0; i < aa.length; i++)
181    {
182  0 selectedRow = -1;
183  0 if (!aa[i].visible)
184    {
185  0 continue;
186    }
187   
188  0 height += aa[i].height;
189   
190  0 if (y < height)
191    {
192  0 selectedRow = i;
193   
194  0 break;
195    }
196    }
197    }
198    }
199   
200    /**
201    * DOCUMENT ME!
202    *
203    * @param evt
204    * DOCUMENT ME!
205    */
 
206  0 toggle @Override
207    public void actionPerformed(ActionEvent evt)
208    {
209  0 AlignmentAnnotation[] aa = ap.av.getAlignment()
210    .getAlignmentAnnotation();
211   
212  0 String action = evt.getActionCommand();
213  0 if (ADDNEW.equals(action))
214    {
215    /*
216    * non-returning dialog
217    */
218  0 AlignmentAnnotation newAnnotation = new AlignmentAnnotation(null,
219    null, new Annotation[ap.av.getAlignment().getWidth()]);
220  0 editLabelDescription(newAnnotation, true);
221    }
222  0 else if (EDITNAME.equals(action))
223    {
224    /*
225    * non-returning dialog
226    */
227  0 editLabelDescription(aa[selectedRow], false);
228    }
229  0 else if (HIDE.equals(action))
230    {
231  0 aa[selectedRow].visible = false;
232    }
233  0 else if (DELETE.equals(action))
234    {
235  0 ap.av.getAlignment().deleteAnnotation(aa[selectedRow]);
236  0 ap.av.getCalcManager().removeWorkerForAnnotation(aa[selectedRow]);
237    }
238  0 else if (SHOWALL.equals(action))
239    {
240  0 for (int i = 0; i < aa.length; i++)
241    {
242  0 if (!aa[i].visible && aa[i].annotations != null)
243    {
244  0 aa[i].visible = true;
245    }
246    }
247    }
248  0 else if (OUTPUT_TEXT.equals(action))
249    {
250  0 new AnnotationExporter(ap).exportAnnotation(aa[selectedRow]);
251    }
252  0 else if (COPYCONS_SEQ.equals(action))
253    {
254  0 SequenceI cons = null;
255  0 if (aa[selectedRow].groupRef != null)
256    {
257  0 cons = aa[selectedRow].groupRef.getConsensusSeq();
258    }
259    else
260    {
261  0 cons = av.getConsensusSeq();
262    }
263  0 if (cons != null)
264    {
265  0 copy_annotseqtoclipboard(cons);
266    }
267    }
268  0 else if (TOGGLE_LABELSCALE.equals(action))
269    {
270  0 aa[selectedRow].scaleColLabel = !aa[selectedRow].scaleColLabel;
271    }
272   
273  0 ap.refresh(true);
274    }
275   
276    /**
277    * Shows a dialog where the annotation name and description may be edited. If
278    * parameter addNew is true, then on confirmation, a new AlignmentAnnotation
279    * is added, else an existing annotation is updated.
280    *
281    * @param annotation
282    * @param addNew
283    */
 
284  0 toggle void editLabelDescription(AlignmentAnnotation annotation, boolean addNew)
285    {
286  0 String name = MessageManager.getString("label.annotation_name");
287  0 String description = MessageManager
288    .getString("label.annotation_description");
289  0 String title = MessageManager
290    .getString("label.edit_annotation_name_description");
291  0 EditNameDialog dialog = new EditNameDialog(annotation.label,
292    annotation.description, name, description);
293   
294  0 dialog.showDialog(ap.alignFrame, title,
295    new RunResponse(JvOptionPane.OK_OPTION)
296    {
 
297  0 toggle @Override
298    public void run()
299    {
300  0 annotation.label = dialog.getName();
301  0 String text = dialog.getDescription();
302  0 if (text != null && text.length() == 0)
303    {
304  0 text = null;
305    }
306  0 annotation.description = text;
307  0 if (addNew)
308    {
309  0 ap.av.getAlignment().addAnnotation(annotation);
310  0 ap.av.getAlignment().setAnnotationIndex(annotation, 0);
311    }
312  0 ap.refresh(true);
313    }
314    });
315    }
316   
 
317  0 toggle @Override
318    public void mousePressed(MouseEvent evt)
319    {
320  0 getSelectedRow(evt.getY() - getScrollOffset());
321  0 oldY = evt.getY();
322  0 if (evt.isPopupTrigger())
323    {
324  0 showPopupMenu(evt);
325    }
326    }
327   
328    /**
329    * Build and show the Pop-up menu at the right-click mouse position
330    *
331    * @param evt
332    */
 
333  0 toggle void showPopupMenu(MouseEvent evt)
334    {
335  0 evt.consume();
336  0 final AlignmentAnnotation[] aa = ap.av.getAlignment()
337    .getAlignmentAnnotation();
338   
339  0 JPopupMenu pop = new JPopupMenu(
340    MessageManager.getString("label.annotations"));
341  0 JMenuItem item = new JMenuItem(ADDNEW);
342  0 item.addActionListener(this);
343  0 pop.add(item);
344  0 if (selectedRow < 0)
345    {
346  0 if (hasHiddenRows)
347    { // let the user make everything visible again
348  0 item = new JMenuItem(SHOWALL);
349  0 item.addActionListener(this);
350  0 pop.add(item);
351    }
352  0 pop.show(this, evt.getX(), evt.getY());
353  0 return;
354    }
355  0 item = new JMenuItem(EDITNAME);
356  0 item.addActionListener(this);
357  0 pop.add(item);
358  0 item = new JMenuItem(HIDE);
359  0 item.addActionListener(this);
360  0 pop.add(item);
361    // JAL-1264 hide all sequence-specific annotations of this type
362  0 if (selectedRow < aa.length)
363    {
364  0 if (aa[selectedRow].sequenceRef != null)
365    {
366  0 final String label = aa[selectedRow].label;
367  0 JMenuItem hideType = new JMenuItem();
368  0 String text = MessageManager.getString("label.hide_all") + " "
369    + label;
370  0 hideType.setText(text);
371  0 hideType.addActionListener(new ActionListener()
372    {
 
373  0 toggle @Override
374    public void actionPerformed(ActionEvent e)
375    {
376  0 AlignmentUtils.showOrHideSequenceAnnotations(
377    ap.av.getAlignment(), Collections.singleton(label),
378    null, false, false);
379    // for (AlignmentAnnotation ann : ap.av.getAlignment()
380    // .getAlignmentAnnotation())
381    // {
382    // if (ann.sequenceRef != null && ann.label != null
383    // && ann.label.equals(label))
384    // {
385    // ann.visible = false;
386    // }
387    // }
388  0 ap.refresh(true);
389    }
390    });
391  0 pop.add(hideType);
392    }
393    }
394  0 item = new JMenuItem(DELETE);
395  0 item.addActionListener(this);
396  0 pop.add(item);
397  0 if (hasHiddenRows)
398    {
399  0 item = new JMenuItem(SHOWALL);
400  0 item.addActionListener(this);
401  0 pop.add(item);
402    }
403  0 item = new JMenuItem(OUTPUT_TEXT);
404  0 item.addActionListener(this);
405  0 pop.add(item);
406    // TODO: annotation object should be typed for autocalculated/derived
407    // property methods
408  0 if (selectedRow < aa.length)
409    {
410  0 final String label = aa[selectedRow].label;
411  0 if (!aa[selectedRow].autoCalculated)
412    {
413  0 if (aa[selectedRow].graph == AlignmentAnnotation.NO_GRAPH)
414    {
415    // display formatting settings for this row.
416  0 pop.addSeparator();
417    // av and sequencegroup need to implement same interface for
418  0 item = new JCheckBoxMenuItem(TOGGLE_LABELSCALE,
419    aa[selectedRow].scaleColLabel);
420  0 item.addActionListener(this);
421  0 pop.add(item);
422    }
423    }
424  0 else if (label.indexOf("Consensus") > -1)
425    {
426  0 pop.addSeparator();
427    // av and sequencegroup need to implement same interface for
428  0 final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
429    MessageManager.getString("label.ignore_gaps_consensus"),
430  0 (aa[selectedRow].groupRef != null)
431    ? aa[selectedRow].groupRef.getIgnoreGapsConsensus()
432    : ap.av.isIgnoreGapsConsensus());
433  0 final AlignmentAnnotation aaa = aa[selectedRow];
434  0 cbmi.addActionListener(new ActionListener()
435    {
 
436  0 toggle @Override
437    public void actionPerformed(ActionEvent e)
438    {
439  0 if (aaa.groupRef != null)
440    {
441    // TODO: pass on reference to ap so the view can be updated.
442  0 aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState());
443  0 ap.getAnnotationPanel()
444    .paint(ap.getAnnotationPanel().getGraphics());
445    }
446    else
447    {
448  0 ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
449    }
450  0 ap.alignmentChanged();
451    }
452    });
453  0 pop.add(cbmi);
454    // av and sequencegroup need to implement same interface for
455  0 if (aaa.groupRef != null)
456    {
457  0 final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
458    MessageManager.getString("label.show_group_histogram"),
459    aa[selectedRow].groupRef.isShowConsensusHistogram());
460  0 chist.addActionListener(new ActionListener()
461    {
 
462  0 toggle @Override
463    public void actionPerformed(ActionEvent e)
464    {
465    // TODO: pass on reference
466    // to ap
467    // so the
468    // view
469    // can be
470    // updated.
471  0 aaa.groupRef.setShowConsensusHistogram(chist.getState());
472  0 ap.repaint();
473    // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
474    }
475    });
476  0 pop.add(chist);
477  0 final JCheckBoxMenuItem cprofl = new JCheckBoxMenuItem(
478    MessageManager.getString("label.show_group_logo"),
479    aa[selectedRow].groupRef.isShowSequenceLogo());
480  0 cprofl.addActionListener(new ActionListener()
481    {
 
482  0 toggle @Override
483    public void actionPerformed(ActionEvent e)
484    {
485    // TODO: pass on reference
486    // to ap
487    // so the
488    // view
489    // can be
490    // updated.
491  0 aaa.groupRef.setshowSequenceLogo(cprofl.getState());
492  0 ap.repaint();
493    // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
494    }
495    });
496  0 pop.add(cprofl);
497  0 final JCheckBoxMenuItem cproflnorm = new JCheckBoxMenuItem(
498    MessageManager.getString("label.normalise_group_logo"),
499    aa[selectedRow].groupRef.isNormaliseSequenceLogo());
500  0 cproflnorm.addActionListener(new ActionListener()
501    {
 
502  0 toggle @Override
503    public void actionPerformed(ActionEvent e)
504    {
505   
506    // TODO: pass on reference
507    // to ap
508    // so the
509    // view
510    // can be
511    // updated.
512  0 aaa.groupRef.setNormaliseSequenceLogo(cproflnorm.getState());
513    // automatically enable logo display if we're clicked
514  0 aaa.groupRef.setshowSequenceLogo(true);
515  0 ap.repaint();
516    // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
517    }
518    });
519  0 pop.add(cproflnorm);
520    }
521    else
522    {
523  0 final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
524    MessageManager.getString("label.show_histogram"),
525    av.isShowConsensusHistogram());
526  0 chist.addActionListener(new ActionListener()
527    {
 
528  0 toggle @Override
529    public void actionPerformed(ActionEvent e)
530    {
531    // TODO: pass on reference
532    // to ap
533    // so the
534    // view
535    // can be
536    // updated.
537  0 av.setShowConsensusHistogram(chist.getState());
538  0 ap.alignFrame.setMenusForViewport();
539  0 ap.repaint();
540    // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
541    }
542    });
543  0 pop.add(chist);
544  0 final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
545    MessageManager.getString("label.show_logo"),
546    av.isShowSequenceLogo());
547  0 cprof.addActionListener(new ActionListener()
548    {
 
549  0 toggle @Override
550    public void actionPerformed(ActionEvent e)
551    {
552    // TODO: pass on reference
553    // to ap
554    // so the
555    // view
556    // can be
557    // updated.
558  0 av.setShowSequenceLogo(cprof.getState());
559  0 ap.alignFrame.setMenusForViewport();
560  0 ap.repaint();
561    // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
562    }
563    });
564  0 pop.add(cprof);
565  0 final JCheckBoxMenuItem cprofnorm = new JCheckBoxMenuItem(
566    MessageManager.getString("label.normalise_logo"),
567    av.isNormaliseSequenceLogo());
568  0 cprofnorm.addActionListener(new ActionListener()
569    {
 
570  0 toggle @Override
571    public void actionPerformed(ActionEvent e)
572    {
573    // TODO: pass on reference
574    // to ap
575    // so the
576    // view
577    // can be
578    // updated.
579  0 av.setShowSequenceLogo(true);
580  0 av.setNormaliseSequenceLogo(cprofnorm.getState());
581  0 ap.alignFrame.setMenusForViewport();
582  0 ap.repaint();
583    // ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
584    }
585    });
586  0 pop.add(cprofnorm);
587    }
588  0 final JMenuItem consclipbrd = new JMenuItem(COPYCONS_SEQ);
589  0 consclipbrd.addActionListener(this);
590  0 pop.add(consclipbrd);
591    }
592    }
593  0 pop.show(this, evt.getX(), evt.getY());
594    }
595   
596    /**
597    * Reorders annotation rows after a drag of a label
598    *
599    * @param evt
600    */
 
601  0 toggle @Override
602    public void mouseReleased(MouseEvent evt)
603    {
604  0 if (evt.isPopupTrigger())
605    {
606  0 showPopupMenu(evt);
607  0 return;
608    }
609   
610  0 int start = selectedRow;
611  0 getSelectedRow(evt.getY() - getScrollOffset());
612  0 int end = selectedRow;
613   
614    /*
615    * if dragging to resize instead, start == end
616    */
617  0 if (start != end)
618    {
619    // Swap these annotations
620  0 AlignmentAnnotation startAA = ap.av.getAlignment()
621    .getAlignmentAnnotation()[start];
622  0 if (end == -1)
623    {
624  0 end = ap.av.getAlignment().getAlignmentAnnotation().length - 1;
625    }
626  0 AlignmentAnnotation endAA = ap.av.getAlignment()
627    .getAlignmentAnnotation()[end];
628   
629  0 ap.av.getAlignment().getAlignmentAnnotation()[end] = startAA;
630  0 ap.av.getAlignment().getAlignmentAnnotation()[start] = endAA;
631    }
632   
633  0 resizePanel = false;
634  0 dragEvent = null;
635  0 repaint();
636  0 ap.getAnnotationPanel().repaint();
637    }
638   
639    /**
640    * Removes the height adjuster image on leaving the panel, unless currently
641    * dragging it
642    */
 
643  0 toggle @Override
644    public void mouseExited(MouseEvent evt)
645    {
646  0 if (resizePanel && dragEvent == null)
647    {
648  0 resizePanel = false;
649  0 repaint();
650    }
651    }
652   
653    /**
654    * A mouse drag may be either an adjustment of the panel height (if flag
655    * resizePanel is set on), or a reordering of the annotation rows. The former
656    * is dealt with by this method, the latter in mouseReleased.
657    *
658    * @param evt
659    */
 
660  0 toggle @Override
661    public void mouseDragged(MouseEvent evt)
662    {
663  0 dragEvent = evt;
664   
665  0 if (resizePanel)
666    {
667  0 Dimension d = ap.annotationScroller.getPreferredSize();
668  0 int dif = evt.getY() - oldY;
669   
670  0 dif /= ap.av.getCharHeight();
671  0 dif *= ap.av.getCharHeight();
672   
673  0 if ((d.height - dif) > 20)
674    {
675  0 ap.annotationScroller
676    .setPreferredSize(new Dimension(d.width, d.height - dif));
677  0 d = ap.annotationSpaceFillerHolder.getPreferredSize();
678  0 ap.annotationSpaceFillerHolder
679    .setPreferredSize(new Dimension(d.width, d.height - dif));
680  0 ap.paintAlignment(true, false);
681    }
682   
683  0 ap.addNotify();
684    }
685    else
686    {
687  0 repaint();
688    }
689    }
690   
691    /**
692    * Updates the tooltip as the mouse moves over the labels
693    *
694    * @param evt
695    */
 
696  0 toggle @Override
697    public void mouseMoved(MouseEvent evt)
698    {
699  0 showOrHideAdjuster(evt);
700   
701  0 getSelectedRow(evt.getY() - getScrollOffset());
702   
703  0 if (selectedRow > -1 && ap.av.getAlignment()
704    .getAlignmentAnnotation().length > selectedRow)
705    {
706  0 AlignmentAnnotation aa = ap.av.getAlignment()
707    .getAlignmentAnnotation()[selectedRow];
708   
709  0 StringBuffer desc = new StringBuffer();
710  0 if (aa.description != null
711    && !aa.description.equals("New description"))
712    {
713    // TODO: we could refactor and merge this code with the code in
714    // jalview.gui.SeqPanel.mouseMoved(..) that formats sequence feature
715    // tooltips
716  0 desc.append(aa.getDescription(true).trim());
717    // check to see if the description is an html fragment.
718  0 if (desc.length() < 6 || (desc.substring(0, 6).toLowerCase()
719    .indexOf("<html>") < 0))
720    {
721    // clean the description ready for embedding in html
722  0 desc = new StringBuffer(LEFT_ANGLE_BRACKET_PATTERN.matcher(desc)
723    .replaceAll("&lt;"));
724  0 desc.insert(0, "<html>");
725    }
726    else
727    {
728    // remove terminating html if any
729  0 int i = desc.substring(desc.length() - 7).toLowerCase()
730    .lastIndexOf("</html>");
731  0 if (i > -1)
732    {
733  0 desc.setLength(desc.length() - 7 + i);
734    }
735    }
736  0 if (aa.hasScore())
737    {
738  0 desc.append("<br/>");
739    }
740    // if (aa.hasProperties())
741    // {
742    // desc.append("<table>");
743    // for (String prop : aa.getProperties())
744    // {
745    // desc.append("<tr><td>" + prop + "</td><td>"
746    // + aa.getProperty(prop) + "</td><tr>");
747    // }
748    // desc.append("</table>");
749    // }
750    }
751    else
752    {
753    // begin the tooltip's html fragment
754  0 desc.append("<html>");
755  0 if (aa.hasScore())
756    {
757    // TODO: limit precision of score to avoid noise from imprecise
758    // doubles
759    // (64.7 becomes 64.7+/some tiny value).
760  0 desc.append(" Score: " + aa.score);
761    }
762    }
763  0 if (desc.length() > 6)
764    {
765  0 desc.append("</html>");
766  0 this.setToolTipText(desc.toString());
767    }
768    else
769    {
770  0 this.setToolTipText(null);
771    }
772    }
773    }
774   
775    /**
776    * Shows the height adjuster image if the mouse moves into the top left
777    * region, or hides it if the mouse leaves the regio
778    *
779    * @param evt
780    */
 
781  0 toggle protected void showOrHideAdjuster(MouseEvent evt)
782    {
783  0 boolean was = resizePanel;
784  0 resizePanel = evt.getY() < HEIGHT_ADJUSTER_HEIGHT
785    && evt.getX() < HEIGHT_ADJUSTER_WIDTH;
786   
787  0 if (resizePanel != was)
788    {
789  0 setCursor(Cursor
790  0 .getPredefinedCursor(resizePanel ? Cursor.S_RESIZE_CURSOR
791    : Cursor.DEFAULT_CURSOR));
792  0 repaint();
793    }
794    }
795   
 
796  0 toggle @Override
797    public void mouseClicked(MouseEvent evt)
798    {
799  0 final AlignmentAnnotation[] aa = ap.av.getAlignment()
800    .getAlignmentAnnotation();
801  0 if (!evt.isPopupTrigger() && SwingUtilities.isLeftMouseButton(evt))
802    {
803  0 if (selectedRow > -1 && selectedRow < aa.length)
804    {
805  0 if (aa[selectedRow].groupRef != null)
806    {
807  0 if (evt.getClickCount() >= 2)
808    {
809    // todo: make the ap scroll to the selection - not necessary, first
810    // click highlights/scrolls, second selects
811  0 ap.getSeqPanel().ap.getIdPanel().highlightSearchResults(null);
812    // process modifiers
813  0 SequenceGroup sg = ap.av.getSelectionGroup();
814  0 if (sg == null || sg == aa[selectedRow].groupRef
815    || !(Platform.isControlDown(evt) || evt.isShiftDown()))
816    {
817  0 if (Platform.isControlDown(evt) || evt.isShiftDown())
818    {
819    // clone a new selection group from the associated group
820  0 ap.av.setSelectionGroup(
821    new SequenceGroup(aa[selectedRow].groupRef));
822    }
823    else
824    {
825    // set selection to the associated group so it can be edited
826  0 ap.av.setSelectionGroup(aa[selectedRow].groupRef);
827    }
828    }
829    else
830    {
831    // modify current selection with associated group
832  0 int remainToAdd = aa[selectedRow].groupRef.getSize();
833  0 for (SequenceI sgs : aa[selectedRow].groupRef.getSequences())
834    {
835  0 if (jalview.util.Platform.isControlDown(evt))
836    {
837  0 sg.addOrRemove(sgs, --remainToAdd == 0);
838    }
839    else
840    {
841    // notionally, we should also add intermediate sequences from
842    // last added sequence ?
843  0 sg.addSequence(sgs, --remainToAdd == 0);
844    }
845    }
846    }
847   
848  0 ap.paintAlignment(false, false);
849  0 PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
850  0 ap.av.sendSelection();
851    }
852    else
853    {
854  0 ap.getSeqPanel().ap.getIdPanel().highlightSearchResults(
855    aa[selectedRow].groupRef.getSequences(null));
856    }
857  0 return;
858    }
859  0 else if (aa[selectedRow].sequenceRef != null)
860    {
861  0 if (evt.getClickCount() == 1)
862    {
863  0 ap.getSeqPanel().ap.getIdPanel()
864    .highlightSearchResults(Arrays.asList(new SequenceI[]
865    { aa[selectedRow].sequenceRef }));
866    }
867  0 else if (evt.getClickCount() >= 2)
868    {
869  0 ap.getSeqPanel().ap.getIdPanel().highlightSearchResults(null);
870  0 SequenceGroup sg = ap.av.getSelectionGroup();
871  0 if (sg != null)
872    {
873    // we make a copy rather than edit the current selection if no
874    // modifiers pressed
875    // see Enhancement JAL-1557
876  0 if (!(Platform.isControlDown(evt) || evt.isShiftDown()))
877    {
878  0 sg = new SequenceGroup(sg);
879  0 sg.clear();
880  0 sg.addSequence(aa[selectedRow].sequenceRef, false);
881    }
882    else
883    {
884  0 if (Platform.isControlDown(evt))
885    {
886  0 sg.addOrRemove(aa[selectedRow].sequenceRef, true);
887    }
888    else
889    {
890    // notionally, we should also add intermediate sequences from
891    // last added sequence ?
892  0 sg.addSequence(aa[selectedRow].sequenceRef, true);
893    }
894    }
895    }
896    else
897    {
898  0 sg = new SequenceGroup();
899  0 sg.setStartRes(0);
900  0 sg.setEndRes(ap.av.getAlignment().getWidth() - 1);
901  0 sg.addSequence(aa[selectedRow].sequenceRef, false);
902    }
903  0 ap.av.setSelectionGroup(sg);
904  0 ap.paintAlignment(false, false);
905  0 PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
906  0 ap.av.sendSelection();
907    }
908   
909    }
910    }
911  0 return;
912    }
913    }
914   
915    /**
916    * do a single sequence copy to jalview and the system clipboard
917    *
918    * @param sq
919    * sequence to be copied to clipboard
920    */
 
921  0 toggle protected void copy_annotseqtoclipboard(SequenceI sq)
922    {
923  0 SequenceI[] seqs = new SequenceI[] { sq };
924  0 String[] omitHidden = null;
925  0 SequenceI[] dseqs = new SequenceI[] { sq.getDatasetSequence() };
926  0 if (dseqs[0] == null)
927    {
928  0 dseqs[0] = new Sequence(sq);
929  0 dseqs[0].setSequence(AlignSeq.extractGaps(Comparison.GapChars,
930    sq.getSequenceAsString()));
931   
932  0 sq.setDatasetSequence(dseqs[0]);
933    }
934  0 Alignment ds = new Alignment(dseqs);
935  0 if (av.hasHiddenColumns())
936    {
937  0 Iterator<int[]> it = av.getAlignment().getHiddenColumns()
938    .getVisContigsIterator(0, sq.getLength(), false);
939  0 omitHidden = new String[] { sq.getSequenceStringFromIterator(it) };
940    }
941   
942  0 int[] alignmentStartEnd = new int[] { 0, ds.getWidth() - 1 };
943  0 if (av.hasHiddenColumns())
944    {
945  0 alignmentStartEnd = av.getAlignment().getHiddenColumns()
946    .getVisibleStartAndEndIndex(av.getAlignment().getWidth());
947    }
948   
949  0 String output = new FormatAdapter().formatSequences(FileFormat.Fasta,
950    seqs, omitHidden, alignmentStartEnd);
951   
952  0 Toolkit.getDefaultToolkit().getSystemClipboard()
953    .setContents(new StringSelection(output), Desktop.instance);
954   
955  0 HiddenColumns hiddenColumns = null;
956   
957  0 if (av.hasHiddenColumns())
958    {
959  0 hiddenColumns = new HiddenColumns(
960    av.getAlignment().getHiddenColumns());
961    }
962   
963  0 Desktop.jalviewClipboard = new Object[] { seqs, ds, // what is the dataset
964    // of a consensus
965    // sequence ? need to
966    // flag
967    // sequence as special.
968    hiddenColumns };
969    }
970   
971    /**
972    * DOCUMENT ME!
973    *
974    * @param g1
975    * DOCUMENT ME!
976    */
 
977  681 toggle @Override
978    public void paintComponent(Graphics g)
979    {
980   
981  681 int width = getWidth();
982  681 if (width == 0)
983    {
984  0 width = ap.calculateIdWidth().width + 4;
985    }
986   
987  681 Graphics2D g2 = (Graphics2D) g;
988  681 if (av.antiAlias)
989    {
990  0 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
991    RenderingHints.VALUE_ANTIALIAS_ON);
992    }
993   
994  681 drawComponent(g2, true, width);
995   
996    }
997   
998    /**
999    * Draw the full set of annotation Labels for the alignment at the given
1000    * cursor
1001    *
1002    * @param g
1003    * Graphics2D instance (needed for font scaling)
1004    * @param width
1005    * Width for scaling labels
1006    *
1007    */
 
1008  348 toggle public void drawComponent(Graphics g, int width)
1009    {
1010  348 drawComponent(g, false, width);
1011    }
1012   
1013    /**
1014    * Draw the full set of annotation Labels for the alignment at the given
1015    * cursor
1016    *
1017    * @param g
1018    * Graphics2D instance (needed for font scaling)
1019    * @param clip
1020    * - true indicates that only current visible area needs to be
1021    * rendered
1022    * @param width
1023    * Width for scaling labels
1024    */
 
1025  1029 toggle public void drawComponent(Graphics g, boolean clip, int width)
1026    {
1027  1029 if (av.getFont().getSize() < 10)
1028    {
1029  0 g.setFont(font);
1030    }
1031    else
1032    {
1033  1029 g.setFont(av.getFont());
1034    }
1035   
1036  1029 FontMetrics fm = g.getFontMetrics(g.getFont());
1037  1029 g.setColor(Color.white);
1038  1029 g.fillRect(0, 0, getWidth(), getHeight());
1039   
1040  1029 g.translate(0, getScrollOffset());
1041  1029 g.setColor(Color.black);
1042   
1043  1029 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
1044  1029 int fontHeight = g.getFont().getSize();
1045  1029 int y = 0;
1046  1029 int x = 0;
1047  1029 int graphExtras = 0;
1048  1029 int offset = 0;
1049  1029 Font baseFont = g.getFont();
1050  1029 FontMetrics baseMetrics = fm;
1051  1029 int ofontH = fontHeight;
1052  1029 int sOffset = 0;
1053  1029 int visHeight = 0;
1054  1029 int[] visr = (ap != null && ap.getAnnotationPanel() != null)
1055    ? ap.getAnnotationPanel().getVisibleVRange()
1056    : null;
1057  1029 if (clip && visr != null)
1058    {
1059  681 sOffset = visr[0];
1060  681 visHeight = visr[1];
1061    }
1062  1029 boolean visible = true, before = false, after = false;
1063  1029 if (aa != null)
1064    {
1065  1029 hasHiddenRows = false;
1066  1029 int olY = 0;
1067  7435 for (int i = 0; i < aa.length; i++)
1068    {
1069  6406 visible = true;
1070  6406 if (!aa[i].visible)
1071    {
1072  653 hasHiddenRows = true;
1073  653 continue;
1074    }
1075  5753 olY = y;
1076  5753 y += aa[i].height;
1077  5753 if (clip)
1078    {
1079  4013 if (y < sOffset)
1080    {
1081  0 if (!before)
1082    {
1083  0 if (debugRedraw)
1084    {
1085  0 System.out.println("before vis: " + i);
1086    }
1087  0 before = true;
1088    }
1089    // don't draw what isn't visible
1090  0 continue;
1091    }
1092  4013 if (olY > visHeight)
1093    {
1094   
1095  14 if (!after)
1096    {
1097  7 if (debugRedraw)
1098    {
1099  0 System.out.println(
1100    "Scroll offset: " + sOffset + " after vis: " + i);
1101    }
1102  7 after = true;
1103    }
1104    // don't draw what isn't visible
1105  14 continue;
1106    }
1107    }
1108  5739 g.setColor(Color.black);
1109   
1110  5739 offset = -aa[i].height / 2;
1111   
1112  5739 if (aa[i].hasText)
1113    {
1114  5391 offset += fm.getHeight() / 2;
1115  5391 offset -= fm.getDescent();
1116    }
1117    else
1118    {
1119  348 offset += fm.getDescent();
1120    }
1121   
1122  5739 x = width - fm.stringWidth(aa[i].label) - 3;
1123   
1124  5739 if (aa[i].graphGroup > -1)
1125    {
1126  0 int groupSize = 0;
1127    // TODO: JAL-1291 revise rendering model so the graphGroup map is
1128    // computed efficiently for all visible labels
1129  0 for (int gg = 0; gg < aa.length; gg++)
1130    {
1131  0 if (aa[gg].graphGroup == aa[i].graphGroup)
1132    {
1133  0 groupSize++;
1134    }
1135    }
1136  0 if (groupSize * (fontHeight + 8) < aa[i].height)
1137    {
1138  0 graphExtras = (aa[i].height - (groupSize * (fontHeight + 8)))
1139    / 2;
1140    }
1141    else
1142    {
1143    // scale font to fit
1144  0 float h = aa[i].height / (float) groupSize, s;
1145  0 if (h < 9)
1146    {
1147  0 visible = false;
1148    }
1149    else
1150    {
1151  0 fontHeight = -8 + (int) h;
1152  0 s = ((float) fontHeight) / (float) ofontH;
1153  0 Font f = baseFont
1154    .deriveFont(AffineTransform.getScaleInstance(s, s));
1155  0 g.setFont(f);
1156  0 fm = g.getFontMetrics();
1157  0 graphExtras = (aa[i].height - (groupSize * (fontHeight + 8)))
1158    / 2;
1159    }
1160    }
1161  0 if (visible)
1162    {
1163  0 for (int gg = 0; gg < aa.length; gg++)
1164    {
1165  0 if (aa[gg].graphGroup == aa[i].graphGroup)
1166    {
1167  0 x = width - fm.stringWidth(aa[gg].label) - 3;
1168  0 g.drawString(aa[gg].label, x, y - graphExtras);
1169   
1170  0 if (aa[gg]._linecolour != null)
1171    {
1172   
1173  0 g.setColor(aa[gg]._linecolour);
1174  0 g.drawLine(x, y - graphExtras + 3,
1175    x + fm.stringWidth(aa[gg].label),
1176    y - graphExtras + 3);
1177    }
1178   
1179  0 g.setColor(Color.black);
1180  0 graphExtras += fontHeight + 8;
1181    }
1182    }
1183    }
1184  0 g.setFont(baseFont);
1185  0 fm = baseMetrics;
1186  0 fontHeight = ofontH;
1187    }
1188    else
1189    {
1190  5739 g.drawString(aa[i].label, x, y + offset);
1191    }
1192    }
1193    }
1194   
1195  1029 if (!resizePanel && dragEvent != null && aa != null)
1196    {
1197  0 g.setColor(Color.lightGray);
1198  0 g.drawString(aa[selectedRow].label, dragEvent.getX(),
1199    dragEvent.getY() - getScrollOffset());
1200    }
1201   
1202  1029 if (!av.getWrapAlignment() && ((aa == null) || (aa.length < 1)))
1203    {
1204  0 g.drawString(MessageManager.getString("label.right_click"), 2, 8);
1205  0 g.drawString(MessageManager.getString("label.to_add_annotation"), 2,
1206    18);
1207    }
1208    }
1209   
 
1210  2328 toggle public int getScrollOffset()
1211    {
1212  2328 return scrollOffset;
1213    }
1214   
 
1215  0 toggle @Override
1216    public void mouseEntered(MouseEvent e)
1217    {
1218    }
1219    }