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