Clover icon

Coverage Report

  1. Project Clover database Mon Nov 11 2024 20:42:03 GMT
  2. Package jalview.gui

File TreeCanvas.java

 

Coverage histogram

../../img/srcFileCovDistChart1.png
56% of files have more coverage

Code metrics

246
551
49
1
1,754
1,202
210
0.38
11.24
49
4.29

Classes

Class Line # Actions
TreeCanvas 83 551 210
0.069739957%
 

Contributing tests

This file is covered by 8 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.BasicStroke;
24    import java.awt.Color;
25    import java.awt.Dimension;
26    import java.awt.Font;
27    import java.awt.FontMetrics;
28    import java.awt.Graphics;
29    import java.awt.Graphics2D;
30    import java.awt.Point;
31    import java.awt.Rectangle;
32    import java.awt.RenderingHints;
33    import java.awt.event.MouseEvent;
34    import java.awt.event.MouseListener;
35    import java.awt.event.MouseMotionListener;
36    import java.awt.print.PageFormat;
37    import java.awt.print.Printable;
38    import java.awt.print.PrinterException;
39    import java.awt.print.PrinterJob;
40    import java.util.ArrayList;
41    import java.util.BitSet;
42    import java.util.HashMap;
43    import java.util.Hashtable;
44    import java.util.LinkedHashSet;
45    import java.util.List;
46    import java.util.Map;
47    import java.util.Map.Entry;
48    import java.util.Vector;
49   
50    import javax.swing.JPanel;
51    import javax.swing.JScrollPane;
52    import javax.swing.SwingUtilities;
53    import javax.swing.ToolTipManager;
54   
55    import jalview.analysis.AlignmentUtils;
56    import jalview.analysis.Conservation;
57    import jalview.analysis.TreeModel;
58    import jalview.api.AlignViewportI;
59    import jalview.bin.Console;
60    import jalview.datamodel.AlignmentAnnotation;
61    import jalview.datamodel.BinaryNode;
62    import jalview.datamodel.ColumnSelection;
63    import jalview.datamodel.ContactMatrixI;
64    import jalview.datamodel.HiddenColumns;
65    import jalview.datamodel.Sequence;
66    import jalview.datamodel.SequenceGroup;
67    import jalview.datamodel.SequenceI;
68    import jalview.datamodel.SequenceNode;
69    import jalview.gui.JalviewColourChooser.ColourChooserListener;
70    import jalview.schemes.ColourSchemeI;
71    import jalview.structure.SelectionSource;
72    import jalview.util.ColorUtils;
73    import jalview.util.Format;
74    import jalview.util.MessageManager;
75    import jalview.ws.datamodel.MappableContactMatrixI;
76   
77    /**
78    * DOCUMENT ME!
79    *
80    * @author $author$
81    * @version $Revision$
82    */
 
