Clover icon

jalviewX

  1. Project Clover database Wed Oct 31 2018 15:13:58 GMT
  2. Package jalview.gui

File TreeCanvas.java

 

Coverage histogram

../../img/srcFileCovDistChart3.png
47% of files have more coverage

Code metrics

154
324
28
1
1,136
727
125
0.39
11.57
28
4.46

Classes

Class Line # Actions
TreeCanvas 72 324 125 363
0.282608728.3%
 

Contributing tests

This file is covered by 14 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 jalview.analysis.Conservation;
24    import jalview.analysis.TreeModel;
25    import jalview.api.AlignViewportI;
26    import jalview.datamodel.Sequence;
27    import jalview.datamodel.SequenceGroup;
28    import jalview.datamodel.SequenceI;
29    import jalview.datamodel.SequenceNode;
30    import jalview.gui.JalviewColourChooser.ColourChooserListener;
31    import jalview.schemes.ColourSchemeI;
32    import jalview.schemes.ColourSchemeProperty;
33    import jalview.schemes.UserColourScheme;
34    import jalview.structure.SelectionSource;
35    import jalview.util.Format;
36    import jalview.util.MappingUtils;
37    import jalview.util.MessageManager;
38   
39    import java.awt.Color;
40    import java.awt.Dimension;
41    import java.awt.Font;
42    import java.awt.FontMetrics;
43    import java.awt.Graphics;
44    import java.awt.Graphics2D;
45    import java.awt.Point;
46    import java.awt.Rectangle;
47    import java.awt.RenderingHints;
48    import java.awt.event.MouseEvent;
49    import java.awt.event.MouseListener;
50    import java.awt.event.MouseMotionListener;
51    import java.awt.print.PageFormat;
52    import java.awt.print.Printable;
53    import java.awt.print.PrinterException;
54    import java.awt.print.PrinterJob;
55    import java.util.Enumeration;
56    import java.util.Hashtable;
57    import java.util.List;
58    import java.util.Vector;
59   
60    import javax.swing.JColorChooser;
61    import javax.swing.JPanel;
62    import javax.swing.JScrollPane;
63    import javax.swing.SwingUtilities;
64    import javax.swing.ToolTipManager;
65   
66    /**
67    * DOCUMENT ME!
68    *
69    * @author $author$
70    * @version $Revision$
71    */
 
