Introduction

This paper describes a portable interface for Tk. If you don't know what is Tk, check www documents from http://www.sco.com/IXI/of_interest/tcl/Tcl.html.

Porting Tk to new UI environments has been a matter of debate for a long time. Our project was to find out is it possible to make an portable interface for Tk and if it is, how it can be done. The result is a proof of the Tk's portability concept. In this document we refer to our portable interface as PortTk.

PortTk is created by Filip Karlemo (fkarlemo@snakemail.hut.fi), Ari Kiviluoma (akiviluo@snakemail.hut.fi) and Kimmo Joki-Korpela (kjk@snakemail.hut.fi) as a project course at Helsinki University of Technology. Our supervisors Pertti Kasanen, Hannu Aronsson and Sami Tikka introduced the idea of PortTk.

Manual

This is a manual describing the PortTk portable interface. PortTk is actually a project that was supposed to investigate how a portable interface would fit into Tk 3.6. Some parts of Tk 3.6 has been reorganized with a portable interface in mind, and this is the manual describing these parts.

1. Tutorial

PortTk supports the X Window System v. 11 and Microsoft Windows 3.1. The main goal with PortTk was to make a portable interface without any preprosessor statements all over the code, e.g. C-type #ifdef TCL_MSDOS_PORT. Usually this seems to be the case when implementing a portable interface, this comment is based on rumours. Code that is the same for all systems is placed in a main directory 'src' which stands for 'source directory'. Here is the big part of Tk situated. The system dependent functions are placed in separate directories, e.g. in X: src/x, in Windows: src/windows. During compilation the linker will get system dependent functions from the platform specific subdirectory. The directory structure is explained in more detail later on. The functions has to have the same number of arguments, but the type of the arguments can be overridden by system dependent structures defined in header files.

When a new port of PortTk is started, the system dependent directories have to be made. Then a suitable 'Makefile' that tells the compiler where to look for system dependent files and where the common files are situated. All object-, library- and executable files should be redirected to the directory system dependent 'wish' directory, e.g. in X: wish/X. This way the source code stay intact and can be used by other system compilers sharing the same permanent storage. Then the real porting can start. The arguments and what the function should do are described later on in this manual. The porter must then implement this for the local system.

2. The original Tk

The problem with the whole idea of making a portable interface of Tk is:

  1. - Tk is not implemented with a portable interface in mind.
  2. - Xlib code and structs are all over the code. At which level of the code should the interface be drawn.
For example the Xevents and the GC structs are used directly in widgets. If one would not use the X-like definitions there would be so many places that would have to be changed that one would be better off by writing a totally new Tk. The GC struct is used in X to define how something is to be drawn on the screen. For example a GC for a line has to contain the color for the line and the width of the line. Optionally there can be defined a stipple that is to be used for the line. In Windows there is no such struct. Windows uses the last type of color etc. used, this is also the case in Mac.

3. How PortTk is organized on a directory level

PORTTK
  +---LIB             - initialization scripts, system independent
  ¦   +---TCL
  ¦   +---TK
  +---SRC             - source code of Tk, system independent
  ¦   +---X           - X dependent code, system dependent
  ¦   +---WINDOWS     - Windows dependent code
  ¦   ¦   +---X11     - Xlib type structures definitions for Windows
  ¦   +---BITMAPS     - Tk bitmaps, system independent
  +---TCL             - Tcl for different systems, empty directory
  ¦   +---WINDOWS     - Tcl for MSDOS - Windows
  +---WISH            - Dependent object-, library- and executable files, empty
      +---WINDOWS     - Windows code for wish and executables etc.
      +---X           - X executables for wish etc.
PortTk's directory structure has separate directories for Tk source files, Tcl source files, initialization scripts and makefiles. This keeps directories clean especially when one is compiling and it illustrates Tk's structure.

Under the PORTTK directory there are four directories LIB, SRC, TCL and WISH. The LIB directory contains Tcl and Tk libraries in their own directories. These are read automatically when Tk is started. Source files which are common to all environments are in SRC directory. Under it there is BITMAPS directory where are all the Tk's built-in bitmaps.

