Unnamed Fossil Project

Artifact [a2af1e0812]
Login

Artifact [a2af1e0812]

Artifact a2af1e08121b2d5f28869edf1d09389cb9799b0a15b14d59a5e22e8dbc22ee21:


/* $Id: widget.c,v 1.28 2004/04/01 03:00:25 jenglish Exp $
 * Copyright (c) 2003, Joe English
 *
 * Tile widget implementation, core widget utilities.
 *
 */

#include <string.h>
#include <tk.h>
#include "tkTheme.h"
#include "widget.h"

/*------------------------------------------------------------------------
 * Helper routines.
 */

static void UpdateLayout(Tcl_Interp *interp, WidgetCore *corePtr)
{
    TTK_Theme themePtr = TTK_GetCurrentTheme(interp);
    TTK_Layout newLayout = 
    	corePtr->widgetSpec->getLayoutProc(interp, themePtr,corePtr);

    /* TODO: @@@ Check for errors */
    if (newLayout) {
	if (corePtr->layout) {
	    TTK_FreeLayout(corePtr->layout);
	}
	corePtr->layout = newLayout;
    }
}

static void UpdateGeometry(WidgetCore *corePtr)
{
    WidgetSpec *ws = corePtr->widgetSpec;
    int reqWidth = 1, reqHeight = 1;

    if (ws->sizeProc(corePtr,&reqWidth,&reqHeight)) {
	Tk_GeometryRequest(corePtr->tkwin, reqWidth, reqHeight);
    }
}

/*
 * RedisplayWidget --
 *	Redraw a widget.  Called as an idle handler.
 */
static void
RedisplayWidget(ClientData clientData)
{
    WidgetCore *corePtr = (WidgetCore *)clientData;
    Tk_Window tkwin = corePtr->tkwin;
    Drawable d;
    XGCValues gcValues;
    GC gc;

    corePtr->flags &= ~(UPDATE_PENDING | REDISPLAY_REQUIRED);
    if (!Tk_IsMapped(tkwin)) {
	return;
    }

    /*
     * Get a Pixmap for drawing in the background:
     */
    d = Tk_GetPixmap(Tk_Display(tkwin), Tk_WindowId(tkwin),
	    Tk_Width(tkwin), Tk_Height(tkwin),
	    DefaultDepthOfScreen(Tk_Screen(tkwin)));

    /*
     * Get a GC for blitting the pixmap to the display:
     */
    gcValues.function = GXcopy;
    gcValues.graphics_exposures = False;
    gc = Tk_GetGC(corePtr->tkwin, GCFunction|GCGraphicsExposures, &gcValues);

    /*
     * Recompute layout and draw widget contents:
     */
    corePtr->widgetSpec->layoutProc(clientData);
    corePtr->widgetSpec->displayProc(clientData, d);

    /*
     * Copy to the screen.
     */
    XCopyArea(Tk_Display(tkwin), d, Tk_WindowId(tkwin), gc,
	    0, 0, (unsigned) Tk_Width(tkwin), (unsigned) Tk_Height(tkwin),
	    0, 0);

    /*
     * Release resources
     */
    Tk_FreePixmap(Tk_Display(tkwin), d);
    Tk_FreeGC(Tk_Display(tkwin), gc);
}

/* WidgetChanged(corePtr, flags) --
 * 	Notification that the widget has changed.
 * 	if flags & RELAYOUT_REQUIRED, recompute geometry.
 * 	if flags & REDISPLAY_REQUIRED, schedule redisplay as an idle call.
 */
void WidgetChanged(WidgetCore *corePtr, unsigned flags)
{
    if (corePtr->flags & WIDGET_DESTROYED)
	return;

    if (flags & RELAYOUT_REQUIRED)
	UpdateGeometry(corePtr);

    if (!(corePtr->flags & UPDATE_PENDING)) {
	Tcl_DoWhenIdle(RedisplayWidget, (ClientData) corePtr);
	corePtr->flags |= (UPDATE_PENDING | flags);
    }

}