72    public class TreeCanvas extends JPanel implements MouseListener, Runnable,
73    Printable, MouseMotionListener, SelectionSource
74    {
75    /** DOCUMENT ME!! */
76    public static final String PLACEHOLDER = " * ";
77   
78    TreeModel tree;
79   
80    JScrollPane scrollPane;
81   
82    TreePanel tp;
83   
84    AlignViewport av;
85   
86    AlignmentPanel ap;
87   
88    Font font;
89   
90    FontMetrics fm;
91   
92    boolean fitToWindow = true;
93   
94    boolean showDistances = false;
95   
96    boolean showBootstrap = false;
97   
98    boolean markPlaceholders = false;
99   
100    int offx = 20;
101   
102    int offy;
103   
104    float threshold;
105   
106    String longestName;
107   
108    int labelLength = -1;
109   
110    Hashtable nameHash = new Hashtable();
111   
112    Hashtable nodeHash = new Hashtable();
113   
114    SequenceNode highlightNode;
115   
116    boolean applyToAllViews = false;
117   
118    /**
119    * Creates a new TreeCanvas object.
120    *
121    * @param av
122    * DOCUMENT ME!
123    * @param tree
124    * DOCUMENT ME!
125    * @param scroller
126    * DOCUMENT ME!
127    * @param label
128    * DOCUMENT ME!
129    */
 
130  12 toggle public TreeCanvas(TreePanel tp, AlignmentPanel ap, JScrollPane scroller)
131    {
132  12 this.tp = tp;
133  12 this.av = ap.av;
134  12 this.ap = ap;
135  12 font = av.getFont();
136  12 scrollPane = scroller;
137  12 addMouseListener(this);
138  12 addMouseMotionListener(this);
139  12 ToolTipManager.sharedInstance().registerComponent(this);
140    }
141   
142    /**
143    * DOCUMENT ME!
144    *
145    * @param sequence
146    * DOCUMENT ME!
147    */
 
148  0 toggle public void treeSelectionChanged(SequenceI sequence)
149    {
150  0 AlignmentPanel[] aps = getAssociatedPanels();
151   
152  0 for (int a = 0; a < aps.length; a++)
153    {
154  0 SequenceGroup selected = aps[a].av.getSelectionGroup();
155   
156  0 if (selected == null)
157    {
158  0 selected = new SequenceGroup();
159  0 aps[a].av.setSelectionGroup(selected);
160    }
161   
162  0 selected.setEndRes(aps[a].av.getAlignment().getWidth() - 1);
163  0 selected.addOrRemove(sequence, true);
164    }
165    }
166   
167    /**
168    * DOCUMENT ME!
169    *
170    * @param tree
171    * DOCUMENT ME!
172    */
 
173  12 toggle public void setTree(TreeModel tree)
174    {
175  12 this.tree = tree;
176  12 tree.findHeight(tree.getTopNode());
177   
178    // Now have to calculate longest name based on the leaves
179  12 Vector<SequenceNode> leaves = tree.findLeaves(tree.getTopNode());
180  12 boolean has_placeholders = false;
181  12 longestName = "";
182   
183  132 for (int i = 0; i < leaves.size(); i++)
184    {
185  120 SequenceNode lf = leaves.elementAt(i);
186   
187  120 if (lf.isPlaceholder())
188    {
189  0 has_placeholders = true;
190    }
191   
192  120 if (longestName.length() < ((Sequence) lf.element()).getName()
193    .length())
194    {
195  24 longestName = TreeCanvas.PLACEHOLDER
196    + ((Sequence) lf.element()).getName();
197    }
198    }
199   
200  12 setMarkPlaceholders(has_placeholders);
201    }
202   
203    /**
204    * DOCUMENT ME!
205    *
206    * @param g
207    * DOCUMENT ME!
208    * @param node
209    * DOCUMENT ME!
210    * @param chunk
211    * DOCUMENT ME!
212    * @param wscale
213    * DOCUMENT ME!
214    * @param width
215    * DOCUMENT ME!
216    * @param offx
217    * DOCUMENT ME!
218    * @param offy
219    * DOCUMENT ME!
220    */
 
221  6802 toggle public void drawNode(Graphics g, SequenceNode node, float chunk,
222    double wscale, int width, int offx, int offy)
223    {
224  6802 if (node == null)
225    {
226  0 return;
227    }
228   
229  6802 if ((node.left() == null) && (node.right() == null))
230    {
231    // Drawing leaf node
232  3580 double height = node.height;
233  3580 double dist = node.dist;
234   
235  3580 int xstart = (int) ((height - dist) * wscale) + offx;
236  3580 int xend = (int) (height * wscale) + offx;
237   
238  3580 int ypos = (int) (node.ycount * chunk) + offy;
239   
240  3580 if (node.element() instanceof SequenceI)
241    {
242  3580 SequenceI seq = (SequenceI) node.element();
243   
244  3580 if (av.getSequenceColour(seq) == Color.white)
245    {
246  0 g.setColor(Color.black);
247    }
248    else
249    {
250  3580 g.setColor(av.getSequenceColour(seq).darker());
251    }
252    }
253    else
254    {
255  0 g.setColor(Color.black);
256    }
257   
258    // Draw horizontal line
259  3580 g.drawLine(xstart, ypos, xend, ypos);
260   
261  3580 String nodeLabel = "";
262   
263  3580 if (showDistances && (node.dist > 0))
264    {
265  0 nodeLabel = new Format("%-.2f").form(node.dist);
266    }
267   
268  3580 if (showBootstrap && node.bootstrap > -1)
269    {
270  0 if (showDistances)
271    {
272  0 nodeLabel = nodeLabel + " : ";
273    }
274   
275  0 nodeLabel = nodeLabel + String.valueOf(node.bootstrap);
276    }
277   
278  3580 if (!nodeLabel.equals(""))
279    {
280  0 g.drawString(nodeLabel, xstart + 2, ypos - 2);
281    }
282   
283  3580 String name = (markPlaceholders && node.isPlaceholder())
284    ? (PLACEHOLDER + node.getName())
285    : node.getName();
286   
287  3580 int charWidth = fm.stringWidth(name) + 3;
288  3580 int charHeight = font.getSize();
289   
290  3580 Rectangle rect = new Rectangle(xend + 10, ypos - charHeight / 2,
291    charWidth, charHeight);
292   
293  3580 nameHash.put(node.element(), rect);
294   
295    // Colour selected leaves differently
296  3580 SequenceGroup selected = av.getSelectionGroup();
297   
298  3580 if ((selected != null)
299    && selected.getSequences(null).contains(node.element()))
300    {
301  0 g.setColor(Color.gray);
302   
303  0 g.fillRect(xend + 10, ypos - charHeight / 2, charWidth, charHeight);
304  0 g.setColor(Color.white);
305    }
306   
307  3580 g.drawString(name, xend + 10, ypos + fm.getDescent());
308  3580 g.setColor(Color.black);
309    }
310    else
311    {
312  3222 drawNode(g, (SequenceNode) node.left(), chunk, wscale, width, offx,
313    offy);
314  3222 drawNode(g, (SequenceNode) node.right(), chunk, wscale, width, offx,
315    offy);
316   
317  3222 double height = node.height;
318  3222 double dist = node.dist;
319   
320  3222 int xstart = (int) ((height - dist) * wscale) + offx;
321  3222 int xend = (int) (height * wscale) + offx;
322  3222 int ypos = (int) (node.ycount * chunk) + offy;
323   
324  3222 g.setColor(node.color.darker());
325   
326    // Draw horizontal line
327  3222 g.drawLine(xstart, ypos, xend, ypos);
328  3222 if (node == highlightNode)
329    {
330  0 g.fillRect(xend - 3, ypos - 3, 6, 6);
331    }
332    else
333    {
334  3222 g.fillRect(xend - 2, ypos - 2, 4, 4);
335    }
336   
337  3222 int ystart = (node.left() == null ? 0
338    : (int) (((SequenceNode) node.left()).ycount * chunk)) + offy;
339  3222 int yend = (node.right() == null ? 0
340    : (int) (((SequenceNode) node.right()).ycount * chunk))
341    + offy;
342   
343  3222 Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
344  3222 nodeHash.put(node, pos);
345   
346  3222 g.drawLine((int) (height * wscale) + offx, ystart,
347    (int) (height * wscale) + offx, yend);
348   
349  3222 String nodeLabel = "";
350   
351  3222 if (showDistances && (node.dist > 0))
352    {
353  0 nodeLabel = new Format("%-.2f").form(node.dist);
354    }
355   
356  3222 if (showBootstrap && node.bootstrap > -1)
357    {
358  0 if (showDistances)
359    {
360  0 nodeLabel = nodeLabel + " : ";
361    }
362   
363  0 nodeLabel = nodeLabel + String.valueOf(node.bootstrap);
364    }
365   
366  3222 if (!nodeLabel.equals(""))
367    {
368  0 g.drawString(nodeLabel, xstart + 2, ypos - 2);
369    }
370    }
371    }
372   
373    /**
374    * DOCUMENT ME!
375    *
376    * @param x
377    * DOCUMENT ME!
378    * @param y
379    * DOCUMENT ME!
380    *
381    * @return DOCUMENT ME!
382    */
 
383  0 toggle public Object findElement(int x, int y)
384    {
385  0 Enumeration keys = nameHash.keys();
386   
387  0 while (keys.hasMoreElements())
388    {
389  0 Object ob = keys.nextElement();
390  0 Rectangle rect = (Rectangle) nameHash.get(ob);
391   
392  0 if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y)
393    && (y <= (rect.y + rect.height)))
394    {
395  0 return ob;
396    }
397    }
398   
399  0 keys = nodeHash.keys();
400   
401  0 while (keys.hasMoreElements())
402    {
403  0 Object ob = keys.nextElement();
404  0 Rectangle rect = (Rectangle) nodeHash.get(ob);
405   
406  0 if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y)
407    && (y <= (rect.y + rect.height)))
408    {
409  0 return ob;
410    }
411    }
412   
413  0 return null;
414    }
415   
416    /**
417    * DOCUMENT ME!
418    *
419    * @param pickBox
420    * DOCUMENT ME!
421    */
 
