1implement Srvbrowse; 2 3# 4# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved. 5# 6 7 8include "sys.m"; 9 sys : Sys; 10include "draw.m"; 11 draw: Draw; 12 Rect: import draw; 13include "tk.m"; 14 tk: Tk; 15include "tkclient.m"; 16 tkclient: Tkclient; 17include "grid/srvbrowse.m"; 18include "registries.m"; 19 registries: Registries; 20 Registry, Attributes, Service: import registries; 21 22init() 23{ 24 sys = load Sys Sys->PATH; 25 if (sys == nil) 26 badmod(Sys->PATH); 27 draw = load Draw Draw->PATH; 28 if (draw == nil) 29 badmod(Draw->PATH); 30 tk = load Tk Tk->PATH; 31 if (tk == nil) 32 badmod(Tk->PATH); 33 tkclient = load Tkclient Tkclient->PATH; 34 if (tkclient == nil) 35 badmod(Tkclient->PATH); 36 tkclient->init(); 37 registries = load Registries Registries->PATH; 38 if (registries == nil) 39 badmod(Registries->PATH); 40 registries->init(); 41 reg = Registry.new("/mnt/registry"); 42 if (reg == nil) { 43 reg = Registry.connect(nil, nil, nil); 44 if (reg == nil) 45 error("Could not find registry"); 46 } 47 qids = array[511] of { * => "" }; 48} 49 50reg : ref Registry; 51qids : array of string; 52 53# Qid stuff is a bit rubbish at the mo but waiting for registries to change: 54# currently address is unique but will not be in the future so waiting 55# for another id to uniquely identify a resource 56 57addqid(srvc: ref Service): int 58{ 59 addr := srvc.addr; 60 qid := addr2qid(addr); 61 for (;;) { 62 if (qids[qid] == nil) 63 break; 64 else if (qids[qid] == addr) 65 return qid; 66 qid++; 67 if (qid >= len qids) 68 qid = 0; 69 } 70 qids[qid] = addr; 71# sys->print("adding %s (%s) to %d\n",srvc.attrs.get("resource"), addr, qid); 72 return qid; 73} 74 75getqid(srvc: ref Service): string 76{ 77 addr := srvc.addr; 78 qid := addr2qid(addr); 79 startqid := qid; 80 for (;;) { 81 if (qids[qid] == addr) 82 return string qid; 83 qid++; 84 if (qid == startqid) 85 break; 86 if (qid >= len qids) 87 qid = 0; 88 } 89 return nil; 90} 91 92addr2qid(addr: string): int 93{ 94 qid := 0; 95 # assume addr starts 'tcp!...' 96 for (i := 4; i < len addr; i++) { 97 qid += addr[i] * 2**(i%10); 98 qid = qid % len qids; 99 } 100 return qid; 101} 102 103addservice(srvc: ref Service) 104{ 105 services = srvc :: services; 106 addqid(srvc); 107} 108 109find(filter: list of list of (string, string)): list of ref Service 110{ 111 lsrv : list of ref Service = nil; 112 if (filter == nil) 113 (lsrv, nil) = reg.services(); 114 else { 115 for (; filter != nil; filter = tl filter) { 116 attr := hd filter; 117 (s, nil) := reg.find(attr); 118 for (; s != nil; s = tl s) 119 lsrv = hd s :: lsrv; 120 } 121 } 122 return sortservices(lsrv); 123} 124 125refreshservices(filter: list of list of (string, string)) 126{ 127 services = find(filter); 128} 129 130servicepath2Service(path, qid: string): list of ref Service 131{ 132 srvl : list of ref Service = nil; 133 (nil, lst) := sys->tokenize(path, "/"); 134 pname: string; 135 l := len lst; 136 if (l < 2 || l > 3) 137 return nil; 138 presource := hd tl lst; 139 if (l == 3) 140 pname = hd tl tl lst; 141 142 for (tmpl := services; tmpl != nil; tmpl = tl tmpl) { 143 srvc := hd tmpl; 144 (resource, name) := getresname(srvc); 145 if (l == 2) { 146 if (resource == presource) 147 srvl = srvc :: srvl; 148 } 149 else if (l == 3) { 150 if (resource == presource) { 151 if (name == pname && qid == getqid(srvc)) { 152 srvl = srvc :: srvl; 153 break; 154 } 155 } 156 } 157 } 158 return srvl; 159} 160 161servicepath2Dir(path: string, qid: int): (array of ref sys->Dir, int) 162{ 163 # sys->print("srvcPath2Dir: '%s' %d\n",path, qid); 164 res : list of (string, string) = nil; 165 (nil, lst) := sys->tokenize(path, "/"); 166 presource, pname: string; 167 pattrib := 0; 168 l := len lst; 169 if (l > 1) 170 presource = hd tl lst; 171 if (l > 2) 172 pname = hd tl tl lst; 173 if (l == 4 && hd tl tl tl lst == "attributes") 174 pattrib = 1; 175 for (tmpl := services; tmpl != nil; tmpl = tl tmpl) { 176 srvc := hd tmpl; 177 (resource, name) := getresname(srvc); 178 if (l == 1) { 179 if (!isin(res, resource)) 180 res = (resource, nil) :: res; 181 } 182 else if (l == 2) { 183 if (resource == presource) 184 res = (name, string getqid(srvc)) :: res; 185 } 186 else if (l == 3) { 187 if (resource == presource && name == pname) { 188 if (qid == int getqid(srvc)) { 189 if (srvc.addr[0] == '@') 190 res = (srvc.addr[1:], string getqid(srvc)) :: res; 191 else { 192 if (srvc.attrs != nil) 193 res = ("attributes", string getqid(srvc)) :: res; 194 res = ("address:\0"+srvc.addr+"}", string getqid(srvc)) :: res; 195 } 196 break; 197 } 198 } 199 } 200 else if (l == 4) { 201 if (resource == presource && name == pname && pattrib) { 202 if (qid == int getqid(srvc)) { 203 for (tmpl2 := srvc.attrs.attrs; tmpl2 != nil; tmpl2 = tl tmpl2) { 204 (attrib, val) := hd tmpl2; 205 if (attrib != "name" && attrib != "resource") 206 res = (attrib+":\0"+val, string getqid(srvc)) :: res; 207 } 208 break; 209 } 210 } 211 } 212 } 213 resa := array [len res] of ref sys->Dir; 214 i := len resa - 1; 215 for (; res != nil; res = tl res) { 216 dir : sys->Dir; 217 qid: string; 218 (dir.name, qid) = hd res; 219 if (l < 3 || dir.name == "attributes") 220 dir.mode = 8r777 | sys->DMDIR; 221 else 222 dir.mode = 8r777; 223 if (qid != nil) 224 dir.qid.path = big qid; 225 resa[i--] = ref dir; 226 } 227 dups := 0; 228 if (l >= 2) 229 dups = 1; 230 return (resa, dups); 231} 232 233isin(l: list of (string, string), s: string): int 234{ 235 for (; l != nil; l = tl l) 236 if ((hd l).t0 == s) 237 return 1; 238 return 0; 239} 240 241getresname(srvc: ref Service): (string, string) 242{ 243 resource := srvc.attrs.get("resource"); 244 if (resource == nil) 245 resource = "Other"; 246 name := srvc.attrs.get("name"); 247 if (name == nil) 248 name = "?????"; 249 return (resource,name); 250} 251 252badmod(path: string) 253{ 254 sys->print("Srvbrowse: failed to load: %s\n",path); 255 exit; 256} 257 258sortservices(lsrv: list of ref Service): list of ref Service 259{ 260 a := array[len lsrv] of ref Service; 261 i := 0; 262 for (; lsrv != nil; lsrv = tl lsrv) { 263 addqid(hd lsrv); 264 a[i++] = hd lsrv; 265 } 266 heapsort(a); 267 lsrvsorted: list of ref Service = nil; 268 for (i = len a - 1; i >= 0; i--) 269 lsrvsorted = a[i] :: lsrvsorted; 270 return lsrvsorted; 271} 272 273 274heapsort(a: array of ref Service) 275{ 276 for (i := (len a / 2) - 1; i >= 0; i--) 277 movedownheap(a, i, len a - 1); 278 279 for (i = len a - 1; i > 0; i--) { 280 tmp := a[0]; 281 a[0] = a[i]; 282 a[i] = tmp; 283 movedownheap(a, 0, i - 1); 284 } 285} 286 287movedownheap(a: array of ref Service, root, end: int) 288{ 289 max: int; 290 while (2*root <= end) { 291 r2 := root * 2; 292 if (2*root == end || comp(a[r2], a[r2+1]) == GT) 293 max = r2; 294 else 295 max = r2 + 1; 296 297 if (comp(a[root], a[max]) == LT) { 298 tmp := a[root]; 299 a[root] = a[max]; 300 a[max] = tmp; 301 root = max; 302 } 303 else 304 break; 305 } 306} 307 308LT: con -1; 309EQ: con 0; 310GT: con 1; 311 312comp(a1, a2: ref Service): int 313{ 314 (resource1, name1) := getresname(a1); 315 (resource2, name2) := getresname(a2); 316 if (resource1 < resource2) 317 return LT; 318 if (resource1 > resource2) 319 return GT; 320 if (name1 < name2) 321 return LT; 322 if (name1 > name2) 323 return GT; 324 return EQ; 325} 326 327error(e: string) 328{ 329 sys->fprint(sys->fildes(2), "Srvbrowse: %s\n", e); 330 raise "fail:error"; 331} 332 333searchscr := array[] of { 334 "frame .f", 335 "scrollbar .f.sy -command {.f.c yview}", 336 "scrollbar .f.sx -command {.f.c xview} -orient horizontal", 337 "canvas .f.c -yscrollcommand {.f.sy set} -xscrollcommand {.f.sx set} -bg white -width 414 -borderwidth 2 -relief sunken -height 180 -xscrollincrement 10 -yscrollincrement 19", 338 "grid .f.sy -row 0 -column 0 -sticky ns -rowspan 2", 339 "grid .f.sx -row 1 -column 1 -sticky ew", 340 "grid .f.c -row 0 -column 1", 341 "pack .f -fill both -expand 1 ; pack propagate . 0; update", 342}; 343 344SEARCH, RESULTS: con iota; 345 346searchwin(ctxt: ref Draw->Context, chanout: chan of string, filter: list of list of (string, string)) 347{ 348 (top, titlebar) := tkclient->toplevel(ctxt,"","Search", tkclient->Appl); 349 butchan := chan of string; 350 tk->namechan(top, butchan, "butchan"); 351 tkcmds(top, searchscr); 352 makesearchframe(top); 353 flid := setframe(top, ".fsearch", nil); 354 selected := ""; 355 lresults : list of ref Service = nil; 356 resultstart := 0; 357 resize(top, 368,220); 358 maxresults := getmaxresults(top); 359 currmode := SEARCH; 360 tkclient->onscreen(top, nil); 361 tkclient->startinput(top, "kbd"::"ptr"::nil); 362 363 main: for (;;) { 364 alt { 365 s := <-top.ctxt.kbd => 366 tk->keyboard(top, s); 367 s := <-top.ctxt.ptr => 368 tk->pointer(top, *s); 369 inp := <-butchan => 370 (nil, lst) := sys->tokenize(inp, " "); 371 case hd lst { 372 "key" => 373 s := " "; 374 id := hd tl lst; 375 nv := hd tl tl lst; 376 tkp : string; 377 if (id != "-1") 378 tkp = ".fsearch.ea"+nv+id; 379 else 380 tkp = ".fsearch.e"+nv; 381 char := int hd tl tl tl lst; 382 s[0] = char; 383 if (char == '\n' || char == '\t') { 384 newtkp := ".fsearch"; 385 if (nv == "n") 386 newtkp += ".eav"+id; 387 else if (nv == "v") { 388 newid := string ((int id)+1); 389 e := tk->cmd(top, ".fsearch.ean"+newid+" cget -width"); 390 if (e == "" || e[0] == '!') { 391 insertattribrow(top); 392 newtkp += ".ean"+newid; 393 } 394 else 395 newtkp += ".ean"+newid; 396 } 397 focus(top, newtkp); 398 } 399 else { 400 tkcmd(top, tkp+" insert insert {"+s+"}"); 401 tkcmd(top, tkp+" see "+tkcmd(top, tkp+" index insert")); 402 } 403 "go" => 404 lresults = search(top, filter); 405 resultstart = 0; 406 makeresultsframe(top, lresults, 0, maxresults); 407 selected = nil; 408 flid = setframe(top, ".fresults", flid); 409 currmode = RESULTS; 410 if (chanout != nil) 411 chanout <-= "search search"; 412 "prev" => 413 selected = nil; 414 resultstart -= maxresults; 415 if (resultstart < 0) 416 resultstart = 0; 417 makeresultsframe(top, lresults, resultstart, maxresults); 418 flid = setframe(top, ".fresults", flid); 419 "next" => 420 selected = nil; 421 if (resultstart < 0) 422 resultstart = 0; 423 resultstart += maxresults; 424 if (resultstart >= len lresults) 425 resultstart -= maxresults; 426 makeresultsframe(top, lresults, resultstart, maxresults); 427 flid = setframe(top, ".fresults", flid); 428 "backto" => 429 flid = setframe(top, ".fsearch", flid); 430 tkcmd(top, ".f.c see 0 "+tkcmd(top, ".fsearch cget -height")); 431 currmode = SEARCH; 432 "new" => 433 resetsearchscr(top); 434 tkcmd(top, ".f.c see 0 0"); 435 setscrollr(top, ".fsearch"); 436 "select" => 437 if (selected != nil) 438 tkcmd(top, selected+" configure -bg white"); 439 if (selected == hd tl lst) 440 selected = nil; 441 else { 442 selected = hd tl lst; 443 tkcmd(top, hd tl lst+" configure -bg #5555FF"); 444 if (chanout != nil) 445 chanout <-= "search select " + 446 tkcmd(top, selected+" cget -text") + " " + hd tl tl lst; 447 } 448 } 449 tkcmd(top, "update"); 450 title := <-top.ctxt.ctl or 451 title = <-top.wreq or 452 title = <-titlebar => 453 if (title == "exit" || title == "ok") 454 break main; 455 e := tkclient->wmctl(top, title); 456 if (e == nil && title[0] == '!') { 457 (nil, lst) := sys->tokenize(title, " \t\n"); 458 if (len lst >= 2 && hd lst == "!size" && hd tl lst == ".") { 459 resize(top, -1,-1); 460 maxresults = getmaxresults(top); 461 if (currmode == RESULTS) { 462 makeresultsframe(top, lresults, resultstart, maxresults); 463 flid = setframe(top, ".fresults", flid); 464 tkcmd(top, "update"); 465 } 466 } 467 } 468 } 469 } 470 471} 472 473getmaxresults(top: ref Tk->Toplevel): int 474{ 475 val := ((int tkcmd(top, ".f.c cget -height")) - 65)/17; 476 if (val < 1) 477 return 1; 478 return val; 479} 480 481setframe(top: ref Tk->Toplevel, f, oldflid: string): string 482{ 483 if (oldflid != nil) 484 tkcmd(top, ".f.c delete " + oldflid); 485 newflid := tkcmd(top, ".f.c create window 0 0 -window "+f+" -anchor nw"); 486 setscrollr(top, f); 487 return newflid; 488} 489 490setscrollr(top: ref Tk->Toplevel, f: string) 491{ 492 h := tkcmd(top, f+" cget -height"); 493 w := tkcmd(top, f+" cget -width"); 494 tkcmd(top, ".f.c configure -scrollregion {0 0 "+w+" "+h+"}"); 495} 496 497resize(top: ref Tk->Toplevel, width, height: int) 498{ 499 if (width == -1) { 500 width = int tkcmd(top, ". cget -width"); 501 height = int tkcmd(top, ". cget -height"); 502 } 503 else 504 tkcmd(top, sys->sprint(". configure -width %d -height %d", width, height)); 505 htitle := int tkcmd(top, ".f cget -acty") - int tkcmd(top, ". cget -acty"); 506 height -= htitle; 507 ws := int tkcmd(top, ".f.sy cget -width"); 508 hs := int tkcmd(top, ".f.sx cget -height"); 509 510 tkcmd(top, ".f.c configure -width "+string (width - ws - 8)+ 511 " -height "+string (height - hs - 8)); 512 513 tkcmd(top, "update"); 514} 515 516makesearchframe(top: ref Tk->Toplevel) 517{ 518 font := " -font /fonts/charon/plain.normal.font"; 519 fontb := " -font /fonts/charon/bold.normal.font"; 520 f := ".fsearch"; 521 522 tkcmd(top, "frame "+f+" -bg white"); 523 tkcmd(top, "label "+f+".l -text {Search for Resource Attributes} -bg white" + fontb); 524 tkcmd(top, "grid "+f+".l -row 0 -column 0 -columnspan 3 -sticky nw"); 525 526 tkcmd(top, "grid rowconfigure "+f+" 0 -minsize 30"); 527 tkcmd(top, "frame "+f+".fgo -bg white"); 528 tkcmd(top, "button "+f+".bs -text {Search} -command {send butchan go} "+font); 529 tkcmd(top, "button "+f+".bc -text {Clear} -command {send butchan new} "+font); 530 tkcmd(top, "grid "+f+".bs -row 3 -column 0 -sticky e -padx 2 -pady 5"); 531 tkcmd(top, "grid "+f+".bc -row 3 -column 1 -sticky w -pady 5"); 532 533 tkcmd(top, "label "+f+".la1 -text {name} -bg white "+fontb); 534 tkcmd(top, "label "+f+".la2 -text {value} -bg white "+fontb); 535 536 tkcmd(top, "grid "+f+".la1 "+f+".la2 -row 1"); 537 538 insertattribrow(top); 539} 540 541insertattribrow(top: ref Tk->Toplevel) 542{ 543 (n, nil) := sys->tokenize(tkcmd(top, "grid slaves .fsearch -column 1"), " \t\n"); 544 row := string (n); 545 sn := string (n - 2); 546 fsn := ".fsearch.ean"+sn; 547 fsv := ".fsearch.eav"+sn; 548 font := " -font /fonts/charon/plain.normal.font"; 549 tkcmd(top, "entry "+fsn+" -width 170 -borderwidth 0 "+font); 550 tkcmd(top, "bind "+fsn+" <Key> {send butchan key "+sn+" n %s}"); 551 tkcmd(top, "entry "+fsv+" -width 170 -borderwidth 0 "+font); 552 tkcmd(top, "bind "+fsv+" <Key> {send butchan key "+sn+" v %s}"); 553 tkcmd(top, "grid rowinsert .fsearch "+row); 554 tkcmd(top, "grid "+fsn+" -column 0 -row "+row+" -sticky w -pady 1 -padx 2"); 555 tkcmd(top, "grid "+fsv+" -column 1 -row "+row+" -sticky w -pady 1"); 556 setscrollr(top, ".fsearch"); 557} 558 559min(a,b: int): int 560{ 561 if (a < b) 562 return a; 563 return b; 564} 565 566max(a,b: int): int 567{ 568 if (a > b) 569 return a; 570 return b; 571} 572 573makeresultsframe(top: ref Tk->Toplevel, lsrv: list of ref Service, resultstart, maxresults: int) 574{ 575 font := " -font /fonts/charon/plain.normal.font"; 576 fontb := " -font /fonts/charon/bold.normal.font"; 577 f := ".fresults"; 578 nresults := len lsrv; 579 row := 0; 580 n := 0; 581 tk->cmd(top, "destroy "+f); 582 tkcmd(top, "frame "+f+" -bg white"); 583 title := "Search Results"; 584 if (nresults > 0) { 585 from := resultstart+1; 586 too := min(resultstart+maxresults, nresults); 587 if (from == too) 588 title += sys->sprint(" (displaying match %d of %d)", from, nresults); 589 else 590 title += sys->sprint(" (displaying matches %d - %d of %d)", from, too, nresults); 591 } 592 tkcmd(top, "label "+f+".l -text {"+title+"} -bg white -anchor w" + fontb); 593 w1 := int tkcmd(top, f+".l cget -width"); 594 w2 := int tkcmd(top, ".f.c cget -width"); 595 tkcmd(top, f+".l configure -width "+string max(w1,w2)); 596 tkcmd(top, "grid "+f+".l -row 0 -column 0 -columnspan 3 -sticky nw"); 597 598 tkcmd(top, "grid rowconfigure "+f+" 0 -minsize 30"); 599 tkcmd(top, "frame "+f+".f -bg white"); 600 for (; lsrv != nil; lsrv = tl lsrv) { 601 if (n >= resultstart && n < resultstart + maxresults) { 602 srvc := hd lsrv; 603 (resource, name) := getresname(srvc); 604 qid := getqid(srvc); 605 if (qid == nil) 606 qid = string addqid(srvc); 607 label := f+".f.lQ"+qid; 608 tkcmd(top, "label "+label+" -bg white -text {services/"+ 609 resource+"/"+name+"/}"+font); 610 tkcmd(top, "grid "+label+" -row "+string row+" -column 0 -sticky w"); 611 tkcmd(top, "bind "+label+" <Button-1> {send butchan select "+label+" "+qid+"}"); 612 row++; 613 } 614 n++; 615 } 616 if (nresults == 0) { 617 tkcmd(top, "label "+f+".f.l0 -bg white -text {No matches found}"+font); 618 tkcmd(top, "grid "+f+".f.l0 -row 0 -column 0 -columnspan 3 -sticky w"); 619 } 620 else { 621 tkcmd(top, "button "+f+".bprev -text {<<} "+ 622 "-command {send butchan prev}"+font); 623 if (resultstart == 0) 624 tkcmd(top, f+".bprev configure -state disabled"); 625 tkcmd(top, "button "+f+".bnext -text {>>} "+ 626 "-command {send butchan next}"+font); 627 if (resultstart + maxresults >= nresults) 628 tkcmd(top, f+".bnext configure -state disabled"); 629 tkcmd(top, "grid "+f+".bprev -column 0 -row 2 -padx 5 -pady 5"); 630 tkcmd(top, "grid "+f+".bnext -column 2 -row 2 -padx 5 -pady 5"); 631 } 632 tkcmd(top, "grid "+f+".f -row 1 -column 0 -columnspan 3 -sticky nw"); 633 tkcmd(top, "grid rowconfigure "+f+" 1 -minsize "+string (maxresults*17)); 634 tkcmd(top, "button "+f+".bsearch -text {Back to Search} "+ 635 "-command {send butchan backto}"+font); 636 tkcmd(top, "grid "+f+".bsearch -column 1 -row 2 -padx 5 -pady 5"); 637} 638 639focus(top: ref Tk->Toplevel, newtkp: string) 640{ 641 tkcmd(top, "focus "+newtkp); 642 x1 := int tkcmd(top, newtkp + " cget -actx") 643 - int tkcmd(top, ".fsearch cget -actx"); 644 y1 := int tkcmd(top, newtkp + " cget -acty") 645 - int tkcmd(top, ".fsearch cget -acty"); 646 x2 := x1 + int tkcmd(top, newtkp + " cget -width"); 647 y2 := y1 + int tkcmd(top, newtkp + " cget -height") + 45; 648 tkcmd(top, sys->sprint(".f.c see %d %d %d %d", x1,y1-30,x2,y2)); 649} 650 651search(top: ref Tk->Toplevel, filter: list of list of (string, string)): list of ref Service 652{ 653 searchattrib: list of (string, string) = nil; 654 (n, nil) := sys->tokenize(tkcmd(top, "grid slaves .fsearch -column 0"), " \t\n"); 655 for (i := 0; i < n - 3; i++) { 656 attrib := tkcmd(top, ".fsearch.ean"+string i+" get"); 657 val := tkcmd(top, ".fsearch.eav"+string i+" get"); 658 if (val == nil) 659 val = "*"; 660 if (attrib != nil) 661 searchattrib = (attrib, val) :: searchattrib; 662 } 663 tmp : list of list of (string, string) = nil; 664 for (; filter != nil; filter = tl filter) { 665 l := hd filter; 666 for (tmp2 := searchattrib; tmp2 != nil; tmp2 = tl tmp2) 667 l = hd tmp2 :: l; 668 tmp = l :: tmp; 669 } 670 filter = tmp; 671 if (filter == nil) 672 filter = searchattrib :: nil; 673 return find(filter); 674} 675 676getitem(l : list of (string, ref Service), testid: string): ref Service 677{ 678 for (; l != nil; l = tl l) { 679 (id, srvc) := hd l; 680 if (testid == id) 681 return srvc; 682 } 683 return nil; 684} 685 686delitem(l : list of (string, ref Service), testid: string): list of (string, ref Service) 687{ 688 l2 : list of (string, ref Service) = nil; 689 for (; l != nil; l = tl l) { 690 (id, srvc) := hd l; 691 if (testid != id) 692 l2 = (id, srvc) :: l2; 693 } 694 return l2; 695} 696 697resetsearchscr(top: ref Tk->Toplevel) 698{ 699 (n, nil) := sys->tokenize(tkcmd(top, "grid slaves .fsearch -column 1"), " \t\n"); 700 for (i := 1; i < n - 2; i++) 701 tkcmd(top, "destroy .fsearch.ean"+string i+" .fsearch.eav"+string i); 702 s := " delete 0 end"; 703 tkcmd(top, ".fsearch.ean0"+s); 704 tkcmd(top, ".fsearch.eav0"+s); 705} 706 707tkcmd(top: ref Tk->Toplevel, cmd: string): string 708{ 709 e := tk->cmd(top, cmd); 710 if (e != "" && e[0] == '!') 711 sys->print("Tk error: '%s': %s\n",cmd,e); 712 return e; 713} 714 715tkcmds(top: ref Tk->Toplevel, a: array of string) 716{ 717 for (j := 0; j < len a; j++) 718 tkcmd(top, a[j]); 719} 720