/* WidgetEnsembleCommand --
 * 	Invoke an ensemble defined by a WidgetCommandSpec.
 */
int WidgetEnsembleCommand(
    WidgetCommandSpec *commands,	/* Ensemble definition */
    int cmdIndex,			/* Index of command word */
    Tcl_Interp *interp,			/* Interpreter to use */
    int objc, Tcl_Obj *const objv[],	/* Argument vector */
    void *clientData)			/* User data (widget record pointer) */
{
    int index;

    if (objc <= cmdIndex) {
	Tcl_WrongNumArgs(interp, cmdIndex, objv, "option ?arg arg...?");
	return TCL_ERROR;
    }
    if (Tcl_GetIndexFromObjStruct(interp, objv[cmdIndex], commands,
		sizeof(commands[0]), "command", 0, &index) != TCL_OK)
    {
	return TCL_ERROR;
    }
    return commands[index].command(interp, objc, objv, clientData);
}

/*
 * WidgetInstanceObjCmd --
 *	Widget instance command implementation.
 */
static int
WidgetInstanceObjCmd(
    ClientData clientData,		/* Widget record pointer */
    Tcl_Interp *interp,			/* Current interpreter. */
    int objc,				/* Number of arguments. */
    Tcl_Obj * CONST objv[])		/* Argument objects. */
{
    WidgetCore *corePtr = (WidgetCore *)clientData;
    WidgetCommandSpec *commands = corePtr->widgetSpec->commands;
    int status = TCL_OK;

    Tcl_Preserve(clientData);
    status = WidgetEnsembleCommand(commands, 1, interp, objc, objv, clientData);
    Tcl_Release(clientData);

    return status;
}

/*
 * Command deletion callback for widget instance commands.
 */
static void
WidgetInstanceObjCmdDeleted(ClientData clientData)
{
    WidgetCore *corePtr = (WidgetCore *) clientData;
    corePtr->widgetCmd = NULL;
    if (corePtr->tkwin != NULL)
	Tk_DestroyWindow(corePtr->tkwin);
}

/*
 * WidgetCleanup --
 *	 Final cleanup for widget.
 *
 * @@@ TODO: check all code paths leading to widget destruction,
 * @@@ describe here.
 * @@@ Call widget-specific cleanup routine at an appropriate point.
 */
static void
WidgetCleanup(char *memPtr)
{
    ckfree(memPtr);
}

/*
 * CoreEventProc --
 *	Event handler for basic events.
 *	Processes Expose, Configure, FocusIn/Out, and Destroy events.
 *	Also handles <<ThemeChanged>> virtual events.
 *
 *	For Expose and Configure, simply schedule the widget for redisplay.
 *	For Destroy events, handle the cleanup process.
 *
 *	For Focus events, set/clear the focus bit in the state field.
 *	It turns out this is impossible to do correctly in a binding script,
 *	because Tk filters out focus events with detail == NotifyInferior.
 *
 *	<<NOTE-REALIZED>> On the first ConfigureNotify event 
 *	(which indicates that the window has just been created),
 *	update the layout.  This is to work around two problems:
 *	(1) Virtual events aren't delivered to unrealized widgets
 *	(see bug #835997), so any intervening <<ThemeChanged>> events
 *	will not have been processed.
 *
 *	(2) Geometry calculations in the XP theme don't work
 *	until the widget is realized.
 */

static const unsigned CoreEventMask
    = ExposureMask
    | StructureNotifyMask
    | FocusChangeMask
    | VirtualEventMask
    | ActivateMask
    ;

