Unique name for new group and treegroup
[jalview.git] / src / jalview / appletgui / TreeCanvas.java
1 /*\r
2  * Jalview - A Sequence Alignment Editor and Viewer\r
3  * Copyright (C) 2006 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle\r
4  *\r
5  * This program is free software; you can redistribute it and/or\r
6  * modify it under the terms of the GNU General Public License\r
7  * as published by the Free Software Foundation; either version 2\r
8  * of the License, or (at your option) any later version.\r
9  *\r
10  * This program is distributed in the hope that it will be useful,\r
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13  * GNU General Public License for more details.\r
14  *\r
15  * You should have received a copy of the GNU General Public License\r
16  * along with this program; if not, write to the Free Software\r
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA\r
18  */\r
19 \r
20 package jalview.appletgui;\r
21 \r
22 import java.util.*;\r
23 \r
24 import java.awt.*;\r
25 import java.awt.event.*;\r
26 \r
27 import jalview.analysis.*;\r
28 import jalview.datamodel.*;\r
29 import jalview.schemes.*;\r
30 import jalview.util.*;\r
31 \r
32 public class TreeCanvas\r
33     extends Panel implements MouseListener, MouseMotionListener\r
34 {\r
35   NJTree tree;\r
36   ScrollPane scrollPane;\r
37   AlignViewport av;\r
38   public static final String PLACEHOLDER = " * ";\r
39   Font font;\r
40   boolean fitToWindow = true;\r
41   boolean showDistances = false;\r
42   boolean showBootstrap = false;\r
43   boolean markPlaceholders = false;\r
44 \r
45   int offx = 20;\r
46   int offy;\r
47 \r
48   float threshold;\r
49 \r
50   String longestName;\r
51   int labelLength = -1;\r
52 \r
53   Hashtable nameHash = new Hashtable();\r
54   Hashtable nodeHash = new Hashtable();\r
55 \r
56   SequenceNode highlightNode;\r
57 \r
58 \r
59   public TreeCanvas(AlignViewport av, ScrollPane scroller)\r
60   {\r
61     this.av = av;\r
62     font = av.getFont();\r
63     scrollPane = scroller;\r
64     addMouseListener(this);\r
65     addMouseMotionListener(this);\r
66     setLayout(null);\r
67 \r
68     PaintRefresher.Register(this, av.getSequenceSetId());\r
69   }\r
70 \r
71   public void treeSelectionChanged(SequenceI sequence)\r
72   {\r
73     SequenceGroup selected = av.getSelectionGroup();\r
74     if (selected == null)\r
75     {\r
76       selected = new SequenceGroup();\r
77       av.setSelectionGroup(selected);\r
78     }\r
79 \r
80     selected.setEndRes(av.alignment.getWidth()-1);\r
81     selected.addOrRemove(sequence, true);\r
82   }\r
83 \r
84   public void setTree(NJTree tree)\r
85   {\r
86     this.tree = tree;\r
87     tree.findHeight(tree.getTopNode());\r
88 \r
89     // Now have to calculate longest name based on the leaves\r
90     Vector leaves = tree.findLeaves(tree.getTopNode(), new Vector());\r
91     boolean has_placeholders = false;\r
92     longestName = "";\r
93 \r
94     for (int i = 0; i < leaves.size(); i++)\r
95     {\r
96       SequenceNode lf = (SequenceNode) leaves.elementAt(i);\r
97 \r
98       if (lf.isPlaceholder())\r
99       {\r
100         has_placeholders = true;\r
101       }\r
102 \r
103       if (longestName.length() < ( (Sequence) lf.element()).getName()\r
104           .length())\r
105       {\r
106         longestName = TreeCanvas.PLACEHOLDER +\r
107             ( (Sequence) lf.element()).getName();\r
108       }\r
109     }\r
110 \r
111     setMarkPlaceholders(has_placeholders);\r
112   }\r
113 \r
114   public void drawNode(Graphics g, SequenceNode node, float chunk, float scale,\r
115                        int width, int offx, int offy)\r
116   {\r
117     if (node == null)\r
118     {\r
119       return;\r
120     }\r
121 \r
122     if (node.left() == null && node.right() == null)\r
123     {\r
124       // Drawing leaf node\r
125 \r
126       float height = node.height;\r
127       float dist = node.dist;\r
128 \r
129       int xstart = (int) ( (height - dist) * scale) + offx;\r
130       int xend = (int) (height * scale) + offx;\r
131 \r
132       int ypos = (int) (node.ycount * chunk) + offy;\r
133 \r
134       if (node.element() instanceof SequenceI)\r
135       {\r
136         SequenceI seq = (SequenceI) ( (SequenceNode) node).element();\r
137 \r
138         if (av.getSequenceColour(seq) == Color.white)\r
139         {\r
140           g.setColor(Color.black);\r
141         }\r
142         else\r
143         {\r
144           g.setColor(av.getSequenceColour(seq).darker());\r
145         }\r
146 \r
147       }\r
148       else\r
149       {\r
150         g.setColor(Color.black);\r
151       }\r
152 \r
153       // Draw horizontal line\r
154       g.drawLine(xstart, ypos, xend, ypos);\r
155 \r
156       String nodeLabel = "";\r
157       if (showDistances && node.dist > 0)\r
158       {\r
159         nodeLabel = new Format("%-.2f").form(node.dist);\r
160       }\r
161       if (showBootstrap)\r
162       {\r
163         if (showDistances)\r
164         {\r
165           nodeLabel = nodeLabel + " : ";\r
166         }\r
167         nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());\r
168       }\r
169       if (!nodeLabel.equals(""))\r
170       {\r
171         g.drawString(nodeLabel, xstart+2, ypos - 2);\r
172       }\r
173 \r
174       String name = (markPlaceholders && node.isPlaceholder()) ?\r
175           (PLACEHOLDER + node.getName()) : node.getName();\r
176       FontMetrics fm = g.getFontMetrics(font);\r
177       int charWidth = fm.stringWidth(name) + 3;\r
178       int charHeight = fm.getHeight();\r
179 \r
180       Rectangle rect = new Rectangle(xend + 10, ypos - charHeight,\r
181                                      charWidth, charHeight);\r
182 \r
183       nameHash.put( (SequenceI) node.element(), rect);\r
184 \r
185       // Colour selected leaves differently\r
186       SequenceGroup selected = av.getSelectionGroup();\r
187       if (selected != null &&\r
188           selected.getSequences(null).contains( (SequenceI) node.element()))\r
189       {\r
190         g.setColor(Color.gray);\r
191 \r
192         g.fillRect(xend + 10, ypos - charHeight + 3, charWidth, charHeight);\r
193         g.setColor(Color.white);\r
194       }\r
195       g.drawString(name, xend + 10, ypos);\r
196       g.setColor(Color.black);\r
197     }\r
198     else\r
199     {\r
200       drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx, offy);\r
201       drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx, offy);\r
202 \r
203       float height = node.height;\r
204       float dist = node.dist;\r
205 \r
206       int xstart = (int) ( (height - dist) * scale) + offx;\r
207       int xend = (int) (height * scale) + offx;\r
208       int ypos = (int) (node.ycount * chunk) + offy;\r
209 \r
210       g.setColor( ( (SequenceNode) node).color.darker());\r
211 \r
212       // Draw horizontal line\r
213       g.drawLine(xstart, ypos, xend, ypos);\r
214       if (node == highlightNode)\r
215         g.fillRect(xend - 3, ypos - 3, 6, 6);\r
216       else\r
217         g.fillRect(xend - 2, ypos - 2, 4, 4);\r
218 \r
219       int ystart = (int) ( ( (SequenceNode) node.left()).ycount * chunk) + offy;\r
220       int yend = (int) ( ( (SequenceNode) node.right()).ycount * chunk) + offy;\r
221 \r
222       Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);\r
223       nodeHash.put(node, pos);\r
224 \r
225       g.drawLine( (int) (height * scale) + offx, ystart,\r
226                  (int) (height * scale) + offx, yend);\r
227 \r
228       if (showDistances && node.dist > 0)\r
229       {\r
230         g.drawString(new Format("%-.2f").form(node.dist), xstart+2, ypos - 2);\r
231       }\r
232 \r
233     }\r
234   }\r
235 \r
236   public Object findElement(int x, int y)\r
237   {\r
238     Enumeration keys = nameHash.keys();\r
239 \r
240     while (keys.hasMoreElements())\r
241     {\r
242       Object ob = keys.nextElement();\r
243       Rectangle rect = (Rectangle) nameHash.get(ob);\r
244 \r
245       if (x >= rect.x && x <= (rect.x + rect.width) &&\r
246           y >= rect.y && y <= (rect.y + rect.height))\r
247       {\r
248         return ob;\r
249       }\r
250     }\r
251     keys = nodeHash.keys();\r
252 \r
253     while (keys.hasMoreElements())\r
254     {\r
255       Object ob = keys.nextElement();\r
256       Rectangle rect = (Rectangle) nodeHash.get(ob);\r
257 \r
258       if (x >= rect.x && x <= (rect.x + rect.width) &&\r
259           y >= rect.y && y <= (rect.y + rect.height))\r
260       {\r
261         return ob;\r
262       }\r
263     }\r
264     return null;\r
265 \r
266   }\r
267 \r
268   public void pickNodes(Rectangle pickBox)\r
269   {\r
270     int width = getSize().width;\r
271     int height = getSize().height;\r
272 \r
273     SequenceNode top = tree.getTopNode();\r
274 \r
275     float wscale = (float) (width * .8 - offx * 2) / tree.getMaxHeight()\r
276         ;\r
277     if (top.count == 0)\r
278     {\r
279       top.count = ( (SequenceNode) top.left()).count +\r
280           ( (SequenceNode) top.right()).count;\r
281     }\r
282     float chunk = (float) (height - offy) / top.count;\r
283 \r
284     pickNode(pickBox, top, chunk, wscale, width, offx, offy);\r
285   }\r
286 \r
287   public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,\r
288                        float scale, int width, int offx, int offy)\r
289   {\r
290     if (node == null)\r
291     {\r
292       return;\r
293     }\r
294 \r
295     if (node.left() == null && node.right() == null)\r
296     {\r
297       float height = node.height;\r
298       //float dist = node.dist;\r
299 \r
300       //int xstart = (int) ( (height - dist) * scale) + offx;\r
301       int xend = (int) (height * scale) + offx;\r
302 \r
303       int ypos = (int) (node.ycount * chunk) + offy;\r
304 \r
305       if (pickBox.contains(new Point(xend, ypos)))\r
306       {\r
307         if (node.element() instanceof SequenceI)\r
308         {\r
309           SequenceI seq = (SequenceI) node.element();\r
310           SequenceGroup sg = av.getSelectionGroup();\r
311           if (sg != null)\r
312           {\r
313             sg.addOrRemove(seq, true);\r
314           }\r
315         }\r
316       }\r
317     }\r
318     else\r
319     {\r
320       pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width, offx,\r
321                offy);\r
322       pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width, offx,\r
323                offy);\r
324     }\r
325   }\r
326 \r
327   public void setColor(SequenceNode node, Color c)\r
328   {\r
329     if (node == null)\r
330     {\r
331       return;\r
332     }\r
333 \r
334     if (node.left() == null && node.right() == null)\r
335     {\r
336       node.color = c;\r
337 \r
338       if (node.element() instanceof SequenceI)\r
339       {\r
340         av.setSequenceColour((SequenceI) node.element(), c);\r
341       }\r
342     }\r
343     else\r
344     {\r
345       node.color = c;\r
346       setColor( (SequenceNode) node.left(), c);\r
347       setColor( (SequenceNode) node.right(), c);\r
348     }\r
349   }\r
350 \r
351   public void update(Graphics g)\r
352   {\r
353     paint(g);\r
354   }\r
355 \r
356   public void paint(Graphics g)\r
357   {\r
358     if(tree==null)\r
359       return;\r
360 \r
361     if (nameHash.size() == 0)\r
362     {\r
363       repaint();\r
364     }\r
365 \r
366     int width = scrollPane.getSize().width;\r
367     int height = scrollPane.getSize().height;\r
368     if(!fitToWindow)\r
369     {\r
370       height = g.getFontMetrics(font).getHeight() * nameHash.size();\r
371     }\r
372 \r
373     if(getSize().width>width)\r
374     {\r
375       setSize(new Dimension(width,height));\r
376       scrollPane.validate();\r
377       return;\r
378     }\r
379 \r
380     setSize(new Dimension(width,height));\r
381 \r
382 \r
383     g.setFont(font);\r
384 \r
385     draw(g, width, height);\r
386 \r
387   }\r
388 \r
389 \r
390   public void draw(Graphics g, int width, int height)\r
391   {\r
392     offy = font.getSize()+10;\r
393 \r
394     g.setColor(Color.white);\r
395     g.fillRect(0, 0, width, height);\r
396 \r
397     labelLength = g.getFontMetrics(font).stringWidth(longestName) + 20; //20 allows for scrollbar\r
398 \r
399     float wscale = (float) (width - labelLength - offx * 2) / tree.getMaxHeight();\r
400 \r
401     SequenceNode top = tree.getTopNode();\r
402 \r
403     if (top.count == 0)\r
404     {\r
405       top.count = ( (SequenceNode) top.left()).count +\r
406           ( (SequenceNode) top.right()).count;\r
407     }\r
408     float chunk = (float) (height - offy) / top.count;\r
409 \r
410     drawNode(g, tree.getTopNode(), chunk, wscale, width, offx, offy);\r
411 \r
412     if (threshold != 0)\r
413     {\r
414       if (av.getCurrentTree() == tree)\r
415       {\r
416         g.setColor(Color.red);\r
417       }\r
418       else\r
419       {\r
420         g.setColor(Color.gray);\r
421       }\r
422 \r
423       int x = (int) (threshold *\r
424                      (float) (getSize().width - labelLength - 2 * offx) + offx);\r
425 \r
426       g.drawLine(x, 0, x, getSize().height);\r
427     }\r
428 \r
429   }\r
430 \r
431   public void mouseReleased(MouseEvent e)\r
432   {}\r
433 \r
434   public void mouseEntered(MouseEvent e)\r
435   {}\r
436 \r
437   public void mouseExited(MouseEvent e)\r
438   {}\r
439 \r
440   public void mouseClicked(MouseEvent evt)\r
441   {\r
442     if (highlightNode != null)\r
443     {\r
444       if (evt.getClickCount() > 1)\r
445       {\r
446         tree.swapNodes(highlightNode);\r
447         tree.reCount(tree.getTopNode());\r
448         tree.findHeight(tree.getTopNode());\r
449       }\r
450       else\r
451       {\r
452         Vector leaves = new Vector();\r
453         tree.findLeaves(highlightNode, leaves);\r
454 \r
455         for (int i = 0; i < leaves.size(); i++)\r
456         {\r
457           SequenceI seq =\r
458               (SequenceI) ( (SequenceNode) leaves.elementAt(i)).element();\r
459           treeSelectionChanged(seq);\r
460         }\r
461       }\r
462 \r
463       PaintRefresher.Refresh(this, av.getSequenceSetId());\r
464       repaint();\r
465     }\r
466   }\r
467 \r
468   public void mouseDragged(MouseEvent ect)\r
469   {}\r
470 \r
471 \r
472   public void mouseMoved(MouseEvent evt)\r
473   {\r
474     av.setCurrentTree(tree);\r
475 \r
476     Object ob = findElement(evt.getX(), evt.getY());\r
477 \r
478     if (ob instanceof SequenceNode)\r
479     {\r
480       highlightNode = (SequenceNode) ob;\r
481       repaint();\r
482     }\r
483     else\r
484     {\r
485       if (highlightNode != null)\r
486       {\r
487         highlightNode = null;\r
488         repaint();\r
489       }\r
490     }\r
491   }\r
492 \r
493   public void mousePressed(MouseEvent e)\r
494   {\r
495     av.setCurrentTree(tree);\r
496 \r
497     int x = e.getX();\r
498     int y = e.getY();\r
499 \r
500     Object ob = findElement(x, y);\r
501 \r
502     if (ob instanceof SequenceI)\r
503     {\r
504       treeSelectionChanged( (Sequence) ob);\r
505       PaintRefresher.Refresh(this, av.getSequenceSetId());\r
506       repaint();\r
507       return;\r
508     }\r
509     else if ( !(ob instanceof SequenceNode))\r
510     {\r
511       // Find threshold\r
512 \r
513       if (tree.getMaxHeight() != 0)\r
514       {\r
515         threshold = (float) (x - offx) /\r
516             (float) (getSize().width - labelLength - 2 * offx);\r
517 \r
518         tree.getGroups().removeAllElements();\r
519         tree.groupNodes(tree.getTopNode(), threshold);\r
520         setColor(tree.getTopNode(), Color.black);\r
521 \r
522         av.setSelectionGroup(null);\r
523         av.alignment.deleteAllGroups();\r
524         av.sequenceColours=null;\r
525 \r
526         colourGroups();\r
527 \r
528       }\r
529     }\r
530 \r
531     PaintRefresher.Refresh(this, av.getSequenceSetId());\r
532     repaint();\r
533 \r
534   }\r
535 \r
536   void colourGroups()\r
537   {\r
538     for (int i = 0; i < tree.getGroups().size(); i++)\r
539         {\r
540 \r
541           Color col = new Color( (int) (Math.random() * 255),\r
542                                 (int) (Math.random() * 255),\r
543                                 (int) (Math.random() * 255));\r
544           setColor( (SequenceNode) tree.getGroups().elementAt(i), col.brighter());\r
545 \r
546           Vector l = tree.findLeaves( (SequenceNode) tree.getGroups().elementAt(\r
547               i), new Vector());\r
548 \r
549           Vector sequences = new Vector();\r
550           for (int j = 0; j < l.size(); j++)\r
551           {\r
552             SequenceI s1 = (SequenceI) ( (SequenceNode) l.elementAt(j)).element();\r
553             if(!sequences.contains(s1))\r
554               sequences.addElement(s1);\r
555           }\r
556 \r
557           ColourSchemeI cs = null;\r
558 \r
559           if (av.getGlobalColourScheme() != null)\r
560           {\r
561             if (av.getGlobalColourScheme() instanceof UserColourScheme)\r
562             {\r
563               cs = new UserColourScheme(\r
564                   ( (UserColourScheme) av.getGlobalColourScheme()).getColours());\r
565 \r
566             }\r
567             else\r
568               cs = ColourSchemeProperty.getColour(sequences,\r
569                                                   av.alignment.getWidth(),\r
570                                                   ColourSchemeProperty.getColourName(\r
571                                                       av.getGlobalColourScheme()));\r
572 \r
573               cs.setThreshold(av.getGlobalColourScheme().getThreshold(),\r
574                                                    av.getIgnoreGapsConsensus());\r
575           }\r
576 \r
577           SequenceGroup sg = new SequenceGroup(sequences, "",\r
578                                                cs, true, true,\r
579                                                false, 0, av.alignment.getWidth()-1);\r
580 \r
581           sg.setName("JTreeGroup:"+sg.hashCode());\r
582 \r
583 \r
584           if (  av.getGlobalColourScheme()!=null\r
585              && av.getGlobalColourScheme().conservationApplied())\r
586             {\r
587             Conservation c = new Conservation("Group",\r
588                                               ResidueProperties.propHash, 3,\r
589                                               sg.getSequences(null),\r
590                                               sg.getStartRes(),\r
591                                               sg.getEndRes());\r
592 \r
593             c.calculate();\r
594             c.verdict(false, av.ConsPercGaps);\r
595             cs.setConservation(c);\r
596 \r
597             sg.cs = cs;\r
598 \r
599           }\r
600 \r
601           av.alignment.addGroup(sg);\r
602 \r
603         }\r
604 \r
605   }\r
606 \r
607   public void setShowDistances(boolean state)\r
608   {\r
609     this.showDistances = state;\r
610     repaint();\r
611   }\r
612 \r
613   public void setShowBootstrap(boolean state)\r
614   {\r
615     this.showBootstrap = state;\r
616     repaint();\r
617   }\r
618 \r
619   public void setMarkPlaceholders(boolean state)\r
620   {\r
621     this.markPlaceholders = state;\r
622     repaint();\r
623   }\r
624 \r
625 }\r