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