422  0 toggle public void pickNodes(Rectangle pickBox)
423    {
424  0 int width = getWidth();
425  0 int height = getHeight();
426   
427  0 SequenceNode top = tree.getTopNode();
428   
429  0 double wscale = ((width * .8) - (offx * 2)) / tree.getMaxHeight();
430   
431  0 if (top.count == 0)
432    {
433  0 top.count = ((SequenceNode) top.left()).count
434    + ((SequenceNode) top.right()).count;
435    }
436   
437  0 float chunk = (float) (height - (offy)) / top.count;
438   
439  0 pickNode(pickBox, top, chunk, wscale, width, offx, offy);
440    }
441   
442    /**
443    * DOCUMENT ME!
444    *
445    * @param pickBox
446    * DOCUMENT ME!
447    * @param node
448    * DOCUMENT ME!
449    * @param chunk
450    * DOCUMENT ME!
451    * @param wscale
452    * DOCUMENT ME!
453    * @param width
454    * DOCUMENT ME!
455    * @param offx
456    * DOCUMENT ME!
457    * @param offy
458    * DOCUMENT ME!
459    */
 
460  0 toggle public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
461    double wscale, int width, int offx, int offy)
462    {
463  0 if (node == null)
464    {
465  0 return;
466    }
467   
468  0 if ((node.left() == null) && (node.right() == null))
469    {
470  0 double height = node.height;
471  0 double dist = node.dist;
472   
473  0 int xstart = (int) ((height - dist) * wscale) + offx;
474  0 int xend = (int) (height * wscale) + offx;
475   
476  0 int ypos = (int) (node.ycount * chunk) + offy;
477   
478  0 if (pickBox.contains(new Point(xend, ypos)))
479    {
480  0 if (node.element() instanceof SequenceI)
481    {
482  0 SequenceI seq = (SequenceI) node.element();
483  0 SequenceGroup sg = av.getSelectionGroup();
484   
485  0 if (sg != null)
486    {
487  0 sg.addOrRemove(seq, true);
488    }
489    }
490    }
491    }
492    else
493    {
494  0 pickNode(pickBox, (SequenceNode) node.left(), chunk, wscale, width,
495    offx, offy);
496  0 pickNode(pickBox, (SequenceNode) node.right(), chunk, wscale, width,
497    offx, offy);
498    }
499    }
500   
501    /**
502    * DOCUMENT ME!
503    *
504    * @param node
505    * DOCUMENT ME!
506    * @param c
507    * DOCUMENT ME!
508    */
 
