1/* $Id$
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
23struct 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 */
33static 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 */
43Ttk_ImageSpec *
44TtkGetImageSpec(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
103error:
104    TtkFreeImageSpec(imageSpec);
105    return NULL;
106}
107
108/* TtkFreeImageSpec --
109 * 	Dispose of an image specification.
110 */
111void 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 */
129Tk_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 */
147static Ttk_Box LPadding(Ttk_Box b, Ttk_Padding p)
148    { return Ttk_MakeBox(b.x, b.y, p.left, b.height); }
149
150static 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
153static 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 */
159static Ttk_Box TPadding(Ttk_Box b, Ttk_Padding p)
160    { return Ttk_MakeBox(b.x, b.y, b.width, p.top); }
161
162static 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
165static 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 */
172static 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 */
194static 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 */
206static 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
219typedef 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
233static 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
243static 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
263static 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
297static 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 */
309static int
310Ttk_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    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
404error:
405    FreeImageData(imageData);
406    return TCL_ERROR;
407}
408
409MODULE_SCOPE
410void TtkImage_Init(Tcl_Interp *interp)
411{
412    Ttk_RegisterElementFactory(interp, "image", Ttk_CreateImageElement, NULL);
413}
414
415/*EOF*/
416