83    public class TreeCanvas extends JPanel implements MouseListener, Runnable,
84    Printable, MouseMotionListener, SelectionSource
85    {
86    /** DOCUMENT ME!! */
87    public static final String PLACEHOLDER = " * ";
88   
89    private static final int DASHED_LINE_Y_OFFSET = 6;
90   
91    TreeModel tree;
92   
93    JScrollPane scrollPane;
94   
95    TreePanel tp;
96   
97    private AlignViewport av;
98   
99    private AlignmentPanel ap;
100   
101    Font font;
102   
103    FontMetrics fm;
104   
105    boolean fitToWindow = true;
106   
107    boolean showDistances = false;
108   
109    boolean showStructureProviderLabels = false;
110   
111    boolean showStructureProviderColouredLines = false;
112   
113    boolean showBootstrap = false;
114   
115    boolean markPlaceholders = false;
116   
117    int offx = 20;
118   
119    int offy;
120   
121    private float threshold;
122   
123    int labelLengthThreshold = 4;
124   
125    String longestName;
126   
127    int labelLength = -1;
128   
129    /**
130    * TODO - these rectangle-hash lookups should be optimised for big trees...
131    */
132    Map<BinaryNode, Rectangle> nameHash = new Hashtable<>();
133   
134    Map<BinaryNode, Rectangle> nodeHash = new Hashtable<>();
135   
136    BinaryNode highlightNode;
137   
138    boolean applyToAllViews = false;
139   
140    // Map to store label positions (BinaryNode -> List of bounding rectangles for
141    // the label)
142    private Map<BinaryNode, List<Rectangle>> labelBoundsMap = new HashMap<>();
143   
144    /**
145    * Creates a new TreeCanvas object.
146    *
147    * @param av
148    * DOCUMENT ME!
149    * @param tree
150    * DOCUMENT ME!
151    * @param scroller
152    * DOCUMENT ME!
153    * @param label
154    * DOCUMENT ME!
155    */
 
156  14 toggle public TreeCanvas(TreePanel tp, AlignmentPanel ap, JScrollPane scroller)
157    {
158  14 this.tp = tp;
159  14 this.av = ap.av;
160  14 this.setAssociatedPanel(ap);
161  14 font = av.getFont();
162  14 scrollPane = scroller;
163  14 addMouseListener(this);
164  14 addMouseMotionListener(this);
165   
166  14 ToolTipManager.sharedInstance().registerComponent(this);
167    }
168   
 
169  0 toggle public void clearSelectedLeaves()
170    {
171  0 Vector<BinaryNode> leaves = tp.getTree()
172    .findLeaves(tp.getTree().getTopNode());
173  0 if (tp.isColumnWise())
174    {
175  0 markColumnsFor(getAssociatedPanels(), leaves, Color.white, true);
176    }
177    else
178    {
179  0 for (AlignmentPanel ap : getAssociatedPanels())
180    {
181  0 SequenceGroup selected = ap.av.getSelectionGroup();
182  0 if (selected != null)
183    {
184    {
185  0 for (int i = 0; i < leaves.size(); i++)
186    {
187  0 SequenceI seq = (SequenceI) leaves.elementAt(i).element();
188  0 if (selected.contains(seq))
189    {
190  0 selected.addOrRemove(seq, false);
191    }
192    }
193  0 selected.recalcConservation();
194    }
195    }
196  0 ap.av.sendSelection();
197    }
198    }
199  0 PaintRefresher.Refresh(tp, av.getSequenceSetId());
200  0 repaint();
201    }
202   
203    /**
204    * DOCUMENT ME!
205    *
206    * @param sequence
207    * DOCUMENT ME!
208    */
 
209  0 toggle public void treeSelectionChanged(SequenceI sequence)
210    {
211  0 AlignmentPanel[] aps = getAssociatedPanels();
212   
213  0 for (int a = 0; a < aps.length; a++)
214    {
215  0 SequenceGroup selected = aps[a].av.getSelectionGroup();
216   
217  0 if (selected == null)
218    {
219  0 selected = new SequenceGroup();
220  0 aps[a].av.setSelectionGroup(selected);
221    }
222   
223  0 selected.setEndRes(aps[a].av.getAlignment().getWidth() - 1);
224  0 selected.addOrRemove(sequence, true);
225    }
226    }
227   
228    /**
229    * DOCUMENT ME!
230    *
231    * @param tree
232    * DOCUMENT ME!
233    */
 
234  14 toggle public void setTree(TreeModel tree)
235    {
236  14 this.tree = tree;
237  14 tree.findHeight(tree.getTopNode());
238   
239    // Now have to calculate longest name based on the leaves
240  14 Vector<BinaryNode> leaves = tree.findLeaves(tree.getTopNode());
241  14 boolean has_placeholders = false;
242  14 longestName = "";
243   
244  14 AlignmentAnnotation aa = tp.getAssocAnnotation();
245  14 ContactMatrixI cm = (aa != null) ? av.getContactMatrix(aa) : null;
246  14 if (cm != null && cm.hasCutHeight())
247    {
248  0 threshold = (float) cm.getCutHeight();
249    }
250   
251  184 for (int i = 0; i < leaves.size(); i++)
252    {
253  170 BinaryNode lf = leaves.elementAt(i);
254   
255  170 if (lf instanceof SequenceNode && ((SequenceNode) lf).isPlaceholder())
256    {
257  0 has_placeholders = true;
258    }
259   
260  170 if (longestName.length() < lf.getDisplayName().length())
261    {
262  22 longestName = TreeCanvas.PLACEHOLDER + lf.getDisplayName();
263    }
264  170 if (tp.isColumnWise() && cm != null)
265    {
266    // get color from group colours, if they are set for the matrix
267  0 try
268    {
269  0 Color col = cm.getGroupColorForPosition(parseColumnNode(lf));
270  0 setColor(lf, col.brighter());
271    } catch (NumberFormatException ex)
272    {
273    }
274  0 ;
275    }
276    }
277   
278  14 setMarkPlaceholders(has_placeholders);
279    }
280   
281    /**
282    * DOCUMENT ME!
283    *
284    * @param g
285    * DOCUMENT ME!
286    * @param node
287    * DOCUMENT ME!
288    * @param chunk
289    * DOCUMENT ME!
290    * @param wscale
291    * DOCUMENT ME!
292    * @param width
293    * DOCUMENT ME!
294    * @param offx
295    * DOCUMENT ME!
296    * @param offy
297    * DOCUMENT ME!
298    */
 
299  0 toggle public void drawNode(Graphics g, BinaryNode node, double chunk,
300    double wscale, int width, int offx, int offy)
301    {
302  0 if (node == null)
303    {
304  0 return;
305    }
306   
307  0 Vector<BinaryNode> leaves = tree.findLeaves(node);
308  0 gatherLabelsTo(node, leaves);
309   
310  0 if ((node.left() == null) && (node.right() == null))
311    {
312  0 Color originalColor, translucentColor = Color.WHITE;
313    // Drawing leaf node
314  0 double height = node.height;
315  0 double dist = node.dist;
316   
317  0 int xstart = (int) ((height - dist) * wscale) + offx;
318  0 int xend = (int) (height * wscale) + offx;
319   
320  0 int ypos = (int) (node.ycount * chunk) + offy;
321   
322  0 if (node.element() instanceof SequenceI)
323    {
324  0 SequenceI seq = (SequenceI) node.element();
325   
326  0 if (av.getSequenceColour(seq) == Color.white)
327    {
328  0 g.setColor(Color.black);
329    }
330    else
331    {
332  0 originalColor = av.getSequenceColour(seq);
333  0 translucentColor = new Color(originalColor.getRed(),
334    originalColor.getGreen(), originalColor.getBlue(), 128); // 128
335    // for
336    // 50%
337    // transparency
338  0 g.setColor(Color.black);
339    }
340    }
341    else
342    {
343  0 g.setColor(Color.black);
344    }
345   
346    // horizontal line
347  0 g.drawLine(xstart, ypos, xend, ypos);
348   
349  0 String nodeLabel = "";
350   
351  0 if (showDistances && (node.dist > 0))
352    {
353  0 nodeLabel = new Format("%g").form(node.dist);
354    }
355   
356  0 if (showBootstrap && node.bootstrap > -1)
357    {
358  0 if (showDistances)
359    {
360  0 nodeLabel = nodeLabel + " : ";
361    }
362   
363  0 nodeLabel = nodeLabel + String.valueOf(node.bootstrap);
364    }
365  0 if (node.hasLabel() && showStructureProviderColouredLines)
366    {
367   
368  0 drawLinesAndLabelsForSecondaryStructureProvider(g, node, xstart,
369    xend, ypos, nodeLabel);
370   
371  0 Rectangle labelBounds = new Rectangle(xstart, ypos, xend,
372    ypos + DASHED_LINE_Y_OFFSET);
373   
374    // Add the bounding box to the map for this node
375  0 labelBoundsMap.computeIfAbsent(node, k -> new ArrayList<>())
376    .add(labelBounds);
377   
378    }
379  0 else if (!nodeLabel.equals(""))
380    {
381  0 g.drawString(nodeLabel, xstart + 2, ypos - 2);
382    }
383   
384  0 String name = (markPlaceholders && ((node instanceof SequenceNode
385    && ((SequenceNode) node).isPlaceholder())))
386    ? (PLACEHOLDER + node.getDisplayName())
387    : node.getDisplayName();
388   
389  0 int charWidth = fm.stringWidth(name) + 3;
390  0 int charHeight = font.getSize();
391   
392  0 Rectangle rect = new Rectangle(xend + 10, ypos - charHeight / 2,
393    charWidth, charHeight);
394   
395  0 nameHash.put(node, rect);
396   
397  0 g.setColor(translucentColor);
398  0 g.fillRect(xend + 10, ypos - charHeight / 2, charWidth, charHeight);
399    // g.fillRect(xstart - 2, ypos - 10, (xend + 20 + charWidth) - xstart,
400    // 20);
401   
402  0 g.setColor(Color.black);
403   
404    // Colour selected leaves differently
405  0 boolean isSelected = false;
406  0 if (tp.isColumnWise())
407    {
408  0 isSelected = isColumnForNodeSelected(node);
409    }
410    else
411    {
412  0 SequenceGroup selected = av.getSelectionGroup();
413   
414  0 if ((selected != null)
415    && selected.getSequences(null).contains(node.element()))
416    {
417  0 isSelected = true;
418    }
419    }
420  0 if (isSelected)
421    {
422  0 g.setColor(Color.gray);
423   
424  0 g.fillRect(xend + 10, ypos - charHeight / 2, charWidth, charHeight);
425  0 g.setColor(Color.white);
426    }
427   
428  0 g.drawString(name, xend + 10, ypos + fm.getDescent());
429  0 g.setColor(Color.black);
430    }
431    else
432    {
433  0 drawNode(g, node.left(), chunk, wscale, width, offx, offy);
434  0 drawNode(g, node.right(), chunk, wscale, width, offx, offy);
435   
436  0 double height = node.height;
437  0 double dist = node.dist;
438   
439  0 int xstart = (int) ((height - dist) * wscale) + offx;
440  0 int xend = (int) (height * wscale) + offx;
441  0 int ypos = (int) (node.ycount * chunk) + offy;
442   
443    // g.setColor(node.color.darker());
444   
445  0 g.setColor(Color.black);
446   
447    // Draw horizontal line
448  0 g.drawLine(xstart, ypos, xend, ypos);
449  0 if (node == highlightNode)
450    {
451  0 g.fillRect(xend - 3, ypos - 3, 6, 6);
452    }
453    else
454    {
455  0 g.fillRect(xend - 2, ypos - 2, 4, 4);
456    }
457   
458  0 int ystart = (node.left() == null ? 0
459    : (int) (node.left().ycount * chunk)) + offy;
460  0 int yend = (node.right() == null ? 0
461    : (int) (node.right().ycount * chunk)) + offy;
462   
463  0 Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
464  0 nodeHash.put(node, pos);
465   
466  0 g.drawLine((int) (height * wscale) + offx, ystart,
467    (int) (height * wscale) + offx, yend);
468   
469  0 String nodeLabel = "";
470   
471  0 if (showDistances && (node.dist > 0))
472    {
473  0 nodeLabel = new Format("%g").form(node.dist);
474    }
475   
476  0 if (showBootstrap && node.bootstrap > -1)
477    {
478  0 if (showDistances)
479    {
480  0 nodeLabel = nodeLabel + " : ";
481    }
482   
483  0 nodeLabel = nodeLabel + String.valueOf(node.bootstrap);
484    }
485   
486    // Display secondary structure providers only if:
487    // ~ node has Label assigned (Secondary structure providers)
488    // ~ either label or coloured lines option is selected by the user
489  0 boolean labelHandled = false;
490  0 if (node.hasLabel())
491    {
492   
493  0 if (showStructureProviderColouredLines)
494    {
495  0 drawLinesAndLabelsForSecondaryStructureProvider(g, node, xstart,
496    xend, ypos, nodeLabel);
497   
498  0 Rectangle labelBounds = new Rectangle(xstart, ypos, xend,
499    ypos + DASHED_LINE_Y_OFFSET);
500   
501    // Add the bounding box to the map for this node (list allows multiple
502    // bounding boxes)
503  0 labelBoundsMap.computeIfAbsent(node, k -> new ArrayList<>())
504    .add(labelBounds);
505  0 labelHandled = true;
506   
507    }
508   
509  0 if (showStructureProviderLabels && node.count > 3)
510    {
511   
512  0 String label = node.getLabel();
513   
514  0 if (label.length() > labelLengthThreshold)
515    {
516   
517    // label = AlignmentUtils.reduceLabelLength(label);
518    }
519   
520  0 nodeLabel = label + " | " + nodeLabel;
521   
522    // Split the nodeLabel by "|"
523  0 String[] lines = nodeLabel.split("\\|");
524   
525    // Draw each string separately
526  0 String longestLabelString = "";
527  0 int i = 0;
528  0 for (i = 0; i < lines.length; i++)
529    {
530  0 g.drawString(lines[i].trim(), xstart + 2,
531    ypos - 2 - (i * fm.getHeight()));
532  0 if (longestLabelString.length() < lines[i].trim().length())
533    {
534  0 longestLabelString = lines[i].trim();
535    }
536    }
537   
538  0 int labelWidth = fm.stringWidth(longestLabelString);
539  0 int labelHeight = fm.getHeight() * (i - 1);
540   
541    // Bounding box of the string
542  0 int xLabelPos = xstart + 2;
543  0 int yLabelPos = ypos - 2;
544  0 Rectangle labelBounds = new Rectangle(xLabelPos,
545    yLabelPos - labelHeight, labelWidth, labelHeight);
546   
547    // Add the bounding box to the map for the node
548  0 labelBoundsMap.computeIfAbsent(node, k -> new ArrayList<>())
549    .add(labelBounds);
550   
551  0 labelHandled = true;
552    }
553    }
554   
555  0 if (!nodeLabel.equals("") && !labelHandled)
556    {
557  0 g.drawString(nodeLabel, xstart + 2, ypos - 2);
558    }
559    }
560    }
561   
 
562  0 toggle private void drawLinesAndLabelsForSecondaryStructureProvider(Graphics g,
563    BinaryNode node, int xstart, int xend, int ypos, String nodeLabel)
564    {
565   
566  0 Graphics2D g2d = (Graphics2D) g.create();
567   
568    // Set dash pattern
569    // float[] dashPattern = {2, 2};
570    // g2d.setStroke(new BasicStroke(
571    // 2.5f,
572    // BasicStroke.CAP_BUTT,
573    // BasicStroke.JOIN_ROUND,
574    // 0.0f,
575    // dashPattern,
576    // 0.0f));
577   
578  0 g2d.setStroke(new BasicStroke(4.0f, BasicStroke.CAP_BUTT,
579    BasicStroke.JOIN_ROUND));
580   
581  0 String label = node.getLabel();
582  0 String[] lines = label.split("\\|");
583   
584  0 int mid = lines.length / 2;
585   
586    // Draw lines for the first half
587  0 int firstHalfLinesCount = mid;
588   
589  0 drawSecondaryStructureProviderLinesSection(g2d, lines, 0, mid, xstart,
590    xend, ypos - DASHED_LINE_Y_OFFSET, true);
591  0 drawVerticalLineAndLabel(g, xstart, ypos, firstHalfLinesCount, true,
592    nodeLabel);
593   
594    // Draw lines for the second half
595  0 int secondHalfLinesCount = lines.length - mid;
596  0 drawSecondaryStructureProviderLinesSection(g2d, lines, mid,
597    lines.length, xstart, xend, ypos, false);
598  0 drawVerticalLineAndLabel(g, xstart, ypos, secondHalfLinesCount, false,
599    nodeLabel);
600   
601  0 g2d.dispose();
602    }
603   
 
604  0 toggle private void drawSecondaryStructureProviderLinesSection(Graphics2D g2d,
605    String[] lines, int start, int end, int xstart, int xend,
606    int baseY, boolean above)
607    {
608   
609  0 for (int i = start, j = 0; i < end; i++, j++)
610    {
611  0 int adjustedY = above ? baseY - ((i - start) * DASHED_LINE_Y_OFFSET)
612    : baseY + ((i - start) * DASHED_LINE_Y_OFFSET);
613    // Color providerColor =
614    // AlignmentUtils.getSecondaryStructureProviderColor(lines[i]);
615   
616  0 Map<String, Color> secondaryStructureProviderColorMap = tp
617    .getSecondaryStructureProviderColorMap();
618  0 Color providerColor = secondaryStructureProviderColorMap
619    .getOrDefault(lines[i].toUpperCase().trim(), Color.BLACK);
620  0 g2d.setColor(providerColor);
621  0 if (i == start && !above)
622    {
623  0 g2d.drawLine(xstart, adjustedY, xend - 3, adjustedY);
624    }
625    else
626    {
627  0 g2d.drawLine(xstart, adjustedY, xend, adjustedY);
628    }
629    }
630    }
631   
 
632  0 toggle private void drawVerticalLineAndLabel(Graphics g, int xstart, int ypos,
633    int linesCount, boolean above, String nodeLabel)
634    {
635   
636  0 int adjustment = (linesCount * DASHED_LINE_Y_OFFSET)
637    + (DASHED_LINE_Y_OFFSET / 3);
638  0 int adjustedY = ypos
639  0 + (above ? -adjustment : adjustment - DASHED_LINE_Y_OFFSET);
640   
641    // draw vertical line
642  0 g.drawLine(xstart, ypos, xstart, adjustedY);
643   
644    // draw label
645  0 if (above && !nodeLabel.equals(""))
646  0 g.drawString(nodeLabel, xstart + 2, adjustedY - 2);
647    }
648   
649    /**
650    * DOCUMENT ME!
651    *
652    * @param x
653    * DOCUMENT ME!
654    * @param y
655    * DOCUMENT ME!
656    *
657    * @return DOCUMENT ME!
658    */
 
659  0 toggle public Object findElement(int x, int y)
660    {
661  0 for (Entry<BinaryNode, Rectangle> entry : nameHash.entrySet())
662    {
663  0 Rectangle rect = entry.getValue();
664   
665  0 if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y)
666    && (y <= (rect.y + rect.height)))
667    {
668  0 return entry.getKey().element();
669    }
670    }
671   
672  0 for (Entry<BinaryNode, Rectangle> entry : nodeHash.entrySet())
673    {
674  0 Rectangle rect = entry.getValue();
675   
676  0 if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y)
677    && (y <= (rect.y + rect.height)))
678    {
679  0 return entry.getKey();
680    }
681    }
682   
683  0 return null;
684    }
685   
686    /**
687    * DOCUMENT ME!
688    *
689    * @param pickBox
690    * DOCUMENT ME!
691    */
 
