---- [http://www.tfh-berlin.de/~s716193/pics/clock.jpg] [[May 06]] ---- The source of '''Clock Widget in C''' is from: '''Practical Programming in Tcl and Tk (fourth edition)''' written by ''Brent B. Welch, Ken Jones and Jeffrey Hobbs'' (Chapter 49, Writing a Tk Widget in C). I was trying to build the extension just for fun and education. And I wanted to see how it looks like. Here are some pictures (WinXP and Linux) and in bold font you see necessary changes to the source. Without the extension crashes on my maschines using simple tcl-code. if {$::tcl_platform(platform) == "windows"} { load Clock_Widget.dll } else { load Clock_Widget.so } oclock .c pack .c The source-modifications are only nesessary because I use oclock and pack without additional arguments (e.g. -fill both -expand true). Than ClockObjConfigure and ComputeGeometry cause runtime-errors! /* Clock_Widget.h */ #ifndef CLOCK_WIDGET_H #define CLOCK_WIDGET_H #define USE_NON_CONST #define USE_TCL_STUBS #define USE_TK_STUBS #include #include #ifndef __WIN32__ #include #include #endif #undef TCL_STORAGE_CLASS #define TCL_STORAGE_CLASS DLLEXPORT /* * Flag bit definitions. */ #define REDRAW_PENDING 0x1 #define GOT_FOCUS 0x2 #define TICKING 0x4 #define GEOMETRY_MASK 0X1 #define GRAPHICS_MASK 0X2 typedef struct Clock { Tk_Window tkwin; /* The window for the widget */ Display *display; /* Tk's handle on the display */ Tcl_Interp *interp; /* Interpreter of the widget */ Tcl_Command widgetCmd; /* clock instance command. */ Tk_OptionTable optionTable; /* Used to parse options */ /* * Clock-specific attributes. */ int borderWidth; /* Size of 3-D border */ Tcl_Obj *borderWidthPtr; /* Original string value */ int relief; /* Style of 3-D border */ Tk_3DBorder background; /* Color for border & background */ XColor *foreground; /* Color for the text */ XColor *highlight; /* Color for active highlight */ XColor *highlightBg; /* Color for neutral highlight */ int highlightWidth; /* Thickness of highlight rim */ Tcl_Obj *highlightWidthPtr; /* Original string value */ Tk_Font tkfont; /* Font info for the text */ char *format; /* Format for time string */ /* * Graphic contexts and other support. */ GC textGC; /* Text graphics context */ Tk_TimerToken token; /* Periodic callback handle*/ char *clock; /* Pointer to the clock string */ int numChars; /* length of the text */ int textWidth; /* in pixels */ Tcl_Obj *widthPtr; /* The original width string value*/ int textHeight; /* in pixels */ Tcl_Obj *heightPtr; /* The original height string value*/ int padX; /* Horizontal padding */ Tcl_Obj *padXPtr; /* The original padX string value*/ int padY; /* Vertical padding */ Tcl_Obj *padYPtr; /* The original padY string value */ int flags; /* Flags defined below */ } Clock; EXTERN int Clock_widget_Init _ANSI_ARGS_((Tcl_Interp *interp)); int ClockObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]); void ClockObjDelete(ClientData clientData); void ComputeGeometry(Clock *clockPtr); void ClockDisplay(ClientData clientData); void ClockEventProc(ClientData clientData, XEvent *eventPtr); void ClockDestroy(ClientData clientData); int ClockInstanceObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]); int ClockObjConfigure(Tcl_Interp *interp, Clock *clockPtr, int objc, Tcl_Obj *CONST objv[]); #endif and: /* Clock_Widget.cpp */ #include "Clock_Widget.h" static Tk_OptionSpec optionSpecs[] = { {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", "2", Tk_Offset(Clock, borderWidthPtr), Tk_Offset(Clock, borderWidth), 0, 0, GEOMETRY_MASK}, {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground", "black",-1, Tk_Offset(Clock, foreground), 0, (ClientData) "black", GRAPHICS_MASK}, {TK_OPTION_BORDER, "-background", "background", "Background", "light blue", -1, Tk_Offset(Clock, background), 0, (ClientData) "white", GRAPHICS_MASK}, {TK_OPTION_RELIEF, "-relief", "relief", "Relief", "ridge", -1, Tk_Offset(Clock, relief), 0, 0, 0}, {TK_OPTION_COLOR, "-highlightcolor", "highlightColor", "HighlightColor", "red",-1, Tk_Offset(Clock, highlight), 0, (ClientData) "black", GRAPHICS_MASK}, {TK_OPTION_COLOR, "-highlightbackground", "highlightBackground", "HighlightBackground", "light blue",-1, Tk_Offset(Clock, highlightBg), 0, (ClientData) "white", GRAPHICS_MASK}, {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness","HighlightThickness", "2", Tk_Offset(Clock, highlightWidthPtr), Tk_Offset(Clock, highlightWidth), 0, 0, GEOMETRY_MASK}, {TK_OPTION_PIXELS, "-padx", "padX", "Pad", "2", Tk_Offset(Clock, padXPtr), Tk_Offset(Clock, padX), 0, 0, GEOMETRY_MASK}, {TK_OPTION_PIXELS, "-pady", "padY", "Pad", "2", Tk_Offset(Clock, padYPtr), Tk_Offset(Clock, padY), 0, 0, GEOMETRY_MASK}, {TK_OPTION_STRING, "-format", "format", "Format", "%H:%M:%S",-1, Tk_Offset(Clock, format), 0, 0, GEOMETRY_MASK}, {TK_OPTION_FONT, "-font", "font", "Font", "Courier 18", -1, Tk_Offset(Clock, tkfont), 0, 0, (GRAPHICS_MASK|GEOMETRY_MASK)}, {TK_OPTION_END, (char*)NULL, (char*)NULL, (char*)NULL,(char*)NULL, -1, 0, 0, 0, 0} }; EXTERN int Clock_widget_Init (Tcl_Interp *interp) { if (Tcl_InitStubs(interp, "8.1", 0) == NULL) { return TCL_ERROR; } if (Tk_InitStubs(interp, "8.1", 0) == NULL) { return TCL_ERROR; } Tcl_CreateObjCommand(interp, "oclock", ClockObjCmd, (ClientData)(NULL), ClockObjDelete); Tcl_PkgProvide(interp, "Tkclock", "1.0"); return TCL_OK; } int ClockObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { Tk_OptionTable optionTable; Clock *clockPtr; Tk_Window tkwin; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?"); return TCL_ERROR; } optionTable = (Tk_OptionTable)(clientData); if (optionTable == NULL) { Tcl_CmdInfo info; char *name; /* * Initialize the option table for this widget the * first time a clock widget is created. The option * table is saved as our client data. */ optionTable = Tk_CreateOptionTable(interp, optionSpecs); name = Tcl_GetString(objv[0]); Tcl_GetCommandInfo(interp, name, &info); info.objClientData = (ClientData)(optionTable); Tcl_SetCommandInfo(interp, name, &info); } tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp), Tcl_GetString(objv[1]), (char *)(NULL)); if (tkwin == NULL) { return TCL_ERROR; } /* * Set resource class. */ Tk_SetClass(tkwin, "Clock"); /* * Allocate and initialize the widget record. */ clockPtr = (Clock *) ckalloc(sizeof(Clock)); clockPtr->tkwin = tkwin; clockPtr->display = Tk_Display(tkwin); clockPtr->interp = interp; clockPtr->optionTable = optionTable; clockPtr->borderWidth = 0; clockPtr->borderWidthPtr = NULL; clockPtr->highlightWidth = 0; clockPtr->highlightWidthPtr = NULL; clockPtr->relief = TK_RELIEF_FLAT; clockPtr->background = NULL; clockPtr->foreground = NULL; clockPtr->highlight = NULL; clockPtr->highlightBg = NULL; clockPtr->tkfont = NULL; clockPtr->textGC = None; clockPtr->token = NULL; clockPtr->clock = NULL; clockPtr->format = NULL; clockPtr->numChars = 0; clockPtr->textWidth = 0; clockPtr->widthPtr = NULL; clockPtr->textHeight = 0; clockPtr->heightPtr = NULL; clockPtr->padX = 0; clockPtr->padXPtr = NULL; clockPtr->padY = 0; clockPtr->padYPtr = NULL; clockPtr->flags = 0; /* * Register a handler for when the window is * exposed or resized. */ Tk_CreateEventHandler(clockPtr->tkwin, ExposureMask|StructureNotifyMask|FocusChangeMask, ClockEventProc, (ClientData) clockPtr); /* * Create a Tcl command that operates on the widget. */ clockPtr->widgetCmd = Tcl_CreateObjCommand(interp, Tk_PathName(clockPtr->tkwin), ClockInstanceObjCmd, (ClientData) clockPtr, NULL); /* * Parse the command line arguments. */ if ((Tk_InitOptions(interp, (char *)clockPtr, optionTable, tkwin) != TCL_OK) || (ClockObjConfigure(interp, clockPtr, objc-2, objv+2) != TCL_OK)) { Tk_DestroyWindow(clockPtr->tkwin); return TCL_ERROR; } Tcl_SetStringObj(Tcl_GetObjResult(interp), Tk_PathName(clockPtr->tkwin), -1); return TCL_OK; } int ClockInstanceObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { Clock *clockPtr = (Clock *)clientData; const char *commands[] = {"cget", "configure", NULL}; enum command {CLOCK_CGET, CLOCK_CONFIGURE}; int result; Tcl_Obj *objPtr; int index; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?"); return TCL_ERROR; } result = Tcl_GetIndexFromObj(interp, objv[1], const_cast(commands), "option", 0, &index); if (result != TCL_OK) { return result; } switch (index) { case CLOCK_CGET: { if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "cget option"); return TCL_ERROR; } objPtr = Tk_GetOptionValue(interp, (char *)clockPtr, clockPtr->optionTable, (objc == 3) ? objv[2] : NULL, clockPtr->tkwin); if (objPtr == NULL) { return TCL_ERROR; } else { Tcl_SetObjResult(interp, objPtr); } break; } case CLOCK_CONFIGURE: { if (objc <= 3) { /* * Return one item if the option is given, * or return all configuration information. */ objPtr = Tk_GetOptionInfo(interp, (char *) clockPtr, clockPtr->optionTable, (objc == 3) ? objv[2] : NULL, clockPtr->tkwin); if (objPtr == NULL) { return TCL_ERROR; } else { Tcl_SetObjResult(interp, objPtr); } } else { /* * Change one or more attributes. */ result = ClockObjConfigure(interp, clockPtr, objc-2, objv+2); } } } return TCL_OK; } int ClockObjConfigure(Tcl_Interp *interp, Clock *clockPtr, int objc, Tcl_Obj *CONST objv[]) { XGCValues gcValues; GC newGC; Tk_SavedOptions savedOptions; int mask, error; Tcl_Obj *errorResult; /* * The first time through this loop we set the * configuration from the command line inputs. The second * pass is used to restore the configuration in case of * errors */ for (error = 0 ; error <= 1 ; error++) { if (!error) { /* * Tk_SetOptions parses the command arguments * and looks for defaults in the resource * database. */ if (Tk_SetOptions(interp, (char *) clockPtr, clockPtr->optionTable, objc, objv, clockPtr->tkwin, &savedOptions, &mask) != TCL_OK) { continue; } } else { /* * Restore options from saved values */ errorResult = Tcl_GetObjResult(interp); Tcl_IncrRefCount(errorResult); Tk_RestoreSavedOptions(&savedOptions); } '''if (mask & GRAPHICS_MASK || clockPtr->textGC == None) {''' /* * Give the widget a default background so it doesn\" t * get a random background between the time it is * initially displayed by the system and we paint it */ Tk_SetBackgroundFromBorder(clockPtr->tkwin, clockPtr->background); /* * Set up the graphics contexts to display the widget. * The context is used to draw off-screen pixmaps, * so turn off exposure notifications. */ gcValues.background = Tk_3DBorderColor(clockPtr->background)->pixel; gcValues.foreground = clockPtr->foreground->pixel; gcValues.font = Tk_FontId(clockPtr->tkfont); gcValues.graphics_exposures = False; newGC = Tk_GetGC(clockPtr->tkwin, GCBackground|GCForeground|GCFont|GCGraphicsExposures, &gcValues); if (clockPtr->textGC != None) { Tk_FreeGC(clockPtr->display, clockPtr->textGC); } clockPtr->textGC = newGC; } /* * Determine how big the widget wants to be. */ '''if (mask & GEOMETRY_MASK || clockPtr->numChars == 0) {''' ComputeGeometry(clockPtr); } /* * Set up a call to display ourself. */ if ((clockPtr->tkwin != NULL) && Tk_IsMapped(clockPtr->tkwin) && !(clockPtr->flags & REDRAW_PENDING)) { Tk_DoWhenIdle(ClockDisplay, (ClientData) clockPtr); clockPtr->flags |= REDRAW_PENDING; } /* * All OK, break out and avoid error rollback. */ break; } if (!error) { Tk_FreeSavedOptions(&savedOptions); return TCL_OK; } else { Tcl_SetObjResult(interp, errorResult); Tcl_DecrRefCount(errorResult); return TCL_ERROR; } } void ComputeGeometry(Clock *clockPtr) { int width, height; Tk_FontMetrics fm; /* Font size information */ struct tm *tmPtr; /* Time info split into fields */ int bd; /* Padding from borders */ char clock[1000]; /* Displayed time */ /* * Get the time and format it to see how big it will be. */ #ifdef __WIN32__ time_t aclock; time( &aclock ); // Get time in seconds tmPtr = localtime( &aclock ); // Convert time to struct tm form #else struct timeval tv; /* BSD-style time value */ gettimeofday(&tv, NULL); tmPtr = localtime(&tv.tv_sec); #endif strftime(clock, 1000, clockPtr->format, tmPtr); if (clockPtr->clock != NULL) { ckfree(clockPtr->clock); } clockPtr->clock = ckalloc(1+strlen(clock)); clockPtr->numChars = strlen(clock); bd = clockPtr->highlightWidth + clockPtr->borderWidth; Tk_GetFontMetrics(clockPtr->tkfont, &fm); height = fm.linespace + 2*(bd + clockPtr->padY); Tk_MeasureChars(clockPtr->tkfont, clock, ''' clockPtr->numChars, -1, TK_PARTIAL_OK, &clockPtr->textWidth);''' width = clockPtr->textWidth + 2*(bd + clockPtr->padX); Tk_GeometryRequest(clockPtr->tkwin, width, height); Tk_SetInternalBorder(clockPtr->tkwin, bd); } void ClockDisplay(ClientData clientData) { Clock *clockPtr = (Clock *)clientData; Tk_Window tkwin = clockPtr->tkwin; GC gc; /* Graphics Context for highlight */ Tk_TextLayout layout; /* Text measurement state */ Pixmap pixmap; /* Temporary drawing area */ int x, y; /* Coordinates */ int width, height; /* Size */ struct tm *tmPtr; /* Time info split into fields */ /* * Make sure the clock still exists * and is mapped onto the display before painting. */ clockPtr->flags &= ~(REDRAW_PENDING|TICKING); if ((clockPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) { return; } /* * Format the time into a string. * localtime chops up the time into fields. * strftime formats the fields into a string. */ #ifdef __WIN32__ time_t aclock; time( &aclock ); // Get time in seconds tmPtr = localtime( &aclock ); // Convert time to struct tm form #else struct timeval tv; /* BSD-style time value */ gettimeofday(&tv, NULL); tmPtr = localtime(&tv.tv_sec); #endif strftime(clockPtr->clock, clockPtr->numChars+1, clockPtr->format, tmPtr); /* * To avoid flicker when the display is updated, the new * image is painted in an offscreen pixmap and then * copied onto the display in one operation. Allocate the * pixmap and paint its background. */ pixmap = Tk_GetPixmap(clockPtr->display, Tk_WindowId(tkwin), Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin)); Tk_Fill3DRectangle(tkwin, pixmap, clockPtr->background, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT); /* * Paint the text first. */ layout = Tk_ComputeTextLayout(clockPtr->tkfont, clockPtr->clock, clockPtr->numChars, 0, TK_JUSTIFY_CENTER, 0, &width, &height); x = (Tk_Width(tkwin) - width)/2; y = (Tk_Height(tkwin) - height)/2; Tk_DrawTextLayout(clockPtr->display, pixmap, clockPtr->textGC, layout, x, y, 0, -1); /* * Display the borders, so they overwrite any of the * text that extends to the edge of the display. */ if (clockPtr->relief != TK_RELIEF_FLAT) { Tk_Draw3DRectangle(tkwin, pixmap, clockPtr->background, clockPtr->highlightWidth, clockPtr->highlightWidth, Tk_Width(tkwin) - 2*clockPtr->highlightWidth, Tk_Height(tkwin) - 2*clockPtr->highlightWidth, clockPtr->borderWidth, clockPtr->relief); } if (clockPtr->highlightWidth != 0) { /* * This GC is associated with the color, and Tk caches * the GC until the color is freed. Hence no freeGC. */ if (clockPtr->flags & GOT_FOCUS) { gc = Tk_GCForColor(clockPtr->highlight, pixmap); } else { gc = Tk_GCForColor(clockPtr->highlightBg, pixmap); } Tk_DrawFocusHighlight(tkwin, gc, clockPtr->highlightWidth, pixmap); } /* * Copy the information from the off-screen pixmap onto * the screen, then delete the pixmap. */ XCopyArea(clockPtr->display, pixmap, Tk_WindowId(tkwin), clockPtr->textGC, 0, 0, (unsigned) Tk_Width(tkwin), (unsigned) Tk_Height(tkwin), 0, 0); Tk_FreePixmap(clockPtr->display, pixmap); /* * Queue another call to ourselves. The rate at which * this is done could be optimized. */ clockPtr->token = Tk_CreateTimerHandler(1000, ClockDisplay, (ClientData)clockPtr); clockPtr->flags |= TICKING; } void ClockEventProc(ClientData clientData, XEvent *eventPtr) { Clock *clockPtr = (Clock *) clientData; if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) { goto redraw; } else if (eventPtr->type == DestroyNotify) { Tcl_DeleteCommandFromToken(clockPtr->interp, clockPtr->widgetCmd); /* * Zapping the tkwin lets the other procedures * know we are being destroyed. */ clockPtr->tkwin = NULL; if (clockPtr->flags & REDRAW_PENDING) { Tk_CancelIdleCall(ClockDisplay, (ClientData) clockPtr); clockPtr->flags &= ~REDRAW_PENDING; } if (clockPtr->flags & TICKING) { Tk_DeleteTimerHandler(clockPtr->token); clockPtr->flags &= ~TICKING; } /* * This results in a call to ClockDestroy. */ Tk_EventuallyFree((ClientData) clockPtr, (Tk_FreeProc *)ClockDestroy); } else if (eventPtr->type == FocusIn) { if (eventPtr->xfocus.detail != NotifyPointer) { clockPtr->flags |= GOT_FOCUS; if (clockPtr->highlightWidth > 0) { goto redraw; } } } else if (eventPtr->type == FocusOut) { if (eventPtr->xfocus.detail != NotifyPointer) { clockPtr->flags &= ~GOT_FOCUS; if (clockPtr->highlightWidth > 0) { goto redraw; } } } return; redraw: if ((clockPtr->tkwin != NULL) && !(clockPtr->flags & REDRAW_PENDING)) { Tk_DoWhenIdle(ClockDisplay, (ClientData) clockPtr); clockPtr->flags |= REDRAW_PENDING; } } void ClockDestroy(ClientData clientData) { register Clock *clockPtr = (Clock *) clientData; /* * Free up all the stuff that requires special handling, * then let Tk_FreeOptions handle resources associated * with the widget attributes. */ if (clockPtr->textGC != None) { Tk_FreeGC(clockPtr->display, clockPtr->textGC); } if (clockPtr->clock != NULL) { Tcl_Free(clockPtr->clock); } if (clockPtr->flags & TICKING) { Tk_DeleteTimerHandler(clockPtr->token); } if (clockPtr->flags & REDRAW_PENDING) { Tk_CancelIdleCall(ClockDisplay, (ClientData) clockPtr); } /* * This frees up colors and fonts and any allocated * storage associated with the widget attributes. */ Tk_FreeConfigOptions((char *)clockPtr,clockPtr->optionTable, clockPtr->tkwin); Tcl_Free((char *) clockPtr); } void ClockObjDelete(ClientData clientData) { Tk_OptionTable optionTable = (Tk_OptionTable) clientData; if (optionTable != NULL) { Tk_DeleteOptionTable(optionTable); } } ---- [[ [Category Education] | [Category Example] ]]