1/* 2 * ttkLayout.c -- 3 * 4 * Generic layout processing. 5 * 6 * Copyright (c) 2003 Joe English. Freely redistributable. 7 * 8 * $Id$ 9 */ 10 11#include <string.h> 12#include <tk.h> 13#include "ttkThemeInt.h" 14 15#define MAX(a,b) (a > b ? a : b) 16#define MIN(a,b) (a < b ? a : b) 17 18/*------------------------------------------------------------------------ 19 * +++ Ttk_Box and Ttk_Padding utilities: 20 */ 21 22Ttk_Box 23Ttk_MakeBox(int x, int y, int width, int height) 24{ 25 Ttk_Box b; 26 b.x = x; b.y = y; b.width = width; b.height = height; 27 return b; 28} 29 30int 31Ttk_BoxContains(Ttk_Box box, int x, int y) 32{ 33 return box.x <= x && x < box.x + box.width 34 && box.y <= y && y < box.y + box.height; 35} 36 37Tcl_Obj * 38Ttk_NewBoxObj(Ttk_Box box) 39{ 40 Tcl_Obj *result[4]; 41 42 result[0] = Tcl_NewIntObj(box.x); 43 result[1] = Tcl_NewIntObj(box.y); 44 result[2] = Tcl_NewIntObj(box.width); 45 result[3] = Tcl_NewIntObj(box.height); 46 47 return Tcl_NewListObj(4, result); 48} 49 50/* 51 * packTop, packBottom, packLeft, packRight -- 52 * Carve out a parcel of the specified height (resp width) 53 * from the specified cavity. 54 * 55 * Returns: 56 * The new parcel. 57 * 58 * Side effects: 59 * Adjust the cavity. 60 */ 61 62static Ttk_Box packTop(Ttk_Box *cavity, int height) 63{ 64 Ttk_Box parcel; 65 height = MIN(height, cavity->height); 66 parcel = Ttk_MakeBox(cavity->x, cavity->y, cavity->width, height); 67 cavity->y += height; 68 cavity->height -= height; 69 return parcel; 70} 71 72static Ttk_Box packBottom(Ttk_Box *cavity, int height) 73{ 74 height = MIN(height, cavity->height); 75 cavity->height -= height; 76 return Ttk_MakeBox( 77 cavity->x, cavity->y + cavity->height, 78 cavity->width, height); 79} 80 81static Ttk_Box packLeft(Ttk_Box *cavity, int width) 82{ 83 Ttk_Box parcel; 84 width = MIN(width, cavity->width); 85 parcel = Ttk_MakeBox(cavity->x, cavity->y, width,cavity->height); 86 cavity->x += width; 87 cavity->width -= width; 88 return parcel; 89} 90 91static Ttk_Box packRight(Ttk_Box *cavity, int width) 92{ 93 width = MIN(width, cavity->width); 94 cavity->width -= width; 95 return Ttk_MakeBox(cavity->x + cavity->width, 96 cavity->y, width, cavity->height); 97} 98 99/* 100 * Ttk_PackBox -- 101 * Carve out a parcel of the specified size on the specified side 102 * in the specified cavity. 103 * 104 * Returns: 105 * The new parcel. 106 * 107 * Side effects: 108 * Adjust the cavity. 109 */ 110 111Ttk_Box Ttk_PackBox(Ttk_Box *cavity, int width, int height, Ttk_Side side) 112{ 113 switch (side) { 114 default: 115 case TTK_SIDE_TOP: return packTop(cavity, height); 116 case TTK_SIDE_BOTTOM: return packBottom(cavity, height); 117 case TTK_SIDE_LEFT: return packLeft(cavity, width); 118 case TTK_SIDE_RIGHT: return packRight(cavity, width); 119 } 120} 121 122/* 123 * Ttk_PadBox -- 124 * Shrink a box by the specified padding amount. 125 */ 126Ttk_Box Ttk_PadBox(Ttk_Box b, Ttk_Padding p) 127{ 128 b.x += p.left; 129 b.y += p.top; 130 b.width -= (p.left + p.right); 131 b.height -= (p.top + p.bottom); 132 if (b.width <= 0) b.width = 1; 133 if (b.height <= 0) b.height = 1; 134 return b; 135} 136 137/* 138 * Ttk_ExpandBox -- 139 * Grow a box by the specified padding amount. 140 */ 141Ttk_Box Ttk_ExpandBox(Ttk_Box b, Ttk_Padding p) 142{ 143 b.x -= p.left; 144 b.y -= p.top; 145 b.width += (p.left + p.right); 146 b.height += (p.top + p.bottom); 147 return b; 148} 149 150/* 151 * Ttk_StickBox -- 152 * Place a box of size w * h in the specified parcel, 153 * according to the specified sticky bits. 154 */ 155Ttk_Box Ttk_StickBox(Ttk_Box parcel, int width, int height, unsigned sticky) 156{ 157 int dx, dy; 158 159 if (width > parcel.width) width = parcel.width; 160 if (height > parcel.height) height = parcel.height; 161 162 dx = parcel.width - width; 163 dy = parcel.height - height; 164 165 /* 166 * X coordinate adjustment: 167 */ 168 switch (sticky & (TTK_STICK_W | TTK_STICK_E)) 169 { 170 case TTK_STICK_W | TTK_STICK_E: 171 /* no-op -- use entire parcel width */ 172 break; 173 case TTK_STICK_W: 174 parcel.width = width; 175 break; 176 case TTK_STICK_E: 177 parcel.x += dx; 178 parcel.width = width; 179 break; 180 default : 181 parcel.x += dx / 2; 182 parcel.width = width; 183 break; 184 } 185 186 /* 187 * Y coordinate adjustment: 188 */ 189 switch (sticky & (TTK_STICK_N | TTK_STICK_S)) 190 { 191 case TTK_STICK_N | TTK_STICK_S: 192 /* use entire parcel height */ 193 break; 194 case TTK_STICK_N: 195 parcel.height = height; 196 break; 197 case TTK_STICK_S: 198 parcel.y += dy; 199 parcel.height = height; 200 break; 201 default : 202 parcel.y += dy / 2; 203 parcel.height = height; 204 break; 205 } 206 207 return parcel; 208} 209 210/* 211 * AnchorToSticky -- 212 * Convert a Tk_Anchor enum to a TTK_STICKY bitmask. 213 */ 214static Ttk_Sticky AnchorToSticky(Tk_Anchor anchor) 215{ 216 switch (anchor) 217 { 218 case TK_ANCHOR_N: return TTK_STICK_N; 219 case TK_ANCHOR_NE: return TTK_STICK_N | TTK_STICK_E; 220 case TK_ANCHOR_E: return TTK_STICK_E; 221 case TK_ANCHOR_SE: return TTK_STICK_S | TTK_STICK_E; 222 case TK_ANCHOR_S: return TTK_STICK_S; 223 case TK_ANCHOR_SW: return TTK_STICK_S | TTK_STICK_W; 224 case TK_ANCHOR_W: return TTK_STICK_W; 225 case TK_ANCHOR_NW: return TTK_STICK_N | TTK_STICK_W; 226 default: 227 case TK_ANCHOR_CENTER: return 0; 228 } 229} 230 231/* 232 * Ttk_AnchorBox -- 233 * Place a box of size w * h in the specified parcel, 234 * according to the specified anchor. 235 */ 236Ttk_Box Ttk_AnchorBox(Ttk_Box parcel, int width, int height, Tk_Anchor anchor) 237{ 238 return Ttk_StickBox(parcel, width, height, AnchorToSticky(anchor)); 239} 240 241/* 242 * Ttk_PlaceBox -- 243 * Combine Ttk_PackBox() and Ttk_StickBox(). 244 */ 245Ttk_Box Ttk_PlaceBox( 246 Ttk_Box *cavity, int width, int height, Ttk_Side side, unsigned sticky) 247{ 248 return Ttk_StickBox( 249 Ttk_PackBox(cavity, width, height, side), width, height, sticky); 250} 251 252/* 253 * Ttk_PositionBox -- 254 * Pack and stick a box according to PositionSpec flags. 255 */ 256MODULE_SCOPE Ttk_Box 257Ttk_PositionBox(Ttk_Box *cavity, int width, int height, Ttk_PositionSpec flags) 258{ 259 Ttk_Box parcel; 260 261 if (flags & TTK_EXPAND) parcel = *cavity; 262 else if (flags & TTK_PACK_TOP) parcel = packTop(cavity, height); 263 else if (flags & TTK_PACK_LEFT) parcel = packLeft(cavity, width); 264 else if (flags & TTK_PACK_BOTTOM) parcel = packBottom(cavity, height); 265 else if (flags & TTK_PACK_RIGHT) parcel = packRight(cavity, width); 266 else parcel = *cavity; 267 268 return Ttk_StickBox(parcel, width, height, flags); 269} 270 271/* 272 * TTKInitPadding -- 273 * Common factor of Ttk_GetPaddingFromObj and Ttk_GetBorderFromObj. 274 * Initializes Ttk_Padding record, supplying default values 275 * for missing entries. 276 */ 277static void TTKInitPadding(int padc, int pixels[4], Ttk_Padding *pad) 278{ 279 switch (padc) 280 { 281 case 0: pixels[0] = 0; /*FALLTHRU*/ 282 case 1: pixels[1] = pixels[0]; /*FALLTHRU*/ 283 case 2: pixels[2] = pixels[0]; /*FALLTHRU*/ 284 case 3: pixels[3] = pixels[1]; /*FALLTHRU*/ 285 } 286 287 pad->left = (short)pixels[0]; 288 pad->top = (short)pixels[1]; 289 pad->right = (short)pixels[2]; 290 pad->bottom = (short)pixels[3]; 291} 292 293/* 294 * Ttk_GetPaddingFromObj -- 295 * 296 * Extract a padding specification from a Tcl_Obj * scaled 297 * to work with a particular Tk_Window. 298 * 299 * The string representation of a Ttk_Padding is a list 300 * of one to four Tk_Pixel specifications, corresponding 301 * to the left, top, right, and bottom padding. 302 * 303 * If the 'bottom' (fourth) element is missing, it defaults to 'top'. 304 * If the 'right' (third) element is missing, it defaults to 'left'. 305 * If the 'top' (second) element is missing, it defaults to 'left'. 306 * 307 * The internal representation is a Tcl_ListObj containing 308 * one to four Tk_PixelObj objects. 309 * 310 * Returns: 311 * TCL_OK or TCL_ERROR. In the latter case an error message is 312 * left in 'interp' and '*paddingPtr' is set to all-zeros. 313 * Otherwise, *paddingPtr is filled in with the padding specification. 314 * 315 */ 316int Ttk_GetPaddingFromObj( 317 Tcl_Interp *interp, 318 Tk_Window tkwin, 319 Tcl_Obj *objPtr, 320 Ttk_Padding *pad) 321{ 322 Tcl_Obj **padv; 323 int i, padc, pixels[4]; 324 325 if (TCL_OK != Tcl_ListObjGetElements(interp, objPtr, &padc, &padv)) { 326 goto error; 327 } 328 329 if (padc > 4) { 330 if (interp) { 331 Tcl_ResetResult(interp); 332 Tcl_AppendResult(interp, "Wrong #elements in padding spec", NULL); 333 } 334 goto error; 335 } 336 337 for (i=0; i < padc; ++i) { 338 if (Tk_GetPixelsFromObj(interp, tkwin, padv[i], &pixels[i]) != TCL_OK) { 339 goto error; 340 } 341 } 342 343 TTKInitPadding(padc, pixels, pad); 344 return TCL_OK; 345 346error: 347 pad->left = pad->top = pad->right = pad->bottom = 0; 348 return TCL_ERROR; 349} 350 351/* Ttk_GetBorderFromObj -- 352 * Same as Ttk_GetPaddingFromObj, except padding is a list of integers 353 * instead of Tk_Pixel specifications. Does not require a Tk_Window 354 * parameter. 355 * 356 */ 357int Ttk_GetBorderFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Ttk_Padding *pad) 358{ 359 Tcl_Obj **padv; 360 int i, padc, pixels[4]; 361 362 if (TCL_OK != Tcl_ListObjGetElements(interp, objPtr, &padc, &padv)) { 363 goto error; 364 } 365 366 if (padc > 4) { 367 if (interp) { 368 Tcl_ResetResult(interp); 369 Tcl_AppendResult(interp, "Wrong #elements in border spec", NULL); 370 } 371 goto error; 372 } 373 374 for (i=0; i < padc; ++i) { 375 if (Tcl_GetIntFromObj(interp, padv[i], &pixels[i]) != TCL_OK) { 376 goto error; 377 } 378 } 379 380 TTKInitPadding(padc, pixels, pad); 381 return TCL_OK; 382 383error: 384 pad->left = pad->top = pad->right = pad->bottom = 0; 385 return TCL_ERROR; 386} 387 388/* 389 * Ttk_MakePadding -- 390 * Return an initialized Ttk_Padding structure. 391 */ 392Ttk_Padding Ttk_MakePadding(short left, short top, short right, short bottom) 393{ 394 Ttk_Padding pad; 395 pad.left = left; 396 pad.top = top; 397 pad.right = right; 398 pad.bottom = bottom; 399 return pad; 400} 401 402/* 403 * Ttk_UniformPadding -- 404 * Returns a uniform Ttk_Padding structure, with the same 405 * border width on all sides. 406 */ 407Ttk_Padding Ttk_UniformPadding(short borderWidth) 408{ 409 Ttk_Padding pad; 410 pad.left = pad.top = pad.right = pad.bottom = borderWidth; 411 return pad; 412} 413 414/* 415 * Ttk_AddPadding -- 416 * Combine two padding records. 417 */ 418Ttk_Padding Ttk_AddPadding(Ttk_Padding p1, Ttk_Padding p2) 419{ 420 p1.left += p2.left; 421 p1.top += p2.top; 422 p1.right += p2.right; 423 p1.bottom += p2.bottom; 424 return p1; 425} 426 427/* Ttk_RelievePadding -- 428 * Add an extra n pixels of padding according to specified relief. 429 * This may be used in element geometry procedures to simulate 430 * a "pressed-in" look for pushbuttons. 431 */ 432Ttk_Padding Ttk_RelievePadding(Ttk_Padding padding, int relief, int n) 433{ 434 switch (relief) 435 { 436 case TK_RELIEF_RAISED: 437 padding.right += n; 438 padding.bottom += n; 439 break; 440 case TK_RELIEF_SUNKEN: /* shift */ 441 padding.left += n; 442 padding.top += n; 443 break; 444 default: 445 { 446 int h1 = n/2, h2 = h1 + n % 2; 447 padding.left += h1; 448 padding.top += h1; 449 padding.right += h2; 450 padding.bottom += h2; 451 break; 452 } 453 } 454 return padding; 455} 456 457/* 458 * Ttk_GetStickyFromObj -- 459 * Returns a stickiness specification from the specified Tcl_Obj*, 460 * consisting of any combination of n, s, e, and w. 461 * 462 * Returns: TCL_OK if objPtr holds a valid stickiness specification, 463 * otherwise TCL_ERROR. interp is used for error reporting if non-NULL. 464 * 465 */ 466int Ttk_GetStickyFromObj( 467 Tcl_Interp *interp, Tcl_Obj *objPtr, Ttk_Sticky *result) 468{ 469 const char *string = Tcl_GetString(objPtr); 470 Ttk_Sticky sticky = 0; 471 char c; 472 473 while ((c = *string++) != '\0') { 474 switch (c) { 475 case 'w': case 'W': sticky |= TTK_STICK_W; break; 476 case 'e': case 'E': sticky |= TTK_STICK_E; break; 477 case 'n': case 'N': sticky |= TTK_STICK_N; break; 478 case 's': case 'S': sticky |= TTK_STICK_S; break; 479 default: 480 if (interp) { 481 Tcl_ResetResult(interp); 482 Tcl_AppendResult(interp, 483 "Bad -sticky specification ", 484 Tcl_GetString(objPtr), 485 NULL); 486 } 487 return TCL_ERROR; 488 } 489 } 490 491 *result = sticky; 492 return TCL_OK; 493} 494 495/* Ttk_NewStickyObj -- 496 * Construct a new Tcl_Obj * containing a stickiness specification. 497 */ 498Tcl_Obj *Ttk_NewStickyObj(Ttk_Sticky sticky) 499{ 500 char buf[5]; 501 char *p = buf; 502 503 if (sticky & TTK_STICK_N) *p++ = 'n'; 504 if (sticky & TTK_STICK_S) *p++ = 's'; 505 if (sticky & TTK_STICK_W) *p++ = 'w'; 506 if (sticky & TTK_STICK_E) *p++ = 'e'; 507 508 *p = '\0'; 509 return Tcl_NewStringObj(buf, p - buf); 510} 511 512/*------------------------------------------------------------------------ 513 * +++ Layout nodes. 514 */ 515 516typedef struct Ttk_LayoutNode_ Ttk_LayoutNode; 517struct Ttk_LayoutNode_ 518{ 519 unsigned flags; /* Packing and sticky flags */ 520 Ttk_ElementClass *eclass; /* Class record */ 521 Ttk_State state; /* Current state */ 522 Ttk_Box parcel; /* allocated parcel */ 523 Ttk_LayoutNode *next, *child; 524}; 525 526static Ttk_LayoutNode *Ttk_NewLayoutNode( 527 unsigned flags, Ttk_ElementClass *elementClass) 528{ 529 Ttk_LayoutNode *node = (Ttk_LayoutNode*)ckalloc(sizeof(*node)); 530 531 node->flags = flags; 532 node->eclass = elementClass; 533 node->state = 0u; 534 node->next = node->child = 0; 535 node->parcel = Ttk_MakeBox(0,0,0,0); 536 537 return node; 538} 539 540static void Ttk_FreeLayoutNode(Ttk_LayoutNode *node) 541{ 542 while (node) { 543 Ttk_LayoutNode *next = node->next; 544 Ttk_FreeLayoutNode(node->child); 545 ckfree((ClientData)node); 546 node = next; 547 } 548} 549 550/*------------------------------------------------------------------------ 551 * +++ Layout templates. 552 */ 553 554struct Ttk_TemplateNode_ { 555 char *name; 556 unsigned flags; 557 struct Ttk_TemplateNode_ *next, *child; 558}; 559 560static Ttk_TemplateNode *Ttk_NewTemplateNode(const char *name, unsigned flags) 561{ 562 Ttk_TemplateNode *op = (Ttk_TemplateNode*)ckalloc(sizeof(*op)); 563 op->name = ckalloc(strlen(name) + 1); strcpy(op->name, name); 564 op->flags = flags; 565 op->next = op->child = 0; 566 return op; 567} 568 569void Ttk_FreeLayoutTemplate(Ttk_LayoutTemplate op) 570{ 571 while (op) { 572 Ttk_LayoutTemplate next = op->next; 573 Ttk_FreeLayoutTemplate(op->child); 574 ckfree(op->name); 575 ckfree((ClientData)op); 576 op = next; 577 } 578} 579 580/* InstantiateLayout -- 581 * Create a layout tree from a template. 582 */ 583static Ttk_LayoutNode * 584Ttk_InstantiateLayout(Ttk_Theme theme, Ttk_TemplateNode *op) 585{ 586 Ttk_ElementClass *elementClass = Ttk_GetElement(theme, op->name); 587 Ttk_LayoutNode *node = Ttk_NewLayoutNode(op->flags, elementClass); 588 589 if (op->next) { 590 node->next = Ttk_InstantiateLayout(theme,op->next); 591 } 592 if (op->child) { 593 node->child = Ttk_InstantiateLayout(theme,op->child); 594 } 595 596 return node; 597} 598 599/* 600 * Ttk_ParseLayoutTemplate -- 601 * Convert a Tcl list into a layout template. 602 * 603 * Syntax: 604 * layoutSpec ::= { elementName ?-option value ...? }+ 605 */ 606 607/* NB: This must match bit definitions TTK_PACK_LEFT etc. */ 608static const char *packSideStrings[] = 609 { "left", "right", "top", "bottom", NULL }; 610 611Ttk_LayoutTemplate Ttk_ParseLayoutTemplate(Tcl_Interp *interp, Tcl_Obj *objPtr) 612{ 613 enum { OP_SIDE, OP_STICKY, OP_EXPAND, OP_BORDER, OP_UNIT, OP_CHILDREN }; 614 static const char *optStrings[] = { 615 "-side", "-sticky", "-expand", "-border", "-unit", "-children", 0 }; 616 617 int i = 0, objc; 618 Tcl_Obj **objv; 619 Ttk_TemplateNode *head = 0, *tail = 0; 620 621 if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) 622 return 0; 623 624 while (i < objc) { 625 const char *elementName = Tcl_GetString(objv[i]); 626 unsigned flags = 0x0, sticky = TTK_FILL_BOTH; 627 Tcl_Obj *childSpec = 0; 628 629 /* 630 * Parse options: 631 */ 632 ++i; 633 while (i < objc) { 634 const char *optName = Tcl_GetString(objv[i]); 635 int option, value; 636 637 if (optName[0] != '-') 638 break; 639 640 if (Tcl_GetIndexFromObj( 641 interp, objv[i], optStrings, "option", 0, &option) 642 != TCL_OK) 643 { 644 goto error; 645 } 646 647 if (++i >= objc) { 648 Tcl_ResetResult(interp); 649 Tcl_AppendResult(interp, 650 "Missing value for option ",Tcl_GetString(objv[i-1]), 651 NULL); 652 goto error; 653 } 654 655 switch (option) { 656 case OP_SIDE: /* <<NOTE-PACKSIDE>> */ 657 if (Tcl_GetIndexFromObj(interp, objv[i], packSideStrings, 658 "side", 0, &value) != TCL_OK) 659 { 660 goto error; 661 } 662 flags |= (TTK_PACK_LEFT << value); 663 664 break; 665 case OP_STICKY: 666 if (Ttk_GetStickyFromObj(interp,objv[i],&sticky) != TCL_OK) 667 goto error; 668 break; 669 case OP_EXPAND: 670 if (Tcl_GetBooleanFromObj(interp,objv[i],&value) != TCL_OK) 671 goto error; 672 if (value) 673 flags |= TTK_EXPAND; 674 break; 675 case OP_BORDER: 676 if (Tcl_GetBooleanFromObj(interp,objv[i],&value) != TCL_OK) 677 goto error; 678 if (value) 679 flags |= TTK_BORDER; 680 break; 681 case OP_UNIT: 682 if (Tcl_GetBooleanFromObj(interp,objv[i],&value) != TCL_OK) 683 goto error; 684 if (value) 685 flags |= TTK_UNIT; 686 break; 687 case OP_CHILDREN: 688 childSpec = objv[i]; 689 break; 690 } 691 ++i; 692 } 693 694 /* 695 * Build new node: 696 */ 697 if (tail) { 698 tail->next = Ttk_NewTemplateNode(elementName, flags | sticky); 699 tail = tail->next; 700 } else { 701 head = tail = Ttk_NewTemplateNode(elementName, flags | sticky); 702 } 703 if (childSpec) { 704 tail->child = Ttk_ParseLayoutTemplate(interp, childSpec); 705 if (!tail->child) { 706 goto error; 707 } 708 } 709 } 710 711 return head; 712 713error: 714 Ttk_FreeLayoutTemplate(head); 715 return 0; 716} 717 718/* Ttk_BuildLayoutTemplate -- 719 * Build a layout template tree from a statically defined 720 * Ttk_LayoutSpec array. 721 */ 722Ttk_LayoutTemplate Ttk_BuildLayoutTemplate(Ttk_LayoutSpec spec) 723{ 724 Ttk_TemplateNode *first = 0, *last = 0; 725 726 for ( ; !(spec->opcode & _TTK_LAYOUT_END) ; ++spec) { 727 if (spec->elementName) { 728 Ttk_TemplateNode *node = 729 Ttk_NewTemplateNode(spec->elementName, spec->opcode); 730 731 if (last) { 732 last->next = node; 733 } else { 734 first = node; 735 } 736 last = node; 737 } 738 739 if (spec->opcode & _TTK_CHILDREN && last) { 740 int depth = 1; 741 last->child = Ttk_BuildLayoutTemplate(spec+1); 742 743 /* Skip to end of group: 744 */ 745 while (depth) { 746 ++spec; 747 if (spec->opcode & _TTK_CHILDREN) { 748 ++depth; 749 } 750 if (spec->opcode & _TTK_LAYOUT_END) { 751 --depth; 752 } 753 } 754 } 755 756 } /* for */ 757 758 return first; 759} 760 761void Ttk_RegisterLayouts(Ttk_Theme theme, Ttk_LayoutSpec spec) 762{ 763 while (!(spec->opcode & _TTK_LAYOUT_END)) { 764 Ttk_LayoutTemplate layoutTemplate = Ttk_BuildLayoutTemplate(spec+1); 765 Ttk_RegisterLayoutTemplate(theme, spec->elementName, layoutTemplate); 766 do { 767 ++spec; 768 } while (!(spec->opcode & _TTK_LAYOUT)); 769 } 770} 771 772Tcl_Obj *Ttk_UnparseLayoutTemplate(Ttk_TemplateNode *node) 773{ 774 Tcl_Obj *result = Tcl_NewListObj(0,0); 775 776# define APPENDOBJ(obj) Tcl_ListObjAppendElement(NULL, result, obj) 777# define APPENDSTR(str) APPENDOBJ(Tcl_NewStringObj(str,-1)) 778 779 while (node) { 780 unsigned flags = node->flags; 781 782 APPENDSTR(node->name); 783 784 /* Back-compute -side. <<NOTE-PACKSIDE>> 785 * @@@ NOTES: Ick. 786 */ 787 if (flags & TTK_EXPAND) { 788 APPENDSTR("-expand"); 789 APPENDSTR("1"); 790 } else { 791 if (flags & _TTK_MASK_PACK) { 792 int side = 0; 793 unsigned sideFlags = flags & _TTK_MASK_PACK; 794 795 while ((sideFlags & TTK_PACK_LEFT) == 0) { 796 ++side; 797 sideFlags >>= 1; 798 } 799 APPENDSTR("-side"); 800 APPENDSTR(packSideStrings[side]); 801 } 802 } 803 804 /* In Ttk_ParseLayoutTemplate, default -sticky is "nsew", 805 * so always include this even if no sticky bits are set. 806 */ 807 APPENDSTR("-sticky"); 808 APPENDOBJ(Ttk_NewStickyObj(flags & _TTK_MASK_STICK)); 809 810 /* @@@ Check again: are these necessary? */ 811 if (flags & TTK_BORDER) { APPENDSTR("-border"); APPENDSTR("1"); } 812 if (flags & TTK_UNIT) { APPENDSTR("-unit"); APPENDSTR("1"); } 813 814 if (node->child) { 815 APPENDSTR("-children"); 816 APPENDOBJ(Ttk_UnparseLayoutTemplate(node->child)); 817 } 818 node = node->next; 819 } 820 821# undef APPENDOBJ 822# undef APPENDSTR 823 824 return result; 825} 826 827/*------------------------------------------------------------------------ 828 * +++ Layouts. 829 */ 830struct Ttk_Layout_ 831{ 832 Ttk_Style style; 833 void *recordPtr; 834 Tk_OptionTable optionTable; 835 Tk_Window tkwin; 836 Ttk_LayoutNode *root; 837}; 838 839static Ttk_Layout TTKNewLayout( 840 Ttk_Style style, 841 void *recordPtr,Tk_OptionTable optionTable, Tk_Window tkwin, 842 Ttk_LayoutNode *root) 843{ 844 Ttk_Layout layout = (Ttk_Layout)ckalloc(sizeof(*layout)); 845 layout->style = style; 846 layout->recordPtr = recordPtr; 847 layout->optionTable = optionTable; 848 layout->tkwin = tkwin; 849 layout->root = root; 850 return layout; 851} 852 853void Ttk_FreeLayout(Ttk_Layout layout) 854{ 855 Ttk_FreeLayoutNode(layout->root); 856 ckfree((ClientData)layout); 857} 858 859/* 860 * Ttk_CreateLayout -- 861 * Create a layout from the specified theme and style name. 862 * Returns: New layout, 0 on error. 863 * Leaves an error message in interp's result if there is an error. 864 */ 865Ttk_Layout Ttk_CreateLayout( 866 Tcl_Interp *interp, /* where to leave error messages */ 867 Ttk_Theme themePtr, 868 const char *styleName, 869 void *recordPtr, 870 Tk_OptionTable optionTable, 871 Tk_Window tkwin) 872{ 873 Ttk_Style style = Ttk_GetStyle(themePtr, styleName); 874 Ttk_LayoutTemplate layoutTemplate = 875 Ttk_FindLayoutTemplate(themePtr,styleName); 876 Ttk_ElementClass *bgelement = Ttk_GetElement(themePtr, "background"); 877 Ttk_LayoutNode *bgnode; 878 879 if (!layoutTemplate) { 880 Tcl_ResetResult(interp); 881 Tcl_AppendResult(interp, "Layout ", styleName, " not found", NULL); 882 return 0; 883 } 884 885 bgnode = Ttk_NewLayoutNode(TTK_FILL_BOTH, bgelement); 886 bgnode->next = Ttk_InstantiateLayout(themePtr, layoutTemplate); 887 888 return TTKNewLayout(style, recordPtr, optionTable, tkwin, bgnode); 889} 890 891/* Ttk_CreateSublayout -- 892 * Creates a new sublayout. 893 * 894 * Sublayouts are used to draw subparts of a compound widget. 895 * They use the same Tk_Window, but a different option table 896 * and data record. 897 */ 898Ttk_Layout 899Ttk_CreateSublayout( 900 Tcl_Interp *interp, 901 Ttk_Theme themePtr, 902 Ttk_Layout parentLayout, 903 const char *baseName, 904 Tk_OptionTable optionTable) 905{ 906 Tcl_DString buf; 907 const char *styleName; 908 Ttk_Style style; 909 Ttk_LayoutTemplate layoutTemplate; 910 911 Tcl_DStringInit(&buf); 912 Tcl_DStringAppend(&buf, Ttk_StyleName(parentLayout->style), -1); 913 Tcl_DStringAppend(&buf, baseName, -1); 914 styleName = Tcl_DStringValue(&buf); 915 916 style = Ttk_GetStyle(themePtr, styleName); 917 layoutTemplate = Ttk_FindLayoutTemplate(themePtr, styleName); 918 919 if (!layoutTemplate) { 920 Tcl_ResetResult(interp); 921 Tcl_AppendResult(interp, "Layout ", styleName, " not found", NULL); 922 return 0; 923 } 924 925 Tcl_DStringFree(&buf); 926 927 return TTKNewLayout( 928 style, 0, optionTable, parentLayout->tkwin, 929 Ttk_InstantiateLayout(themePtr, layoutTemplate)); 930} 931 932/* Ttk_RebindSublayout -- 933 * Bind sublayout to new data source. 934 */ 935void Ttk_RebindSublayout(Ttk_Layout layout, void *recordPtr) 936{ 937 layout->recordPtr = recordPtr; 938} 939 940/* 941 * Ttk_QueryOption -- 942 * Look up an option from a layout's associated option. 943 */ 944Tcl_Obj *Ttk_QueryOption( 945 Ttk_Layout layout, const char *optionName, Ttk_State state) 946{ 947 return Ttk_QueryStyle( 948 layout->style,layout->recordPtr,layout->optionTable,optionName,state); 949} 950 951/* 952 * Ttk_LayoutStyle -- 953 * Extract Ttk_Style from Ttk_Layout. 954 */ 955Ttk_Style Ttk_LayoutStyle(Ttk_Layout layout) 956{ 957 return layout->style; 958} 959 960/*------------------------------------------------------------------------ 961 * +++ Size computation. 962 */ 963static void Ttk_NodeListSize( 964 Ttk_Layout layout, Ttk_LayoutNode *node, 965 Ttk_State state, int *widthPtr, int *heightPtr); /* Forward */ 966 967static void Ttk_NodeSize( 968 Ttk_Layout layout, Ttk_LayoutNode *node, Ttk_State state, 969 int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) 970{ 971 int elementWidth, elementHeight, subWidth, subHeight; 972 Ttk_Padding elementPadding; 973 974 Ttk_ElementSize(node->eclass, 975 layout->style, layout->recordPtr,layout->optionTable, layout->tkwin, 976 state|node->state, 977 &elementWidth, &elementHeight, &elementPadding); 978 979 Ttk_NodeListSize(layout,node->child,state,&subWidth,&subHeight); 980 subWidth += Ttk_PaddingWidth(elementPadding); 981 subHeight += Ttk_PaddingHeight(elementPadding); 982 983 *widthPtr = MAX(elementWidth, subWidth); 984 *heightPtr = MAX(elementHeight, subHeight); 985 *paddingPtr = elementPadding; 986} 987 988static void Ttk_NodeListSize( 989 Ttk_Layout layout, Ttk_LayoutNode *node, 990 Ttk_State state, int *widthPtr, int *heightPtr) 991{ 992 if (!node) { 993 *widthPtr = *heightPtr = 0; 994 } else { 995 int width, height, restWidth, restHeight; 996 Ttk_Padding unused; 997 998 Ttk_NodeSize(layout, node, state, &width, &height, &unused); 999 Ttk_NodeListSize(layout, node->next, state, &restWidth, &restHeight); 1000 1001 if (node->flags & (TTK_PACK_LEFT|TTK_PACK_RIGHT)) { 1002 *widthPtr = width + restWidth; 1003 } else { 1004 *widthPtr = MAX(width, restWidth); 1005 } 1006 1007 if (node->flags & (TTK_PACK_TOP|TTK_PACK_BOTTOM)) { 1008 *heightPtr = height + restHeight; 1009 } else { 1010 *heightPtr = MAX(height, restHeight); 1011 } 1012 } 1013} 1014 1015/* 1016 * Ttk_LayoutNodeInternalPadding -- 1017 * Returns the internal padding of a layout node. 1018 */ 1019Ttk_Padding Ttk_LayoutNodeInternalPadding( 1020 Ttk_Layout layout, Ttk_LayoutNode *node) 1021{ 1022 int unused; 1023 Ttk_Padding padding; 1024 Ttk_ElementSize(node->eclass, 1025 layout->style, layout->recordPtr, layout->optionTable, layout->tkwin, 1026 0/*state*/, &unused, &unused, &padding); 1027 return padding; 1028} 1029 1030/* 1031 * Ttk_LayoutNodeInternalParcel -- 1032 * Returns the inner area of a specified layout node, 1033 * based on current parcel and element's internal padding. 1034 */ 1035Ttk_Box Ttk_LayoutNodeInternalParcel(Ttk_Layout layout, Ttk_LayoutNode *node) 1036{ 1037 Ttk_Padding padding = Ttk_LayoutNodeInternalPadding(layout, node); 1038 return Ttk_PadBox(node->parcel, padding); 1039} 1040 1041/* Ttk_LayoutSize -- 1042 * Compute requested size of a layout. 1043 */ 1044void Ttk_LayoutSize( 1045 Ttk_Layout layout, Ttk_State state, int *widthPtr, int *heightPtr) 1046{ 1047 Ttk_NodeListSize(layout, layout->root, state, widthPtr, heightPtr); 1048} 1049 1050void Ttk_LayoutNodeReqSize( /* @@@ Rename this */ 1051 Ttk_Layout layout, Ttk_LayoutNode *node, int *widthPtr, int *heightPtr) 1052{ 1053 Ttk_Padding unused; 1054 Ttk_NodeSize(layout, node, 0/*state*/, widthPtr, heightPtr, &unused); 1055} 1056 1057/*------------------------------------------------------------------------ 1058 * +++ Layout placement. 1059 */ 1060 1061/* Ttk_PlaceNodeList -- 1062 * Compute parcel for each node in a layout tree 1063 * according to position specification and overall size. 1064 */ 1065static void Ttk_PlaceNodeList( 1066 Ttk_Layout layout, Ttk_LayoutNode *node, Ttk_State state, Ttk_Box cavity) 1067{ 1068 for (; node; node = node->next) 1069 { 1070 int width, height; 1071 Ttk_Padding padding; 1072 1073 /* Compute node size: (@@@ cache this instead?) 1074 */ 1075 Ttk_NodeSize(layout, node, state, &width, &height, &padding); 1076 1077 /* Compute parcel: 1078 */ 1079 node->parcel = Ttk_PositionBox(&cavity, width, height, node->flags); 1080 1081 /* Place child nodes: 1082 */ 1083 if (node->child) { 1084 Ttk_Box childBox = Ttk_PadBox(node->parcel, padding); 1085 Ttk_PlaceNodeList(layout,node->child, state, childBox); 1086 } 1087 } 1088} 1089 1090void Ttk_PlaceLayout(Ttk_Layout layout, Ttk_State state, Ttk_Box b) 1091{ 1092 Ttk_PlaceNodeList(layout, layout->root, state, b); 1093} 1094 1095/*------------------------------------------------------------------------ 1096 * +++ Layout drawing. 1097 */ 1098 1099/* 1100 * Ttk_DrawLayout -- 1101 * Draw a layout tree. 1102 */ 1103static void Ttk_DrawNodeList( 1104 Ttk_Layout layout, Ttk_State state, Ttk_LayoutNode *node, Drawable d) 1105{ 1106 for (; node; node = node->next) 1107 { 1108 int border = node->flags & TTK_BORDER; 1109 int substate = state; 1110 1111 if (node->flags & TTK_UNIT) 1112 substate |= node->state; 1113 1114 if (node->child && border) 1115 Ttk_DrawNodeList(layout, substate, node->child, d); 1116 1117 Ttk_DrawElement( 1118 node->eclass, 1119 layout->style,layout->recordPtr,layout->optionTable,layout->tkwin, 1120 d, node->parcel, state | node->state); 1121 1122 if (node->child && !border) 1123 Ttk_DrawNodeList(layout, substate, node->child, d); 1124 } 1125} 1126 1127void Ttk_DrawLayout(Ttk_Layout layout, Ttk_State state, Drawable d) 1128{ 1129 Ttk_DrawNodeList(layout, state, layout->root, d); 1130} 1131 1132/*------------------------------------------------------------------------ 1133 * +++ Inquiry and modification. 1134 */ 1135 1136/* 1137 * Ttk_IdentifyElement -- 1138 * Find the element at the specified x,y coordinate. 1139 */ 1140static Ttk_Element IdentifyNode(Ttk_Element node, int x, int y) 1141{ 1142 Ttk_Element closest = NULL; 1143 1144 for (; node; node = node->next) { 1145 if (Ttk_BoxContains(node->parcel, x, y)) { 1146 closest = node; 1147 if (node->child && !(node->flags & TTK_UNIT)) { 1148 Ttk_Element childNode = IdentifyNode(node->child, x,y); 1149 if (childNode) { 1150 closest = childNode; 1151 } 1152 } 1153 } 1154 } 1155 return closest; 1156} 1157 1158Ttk_Element Ttk_IdentifyElement(Ttk_Layout layout, int x, int y) 1159{ 1160 return IdentifyNode(layout->root, x, y); 1161} 1162 1163/* 1164 * tail -- 1165 * Return the last component of an element name, e.g., 1166 * "Scrollbar.thumb" => "thumb" 1167 */ 1168static const char *tail(const char *elementName) 1169{ 1170 const char *dot; 1171 while ((dot=strchr(elementName,'.')) != NULL) 1172 elementName = dot + 1; 1173 return elementName; 1174} 1175 1176/* 1177 * Ttk_FindElement -- 1178 * Look up an element by name 1179 */ 1180static Ttk_Element 1181FindNode(Ttk_Element node, const char *nodeName) 1182{ 1183 for (; node ; node = node->next) { 1184 if (!strcmp(tail(Ttk_ElementName(node)), nodeName)) 1185 return node; 1186 1187 if (node->child) { 1188 Ttk_Element childNode = FindNode(node->child, nodeName); 1189 if (childNode) 1190 return childNode; 1191 } 1192 } 1193 return 0; 1194} 1195 1196Ttk_Element Ttk_FindElement(Ttk_Layout layout, const char *nodeName) 1197{ 1198 return FindNode(layout->root, nodeName); 1199} 1200 1201/* 1202 * Ttk_ClientRegion -- 1203 * Find the internal parcel of a named element within a given layout. 1204 * If the element is not present, use the entire window. 1205 */ 1206Ttk_Box Ttk_ClientRegion(Ttk_Layout layout, const char *elementName) 1207{ 1208 Ttk_Element element = Ttk_FindElement(layout, elementName); 1209 return element 1210 ? Ttk_LayoutNodeInternalParcel(layout, element) 1211 : Ttk_WinBox(layout->tkwin) 1212 ; 1213} 1214 1215/* 1216 * Ttk_ElementName -- 1217 * Return the name (class name) of the element. 1218 */ 1219const char *Ttk_ElementName(Ttk_Element node) 1220{ 1221 return Ttk_ElementClassName(node->eclass); 1222} 1223 1224/* 1225 * Ttk_ElementParcel -- 1226 * Return the element's current parcel. 1227 */ 1228Ttk_Box Ttk_ElementParcel(Ttk_Element node) 1229{ 1230 return node->parcel; 1231} 1232 1233/* 1234 * Ttk_PlaceElement -- 1235 * Explicitly specify an element's parcel. 1236 */ 1237void Ttk_PlaceElement(Ttk_Layout layout, Ttk_Element node, Ttk_Box b) 1238{ 1239 node->parcel = b; 1240 if (node->child) { 1241 Ttk_PlaceNodeList(layout, node->child, 0, 1242 Ttk_PadBox(b, Ttk_LayoutNodeInternalPadding(layout, node))); 1243 } 1244} 1245 1246/* 1247 * Ttk_ChangeElementState -- 1248 */ 1249void Ttk_ChangeElementState(Ttk_LayoutNode *node,unsigned set,unsigned clr) 1250{ 1251 node->state = (node->state | set) & ~clr; 1252} 1253 1254/*EOF*/ 1255