692  0 toggle public void pickNodes(Rectangle pickBox)
693    {
694  0 int width = getWidth();
695  0 int height = getHeight();
696   
697  0 BinaryNode top = tree.getTopNode();
698   
699  0 double wscale = ((width * .8) - (offx * 2)) / tree.getMaxHeight();
700   
701  0 if (top.count == 0)
702    {
703  0 top.count = top.left().count + top.right().count;
704    }
705   
706  0 float chunk = (float) (height - (offy)) / top.count;
707   
708  0 pickNode(pickBox, top, chunk, wscale, width, offx, offy);
709    }
710   
711    /**
712    * DOCUMENT ME!
713    *
714    * @param pickBox
715    * DOCUMENT ME!
716    * @param node
717    * DOCUMENT ME!
718    * @param chunk
719    * DOCUMENT ME!
720    * @param wscale
721    * DOCUMENT ME!
722    * @param width
723    * DOCUMENT ME!
724    * @param offx
725    * DOCUMENT ME!
726    * @param offy
727    * DOCUMENT ME!
728    */
 
729  0 toggle public void pickNode(Rectangle pickBox, BinaryNode node, float chunk,
730    double wscale, int width, int offx, int offy)
731    {
732  0 if (node == null)
733    {
734  0 return;
735    }
736   
737  0 if ((node.left() == null) && (node.right() == null))
738    {
739  0 double height = node.height;
740    // double dist = node.dist;
741    // int xstart = (int) ((height - dist) * wscale) + offx;
742  0 int xend = (int) (height * wscale) + offx;
743   
744  0 int ypos = (int) (node.ycount * chunk) + offy;
745   
746  0 if (pickBox.contains(new Point(xend, ypos)))
747    {
748  0 if (node.element() instanceof SequenceI)
749    {
750  0 SequenceI seq = (SequenceI) node.element();
751  0 SequenceGroup sg = av.getSelectionGroup();
752   
753  0 if (sg != null)
754    {
755  0 sg.addOrRemove(seq, true);
756    }
757    }
758    }
759    }
760    else
761    {
762  0 pickNode(pickBox, node.left(), chunk, wscale, width, offx, offy);
763  0 pickNode(pickBox, node.right(), chunk, wscale, width, offx, offy);
764    }
765    }
766   
767    /**
768    * DOCUMENT ME!
769    *
770    * @param node
771    * DOCUMENT ME!
772    * @param c
773    * DOCUMENT ME!
774    */
 
