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 >
Text File  |  1992-02-28  |  36KB  |  712 lines

  1. An OS/2-PM Editor using MLE
  2.  
  3. by Brian R. Anderson
  4.  
  5.  
  6.     MLE is a powerful control window which is available to OS/2-PM
  7. programmers (pushbuttons, radio buttons, and list boxes are also control
  8. windows); the initials stand for Multi Line Edit.  In a very real sense, an MLE
  9. is an object that "knows" how to edit text.  This article describes how I used
  10. an MLE as the basis for building a simple yet functional programmer's editor. 
  11.  
  12.  
  13. Background
  14.  
  15.     You would be well justified in asking: why does the world need another
  16. editor?  This project "happened" due to a unique set of circumstances.  I was
  17. teaching a course on OS/2 programming where the students were (rightfully)
  18. complaining about the OS/2 System Editor because it did not display line
  19. numbers (they needed line numbers to interpret compiler diagnostics which list
  20. syntax errors by line).  Just as the course got under way, a labour dispute
  21. kept me away from my teaching duties for nearly a week; I decided to use the
  22. time productively and thereby give the students a more useful editing tool and
  23. an extended example of OS/2-PM programming.
  24.  
  25.     While you might never wish to write an editor, per se, you might want to
  26. include some minimal editing capability in a larger project -- the MLE provides
  27. a perfect mechanism for doing so with very little effort.  Although this article
  28. describes using the MLE in the client window, I have also used the MLE in a
  29. dialog box to give limited text editing functions for purposes specific to the
  30. application.
  31.  
  32.  
  33. Object Oriented Programming?
  34.  
  35.     I previously referred to the MLE as an object.  This is not really an
  36. article on OOPS, but many of the characteristics that make OOPS attractive
  37. also apply to the MLE.  First of all, the Multi Line Edit control window is
  38. very high level (as are many of the objects in a good class library).  Secondly,
  39. the MLE uses a message based paradigm: you send the MLE messages when you
  40. want it to do something, and it sends you messages when it wants to advise
  41. you of something.  And finally, the MLE can be easily extended (through
  42. subclassing) to enhance functionality.
  43.  
  44.     What I mean by the Multi Line Edit control window being very high
  45. level is that this object itself already knows how to interact with the user for
  46. text entry and insertion, cursor movement, scrolling, search and replace,
  47. cut/copy and paste, and probably a few more that I've forgotten.  For some of
  48. these features, no extra programming is required at all.  For the other
  49. features, all that is necessary is to send the MLE an appropriate message
  50. indicating what you want it to do.  Some MLE features require multiple
  51. messages and/or a small amount of additional programming.
  52. Article Scope
  53.  
  54.     There have been many good articles published about the basic structure
  55. of an OS/2-PM program (creating standard windows, the message loop, the
  56. window procedure, resources, etc.).  This article assumes that you are already
  57. somewhat familiar with those areas of GUI programming and therefore glosses
  58. over them.  The two main areas of emphasis for the article are: programming
  59. with the MLE (including subclassing), and programming for the OS/2 help
  60. system (Information Presentation Facility).  
  61.  
  62.  
  63. Design Criteria
  64.  
  65.     I wanted the editor to be easy to use, but not overly constraining.  I
  66. also wanted the code to be simple and understandable so that I could use it as
  67. an example in the classroom.  Finally, I wanted good compliance to the IBM
  68. Common User Access guidelines.
  69.  
  70.     I decided to limit the size of edit files to 64K -- partly to keep the code
  71. simple, and partly to ensure that my students coded their projects in a
  72. modular fashion (no single C source file should be larger than this).  I also
  73. decided not to support custom colours or fonts -- after all, this is a
  74. programmer's editor, not a word processor or a paint program.  Since with
  75. OS/2, it is possible to start multiple copies of the program and then
  76. cut/copy/paste among them, I saw no need for implementing the MDI (Multiple
  77. Document Interface).
  78.  
  79.     Although I had access to both CASE:PM and Gpf (Presentation Manager
  80. code generation tools), I elected to develop the project from scratch -- mostly
  81. for the practice using the raw API and the SDK.
  82.  
  83.     After several weeks of use, I have found that all of my students prefer
  84. the new editor to anything else (previous classes "got by" using DOS editors
  85. and/or the OS/2 System Editor).  Many students have commented that the
  86. OS/2 environment is much superior to anything that they have experienced
  87. before (prerequisite programming courses have used everything from a 370-style
  88. mainframe to Borland's Turbo C++).  What they like best about OS/2 is being
  89. able to start compiling in one session, and then immediately go back to editing
  90. in another session.
  91.  
  92.  
  93. Production Files
  94.  
  95.     Presentation Manager programs require many types of source files.  Of
  96. course, there are the C source code files and header files.  In keeping with
  97. my dictum of modular decomposition, I have divided the project into three
  98. separately compiled modules: EDIT.C (contains main, the client window
  99. procedure, and the subclassing procedure); EDLG.C (contains the dialog box
  100. window procedures and supporting code); and EFILE.C (contains the file
  101. input/output functions).  Each C source code file has a matching header file. 
  102. Concerning my self-imposed 64K limit: even if all six of these source code files
  103. were concatenated, they would not total 64K!
  104.  
  105.     In addition to the C source code, there is the resource file: EDIT.RC
  106. (contains the menu, keyboard accelerators, help tables, and dialog templates);
  107. the module definition file: EDIT.DEF (contains instructions to the linker); the
  108. help text: EDIT.IPF (contains the tag-language help source code); the icon file:
  109. EDIT.ICO (contains a bitmap); and the makefile (contains instructions for
  110. compiling and linking the entire project).
  111.  
  112.     The total lines of code for the project is about 2500 (including all files
  113. mentioned above, except for the icon file).  The EDIT.EXE file (executable) is
  114. about 25K; the help file (after compiling EDIT.IPF to EDIT.HLP) is about 15K. 
  115. Installation is accomplished by copying the executable file to C:\OS2, and the
  116. help file to C:\OS2\HELP.
  117.  
  118.  
  119. Credits
  120.  
  121.     Most of the work for this project is original -- however, I am not one to
  122. "reinvent the wheel".  The open file dialog box (and associated support code)
  123. was lifted verbatim from Charles Petzold's very good book: Programming the
  124. OS/2 Presentation Manager.  I derived the save as dialog box from the open
  125. file dialog box.
  126.  
  127.  
  128. How Edit Works
  129.  
  130.     I will begin by describing the overall structure of the program, including
  131. a brief description of each function.  Since most of the action occurs in the
  132. window procedure and through its message interaction with the MLE, I will
  133. also describe the purpose of some of the message exchanges.  In many
  134. instances, standard messages sent by PM to the client window procedure result
  135. in messages being sent to the MLE.  When the user makes a menu selection
  136. (e.g., selects Paste from the Edit menu) or uses one of the dialog boxes (e.g.,
  137. to initiate a text search), a considerable amount of MLE message traffic is
  138. also generated -- these will likewise be described.  To gain the most from this
  139. discussion, I suggest that you follow along in the code as each issue is
  140. discussed (reading the code of Petzold, Duncan, and other pioneering OS/2
  141. authors has helped me immensely).
  142.  
  143.  
  144. Functions
  145.  
  146. main()
  147. As in all PM programs, this function contains three things:
  148. initialization code, the message loop, and termination code.  
  149.  
  150. During initialization, an anchor block (whatever that is) is obtained,
  151. a message queue is created, a window class is registered, and a
  152. standard window is created -- this is all the usual "stuff".  For IPF
  153. (help), a HELPINIT structure if filled with information and a help
  154. instance is created.  Finally, a PM timer is created -- this will post
  155. a message to the client window procedure every 200 milliseconds to
  156. tell the editor it is time to update the cursor position display.
  157.  
  158. The message loop is nested inside an "infinite" for loop -- the
  159. purpose of which is to give the user the chance to abort an exit, or
  160. to save the current contents of the edit window to a file before
  161. exit.
  162.  
  163. During termination, everything that was previously created is
  164. destroyed -- including the help instance and the timer.
  165.  
  166.  
  167. ClientWndProc()
  168. A window procedure is what gives a window its behaviourial
  169. characteristics -- i.e., it is what makes one window behave
  170. differently from another (e.g., a pushbutton control window has a
  171. different window procedure than a list box control window).  The
  172. client window procedure (of this or any PM application) is what
  173. mainly determines how the application looks, feels, and acts.  A
  174. client window procedure is a massive switch/case statement, with
  175. one case to handle each of the messages that the window must
  176. respond to.  In the case of the WM_COMMAND message, a further
  177. (nested) switch/case handles each type of WM_COMMAND message. 
  178. Later, I will explain how many of these messages are handled, and
  179. how they affects the MLE.
  180.  
  181. SetPtrArrow() & SetPtrWait()
  182. These two functions are used to alter the appearance of the mouse
  183. pointer.  If a long operation (such as a text search) is initiated, a
  184. call to SetPtrWait changes the mouse pointer to a timer symbol (an
  185. hourglass or a clock).  After the time consuming operation is
  186. finished, a call to SetPtrArrow changes the mouse pointer back to
  187. normal.
  188.  
  189. TabWndProc()
  190. This function is used for subclassing.  The MLE window procedure is
  191. supplied by PM, and determines how the MLE behaves.  Since I
  192. didn't like the way that the MLE handled tabs, I used subclassing to
  193. alter the normal behaviour.  With subclassing, all messages destined
  194. for the MLE are sent first to the TabWndProc, which traps only
  195. WM_CHAR messages consisting of the Tab key so that it can
  196. convert the Tab to an appropriate number of spaces (all other
  197. messages are sent on to the regular MLE window procedure).
  198.  
  199. AboutDlgProc()
  200. A dialog box is just a specific kind of window -- it therefore needs
  201. a window procedure to handle all the messages that PM sends to it
  202. when the window is displayed.  The about box only needs to handle
  203. one type of message -- so that it can dismiss itself when the user
  204. activates the OK pushbutton.
  205.  
  206. OpenDlgProc()
  207. This dialog box does much more than the about box, therefore its
  208. window procedure must handle many more types of messages (hence
  209. a switch/case statement).  The open file dialog box allows the user
  210. to select a drive/directory and a file (which will be read into
  211. memory and displayed in the edit window).  As I mentioned earlier,
  212. this is Charles Petzold's code: refer to his book for an explanation
  213. of how it works (Chapter 14, starting on Page 654).
  214.  
  215. SaveasDlgProc()
  216. This dialog box is a subset of the open file dialog.  It allows the
  217. user to enter a file name.  The user may optionally select a
  218. different drive/directory into which the file is saved, otherwise the
  219. current drive and directory are used.
  220.  
  221. ParseFileName()
  222. This is a helper function used by OpenDlgProc and SaveasDlgProc. 
  223. Its purpose is to validate the drive, directory, and filename
  224. information entered by the user.  Refer to Petzold's book for a
  225. complete explanation of this code.
  226.  
  227. FillDirListBox()
  228. This is a also helper function used by OpenDlgProc and
  229. SaveasDlgProc.  The function name pretty well describes its purpose.
  230. Refer to Petzold's book for a complete explanation of this code.
  231.  
  232. FileFileListBox()
  233. This is also a helper function, but is used only by OpenDlgProc
  234. (SaveasDlgProc does not display a list of files).  The function name
  235. pretty well describes its purpose.  Refer to Petzold's book for a
  236. complete explanation of this code.
  237.  
  238. FindDlgProc()
  239. This dialog box prompts the user for a text string to search for. 
  240. When the user selects OK, the search proceeds.  To facilitate
  241. repeated searches, any previous search string is "suggested" as a
  242. starting point.  The string is placed in a global variable -- code in
  243. the client window procedure does the actual searching (via messages
  244. to the MLE).
  245.  
  246. ReplaceDlgProc()
  247. Similar to above, this dialog box first prompts the user for a search
  248. string, then a replacement string.  Options are provided to allow the
  249. user to continue the search without replacing the current
  250. occurrence, to replace just the current occurrence, or to replace all
  251. occurrences.  As above, the actual work (search/replace) is carried
  252. out by code in the client window procedure (this will be covered
  253. later when messages are discussed).
  254.  
  255. GoLnDlgProc()
  256. Prompts the user for a line number.  When the user selects OK, that
  257. line number is displayed at the top of the edit window.  Again, the
  258. dialog box procedure just collects user input -- the client window
  259. procedure does the real work.
  260.  
  261. ReadFile()
  262. Using a filename provided by OpenDlgProc, this function reads a file
  263. of up to 64K into a dynamically allocated buffer.  Returns file size
  264. (or a negative code indicating an error); passes a pointer to the
  265. buffer back to the client window procedure, which transfers the
  266. data to the edit window (MLE).
  267.  
  268. MakeWriteBuffer() & WriteFile()
  269. These two functions together are the mirror image of ReadFile --
  270. allocation of buffer space and the actual writing to the file must be
  271. split due to the requirements of the MLE.  The sequence of events
  272. is roughly: 1) ask MLE how much data it has; 2) allocate enough
  273. space for that data (that is what MakeWriteBuffer does); 3) transfer
  274. the data from the MLE to the buffer; and 4) create the file and
  275. write to it (that is what WriteFile does).
  276.  
  277. Release()
  278. This function will release (i.e., free) the dynamically allocated
  279. storage used to read or write files.
  280.  
  281.  
  282. Message Traffic
  283.  
  284.     Traditional applications are procedure based: they use a programmed
  285. sequence of operations.  The programmer of such traditional applications
  286. determine what the pattern of user interaction will be via hard coding.  OS/2
  287. Presentation Manager applications are very different -- the user is more in
  288. control and may perform operations in any order.  This focus on the user
  289. requires a different programming paradigm -- the application must be ready to
  290. accept any command from the user at almost any time.  This is accomplished
  291. by way of messages -- when the user requests an operation, the application is
  292. advised by message.  The application must be programmed to respond to any
  293. message that the user can generate.  Specific messages are generated any time
  294. the user moves the mouse, strikes a key on the keyboard, or selects a menu
  295. entry (among other actions).
  296.  
  297.     Most messages are dispatched to the client window procedure; when a
  298. dialog box is being displayed, the messages go to the dialog window procedure. 
  299. Each of the messages that the Edit client window procedure is programmed to
  300. respond to is described below.  In many cases, an incomming message (from
  301. the user) results in other messages being sent to the MLE -- these also are
  302. described.
  303.  
  304. WM_CREATE
  305. The first message received by the client window procedure is
  306. WM_CREATE, which is sent during processing of the
  307. WinCreateStdWindow function.  The purpose of this message is to
  308. allow the window procedure to do any initialization prior to it being
  309. displayed.  In the case of Edit, there is much to be done.  The
  310. single most important job is to create the MLE window by calling
  311. WinCreateWindow with the window class set to WC_MLE -- which
  312. causes the built-in (PM supplied) MLE window procedure to control
  313. this window.  The MLE window is immediately subclassed to allow
  314. for special handling of tabs.  To subclass a window, you must
  315. provide a new window procedure for this window.  A pointer to the
  316. original window procedure is returned as a result of the subclassing
  317. operation -- this function pointer is used (by TabWndProc) to call
  318. the original window procedure from within the new window
  319. procedure to allow the original window procedure to still do most of
  320. its work in the usual way (e.g., in Edit, subclassing affects only the
  321. way that the MLE handles tabs).  Other important actions taken
  322. during WM_CREATE message processing are setting of various MLE
  323. options (font -- set to monospace; maximum size -- set to 64K;
  324. tabstops -- set to 64 pels).  Also, a few minor jobs are done during
  325. WM_CREATE processing: getting handles to various windows
  326. (required for later message processing); determining the menu height
  327. (used for the height of the status line that displays the cursor
  328. position).
  329.  
  330. WM_SIZE
  331. When the MLE is created, its position and size are not specified. 
  332. However, during the WM_SIZE message the MLE is positioned and
  333. sized to just about fill the entire client area -- less a strip along
  334. the top of the window (just below the menu) where cursor position
  335. (line and column) will be displayed.  The WM_SIZE message is sent
  336. by PM every time the main (client) window changes size.
  337.  
  338. WM_TIMER
  339. When the client window procedure receives a WM_TIMER message
  340. (about every 200 milliseconds), it sends a couple of messages to the
  341. MLE, and does a few calculations to determine the position (line and
  342. column) of the cursor.  If the cursor position has changed, the
  343. client window is invalidated to cause PM to send a WM_PAINT
  344. message (so that the new cursor information is displayed).
  345.  
  346. WM_PAINT
  347. During processing of a WM_PAINT message, the current cursor
  348. position (line and column) are displayed along the top of the client
  349. area just above the MLE window.  No other client area drawing is
  350. needed because the MLE window procedure takes care of keeping
  351. the text properly displayed as the user enters or deletes text, or
  352. scrolls through the text, etc..
  353.  
  354. WM_MINMAXFRAME
  355. When Edit has a file loaded into the MLE, the filename is displayed
  356. in the program's title bar.  When the program is minimized (i.e.,
  357. displayed as an icon) the title bar text is usually displayed under
  358. the icon.  Unfortunately, PM limits the amount of text that can be
  359. displayed under an icon -- a full filename (especially if the path is
  360. included) will likely be cut off.  Each time the program is changed
  361. to or from an icon, PM sends a WM_MINMAXFRAME message --
  362. Edit uses this message to change the title bar text to just the
  363. program name when the program is minimized and back to the
  364. program name + the filename otherwise.
  365.  
  366. WM_INITMENU
  367. Several of the program's features are not always available -- for
  368. instance Cut, Copy, and Delete are not available unless some text
  369. has been selected (highlighted) in the edit window; similarly Paste is
  370. not available unless the clipboard contains text.  Each time PM is
  371. about to display a menu, it sends the program a WM_INITMENU
  372. message -- when this message is received, Edit asks the clipboard if
  373. there is any text present, and asks the MLE if any text has been
  374. highlighted.  If not, the appropriate menu item is disabled (greyed
  375. out) so that the user cannot select an operation that is not
  376. currently valid.
  377.  
  378. WM_CONTROL
  379. There are several messages that the MLE can send to the client
  380. window procedure -- all are sent via notification codes as part of a
  381. WM_CONTROL message.  Edit handles only two of these:
  382. MLN_OVERFLOW (the MLE has exceeded its 64K limit), and
  383. MLN_CHANGE (the text in the MLE has changed).  The first of
  384. these is used to warn the user (who should then split the file before
  385. continuing).  The second message (MLN_CHANGE) helps to determine
  386. if the file should be saved before exit, and also helps to determine
  387. whether an undo operation has occurred (and can thus be redone by
  388. invoking undo again).
  389.  
  390. WM_COMMAND
  391. Whenever the user selects a menu entry (whether by mouse or by
  392. keyboard), PM generates a WM_COMMAND message.  Along with the
  393. WM_COMMAND message comes a parameter that indicates the nature
  394. of the command (i.e., which menu was selected).  Just as the many
  395. messages themselves are handled by a large switch/case statement,
  396. the many menu commands are similarly handled by a (now nested)
  397. switch/case statement with one case for each menu selection.  The
  398. following commands are processed:
  399.  
  400.     IDM_NEW
  401. The IDM_NEW command is the result of the user choosing New
  402. from the File menu (clears the edit window).  If the edit
  403. window has changed, the user is given a chance to save to a
  404. file before this command is acted upon.  The New operation is
  405. performed by first asking the MLE how much text it has, and
  406. then deleting it all.
  407.  
  408.     IDM_OPEN
  409. The IDM_OPEN command is the result of the user choosing
  410. Open from the File menu (allows the user to open a new file). 
  411. Edit first sends itself a WM_COMMAND:IDM_NEW message to
  412. clear the edit window in preparation.  Next, the OpenDlgProc
  413. is invoked indirectly by calling WinDlgBox -- if the user
  414. selects OK with a valid filename chosen, this procedure return
  415. TRUE and the file is read by calling ReadFile, the buffer filled
  416. up by ReadFile is displayed in the edit window by sending the
  417. MLE several messages (after checking to be sure ReadFile was
  418. successful).  The technique of "sending yourself messages", as
  419. in the case of sending the WM_COMMAND:IDM_NEW message
  420. above, is a powerful alternative to using subroutines to get
  421. work done inside a window procedure.
  422.  
  423.     IDM_SAVE
  424. The IDM_SAVE command is the result of the user choosing
  425. Save from the File menu -- using the technique just described,
  426. an IDM_SAVE command can also result (indirectly) from the
  427. user choosing Save as from the File menu.  Processing the save
  428. command consists of asking the MLE how much text it has,
  429. making a write buffer for the text, transferring the text from
  430. the MLE to the buffer, and finally writing the buffer to a file. 
  431. In what almost seems like mutual recursion, the
  432. WM_COMMAND:IDM_SAVEAS message is sent if the edit
  433. window does not already have a name.
  434.  
  435.     IDM_SAVEAS
  436. The IDM_SAVEAS command is the result of the user choosing
  437. Save as from the File menu.  The SaveasDlgProc is invoked
  438. (indirectly, by WinDlgBox) to allow the user to choose a
  439. filename.  Once a filename is chosen, a boolean (hasName) is
  440. set to TRUE, and the WM_COMMAND:IDM_SAVE message is
  441. sent.  It is this boolean which prevents any recursion resulting
  442. from IDM_SAVE invoking IDM_SAVEAS and vice versa.
  443.  
  444.     IDM_EXIT
  445. The IDM_EXIT command is the result of the user choosing Exit
  446. from the File menu.  In turn, Edit sends a WM_CLOSE message
  447. which eventually results in a WM_QUIT message being sent by
  448. PM.  When the message loop receives a WM_QUIT message, the
  449. loop terminates.  There are (at least) two other ways that the
  450. WM_CLOSE/WM_QUIT sequence can occur: as a result of the
  451. user choosing Close from the system menu, or End task from
  452. the PM Task List.  In all cases the "infinite" for loop in main
  453. (described earlier) gives the user a chance to save the contents
  454. of the edit window to a file before the program actually exits
  455. or of cancelling the shutdown.
  456.  
  457.     IDM_UNDO
  458. The IDM_UNDO command is the result of the user choosing
  459. Undo from the Edit menu.  The MLE knows how to undo the
  460. last operation, so all that Edit has to do is send the MLE an
  461. MLM_UNDO message.  If the last action was undo, the undo is
  462. undone (i.e., redo).  The undone flag is set to TRUE to allow
  463. redo (whether undo is enabled is controlled by the action of
  464. the WM_INITMENU command processing, explained above).
  465.  
  466.     IDM_CUT, IDM_COPY, & IDM_DELETE
  467. The IDM_CUT, IDM_COPY, and IDM_DELETE messages are all
  468. handled similarly.  They are the result of the user choosing
  469. Cut, Copy, or Delete from the Edit menu.  Since the MLE
  470. "knows how" to do all of these operations, all that is necessary
  471. is to send the appropriate message (MLM_CUT, MLM_COPY, or
  472. MLM_CLEAR) to the MLE -- it does the rest.  As mentioned
  473. above, these commands would have been disabled during
  474. WM_INITMENU processing if the user has not previously
  475. selected any text.  The copy command places the selected text
  476. on the system clipboard; the delete command erases the
  477. selected text; the cut command does both (copys text to the
  478. clipboard, and then erases it).
  479.  
  480.  
  481.     IDM_PASTE
  482. The IDM_PASTE command results from the user choosing Paste
  483. from the Edit menu.  The MLE also "know how" to paste from
  484. the clipboard (all we have to do is send it an MLM_PASTE
  485. message).  Similar to the case with cut, copy, and delete, the
  486. Paste menu selection would have been disabled during the
  487. WM_INITMENU processing if there was no text on the
  488. clipboard.
  489.  
  490.     IDM_FIND
  491. The IDM_FIND command results from the user choosing Find
  492. from the Edit menu.  First, the FindDlgProc is invoked via a
  493. call to WinDlgBox -- if the user does not select Cancel,
  494. WinDlgBox returns DID_OK and the search begins.  Although
  495. the MLE "knows how" to search, a little more work is required:
  496. a structure must be filled with information before the
  497. MLM_SEARCH message is sent to the MLE.  If the target
  498. string is found, the MLE highlights it.  If the target is not
  499. found, the MLE does nothing visible, but it returns FALSE,
  500. which Edit uses to display a "not found" message box.
  501.  
  502.     IDM_REPLACE
  503. The IDM_REPLACE command results from the user choosing
  504. Replace from the Edit menu.  The processing is somewhat
  505. similar to Find, except that two strings are involved (a target
  506. and a replacement), and there is a loop to allow repeated
  507. find/replace cycles.  There is also options to replace either a
  508. single instance or all instances of the target with the
  509. replacement string.  The first time the ReplaceDlgProc is called
  510. (via WinDlgBox), a parameter passed to the dialog box
  511. procedure causes the Replace and Replace All buttons to be
  512. disabled.  For successive iterations, these buttons are enabled
  513. by altering the parameter mentioned earlier (i.e., setting "first"
  514. to FALSE).  If, after the target is found, the user then
  515. chooses replace, an MLM_INSERT message is used to replace
  516. the single instance; if the user chooses replace all,
  517. MLM_INSERT replaces the first instance (the one that was
  518. already found), and an MLM_SEARCH message with a parameter
  519. of MLFSEARCH_CHANGEALL is sent to find and replace the
  520. rest.
  521.  
  522.     IDM_GO
  523. The IDM_GO command results from the user choosing Go to
  524. line from the Edit menu.  The processing is more complicated
  525. than is should be because the MLE offers no way to go
  526. directly to a specific line number -- you must go through two
  527. steps: first convert the line number to an absolute character
  528. position, and then cause the MLE to display that character on
  529. the first line of the edit window.  Matters are complicated
  530. even further because if the user then depresses a cursor
  531. movement key, the MLE will "snap back" to where it was prior
  532. to repositioning.  The "trick" to avoiding this "snapping back"
  533. is to simulate a mouse button hit, which "locks" the MLE in its
  534. new position.
  535.  
  536.     IDM_HELPFORHELP, IDM_EXTENDEDHELP, IDM_KEYSHELP, & IDM_HELPINDEX
  537. The way that I handle processing of the help menu messages is
  538. contrary to the method recommended by IBM -- however, my
  539. method gives me greater flexibility and is somewhat simpler and
  540. more consistent.  In the resource file (EDIT.RC), IBM
  541. recommends that the help menu items (except for Help for
  542. help) generate predefined messages types by sending a
  543. WM_SYSCOMMAND message with a specific parameter (which is
  544. unlike other menus, and prevents the context sensitive help
  545. that is available for other menus).  Instead, I set up the help
  546. menu in the resource file in a manner that is consistent with
  547. other menus, and then send the WM_SYSCOMMAND messages
  548. from the client window procedure (for extended help, keys
  549. help, and the help index), which allows for context sensitive
  550. help within the help menu.  I also simplify the help table and
  551. help subtable by using the same constants for help as for the
  552. menus (e.g., HELPSUBITEM   IDM_FILE, IDM_FILE) -- this is
  553. much simpler than the recommended method (of relating the
  554. menu ID to some new ID), and causes no limitations or other
  555. problems.  Most of the effort in putting together on-line help
  556. involves the IPF file, which is discussed below.
  557.  
  558.     IDM_ABOUT
  559. The IDM_ABOUT command results from the user choosing
  560. About from the Help command.  To display the about box, we
  561. invoke the AboutDlgProc via WinDlgProc.
  562.  
  563. WM_ARGS & WM_CLEANFILE
  564. Edit also uses two so-called user messages -- these are messages
  565. defined by the application instead of by PM.  All messages are
  566. identified by a unique number; any message defined by the
  567. application should be numbered starting at WM_USER (a pre-defined
  568. constant guaranteed to be above the message numbers used by PM). 
  569. The first user message is WM_ARGS, which is actually sent from
  570. main (just before the message loop is entered) as a result of the
  571. user entering a filename on the command line when Edit is started. 
  572. When the client window procedure receives this message, it opens up
  573. a file and reads it into the edit window (of the MLE).  The other
  574. user message, WM_CLEANFILE, is sent whenever the editor is in a
  575. condition where it is safe to exit without saving the current edit
  576. window to a file.  This message also causes the MLE undo-state to
  577. be reset.  Edit sends itself the WM_CLEANFILE message whenever a
  578. new file is read, and whenever the edit window is saved to a file
  579. (these operations cannot be undone, but no need to save before
  580. exit).
  581.  
  582. HM_ERROR & HM_QUERY_KEYS_HELP
  583. The application receives two messages related directly to the help
  584. manager; these are HM_ERROR (which results in the display of a message
  585. box informing the user that help is not available), and
  586. HM_QUERY_KEYS_HELP (which is merely passed back to PM for
  587. processing).
  588.  
  589. WM_DESTROY
  590. After the user has terminated the application, PM sends a WM_DESTROY
  591. message -- the only thing left to be done by Edit is to destroy the MLE
  592. (which was created during the WM_CREATE message).
  593.  
  594.  
  595. The Information Presentation Facility (IPF)
  596.  
  597.     IPF is the on-line help facility that was introduced with OS/2 v1.2, and
  598. replaced and/or supplemented help hooks (depending upon your point of view -
  599. - although applications programmers no longer work with help hooks, IPF still
  600. uses them internally).  IPF is quite flexible and powerful (but has not been
  601. well documented, except for a short and incomplete section in Volume 4 of the
  602. Microsoft OS/2 Programmer's Reference and a few articles in the IBM Personal
  603. Systems Developer).  IPF allows for context sensitive help, a two-level index,
  604. a table-of-contents, hypertext links, multiple viewports, and graphics (including
  605. animation).  This article (and accompanying code) illustrates the basic features,
  606. omitting viewports, graphics, and certain types of links.
  607.  
  608.     Implementing IPF requires minor changes to the C source (discussed above
  609. under messages), additions to the resource script (HELPTABLE and
  610. HELPSUBTABLE(s)), and creation of the IPF help file (source help text).  A
  611. special compiler (IPFC) is required to convert the IPF file to a HLP file.
  612.  
  613.     IPF help files are mainly plain text (which is what the user reads when
  614. requesting help); however, IPF uses a tag language to describe certain features
  615. of the help system.  The IPF source file is compiled by IPFC (the IPF
  616. Compiler) to produce the HLP files that are used at run time.  I have managed
  617. to "figure out" enough of the help system to "get by" based upon scant
  618. examples and write-ups in the sources mentioned above.  I have had to "fill
  619. in" a few holes by guess and experimentation.  Not a very satisfactory state of
  620. affairs -- however, in the end I am able to generate fairly good help files. 
  621. The IPF tag language uses rather verbose multi-character symbols to delimit
  622. the various sections of the help source file.  Some of these tags are explained
  623. below.
  624.  
  625.     The IPF source code is divided into help panels.  Each panel has a
  626. number (which is used by the Help Manager to find the correct panel when
  627. the user requests help, and may also be used as a reference for hypertext
  628. links within IPF) and a title (which is displayed in the help window title bar). 
  629. If the help panel is for a menu item, its reference number must match the
  630. IDM_????? constant defined in the applications header file.  Each help panel
  631. can be divided into paragraphs (:p. tag); within each paragraph, word wrap is
  632. automatic.  If you wish to disable word wrap, it is possible to set up simple
  633. lists (:sl. :li. :esl. tags) of items (which are double spaced unless they are
  634. declared compact).
  635.  
  636.     One of the most powerful features of IPF is the hypertext links.  Links
  637. are set up with a tag statement as follows:
  638.     :link.res=### reftype=hd.?????:elink.
  639. The ### refers to a numbered help panel; the ????? is the text that is
  640. displayed as the hypertext link (shown in contrasting colour -- usually light
  641. green).  If the user double-clicks on the link, the referenced help panel is
  642. displayed.  The Edit program uses hypertext links only in compact lists, but
  643. they can be embedded anywhere in the help text.  Another type of line
  644. (reftype=inform) sends an HM_INFORM message to the application; that type is
  645. not illustrated here.
  646.  
  647.     To set up an index of available help topics, the second line of each help
  648. panel must be one of the following:
  649.     :i1 id=xx.?????
  650.     :i2 refid=xx.??????
  651. The :i1 is used for top level index entries, :i2 for sub-entries (the index is
  652. only two level).  In the case of the =xx., the xx can be any word (sequence of
  653. letters), which specifies which second level index entries go with which first
  654. level entry (there must be only one first level entry for each xx value.  The
  655. ????? can be anything, and is what appears in the index.  Incidently, index
  656. entries are also hypertext links (i.e., double-click on them to display the
  657. associated help panel).  You do not have to add anything additional to also get
  658. a table of contents (IPF does that for you).
  659.  
  660.     To highlight a word or phrase, there are 10 styles of text available:
  661. highlighted phrases 1 through 9, plus the default (which is dark blue).  Here is
  662. an example of a highlighted phrase:
  663.     :hp8.This would be red text!:ehp8.
  664.  
  665. The following list indicates what the highlighted phrases mean:
  666.     hp1 = italics
  667.     hp2 = bold
  668.     hp3 = italics+bold
  669.     hp4 = light blue
  670.     hp5 = underlined
  671.     hp6 = italics+underlined
  672.     hp7 = bold+underlined
  673.     hp8 = red
  674.     hp9 = magenta
  675.  
  676.     The Edit help system uses (or perhaps over uses) a variety of highlighted
  677. phrases for emphasis of certain words and phrases.
  678.  
  679.     Another feature of IPF (not illustrated here) is the ability to give
  680. context sensitive help in dialog boxes (Edit doesn't have any complicated
  681. dialog boxes where such help would be warranted).  Adding dialog box help is
  682. quite simple and requires the following steps: 1) add a help button with button
  683. styles BS_NOPOINTERFOCUS and BS_HELP; 2) add an entry to the
  684. HELPTABLE in the resource file for each dialog box; 3) add a HELPSUBTABLE
  685. for each dialog box with an entry for each element of the dialog box, keyed
  686. by ID; 4) add a panel to the IPF file for each element of the dialog box,
  687. again, keyed by the ID of the element.  When the user selects the help button
  688. (or depresses the F1 key), help will be provided for whatever element of the
  689. dialog box currently has focus (although it is also possible to have the same
  690. help displayed regardless of which element has focus by using the same help
  691. ID -- the second constant in a help subtable item -- for all IDs in the dialog
  692. box).
  693.  
  694.  
  695. Conclusion
  696.  
  697.     Many people have lamented the long, steep learning curve that must be
  698. climbed to become productive programming in a GUI environment such as OS/2
  699. Presentation Manager.  This project illustrates that the powerful features
  700. provided by the GUI can make an otherwise very challenging project almost a
  701. snap.  Without the MLE (Multi Line Edit predefined control window), the
  702. editor presented here would have taken considerably longer to develop, and
  703. would have resulted in a considerably greater number of total lines of code.  I
  704. submit that once mastered, a GUI such as the OS/2 Presentation Manager
  705. actually makes a programmer more productive and results in more functional
  706. and useful applications.  These advantages far outweigh the disadvantage of
  707. any initial difficulty in learning the GUI paradigm.
  708.  
  709.  
  710. *   *   *
  711.  
  712.