Clover icon

Coverage Report

  1. Project Clover database Fri Dec 6 2024 13:47:14 GMT
  2. Package jalview.gui

File TreeCanvas.java

 

Coverage histogram

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

Code metrics

270
578
49
1
1,805
1,232
229
0.4
11.8
49
4.67

Classes

Class Line # Actions
TreeCanvas 84 578 229
0.0657748066.6%
 

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