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