Previous section   Next section

Practical Programming in Tcl & Tk, Third Edition
By Brent B. Welch

Table of Contents
Chapter 46.  Writing a Tk Widget in C

Displaying the Clock

There are two parts to a widget's display. First, the size must be determined. This is done at configuration time, and then that space is requested from the geometry manager. When the widget is later displayed, it should use the Tk_Width and Tk_Height calls to find out how much space was actually allocated to it by the geometry manager. Example 46-13 shows ComputeGeometry. This procedure is identical in both versions of the widget.

Example 46-13 ComputeGeometry computes the widget's size.
static void
ComputeGeometry(Clock *clockPtr)
   int width, height;
   Tk_FontMetrics fm;      /* Font size information */
   struct tm *tmPtr;       /* Time info split into fields */
   struct timeval tv;      /* BSD-style time value */
   int bd;                 /* Padding from borders */
   char clock[1000];       /* Displayed time */

    * Get the time and format it to see how big it will be.
   gettimeofday(&tv, NULL);
   tmPtr = localtime(&tv.tv_sec);
   strftime(clock, 1000, clockPtr->format, tmPtr);
   if (clockPtr->clock != NULL) {
   clockPtr->clock = Tcl_Alloc(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, 0, 0, &clockPtr->textWidth);
   width = clockPtr->textWidth + 2*(bd + clockPtr->padX);

   Tk_GeometryRequest(clockPtr->tkwin, width, height);
   Tk_SetInternalBorder(clockPtr->tkwin, bd);

Finally, we get to the actual display of the widget! The routine is careful to check that the widget still exists and is mapped. This is important because the redisplay is scheduled asynchronously. The current time is converted to a string. This uses the POSIX library procedures gettimeofday, localtime, and strftime. There might be different routines on your system. The string is painted into a pixmap, which is a drawable region of memory that is off-screen. After the whole display has been painted, the pixmap is copied into on-screen memory to avoid flickering as the image is cleared and repainted. The text is painted first, then the borders. This ensures that the borders overwrite the text if the widget has not been allocated enough room by the geometry manager.

This example allocates and frees the off-screen pixmap for each redisplay. This is the standard idiom for Tk widgets. They temporarily allocate the off-screen pixmap each time they redisplay. In the case of a clock that updates every second, it might be reasonable to permanently allocate the pixmap and store its pointer in the Clock data structure. Make sure to reallocate the pixmap if the size changes.

After the display is finished, another call to the display routine is scheduled to happen in one second. If you were to embellish this widget, you might want to make the uptime period a parameter. The TICKING flag is used to note that the timer callback is scheduled. It is checked when the widget is destroyed so that the callback can be canceled. Example 46-14 shows ClockDisplay. This procedure is identical in both versions of the widget.

Example 46-14 The ClockDisplay procedure.
static 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 offset, x, y;      /* Coordinates */
   int width, height;     /* Size */
   struct tm *tmPtr;      /* Time info split into fields */
   struct timeval tv;     /* BSD-style time value */

    * 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)) {
    * Format the time into a string.
    * localtime chops up the time into fields.
    * strftime formats the fields into a string.
   gettimeofday(&tv, NULL);
   tmPtr = localtime(&tv.tv_sec);
   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,
         Tk_Width(tkwin) - 2*clockPtr->highlightWidth,
         Tk_Height(tkwin) - 2*clockPtr->highlightWidth,
         clockPtr->borderWidth, clockPtr->relief);
   if (clockPtr->highlightWidth != 0) {
      GC gc;

       * 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, Tk_Width(tkwin),
      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;

      Previous section   Next section