static void CoreEventProc(ClientData clientData, XEvent *eventPtr)
{
    WidgetCore *corePtr = (WidgetCore *) clientData;

    switch (eventPtr->type)
    {
	case ConfigureNotify :
	    if (!(corePtr->flags & WIDGET_REALIZED)) {
		/* See <<NOTE-REALIZED>> */
		UpdateLayout(corePtr->interp, corePtr);
		UpdateGeometry(corePtr);
		corePtr->flags |= WIDGET_REALIZED;
	    }
	    WidgetChanged(corePtr, REDISPLAY_REQUIRED);
	    break;
	case Expose :
	    if (eventPtr->xexpose.count == 0) {
		WidgetChanged(corePtr, REDISPLAY_REQUIRED);
	    }
	    break;
	case DestroyNotify :
	    if (!corePtr->tkwin) {
		/* All hell has broken loose */
		Tcl_Panic("This is not supposed to happen");
		return;
	    }

	    corePtr->flags |= WIDGET_DESTROYED;

	    Tk_DeleteEventHandler(corePtr->tkwin,
		    CoreEventMask,CoreEventProc,clientData);

	    if (corePtr->flags & UPDATE_PENDING)
		Tcl_CancelIdleCall(RedisplayWidget, clientData);

	    corePtr->widgetSpec->cleanupProc(corePtr);

	    Tk_FreeConfigOptions(
		clientData, corePtr->optionTable, corePtr->tkwin);
	    corePtr->tkwin = NULL;

	    /* NB: this can reenter the interpreter via a command traces */
	    if (corePtr->widgetCmd) {
		Tcl_Command cmd = corePtr->widgetCmd;
		corePtr->widgetCmd = 0;
		Tcl_DeleteCommandFromToken(corePtr->interp, cmd);
	    }
	    Tcl_EventuallyFree(clientData, WidgetCleanup);
	    break;

	case FocusIn:
	case FocusOut:
	    /* Don't process "virtual crossing" events */
	    if (   eventPtr->xfocus.detail == NotifyInferior
		|| eventPtr->xfocus.detail == NotifyAncestor
		|| eventPtr->xfocus.detail == NotifyNonlinear)
	    {
		if (eventPtr->type == FocusIn)
		    corePtr->state |= TTK_STATE_FOCUS;
		else
		    corePtr->state &= ~TTK_STATE_FOCUS;
		WidgetChanged(corePtr, REDISPLAY_REQUIRED);
	    }
	    break;
	case ActivateNotify:
	    corePtr->state &= ~TTK_STATE_BACKGROUND;
	    WidgetChanged(corePtr, REDISPLAY_REQUIRED);
	    break;
	case DeactivateNotify:
	    corePtr->state |= TTK_STATE_BACKGROUND;
	    WidgetChanged(corePtr, REDISPLAY_REQUIRED);
	    break;
	case VirtualEvent:
	    if (!strcmp("ThemeChanged", ((XVirtualEvent *)(eventPtr))->name)) {
		UpdateLayout(corePtr->interp, corePtr);
		UpdateGeometry(corePtr);
		WidgetChanged(corePtr, REDISPLAY_REQUIRED);
	    }
	default:
	    /* can't happen... */
	    break;
    }
}

/*
 * WidgetWorldChanged --
 * 	Default Tk_ClassWorldChangedProc() for widgets.
 * 	Invoked whenever fonts or other system resources are changed;
 * 	recomputes geometry.
 */
static void WidgetWorldChanged(ClientData clientData)
{
    WidgetCore *corePtr = (WidgetCore*)clientData;
    UpdateGeometry(corePtr);
    WidgetChanged(corePtr, REDISPLAY_REQUIRED);
}

static struct Tk_ClassProcs widgetClassProcs = {
    sizeof(Tk_ClassProcs),
    WidgetWorldChanged
};

/*
 * WidgetConstructorObjCmd --
 *	General-purpose widget constructor command implementation.
 *	ClientData is a WidgetSpec *.
 */
