Victor R. Volkman received a BS in Computer Science from Michigan Technological University. He has been a frequent contributor to C/C++ Users Journal since 1987. He is currently employed as Senior Analyst at H.C.I.A. of Ann Arbor, Michigan. He can be reached by dial-in at the HAL 9000 BBS (313) 663-4173 or by Usenet mail to sysop@hal9k.com.
Introduction
This abstract is derived directly from original documentation by David Blum. The Windows Text/Windows Graphics Windows (or WTWG) windows system, by David Blum, is a user-interface system for MS-DOS that allows you to write C programs with modern interface features including the mouse, windows, and menus. WTWG handles PC hardware details, such as video modes, display pages and enhanced keyboards, freeing up programmers to concentrate on higher-level tasks. WTWG includes memory management (expanded memory and disk-swapping virtual memory) and error handling.The routines in these libraries provide screen, keyboard, and mouse management in either text or graphics modes for Turbo C/C++, Borland C++, Zortech C++, and Microsoft C v5. Data collection by C++ forms is new to WTWG 2.0 and constitutes a major improvement. WTWG accepts input from files, command-line arguments, or user input on-screen, and can both display data and write it to a file. WTWG can perform automatic validation of data. Using C++ virtual functions, the library can be extended to provide validated input of any custom data type. WTWG works identically at the source code level in standard DOS, 386-protected mode DOS, text mode, or graphics mode.
System Requirements and Capabilities
Memory
Most windows packages for the IBM PC operate in text mode only. Saving a text-mode screen takes about 4KB of ram, so memory isn't a big deal in text mode. In graphics mode, especially on color monitors (depending on resolution, etc.) saving a screen can take over 100KB. It's not practical to use so much conventional RAM for most serious graphics programs. Some other method of storage must be found. Most large graphics-oriented programs can use expanded memory, but not all PCs have expanded memory. The solution is to use a simulated heap composed of "virtual" memory.The libraries include a memory management routine that allocates this simulated heap memory from expanded memory, the DOS far heap, or a disk file, as required by the particular system configuration in use. The actual location of the data is transparent to the calling routines. Memory use is ultimately limited only by available disk space.
Unfortunately, to achieve this virtual memory capability the manager must allocate a 64KB buffer for disk I/O. Using this buffer will not allow debugging of programs in the Turbo C integrated environment unless you have expanded memory in your system. To allow for debugging, a separate version of the memory management routine, heapdbg.c, is provided. This module doesn't do disk allocation or expanded memory management so allocatable memory is limited, but with it you can run the integrated debugger, TD, or CodeView.
For programs compiled under the large memory model, the routines wmalloc and wrealloc guarantee the return of a normalized pointer. If you use these routines exclusively, and never call malloc, calloc, realloc, or similar, you will avoid segmentation errors (very difficult to debug, intermittent errors in large programs). In addition, the file WSYS.H (intended only for compiling the WTWG source code) contains a NORMALIZE macro that adjusts the segment/offset values of far pointers to help you explicitly avoid these problems. If you use C++ you must redefine the NORMALIZE macro to include a typecast. All WTWG routines are written in C. Future releases will be in C++ but will use a separate NORMALIZE macro.
Video Modes and Monitors
Whether in text or graphics modes, WTWG automatically detects the monitor. WTWG stores the monitor type in the variable wmonitor. Values used are:
TEXTONLY version: 'M' = MGA 'C'= CGA graphics/text version: 'V' - VGA: text modes (25 line and 50 line), and all 16 color graphics (640*350, 640*480, 800*600) 'E' - EGA: text (25 and 43 line), graphics ( 640*350 ) 'H' - Hercules text or graphics modes 'S' - SuperVGA (256 color modes) monitor. (also, TIGA, IBM8514 etc if your graphics drivers support them.The variable wmode tells you if you're in text mode or graphics mode. The only possible values are T or G.
Display Features
Opening and Closing Windows
Some windows systems (especially Microsoft Windows) ask windows to "redraw" themselves. Requiring a window to redraw itself makes for complex programs. Other window systems keep in-memory images of the window and redraw the windows for you. WTWG does neither. Programming effort and execution overhead (both speed and RAM) are less with WTWG than with other systems. The disadvantage of WTWG is you can't write true multitasking programs using this package.After initialization, WTWG automatically opens a full-screen window. Full screen operation using wputs, wprintf, and wputc can proceed identically to ANSI stream output. Alternatively, you can open and close smaller windows.
The global variable w0 always points to the currently open window. Through w0 you can access members of the WINDOW structure. For instance,
w0-> winx gives the current x position (starts at 0).
w0-> winxmax gives the highest valid x position.
w0-> winattr gives the current attribute.
Windows are kept on a LIFO stack. Calling wopen creates a window, wclose closes the most recently created window. The most recently created window is the active window.
You can change the order of the stack by calling wreopen to place an older window on top of the stack, or wbury to place the current window on the bottom of the stack. This obviously requires some caution. You can wreopen the full screen window at any time by calling wreopen (wfullscreen), but you must be sure to wbury it when done.
Self-adjusting Windows
You can use functions wsetlocation and wlocate to automatically position the next window opened. You call wsetlocation to set your options, and then call wlocate to calculate window a position that matches those options. Options include the following: center window on screen, place window relative to current window, relative to current cursor position, or at absolute positions. You can also specify x,y offsets relative to the option. This feature lets you open a window one line below current position (useful for error messages and pulldown menu functions).WTWG's high-level routines call wlocate so you can control their window locations by calling wsetlocation first.
WTWG Coordinate System
WTWG uses a consistent coordinate system. In all function prototypes, x is horizontal, y is vertical, and the order is always x,y. Coordinate numbering starts with 0 (not 1) and ends with winxmax/winymax (or wxabsmax/wyabsmax). (Variables with max in the name specify the largest valid value for that coordinate, i.e., wxabsmax is the largest valid x-value, which is typically 79 for an 80-column screen. (Do not assume that wxabsmax is always 79, on Hercules monitors in graphics modes it is 89 (720 pixels = 90 characters across). If you add Super VGA support (800*600 is easy), then wpxabsmax =799 and wxabsmax =99. winxmax refers only to the current window and is referenced as a member of the WINDOW structure (i.e., w0->winxmax). Variables with names containing px or py refer to pixels rather than text-mode character counts; i.e., w0->wpxmax is the largest valid horizontal pixel number in the current window and wpxabsmax is the largest valid horizontal pixel on the screen.For all routines that refer to absolute positions (wxabs, wyabs), x is horizontal and y is vertical. Counting begins at (0,0) in the upper left corner of the screen. In graphics modes, wpxabs, wpyabs values refer to absolute pixel count, (0,0) being top left corner of screen.
Within a window, WTWG uses a local coordinate system with (0,0) being the upper left corner of the open window, wherever that may be on the screen. For a window having ten columns, the value of w0->winxmax would be 9. Text output would take place at the current position in the window which is given by w0->winx and w0->winy. The same sort of coordinates apply in graphics modes.
Example:
(w0->winxmax) is the last valid x value in the current window.
(w0->winx) is the current x coordinate in the current window.
The first two parameters to wopen specify x,y of the top left position of the first text byte in the window. WTWG places any borders above and to left of this spot. The next two parameters are the number of columns (xmax+1) and number of rows (ymax+1) in the window, again not counting borders. Window coordinates are referenced starting at 0, but the number of rows/columns desired is referenced starting at 1.
Text Output
You can send text to the screen via functions wputc, wputs, or wprintf. The current location (insertion point) is updated appropriately. You can discover the current location by referencing w0->winx and w0->winy. The next byte of text output will take place at that spot. You can change that spot with wgoto(new_x, new_y).The behavior of wputc/wputs/wprintf at line endings and at the bottom of a window is called the "put style" and is controlled by the variable (w0->winputstyle). You can change the put style for the current window. By default wputc/wputs/wprintf behave exactly like their ANSI counterparts putc/puts/printf, i.e., at the last column text wraps to the beginning of the next line, and after the last line the window scrolls up one line. WTWG honors ANSI escape codes. Within each window, you can specify different text behaviors. You can turn off text wrapping at the end of a line and window scrolling after the last line. WTWG can both ignore and print ANSI escape sequences. '\n' can generate a CR/LF or just a LF. The flag w0->winputstyle controls all of these choices. When you close a window, the previous window's put style is restored.
You can turn wrap/scroll on or off for the current window using wturnwrapon and wturnwrapoff.
Window colors are specified as unsigned char. The first four bits specify the background, the low four bits set the foreground. For example, wsetattr((RED<<4)+YELLOW) sets yellow letters on red background, wgetattr returns the current attribute as unsigned char.
On Hercules monitors, text mode colors behave just like they do for other programs (GREEN = white, BLUE = underline, RED = invisible). In graphics mode, only colors that include GREEN or BLUE will be white, all others will be black. So, (GREEN<<4)+YELLOW is a bad choice because in Hercules graphics mode it will show up as solid (unreadable). This color representation scheme is called dithering. The HP laserjet driver whplj_dump uses the same dithering scheme so if you are consistent in your choice of colors your programs will look okay on HERCULES, PAPER, and also VGA monitors.
WTWG manages the cursor automatically: the cursor is off by default and is turned on by wgets when the user has to type something. On exit the cursor is turned on again. In graphics modes WTWG creates a pseudo-cursor for user input (typing). In text mode, if you really want a cursor bad enough, you can call wturn_cursor(ON) or wturn_cursor(OFF), but most of the time it is handled for you.
User Interface Features
Keyboard Input
The program detects the enhanced keyboard at startup. WTWG handles all that nonsense about character/scan codes (see Turbo C manual for getch) automatically a single call to wgetc always returns a single character. Because the enhanced keyboard has a large number of characters, the return type of wgetc is an int, with a range of -32k to +32k. Actual keys on the keyboard use values of only 1 thru about 300. Value 128 is reserved for the mouse. All routines that ask for or return keyboard values expect int data; if you use char or unsigned char, errors may occur.wgetc incorporates the following features:
1) Merges mouse and keyboard into one virtual device.
2) Searches for hotkeys and calls specified routines.
3) Traps keyboard input to send to a file, or redirection to read a file as keyboard input.
4) If the user doesn't press a key after three minutes blanks the screen. Screen blanking happens in text or graphics mode, for EGA, VGA, or HERCULES displays.
Example:
int keystroke; /* 'char keystroke' would be wrong */ keystroke = wgetc(); if ( keystroke == FKEY(1) ) {...} else if ( keystroke == ESCAPE ) {...} else if ( keystroke == ALT_TAB ) {...} /* actual value is 293 */ else if ( keystroke == WMOUSE ) {...} /* mouse used */ else ...etc...Mouse
WTWG supports automatic use of 2-and 3-button mice. WTWG turns off the mouse when not actually accepting user input to allow graphics-mode screen drawing without producing garbage on the screen (try turning the mouse ON and drawing a circle with the mouse moving on a EGA/VGA screen). When the video display is changed to a high page number WTWG disables the mouse if required by the monitor/mode combination (not all monitors and modes support the mouse on pages higher than page 0). If you want to display the mouse cursor at other times, you can use wmouse_turn(ON) and wmouse_turn (OFF) but you must be sure to use these calls in pairs or the poor little critter will get confused.Most mouse drivers do not work in 800*600 mode. If you add a Super VGA 800*600 driver (available through third parties for Turbo C), you will need to write a mouse driver.
The flash graphics (ZORTECH) super VGA modes and supervga mouse drivers do work with WTWG.
Buttons
Buttons are areas onscreen that the mouse can point to and select. WTWG translates mouse selection of a button to a key value that you associated with the button.Example:
wbutton_add ( "Press Me", 3, 5, 9, 'P', WBTN_BOX ); /* The words Press Me are placed at x=3, y=5 in a box of length 8+1. (1is for terminal NULL) The value 'P' is assigned to the button. */ wbutton_add ( "F10 - QUIT", 3, 8, 11, FKEY(10), 0 ); /* The words F10 - QUIT are placed at x=3, y=8 for length 10+1. No box is drawn. The value FKEY(10) is assigned to the button. */ keystroke = wgetc (); /* if the left button was released with the mouse in the 1st button area defined above, keystroke='P' the 2nd button area, keystroke= FKEY(10) NOTE also: if P was pressed on the keyboard, keystroke='P', not 'p' if F10 was pressed, keystroke = FKEY(10) */ if ( keystroke == 'P' ) ... either mouse click in button or 'P' on keyboard... else if ( keystroke == FKEY(10) ) ... keystroke= wgetc_button(); /* waits until user gives acceptable input. ignores keys not matching current buttons. All keys translated to upper case, so no further validation is needed */Scrollbars
Scrollbars are vertical bars that can be set to a "value" by positioning on them with the mouse. You create scrollbars with wscrollbar_add, just as you create buttons with wbutton_add. WTWG assigns a unique keystroke code to the scrollbar, which should not be a valid keyboard key (i.e., -1 works, so does 600) You specify screen location and length of scrollbar, and a virtual range. The value of the scrollbar will then be a long int between 0 and the virtual range. The function wscrollbar_scroll tracks the mouse while the left button is down and returns the new value for the scrollbar. You can redraw the pointer on the scrollbar to show a new value by calling wscrollbar_reset.WTWG provides for higher level interaction with the user via the following functions:
- wpromptc Prompt for a single keystroke.
- wprompts Prompt for a string of specified length.
- wprompts_validate Prompt user for string, validate string before return.
- wpicklist Chose an item from a null-terminated list of strings.
- wmultipick Provide multiple choices from a list of strings.
- Wform::getscreen Manage C++ screen form.
- wscanform (old version) Get multiple items of data, with validation.
- wprompt_color Get color, or get foreground/background text attributes.
Hotkeys
Hotkeys are keys that activate a routine independent of the main program. For example, the function keys in the Turbo C are all hotkeys. The same is true for QuickC.The easy way to install a hotkey handler is to call the whotkey_install function, specifying both the key value and the function, as in:
void myfunc (void); /* function that does something */ winit ( mode ); whotkey_install ( HOME, myfunc );WTWG will call the function myfunc whenever the HOME key is pressed. If the function is already active at the time of the keypress, it will not be called twice, and the keystroke will be passed through.WTWG includes a number of useful hotkey routines: context-sensitive help (whelp_install), pulldown menus (wpulldown), HP LaserJet screen dump (whplj_install), ability to use the mouse center button to act like a hotkey (wmspopup & wpopfkey), and a simple keyboard macro recorder (wmacro_install).
Note that WTWG's hotkey features allow you to write traditional programs (the program controls when keyboard action takes place and what is done with user actions) or event-driven programs (the keyboard manager gets a key and decides which routine should be given the new input). If your program has one main routine that asks the user a series of questions and then takes action, there is no need for hotkeys, menus, etc. If the majority of your program is reached by hotkeys (or pulldown menus, which are implemented as a hotkey) then it is eventdriven. Such programs are made up of many void func(void) functions that communicate with each other by way of global-scope variables.
A more complex, and more flexible way to install hotkeys is to point the global variable wkeytrap to the function that scans for the key and executes the subtask. In this case you must hang on to the addresses of any previously installed hotkey routines and chain to them in your hotkey routine, otherwise the help and menu systems won't work (or any other hotkeys you invent). If you use the first method, you won't have to worry about this stuff.
Within the source code of the library, example programs that use the complex method of creating hotkeys may be found in wpulldn.c (pulldown menus checks for ALT+letter and for mouse click on menu bar) and wmspopup.c (checks for mouse center button).
Popup Menus
Popup menus are lists of choices that the user can make. In WTWG, you create popup menus via the function wpicklist. You pass the function a title for your menu, and a pointer to an array of strings (one string for each choice), terminated by a null pointer. wpicklist(title, list) displays the list, allows the user to choose one element, and returns the element number (first element number is 0). If the user presses ESCAPE, the return value is equal to the number of choices. You can use the return value as an index to the list of choices.Example:
int choice; char *my_list[] = {"ONE", "TWO", "THREE", "FOUR" ,NULL}; choice = wpicklist ( "Choose a number", my_list ); /* a box with the choices pops on the screen and disappears after a choice is made */ if ( my_list [choice] == NULL ) { ... user pressed ESCAPE... } else { ... choice=0 means selection was "ONE", etc... }Pulldown Menus
Pulldown menus have a way of neatly organizing large programs. Each choice in a menu is either a submenu or a function. The functions are declared as void menu_func(void). The menu manager then acts as an event-driven interface, so your functions are called only when the user activates the menu.You create pulldown menus from a tree of tables, organized hierarchically. Each tables is an array of the type WMENU (a typedef in window.h). One table element produces one menu choice. You create nested menus by setting a pointer in the WMENU structure to another table of WMENUs. Each table element references a function of type void with no parameters (i.e., void menu-func(void)); these functions do the indicated tasks. You can place all the startup code and menu tables is one file, and place the functions in other files, and so build complex programs by a mix-and-match approach. At program startup, you call wpulldown_install to setup the menu onscreen. This routine also places a trap in wgetc so any hotkey from the menu table is recognized and control is passed to the menu. Menu items may also be selected by mouse. F1 gives context-sensitive help for each item in the menu tree.
Data Entry Forms
WTWG supports data-entry forms using a table-driven approach. This feature has been revised in WTWG v2.0 to take advantage of C++. (The old version is also supplied in the volume).
Help
WTWG supports online context-sensitive help. You place the help text in a file called HELPFILE. HLP. Help topics all start with the character '@'in column 1. The text for that topic follows. Text should be 50 columns by five lines, per topic. To create cross references to other topics, enclose the other topic in braces with a digit from 1-9. i.e.: {# topic}, where # represents the digit.Here is an example of an entry in a help file:
@ow to quit <<< name of topic To quit using the program, press ALT-X or select "File-Quit" from the main menu. Be sure to save your work. See also {1 File-Quit} {2 File-Save} <<< hypertext x-refs @next topic starts here....After you've created the help file the next step is to use the program MAKEHX. EXE to create a help index file which will be named helpfile. HX. Then, in your C program, you install the online help program with whelp_install ("helpfile"). The help topic changes appropriately with different choices in pulldown menus or with different input lines in FORMS. You can also change the help index explicitly. Sample files are provided with the volume.
Miscellaneous Features
Error handling
To terminate a program due to an error, WTWG provides the routine werror to assign an exit code, restore the video mode, write an error message, then shut down the program.If you use perror, or puts in a graphics mode program, or on a high video page, then your error message will be lost when the video mode is restored. Even worse, using some other windows packages, if you call exit from a bizarre video mode, your PC screen will be unreadable (this is especially true in Hercules mode). The werror routine solves all these problems.
If you run out of memory, malloc returns a null pointer. If you test for this and then write an error message, exit, your message might get lost (when the graphics mode changes back to text mode the screen is erased). The shutdown routine requires some available memory. WTWG provides functions wmalloc, wfarmalloc, and wrealloc to correctly handle the situation of running out of memory. These functions do the testing for you, and deal with error messages correctly.
You can use watexit to install routines you want to have executed at shutdown time.
C++ Streams
If you use C++ you can use stream output by calling wout, just like cout, Stream output goes to the current window. WTWG provides manipulators for clearing the window, setting colors, and goto(x,y) positions.Example:
wout << wout_attr ( BLUE ) << wout_clear() << "Hello World";Defined manipulators are: which are justlike calling:
wout_attr ( char a ) wsetattr(a) wout_clear (void) wclear () wout_goto ( int x, int y ) wgoto ( x, y ) wout_clearline (void) wclearline ()Acknowledgements
The overall plan of the WTWG routines is somewhat unique. WTWG's mode-independent windows system is based wholly on the author's original ideas. In addition, to the author's knowledge, WTWG's method of mouse and keyboard integration is unique, as is its use of an eight-bit character size, which allows simplifications and speed enhancements. However, WTWG could not have been built without benefit of existing resources.Most of the low-level video access methods were derived from Turbo C Programming On the IBM PC, by Lafore. The basics of the 43/50-line EGA-mode logic comes from DOS Power Tools, by Somerson. Other IBM PC hardware routines were derived from DOS Programmers Guide, by Dettman.
The revised logic for using the enhanced keyboard in wkbd.c comes from the article "Controlling the Keyboard Buffer," by Steve Gruel, CUJ, July 1990. The wout stream for C++ comes from "A Console Stream Class for Borland C++," by Al Williams, CUJ, January 1992.
The author would also like to thank Sasha Drachev, who provided many suggestions that went into version 2, and who did much of the testing.