There are also X and WINDOWS directories where the environment dependent source files are. New directories for new Tk ports should be created here. Under the WINDOWS directory there is X11 directory where are some X header files e.g. xlib.h. If similar X header files which replace originals are made in other Tk ports this directory structure should be used.

The TCL directory contains Tcl source files if they are needed in compiling. In the WINDOWS directory there are Tcl msdos port source files. The WISH directory has it's own subdirectory for every environment where Tk is ported. In these directories there are makefiles and file which have main function if Tk's main function isn't valid. E.g. Windows version uses own main function in wish.c. When compiling PortTk files object files, library files and the executable file are created into these directories.

4. Reference manual

PortTk is an effort to make a portable interface to Tk. Because of time restrictions and new ideas, changes all the time, there is not that much to show. By putting the portable interface near X, we have the 'nearly' original version of Tk running on X and some of Tk running under Windows. In Widows there are a couple of widgets that work OK, but the rest work in a little funny way. These versions use a lot of the same code, the system dependent code sections are put aside in system dependent directories (e.g. x/, windows/ ...).

PortTk uses X-lookalike structs, e.g. XColor, renamed to Tk_Color, XFontStruct, renamed to Tk_FontStruct. This is to leave as much as possible untouched code from the original Tk. But it is not a Xlib port, so the structs are just similar (not necessarily totally similar) to X.

PortTk started by using tkwin as the base for the port. By using a lot of Xlib structure definitions, the code is now much more similar to the original Tk code than tkwin.

4.1 Listing of the PortTk portable interface data structures

Please refer to different implementations for further ideas.

Replace 'xxx' with the comment underneath.

src/system_dependent_dir/dptk.h

#define WhitePixelOfScreen(s) xxx  
#define BlackPixelOfScreen(s) xxx  
#define MAX_INTENSITY         xxx  
typedef xxx int_32bit;           
typedef xxx GenEnvDpnt;          
typedef xxx Colormap;            
typedef xxx DrawableArea;        
typedef xxx Screen;
typedef struct {
        ...
        unsigned long foreground;/* foreground pixel */
        unsigned long background;/* background pixel */
        int line_width;         /* line width */
        int line_style;         /* LineSolid, LineOnOffDash, LineDoubleDash */
        ...
        int fill_style;         /* FillSolid, FillTiled, 
                                   FillStippled, FillOpaeueStippled */
        ...
        Pixmap tile;            /* tile pixmap for tiling operations */
        Pixmap stipple;         /* stipple 1 plane pixmap for stipping */
        ...
      Font font;                /* default text font for text operations */
        ...
} XGCValues;
typedef XGCValues * GC;
typedef struct {
        unsigned long pixel;    /* identifier for the located real color */
        unsigned short red, green, blue;
        char flags;  /* do_red, do_green, do_blue */
        char pad;
} Tk_Color;
/*
 * per character font metric information.
 */
typedef struct {
    short       lbearing;       /* origin to left edge of raster */
    short       rbearing;       /* origin to right edge of raster */
    short       width;          /* advance to next char's origin */
    short       ascent;         /* baseline to top edge of raster */
    short       descent;        /* baseline to bottom edge of raster */
    unsigned short attributes;  /* per char flags (not predefined) */
} XCharStruct;
typedef XCharStruct Tk_CharStruct;
typedef struct {
    ...
    Font        fid;            /* Font id for this font */
    ...
    XFontProp   *properties;    /* pointer to array of additional properties*/
    XCharStruct min_bounds;     /* minimum bounds over all existing char*/
    XCharStruct max_bounds;     /* maximum bounds over all existing char*/
    XCharStruct *per_char;      /* first_char to last_char information */
    int         ascent;         /* log. extent above baseline for spacing */
    int         descent;        /* log. descent below baseline for spacing */
} Tk_FontStruct;     // XFontStruct

4.2 Listing of the PortTk portable interface functions

src/system_dependent_dir/dptk.c

/*
 *----------------------------------------------------------------------
 *
 * Tk_Flush -- 
 *
 *      Used to force the screen to be updated
 *
 * Results:
 *      None
 *
 * Side effects:
 *      None
 *
 *----------------------------------------------------------------------
 */
void
Tk_Flush(GenEnvDpnt *genEnvDpnt)
{
};

