Mercurial > hg > camir-aes2014
comparison toolboxes/graph_visualisation/lib/lefty/dotty_edit.lefty @ 0:e9a9cd732c1e tip
first hg version after svn
author | wolffd |
---|---|
date | Tue, 10 Feb 2015 15:05:51 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:e9a9cd732c1e |
---|---|
1 # | |
2 # dotty_edit: editing functions and data structures | |
3 # | |
4 dotty.protogt.getnodesbyattr = function (gt, key, val) { | |
5 local nid, node, nlist; | |
6 | |
7 nlist = []; | |
8 for (nid in gt.graph.nodes) { | |
9 node = gt.graph.nodes[nid]; | |
10 if (node.attr[key] == val) | |
11 nlist[nid] = node; | |
12 } | |
13 return nlist; | |
14 }; | |
15 dotty.protogt.reachablenodes = function (gt, node) { | |
16 local nlist, stack, eid, edge, i; | |
17 | |
18 stack[0] = node; | |
19 i = 1; | |
20 while (i > 0) { | |
21 node = stack[i - 1]; | |
22 i = i - 1; | |
23 nlist[node.nid] = node; | |
24 for (eid in node.edges) { | |
25 edge = node.edges[eid]; | |
26 if (~nlist[edge.head.nid]) { | |
27 nlist[edge.head.nid] = edge.head; | |
28 stack[i] = edge.head; | |
29 i = i + 1; | |
30 } | |
31 } | |
32 } | |
33 return nlist; | |
34 }; | |
35 dotty.protogt.mergegraph = function (gt, graph, show) { | |
36 local nameid, onode, pos, size, eid, eid2, tnode, hnode, oedge; | |
37 | |
38 if (~gt.noundo) | |
39 gt.startadd2undo (gt); | |
40 for (nameid in graph.nodedict) { | |
41 pos = null; | |
42 size = null; | |
43 onode = graph.nodes[graph.nodedict[nameid]]; | |
44 if (onode.pos) | |
45 pos = node.pos; | |
46 if (onode.size) | |
47 size = node.size; | |
48 if (~(gt.graph.nodedict[nameid] >= 0)) { | |
49 pos = null; | |
50 size = null; | |
51 if (onode.pos) | |
52 pos = node.pos; | |
53 if (onode.size) | |
54 size = node.size; | |
55 gt.insertnode (gt, pos, size, nameid, onode.attr, show); | |
56 } | |
57 } | |
58 for (eid in graph.edges) { | |
59 oedge = graph.edges[eid]; | |
60 tnode = gt.graph.nodes[gt.graph.nodedict[oedge.tail.name]]; | |
61 hnode = gt.graph.nodes[gt.graph.nodedict[oedge.head.name]]; | |
62 for (eid2 in tnode.edges) | |
63 if ( | |
64 tnode.edges[eid2].tail == tnode & | |
65 tnode.edges[eid2].head == hnode | |
66 ) { | |
67 oedge = null; | |
68 break; | |
69 } | |
70 if (oedge) | |
71 gt.insertedge (gt, tnode, null, hnode, null, oedge.attr, show); | |
72 } | |
73 if (~gt.noundo) | |
74 gt.endadd2undo (gt); | |
75 }; | |
76 dotty.protogt.insertsgraph = function (gt, name, attr, show) { | |
77 local gid, sgraph, aid; | |
78 | |
79 if (~gt) | |
80 return null; | |
81 gid = gt.graph.maxgid; | |
82 if (~name) { | |
83 while (gt.graph.graphdict[(name = concat ('g', gid))] >= 0) | |
84 gid = gid + 1; | |
85 } else if (gt.graph.graphdict[name]) { | |
86 dotty.message (0, concat ('graph: ', name, ' exists')); | |
87 return null; | |
88 } | |
89 gt.graph.graphdict[name] = gid; | |
90 gt.graph.maxgid = gid + 1; | |
91 gt.graph.graphs[gid] = [ | |
92 dotty.keys.gid = gid; | |
93 dotty.keys.name = name; | |
94 dotty.keys.gattr = copy (gt.graph.graphattr); | |
95 dotty.keys.nattr = copy (gt.graph.nodeattr); | |
96 dotty.keys.eattr = copy (gt.graph.edgeattr); | |
97 ]; | |
98 sgraph = gt.graph.graphs[gid]; | |
99 if (~attr) | |
100 attr = []; | |
101 if (~attr.label) | |
102 attr.label = '\N'; | |
103 for (aid in attr) | |
104 sgraph.graphattr[aid] = attr[aid]; | |
105 gt.unpacksgraphattr (gt, sgraph); | |
106 if (show) | |
107 gt.drawsgraph (gt, gt.views, sgraph); | |
108 return sgraph; | |
109 }; | |
110 dotty.protogt.removesgraph = function (gt, sgraph) { | |
111 gt.undrawsgraph (gt, gt.views, sgraph); | |
112 remove (sgraph.name, gt.graph.graphdict); | |
113 remove (sgraph.gid, gt.graph.graphs); | |
114 }; | |
115 dotty.protogt.insertnode = function (gt, pos, size, name, attr, show) { | |
116 local nid, node, aid; | |
117 | |
118 nid = gt.graph.maxnid; | |
119 if (~name) { | |
120 while (gt.graph.nodedict[(name = concat ('n', nid))] >= 0) | |
121 nid = nid + 1; | |
122 } else if (gt.graph.nodedict[name] >= 0) { | |
123 dotty.message (0, concat ('node: ', name, ' exists')); | |
124 return null; | |
125 } | |
126 gt.graph.nodedict[name] = nid; | |
127 gt.graph.maxnid = nid + 1; | |
128 gt.graph.nodes[nid] = [ | |
129 dotty.keys.nid = nid; | |
130 dotty.keys.name = name; | |
131 dotty.keys.attr = copy (gt.graph.nodeattr); | |
132 dotty.keys.edges = []; | |
133 ]; | |
134 node = gt.graph.nodes[nid]; | |
135 if (~attr) | |
136 attr = []; | |
137 if (~attr.label) | |
138 attr.label = '\N'; | |
139 for (aid in attr) | |
140 node.attr[aid] = attr[aid]; | |
141 gt.unpacknodeattr (gt, node); | |
142 if (~pos) | |
143 pos = ['x' = 10; 'y' = 10;]; | |
144 node[dotty.keys.pos] = copy (pos); | |
145 if (~size) | |
146 size = ['x' = strlen (attr.label) * 30; 'y' = 30;]; | |
147 if (size.x == 0) | |
148 size.x = 30; | |
149 node[dotty.keys.size] = copy (size); | |
150 node[dotty.keys.rect] = [ | |
151 0 = ['x' = pos.x - size.x / 2; 'y' = pos.y - size.y / 2;]; | |
152 1 = ['x' = pos.x + size.x / 2; 'y' = pos.y + size.y / 2;]; | |
153 ]; | |
154 node.draws = gt.simplenodedraw (node, pos, size); | |
155 if (show) | |
156 gt.drawnode (gt, gt.views, node); | |
157 if (~gt.noundo) { | |
158 gt.startadd2undo (gt); | |
159 gt.currundo.inserted.nodes[nid] = node; | |
160 gt.endadd2undo (gt); | |
161 } | |
162 return node; | |
163 }; | |
164 dotty.protogt.removenode = function (gt, node) { | |
165 local eid, list, edge, gid; | |
166 | |
167 if (~gt.noundo) | |
168 gt.startadd2undo (gt); | |
169 for (eid in node.edges) | |
170 list[eid] = node.edges[eid]; | |
171 for (eid in list) | |
172 gt.removeedge (gt, list[eid]); | |
173 gt.undrawnode (gt, gt.views, node); | |
174 for (gid in gt.graph.graphs) | |
175 remove (node.nid, gt.graph.graphs[gid].nodes); | |
176 remove (node.name, gt.graph.nodedict); | |
177 remove (node.nid, gt.graph.nodes); | |
178 if (~gt.noundo) { | |
179 gt.currundo.deleted.nodes[node.nid] = node; | |
180 gt.endadd2undo (gt); | |
181 } | |
182 }; | |
183 dotty.protogt.insertedge = function ( | |
184 gt, nodea, porta, nodeb, portb, attr, show | |
185 ) { | |
186 local eid, edge, aid, tport, hport; | |
187 | |
188 if (~nodea | ~nodeb) | |
189 return null; | |
190 if (porta) | |
191 tport = porta; | |
192 if (portb) | |
193 hport = portb; | |
194 eid = gt.graph.maxeid; | |
195 while (gt.graph.edges[eid]) | |
196 eid = eid + 1; | |
197 gt.graph.maxeid = eid + 1; | |
198 gt.graph.edges[eid] = [ | |
199 dotty.keys.eid = eid; | |
200 dotty.keys.tail = nodea; | |
201 dotty.keys.tport = porta; | |
202 dotty.keys.head = nodeb; | |
203 dotty.keys.hport = portb; | |
204 dotty.keys.attr = copy (gt.graph.edgeattr); | |
205 ]; | |
206 edge = gt.graph.edges[eid]; | |
207 if (~attr) | |
208 attr = []; | |
209 for (aid in attr) | |
210 edge.attr[aid] = attr[aid]; | |
211 nodea.edges[eid] = edge; | |
212 nodeb.edges[eid] = edge; | |
213 gt.unpackedgeattr (gt, edge); | |
214 edge.draws = gt.simpleedgedraw (edge, nodea.pos, nodeb.pos); | |
215 if (show) | |
216 gt.drawedge (gt, gt.views, edge); | |
217 if (~gt.noundo) { | |
218 gt.startadd2undo (gt); | |
219 gt.currundo.inserted.edges[eid] = edge; | |
220 gt.endadd2undo (gt); | |
221 } | |
222 return edge; | |
223 }; | |
224 dotty.protogt.removeedge = function (gt, edge) { | |
225 local head, tail; | |
226 | |
227 if (~gt.noundo) | |
228 gt.startadd2undo (gt); | |
229 if (edge.head.attr.support == 1) | |
230 head = edge.head; | |
231 if (edge.tail.attr.support == 1) | |
232 if (head ~= edge.tail) | |
233 tail = edge.tail; | |
234 gt.undrawedge (gt, gt.views, edge); | |
235 remove (edge.eid, edge.head.edges); | |
236 remove (edge.eid, edge.tail.edges); | |
237 remove (edge.eid, gt.graph.edges); | |
238 if (head & tablesize (head.edges) == 0) | |
239 gt.removenode (gt, head); | |
240 if (tail & tablesize (tail.edges) == 0) | |
241 gt.removenode (gt, tail); | |
242 if (~gt.noundo) { | |
243 gt.currundo.deleted.edges[edge.eid] = edge; | |
244 gt.endadd2undo (gt); | |
245 } | |
246 }; | |
247 dotty.protogt.swapedgeids = function (gt, edge1, edge2) { | |
248 local eid1, eid2; | |
249 | |
250 if (edge1.eid == edge2.eid) | |
251 return; | |
252 if (~gt.noundo) | |
253 gt.startadd2undo (gt); | |
254 eid1 = edge1.eid; | |
255 eid2 = edge2.eid; | |
256 gt.graph.edges[eid1] = edge2; | |
257 gt.graph.edges[eid2] = edge1; | |
258 remove (eid1, edge1.tail.edges); | |
259 remove (eid1, edge1.head.edges); | |
260 remove (eid2, edge2.tail.edges); | |
261 remove (eid2, edge2.head.edges); | |
262 edge1.tail.edges[eid2] = edge1; | |
263 edge1.head.edges[eid2] = edge1; | |
264 edge2.tail.edges[eid1] = edge2; | |
265 edge2.head.edges[eid1] = edge2; | |
266 edge1.eid = eid2; | |
267 edge2.eid = eid1; | |
268 if (~gt.noundo) { | |
269 gt.currundo.swapped.edges[eid1] = edge1; | |
270 gt.currundo.swapped.edges[eid2] = edge2; | |
271 gt.endadd2undo (gt); | |
272 } | |
273 }; | |
274 dotty.protogt.removesubtree = function (gt, obj) { | |
275 local nlist, node, head, nid, edge, eid; | |
276 | |
277 if (~gt.noundo) | |
278 gt.startadd2undo (gt); | |
279 if (obj.nid >= 0) | |
280 node = obj; | |
281 else if (obj.eid >= 0) { | |
282 node = obj.head; | |
283 gt.removeedge (gt, obj); | |
284 if (~gt.graph.nodes[node.nid]) { | |
285 if (~gt.noundo) | |
286 gt.endadd2undo (gt); | |
287 return; | |
288 } | |
289 for (eid in node.edges) { | |
290 edge = node.edges[eid]; | |
291 if (edge.head == node & edge.tail ~= node) { | |
292 if (~gt.noundo) | |
293 gt.endadd2undo (gt); | |
294 return; | |
295 } | |
296 } | |
297 } else { | |
298 dotty.message (0, 'bad object type in gt.removesubtree'); | |
299 return; | |
300 } | |
301 nlist = [node.nid = node;]; | |
302 while (node) { | |
303 for (eid in node.edges) { | |
304 head = node.edges[eid].head; | |
305 if (head ~= node) | |
306 nlist[head.nid] = head; | |
307 } | |
308 gt.removenode (gt, node); | |
309 remove (node.nid, nlist); | |
310 node = null; | |
311 for (nid in nlist) { | |
312 node = nlist[nid]; | |
313 for (eid in node.edges) { | |
314 edge = node.edges[eid]; | |
315 if (edge.head == node & edge.tail ~= node) { | |
316 node = null; | |
317 break; | |
318 } | |
319 } | |
320 if (node) | |
321 break; | |
322 } | |
323 } | |
324 if (~gt.noundo) | |
325 gt.endadd2undo (gt); | |
326 }; | |
327 dotty.protogt.removenodesbyattr = function (gt, key, val) { | |
328 local nlist, nid; | |
329 | |
330 if (~gt.noundo) | |
331 gt.startadd2undo (gt); | |
332 nlist = gt.getnodesbyattr (gt, key, val); | |
333 for (nid in nlist) | |
334 gt.removenode (gt, nlist[nid]); | |
335 if (~gt.noundo) | |
336 gt.endadd2undo (gt); | |
337 }; | |
338 dotty.protogt.removesubtreesbyattr = function (gt, key, val) { | |
339 local nlist, nid; | |
340 | |
341 if (~gt.noundo) | |
342 gt.startadd2undo (gt); | |
343 nlist = gt.getnodesbyattr (gt, key, val); | |
344 for (nid in nlist) | |
345 if (gt.graph.nodes[nid]) | |
346 gt.removesubtree (gt, nlist[nid]); | |
347 if (~gt.noundo) | |
348 gt.endadd2undo (gt); | |
349 }; | |
350 dotty.protogt.groupnodes = function ( | |
351 gt, nlist, gnode, pos, size, attr, keepmulti, show | |
352 ) { | |
353 local nid, node, elist, eid, edge, nodea, nodeb, inlist, outlist; | |
354 | |
355 if (~nlist | tablesize (nlist) == 0) | |
356 return; | |
357 if (gnode.attr.support) { | |
358 dotty.message (0, 'cannot group nodes in a support node'); | |
359 return; | |
360 } | |
361 if (~gt.noundo) | |
362 gt.startadd2undo (gt); | |
363 if (~gnode) | |
364 gnode = gt.insertnode (gt, pos, size, null, attr, show); | |
365 inlist = []; | |
366 outlist = []; | |
367 for (nid in nlist) { | |
368 if ((node = nlist[nid]) == gnode) | |
369 continue; | |
370 elist = []; | |
371 for (eid in node.edges) | |
372 elist[eid] = node.edges[eid]; | |
373 for (eid in elist) { | |
374 edge = elist[eid]; | |
375 if (edge.head == node) { | |
376 nodea = edge.tail; | |
377 nodeb = gnode; | |
378 if (~keepmulti) { | |
379 if (inlist[nodea.nid]) | |
380 continue; | |
381 inlist[nodea.nid] = nodea; | |
382 if (nodea == gnode) | |
383 outlist[nodea.nid] = nodea; | |
384 } | |
385 } else { | |
386 nodea = gnode; | |
387 nodeb = edge.head; | |
388 if (~keepmulti) { | |
389 if (outlist[nodeb.nid]) | |
390 continue; | |
391 outlist[nodeb.nid] = nodeb; | |
392 if (nodeb == gnode) | |
393 inlist[nodeb.nid] = nodeb; | |
394 } | |
395 } | |
396 gt.insertedge (gt, nodea, null, nodeb, null, edge.attr, show); | |
397 } | |
398 gt.removenode (gt, node); | |
399 } | |
400 if (~gt.noundo) | |
401 gt.endadd2undo (gt); | |
402 return gnode; | |
403 }; | |
404 dotty.protogt.groupnodesbyattr = function ( | |
405 gt, key, val, attr, keepmulti, show | |
406 ) { | |
407 local nlist, nid, pos, size; | |
408 | |
409 pos = null; | |
410 size = null; | |
411 nlist = gt.getnodesbyattr (gt, key, val); | |
412 if (show) | |
413 for (nid in nlist) { | |
414 pos = nlist[nid].pos; | |
415 size = nlist[nid].size; | |
416 break; | |
417 } | |
418 return gt.groupnodes (gt, nlist, null, pos, size, attr, keepmulti, show); | |
419 }; | |
420 dotty.protogt.cut = function (gt, obj, set, mode, op) { | |
421 local clipgt, list, node, nid, edge, eid, clipnode; | |
422 | |
423 clipgt = dotty.clipgt; | |
424 clipgt.graph = copy (dotty.protogt.graph); | |
425 if (obj.eid >= 0) { # it's an edge | |
426 list.edges[obj.eid] = obj; | |
427 node = obj.head; | |
428 } else if (obj.nid >= 0) { | |
429 list.nodes[obj.nid] = obj; | |
430 node = obj; | |
431 for (eid in node.edges) | |
432 list.edges[eid] = node.edges[eid]; | |
433 } else { | |
434 dotty.message (0, 'unknown object type in gt.cut'); | |
435 return; | |
436 } | |
437 if (set == 'reachable') { | |
438 list.nodes = gt.reachablenodes (gt, node); | |
439 for (nid in list.nodes) { | |
440 node = list.nodes[nid]; | |
441 for (eid in node.edges) { | |
442 edge = node.edges[eid]; | |
443 list.edges[edge.eid] = edge; | |
444 } | |
445 } | |
446 } | |
447 if (mode == 'support') { | |
448 for (eid in list.edges) { | |
449 edge = list.edges[eid]; | |
450 if (~list.nodes[edge.tail.nid]) { | |
451 list.support[edge.tail.nid] = edge.tail; | |
452 list.nodes[edge.tail.nid] = edge.tail; | |
453 } | |
454 if (~list.nodes[edge.head.nid]) { | |
455 list.support[edge.head.nid] = edge.head; | |
456 list.nodes[edge.head.nid] = edge.head; | |
457 } | |
458 } | |
459 } | |
460 for (nid = 0; nid < gt.graph.maxnid; nid = nid + 1) { | |
461 if (~list.nodes[nid]) | |
462 continue; | |
463 node = list.nodes[nid]; | |
464 clipnode = gt.insertnode (clipgt, null, null, node.name, node.attr, 0); | |
465 if (list.support[nid]) | |
466 clipnode.support = 1; | |
467 list.clipnodes[nid] = clipnode; | |
468 } | |
469 for (eid = 0; eid < gt.graph.maxeid; eid = eid + 1) { | |
470 if (~list.edges[eid]) | |
471 continue; | |
472 edge = list.edges[eid]; | |
473 if (~list.nodes[edge.tail.nid] | ~list.nodes[edge.head.nid]) | |
474 continue; | |
475 gt.insertedge ( | |
476 clipgt, list.clipnodes[edge.tail.nid], null, | |
477 list.clipnodes[edge.head.nid], null, edge.attr, 0 | |
478 ); | |
479 } | |
480 if (op ~= 'cut') | |
481 return; | |
482 if (~gt.noundo) | |
483 gt.startadd2undo (gt); | |
484 for (eid in list.edges) | |
485 gt.removeedge (gt, list.edges[eid]); | |
486 for (nid in list.nodes) | |
487 if (~list.support[nid] & gt.graph.nodes[nid]) | |
488 gt.removenode (gt, list.nodes[nid]); | |
489 if (~gt.noundo) | |
490 gt.endadd2undo (gt); | |
491 }; | |
492 dotty.protogt.paste = function (gt, pos, show) { | |
493 local clipgt, offset, center, nid, node, eid, edge, nodes; | |
494 | |
495 if (~gt.noundo) | |
496 gt.startadd2undo (gt); | |
497 clipgt = dotty.clipgt; | |
498 if (clipgt.graph.rect) | |
499 center = [ | |
500 'x' = (clipgt.graph.rect[1].x + clipgt.graph.rect[0].x) / 2; | |
501 'y' = (clipgt.graph.rect[1].y + clipgt.graph.rect[0].y) / 2; | |
502 ]; | |
503 else | |
504 center = pos; | |
505 offset = [ | |
506 'x' = center.x - pos.x; | |
507 'y' = center.y - pos.y; | |
508 ]; | |
509 for (nid = 0; clipgt.graph.nodes[nid]; nid = nid + 1) { | |
510 node = clipgt.graph.nodes[nid]; | |
511 if (node.attr.label == '\N' | ~node.attr.label) | |
512 node.attr.label = node.name; | |
513 if (node.support == 1) | |
514 nodes[nid] = gt.insertnode (gt, [ | |
515 'x' = node.pos.x - offset.x; | |
516 'y' = node.pos.y - offset.y; | |
517 ], null, null, [ | |
518 'support' = 1; 'shape' = 'circle'; | |
519 'label' = ''; 'width' = 0.2; | |
520 ], show); | |
521 else | |
522 nodes[nid] = gt.insertnode (gt, [ | |
523 'x' = node.pos.x - offset.x; | |
524 'y' = node.pos.y - offset.y; | |
525 ], node.size, null, node.attr, show); | |
526 } | |
527 for (eid = 0; clipgt.graph.edges[eid]; eid = eid + 1) { | |
528 edge = clipgt.graph.edges[eid]; | |
529 gt.insertedge ( | |
530 gt, nodes[edge.tail.nid], null, | |
531 nodes[edge.head.nid], null, edge.attr, show | |
532 ); | |
533 } | |
534 if (~gt.noundo) | |
535 gt.endadd2undo (gt); | |
536 }; | |
537 dotty.protogt.startadd2undo = function (gt) { | |
538 if (~gt.undoarray.level) | |
539 gt.currundo = ( | |
540 gt.undoarray.entries[tablesize (gt.undoarray.entries)] = [] | |
541 ); | |
542 gt.undoarray.level = gt.undoarray.level + 1; | |
543 }; | |
544 dotty.protogt.endadd2undo = function (gt) { | |
545 gt.undoarray.level = gt.undoarray.level - 1; | |
546 }; | |
547 dotty.protogt.undo = function (gt, show) { | |
548 local entry, n, eid, edge, nid, node, edges; | |
549 | |
550 if ((n = tablesize (gt.undoarray.entries)) < 1) | |
551 return; | |
552 entry = gt.undoarray.entries[n - 1]; | |
553 remove (n - 1, gt.undoarray.entries); | |
554 remove ('currundo', gt); | |
555 gt.noundo = 1; | |
556 # hardwire nodes and edges back with the same id's as the originals | |
557 for (nid in entry.deleted.nodes) { | |
558 node = entry.deleted.nodes[nid]; | |
559 gt.graph.nodedict[node.name] = node.nid; | |
560 gt.graph.nodes[node.nid] = node; | |
561 node.edges = []; | |
562 if (show) | |
563 gt.drawnode (gt, gt.views, node); | |
564 } | |
565 for (eid in entry.deleted.edges) { | |
566 edge = entry.deleted.edges[eid]; | |
567 gt.graph.edges[edge.eid] = edge; | |
568 edge.head.edges[edge.eid] = edge; | |
569 edge.tail.edges[edge.eid] = edge; | |
570 if (show) | |
571 gt.drawedge (gt, gt.views, edge); | |
572 } | |
573 if (entry.swapped.edges) { | |
574 if (tablesize (entry.swapped.edges) == 2) { | |
575 n = 0; | |
576 for (eid in entry.swapped.edges) { | |
577 edges[n] = entry.swapped.edges[eid]; | |
578 n = n + 1; | |
579 } | |
580 gt.swapedgeids (gt, edges[0], edges[1]); | |
581 } else | |
582 dotty.message (0, 'cannot handle undoing swap of > 2 edges'); | |
583 } | |
584 for (eid in entry.inserted.edges) { | |
585 edge = entry.inserted.edges[eid]; | |
586 gt.removeedge (gt, edge); | |
587 } | |
588 for (nid in entry.inserted.nodes) { | |
589 node = entry.inserted.nodes[nid]; | |
590 gt.removenode (gt, node); | |
591 } | |
592 gt.noundo = 0; | |
593 }; |