Clover icon

Coverage Report

  1. Project Clover database Fri Nov 1 2024 11:46:37 GMT
  2. Package jalview.gui

File TreeCanvas.java

 

Coverage histogram

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

Code metrics

200
433
42
1
1,410
977
173
0.4
10.31
42
4.12

Classes

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