509  0 toggle public void setColor(SequenceNode node, Color c)
510    {
511  0 if (node == null)
512    {
513  0 return;
514    }
515   
516  0 if ((node.left() == null) && (node.right() == null)) // TODO: internal node
517    {
518  0 node.color = c;
519   
520  0 if (node.element() instanceof SequenceI)
521    {
522  0 AlignmentPanel[] aps = getAssociatedPanels();
523  0 if (aps != null)
524    {
525  0 for (int a = 0; a < aps.length; a++)
526    {
527  0 final SequenceI seq = (SequenceI) node.element();
528  0 aps[a].av.setSequenceColour(seq, c);
529    }
530    }
531    }
532    }
533    else
534    {
535  0 node.color = c;
536  0 setColor((SequenceNode) node.left(), c);
537  0 setColor((SequenceNode) node.right(), c);
538    }
539    }
540   
541    /**
542    * DOCUMENT ME!
543    */
 
544  0 toggle void startPrinting()
545    {
546  0 Thread thread = new Thread(this);
547  0 thread.start();
548    }
549   
550    // put printing in a thread to avoid painting problems
 
551  0 toggle @Override
552    public void run()
553    {
554  0 PrinterJob printJob = PrinterJob.getPrinterJob();
555  0 PageFormat defaultPage = printJob.defaultPage();
556  0 PageFormat pf = printJob.pageDialog(defaultPage);
557   
558  0 if (defaultPage == pf)
559    {
560    /*
561    * user cancelled
562    */
563  0 return;
564    }
565   
566  0 printJob.setPrintable(this, pf);
567   
568  0 if (printJob.printDialog())
569    {
570  0 try
571    {
572  0 printJob.print();
573    } catch (Exception PrintException)
574    {
575  0 PrintException.printStackTrace();
576    }
577    }
578    }
579   
580    /**
581    * DOCUMENT ME!
582    *
583    * @param pg
584    * DOCUMENT ME!
585    * @param pf
586    * DOCUMENT ME!
587    * @param pi
588    * DOCUMENT ME!
589    *
590    * @return DOCUMENT ME!
591    *
592    * @throws PrinterException
593    * DOCUMENT ME!
594    */
 