775  0 toggle public void setColor(BinaryNode node, Color c)
776    {
777  0 if (node == null)
778    {
779  0 return;
780    }
781   
782  0 node.color = c;
783  0 if (node.element() instanceof SequenceI)
784    {
785  0 final SequenceI seq = (SequenceI) node.element();
786  0 AlignmentPanel[] aps = getAssociatedPanels();
787  0 if (aps != null)
788    {
789  0 for (int a = 0; a < aps.length; a++)
790    {
791  0 aps[a].av.setSequenceColour(seq, c);
792    }
793    }
794    }
795  0 setColor(node.left(), c);
796  0 setColor(node.right(), c);
797    }
798   
799    /**
800    * DOCUMENT ME!
801    */
 
802  0 toggle void startPrinting()
803    {
804  0 Thread thread = new Thread(this);
805  0 thread.start();
806    }
807   
808    // put printing in a thread to avoid painting problems
 
809  0 toggle @Override
810    public void run()
811    {
812  0 PrinterJob printJob = PrinterJob.getPrinterJob();
813  0 PageFormat defaultPage = printJob.defaultPage();
814  0 PageFormat pf = printJob.pageDialog(defaultPage);
815   
816  0 if (defaultPage == pf)
817    {
818    /*
819    * user cancelled
820    */
821  0 return;
822    }
823   
824  0 printJob.setPrintable(this, pf);
825   
826  0 if (printJob.printDialog())
827    {
828  0 try
829    {
830  0 printJob.print();
831    } catch (Exception PrintException)
832    {
833  0 PrintException.printStackTrace();
834    }
835    }
836    }
837   
838    /**
839    * DOCUMENT ME!
840    *
841    * @param pg
842    * DOCUMENT ME!
843    * @param pf
844    * DOCUMENT ME!
845    * @param pi
846    * DOCUMENT ME!
847    *
848    * @return DOCUMENT ME!
849    *
850    * @throws PrinterException
851    * DOCUMENT ME!
852    */
 
853  0 toggle @Override
854    public int print(Graphics pg, PageFormat pf, int pi)
855    throws PrinterException
856    {
857  0 pg.setFont(font);
858  0 pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
859   
860  0 int pwidth = (int) pf.getImageableWidth();
861  0 int pheight = (int) pf.getImageableHeight();
862   
863  0 int noPages = getHeight() / pheight;
864   
865  0 if (pi > noPages)
866    {
867  0 return Printable.NO_SUCH_PAGE;
868    }
869   
870  0 if (pwidth > getWidth())
871    {
872  0 pwidth = getWidth();
873    }
874   
875  0 if (fitToWindow)
876    {
877  0 if (pheight > getHeight())
878    {
879  0 pheight = getHeight();
880    }
881   
882  0 noPages = 0;
883    }
884    else
885    {
886  0 FontMetrics fm = pg.getFontMetrics(font);
887  0 int height = fm.getHeight() * nameHash.size();
888  0 pg.translate(0, -pi * pheight);
889  0 pg.setClip(0, pi * pheight, pwidth, (pi * pheight) + pheight);
890   
891    // translate number of pages,
892    // height is screen size as this is the
893    // non overlapping text size
894  0 pheight = height;
895    }
896   
897  0 draw(pg, pwidth, pheight);
898   
899  0 return Printable.PAGE_EXISTS;
900    }
901   
902    /**
903    * DOCUMENT ME!
904    *
905    * @param g
906    * DOCUMENT ME!
907    */
 
