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.
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.
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.
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.
Replace 'xxx' with the comment underneath.
#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
/* *---------------------------------------------------------------------- * * 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) {
}
/* *---------------------------------------------------------------------- * * 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) {
}
Because of these defines the original Tk works fine although we have not ported all the widgets yet.
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.
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;
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)'.
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.
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.
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.
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