/*
 *----------------------------------------------------------------------
 *
 * Initialize_genEnvDpnt --
 *
 *      Initializes general environment independent data, usually for
 *      a widget. (eg. Display in X)
 *
 * Results:
 *      None.
 *
 * Side effects:
 *       GenEnvDpnt data get set to NULL.
 *
 *----------------------------------------------------------------------
 */
void
Initialize_genEnvDpnt(GenEnvDpnt **p, Tk_Window new)
{
};


/*
 *----------------------------------------------------------------------
 *
 * Tk_TextWidth --
 *
 *      Counts the width of a string of text in a specific font
 *
 * Results:
 *      The width of the text in logical units
 *
 * Side effects:
 *      None
 *
 *----------------------------------------------------------------------
 */
int 
Tk_TextWidth(Tk_FontStruct *fontPtr, char *text, int textLength)
{
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_GetTextExtents --  
 *
 *      Called to get informatiom about a string
 *
 * Results:
 *      Measurements of the string, at least lbearing and rbearing 
 *      should be converted according to Xlib's definition of lbearing and
 *      rbearing.
 *
 * Side effects:
 *      Tk_CharStruct get filled in.
 *
 *----------------------------------------------------------------------
 */
void _export
Tk_GetTextExtents(Tk_FontStruct *fontPtr, char *text, int textLength,
                int *dummy1, int *dummy2, int *dummy3, Tk_CharStruct *bbox)
{
}


/*
 *----------------------------------------------------------------------
 *
 * Tk_GetVirtualDrawableArea --
 *
 *      Called to get a place where to do all the offscreen drawing.
 *
 * Results:
 *      An offscreen handle is returned in the DrawableArea argument.
 *
 * Side effects:
 *      Depends, perhaps local variables are filled out
 *
 *----------------------------------------------------------------------
 */
void
Tk_GetVirtualDrawableArea(GenEnvDpnt *ged, DrawableArea *da, Tk_Window tkwin,
                int_32bit width, int_32bit height)
{
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_DrawVirtualDrawableArea --
 *
 *      Called after Tk_GetVirtualDrawableArea after doing offscreen
 *      drawing in the DrawableArea. Copies the offscreen stuff to
 *      the screen.
 *
 * Results:
 *      None
 *
 * Side effects:
 *      The offscreen drawing is copied to the screen.
 *      Static variables has to be cleaned?
 *
 *----------------------------------------------------------------------
 */
void
Tk_DrawVirtualDrawableArea(GenEnvDpnt *display, Tk_Window tkwin, GC gc,
                DrawableArea da, int_32bit src_x, int_32bit src_y,
                int_32bit width, int_32bit height, int_32bit dest_x,
                int_32bit dest_y)
{
}

src/system_dependent_dir/dp2d.c

/*
 *----------------------------------------------------------------------
 *
 * Tk_Fill2DRectangle -- 
 *
 *      Arguments X-compatible.
 *      Fills a rectangle with a stipple or a plain color.
 *
 * Results:
 *      None
 *
 * Side effects:
 *      Drawing done to the given drawable
 *
 *----------------------------------------------------------------------
 */
void
Tk_Fill2DRectangle(GenEnvDpnt *ged, Drawable da, GC gc, int x, int y,
        unsigned int width, unsigned int height)
{
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_Draw2DRectangle --
 *
 *      Arguments X-compatible.
 *      Draws a rectangle with a plain color.
 *
 * Results:
 *      None
 *
 * Side effects:
 *      Drawing done to the given drawable
 *
 *----------------------------------------------------------------------
 */
void
Tk_Draw2DRectangle(GenEnvDpnt *ged, Drawable da, GC gc, int x, int y,
        unsigned int width, unsigned int height)
{
}


/*
 *----------------------------------------------------------------------
 *
 * Tk_Fill2DPolygon --  
 *
 *      Arguments X-compatible.
 *      Fills a polygon.
 *
 * Results:
 *      None
 *
 * Side effects:
 *      Drawing done to the given drawable (offscreen or not)
 *
 *----------------------------------------------------------------------
 */
void
Tk_Fill2DPolygon(GenEnvDpnt *ged, Drawable da, GC gc,
                    XPoint points[], int n_points, int shape, int mode)
{
}


/*
 *----------------------------------------------------------------------
 *
 * Tk_DrawString --
 *
 *      Arguments X-compatible.
 *      Draws a string in foreground and background to the screen
 *
 * Results:
 *      None
 *
 * Side effects:
 *      Drawing done to the given drawable (offscreen or not)
 *
 *----------------------------------------------------------------------
 */
void
Tk_DrawString(GenEnvDpnt *ged, Drawable da, GC gc, int x, int y,
        const char string[], int length)
{
}


/*
 *----------------------------------------------------------------------
 *
 * Tk_Copy2DPlane --  
 *
 *      Arguments X-compatible.
 *      Copies a bitmap to the drawable (Drawable dest).
 *
 * Results:
 *      None
 *
 * Side effects:
 *      Drawing done to the given drawable (offscreen or not)
 *
 *----------------------------------------------------------------------
 */
void 
Tk_Copy2DPlane(GenEnvDpnt *display, Drawable src, Drawable dest, GC gc,
        int src_x, int src_y, int width, int height, int dest_x,
        int dest_y, int_32bit plane)
{
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_Draw2DLines --  
 *
 *      Arguments X-compatible.
 *      Draws lines in a plain color.
 *
 * Results:
 *      None
 *
 * Side effects:
 *      Drawing done to the given drawable (offscreen or not)
 *
 *----------------------------------------------------------------------
 */
void 
Tk_Draw2DLines(GenEnvDpnt *display, Drawable drawable, GC gc, XPoint pointPtr[],
                int numPoints, int mode)
{
}


/*
 *----------------------------------------------------------------------
 *
 * Tk_Draw2DLine --  
 *
 *      Arguments X-compatible.
 *      Draws a line in a plain color.
 *
 * Results:
 *      None
 *
 * Side effects:
 *      Drawing done to the given drawable (offscreen or not)
 *
 *----------------------------------------------------------------------
 */
void 
Tk_Draw2DLine(GenEnvDpnt *display, Drawable drawable, GC gc, int x1, int y1,
                int x2, int y2)
{
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_Fill2DArc --  
 *
 *      Arguments X-compatible.
 *      Fills an arc.
 *
 * Results:
 *      None
 *
 * Side effects:
 *      Drawing done to the given drawable (offscreen or not)
 *
 *----------------------------------------------------------------------
 */
void 
Tk_Fill2DArc(GenEnvDpnt *display, Drawable drawable, GC gc, int x, int y,
                int_32bit width, int_32bit height, int start, int extent)
{
}


/*
 *----------------------------------------------------------------------
 *
 * Tk_Draw2DArc --  
 *
 *      Arguments X-compatible.
 *      Draws an arc.
 *
 * Results:
 *      None
 *
 * Side effects:
 *      Drawing done to the given drawable (offscreen or not)
 *
 *----------------------------------------------------------------------
 */
void 
Tk_Draw2DArc(GenEnvDpnt *display, Drawable drawable, GC gc, int x, int y,
                int_32bit width, int_32bit height, int start, int extent)
{
}

5. PortTk, the X version

Here is actually just a lot of #define:s of the Tk-interface function names to Xlib names. In some places the datastructs has been changed and in some places the interface-functions don't match Xlib, so there is some conversion functions also. These changes can be find in x/dptk.h and x/dptk.c.

Because of these defines the original Tk works fine although we have not ported all the widgets yet.

6. PortTk, the Windows version

Code has been borrowed from Tk, tkwin and twin. The portable interface functions has to do some Windows specified stuff, before they can call the Windows-core functions.

The font and color handling has been borrowed from twin with slight adjustments, they work OK. The button and entry widget's work OK. The rest of the widgets works in unpredictable ways because the PorTk project didn't make the whole port, just some parts. The event handling is very system dependent. Although a lot of code can be lent from Tk, we have put the system dependent level very high here, a lot of Tk code is used. All handling of the different displays are deleted.

Examples of definitions:

- Macros are overridden:

Tk:

#define Tk_Screen(tkwin)		(ScreenOfDisplay(Tk_Display(tkwin), \
	Tk_ScreenNumber(tkwin)))
PortTk-Windows:

#define Tk_Screen(tkwin)                NULL
- New identifier for holding big enough integer
typedef long int int_32bit;
- The variable 'Display' in X is called GenEnvDpnt in Windows, this is defined as  
typedef char GenEnvDpnt;
typedef unsigned long Colormap;    // from x.h
typedef HDC DrawableArea;
typedef char Screen;

Example of code:

The following code is from tkbutton.c, this is the original code that is used in Tk. With the GC struct in use in Windows this code can be left unchanged. This kind of code can be find in all widgets, slightly different. In tkwin GCs are not used, so this code is deleted from here and code for setting up the graphic environment is added in the drawing functions.

    gcValues.graphics_exposures = False;
    newGC = Tk_GetGC(butPtr->tkwin,
            GCForeground|GCBackground|GCFont|GCGraphicsExposures, &gcValues);
    if (butPtr->normalTextGC != None) {
        Tk_FreeGC(butPtr->genEnvDpnt, butPtr->normalTextGC);
    }
    butPtr->normalTextGC = newGC;

    if (butPtr->activeFg != NULL) {
        gcValues.font = butPtr->fontPtr->fid;
        gcValues.foreground = butPtr->activeFg->pixel;
        gcValues.background = Tk_3DBorderColor(butPtr->activeBorder)->pixel;
        newGC = Tk_GetGC(butPtr->tkwin, GCForeground|GCBackground|GCFont,
                &gcValues);
        if (butPtr->activeTextGC != None) {
            Tk_FreeGC(butPtr->genEnvDpnt, butPtr->activeTextGC);
        }
        butPtr->activeTextGC = newGC;
    }

    gcValues.font = butPtr->fontPtr->fid;
    gcValues.background = Tk_3DBorderColor(butPtr->normalBorder)->pixel;
    if (butPtr->disabledFg != NULL) {
        gcValues.foreground = butPtr->disabledFg->pixel;
        mask = GCForeground|GCBackground|GCFont;
    } else {
        gcValues.foreground = gcValues.background;
        if (butPtr->gray == None) {
            butPtr->gray = Tk_GetBitmap(interp, butPtr->tkwin,
                    Tk_GetUid("gray50"));
            if (butPtr->gray == None) {
                return TCL_ERROR;
            }
        }
        gcValues.fill_style = FillStippled;
        gcValues.stipple = butPtr->gray;
        mask = GCForeground|GCFillStyle|GCStipple;
    }
    newGC = Tk_GetGC(butPtr->tkwin, mask, &gcValues);
    if (butPtr->disabledGC != None) {
        Tk_FreeGC(butPtr->genEnvDpnt, butPtr->disabledGC);
    }
    butPtr->disabledGC = newGC;
In the following function call the argument GenEnvDpnt is not removed, but it is not used. If it was removed, then all the functions containing Display in X would have to be changed, like in tkwin. The tkwin solution didn't make sense for a portable interface so we defined Display as char (void) in the Windows version.

tkbutton.c:     Tk_SizeOfBitmap(butPtr->genEnvDpnt, butPtr->bitmap, &width, &height);
In the following example we see how the definition of the macro '#define Screen(s) NULL' can work in Windows, although the macro looks quite different in the X version.

tk3d.c:         dark = BlackPixelOfScreen(Tk_Screen(tkwin));
tk3d.c:         dark = WhitePixelOfScreen(Tk_Screen(tkwin));
The function call BlackPixelOfScreen is an Xlib function, in Windows we have defined it to be an macro returning the local value of black; '#define BlackPixelOfScreen(s) RGB(0,0,0)'.

7. What's not implemented in PortTk

A lot of stuff, we didn't find a clever solution for the event handling.

Another tricky part that we didn't touch was tksend.c, that won't be easy either. The porting of widgets should be straightforward on the PortTk layer, the 2D drawing functions must be slightly adjusted, otherwise they won't work with all widgets. This means that the GC struct must be inspected more carefully.

8. Continuing PortTk for Windows

The implementing of the widgets should be quite straightforward. Some changes to windows/dp2d.c (drawing functions) should be made when more GC values that has not been implemented are used. Check out all the widgets except button and entry. Look for X-drawing functions, if they are not yet added to dp2d.c, add them there. All the structures should be OK by now. We did a very quick port of all the widgets so that the compiler would not complain, so nearly all widgets work, although sometimes in an unpredictable way.

The text widget don't work, just ported so the compiler won't complain.

The menu widget can be used, but there are some serious errors. The rolldowns are one character wide. Bitmaps widths computed OK. The error is probably in computing text widths in the menu widget. The eventhandling in the menu is a little strange. To select an action from the rolldown you have to choose the rolldown, holding down button one move it to the action wanted and release it there. The widget tour gives some errors here, cause of problem not checked out.

Canvas line and rectangle work, at least the very basic drawing. More complex canvas functions like the ones in the widget tour don't work.

The eventhandling works for now, but not good enough for a real Windows version. There seems to be a bug in tkwin and twin here: Make a button, pack it so the wish window is as big as the button, push the button, hold down button 1 and move it quickly out of the window, the button stays down forever.

Now all the widgets are ported in the original Tk fashion, Motif look-and-feel. This seems to be the best way to do the basic packet. This way the scripts should work without modification when moved to a Windows environment from a X environment. But for the normal end user there should exist a Windows look-and-feel. This could be implemented by creating new widgets in Windows look-and-feel, naming them winButton, winEntry, winScrollbar etc.

9. Using PortTk for other environments (e.g. Mac, Amiga)

Start by porting tkevent.c and tkwindow.c. After that PortTk functions can be used for the implementation of the widgets, which should be quite straight forward. If wish and tcl is not yet ported, then tcl first, then wish should be ported.

10. Summary and comments of the project PortTk

The final implementation of PortTk is made on X and on MS-Windows. The PortTk/X is working almost perfectly - the menu is not fully functional, but event handling, configuration and widgets button, entry, scale and canvas line are implemented by portable interface. In PortTk/Win those features and widgets written by portable interface are working.

PortTk is based on X's philosophy in drawing and in event handling. The drawing resources are included in the graphical context, which is passed by every drawing function. In PortTk this is the case also in other UI environments. All the resources in graphical context is not needed by every drawing function, but every UI environment can use the necessary recourses for its specific use.

In the final implementation there are many X definitions. Graphical contexts, events, colors and fonts are imported from X Window System. Importing seemed to be a good idea: the functionality of Tk could be preserved, so the feel of Tk is original. The remaining, not finished widgets should be easily restructured with portable interface.

PortTk does not change the ideas presented in John Ousterhout's book of Tcl and Tk. The syntax of Tcl and the commands of Tk are the same in PortTk. However if widgets are being written by instructions from the book, mapping of X definitions to proper PortTk definitions is necessary.

The final result can be critized that the result isn't really to make Tk portable, more likely to make some of the X definitions and functions portable. Tk is not made to be portable, many of its fine details are very much based on X Window System. The project group consireded to rewrite Tk, but a working rewritten Tk would have been a unreasonable goal in our project schedule.

Continuing PortTk can be meaningful in the case that there are not enough project resources to write the whole Tk again. However the project group thinks that the final PortTk will look like SRA's twin, which has implemented completely by emulating X calls and definitions.

One interesting subject of development could be attaching native widgets from different user interfaces to PortTk. The interface of native widgets could be on the level of Tcl commands. The syntax of these commands should be different, because normal Tk configuration and event handling can't be directly used. Interconnection between native user interface widgets and Tk's widgets is likely to be difficult problem.

Portability can be one of the important features of Tk in the future. In order to maintain portability - and continous development of Tk - Tk needs probably to be rewritten. The event handling and the interconnection require a new approach. The portability does not come cheap, but it can be worth it.

Glossary

X                       - the X Windows System v. 11
Windows                 - Windows 3.1 for MS-DOS
Xlib                    - A C-interface to the protocols of the X Windows S.
PortTk			- A students' project on a portable interface of Tk
portable interface	- Level where system dependent code are separated
Tk			- J. Ousterhout's original Tk v. 3.6 for X
tkwin			- K. Kubota's port of Tk to Windows (summer-94)
twin			- Softwar Reserc A. Inc. port of Tk by emulating Xlib