int WidgetConstructorObjCmd(
    ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
{
    WidgetSpec *widgetSpec = (WidgetSpec *)clientData;
    const char *className = widgetSpec->className;
    TTK_Theme themePtr = TTK_GetCurrentTheme(interp);
    WidgetCore *corePtr;
    ClientData recordPtr;
    Tk_Window tkwin;
    Tk_OptionTable optionTable;
    int i;
    unsigned optionMask;

    if (objc < 2 || objc % 1 == 1) {
	Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?");
	return TCL_ERROR;
    }

    tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp),
	    Tcl_GetStringFromObj(objv[1], NULL), (char *) NULL);
    if (tkwin == NULL)
	return TCL_ERROR;

    /*
     * Check if a -class resource has been specified:
     * We have to do this before the InitOptions() call,
     * since InitOptions() is affected by the widget class.
     */
    for (i = 2; i < objc; i += 2) {
	const char *resourceName = Tcl_GetString(objv[i]);
	if (!strcmp(resourceName, "-class")) {
	    className = Tcl_GetString(objv[i+1]);
	    break;
	}
    }

    Tk_SetClass(tkwin, className);

    /*
     * Set the BackgroundPixmap to ParentRelative here, so
     * subclasses don't need to worry about setting the background.
     */
    Tk_SetWindowBackgroundPixmap(tkwin, ParentRelative);

    optionTable = Tk_CreateOptionTable(interp, widgetSpec->optionSpecs);

    /*
     * Allocate and initialize the widget record.
     */
    recordPtr = ckalloc(widgetSpec->recordSize);
    memset(recordPtr, 0, widgetSpec->recordSize);
    corePtr = (WidgetCore *)recordPtr;

    corePtr->tkwin	= tkwin;
    corePtr->interp 	= interp;
    corePtr->widgetSpec	= widgetSpec;
    corePtr->widgetCmd	= Tcl_CreateObjCommand(interp, Tk_PathName(tkwin),
	WidgetInstanceObjCmd, recordPtr, WidgetInstanceObjCmdDeleted);
    corePtr->optionTable = optionTable;

    Tk_SetClassProcs(tkwin, &widgetClassProcs, recordPtr);

    if (Tk_InitOptions(interp, recordPtr, optionTable, tkwin) != TCL_OK)
    	goto error;

    if (Tk_SetOptions(interp, recordPtr, optionTable, objc - 2,
	    objv + 2, tkwin, NULL/*savePtr*/, (int *)NULL/*maskPtr*/) != TCL_OK)
	goto error;

    if (!(corePtr->layout = widgetSpec->getLayoutProc(interp,themePtr,corePtr)))
    	goto error;

    if (widgetSpec->initializeProc(interp, recordPtr) != TCL_OK)
	goto error;

    optionMask = ~0UL & ~STYLE_CHANGED;
    if (widgetSpec->configureProc(interp, recordPtr, ~0) != TCL_OK)
	goto error;

    if (widgetSpec->postConfigureProc(interp, recordPtr, ~0) != TCL_OK)
	goto error;

    if (WidgetDestroyed(corePtr))
	goto error;

    UpdateGeometry(corePtr);

    Tk_CreateEventHandler(tkwin, CoreEventMask, CoreEventProc, recordPtr);

    Tcl_SetObjResult(interp, Tcl_NewStringObj(Tk_PathName(tkwin), -1));

    return TCL_OK;

error:
    Tk_FreeConfigOptions(recordPtr, optionTable, tkwin);
    Tk_DestroyWindow(tkwin);
    corePtr->tkwin = 0;
    Tcl_DeleteCommandFromToken(interp, corePtr->widgetCmd);
    ckfree(recordPtr);
    return TCL_ERROR;
}

/*
 * WidgetStateChange --
 * 	Set / clear the specified bits in the 'state' flag,
 */
void WidgetChangeState(WidgetCore *corePtr,
    unsigned int setBits, unsigned int clearBits)
{
    unsigned int oldState = corePtr->state;
    corePtr->state = (oldState & ~clearBits) | setBits;
    if (corePtr->state ^ oldState)
	WidgetChanged(corePtr, REDISPLAY_REQUIRED);
}

/*
 * WidgetGetLayout --
 * 	Default getLayoutProc.
 *	Looks up the layout based on the -style resource (if specified),
 *	otherwise use the widget class.
 */
