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