908  0 toggle @Override
909    public void paintComponent(Graphics g)
910    {
911  0 super.paintComponent(g);
912  0 g.setFont(font);
913   
914  0 if (tree == null)
915    {
916  0 g.drawString(
917    MessageManager.getString("label.calculating_tree") + "....",
918    20, getHeight() / 2);
919    }
920    else
921    {
922  0 fm = g.getFontMetrics(font);
923   
924  0 int nameCount = nameHash.size();
925  0 if (nameCount == 0)
926    {
927  0 repaint();
928    }
929   
930  0 if (fitToWindow || (!fitToWindow && (scrollPane
931    .getHeight() > ((fm.getHeight() * nameCount) + offy))))
932    {
933  0 draw(g, scrollPane.getWidth(), scrollPane.getHeight());
934  0 setPreferredSize(null);
935    }
936    else
937    {
938  0 setPreferredSize(new Dimension(scrollPane.getWidth(),
939    fm.getHeight() * nameCount));
940  0 draw(g, scrollPane.getWidth(), fm.getHeight() * nameCount);
941    }
942   
943  0 scrollPane.revalidate();
944    }
945    }
946   
947    /**
948    * DOCUMENT ME!
949    *
950    * @param fontSize
951    * DOCUMENT ME!
952    */
 
953  26 toggle @Override
954    public void setFont(Font font)
955    {
956  26 this.font = font;
957  26 repaint();
958    }
959   
960    /**
961    * DOCUMENT ME!
962    *
963    * @param g1
964    * DOCUMENT ME!
965    * @param width
966    * DOCUMENT ME!
967    * @param height
968    * DOCUMENT ME!
969    */
 
970  0 toggle public void draw(Graphics g1, int width, int height)
971    {
972  0 Graphics2D g2 = (Graphics2D) g1;
973  0 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
974    RenderingHints.VALUE_ANTIALIAS_ON);
975  0 g2.setColor(Color.white);
976  0 g2.fillRect(0, 0, width, height);
977  0 g2.setFont(font);
978   
979  0 if (longestName == null || tree == null)
980    {
981  0 g2.drawString("Calculating tree.", 20, 20);
982  0 return;
983    }
984  0 offy = font.getSize() + 10;
985   
986  0 fm = g2.getFontMetrics(font);
987   
988  0 labelLength = fm.stringWidth(longestName) + 20; // 20 allows for scrollbar
989   
990  0 double wscale = (width - labelLength - (offx * 2))
991    / tree.getMaxHeight();
992   
993  0 BinaryNode top = tree.getTopNode();
994   
995  0 if (top.count == 0)
996    {
997  0 top.count = top.left().count + top.right().count;
998    }
999   
1000  0 float chunk = (float) (height - (offy)) / top.count;
1001   
1002  0 drawNode(g2, tree.getTopNode(), chunk, wscale, width, offx, offy);
1003   
1004  0 if (threshold != 0)
1005    {
1006  0 if (av.getCurrentTree() == tree)
1007    {
1008  0 g2.setColor(Color.red);
1009    }
1010    else
1011    {
1012  0 g2.setColor(Color.gray);
1013    }
1014   
1015  0 int x = (int) ((threshold * (getWidth() - labelLength - (2 * offx)))
1016    + offx);
1017   
1018  0 g2.drawLine(x, 0, x, getHeight());
1019    }
1020    }
1021   
1022    /**
1023    * Empty method to satisfy the MouseListener interface
1024    *
1025    * @param e
1026    */
 
1027  0 toggle @Override
1028    public void mouseReleased(MouseEvent e)
1029    {
1030    /*
1031    * isPopupTrigger is set on mouseReleased on Windows
1032    */
1033  0 if (e.isPopupTrigger())
1034    {
1035  0 chooseSubtreeColour();
1036  0 e.consume(); // prevent mouseClicked happening
1037    }
1038    }
1039   
1040    /**
1041    * Empty method to satisfy the MouseListener interface
1042    *
1043    * @param e
1044    */
 
1045  0 toggle @Override
1046    public void mouseEntered(MouseEvent e)
1047    {
1048    }
1049   
1050    /**
1051    * Empty method to satisfy the MouseListener interface
1052    *
1053    * @param e
1054    */
 
1055  0 toggle @Override
1056    public void mouseExited(MouseEvent e)
1057    {
1058    }
1059   
1060    /**
1061    * Handles a mouse click on a tree node (clicks elsewhere are handled in
1062    * mousePressed). Click selects the sub-tree, double-click swaps leaf nodes
1063    * order, right-click opens a dialogue to choose colour for the sub-tree.
1064    *
1065    * @param e
1066    */
 
1067  0 toggle @Override
1068    public void mouseClicked(MouseEvent evt)
1069    {
1070  0 if (highlightNode == null)
1071    {
1072  0 return;
1073    }
1074   
1075  0 if (evt.getClickCount() > 1)
1076    {
1077  0 tree.swapNodes(highlightNode);
1078  0 tree.reCount(tree.getTopNode());
1079  0 tree.findHeight(tree.getTopNode());
1080    }
1081    else
1082    {
1083  0 Vector<BinaryNode> leaves = tree.findLeaves(highlightNode);
1084  0 if (tp.isColumnWise())
1085    {
1086  0 markColumnsFor(getAssociatedPanels(), leaves, Color.red, false);
1087    }
1088    else
1089    {
1090  0 for (int i = 0; i < leaves.size(); i++)
1091    {
1092  0 SequenceI seq = (SequenceI) leaves.elementAt(i).element();
1093  0 treeSelectionChanged(seq);
1094    }
1095    }
1096  0 av.sendSelection();
1097    }
1098   
1099  0 PaintRefresher.Refresh(tp, av.getSequenceSetId());
1100  0 repaint();
1101    }
1102   
1103    /**
1104    * Offer the user the option to choose a colour for the highlighted node and
1105    * its children; this colour is also applied to the corresponding sequence ids
1106    * in the alignment
1107    */
 
1108  0 toggle void chooseSubtreeColour()
1109    {
1110  0 String ttl = MessageManager.getString("label.select_subtree_colour");
1111  0 ColourChooserListener listener = new ColourChooserListener()
1112    {
 
1113  0 toggle @Override
1114    public void colourSelected(Color c)
1115    {
1116  0 setColor(highlightNode, c);
1117  0 PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
1118  0 repaint();
1119    }
1120    };
1121  0 JalviewColourChooser.showColourChooser(this, ttl, highlightNode.color,
1122    listener);
1123    }
1124   
 
