diff options
Diffstat (limited to 'generic/ttk/ttkFrame.c')
-rw-r--r-- | generic/ttk/ttkFrame.c | 653 |
1 files changed, 653 insertions, 0 deletions
diff --git a/generic/ttk/ttkFrame.c b/generic/ttk/ttkFrame.c new file mode 100644 index 0000000..5971e0d --- /dev/null +++ b/generic/ttk/ttkFrame.c @@ -0,0 +1,653 @@ +/* + * Copyright (c) 2004, Joe English + * + * ttk::frame and ttk::labelframe widgets. + */ + +#include <tk.h> + +#include "ttkTheme.h" +#include "ttkWidget.h" +#include "ttkManager.h" + +/* ====================================================================== + * +++ Frame widget: + */ + +typedef struct { + Tcl_Obj *borderWidthObj; + Tcl_Obj *paddingObj; + Tcl_Obj *reliefObj; + Tcl_Obj *widthObj; + Tcl_Obj *heightObj; +} FramePart; + +typedef struct { + WidgetCore core; + FramePart frame; +} Frame; + +static Tk_OptionSpec FrameOptionSpecs[] = { + {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", NULL, + Tk_Offset(Frame,frame.borderWidthObj), -1, + TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED }, + {TK_OPTION_STRING, "-padding", "padding", "Pad", NULL, + Tk_Offset(Frame,frame.paddingObj), -1, + TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED }, + {TK_OPTION_RELIEF, "-relief", "relief", "Relief", NULL, + Tk_Offset(Frame,frame.reliefObj), -1, + TK_OPTION_NULL_OK,0,0 }, + {TK_OPTION_PIXELS, "-width", "width", "Width", "0", + Tk_Offset(Frame,frame.widthObj), -1, + 0,0,GEOMETRY_CHANGED }, + {TK_OPTION_PIXELS, "-height", "height", "Height", "0", + Tk_Offset(Frame,frame.heightObj), -1, + 0,0,GEOMETRY_CHANGED }, + + WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs) +}; + +static const Ttk_Ensemble FrameCommands[] = { + { "configure", TtkWidgetConfigureCommand,0 }, + { "cget", TtkWidgetCgetCommand,0 }, + { "instate", TtkWidgetInstateCommand,0 }, + { "state", TtkWidgetStateCommand,0 }, + { "identify", TtkWidgetIdentifyCommand,0 }, + { 0,0,0 } +}; + +/* + * FrameMargins -- + * Compute internal margins for a frame widget. + * This includes the -borderWidth, plus any additional -padding. + */ +static Ttk_Padding FrameMargins(Frame *framePtr) +{ + Ttk_Padding margins = Ttk_UniformPadding(0); + + /* Check -padding: + */ + if (framePtr->frame.paddingObj) { + Ttk_GetPaddingFromObj(NULL, + framePtr->core.tkwin, framePtr->frame.paddingObj, &margins); + } + + /* Add padding for border: + */ + if (framePtr->frame.borderWidthObj) { + int border = 0; + Tk_GetPixelsFromObj(NULL, + framePtr->core.tkwin, framePtr->frame.borderWidthObj, &border); + margins = Ttk_AddPadding(margins, Ttk_UniformPadding((short)border)); + } + + return margins; +} + +/* FrameSize procedure -- + * The frame doesn't request a size of its own by default, + * but it does have an internal border. See also <<NOTE-SIZE>> + */ +static int FrameSize(void *recordPtr, int *widthPtr, int *heightPtr) +{ + Frame *framePtr = recordPtr; + Ttk_SetMargins(framePtr->core.tkwin, FrameMargins(framePtr)); + return 0; +} + +/* + * FrameConfigure -- configure hook. + * <<NOTE-SIZE>> Usually the size of a frame is controlled by + * a geometry manager (pack, grid); the -width and -height + * options are only effective if geometry propagation is turned + * off or if the [place] GM is used for child widgets. + * + * To avoid geometry blinking, we issue a geometry request + * in the Configure hook instead of the Size hook, and only + * if -width and/or -height is nonzero and one of them + * or the other size-related options (-borderwidth, -padding) + * has been changed. + */ + +static int FrameConfigure(Tcl_Interp *interp, void *recordPtr, int mask) +{ + Frame *framePtr = recordPtr; + int width, height; + + /* + * Make sure -padding resource, if present, is correct: + */ + if (framePtr->frame.paddingObj) { + Ttk_Padding unused; + if (Ttk_GetPaddingFromObj(interp, + framePtr->core.tkwin, + framePtr->frame.paddingObj, + &unused) != TCL_OK) { + return TCL_ERROR; + } + } + + /* See <<NOTE-SIZE>> + */ + if ( TCL_OK != Tk_GetPixelsFromObj( + interp,framePtr->core.tkwin,framePtr->frame.widthObj,&width) + || TCL_OK != Tk_GetPixelsFromObj( + interp,framePtr->core.tkwin,framePtr->frame.heightObj,&height) + ) + { + return TCL_ERROR; + } + + if ((width > 0 || height > 0) && (mask & GEOMETRY_CHANGED)) { + Tk_GeometryRequest(framePtr->core.tkwin, width, height); + } + + return TtkCoreConfigure(interp, recordPtr, mask); +} + +static WidgetSpec FrameWidgetSpec = { + "TFrame", /* className */ + sizeof(Frame), /* recordSize */ + FrameOptionSpecs, /* optionSpecs */ + FrameCommands, /* subcommands */ + TtkNullInitialize, /* initializeProc */ + TtkNullCleanup, /* cleanupProc */ + FrameConfigure, /* configureProc */ + TtkNullPostConfigure, /* postConfigureProc */ + TtkWidgetGetLayout, /* getLayoutProc */ + FrameSize, /* sizeProc */ + TtkWidgetDoLayout, /* layoutProc */ + TtkWidgetDisplay /* displayProc */ +}; + +TTK_BEGIN_LAYOUT(FrameLayout) + TTK_NODE("Frame.border", TTK_FILL_BOTH) +TTK_END_LAYOUT + +/* ====================================================================== + * +++ Labelframe widget: + */ + +#define DEFAULT_LABELINSET 8 +#define DEFAULT_BORDERWIDTH 2 + +int TtkGetLabelAnchorFromObj( + Tcl_Interp *interp, Tcl_Obj *objPtr, Ttk_PositionSpec *anchorPtr) +{ + const char *string = Tcl_GetString(objPtr); + char c = *string++; + Ttk_PositionSpec flags = 0; + + /* First character determines side: + */ + switch (c) { + case 'w' : flags = TTK_PACK_LEFT; break; + case 'e' : flags = TTK_PACK_RIGHT; break; + case 'n' : flags = TTK_PACK_TOP; break; + case 's' : flags = TTK_PACK_BOTTOM; break; + default : goto error; + } + + /* Remaining characters are as per -sticky: + */ + while ((c = *string++) != '\0') { + switch (c) { + case 'w' : flags |= TTK_STICK_W; break; + case 'e' : flags |= TTK_STICK_E; break; + case 'n' : flags |= TTK_STICK_N; break; + case 's' : flags |= TTK_STICK_S; break; + default : goto error; + } + } + + *anchorPtr = flags; + return TCL_OK; + +error: + if (interp) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, + "Bad label anchor specification ", Tcl_GetString(objPtr), + NULL); + } + return TCL_ERROR; +} + +/* LabelAnchorSide -- + * Returns the side corresponding to a LabelAnchor value. + */ +static Ttk_Side LabelAnchorSide(Ttk_PositionSpec flags) +{ + if (flags & TTK_PACK_LEFT) return TTK_SIDE_LEFT; + else if (flags & TTK_PACK_RIGHT) return TTK_SIDE_RIGHT; + else if (flags & TTK_PACK_TOP) return TTK_SIDE_TOP; + else if (flags & TTK_PACK_BOTTOM) return TTK_SIDE_BOTTOM; + /*NOTREACHED*/ + return TTK_SIDE_TOP; +} + +/* + * Labelframe widget record: + */ +typedef struct { + Tcl_Obj *labelAnchorObj; + Tcl_Obj *textObj; + Tcl_Obj *underlineObj; + Tk_Window labelWidget; + + Ttk_Manager *mgr; + Ttk_Layout labelLayout; /* Sublayout for label */ + Ttk_Box labelParcel; /* Set in layoutProc */ +} LabelframePart; + +typedef struct { + WidgetCore core; + FramePart frame; + LabelframePart label; +} Labelframe; + +#define LABELWIDGET_CHANGED 0x100 + +static Tk_OptionSpec LabelframeOptionSpecs[] = { + {TK_OPTION_STRING, "-labelanchor", "labelAnchor", "LabelAnchor", + "nw", Tk_Offset(Labelframe, label.labelAnchorObj),-1, + 0,0,GEOMETRY_CHANGED}, + {TK_OPTION_STRING, "-text", "text", "Text", "", + Tk_Offset(Labelframe,label.textObj), -1, + 0,0,GEOMETRY_CHANGED }, + {TK_OPTION_INT, "-underline", "underline", "Underline", + "-1", Tk_Offset(Labelframe,label.underlineObj), -1, + 0,0,0 }, + {TK_OPTION_WINDOW, "-labelwidget", "labelWidget", "LabelWidget", NULL, + -1, Tk_Offset(Labelframe,label.labelWidget), + TK_OPTION_NULL_OK,0,LABELWIDGET_CHANGED|GEOMETRY_CHANGED }, + + WIDGET_INHERIT_OPTIONS(FrameOptionSpecs) +}; + +/* + * Labelframe style parameters: + */ +typedef struct { + int borderWidth; /* border width */ + Ttk_Padding padding; /* internal padding */ + Ttk_PositionSpec labelAnchor; /* corner/side to place label */ + Ttk_Padding labelMargins; /* extra space around label */ + int labelOutside; /* true=>place label outside border */ +} LabelframeStyle; + +static void LabelframeStyleOptions(Labelframe *lf, LabelframeStyle *style) +{ + Ttk_Layout layout = lf->core.layout; + Tcl_Obj *objPtr; + + style->borderWidth = DEFAULT_BORDERWIDTH; + style->padding = Ttk_UniformPadding(0); + style->labelAnchor = TTK_PACK_TOP | TTK_STICK_W; + style->labelOutside = 0; + + if ((objPtr = Ttk_QueryOption(layout, "-borderwidth", 0)) != NULL) { + Tk_GetPixelsFromObj(NULL, lf->core.tkwin, objPtr, &style->borderWidth); + } + if ((objPtr = Ttk_QueryOption(layout, "-padding", 0)) != NULL) { + Ttk_GetPaddingFromObj(NULL, lf->core.tkwin, objPtr, &style->padding); + } + if ((objPtr = Ttk_QueryOption(layout,"-labelanchor", 0)) != NULL) { + TtkGetLabelAnchorFromObj(NULL, objPtr, &style->labelAnchor); + } + if ((objPtr = Ttk_QueryOption(layout,"-labelmargins", 0)) != NULL) { + Ttk_GetBorderFromObj(NULL, objPtr, &style->labelMargins); + } else { + if (style->labelAnchor & (TTK_PACK_TOP|TTK_PACK_BOTTOM)) { + style->labelMargins = + Ttk_MakePadding(DEFAULT_LABELINSET,0,DEFAULT_LABELINSET,0); + } else { + style->labelMargins = + Ttk_MakePadding(0,DEFAULT_LABELINSET,0,DEFAULT_LABELINSET); + } + } + if ((objPtr = Ttk_QueryOption(layout,"-labeloutside", 0)) != NULL) { + Tcl_GetBooleanFromObj(NULL, objPtr, &style->labelOutside); + } + + return; +} + +/* LabelframeLabelSize -- + * Extract the requested width and height of the labelframe's label: + * taken from the label widget if specified, otherwise the text label. + */ +static void +LabelframeLabelSize(Labelframe *lframePtr, int *widthPtr, int *heightPtr) +{ + Tk_Window labelWidget = lframePtr->label.labelWidget; + Ttk_Layout labelLayout = lframePtr->label.labelLayout; + + if (labelWidget) { + *widthPtr = Tk_ReqWidth(labelWidget); + *heightPtr = Tk_ReqHeight(labelWidget); + } else if (labelLayout) { + Ttk_LayoutSize(labelLayout, 0, widthPtr, heightPtr); + } else { + *widthPtr = *heightPtr = 0; + } +} + +/* + * LabelframeSize -- + * Like the frame, this doesn't request a size of its own + * but it does have internal padding and a minimum size. + */ +static int LabelframeSize(void *recordPtr, int *widthPtr, int *heightPtr) +{ + Labelframe *lframePtr = recordPtr; + WidgetCore *corePtr = &lframePtr->core; + Ttk_Padding margins; + LabelframeStyle style; + int labelWidth, labelHeight; + + LabelframeStyleOptions(lframePtr, &style); + + /* Compute base margins (See also: FrameMargins) + */ + margins = Ttk_AddPadding( + style.padding, Ttk_UniformPadding((short)style.borderWidth)); + + /* Adjust margins based on label size and position: + */ + LabelframeLabelSize(lframePtr, &labelWidth, &labelHeight); + labelWidth += Ttk_PaddingWidth(style.labelMargins); + labelHeight += Ttk_PaddingHeight(style.labelMargins); + + switch (LabelAnchorSide(style.labelAnchor)) { + case TTK_SIDE_LEFT: margins.left += labelWidth; break; + case TTK_SIDE_RIGHT: margins.right += labelWidth; break; + case TTK_SIDE_TOP: margins.top += labelHeight; break; + case TTK_SIDE_BOTTOM: margins.bottom += labelHeight; break; + } + + Ttk_SetMargins(corePtr->tkwin,margins); + + /* Request minimum size based on border width and label size: + */ + Tk_SetMinimumRequestSize(corePtr->tkwin, + labelWidth + 2*style.borderWidth, + labelHeight + 2*style.borderWidth); + + return 0; +} + +/* + * LabelframeGetLayout -- + * Getlayout widget hook. + */ + +static Ttk_Layout LabelframeGetLayout( + Tcl_Interp *interp, Ttk_Theme theme, void *recordPtr) +{ + Labelframe *lf = recordPtr; + Ttk_Layout frameLayout = TtkWidgetGetLayout(interp, theme, recordPtr); + Ttk_Layout labelLayout; + + if (!frameLayout) { + return NULL; + } + + labelLayout = Ttk_CreateSublayout( + interp, theme, frameLayout, ".Label", lf->core.optionTable); + + if (labelLayout) { + if (lf->label.labelLayout) { + Ttk_FreeLayout(lf->label.labelLayout); + } + Ttk_RebindSublayout(labelLayout, recordPtr); + lf->label.labelLayout = labelLayout; + } + + return frameLayout; +} + +/* + * LabelframeDoLayout -- + * Labelframe layout hook. + * + * Side effects: Computes labelParcel. + */ + +static void LabelframeDoLayout(void *recordPtr) +{ + Labelframe *lframePtr = recordPtr; + WidgetCore *corePtr = &lframePtr->core; + int lw, lh; /* Label width and height */ + LabelframeStyle style; + Ttk_Box borderParcel = Ttk_WinBox(lframePtr->core.tkwin); + Ttk_Box labelParcel; + + /* + * Compute label parcel: + */ + LabelframeStyleOptions(lframePtr, &style); + LabelframeLabelSize(lframePtr, &lw, &lh); + lw += Ttk_PaddingWidth(style.labelMargins); + lh += Ttk_PaddingHeight(style.labelMargins); + + labelParcel = Ttk_PadBox( + Ttk_PositionBox(&borderParcel, lw, lh, style.labelAnchor), + style.labelMargins); + + if (!style.labelOutside) { + /* Move border edge so it's over label: + */ + switch (LabelAnchorSide(style.labelAnchor)) { + case TTK_SIDE_LEFT: borderParcel.x -= lw / 2; + case TTK_SIDE_RIGHT: borderParcel.width += lw/2; break; + case TTK_SIDE_TOP: borderParcel.y -= lh / 2; + case TTK_SIDE_BOTTOM: borderParcel.height += lh / 2; break; + } + } + + /* + * Place border and label: + */ + Ttk_PlaceLayout(corePtr->layout, corePtr->state, borderParcel); + if (lframePtr->label.labelLayout) { + Ttk_PlaceLayout( + lframePtr->label.labelLayout, corePtr->state, labelParcel); + } + /* labelWidget placed in LabelframePlaceSlaves GM hook */ + lframePtr->label.labelParcel = labelParcel; +} + +static void LabelframeDisplay(void *recordPtr, Drawable d) +{ + Labelframe *lframePtr = recordPtr; + Ttk_DrawLayout(lframePtr->core.layout, lframePtr->core.state, d); + if (lframePtr->label.labelLayout) { + Ttk_DrawLayout(lframePtr->label.labelLayout, lframePtr->core.state, d); + } +} + +/* +++ Labelframe geometry manager hooks. + */ + +/* LabelframePlaceSlaves -- + * Sets the position and size of the labelwidget. + */ +static void LabelframePlaceSlaves(void *recordPtr) +{ + Labelframe *lframe = recordPtr; + + if (Ttk_NumberSlaves(lframe->label.mgr) == 1) { + Ttk_Box b; + LabelframeDoLayout(recordPtr); + b = lframe->label.labelParcel; + /* ASSERT: slave #0 is lframe->label.labelWidget */ + Ttk_PlaceSlave(lframe->label.mgr, 0, b.x,b.y,b.width,b.height); + } +} + +static int LabelRequest(void *managerData, int index, int width, int height) +{ + return 1; +} + +/* LabelRemoved -- + * Unset the -labelwidget option. + * + * <<NOTE-LABELREMOVED>>: + * This routine is also called when the widget voluntarily forgets + * the slave in LabelframeConfigure. + */ +static void LabelRemoved(void *managerData, int slaveIndex) +{ + Labelframe *lframe = managerData; + lframe->label.labelWidget = 0; +} + +static Ttk_ManagerSpec LabelframeManagerSpec = { + { "labelframe", Ttk_GeometryRequestProc, Ttk_LostSlaveProc }, + LabelframeSize, + LabelframePlaceSlaves, + LabelRequest, + LabelRemoved +}; + +/* LabelframeInitialize -- + * Initialization hook. + */ +static void LabelframeInitialize(Tcl_Interp *interp, void *recordPtr) +{ + Labelframe *lframe = recordPtr; + + lframe->label.mgr = Ttk_CreateManager( + &LabelframeManagerSpec, lframe, lframe->core.tkwin); + lframe->label.labelWidget = 0; + lframe->label.labelLayout = 0; + lframe->label.labelParcel = Ttk_MakeBox(-1,-1,-1,-1); +} + +/* LabelframeCleanup -- + * Cleanup hook. + */ +static void LabelframeCleanup(void *recordPtr) +{ + Labelframe *lframe = recordPtr; + Ttk_DeleteManager(lframe->label.mgr); + if (lframe->label.labelLayout) { + Ttk_FreeLayout(lframe->label.labelLayout); + } +} + +/* RaiseLabelWidget -- + * Raise the -labelwidget to ensure that the labelframe doesn't + * obscure it (if it's not a direct child), or bring it to + * the top of the stacking order (if it is). + */ +static void RaiseLabelWidget(Labelframe *lframe) +{ + Tk_Window parent = Tk_Parent(lframe->label.labelWidget); + Tk_Window sibling = NULL; + Tk_Window w = lframe->core.tkwin; + + while (w && w != parent) { + sibling = w; + w = Tk_Parent(w); + } + + Tk_RestackWindow(lframe->label.labelWidget, Above, sibling); +} + +/* LabelframeConfigure -- + * Configuration hook. + */ +static int LabelframeConfigure(Tcl_Interp *interp,void *recordPtr,int mask) +{ + Labelframe *lframePtr = recordPtr; + Tk_Window labelWidget = lframePtr->label.labelWidget; + Ttk_PositionSpec unused; + + /* Validate options: + */ + if (mask & LABELWIDGET_CHANGED && labelWidget != NULL) { + if (!Ttk_Maintainable(interp, labelWidget, lframePtr->core.tkwin)) { + return TCL_ERROR; + } + } + + if (TtkGetLabelAnchorFromObj( + interp, lframePtr->label.labelAnchorObj, &unused) != TCL_OK) + { + return TCL_ERROR; + } + + /* Base class configuration: + */ + if (FrameConfigure(interp, recordPtr, mask) != TCL_OK) { + return TCL_ERROR; + } + + /* Update -labelwidget changes, if any: + */ + if (mask & LABELWIDGET_CHANGED) { + if (Ttk_NumberSlaves(lframePtr->label.mgr) == 1) { + Ttk_ForgetSlave(lframePtr->label.mgr, 0); + /* Restore labelWidget field (see <<NOTE-LABELREMOVED>>) + */ + lframePtr->label.labelWidget = labelWidget; + } + + if (labelWidget) { + Ttk_InsertSlave(lframePtr->label.mgr, 0, labelWidget, NULL); + RaiseLabelWidget(lframePtr); + } + } + + if (mask & GEOMETRY_CHANGED) { + Ttk_ManagerSizeChanged(lframePtr->label.mgr); + Ttk_ManagerLayoutChanged(lframePtr->label.mgr); + } + + return TCL_OK; +} + +static WidgetSpec LabelframeWidgetSpec = { + "TLabelframe", /* className */ + sizeof(Labelframe), /* recordSize */ + LabelframeOptionSpecs, /* optionSpecs */ + FrameCommands, /* subcommands */ + LabelframeInitialize, /* initializeProc */ + LabelframeCleanup, /* cleanupProc */ + LabelframeConfigure, /* configureProc */ + TtkNullPostConfigure, /* postConfigureProc */ + LabelframeGetLayout, /* getLayoutProc */ + LabelframeSize, /* sizeProc */ + LabelframeDoLayout, /* layoutProc */ + LabelframeDisplay /* displayProc */ +}; + +TTK_BEGIN_LAYOUT(LabelframeLayout) + TTK_NODE("Labelframe.border", TTK_FILL_BOTH) +TTK_END_LAYOUT + +TTK_BEGIN_LAYOUT(LabelSublayout) + TTK_GROUP("Label.fill", TTK_FILL_BOTH, + TTK_NODE("Label.text", TTK_FILL_BOTH)) +TTK_END_LAYOUT + +/* ====================================================================== + * +++ Initialization. + */ + +MODULE_SCOPE +void TtkFrame_Init(Tcl_Interp *interp) +{ + Ttk_Theme theme = Ttk_GetDefaultTheme(interp); + + Ttk_RegisterLayout(theme, "TFrame", FrameLayout); + Ttk_RegisterLayout(theme, "TLabelframe", LabelframeLayout); + Ttk_RegisterLayout(theme, "Label", LabelSublayout); + + RegisterWidget(interp, "ttk::frame", &FrameWidgetSpec); + RegisterWidget(interp, "ttk::labelframe", &LabelframeWidgetSpec); +} + |