1 /*
2 * Image specifications and image element factory.
3 *
4 * Copyright (C) 2004 Pat Thoyts <patthoyts@users.sf.net>
5 * Copyright (C) 2004 Joe English
6 *
7 * An imageSpec is a multi-element list; the first element
8 * is the name of the default image to use, the remainder of the
9 * list is a sequence of statespec/imagename options as per
10 * [style map].
11 */
12
13 #include <string.h>
14 #include <tk.h>
15 #include "ttkTheme.h"
16
17 #define MIN(a,b) ((a) < (b) ? (a) : (b))
18
19 /*------------------------------------------------------------------------
20 * +++ ImageSpec management.
21 */
22
23 struct TtkImageSpec {
24 Tk_Image baseImage; /* Base image to use */
25 int mapCount; /* #state-specific overrides */
26 Ttk_StateSpec *states; /* array[mapCount] of states ... */
27 Tk_Image *images; /* ... per-state images to use */
28 };
29
30 /* NullImageChanged --
31 * Do-nothing Tk_ImageChangedProc.
32 */
NullImageChanged(ClientData clientData,int x,int y,int width,int height,int imageWidth,int imageHeight)33 static void NullImageChanged(ClientData clientData,
34 int x, int y, int width, int height, int imageWidth, int imageHeight)
35 { /* No-op */ }
36
37 /* TtkGetImageSpec --
38 * Constructs a Ttk_ImageSpec * from a Tcl_Obj *.
39 * Result must be released using TtkFreeImageSpec.
40 *
41 * TODO: Need a variant of this that takes a user-specified ImageChanged proc
42 */
43 Ttk_ImageSpec *
TtkGetImageSpec(Tcl_Interp * interp,Tk_Window tkwin,Tcl_Obj * objPtr)44 TtkGetImageSpec(Tcl_Interp *interp, Tk_Window tkwin, Tcl_Obj *objPtr)
45 {
46 Ttk_ImageSpec *imageSpec = 0;
47 int i = 0, n = 0, objc;
48 Tcl_Obj **objv;
49
50 imageSpec = (Ttk_ImageSpec *)ckalloc(sizeof(*imageSpec));
51 imageSpec->baseImage = 0;
52 imageSpec->mapCount = 0;
53 imageSpec->states = 0;
54 imageSpec->images = 0;
55
56 if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) {
57 goto error;
58 }
59
60 if ((objc % 2) != 1) {
61 if (interp) {
62 Tcl_SetResult(interp,
63 "image specification must contain an odd number of elements",
64 TCL_STATIC);
65 }
66 goto error;
67 }
68
69 n = (objc - 1) / 2;
70 imageSpec->states = (Ttk_StateSpec*)ckalloc(n * sizeof(Ttk_StateSpec));
71 imageSpec->images = (Tk_Image*)ckalloc(n * sizeof(Tk_Image *));
72
73 /* Get base image:
74 */
75 imageSpec->baseImage = Tk_GetImage(
76 interp, tkwin, Tcl_GetString(objv[0]), NullImageChanged, NULL);
77 if (!imageSpec->baseImage) {
78 goto error;
79 }
80
81 /* Extract state and image specifications:
82 */
83 for (i = 0; i < n; ++i) {
84 Tcl_Obj *stateSpec = objv[2*i + 1];
85 const char *imageName = Tcl_GetString(objv[2*i + 2]);
86 Ttk_StateSpec state;
87
88 if (Ttk_GetStateSpecFromObj(interp, stateSpec, &state) != TCL_OK) {
89 goto error;
90 }
91 imageSpec->states[i] = state;
92
93 imageSpec->images[i] = Tk_GetImage(
94 interp, tkwin, imageName, NullImageChanged, NULL);
95 if (imageSpec->images[i] == NULL) {
96 goto error;
97 }
98 imageSpec->mapCount = i+1;
99 }
100
101 return imageSpec;
102
103 error:
104 TtkFreeImageSpec(imageSpec);
105 return NULL;
106 }
107
108 /* TtkFreeImageSpec --
109 * Dispose of an image specification.
110 */
TtkFreeImageSpec(Ttk_ImageSpec * imageSpec)111 void TtkFreeImageSpec(Ttk_ImageSpec *imageSpec)
112 {
113 int i;
114
115 for (i=0; i < imageSpec->mapCount; ++i) {
116 Tk_FreeImage(imageSpec->images[i]);
117 }
118
119 if (imageSpec->baseImage) { Tk_FreeImage(imageSpec->baseImage); }
120 if (imageSpec->states) { ckfree((ClientData)imageSpec->states); }
121 if (imageSpec->images) { ckfree((ClientData)imageSpec->images); }
122
123 ckfree((ClientData)imageSpec);
124 }
125
126 /* TtkSelectImage --
127 * Return a state-specific image from an ImageSpec
128 */
TtkSelectImage(Ttk_ImageSpec * imageSpec,Ttk_State state)129 Tk_Image TtkSelectImage(Ttk_ImageSpec *imageSpec, Ttk_State state)
130 {
131 int i;
132 for (i = 0; i < imageSpec->mapCount; ++i) {
133 if (Ttk_StateMatches(state, imageSpec->states+i)) {
134 return imageSpec->images[i];
135 }
136 }
137 return imageSpec->baseImage;
138 }
139
140 /*------------------------------------------------------------------------
141 * +++ Drawing utilities.
142 */
143
144 /* LPadding, CPadding, RPadding --
145 * Split a box+padding pair into left, center, and right boxes.
146 */
LPadding(Ttk_Box b,Ttk_Padding p)147 static Ttk_Box LPadding(Ttk_Box b, Ttk_Padding p)
148 { return Ttk_MakeBox(b.x, b.y, p.left, b.height); }
149
CPadding(Ttk_Box b,Ttk_Padding p)150 static Ttk_Box CPadding(Ttk_Box b, Ttk_Padding p)
151 { return Ttk_MakeBox(b.x+p.left, b.y, b.width-p.left-p.right, b.height); }
152
RPadding(Ttk_Box b,Ttk_Padding p)153 static Ttk_Box RPadding(Ttk_Box b, Ttk_Padding p)
154 { return Ttk_MakeBox(b.x+b.width-p.right, b.y, p.right, b.height); }
155
156 /* TPadding, MPadding, BPadding --
157 * Split a box+padding pair into top, middle, and bottom parts.
158 */
TPadding(Ttk_Box b,Ttk_Padding p)159 static Ttk_Box TPadding(Ttk_Box b, Ttk_Padding p)
160 { return Ttk_MakeBox(b.x, b.y, b.width, p.top); }
161
MPadding(Ttk_Box b,Ttk_Padding p)162 static Ttk_Box MPadding(Ttk_Box b, Ttk_Padding p)
163 { return Ttk_MakeBox(b.x, b.y+p.top, b.width, b.height-p.top-p.bottom); }
164
BPadding(Ttk_Box b,Ttk_Padding p)165 static Ttk_Box BPadding(Ttk_Box b, Ttk_Padding p)
166 { return Ttk_MakeBox(b.x, b.y+b.height-p.bottom, b.width, p.bottom); }
167
168 /* Ttk_Fill --
169 * Fill the destination area of the drawable by replicating
170 * the source area of the image.
171 */
Ttk_Fill(Tk_Window tkwin,Drawable d,Tk_Image image,Ttk_Box src,Ttk_Box dst)172 static void Ttk_Fill(
173 Tk_Window tkwin, Drawable d, Tk_Image image, Ttk_Box src, Ttk_Box dst)
174 {
175 int dr = dst.x + dst.width;
176 int db = dst.y + dst.height;
177 int x,y;
178
179 if (!(src.width && src.height && dst.width && dst.height))
180 return;
181
182 for (x = dst.x; x < dr; x += src.width) {
183 int cw = MIN(src.width, dr - x);
184 for (y = dst.y; y <= db; y += src.height) {
185 int ch = MIN(src.height, db - y);
186 Tk_RedrawImage(image, src.x, src.y, cw, ch, d, x, y);
187 }
188 }
189 }
190
191 /* Ttk_Stripe --
192 * Fill a horizontal stripe of the destination drawable.
193 */
Ttk_Stripe(Tk_Window tkwin,Drawable d,Tk_Image image,Ttk_Box src,Ttk_Box dst,Ttk_Padding p)194 static void Ttk_Stripe(
195 Tk_Window tkwin, Drawable d, Tk_Image image,
196 Ttk_Box src, Ttk_Box dst, Ttk_Padding p)
197 {
198 Ttk_Fill(tkwin, d, image, LPadding(src,p), LPadding(dst,p));
199 Ttk_Fill(tkwin, d, image, CPadding(src,p), CPadding(dst,p));
200 Ttk_Fill(tkwin, d, image, RPadding(src,p), RPadding(dst,p));
201 }
202
203 /* Ttk_Tile --
204 * Fill successive horizontal stripes of the destination drawable.
205 */
Ttk_Tile(Tk_Window tkwin,Drawable d,Tk_Image image,Ttk_Box src,Ttk_Box dst,Ttk_Padding p)206 static void Ttk_Tile(
207 Tk_Window tkwin, Drawable d, Tk_Image image,
208 Ttk_Box src, Ttk_Box dst, Ttk_Padding p)
209 {
210 Ttk_Stripe(tkwin, d, image, TPadding(src,p), TPadding(dst,p), p);
211 Ttk_Stripe(tkwin, d, image, MPadding(src,p), MPadding(dst,p), p);
212 Ttk_Stripe(tkwin, d, image, BPadding(src,p), BPadding(dst,p), p);
213 }
214
215 /*------------------------------------------------------------------------
216 * +++ Image element definition.
217 */
218
219 typedef struct { /* ClientData for image elements */
220 Ttk_ImageSpec *imageSpec; /* Image(s) to use */
221 int minWidth; /* Minimum width; overrides image width */
222 int minHeight; /* Minimum width; overrides image width */
223 Ttk_Sticky sticky; /* -stickiness specification */
224 Ttk_Padding border; /* Fixed border region */
225 Ttk_Padding padding; /* Internal padding */
226
227 #if TILE_07_COMPAT
228 Ttk_ResourceCache cache; /* Resource cache for images */
229 Ttk_StateMap imageMap; /* State-based lookup table for images */
230 #endif
231 } ImageData;
232
FreeImageData(void * clientData)233 static void FreeImageData(void *clientData)
234 {
235 ImageData *imageData = clientData;
236 if (imageData->imageSpec) { TtkFreeImageSpec(imageData->imageSpec); }
237 #if TILE_07_COMPAT
238 if (imageData->imageMap) { Tcl_DecrRefCount(imageData->imageMap); }
239 #endif
240 ckfree(clientData);
241 }
242
ImageElementSize(void * clientData,void * elementRecord,Tk_Window tkwin,int * widthPtr,int * heightPtr,Ttk_Padding * paddingPtr)243 static void ImageElementSize(
244 void *clientData, void *elementRecord, Tk_Window tkwin,
245 int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr)
246 {
247 ImageData *imageData = clientData;
248 Tk_Image image = imageData->imageSpec->baseImage;
249
250 if (image) {
251 Tk_SizeOfImage(image, widthPtr, heightPtr);
252 }
253 if (imageData->minWidth >= 0) {
254 *widthPtr = imageData->minWidth;
255 }
256 if (imageData->minHeight >= 0) {
257 *heightPtr = imageData->minHeight;
258 }
259
260 *paddingPtr = imageData->padding;
261 }
262
ImageElementDraw(void * clientData,void * elementRecord,Tk_Window tkwin,Drawable d,Ttk_Box b,unsigned int state)263 static void ImageElementDraw(
264 void *clientData, void *elementRecord, Tk_Window tkwin,
265 Drawable d, Ttk_Box b, unsigned int state)
266 {
267 ImageData *imageData = clientData;
268 Tk_Image image = 0;
269 int imgWidth, imgHeight;
270 Ttk_Box src, dst;
271
272 #if TILE_07_COMPAT
273 if (imageData->imageMap) {
274 Tcl_Obj *imageObj = Ttk_StateMapLookup(NULL,imageData->imageMap,state);
275 if (imageObj) {
276 image = Ttk_UseImage(imageData->cache, tkwin, imageObj);
277 }
278 }
279 if (!image) {
280 image = TtkSelectImage(imageData->imageSpec, state);
281 }
282 #else
283 image = TtkSelectImage(imageData->imageSpec, state);
284 #endif
285
286 if (!image) {
287 return;
288 }
289
290 Tk_SizeOfImage(image, &imgWidth, &imgHeight);
291 src = Ttk_MakeBox(0, 0, imgWidth, imgHeight);
292 dst = Ttk_StickBox(b, imgWidth, imgHeight, imageData->sticky);
293
294 Ttk_Tile(tkwin, d, image, src, dst, imageData->border);
295 }
296
297 static Ttk_ElementSpec ImageElementSpec =
298 {
299 TK_STYLE_VERSION_2,
300 sizeof(NullElement),
301 TtkNullElementOptions,
302 ImageElementSize,
303 ImageElementDraw
304 };
305
306 /*------------------------------------------------------------------------
307 * +++ Image element factory.
308 */
309 static int
Ttk_CreateImageElement(Tcl_Interp * interp,void * clientData,Ttk_Theme theme,const char * elementName,int objc,Tcl_Obj * const objv[])310 Ttk_CreateImageElement(
311 Tcl_Interp *interp,
312 void *clientData,
313 Ttk_Theme theme,
314 const char *elementName,
315 int objc, Tcl_Obj *const objv[])
316 {
317 static const char *optionStrings[] =
318 { "-border","-height","-padding","-sticky","-width",NULL };
319 enum { O_BORDER, O_HEIGHT, O_PADDING, O_STICKY, O_WIDTH };
320
321 Ttk_ImageSpec *imageSpec = 0;
322 ImageData *imageData = 0;
323 int padding_specified = 0;
324 int i;
325
326 if (objc <= 0) {
327 Tcl_AppendResult(interp, "Must supply a base image", NULL);
328 return TCL_ERROR;
329 }
330
331 imageSpec = TtkGetImageSpec(interp, Tk_MainWindow(interp), objv[0]);
332 if (!imageSpec) {
333 return TCL_ERROR;
334 }
335
336 imageData = (ImageData*)ckalloc(sizeof(*imageData));
337 imageData->imageSpec = imageSpec;
338 imageData->minWidth = imageData->minHeight = -1;
339 imageData->sticky = TTK_FILL_BOTH;
340 imageData->border = imageData->padding = Ttk_UniformPadding(0);
341 #if TILE_07_COMPAT
342 imageData->cache = Ttk_GetResourceCache(interp);
343 imageData->imageMap = 0;
344 #endif
345
346 for (i = 1; i < objc; i += 2) {
347 int option;
348
349 if (i == objc - 1) {
350 Tcl_AppendResult(interp,
351 "Value for ", Tcl_GetString(objv[i]), " missing",
352 NULL);
353 goto error;
354 }
355
356 #if TILE_07_COMPAT
357 if (!strcmp("-map", Tcl_GetString(objv[i]))) {
358 imageData->imageMap = objv[i+1];
359 Tcl_IncrRefCount(imageData->imageMap);
360 continue;
361 }
362 #endif
363
364 if (Tcl_GetIndexFromObj(interp, objv[i], optionStrings,
365 "option", 0, &option) != TCL_OK) { goto error; }
366
367 switch (option) {
368 case O_BORDER:
369 if (Ttk_GetBorderFromObj(interp, objv[i+1], &imageData->border)
370 != TCL_OK) { goto error; }
371 if (!padding_specified) {
372 imageData->padding = imageData->border;
373 }
374 break;
375 case O_PADDING:
376 if (Ttk_GetBorderFromObj(interp, objv[i+1], &imageData->padding)
377 != TCL_OK) { goto error; }
378 padding_specified = 1;
379 break;
380 case O_WIDTH:
381 if (Tcl_GetIntFromObj(interp, objv[i+1], &imageData->minWidth)
382 != TCL_OK) { goto error; }
383 break;
384 case O_HEIGHT:
385 if (Tcl_GetIntFromObj(interp, objv[i+1], &imageData->minHeight)
386 != TCL_OK) { goto error; }
387 break;
388 case O_STICKY:
389 if (Ttk_GetStickyFromObj(interp, objv[i+1], &imageData->sticky)
390 != TCL_OK) { goto error; }
391 }
392 }
393
394 if (!Ttk_RegisterElement(interp, theme, elementName, &ImageElementSpec,
395 imageData))
396 {
397 goto error;
398 }
399
400 Ttk_RegisterCleanup(interp, imageData, FreeImageData);
401 Tcl_SetObjResult(interp, Tcl_NewStringObj(elementName, -1));
402 return TCL_OK;
403
404 error:
405 FreeImageData(imageData);
406 return TCL_ERROR;
407 }
408
409 MODULE_SCOPE
TtkImage_Init(Tcl_Interp * interp)410 void TtkImage_Init(Tcl_Interp *interp)
411 {
412 Ttk_RegisterElementFactory(interp, "image", Ttk_CreateImageElement, NULL);
413 }
414
415 /*EOF*/
416