TTK_Layout WidgetGetLayout(
	Tcl_Interp *interp, TTK_Theme themePtr, void *recordPtr)
{
    WidgetCore *corePtr = recordPtr;
    const char *styleName = 0;

    if (corePtr->styleObj) 
    	styleName = Tcl_GetString(corePtr->styleObj);

    if (!styleName || *styleName == '\0')
    	styleName = corePtr->widgetSpec->className;

    return TTK_CreateLayout(interp, themePtr,styleName, corePtr->optionTable);
}

/*
 * NullInitialize --
 * 	Default widget initializeProc (no-op)
 */
int  NullInitialize(Tcl_Interp *interp, void *recordPtr)
{
    return TCL_OK;
}

/*
 * NullPostConfigure --
 * 	Default widget postConfigureProc (no-op)
 */
int NullPostConfigure(Tcl_Interp *interp, void *clientData, int mask)
{
    return TCL_OK;
}

/* CoreConfigure --
 * 	Default widget configureProc.
 */
int CoreConfigure(Tcl_Interp *interp, void *clientData, int mask)
{
    WidgetCore *corePtr = clientData;
    
    if (mask & STYLE_CHANGED) {
	TTK_Theme theme = TTK_GetCurrentTheme(interp);
	TTK_Layout newLayout =
	    corePtr->widgetSpec->getLayoutProc(interp,theme,corePtr);

	if (!newLayout) {
	    return TCL_ERROR;
	}
	if (corePtr->layout) {
	    TTK_FreeLayout(corePtr->layout);
	}
	corePtr->layout = newLayout;
    }

    return TCL_OK;
}

/*
 * NullCleanup --
 * 	Default widget cleanupProc (no-op)
 */
void NullCleanup(void *recordPtr)
{
    return;
}

/*
 * WidgetDoLayout --
 * 	Default widget layoutProc.
 */
void WidgetDoLayout(void *clientData)
{
    WidgetCore *corePtr = clientData;
    TTK_LayoutGeometry(
	    corePtr->layout, clientData, corePtr->tkwin, corePtr->state,
	    0,0, Tk_Width(corePtr->tkwin), Tk_Height(corePtr->tkwin));
}

/*
 * WidgetDisplay --
 * 	Default widget displayProc.
 */
void WidgetDisplay(void *clientData, Drawable d)
{
    WidgetCore *corePtr = clientData;
    TTK_LayoutDraw(corePtr->layout,clientData,corePtr->tkwin,d,corePtr->state);
}

/*
 * WidgetSize --
 * 	Default widget sizeProc()
 */
int WidgetSize(void *clientData, int *widthPtr, int *heightPtr)
{
    WidgetCore *corePtr = clientData;

    TTK_LayoutReqSize(corePtr->layout, clientData,
	    corePtr->tkwin, corePtr->state, widthPtr, heightPtr);

    return 1;

/* OR: (@@@)
    return *widthPtr > Tk_Width(corePtr->tkwin) 
	|| *heightPtr > Tk_Height(corePtr->tkwin);
*/
}


/* Default implementations for widget subcommands:
*/
int WidgetCgetCommand(
Tcl_Interp *interp, int objc, Tcl_Obj * CONST objv[], void *recordPtr)
{
    WidgetCore *corePtr = recordPtr;
    Tcl_Obj *result;

    if (objc != 3) {
	Tcl_WrongNumArgs(interp, 2, objv, "option");
	return TCL_ERROR;
    }
    result = Tk_GetOptionValue(interp, recordPtr,
		corePtr->optionTable, objv[2], corePtr->tkwin);
    if (result == NULL)
	return TCL_ERROR;
    Tcl_SetObjResult(interp, result);
    return TCL_OK;
}

