Clover icon

Coverage Report

  1. Project Clover database Thu Nov 7 2024 13:01:17 GMT
  2. Package jalview.gui

File TreeCanvas.java

 

Coverage histogram

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

Code metrics

200
434
42
1
1,405
972
173
0.4
10.33
42
4.12

Classes

Class Line # Actions
TreeCanvas 80 434 173
0.0872781058.7%
 

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