595  0 toggle @Override
596    public int print(Graphics pg, PageFormat pf, int pi)
597    throws PrinterException
598    {
599  0 pg.setFont(font);
600  0 pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
601   
602  0 int pwidth = (int) pf.getImageableWidth();
603  0 int pheight = (int) pf.getImageableHeight();
604   
605  0 int noPages = getHeight() / pheight;
606   
607  0 if (pi > noPages)
608    {
609  0 return Printable.NO_SUCH_PAGE;
610    }
611   
612  0 if (pwidth > getWidth())
613    {
614  0 pwidth = getWidth();
615    }
616   
617  0 if (fitToWindow)
618    {
619  0 if (pheight > getHeight())
620    {
621  0 pheight = getHeight();
622    }
623   
624  0 noPages = 0;
625    }
626    else
627    {
628  0 FontMetrics fm = pg.getFontMetrics(font);
629  0 int height = fm.getHeight() * nameHash.size();
630  0 pg.translate(0, -pi * pheight);
631  0 pg.setClip(0, pi * pheight, pwidth, (pi * pheight) + pheight);
632   
633    // translate number of pages,
634    // height is screen size as this is the
635    // non overlapping text size
636  0 pheight = height;
637    }
638   
639  0 draw(pg, pwidth, pheight);
640   
641  0 return Printable.PAGE_EXISTS;
642    }
643   
644    /**
645    * DOCUMENT ME!
646    *
647    * @param g
648    * DOCUMENT ME!
649    */
 
650  361 toggle @Override
651    public void paintComponent(Graphics g)
652    {
653  361 super.paintComponent(g);
654  361 g.setFont(font);
655   
656  361 if (tree == null)
657    {
658  3 g.drawString(
659    MessageManager.getString("label.calculating_tree") + "....",
660    20, getHeight() / 2);
661    }
662    else
663    {
664  358 fm = g.getFontMetrics(font);
665   
666  358 if (nameHash.size() == 0)
667    {
668  12 repaint();
669    }
670   
671  358 if (fitToWindow || (!fitToWindow && (scrollPane
672    .getHeight() > ((fm.getHeight() * nameHash.size()) + offy))))
673    {
674  358 draw(g, scrollPane.getWidth(), scrollPane.getHeight());
675  358 setPreferredSize(null);
676    }
677    else
678    {
679  0 setPreferredSize(new Dimension(scrollPane.getWidth(),
680    fm.getHeight() * nameHash.size()));
681  0 draw(g, scrollPane.getWidth(), fm.getHeight() * nameHash.size());
682    }
683   
684  358 scrollPane.revalidate();
685    }
686    }
687   
688    /**
689    * DOCUMENT ME!
690    *
691    * @param fontSize
692    * DOCUMENT ME!
693    */
 
694  24 toggle @Override
695    public void setFont(Font font)
696    {
697  24 this.font = font;
698  24 repaint();
699    }
700   
701    /**
702    * DOCUMENT ME!
703    *
704    * @param g1
705    * DOCUMENT ME!
706    * @param width
707    * DOCUMENT ME!
708    * @param height
709    * DOCUMENT ME!
710    */
 
