Clover icon

Coverage Report

  1. Project Clover database Wed Dec 3 2025 15:58:31 GMT
  2. Package jalview.gui

File AnnotationLabels.java

 

Coverage histogram

../../img/srcFileCovDistChart4.png
49% of files have more coverage

Code metrics

304
592
43
1
1,755
1,342
246
0.42
13.77
43
5.72

Classes

Class Line # Actions
AnnotationLabels 82 592 246
0.356762535.7%
 

Contributing tests

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