home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The World of Computer Software
/
World_Of_Computer_Software-02-387-Vol-3of3.iso
/
e
/
e_edit.zip
/
ARTICLE
/
EDITOR.TXT
next >
Wrap
Text File
|
1992-02-28
|
36KB
|
712 lines
An OS/2-PM Editor using MLE
by Brian R. Anderson
MLE is a powerful control window which is available to OS/2-PM
programmers (pushbuttons, radio buttons, and list boxes are also control
windows); the initials stand for Multi Line Edit. In a very real sense, an MLE
is an object that "knows" how to edit text. This article describes how I used
an MLE as the basis for building a simple yet functional programmer's editor.
Background
You would be well justified in asking: why does the world need another
editor? This project "happened" due to a unique set of circumstances. I was
teaching a course on OS/2 programming where the students were (rightfully)
complaining about the OS/2 System Editor because it did not display line
numbers (they needed line numbers to interpret compiler diagnostics which list
syntax errors by line). Just as the course got under way, a labour dispute
kept me away from my teaching duties for nearly a week; I decided to use the
time productively and thereby give the students a more useful editing tool and
an extended example of OS/2-PM programming.
While you might never wish to write an editor, per se, you might want to
include some minimal editing capability in a larger project -- the MLE provides
a perfect mechanism for doing so with very little effort. Although this article
describes using the MLE in the client window, I have also used the MLE in a
dialog box to give limited text editing functions for purposes specific to the
application.
Object Oriented Programming?
I previously referred to the MLE as an object. This is not really an
article on OOPS, but many of the characteristics that make OOPS attractive
also apply to the MLE. First of all, the Multi Line Edit control window is
very high level (as are many of the objects in a good class library). Secondly,
the MLE uses a message based paradigm: you send the MLE messages when you
want it to do something, and it sends you messages when it wants to advise
you of something. And finally, the MLE can be easily extended (through
subclassing) to enhance functionality.
What I mean by the Multi Line Edit control window being very high
level is that this object itself already knows how to interact with the user for
text entry and insertion, cursor movement, scrolling, search and replace,
cut/copy and paste, and probably a few more that I've forgotten. For some of
these features, no extra programming is required at all. For the other
features, all that is necessary is to send the MLE an appropriate message
indicating what you want it to do. Some MLE features require multiple
messages and/or a small amount of additional programming.
Article Scope
There have been many good articles published about the basic structure
of an OS/2-PM program (creating standard windows, the message loop, the
window procedure, resources, etc.). This article assumes that you are already
somewhat familiar with those areas of GUI programming and therefore glosses
over them. The two main areas of emphasis for the article are: programming
with the MLE (including subclassing), and programming for the OS/2 help
system (Information Presentation Facility).
Design Criteria
I wanted the editor to be easy to use, but not overly constraining. I
also wanted the code to be simple and understandable so that I could use it as
an example in the classroom. Finally, I wanted good compliance to the IBM
Common User Access guidelines.
I decided to limit the size of edit files to 64K -- partly to keep the code
simple, and partly to ensure that my students coded their projects in a
modular fashion (no single C source file should be larger than this). I also
decided not to support custom colours or fonts -- after all, this is a
programmer's editor, not a word processor or a paint program. Since with
OS/2, it is possible to start multiple copies of the program and then
cut/copy/paste among them, I saw no need for implementing the MDI (Multiple
Document Interface).
Although I had access to both CASE:PM and Gpf (Presentation Manager
code generation tools), I elected to develop the project from scratch -- mostly
for the practice using the raw API and the SDK.
After several weeks of use, I have found that all of my students prefer
the new editor to anything else (previous classes "got by" using DOS editors
and/or the OS/2 System Editor). Many students have commented that the
OS/2 environment is much superior to anything that they have experienced
before (prerequisite programming courses have used everything from a 370-style
mainframe to Borland's Turbo C++). What they like best about OS/2 is being
able to start compiling in one session, and then immediately go back to editing
in another session.
Production Files
Presentation Manager programs require many types of source files. Of
course, there are the C source code files and header files. In keeping with
my dictum of modular decomposition, I have divided the project into three
separately compiled modules: EDIT.C (contains main, the client window
procedure, and the subclassing procedure); EDLG.C (contains the dialog box
window procedures and supporting code); and EFILE.C (contains the file
input/output functions). Each C source code file has a matching header file.
Concerning my self-imposed 64K limit: even if all six of these source code files
were concatenated, they would not total 64K!
In addition to the C source code, there is the resource file: EDIT.RC
(contains the menu, keyboard accelerators, help tables, and dialog templates);
the module definition file: EDIT.DEF (contains instructions to the linker); the
help text: EDIT.IPF (contains the tag-language help source code); the icon file:
EDIT.ICO (contains a bitmap); and the makefile (contains instructions for
compiling and linking the entire project).
The total lines of code for the project is about 2500 (including all files
mentioned above, except for the icon file). The EDIT.EXE file (executable) is
about 25K; the help file (after compiling EDIT.IPF to EDIT.HLP) is about 15K.
Installation is accomplished by copying the executable file to C:\OS2, and the
help file to C:\OS2\HELP.
Credits
Most of the work for this project is original -- however, I am not one to
"reinvent the wheel". The open file dialog box (and associated support code)
was lifted verbatim from Charles Petzold's very good book: Programming the
OS/2 Presentation Manager. I derived the save as dialog box from the open
file dialog box.
How Edit Works
I will begin by describing the overall structure of the program, including
a brief description of each function. Since most of the action occurs in the
window procedure and through its message interaction with the MLE, I will
also describe the purpose of some of the message exchanges. In many
instances, standard messages sent by PM to the client window procedure result
in messages being sent to the MLE. When the user makes a menu selection
(e.g., selects Paste from the Edit menu) or uses one of the dialog boxes (e.g.,
to initiate a text search), a considerable amount of MLE message traffic is
also generated -- these will likewise be described. To gain the most from this
discussion, I suggest that you follow along in the code as each issue is
discussed (reading the code of Petzold, Duncan, and other pioneering OS/2
authors has helped me immensely).
Functions
main()
As in all PM programs, this function contains three things:
initialization code, the message loop, and termination code.
During initialization, an anchor block (whatever that is) is obtained,
a message queue is created, a window class is registered, and a
standard window is created -- this is all the usual "stuff". For IPF
(help), a HELPINIT structure if filled with information and a help
instance is created. Finally, a PM timer is created -- this will post
a message to the client window procedure every 200 milliseconds to
tell the editor it is time to update the cursor position display.
The message loop is nested inside an "infinite" for loop -- the
purpose of which is to give the user the chance to abort an exit, or
to save the current contents of the edit window to a file before
exit.
During termination, everything that was previously created is
destroyed -- including the help instance and the timer.
ClientWndProc()
A window procedure is what gives a window its behaviourial
characteristics -- i.e., it is what makes one window behave
differently from another (e.g., a pushbutton control window has a
different window procedure than a list box control window). The
client window procedure (of this or any PM application) is what
mainly determines how the application looks, feels, and acts. A
client window procedure is a massive switch/case statement, with
one case to handle each of the messages that the window must
respond to. In the case of the WM_COMMAND message, a further
(nested) switch/case handles each type of WM_COMMAND message.
Later, I will explain how many of these messages are handled, and
how they affects the MLE.
SetPtrArrow() & SetPtrWait()
These two functions are used to alter the appearance of the mouse
pointer. If a long operation (such as a text search) is initiated, a
call to SetPtrWait changes the mouse pointer to a timer symbol (an
hourglass or a clock). After the time consuming operation is
finished, a call to SetPtrArrow changes the mouse pointer back to
normal.
TabWndProc()
This function is used for subclassing. The MLE window procedure is
supplied by PM, and determines how the MLE behaves. Since I
didn't like the way that the MLE handled tabs, I used subclassing to
alter the normal behaviour. With subclassing, all messages destined
for the MLE are sent first to the TabWndProc, which traps only
WM_CHAR messages consisting of the Tab key so that it can
convert the Tab to an appropriate number of spaces (all other
messages are sent on to the regular MLE window procedure).
AboutDlgProc()
A dialog box is just a specific kind of window -- it therefore needs
a window procedure to handle all the messages that PM sends to it
when the window is displayed. The about box only needs to handle
one type of message -- so that it can dismiss itself when the user
activates the OK pushbutton.
OpenDlgProc()
This dialog box does much more than the about box, therefore its
window procedure must handle many more types of messages (hence
a switch/case statement). The open file dialog box allows the user
to select a drive/directory and a file (which will be read into
memory and displayed in the edit window). As I mentioned earlier,
this is Charles Petzold's code: refer to his book for an explanation
of how it works (Chapter 14, starting on Page 654).
SaveasDlgProc()
This dialog box is a subset of the open file dialog. It allows the
user to enter a file name. The user may optionally select a
different drive/directory into which the file is saved, otherwise the
current drive and directory are used.
ParseFileName()
This is a helper function used by OpenDlgProc and SaveasDlgProc.
Its purpose is to validate the drive, directory, and filename
information entered by the user. Refer to Petzold's book for a
complete explanation of this code.
FillDirListBox()
This is a also helper function used by OpenDlgProc and
SaveasDlgProc. The function name pretty well describes its purpose.
Refer to Petzold's book for a complete explanation of this code.
FileFileListBox()
This is also a helper function, but is used only by OpenDlgProc
(SaveasDlgProc does not display a list of files). The function name
pretty well describes its purpose. Refer to Petzold's book for a
complete explanation of this code.
FindDlgProc()
This dialog box prompts the user for a text string to search for.
When the user selects OK, the search proceeds. To facilitate
repeated searches, any previous search string is "suggested" as a
starting point. The string is placed in a global variable -- code in
the client window procedure does the actual searching (via messages
to the MLE).
ReplaceDlgProc()
Similar to above, this dialog box first prompts the user for a search
string, then a replacement string. Options are provided to allow the
user to continue the search without replacing the current
occurrence, to replace just the current occurrence, or to replace all
occurrences. As above, the actual work (search/replace) is carried
out by code in the client window procedure (this will be covered
later when messages are discussed).
GoLnDlgProc()
Prompts the user for a line number. When the user selects OK, that
line number is displayed at the top of the edit window. Again, the
dialog box procedure just collects user input -- the client window
procedure does the real work.
ReadFile()
Using a filename provided by OpenDlgProc, this function reads a file
of up to 64K into a dynamically allocated buffer. Returns file size
(or a negative code indicating an error); passes a pointer to the
buffer back to the client window procedure, which transfers the
data to the edit window (MLE).
MakeWriteBuffer() & WriteFile()
These two functions together are the mirror image of ReadFile --
allocation of buffer space and the actual writing to the file must be
split due to the requirements of the MLE. The sequence of events
is roughly: 1) ask MLE how much data it has; 2) allocate enough
space for that data (that is what MakeWriteBuffer does); 3) transfer
the data from the MLE to the buffer; and 4) create the file and
write to it (that is what WriteFile does).
Release()
This function will release (i.e., free) the dynamically allocated
storage used to read or write files.
Message Traffic
Traditional applications are procedure based: they use a programmed
sequence of operations. The programmer of such traditional applications
determine what the pattern of user interaction will be via hard coding. OS/2
Presentation Manager applications are very different -- the user is more in
control and may perform operations in any order. This focus on the user
requires a different programming paradigm -- the application must be ready to
accept any command from the user at almost any time. This is accomplished
by way of messages -- when the user requests an operation, the application is
advised by message. The application must be programmed to respond to any
message that the user can generate. Specific messages are generated any time
the user moves the mouse, strikes a key on the keyboard, or selects a menu
entry (among other actions).
Most messages are dispatched to the client window procedure; when a
dialog box is being displayed, the messages go to the dialog window procedure.
Each of the messages that the Edit client window procedure is programmed to
respond to is described below. In many cases, an incomming message (from
the user) results in other messages being sent to the MLE -- these also are
described.
WM_CREATE
The first message received by the client window procedure is
WM_CREATE, which is sent during processing of the
WinCreateStdWindow function. The purpose of this message is to
allow the window procedure to do any initialization prior to it being
displayed. In the case of Edit, there is much to be done. The
single most important job is to create the MLE window by calling
WinCreateWindow with the window class set to WC_MLE -- which
causes the built-in (PM supplied) MLE window procedure to control
this window. The MLE window is immediately subclassed to allow
for special handling of tabs. To subclass a window, you must
provide a new window procedure for this window. A pointer to the
original window procedure is returned as a result of the subclassing
operation -- this function pointer is used (by TabWndProc) to call
the original window procedure from within the new window
procedure to allow the original window procedure to still do most of
its work in the usual way (e.g., in Edit, subclassing affects only the
way that the MLE handles tabs). Other important actions taken
during WM_CREATE message processing are setting of various MLE
options (font -- set to monospace; maximum size -- set to 64K;
tabstops -- set to 64 pels). Also, a few minor jobs are done during
WM_CREATE processing: getting handles to various windows
(required for later message processing); determining the menu height
(used for the height of the status line that displays the cursor
position).
WM_SIZE
When the MLE is created, its position and size are not specified.
However, during the WM_SIZE message the MLE is positioned and
sized to just about fill the entire client area -- less a strip along
the top of the window (just below the menu) where cursor position
(line and column) will be displayed. The WM_SIZE message is sent
by PM every time the main (client) window changes size.
WM_TIMER
When the client window procedure receives a WM_TIMER message
(about every 200 milliseconds), it sends a couple of messages to the
MLE, and does a few calculations to determine the position (line and
column) of the cursor. If the cursor position has changed, the
client window is invalidated to cause PM to send a WM_PAINT
message (so that the new cursor information is displayed).
WM_PAINT
During processing of a WM_PAINT message, the current cursor
position (line and column) are displayed along the top of the client
area just above the MLE window. No other client area drawing is
needed because the MLE window procedure takes care of keeping
the text properly displayed as the user enters or deletes text, or
scrolls through the text, etc..
WM_MINMAXFRAME
When Edit has a file loaded into the MLE, the filename is displayed
in the program's title bar. When the program is minimized (i.e.,
displayed as an icon) the title bar text is usually displayed under
the icon. Unfortunately, PM limits the amount of text that can be
displayed under an icon -- a full filename (especially if the path is
included) will likely be cut off. Each time the program is changed
to or from an icon, PM sends a WM_MINMAXFRAME message --
Edit uses this message to change the title bar text to just the
program name when the program is minimized and back to the
program name + the filename otherwise.
WM_INITMENU
Several of the program's features are not always available -- for
instance Cut, Copy, and Delete are not available unless some text
has been selected (highlighted) in the edit window; similarly Paste is
not available unless the clipboard contains text. Each time PM is
about to display a menu, it sends the program a WM_INITMENU
message -- when this message is received, Edit asks the clipboard if
there is any text present, and asks the MLE if any text has been
highlighted. If not, the appropriate menu item is disabled (greyed
out) so that the user cannot select an operation that is not
currently valid.
WM_CONTROL
There are several messages that the MLE can send to the client
window procedure -- all are sent via notification codes as part of a
WM_CONTROL message. Edit handles only two of these:
MLN_OVERFLOW (the MLE has exceeded its 64K limit), and
MLN_CHANGE (the text in the MLE has changed). The first of
these is used to warn the user (who should then split the file before
continuing). The second message (MLN_CHANGE) helps to determine
if the file should be saved before exit, and also helps to determine
whether an undo operation has occurred (and can thus be redone by
invoking undo again).
WM_COMMAND
Whenever the user selects a menu entry (whether by mouse or by
keyboard), PM generates a WM_COMMAND message. Along with the
WM_COMMAND message comes a parameter that indicates the nature
of the command (i.e., which menu was selected). Just as the many
messages themselves are handled by a large switch/case statement,
the many menu commands are similarly handled by a (now nested)
switch/case statement with one case for each menu selection. The
following commands are processed:
IDM_NEW
The IDM_NEW command is the result of the user choosing New
from the File menu (clears the edit window). If the edit
window has changed, the user is given a chance to save to a
file before this command is acted upon. The New operation is
performed by first asking the MLE how much text it has, and
then deleting it all.
IDM_OPEN
The IDM_OPEN command is the result of the user choosing
Open from the File menu (allows the user to open a new file).
Edit first sends itself a WM_COMMAND:IDM_NEW message to
clear the edit window in preparation. Next, the OpenDlgProc
is invoked indirectly by calling WinDlgBox -- if the user
selects OK with a valid filename chosen, this procedure return
TRUE and the file is read by calling ReadFile, the buffer filled
up by ReadFile is displayed in the edit window by sending the
MLE several messages (after checking to be sure ReadFile was
successful). The technique of "sending yourself messages", as
in the case of sending the WM_COMMAND:IDM_NEW message
above, is a powerful alternative to using subroutines to get
work done inside a window procedure.
IDM_SAVE
The IDM_SAVE command is the result of the user choosing
Save from the File menu -- using the technique just described,
an IDM_SAVE command can also result (indirectly) from the
user choosing Save as from the File menu. Processing the save
command consists of asking the MLE how much text it has,
making a write buffer for the text, transferring the text from
the MLE to the buffer, and finally writing the buffer to a file.
In what almost seems like mutual recursion, the
WM_COMMAND:IDM_SAVEAS message is sent if the edit
window does not already have a name.
IDM_SAVEAS
The IDM_SAVEAS command is the result of the user choosing
Save as from the File menu. The SaveasDlgProc is invoked
(indirectly, by WinDlgBox) to allow the user to choose a
filename. Once a filename is chosen, a boolean (hasName) is
set to TRUE, and the WM_COMMAND:IDM_SAVE message is
sent. It is this boolean which prevents any recursion resulting
from IDM_SAVE invoking IDM_SAVEAS and vice versa.
IDM_EXIT
The IDM_EXIT command is the result of the user choosing Exit
from the File menu. In turn, Edit sends a WM_CLOSE message
which eventually results in a WM_QUIT message being sent by
PM. When the message loop receives a WM_QUIT message, the
loop terminates. There are (at least) two other ways that the
WM_CLOSE/WM_QUIT sequence can occur: as a result of the
user choosing Close from the system menu, or End task from
the PM Task List. In all cases the "infinite" for loop in main
(described earlier) gives the user a chance to save the contents
of the edit window to a file before the program actually exits
or of cancelling the shutdown.
IDM_UNDO
The IDM_UNDO command is the result of the user choosing
Undo from the Edit menu. The MLE knows how to undo the
last operation, so all that Edit has to do is send the MLE an
MLM_UNDO message. If the last action was undo, the undo is
undone (i.e., redo). The undone flag is set to TRUE to allow
redo (whether undo is enabled is controlled by the action of
the WM_INITMENU command processing, explained above).
IDM_CUT, IDM_COPY, & IDM_DELETE
The IDM_CUT, IDM_COPY, and IDM_DELETE messages are all
handled similarly. They are the result of the user choosing
Cut, Copy, or Delete from the Edit menu. Since the MLE
"knows how" to do all of these operations, all that is necessary
is to send the appropriate message (MLM_CUT, MLM_COPY, or
MLM_CLEAR) to the MLE -- it does the rest. As mentioned
above, these commands would have been disabled during
WM_INITMENU processing if the user has not previously
selected any text. The copy command places the selected text
on the system clipboard; the delete command erases the
selected text; the cut command does both (copys text to the
clipboard, and then erases it).
IDM_PASTE
The IDM_PASTE command results from the user choosing Paste
from the Edit menu. The MLE also "know how" to paste from
the clipboard (all we have to do is send it an MLM_PASTE
message). Similar to the case with cut, copy, and delete, the
Paste menu selection would have been disabled during the
WM_INITMENU processing if there was no text on the
clipboard.
IDM_FIND
The IDM_FIND command results from the user choosing Find
from the Edit menu. First, the FindDlgProc is invoked via a
call to WinDlgBox -- if the user does not select Cancel,
WinDlgBox returns DID_OK and the search begins. Although
the MLE "knows how" to search, a little more work is required:
a structure must be filled with information before the
MLM_SEARCH message is sent to the MLE. If the target
string is found, the MLE highlights it. If the target is not
found, the MLE does nothing visible, but it returns FALSE,
which Edit uses to display a "not found" message box.
IDM_REPLACE
The IDM_REPLACE command results from the user choosing
Replace from the Edit menu. The processing is somewhat
similar to Find, except that two strings are involved (a target
and a replacement), and there is a loop to allow repeated
find/replace cycles. There is also options to replace either a
single instance or all instances of the target with the
replacement string. The first time the ReplaceDlgProc is called
(via WinDlgBox), a parameter passed to the dialog box
procedure causes the Replace and Replace All buttons to be
disabled. For successive iterations, these buttons are enabled
by altering the parameter mentioned earlier (i.e., setting "first"
to FALSE). If, after the target is found, the user then
chooses replace, an MLM_INSERT message is used to replace
the single instance; if the user chooses replace all,
MLM_INSERT replaces the first instance (the one that was
already found), and an MLM_SEARCH message with a parameter
of MLFSEARCH_CHANGEALL is sent to find and replace the
rest.
IDM_GO
The IDM_GO command results from the user choosing Go to
line from the Edit menu. The processing is more complicated
than is should be because the MLE offers no way to go
directly to a specific line number -- you must go through two
steps: first convert the line number to an absolute character
position, and then cause the MLE to display that character on
the first line of the edit window. Matters are complicated
even further because if the user then depresses a cursor
movement key, the MLE will "snap back" to where it was prior
to repositioning. The "trick" to avoiding this "snapping back"
is to simulate a mouse button hit, which "locks" the MLE in its
new position.
IDM_HELPFORHELP, IDM_EXTENDEDHELP, IDM_KEYSHELP, & IDM_HELPINDEX
The way that I handle processing of the help menu messages is
contrary to the method recommended by IBM -- however, my
method gives me greater flexibility and is somewhat simpler and
more consistent. In the resource file (EDIT.RC), IBM
recommends that the help menu items (except for Help for
help) generate predefined messages types by sending a
WM_SYSCOMMAND message with a specific parameter (which is
unlike other menus, and prevents the context sensitive help
that is available for other menus). Instead, I set up the help
menu in the resource file in a manner that is consistent with
other menus, and then send the WM_SYSCOMMAND messages
from the client window procedure (for extended help, keys
help, and the help index), which allows for context sensitive
help within the help menu. I also simplify the help table and
help subtable by using the same constants for help as for the
menus (e.g., HELPSUBITEM IDM_FILE, IDM_FILE) -- this is
much simpler than the recommended method (of relating the
menu ID to some new ID), and causes no limitations or other
problems. Most of the effort in putting together on-line help
involves the IPF file, which is discussed below.
IDM_ABOUT
The IDM_ABOUT command results from the user choosing
About from the Help command. To display the about box, we
invoke the AboutDlgProc via WinDlgProc.
WM_ARGS & WM_CLEANFILE
Edit also uses two so-called user messages -- these are messages
defined by the application instead of by PM. All messages are
identified by a unique number; any message defined by the
application should be numbered starting at WM_USER (a pre-defined
constant guaranteed to be above the message numbers used by PM).
The first user message is WM_ARGS, which is actually sent from
main (just before the message loop is entered) as a result of the
user entering a filename on the command line when Edit is started.
When the client window procedure receives this message, it opens up
a file and reads it into the edit window (of the MLE). The other
user message, WM_CLEANFILE, is sent whenever the editor is in a
condition where it is safe to exit without saving the current edit
window to a file. This message also causes the MLE undo-state to
be reset. Edit sends itself the WM_CLEANFILE message whenever a
new file is read, and whenever the edit window is saved to a file
(these operations cannot be undone, but no need to save before
exit).
HM_ERROR & HM_QUERY_KEYS_HELP
The application receives two messages related directly to the help
manager; these are HM_ERROR (which results in the display of a message
box informing the user that help is not available), and
HM_QUERY_KEYS_HELP (which is merely passed back to PM for
processing).
WM_DESTROY
After the user has terminated the application, PM sends a WM_DESTROY
message -- the only thing left to be done by Edit is to destroy the MLE
(which was created during the WM_CREATE message).
The Information Presentation Facility (IPF)
IPF is the on-line help facility that was introduced with OS/2 v1.2, and
replaced and/or supplemented help hooks (depending upon your point of view -
- although applications programmers no longer work with help hooks, IPF still
uses them internally). IPF is quite flexible and powerful (but has not been
well documented, except for a short and incomplete section in Volume 4 of the
Microsoft OS/2 Programmer's Reference and a few articles in the IBM Personal
Systems Developer). IPF allows for context sensitive help, a two-level index,
a table-of-contents, hypertext links, multiple viewports, and graphics (including
animation). This article (and accompanying code) illustrates the basic features,
omitting viewports, graphics, and certain types of links.
Implementing IPF requires minor changes to the C source (discussed above
under messages), additions to the resource script (HELPTABLE and
HELPSUBTABLE(s)), and creation of the IPF help file (source help text). A
special compiler (IPFC) is required to convert the IPF file to a HLP file.
IPF help files are mainly plain text (which is what the user reads when
requesting help); however, IPF uses a tag language to describe certain features
of the help system. The IPF source file is compiled by IPFC (the IPF
Compiler) to produce the HLP files that are used at run time. I have managed
to "figure out" enough of the help system to "get by" based upon scant
examples and write-ups in the sources mentioned above. I have had to "fill
in" a few holes by guess and experimentation. Not a very satisfactory state of
affairs -- however, in the end I am able to generate fairly good help files.
The IPF tag language uses rather verbose multi-character symbols to delimit
the various sections of the help source file. Some of these tags are explained
below.
The IPF source code is divided into help panels. Each panel has a
number (which is used by the Help Manager to find the correct panel when
the user requests help, and may also be used as a reference for hypertext
links within IPF) and a title (which is displayed in the help window title bar).
If the help panel is for a menu item, its reference number must match the
IDM_????? constant defined in the applications header file. Each help panel
can be divided into paragraphs (:p. tag); within each paragraph, word wrap is
automatic. If you wish to disable word wrap, it is possible to set up simple
lists (:sl. :li. :esl. tags) of items (which are double spaced unless they are
declared compact).
One of the most powerful features of IPF is the hypertext links. Links
are set up with a tag statement as follows:
:link.res=### reftype=hd.?????:elink.
The ### refers to a numbered help panel; the ????? is the text that is
displayed as the hypertext link (shown in contrasting colour -- usually light
green). If the user double-clicks on the link, the referenced help panel is
displayed. The Edit program uses hypertext links only in compact lists, but
they can be embedded anywhere in the help text. Another type of line
(reftype=inform) sends an HM_INFORM message to the application; that type is
not illustrated here.
To set up an index of available help topics, the second line of each help
panel must be one of the following:
:i1 id=xx.?????
:i2 refid=xx.??????
The :i1 is used for top level index entries, :i2 for sub-entries (the index is
only two level). In the case of the =xx., the xx can be any word (sequence of
letters), which specifies which second level index entries go with which first
level entry (there must be only one first level entry for each xx value. The
????? can be anything, and is what appears in the index. Incidently, index
entries are also hypertext links (i.e., double-click on them to display the
associated help panel). You do not have to add anything additional to also get
a table of contents (IPF does that for you).
To highlight a word or phrase, there are 10 styles of text available:
highlighted phrases 1 through 9, plus the default (which is dark blue). Here is
an example of a highlighted phrase:
:hp8.This would be red text!:ehp8.
The following list indicates what the highlighted phrases mean:
hp1 = italics
hp2 = bold
hp3 = italics+bold
hp4 = light blue
hp5 = underlined
hp6 = italics+underlined
hp7 = bold+underlined
hp8 = red
hp9 = magenta
The Edit help system uses (or perhaps over uses) a variety of highlighted
phrases for emphasis of certain words and phrases.
Another feature of IPF (not illustrated here) is the ability to give
context sensitive help in dialog boxes (Edit doesn't have any complicated
dialog boxes where such help would be warranted). Adding dialog box help is
quite simple and requires the following steps: 1) add a help button with button
styles BS_NOPOINTERFOCUS and BS_HELP; 2) add an entry to the
HELPTABLE in the resource file for each dialog box; 3) add a HELPSUBTABLE
for each dialog box with an entry for each element of the dialog box, keyed
by ID; 4) add a panel to the IPF file for each element of the dialog box,
again, keyed by the ID of the element. When the user selects the help button
(or depresses the F1 key), help will be provided for whatever element of the
dialog box currently has focus (although it is also possible to have the same
help displayed regardless of which element has focus by using the same help
ID -- the second constant in a help subtable item -- for all IDs in the dialog
box).
Conclusion
Many people have lamented the long, steep learning curve that must be
climbed to become productive programming in a GUI environment such as OS/2
Presentation Manager. This project illustrates that the powerful features
provided by the GUI can make an otherwise very challenging project almost a
snap. Without the MLE (Multi Line Edit predefined control window), the
editor presented here would have taken considerably longer to develop, and
would have resulted in a considerably greater number of total lines of code. I
submit that once mastered, a GUI such as the OS/2 Presentation Manager
actually makes a programmer more productive and results in more functional
and useful applications. These advantages far outweigh the disadvantage of
any initial difficulty in learning the GUI paradigm.
* * *