1125  0 toggle @Override
1126    public void mouseMoved(MouseEvent evt)
1127    {
1128  0 av.setCurrentTree(tree);
1129   
1130  0 Object ob = findElement(evt.getX(), evt.getY());
1131   
1132    // Get mouse coordinates
1133  0 int mouseX = evt.getX();
1134  0 int mouseY = evt.getY();
1135   
1136  0 if (ob instanceof BinaryNode)
1137    {
1138  0 highlightNode = (BinaryNode) ob;
1139  0 this.setToolTipText(
1140    "<html>" + MessageManager.getString("label.highlightnode"));
1141  0 repaint();
1142   
1143    }
1144    else
1145    {
1146   
1147  0 if (highlightNode != null)
1148    {
1149  0 highlightNode = null;
1150  0 setToolTipText(null);
1151  0 repaint();
1152    }
1153   
1154    // Iterate through the map of label bounding boxes
1155  0 for (Map.Entry<BinaryNode, List<Rectangle>> entry : labelBoundsMap
1156    .entrySet())
1157    {
1158  0 BinaryNode node = entry.getKey();
1159  0 List<Rectangle> boundsList = entry.getValue();
1160   
1161    // Check each bounding box for this node
1162  0 for (Rectangle labelBounds : boundsList)
1163    {
1164  0 if (labelBounds.contains(mouseX, mouseY))
1165    {
1166    // Show tooltip for this node's label
1167  0 String nodeLabel = node.getDisplayName();
1168  0 this.setToolTipText(nodeLabel);
1169  0 repaint();
1170  0 return; // Exit once we find a matching label
1171    }
1172    }
1173    }
1174    // Clear tooltip if no label is hovered
1175  0 setToolTipText(null);
1176  0 repaint();
1177   
1178    }
1179    }
1180   
 
1181  0 toggle @Override
1182    public void mouseDragged(MouseEvent ect)
1183    {
1184    }
1185   
1186    /**
1187    * Handles a mouse press on a sequence name or the tree background canvas
1188    * (click on a node is handled in mouseClicked). The action is to create
1189    * groups by partitioning the tree at the mouse position. Colours for the
1190    * groups (and sequence names) are generated randomly.
1191    *
1192    * @param e
1193    */
 
1194  0 toggle @Override
1195    public void mousePressed(MouseEvent e)
1196    {
1197  0 av.setCurrentTree(tree);
1198   
1199    /*
1200    * isPopupTrigger is set for mousePressed (Mac)
1201    * or mouseReleased (Windows)
1202    */
1203  0 if (e.isPopupTrigger())
1204    {
1205  0 if (highlightNode != null)
1206    {
1207  0 chooseSubtreeColour();
1208    }
1209  0 return;
1210    }
1211   
1212    /*
1213    * defer right-click handling on Windows to
1214    * mouseClicked; note isRightMouseButton
1215    * also matches Cmd-click on Mac which should do
1216    * nothing here
1217    */
1218  0 if (SwingUtilities.isRightMouseButton(e))
1219    {
1220  0 return;
1221    }
1222   
1223  0 int x = e.getX();
1224  0 int y = e.getY();
1225   
1226  0 Object ob = findElement(x, y);
1227   
1228  0 if (ob instanceof SequenceI)
1229    {
1230  0 treeSelectionChanged((Sequence) ob);
1231  0 PaintRefresher.Refresh(tp,
1232    getAssociatedPanel().av.getSequenceSetId());
1233  0 repaint();
1234  0 av.sendSelection();
1235  0 return;
1236    }
1237  0 else if (!(ob instanceof BinaryNode))
1238    {
1239    // Find threshold
1240  0 if (tree.getMaxHeight() != 0)
1241    {
1242  0 threshold = (float) (x - offx)
1243    / (float) (getWidth() - labelLength - (2 * offx));
1244   
1245  0 List<BinaryNode> groups = tree.groupNodes(threshold);
1246  0 setColor(tree.getTopNode(), Color.black);
1247   
1248  0 AlignmentPanel[] aps = getAssociatedPanels();
1249   
1250    // TODO push calls below into a single AlignViewportI method?
1251    // see also AlignViewController.deleteGroups
1252  0 for (int a = 0; a < aps.length; a++)
1253    {
1254  0 aps[a].av.setSelectionGroup(null);
1255  0 aps[a].av.getAlignment().deleteAllGroups();
1256  0 aps[a].av.clearSequenceColours();
1257  0 if (aps[a].av.getCodingComplement() != null)
1258    {
1259  0 aps[a].av.getCodingComplement().setSelectionGroup(null);
1260  0 aps[a].av.getCodingComplement().getAlignment()
1261    .deleteAllGroups();
1262  0 aps[a].av.getCodingComplement().clearSequenceColours();
1263    }
1264  0 aps[a].av.setUpdateStructures(true);
1265    }
1266  0 colourGroups(groups);
1267   
1268    /*
1269    * clear partition (don't show vertical line) if
1270    * it is to the right of all nodes
1271    */
1272  0 if (groups.isEmpty())
1273    {
1274  0 threshold = 0f;
1275    }
1276    }
1277  0 Console.log.debug("Tree cut threshold set at:" + threshold);
1278  0 PaintRefresher.Refresh(tp,
1279    getAssociatedPanel().av.getSequenceSetId());
1280  0 repaint();
1281    }
1282   
1283    }
1284   
 
1285  0 toggle void colourGroups(List<BinaryNode> groups)
1286    {
1287  0 AlignmentPanel[] aps = getAssociatedPanels();
1288  0 List<BitSet> colGroups = new ArrayList<>();
1289  0 Map<BitSet, Color> colors = new HashMap();
1290  0 for (int i = 0; i < groups.size(); i++)
1291    {
1292  0 String colId = groups.get(i).getBranchIdentifier();
1293  0 Color col = ColorUtils.getDefaultColourFromName(colId);
1294   
1295  0 setColor(groups.get(i), col.brighter());
1296   
1297  0 Vector<BinaryNode> l = tree.findLeaves(groups.get(i));
1298    // gatherLabelsTo(groups.get(i), l);
1299  0 if (!tp.isColumnWise())
1300    {
1301  0 createSeqGroupFor(aps, l, col, groups.get(i).getLabel());
1302    }
1303    else
1304    {
1305  0 BitSet gp = createColumnGroupFor(l, col);
1306   
1307  0 colGroups.add(gp);
1308  0 colors.put(gp, col);
1309    }
1310    }
1311  0 if (tp.isColumnWise())
1312    {
1313  0 AlignmentAnnotation aa = tp.getAssocAnnotation();
1314  0 if (aa != null)
1315    {
1316  0 ContactMatrixI cm = av.getContactMatrix(aa);
1317  0 if (cm != null)
1318    {
1319  0 cm.updateGroups(colGroups);
1320  0 for (BitSet gp : colors.keySet())
1321    {
1322  0 cm.setColorForGroup(gp, colors.get(gp));
1323    }
1324    }
1325  0 cm.transferGroupColorsTo(aa);
1326    }
1327    }
1328   
1329    // notify the panel(s) to redo any group specific stuff
1330    // also updates structure views if necessary
1331  0 for (int a = 0; a < aps.length; a++)
1332    {
1333  0 aps[a].updateAnnotation();
1334  0 final AlignViewportI codingComplement = aps[a].av
1335    .getCodingComplement();
1336  0 if (codingComplement != null)
1337    {
1338  0 ((AlignViewport) codingComplement).getAlignPanel()
1339    .updateAnnotation();
1340    }
1341    }
1342    }
1343   
 