711  358 toggle public void draw(Graphics g1, int width, int height)
712    {
713  358 Graphics2D g2 = (Graphics2D) g1;
714  358 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
715    RenderingHints.VALUE_ANTIALIAS_ON);
716  358 g2.setColor(Color.white);
717  358 g2.fillRect(0, 0, width, height);
718  358 g2.setFont(font);
719   
720  358 if (longestName == null || tree == null)
721    {
722  0 g2.drawString("Calculating tree.", 20, 20);
723    }
724  358 offy = font.getSize() + 10;
725   
726  358 fm = g2.getFontMetrics(font);
727   
728  358 labelLength = fm.stringWidth(longestName) + 20; // 20 allows for scrollbar
729   
730  358 double wscale = (width - labelLength - (offx * 2))
731    / tree.getMaxHeight();
732   
733  358 SequenceNode top = tree.getTopNode();
734   
735  358 if (top.count == 0)
736    {
737  0 top.count = ((SequenceNode) top.left()).count
738    + ((SequenceNode) top.right()).count;
739    }
740   
741  358 float chunk = (float) (height - (offy)) / top.count;
742   
743  358 drawNode(g2, tree.getTopNode(), chunk, wscale, width, offx, offy);
744   
745  358 if (threshold != 0)
746    {
747  358 if (av.getCurrentTree() == tree)
748    {
749  357 g2.setColor(Color.red);
750    }
751    else
752    {
753  1 g2.setColor(Color.gray);
754    }
755   
756  358 int x = (int) ((threshold * (getWidth() - labelLength - (2 * offx)))
757    + offx);
758   
759  358 g2.drawLine(x, 0, x, getHeight());
760    }
761    }
762   
763    /**
764    * Empty method to satisfy the MouseListener interface
765    *
766    * @param e
767    */
 
768  0 toggle @Override
769    public void mouseReleased(MouseEvent e)
770    {
771    /*
772    * isPopupTrigger is set on mouseReleased on Windows
773    */
774  0 if (e.isPopupTrigger())
775    {
776  0 chooseSubtreeColour();
777  0 e.consume(); // prevent mouseClicked happening
778    }
779    }
780   
781    /**
782    * Empty method to satisfy the MouseListener interface
783    *
784    * @param e
785    */
 
786  0 toggle @Override
787    public void mouseEntered(MouseEvent e)
788    {
789    }
790   
791    /**
792    * Empty method to satisfy the MouseListener interface
793    *
794    * @param e
795    */
 
796  0 toggle @Override
797    public void mouseExited(MouseEvent e)
798    {
799    }
800   
801    /**
802    * Handles a mouse click on a tree node (clicks elsewhere are handled in
803    * mousePressed). Click selects the sub-tree, double-click swaps leaf nodes
804    * order, right-click opens a dialogue to choose colour for the sub-tree.
805    *
806    * @param e
807    */
 
808  0 toggle @Override
809    public void mouseClicked(MouseEvent evt)
810    {
811  0 if (highlightNode == null)
812    {
813  0 return;
814    }
815   
816  0 if (evt.getClickCount() > 1)
817    {
818  0 tree.swapNodes(highlightNode);
819  0 tree.reCount(tree.getTopNode());
820  0 tree.findHeight(tree.getTopNode());
821    }
822    else
823    {
824  0 Vector<SequenceNode> leaves = tree.findLeaves(highlightNode);
825   
826  0 for (int i = 0; i < leaves.size(); i++)
827    {
828  0 SequenceI seq = (SequenceI) leaves.elementAt(i).element();
829  0 treeSelectionChanged(seq);
830    }
831  0 av.sendSelection();
832    }
833   
834  0 PaintRefresher.Refresh(tp, av.getSequenceSetId());
835  0 repaint();
836    }
837   
838    /**
839    * Offer the user the option to choose a colour for the highlighted node and
840    * its children; this colour is also applied to the corresponding sequence ids
841    * in the alignment
842    */
 
843  0 toggle void chooseSubtreeColour()
844    {
845  0 String ttl = MessageManager.getString("label.select_subtree_colour");
846  0 ColourChooserListener listener = new ColourChooserListener() {
 
847  0 toggle @Override
848    public void colourSelected(Color c)
849    {
850  0 setColor(highlightNode, c);
851  0 PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
852  0 repaint();
853    }
854    };
855  0 JalviewColourChooser.showColourChooser(this, ttl, highlightNode.color, listener);
856    }
857   
 
858  0 toggle @Override
859    public void mouseMoved(MouseEvent evt)
860    {
861  0 av.setCurrentTree(tree);
862   
863  0 Object ob = findElement(evt.getX(), evt.getY());
864   
865  0 if (ob instanceof SequenceNode)
866    {
867  0 highlightNode = (SequenceNode) ob;
868  0 this.setToolTipText(
869    "<html>" + MessageManager.getString("label.highlightnode"));
870  0 repaint();
871   
872    }
873    else
874    {
875  0 if (highlightNode != null)
876    {
877  0 highlightNode = null;
878  0 setToolTipText(null);
879  0 repaint();
880    }
881    }
882    }
883   
 
