/* $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*/