1344  0 toggle private void gatherLabelsTo(BinaryNode binaryNode, Vector<BinaryNode> l)
1345    {
1346  0 LinkedHashSet<String> labelsForNode = new LinkedHashSet<String>();
1347  0 for (BinaryNode leaf : l)
1348    {
1349  0 if (leaf.hasLabel())
1350    {
1351  0 labelsForNode.add(leaf.getLabel());
1352    }
1353    }
1354  0 StringBuilder sb = new StringBuilder();
1355  0 boolean first = true;
1356  0 for (String label : labelsForNode)
1357    {
1358  0 if (!first)
1359    {
1360  0 sb.append(" | ");
1361    }
1362  0 first = false;
1363    // if(labelsForNode.size()>1) {
1364    // String providerAbbreviation = AlignmentUtils.getProviderKey(label);
1365    // sb.append(providerAbbreviation);
1366    // }
1367  0 sb.append(label);
1368   
1369    }
1370  0 binaryNode.setLabel(sb.toString());
1371    }
1372   
 
1373  0 toggle private int parseColumnNode(BinaryNode bn) throws NumberFormatException
1374    {
1375  0 return Integer.parseInt(
1376    bn.getName().substring(bn.getName().indexOf("c") + 1));
1377    }
1378   
 
1379  0 toggle private boolean isColumnForNodeSelected(BinaryNode bn)
1380    {
1381  0 SequenceI rseq = tp.assocAnnotation.sequenceRef;
1382  0 int colm = -1;
1383  0 try
1384    {
1385  0 colm = parseColumnNode(bn);
1386    } catch (Exception e)
1387    {
1388  0 return false;
1389    }
1390  0 if (av == null || av.getAlignment() == null)
1391    {
1392    // alignment is closed
1393  0 return false;
1394    }
1395  0 ColumnSelection cs = av.getColumnSelection();
1396  0 HiddenColumns hc = av.getAlignment().getHiddenColumns();
1397  0 AlignmentAnnotation aa = tp.getAssocAnnotation();
1398  0 int offp = -1;
1399  0 if (aa != null)
1400    {
1401  0 ContactMatrixI cm = av.getContactMatrix(aa);
1402    // generally, we assume cm has 1:1 mapping to annotation row - probably
1403    // wrong
1404    // but.. if
1405  0 if (cm instanceof MappableContactMatrixI)
1406    {
1407  0 int[] pos;
1408    // use the mappable's mapping - always the case for PAE Matrices so good
1409    // for 2.11.3
1410  0 MappableContactMatrixI mcm = (MappableContactMatrixI) cm;
1411  0 pos = mcm.getMappedPositionsFor(rseq, colm + 1);
1412    // finally, look up the position of the column
1413  0 if (pos != null)
1414    {
1415  0 offp = rseq.findIndex(pos[0]);
1416    }
1417    }
1418    else
1419    {
1420  0 offp = colm;
1421    }
1422    }
1423  0 if (offp <= 0)
1424    {
1425  0 return false;
1426    }
1427   
1428  0 offp -= 2;
1429  0 if (!av.hasHiddenColumns())
1430    {
1431  0 return cs.contains(offp);
1432    }
1433  0 if (hc.isVisible(offp))
1434    {
1435  0 return cs.contains(offp);
1436    // return cs.contains(hc.absoluteToVisibleColumn(offp));
1437    }
1438  0 return false;
1439    }
1440   
 
1441  0 toggle private BitSet createColumnGroupFor(Vector<BinaryNode> l, Color col)
1442    {
1443  0 BitSet gp = new BitSet();
1444  0 for (BinaryNode bn : l)
1445    {
1446  0 int colm = -1;
1447  0 if (bn.element() != null && bn.element() instanceof Integer)
1448    {
1449  0 colm = (Integer) bn.element();
1450    }
1451    else
1452    {
1453    // parse out from nodename
1454  0 try
1455    {
1456  0 colm = parseColumnNode(bn);
1457    } catch (Exception e)
1458    {
1459  0 continue;
1460    }
1461    }
1462  0 gp.set(colm);
1463    }
1464  0 return gp;
1465    }
1466   
 
1467  0 toggle private void markColumnsFor(AlignmentPanel[] aps, Vector<BinaryNode> l,
1468    Color col, boolean clearSelected)
1469    {
1470  0 SequenceI rseq = tp.assocAnnotation.sequenceRef;
1471  0 if (av == null || av.getAlignment() == null)
1472    {
1473    // alignment is closed
1474  0 return;
1475    }
1476   
1477    // TODO - sort indices for faster lookup
1478  0 ColumnSelection cs = av.getColumnSelection();
1479  0 HiddenColumns hc = av.getAlignment().getHiddenColumns();
1480  0 ContactMatrixI cm = av.getContactMatrix(tp.assocAnnotation);
1481  0 MappableContactMatrixI mcm = null;
1482  0 int offp;
1483  0 if (cm instanceof MappableContactMatrixI)
1484    {
1485  0 mcm = (MappableContactMatrixI) cm;
1486    }
1487  0 for (BinaryNode bn : l)
1488    {
1489  0 int colm = -1;
1490  0 try
1491    {
1492  0 colm = Integer.parseInt(
1493    bn.getName().substring(bn.getName().indexOf("c") + 1));
1494    } catch (Exception e)
1495    {
1496  0 continue;
1497    }
1498  0 if (mcm != null)
1499    {
1500  0 int[] seqpos = mcm.getMappedPositionsFor(rseq, colm);
1501  0 if (seqpos == null)
1502    {
1503    // no mapping for this column.
1504  0 continue;
1505    }
1506    // TODO: handle ranges...
1507  0 offp = rseq.findIndex(seqpos[0]) - 1;
1508    }
1509    else
1510    {
1511  0 offp = (rseq != null) ? rseq.findIndex(rseq.getStart() + colm)
1512    : colm;
1513    }
1514  0 if (!av.hasHiddenColumns() || hc.isVisible(offp))
1515    {
1516  0 if (clearSelected || cs.contains(offp))
1517    {
1518  0 cs.removeElement(offp);
1519    }
1520    else
1521    {
1522  0 cs.addElement(offp);
1523    }
1524    }
1525    }
1526  0 PaintRefresher.Refresh(tp, av.getSequenceSetId());
1527    }
1528   
 
