[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. So 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). Without ClockObjConfigure and ComputeGeometry causes 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 <tk.h> #include <ctime> #ifndef __WIN32__ #include <cstring> #include <sys/time.h> #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 */ /*
*/ 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 */ /*
*/ 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 /* 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; /*
*/ 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; } /*
*/ Tk_SetClass(tkwin, "Clock"); /*
*/ 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; /*
*/ Tk_CreateEventHandler(clockPtr->tkwin, ExposureMask|StructureNotifyMask|FocusChangeMask, ClockEventProc, (ClientData) clockPtr); /*
*/ clockPtr->widgetCmd = Tcl_CreateObjCommand(interp, Tk_PathName(clockPtr->tkwin), ClockInstanceObjCmd, (ClientData) clockPtr, NULL); /*
*/ 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<char**>(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) { /*
*/ 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 { /*
*/ 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; /*
*/ for (error = 0 ; error <= 1 ; error++) { if (!error) { /*
*/ if (Tk_SetOptions(interp, (char *) clockPtr, clockPtr->optionTable, objc, objv, clockPtr->tkwin, &savedOptions, &mask) != TCL_OK) { continue; } } else { /*
*/ errorResult = Tcl_GetObjResult(interp); Tcl_IncrRefCount(errorResult); Tk_RestoreSavedOptions(&savedOptions); } '''if (mask & GRAPHICS_MASK || clockPtr->textGC == None) {''' /*
*/ Tk_SetBackgroundFromBorder(clockPtr->tkwin, clockPtr->background); /*
*/ 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; } /*
*/ '''if (mask & GEOMETRY_MASK || clockPtr->numChars == 0) {''' ComputeGeometry(clockPtr); } /*
*/ if ((clockPtr->tkwin != NULL) && Tk_IsMapped(clockPtr->tkwin) && !(clockPtr->flags & REDRAW_PENDING)) { Tk_DoWhenIdle(ClockDisplay, (ClientData) clockPtr); clockPtr->flags |= REDRAW_PENDING; } /*
*/ 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 */ /*
*/ #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 */ /*
*/ clockPtr->flags &= ~(REDRAW_PENDING|TICKING); if ((clockPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) { return; } /*
*/ #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); /*
*/ 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); /*
*/ 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); /*
*/ 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) { /*
*/ 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); } /*
*/ 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); /*
*/ 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); /*
*/ 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; } /*
*/ 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; /*
*/ 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); } /*
*/ 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); } }