int WidgetConfigureCommand(
Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr)
{
    WidgetCore *corePtr = recordPtr;
    Tcl_Obj *result;

    if (objc == 2) {
	result = Tk_GetOptionInfo(interp, recordPtr,
		corePtr->optionTable, (Tcl_Obj *) NULL, corePtr->tkwin);
    } else if (objc == 3) {
	result = Tk_GetOptionInfo(interp, recordPtr,
		corePtr->optionTable, objv[2], corePtr->tkwin);
    } else {
	Tk_SavedOptions savedOptions;
	int status;
	int mask = 0;

	status = Tk_SetOptions(interp, recordPtr,
		corePtr->optionTable, objc - 2, objv + 2,
		corePtr->tkwin, &savedOptions, &mask);
	if (status != TCL_OK)
	    return status;

	if (mask & READONLY_OPTION) {
	    Tcl_SetResult(interp,
		    "Attempt to change read-only option", TCL_STATIC);
	    Tk_RestoreSavedOptions(&savedOptions);
	    return TCL_ERROR;
	}

	status = corePtr->widgetSpec->configureProc(interp, recordPtr, mask);
	if (status != TCL_OK) {
	    Tk_RestoreSavedOptions(&savedOptions);
	    return status;
	}
	Tk_FreeSavedOptions(&savedOptions);

	status = corePtr->widgetSpec->postConfigureProc(interp,recordPtr,mask);
	if (status != TCL_OK) {
	    return status;
	}

	if (mask & (STYLE_CHANGED | GEOMETRY_CHANGED)) {
	    UpdateGeometry(corePtr);
	}

	WidgetChanged(corePtr, REDISPLAY_REQUIRED);
	result = Tcl_NewObj();
    }

    if (result == 0) {
	return TCL_ERROR;
    }
    Tcl_SetObjResult(interp, result);
    return TCL_OK;
}

/*
 * WidgetStateCommand --
 *
 * Usage:
 * 	$w state $stateSpec
 * 	$w state
 *
 * If $stateSpec is specified, modify the widget state accordingly,
 * return a new stateSpec representing the changed bits.
 *
 * Otherwise, return a statespec matching all the currently-set bits.
 */

int WidgetStateCommand(
    Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr)
{
    WidgetCore *corePtr = recordPtr;
    TTK_WidgetStateSpec spec;
    int status;
    unsigned oldState;

    if (objc == 2) {
	Tcl_SetObjResult(interp,
	    TTK_NewWidgetStateSpecObj(corePtr->state, corePtr->state));
	return TCL_OK;
    }

    if (objc != 3) {
	Tcl_WrongNumArgs(interp, 2, objv, "state-spec");
	return TCL_ERROR;
    }
    status = TTK_GetWidgetStateSpecFromObj(interp, objv[2], &spec);
    if (status != TCL_OK)
	return status;

    oldState = corePtr->state;

    corePtr->state = TTK_ModifyWidgetState(corePtr->state, &spec);
    WidgetChanged(corePtr, REDISPLAY_REQUIRED);

    Tcl_SetObjResult(interp,
	TTK_NewWidgetStateSpecObj(spec.mask, oldState & spec.mask));
    return status;
}

/*
 * WidgetInstateCommand --
 *
 * Usage:
 * 	$w instate $stateSpec ?$script?
 *
 * Tests if widget state matches $stateSpec.
 * If $script is specified, execute script if state matches.
 * Otherwise, return true/false
 */

int WidgetInstateCommand(
    Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr)
{
    WidgetCore *corePtr = recordPtr;
    unsigned int state = corePtr->state;
    TTK_WidgetStateSpec spec;
    int status = TCL_OK;

    if (objc < 3 || objc > 4) {
	Tcl_WrongNumArgs(interp, 2, objv, "state-spec ?script?");
	return TCL_ERROR;
    }
    status = TTK_GetWidgetStateSpecFromObj(interp, objv[2], &spec);
    if (status != TCL_OK)
	return status;

    if (objc == 3) {
	Tcl_SetObjResult(interp,
	    Tcl_NewBooleanObj(TTK_WidgetStateMatches(state,&spec)));
    } else if (objc == 4) {
	if (TTK_WidgetStateMatches(state,&spec)) {
	    status = Tcl_EvalObjEx(interp, objv[3], 0);
	}
    }
    return status;
}

/*EOF*/