annotate toolboxes/graph_visualisation/lib/lefty/dotty_layout.lefty @ 0:cc4b1211e677 tip

initial commit to HG from Changeset: 646 (e263d8a21543) added further path and more save "camirversion.m"
author Daniel Wolff
date Fri, 19 Aug 2016 13:07:06 +0200
parents
children
rev   line source
Daniel@0 1 #
Daniel@0 2 # dotty_layout: layout functions and data structures
Daniel@0 3 #
Daniel@0 4 dotty.grablserver = function (lserver) {
Daniel@0 5 local fd;
Daniel@0 6
Daniel@0 7 if (~dotty.lservers[lserver] | tablesize (dotty.lservers[lserver]) == 0) {
Daniel@0 8 if (~((fd = openio ('pipe', lserver, 'r+', '%e -Txdot')) >= 0)) {
Daniel@0 9 dotty.message (0, concat ('cannot start ', lserver));
Daniel@0 10 return null;
Daniel@0 11 }
Daniel@0 12 dotty.lservers[lserver][fd] = [
Daniel@0 13 'fd' = fd;
Daniel@0 14 'count' = 0;
Daniel@0 15 ];
Daniel@0 16 }
Daniel@0 17 for (fd in dotty.lservers[lserver]) {
Daniel@0 18 dotty.lservers[lserver][fd].count = dotty.lservers[
Daniel@0 19 lserver
Daniel@0 20 ][fd].count + 1;
Daniel@0 21 dotty.lservers.inuse[fd] = dotty.lservers[lserver][fd];
Daniel@0 22 remove (fd, dotty.lservers[lserver]);
Daniel@0 23 return fd;
Daniel@0 24 }
Daniel@0 25 };
Daniel@0 26 dotty.releaselserver = function (lserver, fd, state) {
Daniel@0 27 if (state == 'bad' | dotty.lservers.inuse[fd].count > 40) {
Daniel@0 28 closeio (fd, 'kill');
Daniel@0 29 remove (fd, dotty.lservers.inuse);
Daniel@0 30 return;
Daniel@0 31 }
Daniel@0 32 dotty.lservers[lserver][fd] = dotty.lservers.inuse[fd];
Daniel@0 33 remove (fd, dotty.lservers.inuse);
Daniel@0 34 };
Daniel@0 35 dotty.protogt.startlayout = function (gt) {
Daniel@0 36 local lpt, fd;
Daniel@0 37
Daniel@0 38 if (gt.layoutpending >= 1) {
Daniel@0 39 lpt = dotty.layoutpending[gt.gtid];
Daniel@0 40 if (gt.layoutmode == 'async')
Daniel@0 41 monitor ('off', lpt.fd);
Daniel@0 42 dotty.releaselserver (gt.lserver, lpt.fd, 'bad');
Daniel@0 43 remove (gt.gtid, dotty.layoutpending);
Daniel@0 44 gt.layoutpending = 0;
Daniel@0 45 gt.haveinput = 0;
Daniel@0 46 dotty.popbusy (gt, gt.views);
Daniel@0 47 }
Daniel@0 48 if (~((fd = dotty.grablserver (gt.lserver)) >= 0))
Daniel@0 49 return null;
Daniel@0 50 dotty.pushbusy (gt, gt.views);
Daniel@0 51 writegraph (fd, gt.graph, 1);
Daniel@0 52 gt.layoutpending = 1;
Daniel@0 53 dotty.layoutpending[gt.gtid] = [
Daniel@0 54 'fd' = fd;
Daniel@0 55 'gtid' = gt.gtid;
Daniel@0 56 ];
Daniel@0 57 if (gt.layoutmode == 'async')
Daniel@0 58 monitor ('on', fd);
Daniel@0 59 return 1;
Daniel@0 60 };
Daniel@0 61 dotty.protogt.finishlayout = function (gt) {
Daniel@0 62 local graph, lpt, fd;
Daniel@0 63
Daniel@0 64 if (~(gt.layoutpending >= 1)) {
Daniel@0 65 dotty.message (0, concat ('no layout pending for graph ', gt.gtid));
Daniel@0 66 return null;
Daniel@0 67 }
Daniel@0 68 lpt = dotty.layoutpending[gt.gtid];
Daniel@0 69 if (~(graph = readgraph (lpt.fd))) {
Daniel@0 70 if (gt.layoutmode == 'async')
Daniel@0 71 monitor ('off', lpt.fd);
Daniel@0 72 dotty.releaselserver (gt.lserver, lpt.fd, 'bad');
Daniel@0 73 if (gt.layoutpending == 2) {
Daniel@0 74 dotty.message (0, concat ('giving up on ', gt.lserver));
Daniel@0 75 if ((fd = openio ('file', 'dottybug.dot', 'w+')) >= 0) {
Daniel@0 76 writegraph (fd, gt.graph, 0);
Daniel@0 77 closeio (fd);
Daniel@0 78 dotty.message (
Daniel@0 79 0, concat ('graph that causes ', gt.lserver)
Daniel@0 80 );
Daniel@0 81 dotty.message (
Daniel@0 82 0, 'to fail has been saved in file dottybug.dot'
Daniel@0 83 );
Daniel@0 84 dotty.message (
Daniel@0 85 0, 'please fill out a bug report at'
Daniel@0 86 );
Daniel@0 87 dotty.message (
Daniel@0 88 0, 'http://www.graphviz.org/bugs/bugform.html'
Daniel@0 89 );
Daniel@0 90 }
Daniel@0 91 dotty.popbusy (gt, gt.views);
Daniel@0 92 gt.layoutpending = 0;
Daniel@0 93 gt.haveinput = 0;
Daniel@0 94 return 1;
Daniel@0 95 }
Daniel@0 96 dotty.message (
Daniel@0 97 1, concat ('lost connection to ', gt.lserver, ', restarting...')
Daniel@0 98 );
Daniel@0 99 lpt.fd = dotty.grablserver (gt.lserver);
Daniel@0 100 writegraph (lpt.fd, gt.graph, 1);
Daniel@0 101 if (gt.layoutmode == 'async')
Daniel@0 102 monitor ('on', lpt.fd);
Daniel@0 103 gt.layoutpending = 2;
Daniel@0 104 gt.haveinput = 0;
Daniel@0 105 return null;
Daniel@0 106 }
Daniel@0 107 if (gt.layoutmode == 'async')
Daniel@0 108 monitor ('off', lpt.fd);
Daniel@0 109 dotty.releaselserver (gt.lserver, lpt.fd, null);
Daniel@0 110 remove (gt.gtid, dotty.layoutpending);
Daniel@0 111 gt.layoutpending = 0;
Daniel@0 112 gt.haveinput = 0;
Daniel@0 113 gt.unpacklayout (gt, graph);
Daniel@0 114 dotty.popbusy (gt, gt.views);
Daniel@0 115 return 1;
Daniel@0 116 };
Daniel@0 117 dotty.protogt.cancellayout = function (gt) {
Daniel@0 118 local lpt, vid;
Daniel@0 119
Daniel@0 120 if (gt.layoutpending >= 1) {
Daniel@0 121 lpt = dotty.layoutpending[gt.gtid];
Daniel@0 122 if (gt.layoutmode == 'async')
Daniel@0 123 monitor ('off', lpt.fd);
Daniel@0 124 dotty.releaselserver (gt.lserver, lpt.fd, 'bad');
Daniel@0 125 remove (gt.gtid, dotty.layoutpending);
Daniel@0 126 gt.layoutpending = 0;
Daniel@0 127 gt.haveinput = 0;
Daniel@0 128 dotty.popbusy (gt, gt.views);
Daniel@0 129 }
Daniel@0 130 };
Daniel@0 131 dotty.protogt.unpacklayout = function (gt, graph2) {
Daniel@0 132 local graph, gid, sgraph1, sgraph2, nid, node1, node2, eid, edge1, edge2;
Daniel@0 133 local t1, pos, size;
Daniel@0 134
Daniel@0 135 graph = gt.graph;
Daniel@0 136 for (gid in graph2.graphdict) {
Daniel@0 137 if (~(sgraph1 = graph.graphs[graph.graphdict[gid]]))
Daniel@0 138 continue;
Daniel@0 139 sgraph2 = graph2.graphs[graph2.graphdict[gid]];
Daniel@0 140 sgraph1.draws = gt.unpackalldraw (gt, sgraph2.graphattr);
Daniel@0 141 }
Daniel@0 142 for (nid in graph2.nodedict) {
Daniel@0 143 if (~(node1 = graph.nodes[graph.nodedict[nid]]))
Daniel@0 144 continue;
Daniel@0 145 node2 = graph2.nodes[graph2.nodedict[nid]];
Daniel@0 146 node1.draws = gt.unpackalldraw (gt, node2.attr);
Daniel@0 147 t1 = split (node2.attr.pos, ',');
Daniel@0 148 pos = ['x' = ston (t1[0]); 'y' = ston (t1[1]);];
Daniel@0 149 size = [
Daniel@0 150 'x' = ston (node2.attr.width) * 72;
Daniel@0 151 'y' = ston (node2.attr.height) * 72;
Daniel@0 152 ];
Daniel@0 153 node1.pos = pos;
Daniel@0 154 node1.size = size;
Daniel@0 155 node1.rect = [
Daniel@0 156 0 = ['x' = pos.x - size.x / 2; 'y' = pos.y - size.y / 2;];
Daniel@0 157 1 = ['x' = pos.x + size.x / 2; 'y' = pos.y + size.y / 2;];
Daniel@0 158 ];
Daniel@0 159 }
Daniel@0 160 for (eid in graph2.edges) {
Daniel@0 161 edge2 = graph2.edges[eid];
Daniel@0 162 if (edge2.attr.id) {
Daniel@0 163 if (~(edge1 = graph.edges[ston (edge2.attr.id)]))
Daniel@0 164 continue;
Daniel@0 165 } else if (graph == graph2)
Daniel@0 166 edge1 = edge2;
Daniel@0 167 edge1.draws = gt.unpackalldraw (gt, edge2.attr);
Daniel@0 168 }
Daniel@0 169 graph.draws = gt.unpackalldraw (gt, graph2.graphattr);
Daniel@0 170 t1 = split (graph2.graphattr.bb, ',');
Daniel@0 171 graph.rect[0].x = ston (t1[0]);
Daniel@0 172 graph.rect[0].y = ston (t1[1]);
Daniel@0 173 graph.rect[1].x = ston (t1[2]);
Daniel@0 174 graph.rect[1].y = ston (t1[3]);
Daniel@0 175 if (gt.graph ~= graph2)
Daniel@0 176 return;
Daniel@0 177 # strip position and size info from the attributes
Daniel@0 178 for (gid in graph2.graphdict) {
Daniel@0 179 sgraph2 = graph2.graphs[graph2.graphdict[gid]];
Daniel@0 180 gt.removealldraw (gt, sgraph2.graphattr);
Daniel@0 181 if (sgraph2.graphattr.bb)
Daniel@0 182 remove ('bb', sgraph2.graphattr);
Daniel@0 183 }
Daniel@0 184 for (nid in graph2.nodedict) {
Daniel@0 185 node2 = graph2.nodes[graph2.nodedict[nid]];
Daniel@0 186 gt.removealldraw (gt, node2.attr);
Daniel@0 187 if (node2.attr.rects)
Daniel@0 188 remove ('rects', node2.attr);
Daniel@0 189 remove ('pos', node2.attr);
Daniel@0 190 remove ('width', node2.attr);
Daniel@0 191 remove ('height', node2.attr);
Daniel@0 192 }
Daniel@0 193 for (eid in graph2.edges) {
Daniel@0 194 edge2 = graph2.edges[eid];
Daniel@0 195 gt.removealldraw (gt, edge2.attr);
Daniel@0 196 if (edge2.attr.pos)
Daniel@0 197 remove ('pos', edge2.attr);
Daniel@0 198 if (edge2.attr.lp)
Daniel@0 199 remove ('lp', edge2.attr);
Daniel@0 200 }
Daniel@0 201 gt.removealldraw (gt, graph2.graphattr);
Daniel@0 202 remove ('bb', graph2.graphattr);
Daniel@0 203 if (graph2.graphattr.lp)
Daniel@0 204 remove ('lp', graph2.graphattr);
Daniel@0 205 };
Daniel@0 206 #
Daniel@0 207 # draw directive parsing
Daniel@0 208 #
Daniel@0 209 dotty.protogt.unpackalldraw = function (gt, attr) {
Daniel@0 210 local o, did;
Daniel@0 211
Daniel@0 212 o = [];
Daniel@0 213 if (attr._draw_)
Daniel@0 214 o._draw_ = gt.unpackdraw (gt, attr._draw_);
Daniel@0 215 if (attr._ldraw_)
Daniel@0 216 o._ldraw_ = gt.unpackdraw (gt, attr._ldraw_);
Daniel@0 217 if (attr._hdraw_)
Daniel@0 218 o._hdraw_ = gt.unpackdraw (gt, attr._hdraw_);
Daniel@0 219 if (attr._tdraw_)
Daniel@0 220 o._tdraw_ = gt.unpackdraw (gt, attr._tdraw_);
Daniel@0 221 if (attr._hldraw_)
Daniel@0 222 o._hldraw_ = gt.unpackdraw (gt, attr._hldraw_);
Daniel@0 223 if (attr._tldraw_)
Daniel@0 224 o._tldraw_ = gt.unpackdraw (gt, attr._tldraw_);
Daniel@0 225 for (did in o)
Daniel@0 226 if (o[did].ep) {
Daniel@0 227 o.ep = o[did].ep;
Daniel@0 228 break;
Daniel@0 229 }
Daniel@0 230 return o;
Daniel@0 231 };
Daniel@0 232 dotty.protogt.removealldraw = function (gt, attr) {
Daniel@0 233 if (attr._draw_)
Daniel@0 234 remove ('_draw_', attr);
Daniel@0 235 if (attr._ldraw_)
Daniel@0 236 remove ('_ldraw_', attr);
Daniel@0 237 if (attr._hdraw_)
Daniel@0 238 remove ('_hdraw_', attr);
Daniel@0 239 if (attr._tdraw_)
Daniel@0 240 remove ('_tdraw_', attr);
Daniel@0 241 if (attr._hldraw_)
Daniel@0 242 remove ('_hldraw_', attr);
Daniel@0 243 if (attr._tldraw_)
Daniel@0 244 remove ('_tldraw_', attr);
Daniel@0 245 };
Daniel@0 246 dotty.protogt.unpackdraw = function (gt, attr) {
Daniel@0 247 local oo, o, tt, t, n, i, j, s, l, ep;
Daniel@0 248
Daniel@0 249 oo = [];
Daniel@0 250 t = split (attr, ' ', 0);
Daniel@0 251 n = tablesize (t);
Daniel@0 252 if (t[n - 1] == '') {
Daniel@0 253 remove (n - 1, t);
Daniel@0 254 n = n - 1;
Daniel@0 255 }
Daniel@0 256 i = 0;
Daniel@0 257 while (i < n) {
Daniel@0 258 o = [];
Daniel@0 259 if (t[i] == 'E') {
Daniel@0 260 o.type = t[i];
Daniel@0 261 o.c.x = ston (t[i + 1]);
Daniel@0 262 o.c.y = ston (t[i + 2]);
Daniel@0 263 o.s.x = ston (t[i + 3]);
Daniel@0 264 o.s.y = ston (t[i + 4]);
Daniel@0 265 i = i + 5;
Daniel@0 266 } else if (t[i] == 'e') {
Daniel@0 267 o.type = t[i];
Daniel@0 268 o.c.x = ston (t[i + 1]);
Daniel@0 269 o.c.y = ston (t[i + 2]);
Daniel@0 270 o.s.x = ston (t[i + 3]);
Daniel@0 271 o.s.y = ston (t[i + 4]);
Daniel@0 272 i = i + 5;
Daniel@0 273 } else if (t[i] == 'P') {
Daniel@0 274 o.type = t[i];
Daniel@0 275 o.n = ston (t[i + 1]);
Daniel@0 276 for (j = 0; j < o.n; j = j + 1) {
Daniel@0 277 o.ps[j].x = ston (t[i + 2 + j * 2]);
Daniel@0 278 o.ps[j].y = ston (t[i + 2 + j * 2 + 1]);
Daniel@0 279 }
Daniel@0 280 i = i + 2 + o.n * 2;
Daniel@0 281 o.ps[o.n] = o.ps[0];
Daniel@0 282 o.n = o.n + 1;
Daniel@0 283 } else if (t[i] == 'p') {
Daniel@0 284 o.type = t[i];
Daniel@0 285 o.n = ston (t[i + 1]);
Daniel@0 286 for (j = 0; j < o.n; j = j + 1) {
Daniel@0 287 o.ps[j].x = ston (t[i + 2 + j * 2]);
Daniel@0 288 o.ps[j].y = ston (t[i + 2 + j * 2 + 1]);
Daniel@0 289 }
Daniel@0 290 i = i + 2 + o.n * 2;
Daniel@0 291 o.ps[o.n] = o.ps[0];
Daniel@0 292 o.n = o.n + 1;
Daniel@0 293 } else if (t[i] == 'L') {
Daniel@0 294 o.type = t[i];
Daniel@0 295 o.n = ston (t[i + 1]);
Daniel@0 296 for (j = 0; j < o.n; j = j + 1) {
Daniel@0 297 o.ps[j].x = ston (t[i + 2 + j * 2]);
Daniel@0 298 o.ps[j].y = ston (t[i + 2 + j * 2 + 1]);
Daniel@0 299 }
Daniel@0 300 i = i + 2 + o.n * 2;
Daniel@0 301 if (~ep)
Daniel@0 302 ep = copy (o.ps[1]);
Daniel@0 303 } else if (t[i] == 'B') {
Daniel@0 304 o.type = t[i];
Daniel@0 305 o.n = ston (t[i + 1]);
Daniel@0 306 for (j = 0; j < o.n; j = j + 1) {
Daniel@0 307 o.ps[j].x = ston (t[i + 2 + j * 2]);
Daniel@0 308 o.ps[j].y = ston (t[i + 2 + j * 2 + 1]);
Daniel@0 309 }
Daniel@0 310 i = i + 2 + o.n * 2;
Daniel@0 311 if (~ep)
Daniel@0 312 ep = copy (o.ps[1]);
Daniel@0 313 } else if (t[i] == 'b') {
Daniel@0 314 o.type = t[i];
Daniel@0 315 o.n = ston (t[i + 1]);
Daniel@0 316 for (j = 0; j < o.n; j = j + 1) {
Daniel@0 317 o.ps[j].x = ston (t[i + 2 + j * 2]);
Daniel@0 318 o.ps[j].y = ston (t[i + 2 + j * 2 + 1]);
Daniel@0 319 }
Daniel@0 320 i = i + 2 + o.n * 2;
Daniel@0 321 if (~ep)
Daniel@0 322 ep = copy (o.ps[1]);
Daniel@0 323 } else if (t[i] == 'T') {
Daniel@0 324 o.type = t[i];
Daniel@0 325 o.p.x = ston (t[i + 1]);
Daniel@0 326 o.p.y = ston (t[i + 2]);
Daniel@0 327 o.j = ston (t[i + 3]);
Daniel@0 328 if (o.j == -1)
Daniel@0 329 o.j = 'lb';
Daniel@0 330 else if (o.j == 1)
Daniel@0 331 o.j = 'rb';
Daniel@0 332 else if (o.j == 0)
Daniel@0 333 o.j = 'cb';
Daniel@0 334 o.w = ston (t[i + 4]);
Daniel@0 335 o.n = ston (t[i + 5]);
Daniel@0 336 i = i + 6;
Daniel@0 337 s = t[i];
Daniel@0 338 i = i + 1;
Daniel@0 339 l = strlen (s) - 1;
Daniel@0 340 while (l < o.n) {
Daniel@0 341 s = concat (s, ' ', t[i]);
Daniel@0 342 l = l + 1 + strlen (t[i]);
Daniel@0 343 i = i + 1;
Daniel@0 344 }
Daniel@0 345 tt = split (s, '');
Daniel@0 346 l = tablesize (tt);
Daniel@0 347 s = '';
Daniel@0 348 for (j = 1; j < l; j = j + 1)
Daniel@0 349 s = concat (s, tt[j]);
Daniel@0 350 o.s = s;
Daniel@0 351 } else if (t[i] == 'C') {
Daniel@0 352 o.type = t[i];
Daniel@0 353 o.n = ston (t[i + 1]);
Daniel@0 354 i = i + 2;
Daniel@0 355 s = t[i];
Daniel@0 356 i = i + 1;
Daniel@0 357 l = strlen (s) - 1;
Daniel@0 358 while (l < o.n) {
Daniel@0 359 s = concat (s, ' ', t[i]);
Daniel@0 360 l = l + 1 + strlen (t[i]);
Daniel@0 361 i = i + 1;
Daniel@0 362 }
Daniel@0 363 tt = split (s, '');
Daniel@0 364 l = tablesize (tt);
Daniel@0 365 s = '';
Daniel@0 366 for (j = 1; j < l; j = j + 1)
Daniel@0 367 s = concat (s, tt[j]);
Daniel@0 368 o.fillcolor = gt.getcolor (gt.views, s);
Daniel@0 369 } else if (t[i] == 'c') {
Daniel@0 370 o.type = t[i];
Daniel@0 371 o.n = ston (t[i + 1]);
Daniel@0 372 i = i + 2;
Daniel@0 373 s = t[i];
Daniel@0 374 i = i + 1;
Daniel@0 375 l = strlen (s) - 1;
Daniel@0 376 while (l < o.n) {
Daniel@0 377 s = concat (s, ' ', t[i]);
Daniel@0 378 l = l + 1 + strlen (t[i]);
Daniel@0 379 i = i + 1;
Daniel@0 380 }
Daniel@0 381 tt = split (s, '');
Daniel@0 382 l = tablesize (tt);
Daniel@0 383 s = '';
Daniel@0 384 for (j = 1; j < l; j = j + 1)
Daniel@0 385 s = concat (s, tt[j]);
Daniel@0 386 o.drawcolor = gt.getcolor (gt.views, s);
Daniel@0 387 } else if (t[i] == 'F') {
Daniel@0 388 o.type = t[i];
Daniel@0 389 o.fs = ston (t[i + 1]);
Daniel@0 390 o.n = ston (t[i + 2]);
Daniel@0 391 i = i + 3;
Daniel@0 392 s = t[i];
Daniel@0 393 i = i + 1;
Daniel@0 394 l = strlen (s) - 1;
Daniel@0 395 while (l < o.n) {
Daniel@0 396 s = concat (s, ' ', t[i]);
Daniel@0 397 l = l + 1 + strlen (t[i]);
Daniel@0 398 i = i + 1;
Daniel@0 399 }
Daniel@0 400 tt = split (s, '');
Daniel@0 401 l = tablesize (tt);
Daniel@0 402 s = '';
Daniel@0 403 for (j = 1; j < l; j = j + 1)
Daniel@0 404 s = concat (s, tt[j]);
Daniel@0 405 o.ofn = s;
Daniel@0 406 o.fn = dotty.fontmap[s];
Daniel@0 407 } else if (t[i] == 'S') {
Daniel@0 408 o.type = t[i];
Daniel@0 409 o.n = ston (t[i + 1]);
Daniel@0 410 i = i + 2;
Daniel@0 411 s = t[i];
Daniel@0 412 i = i + 1;
Daniel@0 413 l = strlen (s) - 1;
Daniel@0 414 while (l < o.n) {
Daniel@0 415 s = concat (s, ' ', t[i]);
Daniel@0 416 l = l + 1 + strlen (t[i]);
Daniel@0 417 i = i + 1;
Daniel@0 418 }
Daniel@0 419 tt = split (s, '');
Daniel@0 420 l = tablesize (tt);
Daniel@0 421 s = '';
Daniel@0 422 for (j = 1; j < l; j = j + 1)
Daniel@0 423 s = concat (s, tt[j]);
Daniel@0 424 if (
Daniel@0 425 s == 'solid' | s == 'dashed' | s == 'dotted' |
Daniel@0 426 s == 'longdashed' | s == 'shortdashed'
Daniel@0 427 )
Daniel@0 428 o.style = s;
Daniel@0 429 else if (s == 'bold')
Daniel@0 430 o.width = 3;
Daniel@0 431 else {
Daniel@0 432 tt = split (s, '(');
Daniel@0 433 if (tt[0] == 'setlinewidth') {
Daniel@0 434 tt = split (tt[1], ')');
Daniel@0 435 o.width = ston (tt[0]);
Daniel@0 436 } else
Daniel@0 437 continue;
Daniel@0 438 }
Daniel@0 439 } else if (t[i] == 'I') {
Daniel@0 440 i = i + 7;
Daniel@0 441 } else {
Daniel@0 442 dotty.message (0, concat ('draw language parser error: ', t[i]));
Daniel@0 443 return null;
Daniel@0 444 }
Daniel@0 445 oo[tablesize (oo)] = o;
Daniel@0 446 }
Daniel@0 447 oo.ep = ep;
Daniel@0 448 return oo;
Daniel@0 449 };