1/* square.c - Copyright (C) 2004 Pat Thoyts <patthoyts@users.sourceforge.net>
2 *
3 * Minimal sample ttk widget.
4 *
5 * $Id$
6 */
7
8#include <tk.h>
9#include "ttkTheme.h"
10#include "ttkWidget.h"
11
12#if defined(TTK_SQUARE_WIDGET) || 1
13
14#ifndef DEFAULT_BORDERWIDTH
15#define DEFAULT_BORDERWIDTH "2"
16#endif
17
18/*
19 * First, we setup the widget record. The Ttk package provides a structure
20 * that contains standard widget data so it is only necessary to define
21 * a structure that holds the data required for our widget. We do this by
22 * defining a widget part and then specifying the widget record as the
23 * concatenation of the two structures.
24 */
25
26typedef struct
27{
28    Tcl_Obj *widthObj;
29    Tcl_Obj *heightObj;
30    Tcl_Obj *reliefObj;
31    Tcl_Obj *borderWidthObj;
32    Tcl_Obj *foregroundObj;
33    Tcl_Obj *paddingObj;
34    Tcl_Obj *anchorObj;
35} SquarePart;
36
37typedef struct
38{
39    WidgetCore core;
40    SquarePart square;
41} Square;
42
43/*
44 * Widget options.
45 *
46 * This structure is the same as the option specification structure used
47 * for Tk widgets. For each option we provide the type, name and options
48 * database name and class name and the position in the structure and
49 * default values. At the bottom we bring in the standard widget option
50 * defined for all widgets.
51 */
52
53static Tk_OptionSpec SquareOptionSpecs[] =
54{
55    WIDGET_TAKES_FOCUS,
56
57    {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
58     DEFAULT_BORDERWIDTH, Tk_Offset(Square,square.borderWidthObj), -1,
59     0,0,GEOMETRY_CHANGED },
60    {TK_OPTION_BORDER, "-foreground", "foreground", "Foreground",
61     DEFAULT_BACKGROUND, Tk_Offset(Square,square.foregroundObj),
62     -1, 0, 0, 0},
63
64    {TK_OPTION_PIXELS, "-width", "width", "Width",
65     "50", Tk_Offset(Square,square.widthObj), -1, 0, 0,
66     GEOMETRY_CHANGED},
67    {TK_OPTION_PIXELS, "-height", "height", "Height",
68     "50", Tk_Offset(Square,square.heightObj), -1, 0, 0,
69     GEOMETRY_CHANGED},
70
71    {TK_OPTION_STRING, "-padding", "padding", "Pad", NULL,
72     Tk_Offset(Square,square.paddingObj), -1,
73     TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED },
74
75    {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
76     NULL, Tk_Offset(Square,square.reliefObj), -1, TK_OPTION_NULL_OK, 0, 0},
77
78    {TK_OPTION_ANCHOR, "-anchor", "anchor", "Anchor",
79     NULL, Tk_Offset(Square,square.anchorObj), -1, TK_OPTION_NULL_OK, 0, 0},
80
81    WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs)
82};
83
84/*
85 * Almost all of the widget functionality is handled by the default Ttk
86 * widget code and the contained element. The one thing that we must handle
87 * is the -anchor option which positions the square element within the parcel
88 * of space available for the widget.
89 * To do this we must find out the layout preferences for the square
90 * element and adjust its position within our region.
91 *
92 * Note that if we do not have a "square" elememt then just the default
93 * layout will be done. So if someone places a label element into the
94 * widget layout it will still be handled but the -anchor option will be
95 * passed onto the label element instead of handled here.
96 */
97
98static void
99SquareDoLayout(void *clientData)
100{
101    WidgetCore *corePtr = (WidgetCore *)clientData;
102    Ttk_Box winBox;
103    Ttk_Element squareNode;
104
105    squareNode = Ttk_FindElement(corePtr->layout, "square");
106    winBox = Ttk_WinBox(corePtr->tkwin);
107    Ttk_PlaceLayout(corePtr->layout, corePtr->state, winBox);
108
109    /*
110     * Adjust the position of the square element within the widget according
111     * to the -anchor option.
112     */
113
114    if (squareNode) {
115	Square *squarePtr = clientData;
116	Tk_Anchor anchor = TK_ANCHOR_CENTER;
117	Ttk_Box b;
118
119	b = Ttk_ElementParcel(squareNode);
120	if (squarePtr->square.anchorObj != NULL)
121	    Tk_GetAnchorFromObj(NULL, squarePtr->square.anchorObj, &anchor);
122	b = Ttk_AnchorBox(winBox, b.width, b.height, anchor);
123
124	Ttk_PlaceElement(corePtr->layout, squareNode, b);
125    }
126}
127
128/*
129 * Widget commands. A widget is impelemented as an ensemble and the
130 * subcommands are listed here. Ttk provides default implementations
131 * that are sufficient for our needs.
132 */
133
134static const Ttk_Ensemble SquareCommands[] = {
135    { "configure",	TtkWidgetConfigureCommand,0 },
136    { "cget",		TtkWidgetCgetCommand,0 },
137    { "identify",	TtkWidgetIdentifyCommand,0 },
138    { "instate",	TtkWidgetInstateCommand,0 },
139    { "state",  	TtkWidgetStateCommand,0 },
140    { 0,0,0 }
141};
142
143/*
144 * The Widget specification structure holds all the implementation
145 * information about this widget and this is what must be registered
146 * with Tk in the package initialization code (see bottom).
147 */
148
149static WidgetSpec SquareWidgetSpec =
150{
151    "TSquare",			/* className */
152    sizeof(Square),		/* recordSize */
153    SquareOptionSpecs,		/* optionSpecs */
154    SquareCommands,		/* subcommands */
155    TtkNullInitialize,		/* initializeProc */
156    TtkNullCleanup,		/* cleanupProc */
157    TtkCoreConfigure,		/* configureProc */
158    TtkNullPostConfigure,		/* postConfigureProc */
159    TtkWidgetGetLayout,		/* getLayoutProc */
160    TtkWidgetSize, 		/* sizeProc */
161    SquareDoLayout,		/* layoutProc */
162    TtkWidgetDisplay		/* displayProc */
163};
164
165/* ----------------------------------------------------------------------
166 * Square element
167 *
168 * In this section we demonstrate what is required to create a new themed
169 * element.
170 */
171
172typedef struct
173{
174    Tcl_Obj *borderObj;
175    Tcl_Obj *foregroundObj;
176    Tcl_Obj *borderWidthObj;
177    Tcl_Obj *reliefObj;
178    Tcl_Obj *widthObj;
179    Tcl_Obj *heightObj;
180} SquareElement;
181
182static Ttk_ElementOptionSpec SquareElementOptions[] =
183{
184    { "-background", TK_OPTION_BORDER, Tk_Offset(SquareElement,borderObj),
185    	DEFAULT_BACKGROUND },
186    { "-foreground", TK_OPTION_BORDER, Tk_Offset(SquareElement,foregroundObj),
187    	DEFAULT_BACKGROUND },
188    { "-borderwidth", TK_OPTION_PIXELS, Tk_Offset(SquareElement,borderWidthObj),
189    	DEFAULT_BORDERWIDTH },
190    { "-relief", TK_OPTION_RELIEF, Tk_Offset(SquareElement,reliefObj),
191    	"raised" },
192    { "-width",  TK_OPTION_PIXELS, Tk_Offset(SquareElement,widthObj), "20"},
193    { "-height", TK_OPTION_PIXELS, Tk_Offset(SquareElement,heightObj), "20"},
194    { NULL, 0, 0, NULL }
195};
196
197/*
198 * The element geometry function is called when the layout code wishes to
199 * find out how big this element wants to be. We must return our preferred
200 * size and padding information
201 */
202
203static void SquareElementSize(
204    void *clientData, void *elementRecord, Tk_Window tkwin,
205    int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr)
206{
207    SquareElement *square = elementRecord;
208    int borderWidth = 0;
209
210    Tcl_GetIntFromObj(NULL, square->borderWidthObj, &borderWidth);
211    *paddingPtr = Ttk_UniformPadding((short)borderWidth);
212    Tk_GetPixelsFromObj(NULL, tkwin, square->widthObj, widthPtr);
213    Tk_GetPixelsFromObj(NULL, tkwin, square->heightObj, heightPtr);
214}
215
216/*
217 * Draw the element in the box provided.
218 */
219
220static void SquareElementDraw(
221    void *clientData, void *elementRecord, Tk_Window tkwin,
222    Drawable d, Ttk_Box b, unsigned int state)
223{
224    SquareElement *square = elementRecord;
225    Tk_3DBorder border = NULL, foreground = NULL;
226    int borderWidth = 1, relief = TK_RELIEF_FLAT;
227
228    border = Tk_Get3DBorderFromObj(tkwin, square->borderObj);
229    foreground = Tk_Get3DBorderFromObj(tkwin, square->foregroundObj);
230    Tcl_GetIntFromObj(NULL, square->borderWidthObj, &borderWidth);
231    Tk_GetReliefFromObj(NULL, square->reliefObj, &relief);
232
233    Tk_Fill3DRectangle(tkwin, d, foreground,
234	b.x, b.y, b.width, b.height, borderWidth, relief);
235}
236
237static Ttk_ElementSpec SquareElementSpec =
238{
239    TK_STYLE_VERSION_2,
240    sizeof(SquareElement),
241    SquareElementOptions,
242    SquareElementSize,
243    SquareElementDraw
244};
245
246/* ----------------------------------------------------------------------
247 *
248 * Layout section.
249 *
250 * Every widget class needs a layout style that specifies which elements
251 * are part of the widget and how they should be placed. The element layout
252 * engine is similar to the Tk pack geometry manager. Read the documentation
253 * for the details. In this example we just need to have the square element
254 * that has been defined for this widget placed on a background. We will
255 * also need some padding to keep it away from the edges.
256 */
257
258TTK_BEGIN_LAYOUT(SquareLayout)
259     TTK_NODE("Square.background", TTK_FILL_BOTH)
260     TTK_GROUP("Square.padding", TTK_FILL_BOTH,
261	 TTK_NODE("Square.square", 0))
262TTK_END_LAYOUT
263
264/* ----------------------------------------------------------------------
265 *
266 * Widget initialization.
267 *
268 * This file defines a new element and a new widget. We need to register
269 * the element with the themes that will need it. In this case we will
270 * register with the default theme that is the root of the theme inheritance
271 * tree. This means all themes will find this element.
272 * We then need to register the widget class style. This is the layout
273 * specification. If a different theme requires an alternative layout, we
274 * could register that here. For instance, in some themes the scrollbars have
275 * one uparrow, in other themes there are two uparrow elements.
276 * Finally we register the widget itself. This step creates a tcl command so
277 * that we can actually create an instance of this class. The widget is
278 * linked to a particular style by the widget class name. This is important
279 * to realise as the programmer may change the classname when creating a
280 * new instance. If this is done, a new layout will need to be created (which
281 * can be done at script level). Some widgets may require particular elements
282 * to be present but we try to avoid this where possible. In this widget's C
283 * code, no reference is made to any particular elements. The programmer is
284 * free to specify a new style using completely different elements.
285 */
286
287/* public */ MODULE_SCOPE int
288TtkSquareWidget_Init(Tcl_Interp *interp)
289{
290    Ttk_Theme theme = Ttk_GetDefaultTheme(interp);
291
292    /* register the new elements for this theme engine */
293    Ttk_RegisterElement(interp, theme, "square", &SquareElementSpec, NULL);
294
295    /* register the layout for this theme */
296    Ttk_RegisterLayout(theme, "TSquare", SquareLayout);
297
298    /* register the widget */
299    RegisterWidget(interp, "ttk::square", &SquareWidgetSpec);
300
301    return TCL_OK;
302}
303
304#endif /* TTK_SQUARE_WIDGET */
305
306