884  0 toggle @Override
885    public void mouseDragged(MouseEvent ect)
886    {
887    }
888   
889    /**
890    * Handles a mouse press on a sequence name or the tree background canvas
891    * (click on a node is handled in mouseClicked). The action is to create
892    * groups by partitioning the tree at the mouse position. Colours for the
893    * groups (and sequence names) are generated randomly.
894    *
895    * @param e
896    */
 
897  0 toggle @Override
898    public void mousePressed(MouseEvent e)
899    {
900  0 av.setCurrentTree(tree);
901   
902    /*
903    * isPopupTrigger is set for mousePressed (Mac)
904    * or mouseReleased (Windows)
905    */
906  0 if (e.isPopupTrigger())
907    {
908  0 if (highlightNode != null)
909    {
910  0 chooseSubtreeColour();
911    }
912  0 return;
913    }
914   
915    /*
916    * defer right-click handling on Windows to
917    * mouseClicked; note isRightMouseButton
918    * also matches Cmd-click on Mac which should do
919    * nothing here
920    */
921  0 if (SwingUtilities.isRightMouseButton(e))
922    {
923  0 return;
924    }
925   
926  0 int x = e.getX();
927  0 int y = e.getY();
928   
929  0 Object ob = findElement(x, y);
930   
931  0 if (ob instanceof SequenceI)
932    {
933  0 treeSelectionChanged((Sequence) ob);
934  0 PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
935  0 repaint();
936  0 av.sendSelection();
937  0 return;
938    }
939  0 else if (!(ob instanceof SequenceNode))
940    {
941    // Find threshold
942  0 if (tree.getMaxHeight() != 0)
943    {
944  0 threshold = (float) (x - offx)
945    / (float) (getWidth() - labelLength - (2 * offx));
946   
947  0 List<SequenceNode> groups = tree.groupNodes(threshold);
948  0 setColor(tree.getTopNode(), Color.black);
949   
950  0 AlignmentPanel[] aps = getAssociatedPanels();
951   
952    // TODO push calls below into a single AlignViewportI method?
953    // see also AlignViewController.deleteGroups
954  0 for (int a = 0; a < aps.length; a++)
955    {
956  0 aps[a].av.setSelectionGroup(null);
957  0 aps[a].av.getAlignment().deleteAllGroups();
958  0 aps[a].av.clearSequenceColours();
959  0 if (aps[a].av.getCodingComplement() != null)
960    {
961  0 aps[a].av.getCodingComplement().setSelectionGroup(null);
962  0 aps[a].av.getCodingComplement().getAlignment()
963    .deleteAllGroups();
964  0 aps[a].av.getCodingComplement().clearSequenceColours();
965    }
966    }
967  0 colourGroups(groups);
968   
969    /*
970    * clear partition (don't show vertical line) if
971    * it is to the right of all nodes
972    */
973  0 if (groups.isEmpty())
974    {
975  0 threshold = 0f;
976    }
977    }
978   
979  0 PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
980  0 repaint();
981    }
982   
983    }
984   
 
