Clover icon

Coverage Report

  1. Project Clover database Mon Nov 18 2024 09:38:20 GMT
  2. Package jalview.gui

File TreeCanvas.java

 

Coverage histogram

../../img/srcFileCovDistChart0.png
54% of files have more coverage

Code metrics

246
550
49
1
1,724
1,175
210
0.38
11.22
49
4.29

Classes

Class Line # Actions
TreeCanvas 87 550 210
0.00%
 

Contributing tests

No tests hitting this source file were found.

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