1529  0 toggle public void createSeqGroupFor(AlignmentPanel[] aps, Vector<BinaryNode> l,
1530    Color col, String label)
1531    {
1532   
1533  0 Vector<SequenceI> sequences = new Vector<>();
1534   
1535  0 for (int j = 0; j < l.size(); j++)
1536    {
1537  0 SequenceI s1 = (SequenceI) l.elementAt(j).element();
1538   
1539  0 if (!sequences.contains(s1))
1540    {
1541  0 sequences.addElement(s1);
1542    }
1543    }
1544   
1545  0 ColourSchemeI cs = null;
1546  0 SequenceGroup _sg = new SequenceGroup(sequences, null, cs, true, true,
1547    false, 0, av.getAlignment().getWidth() - 1);
1548   
1549    // Check if the label is not null and not empty
1550  0 if (label != null && !label.isEmpty())
1551    {
1552    // Retrieve the existing groups from the alignment
1553  0 List<SequenceGroup> existingGroups = av.getAlignment().getGroups();
1554   
1555    // Reduce the label length
1556  0 label = AlignmentUtils.reduceLabelLength(label);
1557   
1558    // Create group name based on the label
1559  0 String newGroupName = "JTreeGroup:" + label;
1560   
1561    // Counter for groups with the same name
1562  0 int noOfGroupsWithSameName = 0;
1563   
1564    // Iterate through the existing groups to check for naming conflicts
1565  0 for (SequenceGroup sg : existingGroups)
1566    {
1567  0 if (sg.getName().equals(newGroupName)
1568    || sg.getName().matches(newGroupName + " \\d+"))
1569    {
1570   
1571  0 noOfGroupsWithSameName++;
1572   
1573    // If a group name matches exactly, update the group's name by
1574    // appending the count
1575  0 if (sg.getName().equals(newGroupName))
1576    {
1577  0 String updatedGroupName = sg.getName() + " "
1578    + noOfGroupsWithSameName;
1579  0 sg.setName(updatedGroupName);
1580    }
1581    }
1582    }
1583   
1584    // If count > 0, increment the count and append it to the new group name
1585  0 if (noOfGroupsWithSameName > 0)
1586    {
1587  0 noOfGroupsWithSameName++;
1588  0 newGroupName = newGroupName + " " + noOfGroupsWithSameName;
1589    }
1590   
1591  0 _sg.setName(newGroupName);
1592    }
1593    else
1594    {
1595  0 _sg.setName("JTreeGroup:" + _sg.hashCode());
1596    }
1597  0 _sg.setIdColour(col);
1598   
1599  0 for (int a = 0; a < aps.length; a++)
1600    {
1601  0 SequenceGroup sg = new SequenceGroup(_sg);
1602  0 AlignViewport viewport = aps[a].av;
1603   
1604    // Propagate group colours in each view
1605  0 if (viewport.getGlobalColourScheme() != null)
1606    {
1607  0 cs = viewport.getGlobalColourScheme().getInstance(viewport, sg);
1608  0 sg.setColourScheme(cs);
1609  0 sg.getGroupColourScheme().setThreshold(
1610    viewport.getResidueShading().getThreshold(),
1611    viewport.isIgnoreGapsConsensus());
1612   
1613  0 if (viewport.getResidueShading().conservationApplied())
1614    {
1615  0 Conservation c = new Conservation("Group", sg.getSequences(null),
1616    sg.getStartRes(), sg.getEndRes());
1617  0 c.calculate();
1618  0 c.verdict(false, viewport.getConsPercGaps());
1619  0 sg.cs.setConservation(c);
1620    }
1621  0 if (viewport.getResidueShading()
1622    .isConsensusSecondaryStructureColouring())
1623    {
1624  0 sg.getGroupColourScheme().setConsensusSecondaryStructureThreshold(
1625    viewport.getResidueShading().getThreshold());
1626  0 sg.getGroupColourScheme()
1627    .setConsensusSecondaryStructureColouring(true);
1628    }
1629    }
1630    // indicate that associated structure views will need an update
1631  0 viewport.setUpdateStructures(true);
1632    // propagate structure view update and sequence group to complement view
1633  0 viewport.addSequenceGroup(sg);
1634    }
1635    }
1636   
1637    /**
1638    * DOCUMENT ME!
1639    *
1640    * @param state
1641    * DOCUMENT ME!
1642    */
 
1643  40 toggle public void setShowDistances(boolean state)
1644    {
1645  40 this.showDistances = state;
1646  40 repaint();
1647    }
1648   
 
1649  0 toggle public void hideStructureProviders(boolean state)
1650    {
1651  0 if (state)
1652    {
1653  0 this.showStructureProviderColouredLines = false;
1654  0 this.showStructureProviderLabels = false;
1655  0 repaint();
1656    }
1657    }
1658   
 
1659  0 toggle public void setShowStructureProviderColouredLines(boolean state)
1660    {
1661  0 this.showStructureProviderColouredLines = state;
1662  0 if (state)
1663    {
1664  0 this.showStructureProviderLabels = false;
1665    }
1666  0 repaint();
1667    }
1668   
 
1669  0 toggle public void setShowStructureProviderLabels(boolean state)
1670    {
1671  0 this.showStructureProviderLabels = state;
1672  0 if (state)
1673    {
1674  0 this.showStructureProviderColouredLines = false;
1675    }
1676  0 repaint();
1677    }
1678   
1679    /**
1680    * DOCUMENT ME!
1681    *
1682    * @param state
1683    * DOCUMENT ME!
1684    */
 
1685  26 toggle public void setShowBootstrap(boolean state)
1686    {
1687  26 this.showBootstrap = state;
1688  26 repaint();
1689    }
1690   
1691    /**
1692    * DOCUMENT ME!
1693    *
1694    * @param state
1695    * DOCUMENT ME!
1696    */
 
1697  26 toggle public void setMarkPlaceholders(boolean state)
1698    {
1699  26 this.markPlaceholders = state;
1700  26 repaint();
1701    }
1702   
 
1703  0 toggle AlignmentPanel[] getAssociatedPanels()
1704    {
1705  0 if (applyToAllViews)
1706    {
1707  0 return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());
1708    }
1709    else
1710    {
1711  0 return new AlignmentPanel[] { getAssociatedPanel() };
1712    }
1713    }
1714   
 
1715  32 toggle public AlignmentPanel getAssociatedPanel()
1716    {
1717  32 return ap;
1718    }
1719   
 
1720  14 toggle public void setAssociatedPanel(AlignmentPanel ap)
1721    {
1722  14 this.ap = ap;
1723    }
1724   
 
1725  4 toggle public AlignViewport getViewport()
1726    {
1727  4 return av;
1728    }
1729   
 
1730  0 toggle public void setViewport(AlignViewport av)
1731    {
1732  0 this.av = av;
1733    }
1734   
 
1735  4 toggle public float getThreshold()
1736    {
1737  4 return threshold;
1738    }
1739   
 
1740  12 toggle public void setThreshold(float threshold)
1741    {
1742  12 this.threshold = threshold;
1743    }
1744   
 
1745  4 toggle public boolean isApplyToAllViews()
1746    {
1747  4 return this.applyToAllViews;
1748    }
1749   
 
1750  12 toggle public void setApplyToAllViews(boolean applyToAllViews)
1751    {
1752  12 this.applyToAllViews = applyToAllViews;
1753    }
1754    }