1/* $Id$
2 * Copyright (c) 2003, Joe English
3 *
4 * label, button, checkbutton, radiobutton, and menubutton widgets.
5 */
6
7#include <string.h>
8#include <tk.h>
9#include "ttkTheme.h"
10#include "ttkWidget.h"
11
12/* Bit fields for OptionSpec mask field:
13 */
14#define STATE_CHANGED	 	(0x100)		/* -state option changed */
15#define DEFAULTSTATE_CHANGED	(0x200)		/* -default option changed */
16
17/*------------------------------------------------------------------------
18 * +++ Base resources for labels, buttons, checkbuttons, etc:
19 */
20typedef struct
21{
22    /*
23     * Text element resources:
24     */
25    Tcl_Obj *textObj;
26    Tcl_Obj *textVariableObj;
27    Tcl_Obj *underlineObj;
28    Tcl_Obj *widthObj;
29
30    Ttk_TraceHandle	*textVariableTrace;
31    Ttk_ImageSpec	*imageSpec;
32
33    /*
34     * Image element resources:
35     */
36    Tcl_Obj *imageObj;
37
38    /*
39     * Compound label/image resources:
40     */
41    Tcl_Obj *compoundObj;
42    Tcl_Obj *paddingObj;
43
44    /*
45     * Compatibility/legacy options:
46     */
47    Tcl_Obj *stateObj;
48
49} BasePart;
50
51typedef struct
52{
53    WidgetCore	core;
54    BasePart	base;
55} Base;
56
57static Tk_OptionSpec BaseOptionSpecs[] =
58{
59    {TK_OPTION_STRING, "-text", "text", "Text", "",
60	Tk_Offset(Base,base.textObj), -1,
61	0,0,GEOMETRY_CHANGED },
62    {TK_OPTION_STRING, "-textvariable", "textVariable", "Variable", "",
63	Tk_Offset(Base,base.textVariableObj), -1,
64	TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED },
65    {TK_OPTION_INT, "-underline", "underline", "Underline",
66	"-1", Tk_Offset(Base,base.underlineObj), -1,
67	0,0,0 },
68    /* SB: OPTION_INT, see <<NOTE-NULLOPTIONS>> */
69    {TK_OPTION_STRING, "-width", "width", "Width",
70	NULL, Tk_Offset(Base,base.widthObj), -1,
71	TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED },
72
73    /*
74     * Image options
75     */
76    {TK_OPTION_STRING, "-image", "image", "Image", NULL/*default*/,
77	Tk_Offset(Base,base.imageObj), -1,
78	TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED },
79
80    /*
81     * Compound base/image options
82     */
83    {TK_OPTION_STRING_TABLE, "-compound", "compound", "Compound",
84	 "none", Tk_Offset(Base,base.compoundObj), -1,
85	 0,(ClientData)ttkCompoundStrings,GEOMETRY_CHANGED },
86    {TK_OPTION_STRING, "-padding", "padding", "Pad",
87	NULL, Tk_Offset(Base,base.paddingObj), -1,
88	TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED},
89
90    /*
91     * Compatibility/legacy options
92     */
93    {TK_OPTION_STRING, "-state", "state", "State",
94	 "normal", Tk_Offset(Base,base.stateObj), -1,
95	 0,0,STATE_CHANGED },
96
97    WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs)
98};
99
100/*
101 * Variable trace procedure for -textvariable option:
102 */
103static void TextVariableChanged(void *clientData, const char *value)
104{
105    Base *basePtr = clientData;
106    Tcl_Obj *newText;
107
108    if (WidgetDestroyed(&basePtr->core)) {
109	return;
110    }
111
112    newText = value ? Tcl_NewStringObj(value, -1) : Tcl_NewStringObj("", 0);
113
114    Tcl_IncrRefCount(newText);
115    Tcl_DecrRefCount(basePtr->base.textObj);
116    basePtr->base.textObj = newText;
117
118    TtkResizeWidget(&basePtr->core);
119}
120
121static void
122BaseInitialize(Tcl_Interp *interp, void *recordPtr)
123{
124    Base *basePtr = recordPtr;
125    basePtr->base.textVariableTrace = 0;
126    basePtr->base.imageSpec = NULL;
127}
128
129static void
130BaseCleanup(void *recordPtr)
131{
132    Base *basePtr = recordPtr;
133    if (basePtr->base.textVariableTrace)
134	Ttk_UntraceVariable(basePtr->base.textVariableTrace);
135    if (basePtr->base.imageSpec)
136    	TtkFreeImageSpec(basePtr->base.imageSpec);
137}
138
139static int BaseConfigure(Tcl_Interp *interp, void *recordPtr, int mask)
140{
141    Base *basePtr = recordPtr;
142    Tcl_Obj *textVarName = basePtr->base.textVariableObj;
143    Ttk_TraceHandle *vt = 0;
144    Ttk_ImageSpec *imageSpec = NULL;
145
146    if (textVarName != NULL && *Tcl_GetString(textVarName) != '\0') {
147	vt = Ttk_TraceVariable(interp,textVarName,TextVariableChanged,basePtr);
148	if (!vt) return TCL_ERROR;
149    }
150
151    if (basePtr->base.imageObj) {
152	imageSpec = TtkGetImageSpec(
153	    interp, basePtr->core.tkwin, basePtr->base.imageObj);
154	if (!imageSpec) {
155	    goto error;
156	}
157    }
158
159    if (TtkCoreConfigure(interp, recordPtr, mask) != TCL_OK) {
160error:
161	if (imageSpec) TtkFreeImageSpec(imageSpec);
162	if (vt) Ttk_UntraceVariable(vt);
163	return TCL_ERROR;
164    }
165
166    if (basePtr->base.textVariableTrace) {
167	Ttk_UntraceVariable(basePtr->base.textVariableTrace);
168    }
169    basePtr->base.textVariableTrace = vt;
170
171    if (basePtr->base.imageSpec) {
172	TtkFreeImageSpec(basePtr->base.imageSpec);
173    }
174    basePtr->base.imageSpec = imageSpec;
175
176    if (mask & STATE_CHANGED) {
177	TtkCheckStateOption(&basePtr->core, basePtr->base.stateObj);
178    }
179
180    return TCL_OK;
181}
182
183static int
184BasePostConfigure(Tcl_Interp *interp, void *recordPtr, int mask)
185{
186    Base *basePtr = recordPtr;
187    int status = TCL_OK;
188
189    if (basePtr->base.textVariableTrace) {
190	status = Ttk_FireTrace(basePtr->base.textVariableTrace);
191    }
192
193    return status;
194}
195
196/*------------------------------------------------------------------------
197 * +++ Label widget.
198 * Just a base widget that adds a few appearance-related options
199 */
200
201typedef struct
202{
203    Tcl_Obj *backgroundObj;
204    Tcl_Obj *foregroundObj;
205    Tcl_Obj *fontObj;
206    Tcl_Obj *borderWidthObj;
207    Tcl_Obj *reliefObj;
208    Tcl_Obj *anchorObj;
209    Tcl_Obj *justifyObj;
210    Tcl_Obj *wrapLengthObj;
211} LabelPart;
212
213typedef struct
214{
215    WidgetCore	core;
216    BasePart	base;
217    LabelPart	label;
218} Label;
219
220static Tk_OptionSpec LabelOptionSpecs[] =
221{
222    {TK_OPTION_BORDER, "-background", "frameColor", "FrameColor",
223	NULL, Tk_Offset(Label,label.backgroundObj), -1,
224	TK_OPTION_NULL_OK,0,0 },
225    {TK_OPTION_COLOR, "-foreground", "textColor", "TextColor",
226	NULL, Tk_Offset(Label,label.foregroundObj), -1,
227	TK_OPTION_NULL_OK,0,0 },
228    {TK_OPTION_FONT, "-font", "font", "Font",
229	NULL, Tk_Offset(Label,label.fontObj), -1,
230	TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED },
231    {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
232	NULL, Tk_Offset(Label,label.borderWidthObj), -1,
233	TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED },
234    {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
235	NULL, Tk_Offset(Label,label.reliefObj), -1,
236	TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED },
237    {TK_OPTION_ANCHOR, "-anchor", "anchor", "Anchor",
238	NULL, Tk_Offset(Label,label.anchorObj), -1,
239	TK_OPTION_NULL_OK, 0, GEOMETRY_CHANGED},
240    {TK_OPTION_JUSTIFY, "-justify", "justify", "Justify",
241	NULL, Tk_Offset(Label, label.justifyObj), -1,
242	TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED },
243    {TK_OPTION_PIXELS, "-wraplength", "wrapLength", "WrapLength",
244	NULL, Tk_Offset(Label, label.wrapLengthObj), -1,
245	TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED /*SB: SIZE_CHANGED*/ },
246
247    WIDGET_INHERIT_OPTIONS(BaseOptionSpecs)
248};
249
250static const Ttk_Ensemble LabelCommands[] = {
251    { "configure",	TtkWidgetConfigureCommand,0 },
252    { "cget",		TtkWidgetCgetCommand,0 },
253    { "instate",	TtkWidgetInstateCommand,0 },
254    { "state",  	TtkWidgetStateCommand,0 },
255    { "identify",	TtkWidgetIdentifyCommand,0 },
256    { 0,0,0 }
257};
258
259static WidgetSpec LabelWidgetSpec =
260{
261    "TLabel",			/* className */
262    sizeof(Label),		/* recordSize */
263    LabelOptionSpecs,		/* optionSpecs */
264    LabelCommands,		/* subcommands */
265    BaseInitialize,		/* initializeProc */
266    BaseCleanup,		/* cleanupProc */
267    BaseConfigure,		/* configureProc */
268    BasePostConfigure,		/* postConfigureProc */
269    TtkWidgetGetLayout, 	/* getLayoutProc */
270    TtkWidgetSize, 		/* sizeProc */
271    TtkWidgetDoLayout,		/* layoutProc */
272    TtkWidgetDisplay		/* displayProc */
273};
274
275TTK_BEGIN_LAYOUT(LabelLayout)
276    TTK_GROUP("Label.border", TTK_FILL_BOTH|TTK_BORDER,
277	TTK_GROUP("Label.padding", TTK_FILL_BOTH|TTK_BORDER,
278	    TTK_NODE("Label.label", TTK_FILL_BOTH)))
279TTK_END_LAYOUT
280
281/*------------------------------------------------------------------------
282 * +++ Button widget.
283 * Adds a new subcommand "invoke", and options "-command" and "-default"
284 */
285
286typedef struct
287{
288    Tcl_Obj *commandObj;
289    Tcl_Obj *defaultStateObj;
290} ButtonPart;
291
292typedef struct
293{
294    WidgetCore	core;
295    BasePart	base;
296    ButtonPart	button;
297} Button;
298
299/*
300 * Option specifications:
301 */
302static Tk_OptionSpec ButtonOptionSpecs[] =
303{
304    WIDGET_TAKES_FOCUS,
305
306    {TK_OPTION_STRING, "-command", "command", "Command",
307	"", Tk_Offset(Button, button.commandObj), -1, 0,0,0},
308    {TK_OPTION_STRING_TABLE, "-default", "default", "Default",
309	"normal", Tk_Offset(Button, button.defaultStateObj), -1,
310	0, (ClientData) ttkDefaultStrings, DEFAULTSTATE_CHANGED},
311
312    WIDGET_INHERIT_OPTIONS(BaseOptionSpecs)
313};
314
315static int ButtonConfigure(Tcl_Interp *interp, void *recordPtr, int mask)
316{
317    Button *buttonPtr = recordPtr;
318
319    if (BaseConfigure(interp, recordPtr, mask) != TCL_OK) {
320	return TCL_ERROR;
321    }
322
323    /* Handle "-default" option:
324     */
325    if (mask & DEFAULTSTATE_CHANGED) {
326	int defaultState = TTK_BUTTON_DEFAULT_DISABLED;
327	Ttk_GetButtonDefaultStateFromObj(
328	    NULL, buttonPtr->button.defaultStateObj, &defaultState);
329	if (defaultState == TTK_BUTTON_DEFAULT_ACTIVE) {
330	    TtkWidgetChangeState(&buttonPtr->core, TTK_STATE_ALTERNATE, 0);
331	} else {
332	    TtkWidgetChangeState(&buttonPtr->core, 0, TTK_STATE_ALTERNATE);
333	}
334    }
335    return TCL_OK;
336}
337
338/* $button invoke --
339 * 	Evaluate the button's -command.
340 */
341static int
342ButtonInvokeCommand(
343    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
344{
345    Button *buttonPtr = recordPtr;
346    if (objc > 2) {
347	Tcl_WrongNumArgs(interp, 1, objv, "invoke");
348	return TCL_ERROR;
349    }
350    if (buttonPtr->core.state & TTK_STATE_DISABLED) {
351	return TCL_OK;
352    }
353    return Tcl_EvalObjEx(interp, buttonPtr->button.commandObj, TCL_EVAL_GLOBAL);
354}
355
356static const Ttk_Ensemble ButtonCommands[] = {
357    { "configure",	TtkWidgetConfigureCommand,0 },
358    { "cget",		TtkWidgetCgetCommand,0 },
359    { "invoke",		ButtonInvokeCommand,0 },
360    { "instate",	TtkWidgetInstateCommand,0 },
361    { "state",  	TtkWidgetStateCommand,0 },
362    { "identify",	TtkWidgetIdentifyCommand,0 },
363    { 0,0,0 }
364};
365
366static WidgetSpec ButtonWidgetSpec =
367{
368    "TButton",			/* className */
369    sizeof(Button),		/* recordSize */
370    ButtonOptionSpecs,		/* optionSpecs */
371    ButtonCommands,		/* subcommands */
372    BaseInitialize,		/* initializeProc */
373    BaseCleanup,		/* cleanupProc */
374    ButtonConfigure,		/* configureProc */
375    BasePostConfigure,		/* postConfigureProc */
376    TtkWidgetGetLayout,		/* getLayoutProc */
377    TtkWidgetSize, 		/* sizeProc */
378    TtkWidgetDoLayout,		/* layoutProc */
379    TtkWidgetDisplay		/* displayProc */
380};
381
382TTK_BEGIN_LAYOUT(ButtonLayout)
383    TTK_GROUP("Button.border", TTK_FILL_BOTH|TTK_BORDER,
384	TTK_GROUP("Button.focus", TTK_FILL_BOTH,
385	    TTK_GROUP("Button.padding", TTK_FILL_BOTH,
386	        TTK_NODE("Button.label", TTK_FILL_BOTH))))
387TTK_END_LAYOUT
388
389/*------------------------------------------------------------------------
390 * +++ Checkbutton widget.
391 */
392typedef struct
393{
394    Tcl_Obj *variableObj;
395    Tcl_Obj *onValueObj;
396    Tcl_Obj *offValueObj;
397    Tcl_Obj *commandObj;
398
399    Ttk_TraceHandle *variableTrace;
400
401} CheckbuttonPart;
402
403typedef struct
404{
405    WidgetCore core;
406    BasePart base;
407    CheckbuttonPart checkbutton;
408} Checkbutton;
409
410/*
411 * Option specifications:
412 */
413static Tk_OptionSpec CheckbuttonOptionSpecs[] =
414{
415    WIDGET_TAKES_FOCUS,
416
417    {TK_OPTION_STRING, "-variable", "variable", "Variable",
418	"", Tk_Offset(Checkbutton, checkbutton.variableObj), -1,
419	TK_OPTION_DONT_SET_DEFAULT,0,0},
420    {TK_OPTION_STRING, "-onvalue", "onValue", "OnValue",
421	"1", Tk_Offset(Checkbutton, checkbutton.onValueObj), -1,
422	0,0,0},
423    {TK_OPTION_STRING, "-offvalue", "offValue", "OffValue",
424	"0", Tk_Offset(Checkbutton, checkbutton.offValueObj), -1,
425	0,0,0},
426    {TK_OPTION_STRING, "-command", "command", "Command",
427	"", Tk_Offset(Checkbutton, checkbutton.commandObj), -1,
428	0,0,0},
429
430    WIDGET_INHERIT_OPTIONS(BaseOptionSpecs)
431};
432
433/*
434 * Variable trace procedure for checkbutton -variable option
435 */
436static void CheckbuttonVariableChanged(void *clientData, const char *value)
437{
438    Checkbutton *checkPtr = clientData;
439
440    if (WidgetDestroyed(&checkPtr->core)) {
441	return;
442    }
443
444    if (!value) {
445	TtkWidgetChangeState(&checkPtr->core, TTK_STATE_ALTERNATE, 0);
446	return;
447    }
448    /* else */
449    TtkWidgetChangeState(&checkPtr->core, 0, TTK_STATE_ALTERNATE);
450    if (!strcmp(value, Tcl_GetString(checkPtr->checkbutton.onValueObj))) {
451	TtkWidgetChangeState(&checkPtr->core, TTK_STATE_SELECTED, 0);
452    } else {
453	TtkWidgetChangeState(&checkPtr->core, 0, TTK_STATE_SELECTED);
454    }
455}
456
457static void
458CheckbuttonInitialize(Tcl_Interp *interp, void *recordPtr)
459{
460    Checkbutton *checkPtr = recordPtr;
461    Tcl_Obj *variableObj;
462
463    /* default -variable is the widget name:
464     */
465    variableObj = Tcl_NewStringObj(Tk_PathName(checkPtr->core.tkwin), -1);
466    Tcl_IncrRefCount(variableObj);
467    checkPtr->checkbutton.variableObj = variableObj;
468    BaseInitialize(interp, recordPtr);
469}
470
471static void
472CheckbuttonCleanup(void *recordPtr)
473{
474    Checkbutton *checkPtr = recordPtr;
475    Ttk_UntraceVariable(checkPtr->checkbutton.variableTrace);
476    checkPtr->checkbutton.variableTrace = 0;
477    BaseCleanup(recordPtr);
478}
479
480static int
481CheckbuttonConfigure(Tcl_Interp *interp, void *recordPtr, int mask)
482{
483    Checkbutton *checkPtr = recordPtr;
484    Ttk_TraceHandle *vt = Ttk_TraceVariable(
485	interp, checkPtr->checkbutton.variableObj,
486	CheckbuttonVariableChanged, checkPtr);
487
488    if (!vt) {
489	return TCL_ERROR;
490    }
491
492    if (BaseConfigure(interp, recordPtr, mask) != TCL_OK){
493	Ttk_UntraceVariable(vt);
494	return TCL_ERROR;
495    }
496
497    Ttk_UntraceVariable(checkPtr->checkbutton.variableTrace);
498    checkPtr->checkbutton.variableTrace = vt;
499
500    return TCL_OK;
501}
502
503static int
504CheckbuttonPostConfigure(Tcl_Interp *interp, void *recordPtr, int mask)
505{
506    Checkbutton *checkPtr = recordPtr;
507    int status = TCL_OK;
508
509    if (checkPtr->checkbutton.variableTrace)
510	status = Ttk_FireTrace(checkPtr->checkbutton.variableTrace);
511    if (status == TCL_OK && !WidgetDestroyed(&checkPtr->core))
512	status = BasePostConfigure(interp, recordPtr, mask);
513    return status;
514}
515
516/*
517 * Checkbutton 'invoke' subcommand:
518 * 	Toggles the checkbutton state.
519 */
520static int
521CheckbuttonInvokeCommand(
522    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
523{
524    Checkbutton *checkPtr = recordPtr;
525    WidgetCore *corePtr = &checkPtr->core;
526    Tcl_Obj *newValue;
527
528    if (objc > 2) {
529	Tcl_WrongNumArgs(interp, 1, objv, "invoke");
530	return TCL_ERROR;
531    }
532    if (corePtr->state & TTK_STATE_DISABLED)
533	return TCL_OK;
534
535    /*
536     * Toggle the selected state.
537     */
538    if (corePtr->state & TTK_STATE_SELECTED)
539	newValue = checkPtr->checkbutton.offValueObj;
540    else
541	newValue = checkPtr->checkbutton.onValueObj;
542
543    if (Tcl_ObjSetVar2(interp,
544	    checkPtr->checkbutton.variableObj, NULL, newValue,
545	    TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG)
546	== NULL)
547	return TCL_ERROR;
548
549    if (WidgetDestroyed(corePtr))
550	return TCL_ERROR;
551
552    return Tcl_EvalObjEx(interp,
553	checkPtr->checkbutton.commandObj, TCL_EVAL_GLOBAL);
554}
555
556static const Ttk_Ensemble CheckbuttonCommands[] = {
557    { "configure",	TtkWidgetConfigureCommand,0 },
558    { "cget",		TtkWidgetCgetCommand,0 },
559    { "invoke",		CheckbuttonInvokeCommand,0 },
560    { "instate",	TtkWidgetInstateCommand,0 },
561    { "state",  	TtkWidgetStateCommand,0 },
562    { "identify",	TtkWidgetIdentifyCommand,0 },
563    /* MISSING: select, deselect, toggle */
564    { 0,0,0 }
565};
566
567static WidgetSpec CheckbuttonWidgetSpec =
568{
569    "TCheckbutton",		/* className */
570    sizeof(Checkbutton),	/* recordSize */
571    CheckbuttonOptionSpecs,	/* optionSpecs */
572    CheckbuttonCommands,	/* subcommands */
573    CheckbuttonInitialize,	/* initializeProc */
574    CheckbuttonCleanup,		/* cleanupProc */
575    CheckbuttonConfigure,	/* configureProc */
576    CheckbuttonPostConfigure,	/* postConfigureProc */
577    TtkWidgetGetLayout, 	/* getLayoutProc */
578    TtkWidgetSize, 		/* sizeProc */
579    TtkWidgetDoLayout,		/* layoutProc */
580    TtkWidgetDisplay		/* displayProc */
581};
582
583TTK_BEGIN_LAYOUT(CheckbuttonLayout)
584     TTK_GROUP("Checkbutton.padding", TTK_FILL_BOTH,
585	 TTK_NODE("Checkbutton.indicator", TTK_PACK_LEFT)
586	 TTK_GROUP("Checkbutton.focus", TTK_PACK_LEFT | TTK_STICK_W,
587	     TTK_NODE("Checkbutton.label", TTK_FILL_BOTH)))
588TTK_END_LAYOUT
589
590/*------------------------------------------------------------------------
591 * +++ Radiobutton widget.
592 */
593
594typedef struct
595{
596    Tcl_Obj *variableObj;
597    Tcl_Obj *valueObj;
598    Tcl_Obj *commandObj;
599
600    Ttk_TraceHandle	*variableTrace;
601
602} RadiobuttonPart;
603
604typedef struct
605{
606    WidgetCore core;
607    BasePart base;
608    RadiobuttonPart radiobutton;
609} Radiobutton;
610
611/*
612 * Option specifications:
613 */
614static Tk_OptionSpec RadiobuttonOptionSpecs[] =
615{
616    WIDGET_TAKES_FOCUS,
617
618    {TK_OPTION_STRING, "-variable", "variable", "Variable",
619	"::selectedButton", Tk_Offset(Radiobutton, radiobutton.variableObj),-1,
620	0,0,0},
621    {TK_OPTION_STRING, "-value", "Value", "Value",
622	"1", Tk_Offset(Radiobutton, radiobutton.valueObj), -1,
623	0,0,0},
624    {TK_OPTION_STRING, "-command", "command", "Command",
625	"", Tk_Offset(Radiobutton, radiobutton.commandObj), -1,
626	0,0,0},
627
628    WIDGET_INHERIT_OPTIONS(BaseOptionSpecs)
629};
630
631/*
632 * Variable trace procedure for radiobuttons.
633 */
634static void
635RadiobuttonVariableChanged(void *clientData, const char *value)
636{
637    Radiobutton *radioPtr = clientData;
638
639    if (WidgetDestroyed(&radioPtr->core)) {
640	return;
641    }
642
643    if (!value) {
644	TtkWidgetChangeState(&radioPtr->core, TTK_STATE_ALTERNATE, 0);
645	return;
646    }
647    /* else */
648    TtkWidgetChangeState(&radioPtr->core, 0, TTK_STATE_ALTERNATE);
649    if (!strcmp(value, Tcl_GetString(radioPtr->radiobutton.valueObj))) {
650	TtkWidgetChangeState(&radioPtr->core, TTK_STATE_SELECTED, 0);
651    } else {
652	TtkWidgetChangeState(&radioPtr->core, 0, TTK_STATE_SELECTED);
653    }
654}
655
656static void
657RadiobuttonCleanup(void *recordPtr)
658{
659    Radiobutton *radioPtr = recordPtr;
660    Ttk_UntraceVariable(radioPtr->radiobutton.variableTrace);
661    radioPtr->radiobutton.variableTrace = 0;
662    BaseCleanup(recordPtr);
663}
664
665static int
666RadiobuttonConfigure(Tcl_Interp *interp, void *recordPtr, int mask)
667{
668    Radiobutton *radioPtr = recordPtr;
669    Ttk_TraceHandle *vt = Ttk_TraceVariable(
670	interp, radioPtr->radiobutton.variableObj,
671	RadiobuttonVariableChanged, radioPtr);
672
673    if (!vt) {
674	return TCL_ERROR;
675    }
676
677    if (BaseConfigure(interp, recordPtr, mask) != TCL_OK) {
678	Ttk_UntraceVariable(vt);
679	return TCL_ERROR;
680    }
681
682    Ttk_UntraceVariable(radioPtr->radiobutton.variableTrace);
683    radioPtr->radiobutton.variableTrace = vt;
684
685    return TCL_OK;
686}
687
688static int
689RadiobuttonPostConfigure(Tcl_Interp *interp, void *recordPtr, int mask)
690{
691    Radiobutton *radioPtr = recordPtr;
692    int status = TCL_OK;
693
694    if (radioPtr->radiobutton.variableTrace)
695	status = Ttk_FireTrace(radioPtr->radiobutton.variableTrace);
696    if (status == TCL_OK && !WidgetDestroyed(&radioPtr->core))
697	status = BasePostConfigure(interp, recordPtr, mask);
698    return status;
699}
700
701/*
702 * Radiobutton 'invoke' subcommand:
703 * 	Sets the radiobutton -variable to the -value, evaluates the -command.
704 */
705static int
706RadiobuttonInvokeCommand(
707    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
708{
709    Radiobutton *radioPtr = recordPtr;
710    WidgetCore *corePtr = &radioPtr->core;
711
712    if (objc > 2) {
713	Tcl_WrongNumArgs(interp, 1, objv, "invoke");
714	return TCL_ERROR;
715    }
716    if (corePtr->state & TTK_STATE_DISABLED)
717	return TCL_OK;
718
719    if (Tcl_ObjSetVar2(interp,
720	    radioPtr->radiobutton.variableObj, NULL,
721	    radioPtr->radiobutton.valueObj,
722	    TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG)
723	== NULL)
724	return TCL_ERROR;
725
726    if (WidgetDestroyed(corePtr))
727	return TCL_ERROR;
728
729    return Tcl_EvalObjEx(interp,
730	radioPtr->radiobutton.commandObj, TCL_EVAL_GLOBAL);
731}
732
733static const Ttk_Ensemble RadiobuttonCommands[] = {
734    { "configure",	TtkWidgetConfigureCommand,0 },
735    { "cget",		TtkWidgetCgetCommand,0 },
736    { "invoke",		RadiobuttonInvokeCommand,0 },
737    { "instate",	TtkWidgetInstateCommand,0 },
738    { "state",  	TtkWidgetStateCommand,0 },
739    { "identify",	TtkWidgetIdentifyCommand,0 },
740    /* MISSING: select, deselect */
741    { 0,0,0 }
742};
743
744static WidgetSpec RadiobuttonWidgetSpec =
745{
746    "TRadiobutton",		/* className */
747    sizeof(Radiobutton),	/* recordSize */
748    RadiobuttonOptionSpecs,	/* optionSpecs */
749    RadiobuttonCommands,	/* subcommands */
750    BaseInitialize,		/* initializeProc */
751    RadiobuttonCleanup,		/* cleanupProc */
752    RadiobuttonConfigure,	/* configureProc */
753    RadiobuttonPostConfigure,	/* postConfigureProc */
754    TtkWidgetGetLayout, 	/* getLayoutProc */
755    TtkWidgetSize, 		/* sizeProc */
756    TtkWidgetDoLayout,		/* layoutProc */
757    TtkWidgetDisplay		/* displayProc */
758};
759
760TTK_BEGIN_LAYOUT(RadiobuttonLayout)
761     TTK_GROUP("Radiobutton.padding", TTK_FILL_BOTH,
762	 TTK_NODE("Radiobutton.indicator", TTK_PACK_LEFT)
763	 TTK_GROUP("Radiobutton.focus", TTK_PACK_LEFT,
764	     TTK_NODE("Radiobutton.label", TTK_FILL_BOTH)))
765TTK_END_LAYOUT
766
767/*------------------------------------------------------------------------
768 * +++ Menubutton widget.
769 */
770
771typedef struct
772{
773    Tcl_Obj *menuObj;
774    Tcl_Obj *directionObj;
775} MenubuttonPart;
776
777typedef struct
778{
779    WidgetCore core;
780    BasePart base;
781    MenubuttonPart menubutton;
782} Menubutton;
783
784/*
785 * Option specifications:
786 */
787static const char *const directionStrings[] = {
788    "above", "below", "left", "right", "flush", NULL
789};
790static Tk_OptionSpec MenubuttonOptionSpecs[] =
791{
792    WIDGET_TAKES_FOCUS,
793
794    {TK_OPTION_STRING, "-menu", "menu", "Menu",
795	"", Tk_Offset(Menubutton, menubutton.menuObj), -1, 0,0,0},
796    {TK_OPTION_STRING_TABLE, "-direction", "direction", "Direction",
797	"below", Tk_Offset(Menubutton, menubutton.directionObj), -1,
798	0,(ClientData)directionStrings,GEOMETRY_CHANGED},
799
800    WIDGET_INHERIT_OPTIONS(BaseOptionSpecs)
801};
802
803static const Ttk_Ensemble MenubuttonCommands[] = {
804    { "configure",	TtkWidgetConfigureCommand,0 },
805    { "cget",		TtkWidgetCgetCommand,0 },
806    { "instate",	TtkWidgetInstateCommand,0 },
807    { "state",  	TtkWidgetStateCommand,0 },
808    { "identify",	TtkWidgetIdentifyCommand,0 },
809    { 0,0,0 }
810};
811
812static WidgetSpec MenubuttonWidgetSpec =
813{
814    "TMenubutton",		/* className */
815    sizeof(Menubutton), 	/* recordSize */
816    MenubuttonOptionSpecs, 	/* optionSpecs */
817    MenubuttonCommands,  	/* subcommands */
818    BaseInitialize,     	/* initializeProc */
819    BaseCleanup,		/* cleanupProc */
820    BaseConfigure,		/* configureProc */
821    BasePostConfigure,  	/* postConfigureProc */
822    TtkWidgetGetLayout, 	/* getLayoutProc */
823    TtkWidgetSize, 		/* sizeProc */
824    TtkWidgetDoLayout,		/* layoutProc */
825    TtkWidgetDisplay		/* displayProc */
826};
827
828TTK_BEGIN_LAYOUT(MenubuttonLayout)
829    TTK_GROUP("Menubutton.border", TTK_FILL_BOTH,
830	TTK_GROUP("Menubutton.focus", TTK_FILL_BOTH,
831	    TTK_NODE("Menubutton.indicator", TTK_PACK_RIGHT)
832	    TTK_GROUP("Menubutton.padding", TTK_PACK_LEFT|TTK_EXPAND|TTK_FILL_X,
833	        TTK_NODE("Menubutton.label", TTK_PACK_LEFT))))
834TTK_END_LAYOUT
835
836/*------------------------------------------------------------------------
837 * +++ Initialization.
838 */
839
840MODULE_SCOPE
841void TtkButton_Init(Tcl_Interp *interp)
842{
843    Ttk_Theme theme = Ttk_GetDefaultTheme(interp);
844
845    Ttk_RegisterLayout(theme, "TLabel", LabelLayout);
846    Ttk_RegisterLayout(theme, "TButton", ButtonLayout);
847    Ttk_RegisterLayout(theme, "TCheckbutton", CheckbuttonLayout);
848    Ttk_RegisterLayout(theme, "TRadiobutton", RadiobuttonLayout);
849    Ttk_RegisterLayout(theme, "TMenubutton", MenubuttonLayout);
850
851    RegisterWidget(interp, "ttk::label", &LabelWidgetSpec);
852    RegisterWidget(interp, "ttk::button", &ButtonWidgetSpec);
853    RegisterWidget(interp, "ttk::checkbutton", &CheckbuttonWidgetSpec);
854    RegisterWidget(interp, "ttk::radiobutton", &RadiobuttonWidgetSpec);
855    RegisterWidget(interp, "ttk::menubutton", &MenubuttonWidgetSpec);
856}
857