This article contains the following executables: DFLT14.ARC D14TXT.ARC
This month I cover the D-Flat help system, look at the new Microsoft C/C++ compiler, and review some books. D-Flat is winding down; DF++ is heating up. The Brevard County Food Bank still needs all the help they can get, so DF++ will continue the Careware tradition.
The D-Flat help system consists of four parts: a text help database; a compression/decompression algorithm; the hooks in the dialog boxes, menus, and program code that make a particular text display the current message; and the HELPBOX window class that displays the help text and allows the user to navigate it. We'll discuss compression next month.
D-Flat supports context-sensitive help: Pressing the F1 key and choosing the Help command button on a dialog box displays an appropriate help text. If the user has a menu popped down, the help text describes the current menu selection. If the user is working in a dialog box, the help text describes the currently selected control. Usually, the selection of the text is automatic. The application code does not need to do anything to support it. Each help text in the database has a name that resembles a C identifier. A help text's name is the same as a menu selection's or control's command code. So if you are poised on the File menu's New command, the associated help text is named ID_NEW. The names are surrounded by angle brackets in the text, so it is really <ID_NEW>. Each item on the menu bar has a help text. Its name is the same as the menu's identifier. The help text for the File menu is named <File>. Each dialog box has a help text. Its name is the same as the DIALOGBOX entry. The help text for the File Open dialog box is named <FileOpen>.
Each help text can reference the one that logically precedes it and the one that logically follows it. These references follow the text's name and are identified by [<<] and [>>] tokens. The tokens are followed by the name of the help text that is prior or next.
The first line of text that follows the name and reference token lines is the title of the help window.
The D-Flat help system includes a limited form of hypertext. You can encode a word or phrase in the text to display in a highlighted font. The user can select that highlighted text, and the help system will switch to the help text that you associated with the phrase. A hypertext reference is indicated by this sequence: [..key phrase]<helpname>. The words "key phrase" represent the highlighted text, and <helpname> is the name of the help text displayed when the user chooses the key phrase.
Alternatively, you can supply a brief definition window for the word or phrase. The definition window will display momentarily, only as long as the user holds down the Enter key or mouse button after selecting the phrase. A definition reference is indicated by this sequence: [**key phrase]<helpname>. A definition help text does not have a title. Its first line is the first line of its text and is displayed in the definition window's body. The hypertext and definition references may be anywhere in the text. The help text's own name and its forward and backward references must be at the beginning of the help text. They must each start on a new line with the name coming first.
Any line in the database that begins with a semicolon is a comment and does not display in the help window. Example 1 shows a sample help text with all the components.
The File menu contains commands that open, save, and print files. The menu also has the command that exits the program. Following are the commands and associated [**function keys]<shortcut>.
You prepare the help database for the application with a text editor. The memopad help database, MEMOPAD.TXT, provides the format and a large example of an almost complete help database. It also includes all the texts that describe the generic D-Flat operations, such as what a button is and how a dialog box works. You might want to use them in your application. The file is big, and it would serve no purpose to publish it here. When you get the source code for D-Flat, you get the entire help database. See the end of this discussion for instructions about where to get the D-Flat source code.
When the user presses F1 or chooses a Help command button, D-Flat opens a HELPBOX window and displays the appropriate help text. The window has four command buttons at the bottom: The Close button closes the window and exits from the help system back to the application; the Back button displays the window that was displayed immediately prior to the current one; and the Prev and Next buttons display the previous and next help texts as defined in the help database. If there is a highlighted definition phrase, the user can tab to it and press Enter or click it with the mouse to see the definition. Releasing the key or button erases the definition. If a hypertext phrase is highlighted, the user can select it in the same way to display the associated help window.
Listing One, page 152, is helpbox.c, the code that implements the HELPBOX window class. The application program calls the LoadHelpFile function once to build the table of help texts. The menu and dialog-box control windows call the DisplayHelp function to tell it to display a help window and take over. The application program can do the same thing. If the user presses F1 or clicks a Help command button from somewhere, whatever help text is current gets displayed. If no help text is current, the help system defaults to a text that describes the class of window that currently has the focus. In a well-designed help database, these texts are all available to the user through the navigation process.
A help window is a dialog box with some text and the command buttons we already discussed. The buttons permit the navigation. The table built by LoadHelpFile includes the names of the help texts that precede and follow the current one. The HelpStack structure forms a history of help windows that the user has selected since pressing F1 or choosing a Help command. Each Next or Prev command pushes an entry on the stack. Each Back command pops an entry and makes that help text the current one.
When the user selects a help text, the program finds its entry in the table and reads it in a line at a time by calling the GetHelpLine function. If the line of text has a hypertext or definition reference, the program modifies the text to display the keyword in a highlighted color.
After a page of help text is displayed in the help dialog box's edit-box control, the user uses the command buttons to navigate or clicks on or tabs to one of the highlighted reference words or phrases. Clicking a reference selects it for the next display. Tabbing to the reference displays it in reverse colors so that the user can tab past several to get to the desired one. The Enter key selects a tabbed reference. If the reference is hypertext, the program selects the associated help text to display. If the reference is a definition, the program opens another, smaller window and displays the definition in it, leaving the window on the screen until the user releases the mouse button or the Enter key.
The program always tries to position the help window so that it does not obscure whatever had the focus when the user chose Help. The BestFit function performs that task, trying to position the help window completely outside of the other window, or, if that is not possible, where it least obscures the application.
Next month we'll discuss the help system's text-compression algorithms and the File Open and Save dialog boxes.
The D-Flat source code is on CompuServe in Library 0 of the DDJ Forum and on M&T Online. If you cannot use either online service, send a formatted 360K or 720K diskette and an addressed, stamped diskette mailer to me in care of D-Flat, Dr. Dobb's Journal, 411 Borel Ave., San Mateo, CA 94402. I'll send you the latest version of D-Flat. The software is free, but if you'd care to, stuff a dollar bill in the mailer for the Brevard County Food Bank. They help the homeless and hungry. We call it DDJ's program of "careware." If you want to discuss D-Flat with me, use CompuServe. My ID is 71101,1262, and I monitor the DDJ Forum daily.
I had beta versions of the new Microsoft C++ compiler early on, but they would never install. Our development system holds all the Goliath compiler products on a Netware file server, which stores the newest of every new C product without strangling my hard disk and allows Judy and I to work separately and share a laser printer without strangling each other. The beta versions of Microsoft C++ got confused about file-server drives and would not install. Being in no hurry, I waited for the released version, which arrived not long ago. I want to compile D-Flat with Microsoft C 7 and launch a first version of D-Flat++ that works with Microsoft C++. So, to that end, I opened the package and ran the Setup program. If Setup finds Windows installed, it runs itself under Windows, opening the Notepad application so you can browse the README file while it occasionally interrupts to ask for diskettes to install.
Now, here's a situation when Windows' shine fades to a dull haze. If you ever want true preemptive multitasking, it is while scrolling a document--not exactly a CPU-intensive task--at the same time the computer reads and writes disk files in the background--also not a heavy drain on the old time slicer. Nonetheless, when you are doing these two things with Windows, the cursor spasmodically twitches around the screen as you try to scroll and page. I gaze longingly at that unopened copy of IBM OS/2 2.0, but that's another story. Back to the installation.
After about four disks worth of C/C++ 7 installation, the process stopped abruptly with a dialog box telling me that Setup failed and that I should call Microsoft Product Support immediately. No clue in that error message about what is wrong. It's 8 o'clock in the morning here on the Florida Space Coast. Nobody's going to the riding the tech support desk in Redmond, Washington at this hour. I wish I had the phone number of the Gates mansion. Wake up, Bill, there's a crotchety columnist on the line. If I have time later today, I'll give it another try.
Before trying the Microsoft C/C++ installation a second time, I looked at my own setup. Every piece of software that is running in my computer comes directly from Redmond: DOS 5.0, Windows 3.1, EMM386.EXE, SMARTDRV, DOSKEY, SETVER, HIMEM, RAMDRIVE, and LMOUSE.COM. Even the network shell came with Windows 3.1. If the stupid error message would have said something about what caused the program to quit, I might have a clue about what to remove.
But, persevere and try again. I use several different operating configurations, depending on what I am going to do. Borland C++ 3.0 and Brief 3.1, my principal development environment for DOS, do not get along with the EMM386.EXE expanded memory manager that comes with Windows 3.1. They simply blow up. To keep them running, I need to use the EMM386.EXE that comes with DOS 5.0. When I want to use Desqview, there are different memory managers to install. So, depending on whether I am going to run Windows or DOS, I use different CONFIG.SYS and AUTOEXEC.BAT files. Well, the C/C++ 7 setup program just hauls off and starts Windows without asking about such things, so it was running Windows under the DOS expanded-memory manager. That's never been a problem before. Why should it be? They both come from Microsoft and are currently supported programs. But, just to be sure, I removed all traces of the earlier failed installation, started up under my normal Windows configuration with the correct memory manager for Windows, and the installation went much better.
Up to a point, that is. When it came time for the setup program to make its modifications to some "system" files, it told me that it could not do that. It did not tell me why it could not, what system files it was trying to change, or what it was trying to change them to. It just said that it couldn't. How helpful. Then it finished the installation by building a nice Windows Program Manager group with all these pretty new icons that are supposed to run the newly installed software.
Guess what? Nothing works. Most of the programs tell me that some necessary driver is not installed. The Programmer's Workbench causes Windows to report that an application "has violated system integrity due to execution of an invalid instruction..." and that I had better quit Windows and start over again, or else. Don't you just love that? I now have 20 Mbytes of useless software installed on my file server and no clue as to how to get it going.
Well, maybe not so useless. It's the Windows stuff that doesn't work. D-Flat and D-Flat++ are DOS text-mode libraries. To use C/C++ 7 under DOS, you need a DPMI manager. Windows provides that support, but DOS does not, so Microsoft bundles 386MAX for that purpose, and I installed it. However, when compiling D-Flat with C 7, the make utility runs the CL.EXE file, which gives an error message saying that the DOS extender cannot find the CL.EXE file. The error message includes the path where the program is looking for that file, and when I look in that subdirectory, there it is. DOS found the file. Twice. Why can't the extender? The so-called Comprehensive Index and Errors Reference is no help at all.
I left several messages to the Microsoft sysop on CompuServe. Because I write this column and they know me, I'll probably get a lot of attention and get things running real soon. That's one of the perks of being an international media superstar: Vendors jump through hoops to keep us happy. But how about the rest of you -- those without all this media clout that I enjoy? I wonder what you do. Next time I'll flame incognito to find out.
Forget media clout. It took two days to get an answer. No need to wonder how they might treat the huddled masses. I figured that I was as likely to get Microsoft C/C++ 7 installed and running, as a Haitian boat person was to be invited to dinner at the White House. But, patience. The answer came, and I have to map the network drive where I installed C/C++ 7 in a different way. Use MAP ROOT instead of MAP INS. I don't really like that, because Netware eats the first entry in the DOS path when I use MAP or MAP ROOT for a Search drive. Oh, well, if that's what it takes.
The suggestion worked. I now have Microsoft C/C++ 7 installed and compiling D-Flat and DF++. Here are my initial observations. No modifications to the D-Flat code or the makefile were necessary to get a compiled copy that works. MSC 6.0 always did give me more warnings than BC, and the new version adds more still. I might look into what is needed to kill the warnings, but they do not hurt the running program. DF++ was not so easy to port because there was no prior version of MS C++ to port from. I had been using BC++ exclusively, and there are some things in the program that are compiler-specific, such as interrupt functions for the timers. It took about a half hour to build a portability layer, mainly because I had done it before for D-Flat. My test DF++ program compiles and runs with MS C++ without any problems.
For the first time in the many-years war between Borland and Microsoft, the youngsters at Redmond seemed to have gotten the word. Programmers prefer fast compilers. It took MSC 6.0 about 12 minutes to compile all of D-Flat using the default compiler settings. MSC 7 does it in about five minutes. That seems to be a significant improvement, and you might conclude that the programmers at Microsoft had finally caught up. But when you look under the surface, you see that what they did was change the defaults. MSC 6 optimizes by default, and MSC 7 defaults to no optimizations. If you turn them both around, you'll see the real picture. Table 1 shows how it looks with Borland and Watcom thrown in for good measure.
Version 6 did a better job on code size in both configurations, and version 7 compiles about a minute-and-a-half faster for both, so there has been an improvement. But in the overall picture, Borland is the runaway winner for compile time, while Watcom takes the gold for code size.
Many factors can affect the speed performance of a C compiler. Objective benchmarks are difficult to run because of these. Where and how the compiler is installed will alter its performance. Table 1 shows that a compiler's own options affect its performance. For example, the Borland compiler has a feature that saves precompiled header files. If a subsequent compile uses the same header files, BC will use the precompiled version, speeding up the compile. I use that feature and store the precompiled headers on a RAM disk. A compiler's Make program and the make-file can have features that optimize performance. Borland's Make program lets you tell it to make the same number of target files as the number of source filenames you can fit on the command line. You can speed up the other compilers by using Borland's make utility. You can improve the performance of all those compilers by using the Windows 3.1 smartdrv.exe cache program instead of the 386MAX qcache program. And you can bias a benchmark by tweaking the environment of each compiler to show the results you want to show. The benchmark itself can be biased to favor the features of a particular product. As a rule I do not pay much attention to compiler benchmarks, particularly if the vendor of one of the compilers conducts the benchmark.
Effective C++ by Scott Meyers (Addison-Wesley, 1992) is one of several books essential to every C++ programmer's library. It begins with the obligatory discussions of going from C to C++ and then slides into some advanced treatments of C++ program design and code. The book is organized into "items," where each item addresses an area of concern to the C++ programmer. You can read the table of contents and go directly to an item without reading everything up to that point. This practice assumes that you are conversant in C++, of course. The items are detailed and complete, and they frequently refer to one another for discussions of related issues. This book is very well organized in this respect. The writing style occasionally gets a little chummy for my taste, which is forgivable in an author's first book. However, the style doesn't overwhelm the book. Effective C++ is readable and educational. But beyond that, it is the only reference work I have seen where all these specific issues are addressed and explained. The book is as comprehensive in this respect as an advanced C++ book can be -- in light of the current state of the C++ language. It stays on my desk within easy reach.
By the way, the three other essential C++ books are Bjarne Stroustrup's second edition of The C++ Programming Language, The Annotated C++ Reference Manual by Stroustrup and Ellis (also called the ARM), and Advanced C++ Programming Styles and Idioms by James O. Coplien, which I reviewed last month. All three are from Addison-Wesley.
Copyright © 1992, Dr. Dobb's JournalHelp for D-Flat
HyperHelp
Example 1: Sample D-Flat Help text.
<File>
[<<]<Pulldowns>
[>>]<Edit>
The File Menu
[..New]<ID_NEW>
[..Open]<ID_OPEN>
[..Save]<ID_SAVE>
[..Save as]<ID_SAVEAS>
[..Print]<ID_PRINT>
[..Exit]<ID_EXIT>Building a Help Database
Using Help
The HELPBOX
How to Get D-Flat Now
Microsoft C/C++ 7
Later That Same Day...
Clout? Not!
Redmond Catchup
Benchmark Lite
Table 1: Compiler comparison.
Compiler Optimized for size Compile/Link Code size
---------------------------------------------------------
MSC 6.0 yes 11:58 167193
no 6:52 195641
MSC 7.0 yes 10:25 169653
no 5:17 217957
BC 3.0 yes 2:53 170082
no 2:40 177362
WATCOM C 8.0 yes 9:21 146078
no 8:59 165726Book Reports
_C PROGRAMMING COLUMN_
by Al Stevens
[LISTING ONE]
/* ------------ helpbox.c ----------- */
#include "dflat.h"
#include "htree.h"
extern DBOX HelpBox;
/* -- strings of D-Flat classes for calling default help text collections -- */
char *ClassNames[] = {
#undef ClassDef
#define ClassDef(c,b,p,a) #c,
#include "classes.h"
NULL
};
#define MAXHEIGHT (SCREENHEIGHT-10)
/* --------- linked list of help text collections -------- */
struct helps {
char *hname;
char *NextName;
char *PrevName;
long hptr;
int bit;
int hheight;
int hwidth;
WINDOW hwnd;
struct helps *NextHelp;
};
static struct helps *FirstHelp;
static struct helps *LastHelp;
static struct helps *ThisHelp;
/* --- linked stack of help windows that have been used --- */
struct HelpStack {
char *hname;
struct HelpStack *PrevStack;
};
static struct HelpStack *LastStack;
static struct HelpStack *ThisStack;
/* -- linked list of keywords in current help text collection -- */
struct keywords {
char *hname;
int lineno;
int off1, off2, off3;
int isDefinition;
struct keywords *nextword;
struct keywords *prevword;
};
static FILE *helpfp;
static char hline [160];
static BOOL Helping;
static void SelectHelp(WINDOW, char *);
static void ReadHelp(WINDOW);
static void FindHelp(char *);
static void FindHelpWindow(WINDOW);
static void DisplayDefinition(WINDOW, char *);
static void BestFit(WINDOW, DIALOGWINDOW *);
/* ------------- CREATE_WINDOW message ------------ */
static void CreateWindowMsg(WINDOW wnd)
{
Helping = TRUE;
GetClass(wnd) = HELPBOX;
ClearAttribute(wnd, SHADOW);
InitWindowColors(wnd);
if (ThisHelp != NULL)
ThisHelp->hwnd = wnd;
}
/* ------------- COMMAND message ------------ */
static BOOL CommandMsg(WINDOW wnd, PARAM p1)
{
switch ((int)p1) {
case ID_CANCEL:
ThisStack = LastStack;
while (ThisStack != NULL) {
LastStack = ThisStack->PrevStack;
if (ThisStack->hname != NULL)
free(ThisStack->hname);
free(ThisStack);
ThisStack = LastStack;
}
break;
case ID_PREV:
FindHelpWindow(wnd);
if (ThisHelp != NULL)
SelectHelp(wnd, ThisHelp->PrevName);
return TRUE;
case ID_NEXT:
FindHelpWindow(wnd);
if (ThisHelp != NULL)
SelectHelp(wnd, ThisHelp->NextName);
return TRUE;
case ID_BACK:
if (LastStack != NULL) {
if (LastStack->PrevStack != NULL) {
ThisStack = LastStack->PrevStack;
if (LastStack->hname != NULL)
free(LastStack->hname);
free(LastStack);
LastStack = ThisStack;
SelectHelp(wnd, ThisStack->hname);
}
}
return TRUE;
default:
break;
}
return FALSE;
}
/* ------------- KEYBOARD message ------------ */
static BOOL KeyboardMsg(WINDOW wnd, PARAM p1)
{
WINDOW cwnd;
struct keywords *thisword;
static char HelpName[50];
cwnd = ControlWindow(wnd->extension, ID_HELPTEXT);
if (cwnd == NULL || inFocus != cwnd)
return FALSE;
thisword = cwnd->thisword;
switch ((int)p1) {
case '\r':
if (thisword != NULL) {
if (thisword->isDefinition)
DisplayDefinition(GetParent(wnd), thisword->hname);
else {
strncpy(HelpName, thisword->hname,
sizeof HelpName);
SelectHelp(wnd, HelpName);
}
}
return TRUE;
case '\t':
if (thisword == NULL)
thisword = cwnd->firstword;
else {
if (thisword->nextword == NULL)
thisword = cwnd->firstword;
else
thisword = thisword->nextword;
}
break;
case SHIFT_HT:
if (thisword == NULL)
thisword = cwnd->lastword;
else {
if (thisword->prevword == NULL)
thisword = cwnd->lastword;
else
thisword = thisword->prevword;
}
break;
default:
thisword = NULL;
break;
}
if (thisword != NULL) {
cwnd->thisword = thisword;
if (thisword->lineno < cwnd->wtop ||
thisword->lineno >=
cwnd->wtop + ClientHeight(cwnd)) {
int distance = ClientHeight(cwnd)/2;
do {
cwnd->wtop = thisword->lineno-distance;
distance /= 2;
}
while (cwnd->wtop < 0);
}
SendMessage(cwnd, PAINT, 0, 0);
return TRUE;
}
return FALSE;
}
/* ---- window processing module for the HELPBOX ------- */
int HelpBoxProc(WINDOW wnd, MESSAGE msg, PARAM p1, PARAM p2)
{
DBOX *db = wnd->extension;
switch (msg) {
case CREATE_WINDOW:
CreateWindowMsg(wnd);
break;
case INITIATE_DIALOG:
ReadHelp(wnd);
break;
case COMMAND:
if (p2 != 0)
break;
if (CommandMsg(wnd, p1))
return TRUE;
break;
case KEYBOARD:
if (WindowMoving)
break;
if (KeyboardMsg(wnd, p1))
return TRUE;
break;
case CLOSE_WINDOW:
if (db != NULL) {
if (db->dwnd.title != NULL) {
free(db->dwnd.title);
db->dwnd.title = NULL;
}
}
FindHelpWindow(wnd);
if (ThisHelp != NULL)
ThisHelp->hwnd = NULL;
Helping = FALSE;
break;
default:
break;
}
return BaseWndProc(HELPBOX, wnd, msg, p1, p2);
}
/* ----- select a new help window from its name ----- */
static void SelectHelp(WINDOW wnd, char *hname)
{
if (hname != NULL) {
WINDOW pwnd = GetParent(wnd);
PostMessage(wnd, ENDDIALOG, 0, 0);
PostMessage(pwnd, DISPLAY_HELP, (PARAM) hname, 0);
}
}
/* ---- PAINT message for the helpbox text editbox ---- */
static int PaintMsg(WINDOW wnd, PARAM p1, PARAM p2)
{
struct keywords *thisword;
int rtn;
if (wnd->thisword != NULL) {
WINDOW pwnd = GetParent(wnd);
char *cp;
thisword = wnd->thisword;
cp = TextLine(wnd, thisword->lineno);
cp += thisword->off1;
*(cp+1) =
(pwnd->WindowColors[SELECT_COLOR][FG] & 255) | 0x80;
*(cp+2) =
(pwnd->WindowColors[SELECT_COLOR][BG] & 255) | 0x80;
rtn = DefaultWndProc(wnd, PAINT, p1, p2);
*(cp+1) =
(pwnd->WindowColors[HILITE_COLOR][FG] & 255) | 0x80;
*(cp+2) =
(pwnd->WindowColors[HILITE_COLOR][BG] & 255) | 0x80;
return rtn;
}
return DefaultWndProc(wnd, PAINT, p1, p2);
}
/* ---- LEFT_BUTTON message for the helpbox text editbox ---- */
static int LeftButtonMsg(WINDOW wnd, PARAM p1, PARAM p2)
{
struct keywords *thisword;
int rtn, mx, my;
rtn = DefaultWndProc(wnd, LEFT_BUTTON, p1, p2);
mx = (int)p1 - GetClientLeft(wnd);
my = (int)p2 - GetClientTop(wnd);
my += wnd->wtop;
thisword = wnd->firstword;
while (thisword != NULL) {
if (my == thisword->lineno) {
if (mx >= thisword->off2 &&
mx < thisword->off3) {
wnd->thisword = thisword;
SendMessage(wnd, PAINT, 0, 0);
if (thisword->isDefinition) {
WINDOW pwnd = GetParent(wnd);
if (pwnd != NULL)
DisplayDefinition(GetParent(pwnd),
thisword->hname);
}
break;
}
}
thisword = thisword->nextword;
}
return rtn;
}
/* --- window processing module for HELPBOX's text EDITBOX -- */
int HelpTextProc(WINDOW wnd, MESSAGE msg, PARAM p1, PARAM p2)
{
struct keywords *thisword;
int rtn, mx, my;
switch (msg) {
case PAINT:
return PaintMsg(wnd, p1, p2);
case LEFT_BUTTON:
return LeftButtonMsg(wnd, p1, p2);
case DOUBLE_CLICK:
PostMessage(wnd, KEYBOARD, '\r', 0);
break;
case CLOSE_WINDOW:
thisword = wnd->firstword;
while (thisword != NULL) {
struct keywords *nextword = thisword->nextword;
if (thisword->hname != NULL)
free(thisword->hname);
free(thisword);
thisword = nextword;
}
break;
default:
break;
}
return DefaultWndProc(wnd, msg, p1, p2);
}
/* -------- read the help text into the editbox ------- */
static void ReadHelp(WINDOW wnd)
{
WINDOW cwnd = ControlWindow(wnd->extension, ID_HELPTEXT);
int linectr = 0;
if (cwnd == NULL)
return;
cwnd->wndproc = HelpTextProc;
/* ----- read the help text ------- */
while (TRUE) {
unsigned char *cp = hline, *cp1;
int colorct = 0;
if (GetHelpLine(hline) == NULL)
break;
if (*hline == '<')
break;
hline[strlen(hline)-1] = '\0';
/* --- add help text to the help window --- */
while (cp != NULL) {
if ((cp = strchr(cp, '[')) != NULL) {
/* ----- hit a new key word ----- */
struct keywords *thisword;
if (*(cp+1) != '.' && *(cp+1) != '*') {
cp++;
continue;
}
thisword = DFcalloc(1, sizeof(struct keywords));
if (cwnd->firstword == NULL)
cwnd->firstword = thisword;
if (cwnd->lastword != NULL) {
((struct keywords *)
(cwnd->lastword))->nextword = thisword;
thisword->prevword = cwnd->lastword;
}
cwnd->lastword = thisword;
thisword->lineno = cwnd->wlines;
thisword->off1 = (int) (cp - hline);
thisword->off2 = thisword->off1 - colorct * 4;
thisword->isDefinition = *(cp+1) == '*';
colorct++;
*cp++ = CHANGECOLOR;
*cp++ =
(wnd->WindowColors [HILITE_COLOR] [FG] & 255) | 0x80;
*cp++ =
(wnd->WindowColors [HILITE_COLOR] [BG] & 255) | 0x80;
cp1 = cp;
if ((cp = strchr(cp, ']')) != NULL) {
if (thisword != NULL)
thisword->off3 =
thisword->off2 + (int) (cp - cp1);
*cp++ = RESETCOLOR;
}
if ((cp = strchr(cp, '<')) != NULL) {
char *cp1 = strchr(cp, '>');
if (cp1 != NULL) {
int len = (int) (cp1 - cp);
thisword->hname = DFcalloc(1, len);
strncpy(thisword->hname, cp+1, len-1);
memmove(cp, cp1+1, strlen(cp1));
}
}
}
}
PutItemText(wnd, ID_HELPTEXT, hline);
/* -- display help text as soon as window is full -- */
if (++linectr == ClientHeight(cwnd))
SendMessage(cwnd, PAINT, 0, 0);
if (linectr > ClientHeight(cwnd) &&
!TestAttribute(cwnd, VSCROLLBAR)) {
AddAttribute(cwnd, VSCROLLBAR);
SendMessage(cwnd, BORDER, 0, 0);
}
}
}
/* ---- compute the displayed length of a help text line --- */
static int HelpLength(char *s)
{
int len = strlen(s);
char *cp = strchr(s, '[');
while (cp != NULL) {
len -= 4;
cp = strchr(cp+1, '[');
}
cp = strchr(s, '<');
while (cp != NULL) {
char *cp1 = strchr(cp, '>');
if (cp1 != NULL)
len -= (int) (cp1-cp)+1;
cp = strchr(cp1, '<');
}
return len;
}
/* ----------- load the help text file ------------ */
void LoadHelpFile()
{
char *cp;
if (Helping)
return;
UnLoadHelpFile();
if ((helpfp = OpenHelpFile()) == NULL)
return;
*hline = '\0';
while (*hline != '<') {
if (GetHelpLine(hline) == NULL) {
fclose(helpfp);
return;
}
}
while (*hline == '<') {
if (strncmp(hline, "<end>", 5) == 0)
break;
/* -------- parse the help window's text name ----- */
if ((cp = strchr(hline, '>')) != NULL) {
ThisHelp = DFcalloc(1, sizeof(struct helps));
if (FirstHelp == NULL)
FirstHelp = ThisHelp;
*cp = '\0';
ThisHelp->hname=DFmalloc(strlen(hline+1)+1);
strcpy(ThisHelp->hname, hline+1);
HelpFilePosition(&ThisHelp->hptr, &ThisHelp->bit);
if (GetHelpLine(hline) == NULL)
break;
/* ------- build the help linked list entry --- */
while (*hline == '[') {
HelpFilePosition(&ThisHelp->hptr, &ThisHelp->bit);
/* ---- parse the <<prev button pointer ---- */
if (strncmp(hline, "[<<]", 4) == 0) {
char *cp = strchr(hline+4, '<');
if (cp != NULL) {
char *cp1 = strchr(cp, '>');
if (cp1 != NULL) {
int len = (int) (cp1-cp);
ThisHelp->PrevName=DFcalloc(1,len);
strncpy(ThisHelp->PrevName, cp+1,len-1);
}
}
if (GetHelpLine(hline) == NULL)
break;
continue;
}
/* ---- parse the next>> button pointer ---- */
else if (strncmp(hline, "[>>]", 4) == 0) {
char *cp = strchr(hline+4, '<');
if (cp != NULL) {
char *cp1 = strchr(cp, '>');
if (cp1 != NULL) {
int len = (int) (cp1-cp);
ThisHelp->NextName=DFcalloc(1,len);
strncpy(ThisHelp->NextName, cp+1,len-1);
}
}
if (GetHelpLine(hline) == NULL)
break;
continue;
}
else
break;
}
ThisHelp->hheight = 0;
ThisHelp->hwidth = 0;
ThisHelp->NextHelp = NULL;
/* ------ append entry to the linked list ------ */
if (LastHelp != NULL)
LastHelp->NextHelp = ThisHelp;
LastHelp = ThisHelp;
}
/* -------- move to the next <helpname> token ------ */
if (GetHelpLine(hline) == NULL)
strcpy(hline, "<end>");
while (*hline != '<') {
ThisHelp->hwidth = max(ThisHelp->hwidth, HelpLength(hline));
ThisHelp->hheight++;
if (GetHelpLine(hline) == NULL)
strcpy(hline, "<end>");
}
}
fclose(helpfp);
}
/* ------ free the memory used by the help file table ------ */
void UnLoadHelpFile(void)
{
while (FirstHelp != NULL) {
ThisHelp = FirstHelp;
if (ThisHelp->hname != NULL)
free(ThisHelp->hname);
if (ThisHelp->PrevName != NULL)
free(ThisHelp->PrevName);
if (ThisHelp->NextName != NULL)
free(ThisHelp->NextName);
FirstHelp = ThisHelp->NextHelp;
free(ThisHelp);
}
ThisHelp = LastHelp = NULL;
free(HelpTree);
}
/* ---------- display a specified help text ----------- */
BOOL DisplayHelp(WINDOW wnd, char *Help)
{
if (Helping)
return TRUE;
FindHelp(Help);
if (ThisHelp != NULL) {
if (LastStack == NULL ||
stricmp(Help, LastStack->hname)) {
/* ---- add the window to the history stack ---- */
ThisStack = DFcalloc(1,sizeof(struct HelpStack));
ThisStack->hname = DFmalloc(strlen(Help)+1);
if (ThisStack->hname != NULL)
strcpy(ThisStack->hname, Help);
ThisStack->PrevStack = LastStack;
LastStack = ThisStack;
}
if ((helpfp = OpenHelpFile()) != NULL) {
DBOX *db;
int offset, i;
db = DFcalloc(1,sizeof HelpBox);
memcpy(db, &HelpBox, sizeof HelpBox);
/* -- seek to the first line of the help text -- */
SeekHelpLine(ThisHelp->hptr, ThisHelp->bit);
/* ----- read the title ----- */
GetHelpLine(hline);
hline[strlen(hline)-1] = '\0';
db->dwnd.title = DFmalloc(strlen(hline)+1);
strcpy(db->dwnd.title, hline);
/* ----- set the height and width ----- */
db->dwnd.h = min(ThisHelp->hheight, MAXHEIGHT)+7;
db->dwnd.w = max(45, ThisHelp->hwidth+6);
/* ------ position the help window ----- */
BestFit(wnd, &db->dwnd);
/* ------- position the command buttons ------ */
db->ctl[0].dwnd.w = max(40, ThisHelp->hwidth+2);
db->ctl[0].dwnd.h = min(ThisHelp->hheight, MAXHEIGHT)+2;
offset = (db->dwnd.w-40) / 2;
for (i = 1; i < 5; i++) {
db->ctl[i].dwnd.y =
min(ThisHelp->hheight, MAXHEIGHT)+3;
db->ctl[i].dwnd.x += offset;
}
/* ---- disable ineffective buttons ---- */
if (ThisStack != NULL)
if (ThisStack->PrevStack == NULL)
DisableButton(db, ID_BACK);
if (ThisHelp->NextName == NULL)
DisableButton(db, ID_NEXT);
if (ThisHelp->PrevName == NULL)
DisableButton(db, ID_PREV);
/* ------- display the help window ----- */
DialogBox(wnd, db, TRUE, HelpBoxProc);
free(db);
fclose(helpfp);
return TRUE;
}
}
return FALSE;
}
/* ------- display a definition window --------- */
static void DisplayDefinition(WINDOW wnd, char *def)
{
WINDOW dwnd;
WINDOW hwnd = wnd;
int y;
if (GetClass(wnd) == POPDOWNMENU)
hwnd = GetParent(wnd);
y = GetClass(hwnd) == MENUBAR ? 2 : 1;
FindHelp(def);
if (ThisHelp != NULL) {
clearBIOSbuffer();
if ((helpfp = OpenHelpFile()) != NULL) {
clearBIOSbuffer();
dwnd = CreateWindow(
TEXTBOX,
NULL,
GetClientLeft(hwnd),
GetClientTop(hwnd)+y,
min(ThisHelp->hheight, MAXHEIGHT)+3,
ThisHelp->hwidth+2,
NULL,
wnd,
NULL,
HASBORDER | NOCLIP | SAVESELF);
if (dwnd != NULL) {
clearBIOSbuffer();
/* ----- read the help text ------- */
SeekHelpLine(ThisHelp->hptr, ThisHelp->bit);
while (TRUE) {
clearBIOSbuffer();
if (GetHelpLine(hline) == NULL)
break;
if (*hline == '<')
break;
hline[strlen(hline)-1] = '\0';
SendMessage(dwnd,ADDTEXT,(PARAM)hline,0);
}
SendMessage(dwnd, SHOW_WINDOW, 0, 0);
SendMessage(NULL, WAITKEYBOARD, 0, 0);
SendMessage(NULL, WAITMOUSE, 0, 0);
SendMessage(dwnd, CLOSE_WINDOW, 0, 0);
}
fclose(helpfp);
}
}
}
/* ------ compare help names with wild cards ----- */
static BOOL wildcmp(char *s1, char *s2)
{
while (*s1 || *s2) {
if (tolower(*s1) != tolower(*s2))
if (*s1 != '?' && *s2 != '?')
return TRUE;
s1++, s2++;
}
return FALSE;
}
/* --- ThisHelp = the help window matching specified name --- */
static void FindHelp(char *Help)
{
ThisHelp = FirstHelp;
while (ThisHelp != NULL) {
if (wildcmp(Help, ThisHelp->hname) == FALSE)
break;
ThisHelp = ThisHelp->NextHelp;
}
}
/* --- ThisHelp = the help window matching specified wnd --- */
static void FindHelpWindow(WINDOW wnd)
{
ThisHelp = FirstHelp;
while (ThisHelp != NULL) {
if (wnd == ThisHelp->hwnd)
break;
ThisHelp = ThisHelp->NextHelp;
}
}
static int OverLap(int a, int b)
{
int ov = a - b;
if (ov < 0)
ov = 0;
return ov;
}
/* ----- compute the best location for a help dialogbox ----- */
static void BestFit(WINDOW wnd, DIALOGWINDOW *dwnd)
{
int above, below, right, left;
if (GetClass(wnd) == MENUBAR ||
GetClass(wnd) == APPLICATION) {
dwnd->x = dwnd->y = -1;
return;
}
/* --- compute above overlap ---- */
above = OverLap(dwnd->h, GetTop(wnd));
/* --- compute below overlap ---- */
below = OverLap(GetBottom(wnd), SCREENHEIGHT-dwnd->h);
/* --- compute right overlap ---- */
right = OverLap(GetRight(wnd), SCREENWIDTH-dwnd->w);
/* --- compute left overlap ---- */
left = OverLap(dwnd->w, GetLeft(wnd));
if (above < below)
dwnd->y = max(0, GetTop(wnd)-dwnd->h-2);
else
dwnd->y = min(SCREENHEIGHT-dwnd->h, GetBottom(wnd)+2);
if (right < left)
dwnd->x = min(GetRight(wnd)+2, SCREENWIDTH-dwnd->w);
else
dwnd->x = max(0, GetLeft(wnd)-dwnd->w-2);
if (dwnd->x == GetRight(wnd)+2 ||
dwnd->x == GetLeft(wnd)-dwnd->w-2)
dwnd->y = -1;
if (dwnd->y ==GetTop(wnd)-dwnd->h-2 ||
dwnd->y == GetBottom(wnd)+2)
dwnd->x = -1;
}