452 lines
12 KiB
C
452 lines
12 KiB
C
/*
|
|
* Image specifications and image element factory.
|
|
*
|
|
* Copyright (C) 2004 Pat Thoyts <patthoyts@users.sf.net>
|
|
* Copyright (C) 2004 Joe English
|
|
*
|
|
* An imageSpec is a multi-element list; the first element
|
|
* is the name of the default image to use, the remainder of the
|
|
* list is a sequence of statespec/imagename options as per
|
|
* [style map].
|
|
*/
|
|
|
|
#include "tkInt.h"
|
|
#include "ttkTheme.h"
|
|
|
|
#define MIN(a,b) ((a) < (b) ? (a) : (b))
|
|
|
|
/*------------------------------------------------------------------------
|
|
* +++ ImageSpec management.
|
|
*/
|
|
|
|
struct TtkImageSpec {
|
|
Tk_Image baseImage; /* Base image to use */
|
|
int mapCount; /* #state-specific overrides */
|
|
Ttk_StateSpec *states; /* array[mapCount] of states ... */
|
|
Tk_Image *images; /* ... per-state images to use */
|
|
Tk_ImageChangedProc *imageChanged;
|
|
ClientData imageChangedClientData;
|
|
};
|
|
|
|
/* NullImageChanged --
|
|
* Do-nothing Tk_ImageChangedProc.
|
|
*/
|
|
static void NullImageChanged(ClientData clientData,
|
|
int x, int y, int width, int height, int imageWidth, int imageHeight)
|
|
{ /* No-op */ }
|
|
|
|
/* ImageSpecImageChanged --
|
|
* Image changes should trigger a repaint.
|
|
*/
|
|
static void ImageSpecImageChanged(ClientData clientData,
|
|
int x, int y, int width, int height, int imageWidth, int imageHeight)
|
|
{
|
|
Ttk_ImageSpec *imageSpec = (Ttk_ImageSpec *)clientData;
|
|
if (imageSpec->imageChanged != NULL) {
|
|
imageSpec->imageChanged(imageSpec->imageChangedClientData,
|
|
x, y, width, height,
|
|
imageWidth, imageHeight);
|
|
}
|
|
}
|
|
|
|
/* TtkGetImageSpec --
|
|
* Constructs a Ttk_ImageSpec * from a Tcl_Obj *.
|
|
* Result must be released using TtkFreeImageSpec.
|
|
*
|
|
*/
|
|
Ttk_ImageSpec *
|
|
TtkGetImageSpec(Tcl_Interp *interp, Tk_Window tkwin, Tcl_Obj *objPtr)
|
|
{
|
|
return TtkGetImageSpecEx(interp, tkwin, objPtr, NULL, NULL);
|
|
}
|
|
|
|
/* TtkGetImageSpecEx --
|
|
* Constructs a Ttk_ImageSpec * from a Tcl_Obj *.
|
|
* Result must be released using TtkFreeImageSpec.
|
|
* imageChangedProc will be called when not NULL when
|
|
* the image changes to allow widgets to repaint.
|
|
*/
|
|
Ttk_ImageSpec *
|
|
TtkGetImageSpecEx(Tcl_Interp *interp, Tk_Window tkwin, Tcl_Obj *objPtr,
|
|
Tk_ImageChangedProc *imageChangedProc, ClientData imageChangedClientData)
|
|
{
|
|
Ttk_ImageSpec *imageSpec = 0;
|
|
int i = 0, n = 0, objc;
|
|
Tcl_Obj **objv;
|
|
|
|
imageSpec = ckalloc(sizeof(*imageSpec));
|
|
imageSpec->baseImage = 0;
|
|
imageSpec->mapCount = 0;
|
|
imageSpec->states = 0;
|
|
imageSpec->images = 0;
|
|
imageSpec->imageChanged = imageChangedProc;
|
|
imageSpec->imageChangedClientData = imageChangedClientData;
|
|
|
|
if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) {
|
|
goto error;
|
|
}
|
|
|
|
if ((objc % 2) != 1) {
|
|
if (interp) {
|
|
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
|
"image specification must contain an odd number of elements",
|
|
-1));
|
|
Tcl_SetErrorCode(interp, "TTK", "IMAGE", "SPEC", NULL);
|
|
}
|
|
goto error;
|
|
}
|
|
|
|
n = (objc - 1) / 2;
|
|
imageSpec->states = ckalloc(n * sizeof(Ttk_StateSpec));
|
|
imageSpec->images = ckalloc(n * sizeof(Tk_Image *));
|
|
|
|
/* Get base image:
|
|
*/
|
|
imageSpec->baseImage = Tk_GetImage(
|
|
interp, tkwin, Tcl_GetString(objv[0]), ImageSpecImageChanged, imageSpec);
|
|
if (!imageSpec->baseImage) {
|
|
goto error;
|
|
}
|
|
|
|
/* Extract state and image specifications:
|
|
*/
|
|
for (i = 0; i < n; ++i) {
|
|
Tcl_Obj *stateSpec = objv[2*i + 1];
|
|
const char *imageName = Tcl_GetString(objv[2*i + 2]);
|
|
Ttk_StateSpec state;
|
|
|
|
if (Ttk_GetStateSpecFromObj(interp, stateSpec, &state) != TCL_OK) {
|
|
goto error;
|
|
}
|
|
imageSpec->states[i] = state;
|
|
|
|
imageSpec->images[i] = Tk_GetImage(
|
|
interp, tkwin, imageName, NullImageChanged, NULL);
|
|
if (imageSpec->images[i] == NULL) {
|
|
goto error;
|
|
}
|
|
imageSpec->mapCount = i+1;
|
|
}
|
|
|
|
return imageSpec;
|
|
|
|
error:
|
|
TtkFreeImageSpec(imageSpec);
|
|
return NULL;
|
|
}
|
|
|
|
/* TtkFreeImageSpec --
|
|
* Dispose of an image specification.
|
|
*/
|
|
void TtkFreeImageSpec(Ttk_ImageSpec *imageSpec)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i < imageSpec->mapCount; ++i) {
|
|
Tk_FreeImage(imageSpec->images[i]);
|
|
}
|
|
|
|
if (imageSpec->baseImage) { Tk_FreeImage(imageSpec->baseImage); }
|
|
if (imageSpec->states) { ckfree(imageSpec->states); }
|
|
if (imageSpec->images) { ckfree(imageSpec->images); }
|
|
|
|
ckfree(imageSpec);
|
|
}
|
|
|
|
/* TtkSelectImage --
|
|
* Return a state-specific image from an ImageSpec
|
|
*/
|
|
Tk_Image TtkSelectImage(Ttk_ImageSpec *imageSpec, Ttk_State state)
|
|
{
|
|
int i;
|
|
for (i = 0; i < imageSpec->mapCount; ++i) {
|
|
if (Ttk_StateMatches(state, imageSpec->states+i)) {
|
|
return imageSpec->images[i];
|
|
}
|
|
}
|
|
return imageSpec->baseImage;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------
|
|
* +++ Drawing utilities.
|
|
*/
|
|
|
|
/* LPadding, CPadding, RPadding --
|
|
* Split a box+padding pair into left, center, and right boxes.
|
|
*/
|
|
static Ttk_Box LPadding(Ttk_Box b, Ttk_Padding p)
|
|
{ return Ttk_MakeBox(b.x, b.y, p.left, b.height); }
|
|
|
|
static Ttk_Box CPadding(Ttk_Box b, Ttk_Padding p)
|
|
{ return Ttk_MakeBox(b.x+p.left, b.y, b.width-p.left-p.right, b.height); }
|
|
|
|
static Ttk_Box RPadding(Ttk_Box b, Ttk_Padding p)
|
|
{ return Ttk_MakeBox(b.x+b.width-p.right, b.y, p.right, b.height); }
|
|
|
|
/* TPadding, MPadding, BPadding --
|
|
* Split a box+padding pair into top, middle, and bottom parts.
|
|
*/
|
|
static Ttk_Box TPadding(Ttk_Box b, Ttk_Padding p)
|
|
{ return Ttk_MakeBox(b.x, b.y, b.width, p.top); }
|
|
|
|
static Ttk_Box MPadding(Ttk_Box b, Ttk_Padding p)
|
|
{ return Ttk_MakeBox(b.x, b.y+p.top, b.width, b.height-p.top-p.bottom); }
|
|
|
|
static Ttk_Box BPadding(Ttk_Box b, Ttk_Padding p)
|
|
{ return Ttk_MakeBox(b.x, b.y+b.height-p.bottom, b.width, p.bottom); }
|
|
|
|
/* Ttk_Fill --
|
|
* Fill the destination area of the drawable by replicating
|
|
* the source area of the image.
|
|
*/
|
|
static void Ttk_Fill(
|
|
Tk_Window tkwin, Drawable d, Tk_Image image, Ttk_Box src, Ttk_Box dst)
|
|
{
|
|
int dr = dst.x + dst.width;
|
|
int db = dst.y + dst.height;
|
|
int x,y;
|
|
|
|
if (!(src.width && src.height && dst.width && dst.height))
|
|
return;
|
|
|
|
for (x = dst.x; x < dr; x += src.width) {
|
|
int cw = MIN(src.width, dr - x);
|
|
for (y = dst.y; y <= db; y += src.height) {
|
|
int ch = MIN(src.height, db - y);
|
|
Tk_RedrawImage(image, src.x, src.y, cw, ch, d, x, y);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Ttk_Stripe --
|
|
* Fill a horizontal stripe of the destination drawable.
|
|
*/
|
|
static void Ttk_Stripe(
|
|
Tk_Window tkwin, Drawable d, Tk_Image image,
|
|
Ttk_Box src, Ttk_Box dst, Ttk_Padding p)
|
|
{
|
|
Ttk_Fill(tkwin, d, image, LPadding(src,p), LPadding(dst,p));
|
|
Ttk_Fill(tkwin, d, image, CPadding(src,p), CPadding(dst,p));
|
|
Ttk_Fill(tkwin, d, image, RPadding(src,p), RPadding(dst,p));
|
|
}
|
|
|
|
/* Ttk_Tile --
|
|
* Fill successive horizontal stripes of the destination drawable.
|
|
*/
|
|
static void Ttk_Tile(
|
|
Tk_Window tkwin, Drawable d, Tk_Image image,
|
|
Ttk_Box src, Ttk_Box dst, Ttk_Padding p)
|
|
{
|
|
Ttk_Stripe(tkwin, d, image, TPadding(src,p), TPadding(dst,p), p);
|
|
Ttk_Stripe(tkwin, d, image, MPadding(src,p), MPadding(dst,p), p);
|
|
Ttk_Stripe(tkwin, d, image, BPadding(src,p), BPadding(dst,p), p);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------
|
|
* +++ Image element definition.
|
|
*/
|
|
|
|
typedef struct { /* ClientData for image elements */
|
|
Ttk_ImageSpec *imageSpec; /* Image(s) to use */
|
|
int minWidth; /* Minimum width; overrides image width */
|
|
int minHeight; /* Minimum width; overrides image width */
|
|
Ttk_Sticky sticky; /* -stickiness specification */
|
|
Ttk_Padding border; /* Fixed border region */
|
|
Ttk_Padding padding; /* Internal padding */
|
|
|
|
#if TILE_07_COMPAT
|
|
Ttk_ResourceCache cache; /* Resource cache for images */
|
|
Ttk_StateMap imageMap; /* State-based lookup table for images */
|
|
#endif
|
|
} ImageData;
|
|
|
|
static void FreeImageData(void *clientData)
|
|
{
|
|
ImageData *imageData = clientData;
|
|
if (imageData->imageSpec) { TtkFreeImageSpec(imageData->imageSpec); }
|
|
#if TILE_07_COMPAT
|
|
if (imageData->imageMap) { Tcl_DecrRefCount(imageData->imageMap); }
|
|
#endif
|
|
ckfree(clientData);
|
|
}
|
|
|
|
static void ImageElementSize(
|
|
void *clientData, void *elementRecord, Tk_Window tkwin,
|
|
int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr)
|
|
{
|
|
ImageData *imageData = clientData;
|
|
Tk_Image image = imageData->imageSpec->baseImage;
|
|
|
|
if (image) {
|
|
Tk_SizeOfImage(image, widthPtr, heightPtr);
|
|
}
|
|
if (imageData->minWidth >= 0) {
|
|
*widthPtr = imageData->minWidth;
|
|
}
|
|
if (imageData->minHeight >= 0) {
|
|
*heightPtr = imageData->minHeight;
|
|
}
|
|
|
|
*paddingPtr = imageData->padding;
|
|
}
|
|
|
|
static void ImageElementDraw(
|
|
void *clientData, void *elementRecord, Tk_Window tkwin,
|
|
Drawable d, Ttk_Box b, unsigned int state)
|
|
{
|
|
ImageData *imageData = clientData;
|
|
Tk_Image image = 0;
|
|
int imgWidth, imgHeight;
|
|
Ttk_Box src, dst;
|
|
|
|
#if TILE_07_COMPAT
|
|
if (imageData->imageMap) {
|
|
Tcl_Obj *imageObj = Ttk_StateMapLookup(NULL,imageData->imageMap,state);
|
|
if (imageObj) {
|
|
image = Ttk_UseImage(imageData->cache, tkwin, imageObj);
|
|
}
|
|
}
|
|
if (!image) {
|
|
image = TtkSelectImage(imageData->imageSpec, state);
|
|
}
|
|
#else
|
|
image = TtkSelectImage(imageData->imageSpec, state);
|
|
#endif
|
|
|
|
if (!image) {
|
|
return;
|
|
}
|
|
|
|
Tk_SizeOfImage(image, &imgWidth, &imgHeight);
|
|
src = Ttk_MakeBox(0, 0, imgWidth, imgHeight);
|
|
dst = Ttk_StickBox(b, imgWidth, imgHeight, imageData->sticky);
|
|
|
|
Ttk_Tile(tkwin, d, image, src, dst, imageData->border);
|
|
}
|
|
|
|
static Ttk_ElementSpec ImageElementSpec =
|
|
{
|
|
TK_STYLE_VERSION_2,
|
|
sizeof(NullElement),
|
|
TtkNullElementOptions,
|
|
ImageElementSize,
|
|
ImageElementDraw
|
|
};
|
|
|
|
/*------------------------------------------------------------------------
|
|
* +++ Image element factory.
|
|
*/
|
|
static int
|
|
Ttk_CreateImageElement(
|
|
Tcl_Interp *interp,
|
|
void *clientData,
|
|
Ttk_Theme theme,
|
|
const char *elementName,
|
|
int objc, Tcl_Obj *const objv[])
|
|
{
|
|
static const char *optionStrings[] =
|
|
{ "-border","-height","-padding","-sticky","-width",NULL };
|
|
enum { O_BORDER, O_HEIGHT, O_PADDING, O_STICKY, O_WIDTH };
|
|
|
|
Ttk_ImageSpec *imageSpec = 0;
|
|
ImageData *imageData = 0;
|
|
int padding_specified = 0;
|
|
int i;
|
|
|
|
if (objc <= 0) {
|
|
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
|
"Must supply a base image", -1));
|
|
Tcl_SetErrorCode(interp, "TTK", "IMAGE", "BASE", NULL);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
imageSpec = TtkGetImageSpec(interp, Tk_MainWindow(interp), objv[0]);
|
|
if (!imageSpec) {
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
imageData = ckalloc(sizeof(*imageData));
|
|
imageData->imageSpec = imageSpec;
|
|
imageData->minWidth = imageData->minHeight = -1;
|
|
imageData->sticky = TTK_FILL_BOTH;
|
|
imageData->border = imageData->padding = Ttk_UniformPadding(0);
|
|
#if TILE_07_COMPAT
|
|
imageData->cache = Ttk_GetResourceCache(interp);
|
|
imageData->imageMap = 0;
|
|
#endif
|
|
|
|
for (i = 1; i < objc; i += 2) {
|
|
int option;
|
|
|
|
if (i == objc - 1) {
|
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|
"Value for %s missing", Tcl_GetString(objv[i])));
|
|
Tcl_SetErrorCode(interp, "TTK", "IMAGE", "VALUE", NULL);
|
|
goto error;
|
|
}
|
|
|
|
#if TILE_07_COMPAT
|
|
if (!strcmp("-map", Tcl_GetString(objv[i]))) {
|
|
imageData->imageMap = objv[i+1];
|
|
Tcl_IncrRefCount(imageData->imageMap);
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
if (Tcl_GetIndexFromObjStruct(interp, objv[i], optionStrings,
|
|
sizeof(char *), "option", 0, &option) != TCL_OK) {
|
|
goto error;
|
|
}
|
|
|
|
switch (option) {
|
|
case O_BORDER:
|
|
if (Ttk_GetBorderFromObj(interp, objv[i+1], &imageData->border)
|
|
!= TCL_OK) {
|
|
goto error;
|
|
}
|
|
if (!padding_specified) {
|
|
imageData->padding = imageData->border;
|
|
}
|
|
break;
|
|
case O_PADDING:
|
|
if (Ttk_GetBorderFromObj(interp, objv[i+1], &imageData->padding)
|
|
!= TCL_OK) { goto error; }
|
|
padding_specified = 1;
|
|
break;
|
|
case O_WIDTH:
|
|
if (Tcl_GetIntFromObj(interp, objv[i+1], &imageData->minWidth)
|
|
!= TCL_OK) { goto error; }
|
|
break;
|
|
case O_HEIGHT:
|
|
if (Tcl_GetIntFromObj(interp, objv[i+1], &imageData->minHeight)
|
|
!= TCL_OK) { goto error; }
|
|
break;
|
|
case O_STICKY:
|
|
if (Ttk_GetStickyFromObj(interp, objv[i+1], &imageData->sticky)
|
|
!= TCL_OK) { goto error; }
|
|
}
|
|
}
|
|
|
|
if (!Ttk_RegisterElement(interp, theme, elementName, &ImageElementSpec,
|
|
imageData))
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
Ttk_RegisterCleanup(interp, imageData, FreeImageData);
|
|
Tcl_SetObjResult(interp, Tcl_NewStringObj(elementName, -1));
|
|
return TCL_OK;
|
|
|
|
error:
|
|
FreeImageData(imageData);
|
|
return TCL_ERROR;
|
|
}
|
|
|
|
MODULE_SCOPE
|
|
void TtkImage_Init(Tcl_Interp *interp)
|
|
{
|
|
Ttk_RegisterElementFactory(interp, "image", Ttk_CreateImageElement, NULL);
|
|
}
|
|
|
|
/*EOF*/
|