2 * Copyright (c) 2004, Joe English
4 * ttk::frame and ttk::labelframe widgets.
10 #include "ttkManager.h"
12 /* ======================================================================
17 Tcl_Obj *borderWidthObj;
29 static Tk_OptionSpec FrameOptionSpecs[] = {
30 {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", NULL,
31 Tk_Offset(Frame,frame.borderWidthObj), -1,
32 TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED },
33 {TK_OPTION_STRING, "-padding", "padding", "Pad", NULL,
34 Tk_Offset(Frame,frame.paddingObj), -1,
35 TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED },
36 {TK_OPTION_RELIEF, "-relief", "relief", "Relief", NULL,
37 Tk_Offset(Frame,frame.reliefObj), -1,
38 TK_OPTION_NULL_OK,0,0 },
39 {TK_OPTION_PIXELS, "-width", "width", "Width", "0",
40 Tk_Offset(Frame,frame.widthObj), -1,
41 0,0,GEOMETRY_CHANGED },
42 {TK_OPTION_PIXELS, "-height", "height", "Height", "0",
43 Tk_Offset(Frame,frame.heightObj), -1,
44 0,0,GEOMETRY_CHANGED },
46 WIDGET_TAKEFOCUS_FALSE,
47 WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs)
50 static const Ttk_Ensemble FrameCommands[] = {
51 { "configure", TtkWidgetConfigureCommand,0 },
52 { "cget", TtkWidgetCgetCommand,0 },
53 { "instate", TtkWidgetInstateCommand,0 },
54 { "state", TtkWidgetStateCommand,0 },
55 { "identify", TtkWidgetIdentifyCommand,0 },
61 * Compute internal margins for a frame widget.
62 * This includes the -borderWidth, plus any additional -padding.
64 static Ttk_Padding FrameMargins(Frame *framePtr)
66 Ttk_Padding margins = Ttk_UniformPadding(0);
70 if (framePtr->frame.paddingObj) {
71 Ttk_GetPaddingFromObj(NULL,
72 framePtr->core.tkwin, framePtr->frame.paddingObj, &margins);
75 /* Add padding for border:
77 if (framePtr->frame.borderWidthObj) {
79 Tk_GetPixelsFromObj(NULL,
80 framePtr->core.tkwin, framePtr->frame.borderWidthObj, &border);
81 margins = Ttk_AddPadding(margins, Ttk_UniformPadding((short)border));
87 /* FrameSize procedure --
88 * The frame doesn't request a size of its own by default,
89 * but it does have an internal border. See also <<NOTE-SIZE>>
96 Frame *framePtr = (Frame *)recordPtr;
97 Ttk_SetMargins(framePtr->core.tkwin, FrameMargins(framePtr));
102 * FrameConfigure -- configure hook.
103 * <<NOTE-SIZE>> Usually the size of a frame is controlled by
104 * a geometry manager (pack, grid); the -width and -height
105 * options are only effective if geometry propagation is turned
106 * off or if the [place] GM is used for child widgets.
108 * To avoid geometry blinking, we issue a geometry request
109 * in the Configure hook instead of the Size hook, and only
110 * if -width and/or -height is nonzero and one of them
111 * or the other size-related options (-borderwidth, -padding)
115 static int FrameConfigure(Tcl_Interp *interp, void *recordPtr, int mask)
117 Frame *framePtr = (Frame *)recordPtr;
121 * Make sure -padding resource, if present, is correct:
123 if (framePtr->frame.paddingObj) {
125 if (Ttk_GetPaddingFromObj(interp,
126 framePtr->core.tkwin,
127 framePtr->frame.paddingObj,
128 &unused) != TCL_OK) {
135 if ( TCL_OK != Tk_GetPixelsFromObj(
136 interp,framePtr->core.tkwin,framePtr->frame.widthObj,&width)
137 || TCL_OK != Tk_GetPixelsFromObj(
138 interp,framePtr->core.tkwin,framePtr->frame.heightObj,&height)
144 if ((width > 0 || height > 0) && (mask & GEOMETRY_CHANGED)) {
145 Tk_GeometryRequest(framePtr->core.tkwin, width, height);
148 return TtkCoreConfigure(interp, recordPtr, mask);
151 static WidgetSpec FrameWidgetSpec = {
152 "TFrame", /* className */
153 sizeof(Frame), /* recordSize */
154 FrameOptionSpecs, /* optionSpecs */
155 FrameCommands, /* subcommands */
156 TtkNullInitialize, /* initializeProc */
157 TtkNullCleanup, /* cleanupProc */
158 FrameConfigure, /* configureProc */
159 TtkNullPostConfigure, /* postConfigureProc */
160 TtkWidgetGetLayout, /* getLayoutProc */
161 FrameSize, /* sizeProc */
162 TtkWidgetDoLayout, /* layoutProc */
163 TtkWidgetDisplay /* displayProc */
166 TTK_BEGIN_LAYOUT(FrameLayout)
167 TTK_NODE("Frame.border", TTK_FILL_BOTH)
170 /* ======================================================================
171 * +++ Labelframe widget:
174 #define DEFAULT_LABELINSET 8
175 #define DEFAULT_BORDERWIDTH 2
177 int TtkGetLabelAnchorFromObj(
178 Tcl_Interp *interp, Tcl_Obj *objPtr, Ttk_PositionSpec *anchorPtr)
180 const char *string = Tcl_GetString(objPtr);
182 Ttk_PositionSpec flags = 0;
184 /* First character determines side:
187 case 'w' : flags = TTK_PACK_LEFT; break;
188 case 'e' : flags = TTK_PACK_RIGHT; break;
189 case 'n' : flags = TTK_PACK_TOP; break;
190 case 's' : flags = TTK_PACK_BOTTOM; break;
191 default : goto error;
194 /* Remaining characters are as per -sticky:
196 while ((c = *string++) != '\0') {
198 case 'w' : flags |= TTK_STICK_W; break;
199 case 'e' : flags |= TTK_STICK_E; break;
200 case 'n' : flags |= TTK_STICK_N; break;
201 case 's' : flags |= TTK_STICK_S; break;
202 default : goto error;
211 Tcl_SetObjResult(interp, Tcl_ObjPrintf(
212 "Bad label anchor specification %s", Tcl_GetString(objPtr)));
213 Tcl_SetErrorCode(interp, "TTK", "LABEL", "ANCHOR", NULL);
218 /* LabelAnchorSide --
219 * Returns the side corresponding to a LabelAnchor value.
221 static Ttk_Side LabelAnchorSide(Ttk_PositionSpec flags)
223 if (flags & TTK_PACK_LEFT) return TTK_SIDE_LEFT;
224 else if (flags & TTK_PACK_RIGHT) return TTK_SIDE_RIGHT;
225 else if (flags & TTK_PACK_TOP) return TTK_SIDE_TOP;
226 else if (flags & TTK_PACK_BOTTOM) return TTK_SIDE_BOTTOM;
232 * Labelframe widget record:
235 Tcl_Obj *labelAnchorObj;
237 Tcl_Obj *underlineObj;
238 Tk_Window labelWidget;
241 Ttk_Layout labelLayout; /* Sublayout for label */
242 Ttk_Box labelParcel; /* Set in layoutProc */
248 LabelframePart label;
251 #define LABELWIDGET_CHANGED 0x100
253 static Tk_OptionSpec LabelframeOptionSpecs[] = {
254 {TK_OPTION_STRING, "-labelanchor", "labelAnchor", "LabelAnchor",
255 "nw", Tk_Offset(Labelframe, label.labelAnchorObj),-1,
256 0,0,GEOMETRY_CHANGED},
257 {TK_OPTION_STRING, "-text", "text", "Text", "",
258 Tk_Offset(Labelframe,label.textObj), -1,
259 0,0,GEOMETRY_CHANGED },
260 {TK_OPTION_INT, "-underline", "underline", "Underline",
261 "-1", Tk_Offset(Labelframe,label.underlineObj), -1,
263 {TK_OPTION_WINDOW, "-labelwidget", "labelWidget", "LabelWidget", NULL,
264 -1, Tk_Offset(Labelframe,label.labelWidget),
265 TK_OPTION_NULL_OK,0,LABELWIDGET_CHANGED|GEOMETRY_CHANGED },
267 WIDGET_INHERIT_OPTIONS(FrameOptionSpecs)
271 * Labelframe style parameters:
274 int borderWidth; /* border width */
275 Ttk_Padding padding; /* internal padding */
276 Ttk_PositionSpec labelAnchor; /* corner/side to place label */
277 Ttk_Padding labelMargins; /* extra space around label */
278 int labelOutside; /* true=>place label outside border */
281 static void LabelframeStyleOptions(Labelframe *lf, LabelframeStyle *style)
283 Ttk_Layout layout = lf->core.layout;
286 style->borderWidth = DEFAULT_BORDERWIDTH;
287 style->padding = Ttk_UniformPadding(0);
288 style->labelAnchor = TTK_PACK_TOP | TTK_STICK_W;
289 style->labelOutside = 0;
291 if ((objPtr = Ttk_QueryOption(layout, "-borderwidth", 0)) != NULL) {
292 Tk_GetPixelsFromObj(NULL, lf->core.tkwin, objPtr, &style->borderWidth);
294 if ((objPtr = Ttk_QueryOption(layout, "-padding", 0)) != NULL) {
295 Ttk_GetPaddingFromObj(NULL, lf->core.tkwin, objPtr, &style->padding);
297 if ((objPtr = Ttk_QueryOption(layout,"-labelanchor", 0)) != NULL) {
298 TtkGetLabelAnchorFromObj(NULL, objPtr, &style->labelAnchor);
300 if ((objPtr = Ttk_QueryOption(layout,"-labelmargins", 0)) != NULL) {
301 Ttk_GetBorderFromObj(NULL, objPtr, &style->labelMargins);
303 if (style->labelAnchor & (TTK_PACK_TOP|TTK_PACK_BOTTOM)) {
304 style->labelMargins =
305 Ttk_MakePadding(DEFAULT_LABELINSET,0,DEFAULT_LABELINSET,0);
307 style->labelMargins =
308 Ttk_MakePadding(0,DEFAULT_LABELINSET,0,DEFAULT_LABELINSET);
311 if ((objPtr = Ttk_QueryOption(layout,"-labeloutside", 0)) != NULL) {
312 Tcl_GetBooleanFromObj(NULL, objPtr, &style->labelOutside);
318 /* LabelframeLabelSize --
319 * Extract the requested width and height of the labelframe's label:
320 * taken from the label widget if specified, otherwise the text label.
323 LabelframeLabelSize(Labelframe *lframePtr, int *widthPtr, int *heightPtr)
325 Tk_Window labelWidget = lframePtr->label.labelWidget;
326 Ttk_Layout labelLayout = lframePtr->label.labelLayout;
329 *widthPtr = Tk_ReqWidth(labelWidget);
330 *heightPtr = Tk_ReqHeight(labelWidget);
331 } else if (labelLayout) {
332 Ttk_LayoutSize(labelLayout, 0, widthPtr, heightPtr);
334 *widthPtr = *heightPtr = 0;
340 * Like the frame, this doesn't request a size of its own
341 * but it does have internal padding and a minimum size.
343 static int LabelframeSize(
348 Labelframe *lframePtr = (Labelframe *)recordPtr;
349 WidgetCore *corePtr = &lframePtr->core;
351 LabelframeStyle style;
352 int labelWidth, labelHeight;
354 LabelframeStyleOptions(lframePtr, &style);
356 /* Compute base margins (See also: FrameMargins)
358 margins = Ttk_AddPadding(
359 style.padding, Ttk_UniformPadding((short)style.borderWidth));
361 /* Adjust margins based on label size and position:
363 LabelframeLabelSize(lframePtr, &labelWidth, &labelHeight);
364 labelWidth += Ttk_PaddingWidth(style.labelMargins);
365 labelHeight += Ttk_PaddingHeight(style.labelMargins);
367 switch (LabelAnchorSide(style.labelAnchor)) {
368 case TTK_SIDE_LEFT: margins.left += labelWidth; break;
369 case TTK_SIDE_RIGHT: margins.right += labelWidth; break;
370 case TTK_SIDE_TOP: margins.top += labelHeight; break;
371 case TTK_SIDE_BOTTOM: margins.bottom += labelHeight; break;
374 Ttk_SetMargins(corePtr->tkwin,margins);
376 /* Request minimum size based on border width and label size:
378 Tk_SetMinimumRequestSize(corePtr->tkwin,
379 labelWidth + 2*style.borderWidth,
380 labelHeight + 2*style.borderWidth);
386 * LabelframeGetLayout --
387 * Getlayout widget hook.
390 static Ttk_Layout LabelframeGetLayout(
391 Tcl_Interp *interp, Ttk_Theme theme, void *recordPtr)
393 Labelframe *lf = (Labelframe *)recordPtr;
394 Ttk_Layout frameLayout = TtkWidgetGetLayout(interp, theme, recordPtr);
395 Ttk_Layout labelLayout;
401 labelLayout = Ttk_CreateSublayout(
402 interp, theme, frameLayout, ".Label", lf->core.optionTable);
405 if (lf->label.labelLayout) {
406 Ttk_FreeLayout(lf->label.labelLayout);
408 Ttk_RebindSublayout(labelLayout, recordPtr);
409 lf->label.labelLayout = labelLayout;
416 * LabelframeDoLayout --
417 * Labelframe layout hook.
419 * Side effects: Computes labelParcel.
422 static void LabelframeDoLayout(void *recordPtr)
424 Labelframe *lframePtr = (Labelframe *)recordPtr;
425 WidgetCore *corePtr = &lframePtr->core;
426 int lw, lh; /* Label width and height */
427 LabelframeStyle style;
428 Ttk_Box borderParcel = Ttk_WinBox(lframePtr->core.tkwin);
432 * Compute label parcel:
434 LabelframeStyleOptions(lframePtr, &style);
435 LabelframeLabelSize(lframePtr, &lw, &lh);
436 lw += Ttk_PaddingWidth(style.labelMargins);
437 lh += Ttk_PaddingHeight(style.labelMargins);
439 labelParcel = Ttk_PadBox(
440 Ttk_PositionBox(&borderParcel, lw, lh, style.labelAnchor),
443 if (!style.labelOutside) {
444 /* Move border edge so it's over label:
446 switch (LabelAnchorSide(style.labelAnchor)) {
447 case TTK_SIDE_LEFT: borderParcel.x -= lw / 2;
449 case TTK_SIDE_RIGHT: borderParcel.width += lw/2; break;
450 case TTK_SIDE_TOP: borderParcel.y -= lh / 2;
452 case TTK_SIDE_BOTTOM: borderParcel.height += lh / 2; break;
457 * Place border and label:
459 Ttk_PlaceLayout(corePtr->layout, corePtr->state, borderParcel);
460 if (lframePtr->label.labelLayout) {
462 lframePtr->label.labelLayout, corePtr->state, labelParcel);
464 /* labelWidget placed in LabelframePlaceContent GM hook */
465 lframePtr->label.labelParcel = labelParcel;
468 static void LabelframeDisplay(void *recordPtr, Drawable d)
470 Labelframe *lframePtr = (Labelframe *)recordPtr;
471 Ttk_DrawLayout(lframePtr->core.layout, lframePtr->core.state, d);
472 if (lframePtr->label.labelLayout) {
473 Ttk_DrawLayout(lframePtr->label.labelLayout, lframePtr->core.state, d);
477 /* +++ Labelframe geometry manager hooks.
480 /* LabelframePlaceContent --
481 * Sets the position and size of the labelwidget.
483 static void LabelframePlaceContent(void *recordPtr)
485 Labelframe *lframe = (Labelframe *)recordPtr;
487 if (Ttk_NumberContent(lframe->label.mgr) == 1) {
489 LabelframeDoLayout(recordPtr);
490 b = lframe->label.labelParcel;
491 /* ASSERT: content #0 is lframe->label.labelWidget */
492 Ttk_PlaceContent(lframe->label.mgr, 0, b.x,b.y,b.width,b.height);
496 static int LabelRequest(
506 * Unset the -labelwidget option.
508 * <<NOTE-LABELREMOVED>>:
509 * This routine is also called when the widget voluntarily forgets
510 * the window in LabelframeConfigure.
512 static void LabelRemoved(
516 Labelframe *lframe = (Labelframe *)managerData;
518 lframe->label.labelWidget = 0;
521 static Ttk_ManagerSpec LabelframeManagerSpec = {
522 { "labelframe", Ttk_GeometryRequestProc, Ttk_LostContentProc },
524 LabelframePlaceContent,
529 /* LabelframeInitialize --
530 * Initialization hook.
532 static void LabelframeInitialize(
533 TCL_UNUSED(Tcl_Interp *),
536 Labelframe *lframe = (Labelframe *)recordPtr;
538 lframe->label.mgr = Ttk_CreateManager(
539 &LabelframeManagerSpec, lframe, lframe->core.tkwin);
540 lframe->label.labelWidget = 0;
541 lframe->label.labelLayout = 0;
542 lframe->label.labelParcel = Ttk_MakeBox(-1,-1,-1,-1);
545 /* LabelframeCleanup --
548 static void LabelframeCleanup(void *recordPtr)
550 Labelframe *lframe = (Labelframe *)recordPtr;
551 Ttk_DeleteManager(lframe->label.mgr);
552 if (lframe->label.labelLayout) {
553 Ttk_FreeLayout(lframe->label.labelLayout);
557 /* RaiseLabelWidget --
558 * Raise the -labelwidget to ensure that the labelframe doesn't
559 * obscure it (if it's not a direct child), or bring it to
560 * the top of the stacking order (if it is).
562 static void RaiseLabelWidget(Labelframe *lframe)
564 Tk_Window parent = Tk_Parent(lframe->label.labelWidget);
565 Tk_Window sibling = NULL;
566 Tk_Window w = lframe->core.tkwin;
568 while (w && w != parent) {
573 Tk_RestackWindow(lframe->label.labelWidget, Above, sibling);
576 /* LabelframeConfigure --
577 * Configuration hook.
579 static int LabelframeConfigure(Tcl_Interp *interp,void *recordPtr,int mask)
581 Labelframe *lframePtr = (Labelframe *)recordPtr;
582 Tk_Window labelWidget = lframePtr->label.labelWidget;
583 Ttk_PositionSpec unused;
587 if (mask & LABELWIDGET_CHANGED && labelWidget != NULL) {
588 if (!Ttk_Maintainable(interp, labelWidget, lframePtr->core.tkwin)) {
593 if (TtkGetLabelAnchorFromObj(
594 interp, lframePtr->label.labelAnchorObj, &unused) != TCL_OK)
599 /* Base class configuration:
601 if (FrameConfigure(interp, recordPtr, mask) != TCL_OK) {
605 /* Update -labelwidget changes, if any:
607 if (mask & LABELWIDGET_CHANGED) {
608 if (Ttk_NumberContent(lframePtr->label.mgr) == 1) {
609 Ttk_ForgetContent(lframePtr->label.mgr, 0);
610 /* Restore labelWidget field (see <<NOTE-LABELREMOVED>>)
612 lframePtr->label.labelWidget = labelWidget;
616 Ttk_InsertContent(lframePtr->label.mgr, 0, labelWidget, NULL);
617 RaiseLabelWidget(lframePtr);
621 if (mask & GEOMETRY_CHANGED) {
622 Ttk_ManagerSizeChanged(lframePtr->label.mgr);
623 Ttk_ManagerLayoutChanged(lframePtr->label.mgr);
629 static WidgetSpec LabelframeWidgetSpec = {
630 "TLabelframe", /* className */
631 sizeof(Labelframe), /* recordSize */
632 LabelframeOptionSpecs, /* optionSpecs */
633 FrameCommands, /* subcommands */
634 LabelframeInitialize, /* initializeProc */
635 LabelframeCleanup, /* cleanupProc */
636 LabelframeConfigure, /* configureProc */
637 TtkNullPostConfigure, /* postConfigureProc */
638 LabelframeGetLayout, /* getLayoutProc */
639 LabelframeSize, /* sizeProc */
640 LabelframeDoLayout, /* layoutProc */
641 LabelframeDisplay /* displayProc */
644 TTK_BEGIN_LAYOUT(LabelframeLayout)
645 TTK_NODE("Labelframe.border", TTK_FILL_BOTH)
648 TTK_BEGIN_LAYOUT(LabelSublayout)
649 TTK_GROUP("Label.fill", TTK_FILL_BOTH,
650 TTK_NODE("Label.text", TTK_FILL_BOTH))
653 /* ======================================================================
654 * +++ Initialization.
658 void TtkFrame_Init(Tcl_Interp *interp)
660 Ttk_Theme theme = Ttk_GetDefaultTheme(interp);
662 Ttk_RegisterLayout(theme, "TFrame", FrameLayout);
663 Ttk_RegisterLayout(theme, "TLabelframe", LabelframeLayout);
664 Ttk_RegisterLayout(theme, "Label", LabelSublayout);
666 RegisterWidget(interp, "ttk::frame", &FrameWidgetSpec);
667 RegisterWidget(interp, "ttk::labelframe", &LabelframeWidgetSpec);