985  0 toggle void colourGroups(List<SequenceNode> groups)
986    {
987  0 AlignmentPanel[] aps = getAssociatedPanels();
988  0 for (int i = 0; i < groups.size(); i++)
989    {
990  0 Color col = new Color((int) (Math.random() * 255),
991    (int) (Math.random() * 255), (int) (Math.random() * 255));
992  0 setColor(groups.get(i), col.brighter());
993   
994  0 Vector<SequenceNode> l = tree.findLeaves(groups.get(i));
995   
996  0 Vector<SequenceI> sequences = new Vector<>();
997   
998  0 for (int j = 0; j < l.size(); j++)
999    {
1000  0 SequenceI s1 = (SequenceI) l.elementAt(j).element();
1001   
1002  0 if (!sequences.contains(s1))
1003    {
1004  0 sequences.addElement(s1);
1005    }
1006    }
1007   
1008  0 ColourSchemeI cs = null;
1009  0 SequenceGroup sg = new SequenceGroup(sequences, null, cs, true, true,
1010    false, 0, av.getAlignment().getWidth() - 1);
1011   
1012  0 if (av.getGlobalColourScheme() != null)
1013    {
1014  0 if (av.getGlobalColourScheme() instanceof UserColourScheme)
1015    {
1016  0 cs = new UserColourScheme(
1017    ((UserColourScheme) av.getGlobalColourScheme())
1018    .getColours());
1019   
1020    }
1021    else
1022    {
1023  0 cs = ColourSchemeProperty.getColourScheme(sg, ColourSchemeProperty
1024    .getColourName(av.getGlobalColourScheme()));
1025    }
1026    // cs is null if shading is an annotationColourGradient
1027    // if (cs != null)
1028    // {
1029    // cs.setThreshold(av.getViewportColourScheme().getThreshold(),
1030    // av.isIgnoreGapsConsensus());
1031    // }
1032    }
1033  0 sg.setColourScheme(cs);
1034  0 sg.getGroupColourScheme().setThreshold(
1035    av.getResidueShading().getThreshold(),
1036    av.isIgnoreGapsConsensus());
1037    // sg.recalcConservation();
1038  0 sg.setName("JTreeGroup:" + sg.hashCode());
1039  0 sg.setIdColour(col);
1040   
1041  0 for (int a = 0; a < aps.length; a++)
1042    {
1043  0 if (aps[a].av.getGlobalColourScheme() != null
1044    && aps[a].av.getResidueShading().conservationApplied())
1045    {
1046  0 Conservation c = new Conservation("Group", sg.getSequences(null),
1047    sg.getStartRes(), sg.getEndRes());
1048  0 c.calculate();
1049  0 c.verdict(false, aps[a].av.getConsPercGaps());
1050  0 sg.cs.setConservation(c);
1051    }
1052   
1053  0 aps[a].av.getAlignment().addGroup(new SequenceGroup(sg));
1054    // TODO can we push all of the below into AlignViewportI?
1055  0 final AlignViewportI codingComplement = aps[a].av
1056    .getCodingComplement();
1057  0 if (codingComplement != null)
1058    {
1059  0 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg, av,
1060    codingComplement);
1061  0 if (mappedGroup.getSequences().size() > 0)
1062    {
1063  0 codingComplement.getAlignment().addGroup(mappedGroup);
1064  0 for (SequenceI seq : mappedGroup.getSequences())
1065    {
1066  0 codingComplement.setSequenceColour(seq, col.brighter());
1067    }
1068    }
1069    }
1070    }
1071    }
1072   
1073    // notify the panel(s) to redo any group specific stuff.
1074  0 for (int a = 0; a < aps.length; a++)
1075    {
1076  0 aps[a].updateAnnotation();
1077    // TODO: JAL-868 - need to ensure view colour change message is broadcast
1078    // to any Jmols listening in
1079  0 final AlignViewportI codingComplement = aps[a].av
1080    .getCodingComplement();
1081  0 if (codingComplement != null)
1082    {
1083  0 ((AlignViewport) codingComplement).getAlignPanel()
1084    .updateAnnotation();
1085    }
1086    }
1087    }
1088   
1089    /**
1090    * DOCUMENT ME!
1091    *
1092    * @param state
1093    * DOCUMENT ME!
1094    */
 
1095  24 toggle public void setShowDistances(boolean state)
1096    {
1097  24 this.showDistances = state;
1098  24 repaint();
1099    }
1100   
1101    /**
1102    * DOCUMENT ME!
1103    *
1104    * @param state
1105    * DOCUMENT ME!
1106    */
 
1107  24 toggle public void setShowBootstrap(boolean state)
1108    {
1109  24 this.showBootstrap = state;
1110  24 repaint();
1111    }
1112   
1113    /**
1114    * DOCUMENT ME!
1115    *
1116    * @param state
1117    * DOCUMENT ME!
1118    */
 
1119  24 toggle public void setMarkPlaceholders(boolean state)
1120    {
1121  24 this.markPlaceholders = state;
1122  24 repaint();
1123    }
1124   
 
1125  0 toggle AlignmentPanel[] getAssociatedPanels()
1126    {
1127  0 if (applyToAllViews)
1128    {
1129  0 return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());
1130    }
1131    else
1132    {
1133  0 return new AlignmentPanel[] { ap };
1134    }
1135    }
1136    }