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