1 #include "lib9.h" 2 #include "draw.h" 3 #include "keyboard.h" 4 #include "tk.h" 5 #include "frame.h" 6 #include "label.h" 7 8 /* 9 arrow annotation for choicebutton: how do we make sure 10 the menu items come up the same size? 11 - set menu items to same req.width & height as button itself. 12 13 autorepeat: 14 when we get mouse event at the edge of the screen 15 and the menu overlaps that edge, 16 start autorepeat timer to slide the menu the opposite direction. 17 18 variable setting + command invocation: 19 is the value of the variable the text or the index? 20 same for the value appended to the command, text or index? 21 22 if it's reimplemented as a custom widget, how does the custom widget 23 get notified of variable changes? 24 */ 25 26 /* Widget Commands (+ means implemented) 27 +activate 28 +add 29 +cget 30 +configure 31 +delete 32 +entrycget 33 +entryconfigure 34 +index 35 +insert 36 +invoke 37 +post 38 +postcascade 39 +type 40 +unpost 41 +yposition 42 */ 43 44 #define O(t, e) ((long)(&((t*)0)->e)) 45 46 /* Layout constants */ 47 enum { 48 Sepheight = 6, /* Height of menu separator */ 49 }; 50 51 #define NOCHOICE "-----" 52 53 enum { 54 Startspeed = TKI2F(1), 55 }; 56 57 static 58 TkOption mbopts[] = 59 { 60 "text", OPTtext, O(TkLabel, text), nil, 61 "anchor", OPTflag, O(TkLabel, anchor), tkanchor, 62 "underline", OPTdist, O(TkLabel, ul), nil, 63 "justify", OPTstab, O(TkLabel, justify), tkjustify, 64 "menu", OPTtext, O(TkLabel, menu), nil, 65 "bitmap", OPTbmap, O(TkLabel, bitmap), nil, 66 "image", OPTimag, O(TkLabel, img), nil, 67 nil 68 }; 69 70 static 71 TkOption choiceopts[] = 72 { 73 "variable", OPTtext, O(TkLabel, variable), nil, 74 "values", OPTlist, O(TkLabel, values), nil, 75 "command", OPTtext, O(TkLabel, command), nil, 76 nil 77 }; 78 79 static 80 TkEbind mbbindings[] = 81 { 82 {TkEnter, "%W tkMBenter %s"}, 83 {TkLeave, "%W tkMBleave"}, 84 {TkButton1P, "%W tkMBpress 1"}, 85 {TkKey, "%W tkMBkey 0x%K"}, 86 {TkButton1P|TkMotion, "%W tkMBpress 0"}, 87 }; 88 89 extern Rectangle bbnil; 90 static char* tkmpost(Tk*, int, int, int, int, int); 91 static void menuclr(Tk*); 92 static void freemenu(Tk*); 93 static void appenditem(Tk*, Tk*, int); 94 static void layout(Tk*); 95 static Tk* tkmenuindex2ptr(Tk*, char**); 96 static void activateitem(Tk*); 97 98 /* 99 * unmap menu cascade upto (but not including) tk 100 */ 101 static void 102 tkunmapmenus(TkTop *top, Tk *tk) 103 { 104 TkTop *t; 105 Tk *menu; 106 TkWin *tkw; 107 108 menu = top->ctxt->tkmenu; 109 if (menu == nil) 110 return; 111 t = menu->env->top; 112 113 /* if something went wrong, clear down all menus */ 114 if (tk != nil && tk->env->top != t) 115 tk = nil; 116 117 while (menu != nil && menu != tk) { 118 menuclr(menu); 119 tkunmap(menu); 120 tkcancelrepeat(menu); 121 tkw = TKobj(TkWin, menu); 122 if (tkw->cascade != nil) { 123 menu = tklook(t, tkw->cascade, 0); 124 free(tkw->cascade); 125 tkw->cascade = nil; 126 } else 127 menu = nil; 128 } 129 top->ctxt->tkmenu = menu; 130 tksetmgrab(top, menu); 131 } 132 133 static void 134 tkunmapmenu(Tk *tk) 135 { 136 TkTop *t; 137 TkWin *tkw; 138 Tk *parent; 139 140 parent = nil; 141 tkw = TKobj(TkWin, tk); 142 t = tk->env->top; 143 if (tkw->cascade != nil) 144 parent = tklook(t, tkw->cascade, 0); 145 tkunmapmenus(t, parent); 146 if (tkw->freeonunmap) 147 freemenu(tk); 148 } 149 150 static void 151 tksizemenubutton(Tk *tk) 152 { 153 int w, h; 154 char **v, *cur; 155 TkLabel *tkl = TKobj(TkLabel, tk); 156 157 tksizelabel(tk); 158 if(tk->type != TKchoicebutton) 159 return; 160 w = tk->req.width; 161 h = tk->req.height; 162 v = tkl->values; 163 if (v == nil || *v == nil) 164 return; 165 cur = tkl->text; 166 for (; *v; v++) { 167 tkl->text = *v; 168 tksizelabel(tk); 169 if (tk->req.width > w) 170 w = tk->req.width; 171 if (tk->req.height > h) 172 h = tk->req.height; 173 } 174 tkl->text = cur; 175 tksizelabel(tk); 176 tk->req.width = w; 177 tk->req.height = h; 178 } 179 180 static char* 181 tkmkmenubutton(TkTop *t, char *arg, char **ret, int type, TkOption *opts) 182 { 183 Tk *tk; 184 char *e, **v; 185 TkName *names; 186 TkLabel *tkl; 187 TkOptab tko[3]; 188 189 /* need to get the label from elsewhere */ 190 tk = tknewobj(t, type, sizeof(Tk)+sizeof(TkLabel)); 191 if(tk == nil) 192 return TkNomem; 193 tk->borderwidth = 2; 194 tk->flag |= Tknograb; 195 196 tkl = TKobj(TkLabel, tk); 197 tkl->ul = -1; 198 if(type == TKchoicebutton) 199 tkl->anchor = Tknorth|Tkwest; 200 201 tko[0].ptr = tk; 202 tko[0].optab = tkgeneric; 203 tko[1].ptr = tkl; 204 tko[1].optab = opts; 205 tko[2].ptr = nil; 206 207 names = nil; 208 e = tkparse(t, arg, tko, &names); 209 if(e != nil) { 210 tkfreeobj(tk); 211 return e; 212 } 213 tkl->nvalues = 0; 214 if (tkl->values != nil) { 215 for (v = tkl->values; *v; v++) 216 ; 217 tkl->nvalues = v - tkl->values; 218 } 219 if(type == TKchoicebutton){ 220 if(tkl->nvalues > 0) 221 tkl->text = strdup(tkl->values[0]); 222 else 223 tkl->text = strdup(NOCHOICE); 224 } 225 tksettransparent(tk, 226 tkhasalpha(tk->env, TkCbackgnd) || 227 tkhasalpha(tk->env, TkCselectbgnd) || 228 tkhasalpha(tk->env, TkCactivebgnd)); 229 230 e = tkbindings(t, tk, mbbindings, nelem(mbbindings)); 231 232 if(e != nil) { 233 tkfreeobj(tk); 234 return e; 235 } 236 tksizemenubutton(tk); 237 238 e = tkaddchild(t, tk, &names); 239 tkfreename(names); 240 if(e != nil) { 241 tkfreeobj(tk); 242 return e; 243 } 244 tk->name->link = nil; 245 246 return tkvalue(ret, "%s", tk->name->name); 247 } 248 249 char* 250 tkchoicebutton(TkTop *t, char *arg, char **ret) 251 { 252 return tkmkmenubutton(t, arg, ret, TKchoicebutton, choiceopts); 253 } 254 255 char* 256 tkmenubutton(TkTop *t, char *arg, char **ret) 257 { 258 return tkmkmenubutton(t, arg, ret, TKmenubutton, mbopts); 259 } 260 261 static char* 262 tkmenubutcget(Tk *tk, char *arg, char **val) 263 { 264 TkOptab tko[3]; 265 TkLabel *tkl = TKobj(TkLabel, tk); 266 267 tko[0].ptr = tk; 268 tko[0].optab = tkgeneric; 269 tko[1].ptr = tkl; 270 tko[1].optab = (tk->type == TKchoicebutton ? choiceopts : mbopts); 271 tko[2].ptr = nil; 272 273 return tkgencget(tko, arg, val, tk->env->top); 274 } 275 276 static char* 277 tkmenubutconf(Tk *tk, char *arg, char **val) 278 { 279 char *e, **v; 280 TkGeom g; 281 int bd; 282 TkOptab tko[3]; 283 TkLabel *tkl = TKobj(TkLabel, tk); 284 285 tko[0].ptr = tk; 286 tko[0].optab = tkgeneric; 287 tko[1].ptr = tkl; 288 tko[1].optab = (tk->type == TKchoicebutton ? choiceopts : mbopts); 289 tko[2].ptr = nil; 290 291 if(*arg == '\0') 292 return tkconflist(tko, val); 293 294 g = tk->req; 295 bd = tk->borderwidth; 296 e = tkparse(tk->env->top, arg, tko, nil); 297 298 if (tk->type == TKchoicebutton) { 299 tkl->nvalues = 0; 300 if (tkl->values != nil) { 301 for (v = tkl->values; *v; v++) 302 ; 303 tkl->nvalues = v - tkl->values; 304 } 305 if (tkl->check >= tkl->nvalues || strcmp(tkl->text, tkl->values[tkl->check])) { 306 /* 307 * try to keep selected value the same if possible 308 */ 309 for (v = tkl->values; v && *v; v++) 310 if (!strcmp(*v, tkl->text)) 311 break; 312 free(tkl->text); 313 if (v == nil || *v == nil) { 314 tkl->text = strdup(tkl->nvalues > 0 ? tkl->values[0] : NOCHOICE); 315 tkl->check = 0; 316 } else { 317 tkl->check = v - tkl->values; 318 tkl->text = strdup(*v); 319 } 320 } 321 } 322 tksettransparent(tk, 323 tkhasalpha(tk->env, TkCbackgnd) || 324 tkhasalpha(tk->env, TkCselectbgnd) || 325 tkhasalpha(tk->env, TkCactivebgnd)); 326 tksizemenubutton(tk); 327 tkgeomchg(tk, &g, bd); 328 329 tk->dirty = tkrect(tk, 1); 330 return e; 331 } 332 333 static char* 334 tkMBleave(Tk *tk, char *arg, char **val) 335 { 336 USED(arg); 337 USED(val); 338 339 tk->flag &= ~Tkactive; 340 tk->dirty = tkrect(tk, 1); 341 return nil; 342 } 343 344 static Tk* 345 mkchoicemenu(Tk *tkb) 346 { 347 Tk *menu, *tkc; 348 int i; 349 TkLabel *tkl, *tkcl; 350 TkWin *tkw; 351 TkTop *t; 352 353 tkl = TKobj(TkLabel, tkb); 354 t = tkb->env->top; 355 356 menu = tknewobj(t, TKmenu, sizeof(Tk)+sizeof(TkWin)); 357 if(menu == nil) 358 return nil; 359 360 menu->relief = TKraised; 361 menu->flag |= Tknograb; 362 menu->borderwidth = 1; 363 tkputenv(menu->env); 364 menu->env = tkb->env; 365 menu->env->ref++; 366 367 menu->flag |= Tkwindow; 368 menu->geom = tkmoveresize; 369 tkw = TKobj(TkWin, menu); 370 tkw->cbname = strdup(tkb->name->name); 371 tkw->di = (void*)-1; // XXX 372 373 for(i = tkl->nvalues - 1; i >= 0; i--){ 374 tkc = tknewobj(t, TKlabel, sizeof(Tk)+sizeof(TkLabel)); 375 /* XXX recover from malloc failure */ 376 tkc->flag = Tkwest|Tkfillx|Tktop; 377 tkc->highlightwidth = 0; 378 tkc->borderwidth = 1; 379 tkc->relief = TKflat; 380 tkputenv(tkc->env); 381 tkc->env = tkb->env; 382 tkc->env->ref++; 383 tkcl = TKobj(TkLabel, tkc); 384 tkcl->anchor = Tkwest; 385 tkcl->ul = -1; 386 tkcl->justify = Tkleft; 387 tkcl->text = strdup(tkl->values[i]); 388 tkcl->command = smprint("%s invoke %d", tkb->name->name, i); 389 /* XXX recover from malloc failure */ 390 tksizelabel(tkc); 391 tkc->req.height = tkb->req.height; 392 appenditem(menu, tkc, 0); 393 } 394 layout(menu); 395 396 tkw->next = t->windows; 397 tkw->freeonunmap = 1; 398 t->windows = menu; 399 return menu; 400 } 401 402 static char* 403 tkMBpress(Tk *tk, char *arg, char **val) 404 { 405 Tk *menu, *item; 406 TkLabel *tkl = TKobj(TkLabel, tk); 407 Point g; 408 char buf[12], *bufp, *e; 409 410 USED(arg); 411 USED(val); 412 413 g = tkposn(tk); 414 if (tk->type == TKchoicebutton) { 415 menu = mkchoicemenu(tk); 416 if (menu == nil) 417 return TkNomem; 418 sprint(buf, "%d", tkl->check); 419 bufp = buf; 420 item = tkmenuindex2ptr(menu, &bufp); 421 if(item == nil) 422 return nil; 423 g.y -= item->act.y; 424 e = tkmpost(menu, g.x, g.y, 0, 0, 0); 425 activateitem(item); 426 return e; 427 } else { 428 if (tkl->menu == nil) 429 return nil; 430 menu = tklook(tk->env->top, tkl->menu, 0); 431 if(menu == nil || menu->type != TKmenu) 432 return TkBadwp; 433 434 if(menu->flag & Tkmapped) { 435 if(atoi(arg)) 436 tkunmapmenu(menu); 437 return nil; 438 } 439 return tkmpost(menu, g.x, g.y, 0, tk->act.height + 2*tk->borderwidth, 1); 440 } 441 } 442 443 static char* 444 tkMBkey(Tk *tk, char *arg, char **val) 445 { 446 int key; 447 USED(val); 448 449 if(tk->flag & Tkdisabled) 450 return nil; 451 452 key = strtol(arg, nil, 0); 453 if (key == '\n' || key == ' ') 454 return tkMBpress(tk, "1", nil); 455 return nil; 456 } 457 458 static char* 459 tkMBenter(Tk *tk, char *arg, char **val) 460 { 461 USED(arg); 462 USED(val); 463 464 tk->flag |= Tkactive; 465 tk->dirty = tkrect(tk, 1); 466 return nil; 467 } 468 469 static char* 470 tkchoicebutset(Tk *tk, char *arg, char **val) 471 { 472 char buf[12], *e; 473 int v; 474 TkLabel *tkl = TKobj(TkLabel, tk); 475 476 USED(val); 477 478 tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); 479 if (*buf == '\0') 480 return TkBadvl; 481 v = atoi(buf); 482 if (v < 0 || v >= tkl->nvalues) 483 return TkBadvl; 484 if (v == tkl->check) 485 return nil; 486 free(tkl->text); 487 tkl->text = strdup(tkl->values[v]); 488 /* XXX recover from malloc error */ 489 tkl->check = v; 490 491 sprint(buf, "%d", v); 492 e = tksetvar(tk->env->top, tkl->variable, buf); 493 if(e != nil) 494 return e; 495 496 tk->dirty = tkrect(tk, 1); 497 return nil; 498 } 499 500 static char* 501 tkchoicebutinvoke(Tk *tk, char *arg, char **val) 502 { 503 TkLabel *tkl = TKobj(TkLabel, tk); 504 char *e; 505 506 e = tkchoicebutset(tk, arg, val); 507 if(e != nil) 508 return e; 509 if(tkl->command) 510 return tkexec(tk->env->top, tkl->command, val); 511 return nil; 512 } 513 514 static char* 515 tkchoicebutgetvalue(Tk *tk, char *arg, char **val) 516 { 517 char buf[12]; 518 int gotarg, v; 519 TkLabel *tkl = TKobj(TkLabel, tk); 520 if (tkl->nvalues == 0) 521 return nil; 522 tkword(tk->env->top, arg, buf, buf+sizeof(buf), &gotarg); 523 if (!gotarg) 524 return tkvalue(val, "%s", tkl->values[tkl->check]); 525 v = atoi(buf); 526 if (buf[0] < '0' || buf[0] > '9' || v >= tkl->nvalues) 527 return TkBadvl; 528 return tkvalue(val, "%s", tkl->values[tkl->check]); 529 } 530 531 static char* 532 tkchoicebutsetvalue(Tk *tk, char *arg, char **val) 533 { 534 char *buf; 535 char **v; 536 int gotarg; 537 TkLabel *tkl = TKobj(TkLabel, tk); 538 539 USED(val); 540 if (tkl->nvalues == 0) 541 return TkBadvl; 542 buf = mallocz(Tkmaxitem, 0); 543 if (buf == nil) 544 return TkNomem; 545 tkword(tk->env->top, arg, buf, buf+Tkmaxitem, &gotarg); 546 if (!gotarg) { 547 free(buf); 548 return TkBadvl; 549 } 550 for (v = tkl->values; *v; v++) 551 if (strcmp(*v, buf) == 0) 552 break; 553 free(buf); 554 if (*v == nil) 555 return TkBadvl; 556 free(tkl->text); 557 tkl->text = strdup(*v); 558 /* XXX recover from malloc error */ 559 tkl->check = v - tkl->values; 560 561 tk->dirty = tkrect(tk, 1); 562 return nil; 563 } 564 565 static char* 566 tkchoicebutget(Tk *tk, char *arg, char **val) 567 { 568 TkLabel *tkl = TKobj(TkLabel, tk); 569 char *buf, **v; 570 int gotarg; 571 572 if (tkl->nvalues == 0) 573 return nil; 574 buf = mallocz(Tkmaxitem, 0); 575 if (buf == nil) 576 return TkNomem; 577 tkword(tk->env->top, arg, buf, buf+Tkmaxitem, &gotarg); 578 if (!gotarg) { 579 free(buf); 580 return tkvalue(val, "%d", tkl->check); 581 } 582 583 for (v = tkl->values; *v; v++) 584 if (strcmp(*v, buf) == 0) 585 break; 586 free(buf); 587 if (*v) 588 return tkvalue(val, "%d", v - tkl->values); 589 return nil; 590 } 591 592 static char* 593 tkchoicebutvaluecount(Tk *tk, char *arg, char **val) 594 { 595 TkLabel *tkl = TKobj(TkLabel, tk); 596 USED(arg); 597 return tkvalue(val, "%d", tkl->nvalues); 598 } 599 600 601 static void 602 tkchoicevarchanged(Tk *tk, char *var, char *value) 603 { 604 TkLabel *tkl = TKobj(TkLabel, tk); 605 int v; 606 607 if(tkl->variable != nil && strcmp(tkl->variable, var) == 0){ 608 if(value[0] < '0' || value[0] > '9') 609 return; 610 v = atoi(value); 611 if(v < 0 || v >= tkl->nvalues) 612 return; /* what else can we do? */ 613 free(tkl->text); 614 tkl->text = strdup(tkl->values[v]); 615 /* XXX recover from malloc error */ 616 tkl->check = v; 617 tk->dirty = tkrect(tk, 0); 618 tkdirty(tk); 619 } 620 } 621 622 Tk * 623 tkfindchoicemenu(Tk *tkb) 624 { 625 Tk *tk, *next; 626 TkTop *top; 627 TkWin *tkw; 628 629 top = tkb->env->top; 630 for (tk = top->windows; tk != nil; tk = next){ 631 tkw = TKobj(TkWin, tk); 632 if(tk->name == nil){ 633 assert(strcmp(tkw->cbname, tkb->name->name) == 0); 634 return tk; 635 } 636 next = tkw->next; 637 } 638 return nil; 639 } 640 641 static 642 TkOption menuopt[] = 643 { 644 "postcommand", OPTtext, O(TkWin, postcmd), nil, 645 nil, 646 }; 647 648 char* 649 tkmenu(TkTop *t, char *arg, char **ret) 650 { 651 Tk *tk; 652 char *e; 653 TkWin *tkw; 654 TkName *names; 655 TkOptab tko[3]; 656 657 tk = tknewobj(t, TKmenu, sizeof(Tk)+sizeof(TkWin)); 658 if(tk == nil) 659 return TkNomem; 660 661 tkw = TKobj(TkWin, tk); 662 tkw->di = (void*)-1; // XXX 663 tk->relief = TKraised; 664 tk->flag |= Tknograb; 665 tk->borderwidth = 1; 666 667 tko[0].ptr = tk; 668 tko[0].optab = tkgeneric; 669 tko[1].ptr = tkw; 670 tko[1].optab = menuopt; 671 tko[2].ptr = nil; 672 673 names = nil; 674 e = tkparse(t, arg, tko, &names); 675 if(e != nil) { 676 tkfreeobj(tk); 677 return e; 678 } 679 680 e = tkaddchild(t, tk, &names); 681 tkfreename(names); 682 if(e != nil) { 683 tkfreeobj(tk); 684 return e; 685 } 686 tk->name->link = nil; 687 688 tk->flag |= Tkwindow; 689 tk->geom = tkmoveresize; 690 691 tkw->next = t->windows; 692 t->windows = tk; 693 694 return tkvalue(ret, "%s", tk->name->name); 695 } 696 697 static void 698 freemenu(Tk *top) 699 { 700 Tk *tk, *f, *nexttk, *nextf; 701 TkWin *tkw; 702 703 tkunmapmenu(top); 704 tkw = TKobj(TkWin, top); 705 for(tk = tkw->slave; tk; tk = nexttk) { 706 nexttk = tk->next; 707 for(f = tk->slave; f; f = nextf) { 708 nextf = f->next; 709 tkfreeobj(f); 710 } 711 tkfreeobj(tk); 712 } 713 top->slave = nil; 714 tkfreeframe(top); 715 } 716 717 static 718 TkOption mopt[] = 719 { 720 "menu", OPTtext, O(TkLabel, menu), nil, 721 nil, 722 }; 723 724 static void 725 tkbuildmopt(TkOptab *tko, int n, Tk *tk) 726 { 727 memset(tko, 0, n*sizeof(TkOptab)); 728 729 n = 0; 730 tko[n].ptr = tk; 731 tko[n++].optab = tkgeneric; 732 733 switch(tk->type) { 734 case TKcascade: 735 tko[n].ptr = TKobj(TkLabel, tk); 736 tko[n++].optab = mopt; 737 goto norm; 738 case TKradiobutton: 739 tko[n].ptr = TKobj(TkLabel, tk); 740 tko[n++].optab = tkradopts; 741 goto norm; 742 case TKcheckbutton: 743 tko[n].ptr = TKobj(TkLabel, tk); 744 tko[n++].optab = tkcbopts; 745 /* fall through */ 746 case TKlabel: 747 norm: 748 tko[n].ptr = TKobj(TkLabel, tk); 749 tko[n].optab = tkbutopts; 750 break; 751 } 752 } 753 754 static char* 755 tkmenuentryconf(Tk *menu, Tk *tk, char *arg) 756 { 757 char *e; 758 TkOptab tko[4]; 759 760 USED(menu); 761 762 tkbuildmopt(tko, nelem(tko), tk); 763 e = tkparse(tk->env->top, arg, tko, nil); 764 switch (tk->type) { 765 case TKlabel: 766 case TKcascade: 767 tksizelabel(tk); 768 break; 769 case TKradiobutton: 770 case TKcheckbutton: 771 tksizebutton(tk); 772 } 773 774 return e; 775 } 776 777 static void 778 layout(Tk *menu) 779 { 780 TkWin *tkw; 781 Tk *tk; 782 int m, w, y, maxmargin, maxw; 783 784 y = 0; 785 maxmargin = 0; 786 maxw = 0; 787 788 tkw = TKobj(TkWin, menu); 789 790 /* determine padding for item text alignment */ 791 for (tk = tkw->slave; tk != nil; tk = tk->next) { 792 m = tkbuttonmargin(tk); /* TO DO: relies on buttonmargin defaulting to labelmargin */ 793 tk->act.x = m; /* temp store */ 794 if (m > maxmargin) 795 maxmargin = m; 796 } 797 /* set x pos and determine max width */ 798 for (tk = tkw->slave; tk != nil; tk = tk->next) { 799 tk->act.x = tk->borderwidth + maxmargin - tk->act.x; 800 tk->act.y = y + tk->borderwidth; 801 tk->act.height = tk->req.height; 802 tk->act.width = tk->req.width; 803 y += tk->act.height+2*tk->borderwidth; 804 w = tk->act.x + tk->req.width + 2* tk->borderwidth; 805 if (w > maxw) 806 maxw = w; 807 } 808 /* expand separators and cascades and mark all as dirty */ 809 for (tk = tkw->slave; tk != nil; tk = tk->next) { 810 switch (tk->type) { 811 case TKseparator: 812 tk->act.x = tk->borderwidth; 813 /*FALLTHRU*/ 814 case TKcascade: 815 tk->act.width = (maxw - tk->act.x) - tk->borderwidth; 816 } 817 tk->dirty = tkrect(tk, 1); 818 } 819 menu->dirty = tkrect(menu, 1); 820 tkmoveresize(menu, 0, 0, maxw, y); 821 } 822 823 static void 824 menuitemgeom(Tk *sub, int x, int y, int w, int h) 825 { 826 if (sub->parent == nil) 827 return; 828 if(w < 0) 829 w = 0; 830 if(h < 0) 831 h = 0; 832 sub->req.x = x; 833 sub->req.y = y; 834 sub->req.width = w; 835 sub->req.height = h; 836 layout(sub->parent); 837 } 838 839 static void 840 appenditem(Tk *menu, Tk *item, int where) 841 { 842 TkWin *tkw; 843 Tk *f, **l; 844 845 tkw = TKobj(TkWin, menu); 846 l = &tkw->slave; 847 for (f = *l; f != nil; f = f->next) { 848 if (where-- == 0) 849 break; 850 l = &f->next; 851 } 852 *l = item; 853 item->next = f; 854 item->parent = menu; 855 item->geom = menuitemgeom; 856 } 857 858 static char* 859 menuadd(Tk *menu, char *arg, int where) 860 { 861 Tk *tkc; 862 int configure; 863 char *e; 864 TkTop *t; 865 TkLabel *tkl; 866 char buf[Tkmaxitem]; 867 868 t = menu->env->top; 869 arg = tkword(t, arg, buf, buf+sizeof(buf), nil); 870 configure = 1; 871 e = nil; 872 873 if(strcmp(buf, "checkbutton") == 0) 874 tkc = tkmkbutton(t, TKcheckbutton); 875 else if(strcmp(buf, "radiobutton") == 0) 876 tkc = tkmkbutton(t, TKradiobutton); 877 else if(strcmp(buf, "command") == 0) 878 tkc = tknewobj(t, TKlabel, sizeof(Tk)+sizeof(TkLabel)); 879 else if(strcmp(buf, "cascade") == 0) 880 tkc = tknewobj(t, TKcascade, sizeof(Tk)+sizeof(TkLabel)); 881 else if(strcmp(buf, "separator") == 0) { 882 tkc = tknewobj(t, TKseparator, sizeof(Tk)); /* it's really a frame */ 883 if (tkc != nil) { 884 tkc->flag = Tkfillx|Tktop; 885 tkc->req.height = Sepheight; 886 configure = 0; 887 } 888 } 889 else 890 return TkBadvl; 891 892 if (tkc == nil) 893 e = TkNomem; 894 895 if (e == nil) { 896 if(tkc->env == t->env && menu->env != t->env) { 897 tkputenv(tkc->env); 898 tkc->env = menu->env; 899 tkc->env->ref++; 900 } 901 if (configure) { 902 tkc->flag = Tkwest|Tkfillx|Tktop; 903 tkc->highlightwidth = 0; 904 tkc->borderwidth = 1; 905 tkc->relief = TKflat; 906 tkl = TKobj(TkLabel, tkc); 907 tkl->anchor = Tkwest; 908 tkl->ul = -1; 909 tkl->justify = Tkleft; 910 e = tkmenuentryconf(menu, tkc, arg); 911 } 912 } 913 914 if(e != nil) { 915 if (tkc != nil) 916 tkfreeobj(tkc); 917 return e; 918 } 919 920 appenditem(menu, tkc, where); 921 layout(menu); 922 return nil; 923 } 924 925 static int 926 tkmindex(Tk *tk, char *p) 927 { 928 TkWin *tkw; 929 int y, n; 930 931 if(*p >= '0' && *p <= '9') 932 return atoi(p); 933 934 tkw = TKobj(TkWin, tk); 935 n = 0; 936 if(*p == '@') { 937 y = atoi(p+1); 938 for(tk = tkw->slave; tk; tk = tk->next) { 939 if(y >= tk->act.y && y < tk->act.y+tk->act.height+2*tk->borderwidth ) 940 return n; 941 n++; 942 } 943 } 944 if(strcmp(p, "end") == 0 || strcmp(p, "last") == 0) { 945 for(tk = tkw->slave; tk && tk->next; tk = tk->next) 946 n++; 947 return n; 948 } 949 if(strcmp(p, "active") == 0) { 950 for(tk = tkw->slave; tk; tk = tk->next) { 951 if(tk->flag & Tkactive) 952 return n; 953 n++; 954 } 955 return -2; 956 } 957 if(strcmp(p, "none") == 0) 958 return -2; 959 960 return -1; 961 } 962 963 static int 964 tkmenudel(Tk *tk, int y) 965 { 966 TkWin *tkw; 967 Tk *f, **l, *next; 968 969 tkw = TKobj(TkWin, tk); 970 l = &tkw->slave; 971 for(tk = *l; tk; tk = tk->next) { 972 if(y-- == 0) { 973 *l = tk->next; 974 for(f = tk->slave; f; f = next) { 975 next = f->next; 976 tkfreeobj(f); 977 } 978 tkfreeobj(tk); 979 return 1; 980 } 981 l = &tk->next; 982 } 983 return 0; 984 } 985 986 static char* 987 tkmpost(Tk *tk, int x, int y, int cascade, int bh, int adjust) 988 { 989 char *e; 990 TkWin *w; 991 TkTop *t; 992 Rectangle *dr; 993 994 t = tk->env->top; 995 if(adjust){ 996 dr = &t->screenr; 997 if(x+tk->act.width > dr->max.x) 998 x = dr->max.x - tk->act.width; 999 if(x < 0) 1000 x = 0; 1001 if(y+bh+tk->act.height > dr->max.y) 1002 y -= tk->act.height + 2* tk->borderwidth; 1003 else 1004 y += bh; 1005 if(y < 0) 1006 y = 0; 1007 } 1008 menuclr(tk); 1009 tkmovewin(tk, Pt(x, y)); 1010 1011 /* stop possible postcommand recursion */ 1012 if (tk->flag & Tkmapped) 1013 return nil; 1014 1015 w = TKobj(TkWin, tk); 1016 if(w->postcmd != nil) { 1017 e = tkexec(tk->env->top, w->postcmd, nil); 1018 if(e != nil) { 1019 print("%s: postcommand: %s: %s\n", tkname(tk), w->postcmd, e); 1020 return e; 1021 } 1022 } 1023 if (!cascade) 1024 tkunmapmenus(t, nil); 1025 1026 e = tkmap(tk); 1027 if(e != nil) 1028 return e; 1029 1030 if (t->ctxt->tkmenu != nil) 1031 w->cascade = strdup(t->ctxt->tkmenu->name->name); 1032 t->ctxt->tkmenu = tk; 1033 tksetmgrab(t, tk); 1034 1035 /* Make sure slaves are redrawn */ 1036 return tkupdate(tk->env->top); 1037 } 1038 1039 static Tk* 1040 tkmenuindex2ptr(Tk *tk, char **arg) 1041 { 1042 TkWin *tkw; 1043 int index; 1044 char *buf; 1045 1046 buf = mallocz(Tkmaxitem, 0); 1047 if(buf == nil) 1048 return nil; 1049 *arg = tkword(tk->env->top, *arg, buf, buf+Tkmaxitem, nil); 1050 index = tkmindex(tk, buf); 1051 free(buf); 1052 if(index < 0) 1053 return nil; 1054 1055 tkw = TKobj(TkWin, tk); 1056 for(tk = tkw->slave; tk && index; tk = tk->next) 1057 index--; 1058 1059 if(tk == nil) 1060 return nil; 1061 1062 return tk; 1063 } 1064 1065 static char* 1066 tkmenuentrycget(Tk *tk, char *arg, char **val) 1067 { 1068 Tk *etk; 1069 TkOptab tko[4]; 1070 1071 etk = tkmenuindex2ptr(tk, &arg); 1072 if(etk == nil) 1073 return TkBadix; 1074 1075 tkbuildmopt(tko, nelem(tko), etk); 1076 return tkgencget(tko, arg, val, tk->env->top); 1077 } 1078 1079 static char* 1080 tkmenucget(Tk *tk, char *arg, char **val) 1081 { 1082 TkWin *tkw; 1083 TkOptab tko[4]; 1084 1085 tkw = TKobj(TkWin, tk); 1086 tko[0].ptr = tk; 1087 tko[0].optab = tkgeneric; 1088 tko[1].ptr = tkw; 1089 tko[1].optab = tktop; 1090 tko[2].ptr = tkw; 1091 tko[2].optab = menuopt; 1092 tko[3].ptr = nil; 1093 1094 return tkgencget(tko, arg, val, tk->env->top); 1095 } 1096 1097 static char* 1098 tkmenuconf(Tk *tk, char *arg, char **val) 1099 { 1100 char *e; 1101 TkGeom g; 1102 int bd; 1103 TkWin *tkw; 1104 TkOptab tko[3]; 1105 1106 tkw = TKobj(TkWin, tk); 1107 tko[0].ptr = tk; 1108 tko[0].optab = tkgeneric; 1109 tko[1].ptr = tkw; 1110 tko[1].optab = menuopt; 1111 tko[2].ptr = nil; 1112 1113 if(*arg == '\0') 1114 return tkconflist(tko, val); 1115 1116 g = tk->req; 1117 bd = tk->borderwidth; 1118 e = tkparse(tk->env->top, arg, tko, nil); 1119 tkgeomchg(tk, &g, bd); 1120 tk->dirty = tkrect(tk, 1); 1121 return e; 1122 } 1123 1124 static char* 1125 tkmenuadd(Tk *tk, char *arg, char **val) 1126 { 1127 USED(val); 1128 return menuadd(tk, arg, -1); 1129 } 1130 1131 static char* 1132 tkmenuinsert(Tk *tk, char *arg, char **val) 1133 { 1134 int index; 1135 char *buf; 1136 1137 USED(val); 1138 buf = mallocz(Tkmaxitem, 0); 1139 if(buf == nil) 1140 return TkNomem; 1141 arg = tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil); 1142 index = tkmindex(tk, buf); 1143 free(buf); 1144 if (index < 0) 1145 return TkBadix; 1146 return menuadd(tk, arg, index); 1147 } 1148 1149 static void 1150 menuitemdirty(Tk *item) 1151 { 1152 Tk *menu; 1153 Rectangle r; 1154 1155 menu = item->parent; 1156 if (menu == nil) 1157 return; 1158 item->dirty = tkrect(item, 1); 1159 r = rectaddpt(item->dirty, Pt(item->act.x, item->act.y)); 1160 combinerect(&menu->dirty, r); 1161 } 1162 1163 static void 1164 menuclr(Tk *tk) 1165 { 1166 TkWin *tkw; 1167 Tk *f; 1168 tkw = TKobj(TkWin, tk); 1169 for(f = tkw->slave; f; f = f->next) { 1170 if(f->flag & Tkactive) { 1171 f->flag &= ~Tkactive; 1172 menuitemdirty(f); 1173 } 1174 } 1175 } 1176 1177 static char* 1178 tkpostcascade(Tk *parent, Tk *tk, int toggle) 1179 { 1180 Tk *tkm; 1181 TkWin *tkw; 1182 Point g; 1183 TkTop *t; 1184 TkLabel *tkl; 1185 char *e; 1186 1187 if(tk->flag & Tkdisabled) 1188 return nil; 1189 1190 tkl = TKobj(TkLabel, tk); 1191 t = tk->env->top; 1192 tkm = tklook(t, tkl->menu, 0); 1193 if(tkm == nil || tkm->type != TKmenu) 1194 return TkBadwp; 1195 1196 if((tkm->flag & Tkmapped)) { 1197 if (toggle) { 1198 tkunmapmenus(t, parent); 1199 return nil; 1200 } else { 1201 /* check that it is immediate cascade */ 1202 tkw = TKobj(TkWin, t->ctxt->tkmenu); 1203 if (strcmp(tkw->cascade, parent->name->name) == 0) 1204 return nil; 1205 } 1206 } 1207 1208 tkunmapmenus(t, parent); 1209 1210 tkl = TKobj(TkLabel, tk); 1211 if(tkl->command != nil) { 1212 e = tkexec(t, tkl->command, nil); 1213 if (e != nil) 1214 return e; 1215 } 1216 1217 g = tkposn(tk); 1218 g.x += tk->act.width; 1219 g.y -= tkm->borderwidth; 1220 e = tkmpost(tkm, g.x, g.y, 1, 0, 1); 1221 return e; 1222 } 1223 1224 static void 1225 activateitem(Tk *item) 1226 { 1227 Tk *menu; 1228 if (item == nil || (menu = item->parent) == nil) 1229 return; 1230 menuclr(menu); 1231 if (!(item->flag & Tkdisabled)) { 1232 item->flag |= Tkactive; 1233 menuitemdirty(item); 1234 } 1235 } 1236 1237 static char* 1238 tkmenuactivate(Tk *tk, char *arg, char **val) 1239 { 1240 Tk *f; 1241 TkWin *tkw; 1242 int index; 1243 char *buf; 1244 1245 USED(val); 1246 buf = mallocz(Tkmaxitem, 0); 1247 if(buf == nil) 1248 return TkNomem; 1249 tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil); 1250 index = tkmindex(tk, buf); 1251 free(buf); 1252 if (index == -1) 1253 return TkBadix; 1254 if (index == -2) { 1255 menuclr(tk); 1256 return nil; 1257 } 1258 1259 tkw = TKobj(TkWin, tk); 1260 for(f = tkw->slave; f; f = f->next) 1261 if(index-- == 0) 1262 break; 1263 1264 if(f == nil || f->flag & Tkdisabled) { 1265 menuclr(tk); 1266 return nil; 1267 } 1268 if(f->flag & Tkactive) 1269 return nil; 1270 1271 activateitem(f); 1272 return nil; 1273 } 1274 1275 static int 1276 iteminvoke(Tk *tk, Tk *tki, char *arg) 1277 { 1278 int unmap = 0; 1279 menuitemdirty(tki); 1280 switch(tki->type) { 1281 case TKlabel: 1282 unmap = 1; 1283 case TKcheckbutton: 1284 case TKradiobutton: 1285 tkbuttoninvoke(tki, arg, nil); 1286 break; 1287 case TKcascade: 1288 tkpostcascade(tk, tki, 0); 1289 break; 1290 } 1291 return unmap; 1292 } 1293 1294 static char* 1295 tkmenuinvoke(Tk *tk, char *arg, char **val) 1296 { 1297 Tk *tki; 1298 USED(val); 1299 tki = tkmenuindex2ptr(tk, &arg); 1300 if(tki == nil) 1301 return nil; 1302 iteminvoke(tk, tki, arg); 1303 return nil; 1304 } 1305 1306 static char* 1307 tkmenudelete(Tk *tk, char *arg, char **val) 1308 { 1309 int index1, index2; 1310 char *buf; 1311 1312 USED(val); 1313 buf = mallocz(Tkmaxitem, 0); 1314 if(buf == nil) 1315 return TkNomem; 1316 arg = tkitem(buf, arg); 1317 index1 = tkmindex(tk, buf); 1318 if(index1 < 0) { 1319 free(buf); 1320 return TkBadix; 1321 } 1322 index2 = index1; 1323 if(*arg != '\0') { 1324 tkitem(buf, arg); 1325 index2 = tkmindex(tk, buf); 1326 } 1327 free(buf); 1328 if(index2 < 0) 1329 return TkBadix; 1330 while(index2 >= index1 && tkmenudel(tk, index2)) 1331 index2--; 1332 1333 layout(tk); 1334 return nil; 1335 } 1336 1337 static char* 1338 tkmenupost(Tk *tk, char *arg, char **val) 1339 { 1340 int x, y; 1341 TkTop *t; 1342 char *buf; 1343 1344 USED(val); 1345 1346 buf = mallocz(Tkmaxitem, 0); 1347 if(buf == nil) 1348 return TkNomem; 1349 t = tk->env->top; 1350 arg = tkword(t, arg, buf, buf+Tkmaxitem, nil); 1351 if(buf[0] == '\0') { 1352 free(buf); 1353 return TkBadvl; 1354 } 1355 x = atoi(buf); 1356 tkword(t, arg, buf, buf+Tkmaxitem, nil); 1357 if(buf[0] == '\0') { 1358 free(buf); 1359 return TkBadvl; 1360 } 1361 y = atoi(buf); 1362 free(buf); 1363 1364 return tkmpost(tk, x, y, 0, 0, 1); 1365 } 1366 1367 static char* 1368 tkmenuunpost(Tk *tk, char *arg, char **val) 1369 { 1370 USED(arg); 1371 USED(val); 1372 tkunmapmenu(tk); 1373 return nil; 1374 } 1375 1376 static char* 1377 tkmenuindex(Tk *tk, char *arg, char **val) 1378 { 1379 char *buf; 1380 int index; 1381 1382 buf = mallocz(Tkmaxitem, 0); 1383 if(buf == nil) 1384 return TkNomem; 1385 tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil); 1386 index = tkmindex(tk, buf); 1387 free(buf); 1388 if (index == -1) 1389 return TkBadix; 1390 if (index == -2) 1391 return "none"; 1392 return tkvalue(val, "%d", index); 1393 } 1394 1395 static char* 1396 tkmenuyposn(Tk *tk, char *arg, char **val) 1397 { 1398 tk = tkmenuindex2ptr(tk, &arg); 1399 if(tk == nil) 1400 return TkBadix; 1401 return tkvalue(val, "%d", tk->act.y); 1402 } 1403 1404 static char* 1405 tkmenupostcascade(Tk *tk, char *arg, char **val) 1406 { 1407 Tk *tki; 1408 USED(val); 1409 tki = tkmenuindex2ptr(tk, &arg); 1410 if(tki == nil || tki->type != TKcascade) 1411 return nil; 1412 1413 return tkpostcascade(tk, tki, 0); 1414 } 1415 1416 static char* 1417 tkmenutype(Tk *tk, char *arg, char **val) 1418 { 1419 tk = tkmenuindex2ptr(tk, &arg); 1420 if(tk == nil) 1421 return TkBadix; 1422 1423 return tkvalue(val, tk->type == TKlabel ? "command" : tkmethod[tk->type]->name); 1424 } 1425 1426 static char* 1427 tkmenususpend(Tk *tk, char *arg, char **val) 1428 { 1429 USED(arg); 1430 USED(val); 1431 if(tk->type == TKchoicebutton){ 1432 tk = tkfindchoicemenu(tk); 1433 if(tk == nil) 1434 return TkNotwm; 1435 } 1436 tk->flag |= Tksuspended; 1437 return nil; 1438 } 1439 1440 static char* 1441 tkmenuentryconfig(Tk *tk, char *arg, char **val) 1442 { 1443 Tk *etk; 1444 char *e; 1445 1446 USED(val); 1447 etk = tkmenuindex2ptr(tk, &arg); 1448 if(etk == nil) 1449 return TkBadix; 1450 1451 e = tkmenuentryconf(tk, etk, arg); 1452 layout(tk); 1453 return e; 1454 } 1455 1456 static Tk* 1457 xymenuitem(Tk *tk, int x, int y) 1458 { 1459 TkWin *tkw = TKobj(TkWin, tk); 1460 x -= tkw->act.x; 1461 y -= tkw->act.y; 1462 1463 x -= tk->borderwidth; 1464 y -= tk->act.y + tk->borderwidth; 1465 if (x < tk->act.x || x > tk->act.x+tk->act.width) 1466 return nil; 1467 for(tk = tkw->slave; tk; tk = tk->next) { 1468 if(y >= tk->act.y && y < tk->act.y+tk->act.height+2*tk->borderwidth) 1469 return tk; 1470 } 1471 return nil; 1472 } 1473 1474 static char * 1475 menukey(Tk *tk, int key) 1476 { 1477 Tk *scan, *active, *first, *last, *prev, *next; 1478 TkWin *tkw; 1479 TkTop *top; 1480 1481 top = tk->env->top; 1482 1483 active = first = last = prev = next = nil; 1484 tkw = TKobj(TkWin, tk); 1485 for(scan = tkw->slave; scan != nil; scan = scan->next) { 1486 if(scan->type == TKseparator) 1487 continue; 1488 if(first == nil) 1489 first = scan; 1490 if (active != nil && next == nil) 1491 next = scan; 1492 if(active == nil && scan->flag & Tkactive) 1493 active = scan; 1494 if (active == nil) 1495 prev = scan; 1496 last = scan; 1497 } 1498 if (next == nil) 1499 next = first; 1500 if (prev == nil) 1501 prev = last; 1502 1503 switch (key) { 1504 case Esc: 1505 tkunmapmenus(top, nil); 1506 break; 1507 case Left: 1508 if (tkw->cascade != nil) 1509 tkunmapmenu(tk); 1510 break; 1511 case Right: 1512 if (active == nil || active->type != TKcascade) 1513 break; 1514 case ' ': 1515 case '\n': 1516 if (active != nil) { 1517 if (iteminvoke(tk, active, nil)) 1518 tkunmapmenus(top, nil); 1519 } 1520 break; 1521 case Up: 1522 next = prev; 1523 case Down: 1524 if (next != nil) 1525 activateitem(next); 1526 } 1527 return nil; 1528 } 1529 1530 static char* 1531 drawmenu(Tk *tk, Point orig) 1532 { 1533 Image *dst; 1534 TkWin *tkw; 1535 Tk *sub; 1536 Point p, bd; 1537 int bg; 1538 Rectangle mainr, clientr, subr; 1539 1540 tkw = TKobj(TkWin, tk); 1541 dst = tkimageof(tk); 1542 1543 bd = Pt(tk->borderwidth, tk->borderwidth); 1544 mainr.min = addpt(orig, Pt(tk->act.x, tk->act.y)); 1545 clientr.min = addpt(mainr.min, bd); 1546 clientr.max = addpt(clientr.min, Pt(tk->act.width, tk->act.height)); 1547 mainr.max = addpt(clientr.max, bd); 1548 1549 /* 1550 * note that we draw item background to get full menu width 1551 * active indicator, this means we must dirty the entire 1552 * item rectangle to ensure it is fully redrawn 1553 */ 1554 p = clientr.min; 1555 subr = clientr; 1556 for (sub = tkw->slave; sub != nil; sub = sub->next) { 1557 if (Dx(sub->dirty) == 0) 1558 continue; 1559 subr.min.y = p.y + sub->act.y - sub->borderwidth; 1560 subr.max.y = p.y + sub->act.y + sub->act.height + sub->borderwidth; 1561 bg = TkCbackgnd; 1562 if (sub->flag & Tkactive) 1563 bg = TkCactivebgnd; 1564 draw(dst, subr, tkgc(sub->env, bg), nil, ZP); 1565 sub->dirty = tkrect(sub, 1); 1566 sub->flag |= Tkrefresh; 1567 tkmethod[sub->type]->draw(sub, p); 1568 sub->dirty = bbnil; 1569 sub->flag &= ~Tkrefresh; 1570 } 1571 /* todo: dirty check */ 1572 tkdrawrelief(dst, tk, mainr.min, TkCbackgnd, tk->relief); 1573 return nil; 1574 } 1575 1576 static void 1577 menudirty(Tk *sub) 1578 { 1579 menuitemdirty(sub); 1580 } 1581 1582 static Point 1583 menurelpos(Tk *sub) 1584 { 1585 return Pt(sub->act.x-sub->borderwidth, sub->act.y-sub->borderwidth); 1586 } 1587 1588 static void 1589 autoscroll(Tk *tk, void *v, int cancelled) 1590 { 1591 TkWin *tkw; 1592 Rectangle r, dr; 1593 Point delta, od; 1594 TkMouse *m; 1595 Tk *item; 1596 USED(v); 1597 1598 tkw = TKobj(TkWin, tk); 1599 if (cancelled) { 1600 tkw->speed = 0; 1601 return; 1602 } 1603 if(!eqpt(tkw->act, tkw->req)){ 1604 print("not autoscrolling, act: %P, req: %P\n", tkw->act, tkw->req); 1605 return; 1606 } 1607 dr = tk->env->top->screenr; 1608 delta.x = TKF2I(tkw->delta.x * tkw->speed); 1609 delta.y = TKF2I(tkw->delta.y * tkw->speed); 1610 r = rectaddpt(tkrect(tk, 1), Pt(tk->borderwidth + tkw->act.x, tk->borderwidth + tkw->act.y)); 1611 1612 od = delta; 1613 /* make sure we don't go too far */ 1614 if (delta.x > 0 && r.min.x + delta.x > dr.min.x) 1615 delta.x = dr.min.x - r.min.x; 1616 else if (delta.x < 0 && r.max.x + delta.x < dr.max.x) 1617 delta.x = dr.max.x - r.max.x; 1618 if (delta.y > 0 && r.min.y + delta.y > dr.min.y) 1619 delta.y = dr.min.y - r.min.y; 1620 else if (delta.y < 0 && r.max.y + delta.y < dr.max.y) 1621 delta.y = dr.max.y - r.max.y; 1622 1623 m = &tk->env->top->ctxt->mstate; 1624 item = xymenuitem(tk, m->x - delta.x, m->y - delta.y); 1625 if (item == nil) 1626 menuclr(tk); 1627 else 1628 activateitem(item); 1629 tkmovewin(tk, Pt(tkw->req.x + delta.x, tkw->req.y + delta.y)); 1630 tkupdate(tk->env->top); 1631 /* tkenterleave won't do this for us, so we have to do it ourselves */ 1632 1633 tkw->speed += tkw->speed / 3; 1634 1635 r = rectaddpt(tkrect(tk, 1), Pt(tk->borderwidth + tkw->act.x, tk->borderwidth + tkw->act.y)); 1636 if((delta.y > 0 && r.min.x >= dr.min.x) || (delta.x < 0 && r.max.x <= dr.max.x)) 1637 tkw->delta.x = 0; 1638 if((delta.y > 0 && r.min.y >= dr.min.y) || (delta.y < 0 && r.max.y <= dr.max.y)) 1639 tkw->delta.y = 0; 1640 if (eqpt(tkw->delta, ZP)) { 1641 tkcancelrepeat(tk); 1642 tkw->speed = 0; 1643 } 1644 } 1645 1646 static void 1647 startautoscroll(Tk *tk, TkMouse *m) 1648 { 1649 Rectangle dr, r; 1650 Point d; 1651 TkWin *tkw; 1652 tkw = TKobj(TkWin, tk); 1653 dr = tk->env->top->screenr; 1654 r = rectaddpt(tkrect(tk, 1), Pt(tk->borderwidth + tkw->act.x, tk->borderwidth + tkw->act.y)); 1655 d = Pt(0, 0); 1656 if(m->x <= 0 && r.min.x < dr.min.x) 1657 d.x = 1; 1658 else if (m->x >= dr.max.x - 1 && r.max.x >= dr.max.x) 1659 d.x = -1; 1660 if(m->y <= 0 && r.min.y < dr.min.y) 1661 d.y = 1; 1662 else if (m->y >= dr.max.y - 1 && r.max.y >= dr.max.y) 1663 d.y = -1; 1664 //print("startautoscroll, delta %P\n", d); 1665 if (d.x == 0 && d.y == 0){ 1666 if (tkw->speed > 0){ 1667 tkcancelrepeat(tk); 1668 tkw->speed = 0; 1669 } 1670 return; 1671 } 1672 if (tkw->speed == 0) { 1673 tkw->speed = TKI2F(Dy(r)) / 100; 1674 tkrepeat(tk, autoscroll, nil, 0, TkRptinterval/2); 1675 } 1676 tkw->delta = d; 1677 } 1678 1679 static void 1680 menuevent1(Tk *tk, int event, void *a) 1681 { 1682 TkMouse *m; 1683 Tk *item; 1684 1685 if (event & TkKey) { 1686 menukey(tk, event & 0xffff); 1687 return; 1688 } 1689 1690 if (event & TkLeave) { 1691 menuclr(tk); 1692 return; 1693 } 1694 1695 if ((!(event & TkEmouse) || (event & TkTakefocus)) && !(event & TkEnter)) 1696 return; 1697 1698 m = (TkMouse*)a; 1699 1700 startautoscroll(tk, m); 1701 1702 item = xymenuitem(tk, m->x, m->y); 1703 if (item == nil) 1704 menuclr(tk); 1705 else 1706 activateitem(item); 1707 if ((event & (TkMotion|TkEnter)) && item == nil) 1708 return; 1709 if (event & TkEpress) { 1710 if (item == nil) { 1711 tkunmapmenus(tk->env->top, nil); 1712 return; 1713 } 1714 if (item->type == TKcascade) 1715 tkpostcascade(tk, item, !(event & TkMotion)); 1716 else 1717 tkunmapmenus(tk->env->top, tk); 1718 return; 1719 } 1720 if ((event & TkErelease) && m->b == 0) { 1721 if (item != nil) { 1722 if (item->type == TKcascade) 1723 return; 1724 if (!iteminvoke(tk, item, nil)) 1725 return; 1726 } 1727 tkunmapmenus(tk->env->top, nil); 1728 } 1729 } 1730 1731 static Tk* 1732 menuevent(Tk *tk, int event, void *a) 1733 { 1734 menuevent1(tk, event, a); 1735 tksubdeliver(tk, tk->binds, event, a, 0); 1736 return nil; 1737 } 1738 1739 static 1740 TkCmdtab menucmd[] = 1741 { 1742 "activate", tkmenuactivate, 1743 "add", tkmenuadd, 1744 "cget", tkmenucget, 1745 "configure", tkmenuconf, 1746 "delete", tkmenudelete, 1747 "entryconfigure", tkmenuentryconfig, 1748 "entrycget", tkmenuentrycget, 1749 "index", tkmenuindex, 1750 "insert", tkmenuinsert, 1751 "invoke", tkmenuinvoke, 1752 "post", tkmenupost, 1753 "postcascade", tkmenupostcascade, 1754 "type", tkmenutype, 1755 "unpost", tkmenuunpost, 1756 "yposition", tkmenuyposn, 1757 "suspend", tkmenususpend, 1758 nil 1759 }; 1760 1761 static 1762 TkCmdtab menubutcmd[] = 1763 { 1764 "cget", tkmenubutcget, 1765 "configure", tkmenubutconf, 1766 "tkMBenter", tkMBenter, 1767 "tkMBleave", tkMBleave, 1768 "tkMBpress", tkMBpress, 1769 "tkMBkey", tkMBkey, 1770 nil 1771 }; 1772 1773 static 1774 TkCmdtab choicebutcmd[] = 1775 { 1776 "cget", tkmenubutcget, 1777 "configure", tkmenubutconf, 1778 "set", tkchoicebutset, 1779 "get", tkchoicebutget, 1780 "setvalue", tkchoicebutsetvalue, 1781 "getvalue", tkchoicebutgetvalue, 1782 "invoke", tkchoicebutinvoke, 1783 "valuecount", tkchoicebutvaluecount, 1784 "tkMBenter", tkMBenter, 1785 "tkMBleave", tkMBleave, 1786 "tkMBpress", tkMBpress, 1787 "tkMBkey", tkMBkey, 1788 "suspend", tkmenususpend, 1789 nil 1790 }; 1791 1792 TkMethod menumethod = { 1793 "menu", 1794 menucmd, 1795 freemenu, 1796 drawmenu, 1797 nil, 1798 nil, 1799 nil, 1800 menudirty, 1801 menurelpos, 1802 menuevent 1803 }; 1804 1805 TkMethod menubuttonmethod = { 1806 "menubutton", 1807 menubutcmd, 1808 tkfreelabel, 1809 tkdrawlabel 1810 }; 1811 1812 TkMethod choicebuttonmethod = { 1813 "choicebutton", 1814 choicebutcmd, 1815 tkfreelabel, 1816 tkdrawlabel, 1817 nil, 1818 nil, 1819 nil, 1820 nil, 1821 nil, 1822 nil, 1823 nil, 1824 nil, 1825 tkchoicevarchanged 1826 }; 1827 1828 TkMethod separatormethod = { 1829 "separator", 1830 nil, 1831 tkfreeframe, 1832 tkdrawframe 1833 }; 1834 1835 TkMethod cascademethod = { 1836 "cascade", 1837 nil, 1838 tkfreelabel, 1839 tkdrawlabel 1840 }; 1841