JAL-4367 Limit right and top adjustments
[jalview.git] / src / jalview / gui / AnnotationLabels.java
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 java.awt.Canvas;
24 import java.awt.Color;
25 import java.awt.Cursor;
26 import java.awt.Dimension;
27 import java.awt.Font;
28 import java.awt.FontMetrics;
29 import java.awt.Graphics;
30 import java.awt.Graphics2D;
31 import java.awt.RenderingHints;
32 import java.awt.Toolkit;
33 import java.awt.datatransfer.StringSelection;
34 import java.awt.event.ActionEvent;
35 import java.awt.event.ActionListener;
36 import java.awt.event.MouseEvent;
37 import java.awt.event.MouseListener;
38 import java.awt.event.MouseMotionListener;
39 import java.awt.geom.AffineTransform;
40 import java.util.Arrays;
41 import java.util.Collections;
42 import java.util.Iterator;
43 import java.util.Locale;
44
45 import javax.swing.JCheckBoxMenuItem;
46 import javax.swing.JMenuItem;
47 import javax.swing.JPanel;
48 import javax.swing.JPopupMenu;
49 import javax.swing.SwingUtilities;
50 import javax.swing.ToolTipManager;
51
52 import jalview.analysis.AlignSeq;
53 import jalview.analysis.AlignmentUtils;
54 import jalview.bin.Cache;
55 import jalview.bin.Jalview;
56 import jalview.datamodel.Alignment;
57 import jalview.datamodel.AlignmentAnnotation;
58 import jalview.datamodel.Annotation;
59 import jalview.datamodel.ContactMatrixI;
60 import jalview.datamodel.GroupSet;
61 import jalview.datamodel.HiddenColumns;
62 import jalview.datamodel.Sequence;
63 import jalview.datamodel.SequenceGroup;
64 import jalview.datamodel.SequenceI;
65 import jalview.io.FileFormat;
66 import jalview.io.FormatAdapter;
67 import jalview.util.Comparison;
68 import jalview.util.MessageManager;
69 import jalview.util.Platform;
70
71 /**
72  * The panel that holds the labels for alignment annotations, providing
73  * tooltips, context menus, drag to reorder rows, and drag to adjust panel
74  * height
75  */
76 public class AnnotationLabels extends JPanel
77         implements MouseListener, MouseMotionListener, ActionListener
78 {
79   private static final String HTML_END_TAG = "</html>";
80
81   private static final String HTML_START_TAG = "<html>";
82
83   /**
84    * width in pixels within which height adjuster arrows are shown and active
85    */
86   private static final int HEIGHT_ADJUSTER_WIDTH = 50;
87
88   /**
89    * height in pixels for allowing height adjuster to be active
90    */
91   public static int HEIGHT_ADJUSTER_HEIGHT = 10;
92
93   private static final Font font = new Font("Arial", Font.PLAIN, 11);
94
95   private static final String TOGGLE_LABELSCALE = MessageManager
96           .getString("label.scale_label_to_column");
97
98   private static final String ADDNEW = MessageManager
99           .getString("label.add_new_row");
100
101   private static final String EDITNAME = MessageManager
102           .getString("label.edit_label_description");
103
104   private static final String HIDE = MessageManager
105           .getString("label.hide_row");
106
107   private static final String DELETE = MessageManager
108           .getString("label.delete_row");
109
110   private static final String SHOWALL = MessageManager
111           .getString("label.show_all_hidden_rows");
112
113   private static final String OUTPUT_TEXT = MessageManager
114           .getString("label.export_annotation");
115
116   private static final String COPYCONS_SEQ = MessageManager
117           .getString("label.copy_consensus_sequence");
118
119   private static final String ADJUST_ANNOTATION_LABELS_WIDTH_PREF = "ADJUST_ANNOTATION_LABELS_WIDTH";
120
121   private final boolean debugRedraw = false;
122
123   private AlignmentPanel ap;
124
125   AlignViewport av;
126
127   private MouseEvent dragEvent;
128
129   private int oldY;
130
131   private int selectedRow;
132
133   private int scrollOffset = 0;
134
135   private boolean hasHiddenRows;
136
137   private boolean resizePanel = false;
138
139   private int annotationIdWidth = -1;
140
141   public static final String RESIZE_MARGINS_MARK_PREF = "RESIZE_MARGINS_MARK";
142
143   /**
144    * Creates a new AnnotationLabels object
145    * 
146    * @param ap
147    */
148   public AnnotationLabels(AlignmentPanel ap)
149   {
150     this.ap = ap;
151     av = ap.av;
152     ToolTipManager.sharedInstance().registerComponent(this);
153
154     addMouseListener(this);
155     addMouseMotionListener(this);
156     addMouseWheelListener(ap.getAnnotationPanel());
157   }
158
159   public AnnotationLabels(AlignViewport av)
160   {
161     this.av = av;
162   }
163
164   /**
165    * DOCUMENT ME!
166    * 
167    * @param y
168    *          DOCUMENT ME!
169    */
170   public void setScrollOffset(int y)
171   {
172     scrollOffset = y;
173     repaint();
174   }
175
176   /**
177    * sets selectedRow to -2 if no annotation preset, -1 if no visible row is at
178    * y
179    * 
180    * @param y
181    *          coordinate position to search for a row
182    */
183   void getSelectedRow(int y)
184   {
185     int height = 0;
186     AlignmentAnnotation[] aa = ap.av.getAlignment()
187             .getAlignmentAnnotation();
188     selectedRow = -2;
189     if (aa != null)
190     {
191       for (int i = 0; i < aa.length; i++)
192       {
193         selectedRow = -1;
194         if (!aa[i].visible)
195         {
196           continue;
197         }
198
199         height += aa[i].height;
200
201         if (y < height)
202         {
203           selectedRow = i;
204
205           break;
206         }
207       }
208     }
209   }
210
211   /**
212    * DOCUMENT ME!
213    * 
214    * @param evt
215    *          DOCUMENT ME!
216    */
217   @Override
218   public void actionPerformed(ActionEvent evt)
219   {
220     AlignmentAnnotation[] aa = ap.av.getAlignment()
221             .getAlignmentAnnotation();
222
223     String action = evt.getActionCommand();
224     if (ADDNEW.equals(action))
225     {
226       /*
227        * non-returning dialog
228        */
229       AlignmentAnnotation newAnnotation = new AlignmentAnnotation(null,
230               null, new Annotation[ap.av.getAlignment().getWidth()]);
231       editLabelDescription(newAnnotation, true);
232     }
233     else if (EDITNAME.equals(action))
234     {
235       /*
236        * non-returning dialog
237        */
238       editLabelDescription(aa[selectedRow], false);
239     }
240     else if (HIDE.equals(action))
241     {
242       aa[selectedRow].visible = false;
243     }
244     else if (DELETE.equals(action))
245     {
246       ap.av.getAlignment().deleteAnnotation(aa[selectedRow]);
247       ap.av.getCalcManager().removeWorkerForAnnotation(aa[selectedRow]);
248     }
249     else if (SHOWALL.equals(action))
250     {
251       for (int i = 0; i < aa.length; i++)
252       {
253         if (!aa[i].visible && aa[i].annotations != null)
254         {
255           aa[i].visible = true;
256         }
257       }
258     }
259     else if (OUTPUT_TEXT.equals(action))
260     {
261       new AnnotationExporter(ap).exportAnnotation(aa[selectedRow]);
262     }
263     else if (COPYCONS_SEQ.equals(action))
264     {
265       SequenceI cons = null;
266       if (aa[selectedRow].groupRef != null)
267       {
268         cons = aa[selectedRow].groupRef.getConsensusSeq();
269       }
270       else
271       {
272         cons = av.getConsensusSeq();
273       }
274       if (cons != null)
275       {
276         copy_annotseqtoclipboard(cons);
277       }
278     }
279     else if (TOGGLE_LABELSCALE.equals(action))
280     {
281       aa[selectedRow].scaleColLabel = !aa[selectedRow].scaleColLabel;
282     }
283
284     ap.refresh(true);
285   }
286
287   /**
288    * Shows a dialog where the annotation name and description may be edited. If
289    * parameter addNew is true, then on confirmation, a new AlignmentAnnotation
290    * is added, else an existing annotation is updated.
291    * 
292    * @param annotation
293    * @param addNew
294    */
295   void editLabelDescription(AlignmentAnnotation annotation, boolean addNew)
296   {
297     String name = MessageManager.getString("label.annotation_name");
298     String description = MessageManager
299             .getString("label.annotation_description");
300     String title = MessageManager
301             .getString("label.edit_annotation_name_description");
302     EditNameDialog dialog = new EditNameDialog(annotation.label,
303             annotation.description, name, description);
304
305     dialog.showDialog(ap.alignFrame, title, () -> {
306       annotation.label = dialog.getName();
307       String text = dialog.getDescription();
308       if (text != null && text.length() == 0)
309       {
310         text = null;
311       }
312       annotation.description = text;
313       if (addNew)
314       {
315         ap.av.getAlignment().addAnnotation(annotation);
316         ap.av.getAlignment().setAnnotationIndex(annotation, 0);
317       }
318       ap.refresh(true);
319     });
320   }
321
322   @Override
323   public void mousePressed(MouseEvent evt)
324   {
325     getSelectedRow(evt.getY() - getScrollOffset());
326     oldY = evt.getY();
327     if (evt.isPopupTrigger())
328     {
329       showPopupMenu(evt);
330     }
331   }
332
333   /**
334    * Build and show the Pop-up menu at the right-click mouse position
335    * 
336    * @param evt
337    */
338   void showPopupMenu(MouseEvent evt)
339   {
340     evt.consume();
341     final AlignmentAnnotation[] aa = ap.av.getAlignment()
342             .getAlignmentAnnotation();
343
344     JPopupMenu pop = new JPopupMenu(
345             MessageManager.getString("label.annotations"));
346     JMenuItem item = new JMenuItem(ADDNEW);
347     item.addActionListener(this);
348     pop.add(item);
349     if (selectedRow < 0)
350     {
351       if (hasHiddenRows)
352       { // let the user make everything visible again
353         item = new JMenuItem(SHOWALL);
354         item.addActionListener(this);
355         pop.add(item);
356       }
357       pop.show(this, evt.getX(), evt.getY());
358       return;
359     }
360     item = new JMenuItem(EDITNAME);
361     item.addActionListener(this);
362     pop.add(item);
363     item = new JMenuItem(HIDE);
364     item.addActionListener(this);
365     pop.add(item);
366     // JAL-1264 hide all sequence-specific annotations of this type
367     if (selectedRow < aa.length)
368     {
369       if (aa[selectedRow].sequenceRef != null)
370       {
371         final String label = aa[selectedRow].label;
372         JMenuItem hideType = new JMenuItem();
373         String text = MessageManager.getString("label.hide_all") + " "
374                 + label;
375         hideType.setText(text);
376         hideType.addActionListener(new ActionListener()
377         {
378           @Override
379           public void actionPerformed(ActionEvent e)
380           {
381             AlignmentUtils.showOrHideSequenceAnnotations(
382                     ap.av.getAlignment(), Collections.singleton(label),
383                     null, false, false);
384             ap.refresh(true);
385           }
386         });
387         pop.add(hideType);
388       }
389     }
390     item = new JMenuItem(DELETE);
391     item.addActionListener(this);
392     pop.add(item);
393     if (hasHiddenRows)
394     {
395       item = new JMenuItem(SHOWALL);
396       item.addActionListener(this);
397       pop.add(item);
398     }
399     item = new JMenuItem(OUTPUT_TEXT);
400     item.addActionListener(this);
401     pop.add(item);
402     // TODO: annotation object should be typed for autocalculated/derived
403     // property methods
404     if (selectedRow < aa.length)
405     {
406       final String label = aa[selectedRow].label;
407       if (!aa[selectedRow].autoCalculated)
408       {
409         if (aa[selectedRow].graph == AlignmentAnnotation.NO_GRAPH)
410         {
411           // display formatting settings for this row.
412           pop.addSeparator();
413           // av and sequencegroup need to implement same interface for
414           item = new JCheckBoxMenuItem(TOGGLE_LABELSCALE,
415                   aa[selectedRow].scaleColLabel);
416           item.addActionListener(this);
417           pop.add(item);
418         }
419       }
420       else if (label.indexOf("Consensus") > -1)
421       {
422         addConsensusMenuOptions(ap, aa[selectedRow], pop);
423
424         final JMenuItem consclipbrd = new JMenuItem(COPYCONS_SEQ);
425         consclipbrd.addActionListener(this);
426         pop.add(consclipbrd);
427       }
428
429       addColourOrFilterByOptions(ap, aa[selectedRow], pop);
430
431       if (aa[selectedRow].graph == AlignmentAnnotation.CONTACT_MAP)
432       {
433         addContactMatrixOptions(ap, aa[selectedRow], pop);
434         // Set/adjust threshold for grouping ?
435         // colour alignment by this [type]
436         // select/hide columns by this row
437
438       }
439     }
440
441     pop.show(this, evt.getX(), evt.getY());
442   }
443
444   static void addColourOrFilterByOptions(final AlignmentPanel ap,
445           final AlignmentAnnotation alignmentAnnotation,
446           final JPopupMenu pop)
447   {
448     JMenuItem item;
449     item = new JMenuItem(
450             MessageManager.getString("label.colour_by_annotation"));
451     item.addActionListener(new ActionListener()
452     {
453
454       @Override
455       public void actionPerformed(ActionEvent e)
456       {
457         AnnotationColourChooser.displayFor(ap.av, ap, alignmentAnnotation,
458                 false);
459       };
460     });
461     pop.add(item);
462     if (alignmentAnnotation.sequenceRef != null)
463     {
464       item = new JMenuItem(
465               MessageManager.getString("label.colour_by_annotation") + " ("
466                       + MessageManager.getString("label.per_seq") + ")");
467       item.addActionListener(new ActionListener()
468       {
469         @Override
470         public void actionPerformed(ActionEvent e)
471         {
472           AnnotationColourChooser.displayFor(ap.av, ap, alignmentAnnotation,
473                   true);
474         };
475       });
476       pop.add(item);
477     }
478     item = new JMenuItem(
479             MessageManager.getString("action.select_by_annotation"));
480     item.addActionListener(new ActionListener()
481     {
482
483       @Override
484       public void actionPerformed(ActionEvent e)
485       {
486         AnnotationColumnChooser.displayFor(ap.av, ap, alignmentAnnotation);
487       };
488     });
489     pop.add(item);
490   }
491
492   static void addContactMatrixOptions(final AlignmentPanel ap,
493           final AlignmentAnnotation alignmentAnnotation,
494           final JPopupMenu pop)
495   {
496
497     final ContactMatrixI cm = ap.av.getContactMatrix(alignmentAnnotation);
498     JMenuItem item;
499     if (cm != null)
500     {
501       pop.addSeparator();
502
503       if (cm.hasGroups())
504       {
505         JCheckBoxMenuItem chitem = new JCheckBoxMenuItem(
506                 MessageManager.getString("action.show_groups_on_matrix"));
507         chitem.setToolTipText(MessageManager
508                 .getString("action.show_groups_on_matrix_tooltip"));
509         boolean showGroups = alignmentAnnotation
510                 .isShowGroupsForContactMatrix();
511         final AlignmentAnnotation sel_row = alignmentAnnotation;
512         chitem.setState(showGroups);
513         chitem.addActionListener(new ActionListener()
514         {
515
516           @Override
517           public void actionPerformed(ActionEvent e)
518           {
519             sel_row.setShowGroupsForContactMatrix(chitem.getState());
520             // so any annotation colour changes are propagated - though they
521             // probably won't be unless the annotation row colours are removed
522             // too!
523             ap.alignmentChanged();
524           }
525         });
526         pop.add(chitem);
527       }
528       if (cm.hasTree())
529       {
530         item = new JMenuItem(
531                 MessageManager.getString("action.show_tree_for_matrix"));
532         item.setToolTipText(MessageManager
533                 .getString("action.show_tree_for_matrix_tooltip"));
534         item.addActionListener(new ActionListener()
535         {
536
537           @Override
538           public void actionPerformed(ActionEvent e)
539           {
540
541             ap.alignFrame.showContactMapTree(alignmentAnnotation, cm);
542
543           }
544         });
545         pop.add(item);
546       }
547       else
548       {
549         item = new JMenuItem(
550                 MessageManager.getString("action.cluster_matrix"));
551         item.setToolTipText(
552                 MessageManager.getString("action.cluster_matrix_tooltip"));
553         item.addActionListener(new ActionListener()
554         {
555           @Override
556           public void actionPerformed(ActionEvent e)
557           {
558             new Thread(new Runnable()
559             {
560               @Override
561               public void run()
562               {
563                 final long progBar;
564                 ap.alignFrame.setProgressBar(
565                         MessageManager.formatMessage(
566                                 "action.clustering_matrix_for",
567                                 cm.getAnnotDescr(), 5f),
568                         progBar = System.currentTimeMillis());
569                 cm.setGroupSet(GroupSet.makeGroups(cm, true));
570                 cm.randomlyReColourGroups();
571                 cm.transferGroupColorsTo(alignmentAnnotation);
572                 ap.alignmentChanged();
573                 ap.alignFrame.showContactMapTree(alignmentAnnotation, cm);
574                 ap.alignFrame.setProgressBar(null, progBar);
575               }
576             }).start();
577           }
578         });
579         pop.add(item);
580       }
581     }
582   }
583
584   /**
585    * A helper method that adds menu options for calculation and visualisation of
586    * group and/or alignment consensus annotation to a popup menu. This is
587    * designed to be reusable for either unwrapped mode (popup menu is shown on
588    * component AnnotationLabels), or wrapped mode (popup menu is shown on
589    * IdPanel when the mouse is over an annotation label).
590    * 
591    * @param ap
592    * @param ann
593    * @param pop
594    */
595   static void addConsensusMenuOptions(AlignmentPanel ap,
596           AlignmentAnnotation ann, JPopupMenu pop)
597   {
598     pop.addSeparator();
599
600     final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
601             MessageManager.getString("label.ignore_gaps_consensus"),
602             (ann.groupRef != null) ? ann.groupRef.getIgnoreGapsConsensus()
603                     : ap.av.isIgnoreGapsConsensus());
604     final AlignmentAnnotation aaa = ann;
605     cbmi.addActionListener(new ActionListener()
606     {
607       @Override
608       public void actionPerformed(ActionEvent e)
609       {
610         if (aaa.groupRef != null)
611         {
612           aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState());
613           ap.getAnnotationPanel()
614                   .paint(ap.getAnnotationPanel().getGraphics());
615         }
616         else
617         {
618           ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
619         }
620         ap.alignmentChanged();
621       }
622     });
623     pop.add(cbmi);
624
625     if (aaa.groupRef != null)
626     {
627       /*
628        * group consensus options
629        */
630       final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
631               MessageManager.getString("label.show_group_histogram"),
632               ann.groupRef.isShowConsensusHistogram());
633       chist.addActionListener(new ActionListener()
634       {
635         @Override
636         public void actionPerformed(ActionEvent e)
637         {
638           aaa.groupRef.setShowConsensusHistogram(chist.getState());
639           ap.repaint();
640         }
641       });
642       pop.add(chist);
643       final JCheckBoxMenuItem cprofl = new JCheckBoxMenuItem(
644               MessageManager.getString("label.show_group_logo"),
645               ann.groupRef.isShowSequenceLogo());
646       cprofl.addActionListener(new ActionListener()
647       {
648         @Override
649         public void actionPerformed(ActionEvent e)
650         {
651           aaa.groupRef.setshowSequenceLogo(cprofl.getState());
652           ap.repaint();
653         }
654       });
655       pop.add(cprofl);
656       final JCheckBoxMenuItem cproflnorm = new JCheckBoxMenuItem(
657               MessageManager.getString("label.normalise_group_logo"),
658               ann.groupRef.isNormaliseSequenceLogo());
659       cproflnorm.addActionListener(new ActionListener()
660       {
661         @Override
662         public void actionPerformed(ActionEvent e)
663         {
664           aaa.groupRef.setNormaliseSequenceLogo(cproflnorm.getState());
665           // automatically enable logo display if we're clicked
666           aaa.groupRef.setshowSequenceLogo(true);
667           ap.repaint();
668         }
669       });
670       pop.add(cproflnorm);
671     }
672     else
673     {
674       /*
675        * alignment consensus options
676        */
677       final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
678               MessageManager.getString("label.show_histogram"),
679               ap.av.isShowConsensusHistogram());
680       chist.addActionListener(new ActionListener()
681       {
682         @Override
683         public void actionPerformed(ActionEvent e)
684         {
685           ap.av.setShowConsensusHistogram(chist.getState());
686           ap.alignFrame.setMenusForViewport();
687           ap.repaint();
688         }
689       });
690       pop.add(chist);
691       final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
692               MessageManager.getString("label.show_logo"),
693               ap.av.isShowSequenceLogo());
694       cprof.addActionListener(new ActionListener()
695       {
696         @Override
697         public void actionPerformed(ActionEvent e)
698         {
699           ap.av.setShowSequenceLogo(cprof.getState());
700           ap.alignFrame.setMenusForViewport();
701           ap.repaint();
702         }
703       });
704       pop.add(cprof);
705       final JCheckBoxMenuItem cprofnorm = new JCheckBoxMenuItem(
706               MessageManager.getString("label.normalise_logo"),
707               ap.av.isNormaliseSequenceLogo());
708       cprofnorm.addActionListener(new ActionListener()
709       {
710         @Override
711         public void actionPerformed(ActionEvent e)
712         {
713           ap.av.setShowSequenceLogo(true);
714           ap.av.setNormaliseSequenceLogo(cprofnorm.getState());
715           ap.alignFrame.setMenusForViewport();
716           ap.repaint();
717         }
718       });
719       pop.add(cprofnorm);
720     }
721   }
722
723   /**
724    * Reorders annotation rows after a drag of a label
725    * 
726    * @param evt
727    */
728   @Override
729   public void mouseReleased(MouseEvent evt)
730   {
731     if (evt.isPopupTrigger())
732     {
733       showPopupMenu(evt);
734       return;
735     }
736
737     int start = selectedRow;
738     getSelectedRow(evt.getY() - getScrollOffset());
739     int end = selectedRow;
740
741     /*
742      * if dragging to resize instead, start == end
743      */
744     if (start != end)
745     {
746       // Swap these annotations
747       AlignmentAnnotation startAA = ap.av.getAlignment()
748               .getAlignmentAnnotation()[start];
749       if (end == -1)
750       {
751         end = ap.av.getAlignment().getAlignmentAnnotation().length - 1;
752       }
753       AlignmentAnnotation endAA = ap.av.getAlignment()
754               .getAlignmentAnnotation()[end];
755
756       ap.av.getAlignment().getAlignmentAnnotation()[end] = startAA;
757       ap.av.getAlignment().getAlignmentAnnotation()[start] = endAA;
758     }
759
760     resizePanel = false;
761     dragEvent = null;
762     repaint();
763     ap.getAnnotationPanel().repaint();
764   }
765
766   /**
767    * Removes the height adjuster image on leaving the panel, unless currently
768    * dragging it
769    */
770   @Override
771   public void mouseExited(MouseEvent evt)
772   {
773     if (resizePanel && dragEvent == null)
774     {
775       resizePanel = false;
776       repaint();
777     }
778   }
779
780   /**
781    * A mouse drag may be either an adjustment of the panel height (if flag
782    * resizePanel is set on), or a reordering of the annotation rows. The former
783    * is dealt with by this method, the latter in mouseReleased.
784    * 
785    * @param evt
786    */
787   @Override
788   public void mouseDragged(MouseEvent evt)
789   {
790     dragEvent = evt;
791
792     if (resizePanel)
793     {
794       Dimension d = ap.annotationScroller.getPreferredSize();
795       int dif = evt.getY() - oldY;
796       dif -= dif % ap.av.getCharHeight();
797
798       // don't allow setting an annotation panel height larger than visible
799       // (otherwise you can't get back)
800       if (d.height - dif > ap.idPanelHolder.getHeight()
801               - ap.getIdSpaceFillerPanel1().getHeight())
802       {
803         return;
804       }
805
806       if ((d.height - dif) > 20)
807       {
808         ap.annotationScroller
809                 .setPreferredSize(new Dimension(d.width, d.height - dif));
810         d = ap.annotationSpaceFillerHolder.getPreferredSize();
811         ap.annotationSpaceFillerHolder
812                 .setPreferredSize(new Dimension(d.width, d.height - dif));
813         ap.paintAlignment(true, false);
814       }
815
816       ap.addNotify();
817     }
818     else
819     {
820       repaint();
821     }
822   }
823
824   /**
825    * Updates the tooltip as the mouse moves over the labels
826    * 
827    * @param evt
828    */
829   @Override
830   public void mouseMoved(MouseEvent evt)
831   {
832     showOrHideAdjuster(evt);
833
834     getSelectedRow(evt.getY() - getScrollOffset());
835
836     if (selectedRow > -1 && ap.av.getAlignment()
837             .getAlignmentAnnotation().length > selectedRow)
838     {
839       AlignmentAnnotation[] anns = ap.av.getAlignment()
840               .getAlignmentAnnotation();
841       AlignmentAnnotation aa = anns[selectedRow];
842
843       String desc = getTooltip(aa);
844       this.setToolTipText(desc);
845       String msg = getStatusMessage(aa, anns);
846       ap.alignFrame.setStatus(msg);
847     }
848   }
849
850   /**
851    * Constructs suitable text to show in the status bar when over an annotation
852    * label, containing the associated sequence name (if any), and the annotation
853    * labels (or all labels for a graph group annotation)
854    * 
855    * @param aa
856    * @param anns
857    * @return
858    */
859   static String getStatusMessage(AlignmentAnnotation aa,
860           AlignmentAnnotation[] anns)
861   {
862     if (aa == null)
863     {
864       return null;
865     }
866
867     StringBuilder msg = new StringBuilder(32);
868     if (aa.sequenceRef != null)
869     {
870       msg.append(aa.sequenceRef.getName()).append(" : ");
871     }
872
873     if (aa.graphGroup == -1)
874     {
875       msg.append(aa.label);
876     }
877     else if (anns != null)
878     {
879       boolean first = true;
880       for (int i = anns.length - 1; i >= 0; i--)
881       {
882         if (anns[i].graphGroup == aa.graphGroup)
883         {
884           if (!first)
885           {
886             msg.append(", ");
887           }
888           msg.append(anns[i].label);
889           first = false;
890         }
891       }
892     }
893
894     return msg.toString();
895   }
896
897   /**
898    * Answers a tooltip, formatted as html, containing the annotation description
899    * (prefixed by associated sequence id if applicable), and the annotation
900    * (non-positional) score if it has one. Answers null if neither description
901    * nor score is found.
902    * 
903    * @param aa
904    * @return
905    */
906   static String getTooltip(AlignmentAnnotation aa)
907   {
908     if (aa == null)
909     {
910       return null;
911     }
912     StringBuilder tooltip = new StringBuilder();
913     if (aa.description != null && !aa.description.equals("New description"))
914     {
915       // TODO: we could refactor and merge this code with the code in
916       // jalview.gui.SeqPanel.mouseMoved(..) that formats sequence feature
917       // tooltips
918       String desc = aa.getDescription(true).trim();
919       if (!desc.toLowerCase(Locale.ROOT).startsWith(HTML_START_TAG))
920       {
921         tooltip.append(HTML_START_TAG);
922         desc = desc.replace("<", "&lt;");
923       }
924       else if (desc.toLowerCase(Locale.ROOT).endsWith(HTML_END_TAG))
925       {
926         desc = desc.substring(0, desc.length() - HTML_END_TAG.length());
927       }
928       tooltip.append(desc);
929     }
930     else
931     {
932       // begin the tooltip's html fragment
933       tooltip.append(HTML_START_TAG);
934     }
935     if (aa.hasScore())
936     {
937       if (tooltip.length() > HTML_START_TAG.length())
938       {
939         tooltip.append("<br/>");
940       }
941       // TODO: limit precision of score to avoid noise from imprecise
942       // doubles
943       // (64.7 becomes 64.7+/some tiny value).
944       tooltip.append(" Score: ").append(String.valueOf(aa.score));
945     }
946
947     if (tooltip.length() > HTML_START_TAG.length())
948     {
949       return tooltip.append(HTML_END_TAG).toString();
950     }
951
952     /*
953      * nothing in the tooltip (except "<html>")
954      */
955     return null;
956   }
957
958   /**
959    * Shows the height adjuster image if the mouse moves into the top left
960    * region, or hides it if the mouse leaves the regio
961    * 
962    * @param evt
963    */
964   protected void showOrHideAdjuster(MouseEvent evt)
965   {
966     boolean was = resizePanel;
967     resizePanel = evt.getY() < HEIGHT_ADJUSTER_HEIGHT
968             && evt.getX() < HEIGHT_ADJUSTER_WIDTH;
969
970     if (resizePanel != was)
971     {
972       setCursor(Cursor
973               .getPredefinedCursor(resizePanel ? Cursor.S_RESIZE_CURSOR
974                       : Cursor.DEFAULT_CURSOR));
975       repaint();
976     }
977   }
978
979   @Override
980   public void mouseClicked(MouseEvent evt)
981   {
982     final AlignmentAnnotation[] aa = ap.av.getAlignment()
983             .getAlignmentAnnotation();
984     if (!evt.isPopupTrigger() && SwingUtilities.isLeftMouseButton(evt))
985     {
986       if (selectedRow > -1 && selectedRow < aa.length)
987       {
988         if (aa[selectedRow].groupRef != null)
989         {
990           if (evt.getClickCount() >= 2)
991           {
992             // todo: make the ap scroll to the selection - not necessary, first
993             // click highlights/scrolls, second selects
994             ap.getSeqPanel().ap.getIdPanel().highlightSearchResults(null);
995             // process modifiers
996             SequenceGroup sg = ap.av.getSelectionGroup();
997             if (sg == null || sg == aa[selectedRow].groupRef
998                     || !(Platform.isControlDown(evt) || evt.isShiftDown()))
999             {
1000               if (Platform.isControlDown(evt) || evt.isShiftDown())
1001               {
1002                 // clone a new selection group from the associated group
1003                 ap.av.setSelectionGroup(
1004                         new SequenceGroup(aa[selectedRow].groupRef));
1005               }
1006               else
1007               {
1008                 // set selection to the associated group so it can be edited
1009                 ap.av.setSelectionGroup(aa[selectedRow].groupRef);
1010               }
1011             }
1012             else
1013             {
1014               // modify current selection with associated group
1015               int remainToAdd = aa[selectedRow].groupRef.getSize();
1016               for (SequenceI sgs : aa[selectedRow].groupRef.getSequences())
1017               {
1018                 if (jalview.util.Platform.isControlDown(evt))
1019                 {
1020                   sg.addOrRemove(sgs, --remainToAdd == 0);
1021                 }
1022                 else
1023                 {
1024                   // notionally, we should also add intermediate sequences from
1025                   // last added sequence ?
1026                   sg.addSequence(sgs, --remainToAdd == 0);
1027                 }
1028               }
1029             }
1030
1031             ap.paintAlignment(false, false);
1032             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
1033             ap.av.sendSelection();
1034           }
1035           else
1036           {
1037             ap.getSeqPanel().ap.getIdPanel().highlightSearchResults(
1038                     aa[selectedRow].groupRef.getSequences(null));
1039           }
1040           return;
1041         }
1042         else if (aa[selectedRow].sequenceRef != null)
1043         {
1044           if (evt.getClickCount() == 1)
1045           {
1046             ap.getSeqPanel().ap.getIdPanel()
1047                     .highlightSearchResults(Arrays.asList(new SequenceI[]
1048                     { aa[selectedRow].sequenceRef }));
1049           }
1050           else if (evt.getClickCount() >= 2)
1051           {
1052             ap.getSeqPanel().ap.getIdPanel().highlightSearchResults(null);
1053             SequenceGroup sg = ap.av.getSelectionGroup();
1054             if (sg != null)
1055             {
1056               // we make a copy rather than edit the current selection if no
1057               // modifiers pressed
1058               // see Enhancement JAL-1557
1059               if (!(Platform.isControlDown(evt) || evt.isShiftDown()))
1060               {
1061                 sg = new SequenceGroup(sg);
1062                 sg.clear();
1063                 sg.addSequence(aa[selectedRow].sequenceRef, false);
1064               }
1065               else
1066               {
1067                 if (Platform.isControlDown(evt))
1068                 {
1069                   sg.addOrRemove(aa[selectedRow].sequenceRef, true);
1070                 }
1071                 else
1072                 {
1073                   // notionally, we should also add intermediate sequences from
1074                   // last added sequence ?
1075                   sg.addSequence(aa[selectedRow].sequenceRef, true);
1076                 }
1077               }
1078             }
1079             else
1080             {
1081               sg = new SequenceGroup();
1082               sg.setStartRes(0);
1083               sg.setEndRes(ap.av.getAlignment().getWidth() - 1);
1084               sg.addSequence(aa[selectedRow].sequenceRef, false);
1085             }
1086             ap.av.setSelectionGroup(sg);
1087             ap.paintAlignment(false, false);
1088             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
1089             ap.av.sendSelection();
1090           }
1091
1092         }
1093       }
1094       return;
1095     }
1096   }
1097
1098   /**
1099    * do a single sequence copy to jalview and the system clipboard
1100    * 
1101    * @param sq
1102    *          sequence to be copied to clipboard
1103    */
1104   protected void copy_annotseqtoclipboard(SequenceI sq)
1105   {
1106     SequenceI[] seqs = new SequenceI[] { sq };
1107     String[] omitHidden = null;
1108     SequenceI[] dseqs = new SequenceI[] { sq.getDatasetSequence() };
1109     if (dseqs[0] == null)
1110     {
1111       dseqs[0] = new Sequence(sq);
1112       dseqs[0].setSequence(AlignSeq.extractGaps(Comparison.GapChars,
1113               sq.getSequenceAsString()));
1114
1115       sq.setDatasetSequence(dseqs[0]);
1116     }
1117     Alignment ds = new Alignment(dseqs);
1118     if (av.hasHiddenColumns())
1119     {
1120       Iterator<int[]> it = av.getAlignment().getHiddenColumns()
1121               .getVisContigsIterator(0, sq.getLength() + 1, false);
1122       omitHidden = new String[] { sq.getSequenceStringFromIterator(it) };
1123     }
1124
1125     int[] alignmentStartEnd = new int[] { 0, ds.getWidth() - 1 };
1126     if (av.hasHiddenColumns())
1127     {
1128       alignmentStartEnd = av.getAlignment().getHiddenColumns()
1129               .getVisibleStartAndEndIndex(av.getAlignment().getWidth());
1130     }
1131
1132     String output = new FormatAdapter().formatSequences(FileFormat.Fasta,
1133             seqs, omitHidden, alignmentStartEnd);
1134
1135     Toolkit.getDefaultToolkit().getSystemClipboard()
1136             .setContents(new StringSelection(output), Desktop.instance);
1137
1138     HiddenColumns hiddenColumns = null;
1139
1140     if (av.hasHiddenColumns())
1141     {
1142       hiddenColumns = new HiddenColumns(
1143               av.getAlignment().getHiddenColumns());
1144     }
1145
1146     Desktop.jalviewClipboard = new Object[] { seqs, ds, // what is the dataset
1147                                                         // of a consensus
1148                                                         // sequence ? need to
1149                                                         // flag
1150         // sequence as special.
1151         hiddenColumns };
1152   }
1153
1154   /**
1155    * DOCUMENT ME!
1156    * 
1157    * @param g1
1158    *          DOCUMENT ME!
1159    */
1160   @Override
1161   public void paintComponent(Graphics g)
1162   {
1163
1164     int width = getWidth();
1165     if (width == 0)
1166     {
1167       width = ap.calculateIdWidth().width;
1168     }
1169
1170     Graphics2D g2 = (Graphics2D) g;
1171     if (av.antiAlias)
1172     {
1173       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1174               RenderingHints.VALUE_ANTIALIAS_ON);
1175     }
1176
1177     drawComponent(g2, true, width, true);
1178   }
1179
1180   /**
1181    * Draw the full set of annotation Labels for the alignment at the given
1182    * cursor
1183    * 
1184    * @param g
1185    *          Graphics2D instance (needed for font scaling)
1186    * @param width
1187    *          Width for scaling labels
1188    * 
1189    */
1190   public void drawComponent(Graphics g, int width)
1191   {
1192     drawComponent(g, false, width, true);
1193   }
1194
1195   /**
1196    * Draw the full set of annotation Labels for the alignment at the given
1197    * cursor
1198    * 
1199    * @param g
1200    *          Graphics2D instance (needed for font scaling)
1201    * @param clip
1202    *          - true indicates that only current visible area needs to be
1203    *          rendered
1204    * @param width
1205    *          Width for scaling labels
1206    */
1207   public void drawComponent(Graphics g, boolean clip, int givenWidth,
1208           boolean forGUI)
1209   {
1210     int width = givenWidth;
1211     IdwidthAdjuster iwa = null;
1212     if (ap != null)
1213     {
1214       iwa = ap.idwidthAdjuster;
1215       if (Cache.getDefault(ADJUST_ANNOTATION_LABELS_WIDTH_PREF, true)
1216               || Jalview.isHeadlessMode())
1217       {
1218         Graphics2D g2d = (Graphics2D) g;
1219         Graphics dummy = g2d.create();
1220         int newAnnotationIdWidth = drawLabels(dummy, clip, width, false,
1221                 forGUI, null, false);
1222         dummy.dispose();
1223         Dimension d = ap.calculateDefaultAlignmentIdWidth();
1224         int alignmentIdWidth = d.width;
1225         if (iwa != null && !iwa.manuallyAdjusted())
1226         {
1227           // If no manual adjustment to ID column with has been made then adjust
1228           // width match widest of alignment or annotation id widths
1229           boolean allowShrink = Cache.getDefault("ALLOW_SHRINK_ID_WIDTH",
1230                   false);
1231           width = Math.max(alignmentIdWidth, newAnnotationIdWidth);
1232           if (clip && width < givenWidth && !allowShrink)
1233           {
1234             width = givenWidth;
1235           }
1236         }
1237         else if (newAnnotationIdWidth != annotationIdWidth
1238                 && newAnnotationIdWidth > givenWidth
1239                 && newAnnotationIdWidth > alignmentIdWidth)
1240         {
1241           // otherwise if the annotation id width has become larger than the
1242           // current id width, increase
1243           width = newAnnotationIdWidth;
1244           annotationIdWidth = newAnnotationIdWidth;
1245         }
1246         // set the width if it's changed
1247         if (width != ap.av.getIdWidth())
1248         {
1249           iwa.setWidth(width);
1250         }
1251       }
1252     }
1253     else
1254     {
1255       int newAnnotationIdWidth = drawLabels(g, clip, width, false, forGUI,
1256               null, false);
1257       width = newAnnotationIdWidth < givenWidth ? givenWidth
1258               : Math.min(newAnnotationIdWidth, givenWidth);
1259     }
1260     drawLabels(g, clip, width, true, forGUI, null, false);
1261   }
1262
1263   /**
1264    * Render the full set of annotation Labels for the alignment at the given
1265    * cursor. If actuallyDraw is false or g is null then no actual drawing will
1266    * occur, but the widest label width will be returned. If g is null then
1267    * fmetrics must be supplied.
1268    * 
1269    * @param g
1270    *          Graphics2D instance (used for rendering and font scaling if no
1271    *          fmetrics supplied)
1272    * @param clip
1273    *          - true indicates that only current visible area needs to be
1274    *          rendered
1275    * @param width
1276    *          Width for scaling labels
1277    * @param actuallyDraw
1278    *          - when false, no graphics are rendered to g0
1279    * @param forGUI
1280    *          - when false, GUI relevant marks like indicators for dragging
1281    *          annotation panel height are not rendered
1282    * @param fmetrics
1283    *          FontMetrics if Graphics object g is null
1284    * @param includeHidden
1285    *          - when true returned width includes labels in hidden row width
1286    *          calculation
1287    * @return the width of the annotation labels.
1288    */
1289   public int drawLabels(Graphics g0, boolean clip, int width,
1290           boolean actuallyDraw, boolean forGUI, FontMetrics fmetrics,
1291           boolean includeHidden)
1292   {
1293     if (clip)
1294     {
1295       clip = Cache.getDefault("MOVE_SEQUENCE_ID_WITH_VISIBLE_ANNOTATIONS",
1296               true);
1297     }
1298     Graphics g = null;
1299     // create a dummy Graphics object if not drawing and one is supplied
1300     if (g0 != null)
1301     {
1302       if (!actuallyDraw)
1303       {
1304         Graphics2D g2d = (Graphics2D) g0;
1305         g = g2d.create();
1306       }
1307       else
1308       {
1309         g = g0;
1310       }
1311     }
1312     int actualWidth = 0;
1313     if (g != null)
1314     {
1315       if (av.getFont().getSize() < 10)
1316       {
1317         g.setFont(font);
1318       }
1319       else
1320       {
1321         g.setFont(av.getFont());
1322       }
1323     }
1324
1325     FontMetrics fm = fmetrics == null ? g.getFontMetrics(g.getFont())
1326             : fmetrics;
1327     if (actuallyDraw)
1328     {
1329       g.setColor(Color.white);
1330       g.fillRect(0, 0, getWidth(), getHeight());
1331
1332       if (!Cache.getDefault(RESIZE_MARGINS_MARK_PREF, false)
1333               && !av.getWrapAlignment() && forGUI)
1334       {
1335         g.setColor(Color.LIGHT_GRAY);
1336         g.drawLine(0, HEIGHT_ADJUSTER_HEIGHT / 4, HEIGHT_ADJUSTER_WIDTH / 4,
1337                 HEIGHT_ADJUSTER_HEIGHT / 4);
1338         g.drawLine(0, 3 * HEIGHT_ADJUSTER_HEIGHT / 4,
1339                 HEIGHT_ADJUSTER_WIDTH / 4, 3 * HEIGHT_ADJUSTER_HEIGHT / 4);
1340
1341       }
1342     }
1343
1344     if (actuallyDraw)
1345     {
1346       g.translate(0, getScrollOffset());
1347       g.setColor(Color.black);
1348     }
1349     SequenceI lastSeqRef = null;
1350     String lastLabel = null;
1351     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
1352     int fontHeight = g != null ? g.getFont().getSize()
1353             : fm.getFont().getSize();
1354     int y = 0;
1355     int x = 0;
1356     int graphExtras = 0;
1357     int offset = 0;
1358     Font baseFont = g != null ? g.getFont() : fm.getFont();
1359     FontMetrics baseMetrics = fm;
1360     int ofontH = fontHeight;
1361     int sOffset = 0;
1362     int visHeight = 0;
1363     int[] visr = (ap != null && ap.getAnnotationPanel() != null)
1364             ? ap.getAnnotationPanel().getVisibleVRange()
1365             : null;
1366     if (clip && visr != null)
1367     {
1368       sOffset = visr[0];
1369       visHeight = visr[1];
1370     }
1371     boolean visible = true, before = false, after = false;
1372     if (aa != null)
1373     {
1374       hasHiddenRows = false;
1375       int olY = 0;
1376       int nexAA = 0;
1377       for (int i = 0; i < aa.length; i++)
1378       {
1379         visible = true;
1380         if (!aa[i].visible && !includeHidden)
1381         {
1382           hasHiddenRows = true;
1383           continue;
1384         }
1385         olY = y;
1386         // look ahead to next annotation
1387         for (nexAA = i + 1; nexAA < aa.length
1388                 && (!aa[nexAA].visible && includeHidden); nexAA++)
1389           ;
1390         y += aa[i].height;
1391         if (clip)
1392         {
1393           if (y < sOffset)
1394           {
1395             if (!before)
1396             {
1397               if (debugRedraw)
1398               {
1399                 jalview.bin.Console.outPrintln("before vis: " + i);
1400               }
1401               before = true;
1402             }
1403             // don't draw what isn't visible
1404             continue;
1405           }
1406           if (olY > visHeight)
1407           {
1408
1409             if (!after)
1410             {
1411               if (debugRedraw)
1412               {
1413                 jalview.bin.Console.outPrintln(
1414                         "Scroll offset: " + sOffset + " after vis: " + i);
1415               }
1416               after = true;
1417             }
1418             // don't draw what isn't visible
1419             continue;
1420           }
1421         }
1422         if (actuallyDraw && g != null)
1423         {
1424           g.setColor(Color.black);
1425         }
1426         offset = -aa[i].height / 2;
1427
1428         if (aa[i].hasText)
1429         {
1430           offset += fm.getHeight() / 2;
1431           offset -= fm.getDescent();
1432         }
1433         else
1434         {
1435           offset += fm.getDescent();
1436         }
1437         String label = aa[i].label;
1438         boolean vertBar = false;
1439         if ((lastLabel != null && lastLabel.equals(label)))
1440         {
1441           label = aa[i].description;
1442         }
1443         else
1444         {
1445           if (nexAA < aa.length && label.equals(aa[nexAA].label)) // &&
1446                                                                   // aa[nexY].sequenceRef==aa[i].sequenceRef)
1447           {
1448             lastLabel = label;
1449             // next label is the same as this label
1450             label = aa[i].description;
1451           }
1452           else
1453           {
1454             lastLabel = label;
1455           }
1456         }
1457         if (aa[i].sequenceRef != null)
1458         {
1459           if (aa[i].sequenceRef != lastSeqRef)
1460           {
1461             label = aa[i].sequenceRef.getName() + " " + label;
1462             // TODO record relationship between sequence and this annotation and
1463             // display it here
1464           }
1465           else
1466           {
1467             vertBar = true;
1468           }
1469         }
1470
1471         int labelWidth = fm.stringWidth(label) + 3;
1472         x = width - labelWidth;
1473
1474         if (aa[i].graphGroup > -1)
1475         {
1476           int groupSize = 0;
1477           // TODO: JAL-1291 revise rendering model so the graphGroup map is
1478           // computed efficiently for all visible labels
1479           for (int gg = 0; gg < aa.length; gg++)
1480           {
1481             if (aa[gg].graphGroup == aa[i].graphGroup)
1482             {
1483               groupSize++;
1484             }
1485           }
1486           if (groupSize * (fontHeight + 8) < aa[i].height)
1487           {
1488             graphExtras = (aa[i].height - (groupSize * (fontHeight + 8)))
1489                     / 2;
1490           }
1491           else
1492           {
1493             // scale font to fit
1494             float h = aa[i].height / (float) groupSize, s;
1495             if (h < 9)
1496             {
1497               visible = false;
1498             }
1499             else
1500             {
1501               fontHeight = -8 + (int) h;
1502               s = ((float) fontHeight) / (float) ofontH;
1503               Font f = baseFont
1504                       .deriveFont(AffineTransform.getScaleInstance(s, s));
1505               Canvas c = new Canvas();
1506               fm = c.getFontMetrics(f);
1507               if (actuallyDraw && g != null)
1508               {
1509                 g.setFont(f);
1510                 // fm = g.getFontMetrics();
1511                 graphExtras = (aa[i].height
1512                         - (groupSize * (fontHeight + 8))) / 2;
1513               }
1514             }
1515           }
1516           if (visible)
1517           {
1518             for (int gg = 0; gg < aa.length; gg++)
1519             {
1520               if (aa[gg].graphGroup == aa[i].graphGroup)
1521               {
1522                 labelWidth = fm.stringWidth(aa[gg].label) + 3;
1523                 x = width - labelWidth;
1524                 if (actuallyDraw && g != null)
1525                 {
1526                   g.drawString(aa[gg].label, x, y - graphExtras);
1527
1528                   if (aa[gg]._linecolour != null)
1529                   {
1530
1531                     g.setColor(aa[gg]._linecolour);
1532                     g.drawLine(x, y - graphExtras + 3,
1533                             x + fm.stringWidth(aa[gg].label),
1534                             y - graphExtras + 3);
1535                   }
1536
1537                   g.setColor(Color.black);
1538                 }
1539                 graphExtras += fontHeight + 8;
1540               }
1541             }
1542           }
1543           if (actuallyDraw && g != null)
1544           {
1545             g.setFont(baseFont);
1546           }
1547           fm = baseMetrics;
1548           fontHeight = ofontH;
1549         }
1550         else
1551         {
1552           if (actuallyDraw && g != null)
1553           {
1554             if (vertBar)
1555             {
1556               g.drawLine(width - 3, y + offset - fontHeight, width - 3,
1557                       (int) (y - 1.5 * aa[i].height - offset - fontHeight));
1558               // g.drawLine(20, y + offset, x - 20, y + offset);
1559
1560             }
1561             g.drawString(label, x, y + offset);
1562           }
1563         }
1564         lastSeqRef = aa[i].sequenceRef;
1565
1566         if (labelWidth > actualWidth)
1567         {
1568           actualWidth = labelWidth;
1569         }
1570       }
1571     }
1572
1573     if (!resizePanel && dragEvent != null && aa != null && selectedRow > -1
1574             && selectedRow < aa.length)
1575     {
1576       if (actuallyDraw && g != null)
1577       {
1578         g.setColor(Color.lightGray);
1579         g.drawString(
1580                 (aa[selectedRow].sequenceRef == null ? ""
1581                         : aa[selectedRow].sequenceRef.getName())
1582                         + aa[selectedRow].label,
1583                 dragEvent.getX(), dragEvent.getY() - getScrollOffset());
1584       }
1585     }
1586
1587     if (!av.getWrapAlignment() && ((aa == null) || (aa.length < 1)))
1588     {
1589       if (actuallyDraw && g != null)
1590       {
1591         g.drawString(MessageManager.getString("label.right_click"), 2, 8);
1592         g.drawString(MessageManager.getString("label.to_add_annotation"), 2,
1593                 18);
1594       }
1595     }
1596
1597     return actualWidth;
1598   }
1599
1600   public int getScrollOffset()
1601   {
1602     return scrollOffset;
1603   }
1604
1605   @Override
1606   public void mouseEntered(MouseEvent e)
1607   {
1608   }
1609
1610   public void drawComponentNotGUI(Graphics idGraphics, int idWidth)
1611   {
1612     drawComponent(idGraphics, false, idWidth, false);
1613   }
1614 }