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