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