/* $Id: ttkWidget.c,v 1.1 2006/10/31 01:42:26 hobbs Exp $ * Copyright (c) 2003, Joe English * * Ttk widget implementation, core widget utilities. * */ #include #include #include "ttkTheme.h" #include "ttkWidget.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) { int reqWidth = 1, reqHeight = 1; if (corePtr->widgetSpec->sizeProc(corePtr,&reqWidth,&reqHeight)) { Tk_GeometryRequest(corePtr->tkwin, reqWidth, reqHeight); } } /* * RedisplayWidget -- * Redraw a widget. Called as an idle handler. */ static void RedisplayWidget(ClientData recordPtr) { WidgetCore *corePtr = (WidgetCore *)recordPtr; Tk_Window tkwin = corePtr->tkwin; Drawable d; XGCValues gcValues; GC gc; corePtr->flags &= ~REDISPLAY_PENDING; 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(recordPtr); corePtr->widgetSpec->displayProc(recordPtr, 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); } /* TtkRedisplayWidget -- * Schedule redisplay as an idle handler. */ void TtkRedisplayWidget(WidgetCore *corePtr) { if (corePtr->flags & WIDGET_DESTROYED) { return; } if (!(corePtr->flags & REDISPLAY_PENDING)) { Tcl_DoWhenIdle(RedisplayWidget, (ClientData) corePtr); corePtr->flags |= REDISPLAY_PENDING; } } /* TtkResizeWidget -- * Recompute widget size, schedule geometry propagation and redisplay. */ void TtkResizeWidget(WidgetCore *corePtr) { if (corePtr->flags & WIDGET_DESTROYED) { return; } UpdateGeometry(corePtr); TtkRedisplayWidget(corePtr); } /* 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 <> 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. * * For Deactivate/Activate pseudo-events, clear/set the background state flag. * * <> 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 <> 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 <> */ UpdateLayout(corePtr->interp, corePtr); UpdateGeometry(corePtr); corePtr->flags |= WIDGET_REALIZED; } TtkRedisplayWidget(corePtr); break; case Expose : if (eventPtr->xexpose.count == 0) { TtkRedisplayWidget(corePtr); } break; case DestroyNotify : corePtr->flags |= WIDGET_DESTROYED; Tk_DeleteEventHandler(corePtr->tkwin, CoreEventMask,CoreEventProc,clientData); if (corePtr->flags & REDISPLAY_PENDING) { Tcl_CancelIdleCall(RedisplayWidget, clientData); } corePtr->widgetSpec->cleanupProc(corePtr); Tk_FreeConfigOptions( clientData, corePtr->optionTable, corePtr->tkwin); corePtr->tkwin = NULL; if (corePtr->layout) { Ttk_FreeLayout(corePtr->layout); } /* 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; TtkRedisplayWidget(corePtr); } break; case ActivateNotify: corePtr->state &= ~TTK_STATE_BACKGROUND; TtkRedisplayWidget(corePtr); break; case DeactivateNotify: corePtr->state |= TTK_STATE_BACKGROUND; TtkRedisplayWidget(corePtr); break; case VirtualEvent: if (!strcmp("ThemeChanged", ((XVirtualEvent *)(eventPtr))->name)) { UpdateLayout(corePtr->interp, corePtr); UpdateGeometry(corePtr); TtkRedisplayWidget(corePtr); } 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); TtkRedisplayWidget(corePtr); } 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; WidgetCore *corePtr; ClientData recordPtr; Tk_Window tkwin; Tk_OptionTable optionTable; int i; 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 (widgetSpec->initializeProc(interp, recordPtr) != 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 (widgetSpec->configureProc(interp, recordPtr, ~0) != TCL_OK) goto error; if (widgetSpec->postConfigureProc(interp, recordPtr, ~0) != TCL_OK) goto error; if (WidgetDestroyed(corePtr)) goto error; UpdateLayout(interp, corePtr); UpdateGeometry(corePtr); Tk_CreateEventHandler(tkwin, CoreEventMask, CoreEventProc, recordPtr); Tcl_SetObjResult(interp, Tcl_NewStringObj(Tk_PathName(tkwin), -1)); return TCL_OK; error: if (corePtr->layout) { Ttk_FreeLayout(corePtr->layout); corePtr->layout = 0; } Tk_FreeConfigOptions(recordPtr, optionTable, tkwin); Tk_DestroyWindow(tkwin); corePtr->tkwin = 0; Tcl_DeleteCommandFromToken(interp, corePtr->widgetCmd); ckfree(recordPtr); return TCL_ERROR; } /* * WidgetChangeState -- * Set / clear the specified bits in the 'state' flag, */ void WidgetChangeState(WidgetCore *corePtr, unsigned int setBits, unsigned int clearBits) { Ttk_State oldState = corePtr->state; corePtr->state = (oldState & ~clearBits) | setBits; if (corePtr->state ^ oldState) { TtkRedisplayWidget(corePtr); } } /* * 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, recordPtr, corePtr->optionTable, corePtr->tkwin); } /* * WidgetGetOrientedLayout -- * Helper routine. Same as WidgetGetLayout, but prefixes * "Horizontal." or "Vertical." to the style name, depending * on the value of the 'orient' option. */ Ttk_Layout WidgetGetOrientedLayout( Tcl_Interp *interp, Ttk_Theme themePtr, void *recordPtr, Tcl_Obj *orientObj) { WidgetCore *corePtr = recordPtr; const char *baseStyleName = 0; Tcl_DString styleName; int orient = TTK_ORIENT_HORIZONTAL; Ttk_Layout layout; Tcl_DStringInit(&styleName); /* Prefix: */ Ttk_GetOrientFromObj(NULL, orientObj, &orient); if (orient == TTK_ORIENT_HORIZONTAL) Tcl_DStringAppend(&styleName, "Horizontal.", -1); else Tcl_DStringAppend(&styleName, "Vertical.", -1); /* Add base style name: */ if (corePtr->styleObj) baseStyleName = Tcl_GetString(corePtr->styleObj); if (!baseStyleName || *baseStyleName == '\0') baseStyleName = corePtr->widgetSpec->className; Tcl_DStringAppend(&styleName, baseStyleName, -1); /* Create layout: */ layout= Ttk_CreateLayout(interp, themePtr, Tcl_DStringValue(&styleName), recordPtr, corePtr->optionTable, corePtr->tkwin); Tcl_DStringFree(&styleName); return layout; } /* * 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_PlaceLayout(corePtr->layout,corePtr->state,Ttk_WinBox(corePtr->tkwin)); } /* * WidgetDisplay -- * Default widget displayProc. */ void WidgetDisplay(void *recordPtr, Drawable d) { WidgetCore *corePtr = recordPtr; Ttk_DrawLayout(corePtr->layout, corePtr->state, d); } /* * WidgetSize -- * Default widget sizeProc() */ int WidgetSize(void *recordPtr, int *widthPtr, int *heightPtr) { WidgetCore *corePtr = recordPtr; Ttk_LayoutSize(corePtr->layout, 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); } TtkRedisplayWidget(corePtr); result = Tcl_NewObj(); } if (result == 0) { return TCL_ERROR; } Tcl_SetObjResult(interp, result); return TCL_OK; } /* $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_StateSpec spec; int status; Ttk_State oldState, changed; if (objc == 2) { Tcl_SetObjResult(interp, Ttk_NewStateSpecObj(corePtr->state, 0ul)); return TCL_OK; } if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "state-spec"); return TCL_ERROR; } status = Ttk_GetStateSpecFromObj(interp, objv[2], &spec); if (status != TCL_OK) return status; oldState = corePtr->state; corePtr->state = Ttk_ModifyState(corePtr->state, &spec); changed = corePtr->state ^ oldState; TtkRedisplayWidget(corePtr); Tcl_SetObjResult(interp, Ttk_NewStateSpecObj(oldState & changed, ~oldState & changed)); return status; } /* $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; Ttk_State state = corePtr->state; Ttk_StateSpec spec; int status = TCL_OK; if (objc < 3 || objc > 4) { Tcl_WrongNumArgs(interp, 2, objv, "state-spec ?script?"); return TCL_ERROR; } status = Ttk_GetStateSpecFromObj(interp, objv[2], &spec); if (status != TCL_OK) return status; if (objc == 3) { Tcl_SetObjResult(interp, Tcl_NewBooleanObj(Ttk_StateMatches(state,&spec))); } else if (objc == 4) { if (Ttk_StateMatches(state,&spec)) { status = Tcl_EvalObjEx(interp, objv[3], 0); } } return status; } /* $w identify $x $y * Returns: name of element at $x, $y */ int WidgetIdentifyCommand( Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr) { WidgetCore *corePtr = recordPtr; Ttk_LayoutNode *node; int x, y; if (objc != 4) { Tcl_WrongNumArgs(interp, 2, objv, "x y"); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK || Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK) return TCL_ERROR; node = Ttk_LayoutIdentify(corePtr->layout, x, y); if (node) { const char *elementName = Ttk_LayoutNodeName(node); Tcl_SetObjResult(interp,Tcl_NewStringObj(elementName,-1)); } return TCL_OK; } /*EOF*/