Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

The Mewel Window System


MAR91: THE MEWEL WINDOW SYSTEM

Al is a DDJ contributing editor and can be contacted at 501 Galveston Drive, Redwood City, CA 94063.


The Mewel 3 Window System from Magma Software Systems is a function library that resembles the Microsoft Windows 3.0 SDK (Software Development Kit) API and implements a subset of the SDK functions in a text-mode DOS environment. Mewel addresses a number of issues faced by programmers today, and its viability as a programmer's tool is reflected in the way it addresses those issues.

A programmer who is writing text-based DOS applications might choose Mewel for any of several reasons. First, like other such products, Mewel is a text-based library that supports windows, menus, data entry templates, and mouse input. As such, it competes with other C function libraries that support video windows on the PC. The one you choose will depend on the style you prefer for your user interface.

A second reason to use Mewel is that it is an implementation of the IBM Systems Application Architecture (SAA) Common User Access (CUA) standard. This is the user interface model that OS/2 Presentation Manager, Windows 3.0, and many applications use. Whether or not you like the CUA approach, the PC industry is moving toward it, and many users will come to expect it. Mewel is the closest thing to an SAA-compliant text-based window package that I have seen.

A third reason for using Mewel is that it is a text-based subset of the Windows SDK API. This opens several possibilities. Programmers can use Mewel to port their Windows applications to DOS with a minimum of fuss, provided that the applications are not heavily dependent on Windows memory management and multitasking. You can design new applications to compile under both APIs and increase your potential market. You can use Mewel to prototype Windows applications. But a hidden strength of Mewel's near-compatibility with Windows is that it provides a stepping stone for DOS programmers to learn Windows programming, a stepping stone that does not require the programmer to purchase the Windows SDK, to develop under Windows, or even to run Windows.

The Windows API goes far beyond the user interface functions of CUA. The Windows operating environment encompasses a complex memory management system and supports a multi-tasking environment with intertask communication through dynamic data exchange. The API is huge and overwhelming. The biggest books on the computer bookshelves are about Windows programming. No wonder programmers are intimidated. Windows programming has become another subculture of esoteric folklore that makes outsiders wince when they see the volume of knowledge required to gain entrance. Many GUI opponents are programmers who are unsure of their ability or willingness to learn the APIs. Mewel offers an opening. Its API is a small subset of the Windows API, appears manageable on the surface, and has the advantage that you do not need a Windows development system. Software development with the Windows SDK is inhospitable at best. The Microsoft C compiler and resource compiler do not themselves run under Windows, and the CodeView debugger requires a monochrome monitor in addition to the graphics monitor used by Windows itself. All you need to develop with Mewel is a DOS system and a C compiler.

Event-Driven Programming

You will hear that the program development environments for Windows and Mewel are object oriented. The Mewel documentation itself implies that it supports an object-oriented programming environment. This is not entirely true. The Windows and Mewel APIs have some things in common with OOP. Windows are classes of a kind. You can derive window classes and subclass them. You send messages to windows to make them react and behave. But the similarities end there. There is no encapsulation of objects and no inherent polymorphism. You do not instantiate a window class by declaring an instance of an object. There are products that surround the Windows API with object-oriented development environments, but the Windows API itself is not OOP. You could similarly surround the Mewel API with C++ classes, but the Mewel API itself is not OOP. What these APIs support is something else -- something called "event-driven" programming.

In event-driven programming, the functions of the program execute as the result of events. The main body of the program waits in a loop for an event to occur. When the event takes place, the program sends it to a dispatcher function that calls whatever functions should deal with the particular event. Events in the Mewel API generate messages that are sent to windows. Before any messages can occur, the program must create at least one window. Then the program goes into the loop that senses events and dispatches messages to windows.

There are two kinds of messages, those sent as the result of user events, and those sent by the program itself. Users can do three things -- type keys, move the mouse, and click the mouse. The user events are sent as messages to the window that the program has created. When there are several windows on the screen, only one of them "has the focus," and that window is the one that receives the user-generated messages. The program-generated messages can be anything at all, and the program can send the messages to specific windows regardless of which window has the focus. That is how the Windows/Mewel API works in a nut-shell, and it is the foundation of the mysteries of Windows programming.

Of course there is much more than that to Mewel and Windows programming. The API has its own classes of windows for menus, documents, scroll bars, frames, titles, minimize/maximize boxes, control menus, window hierarchies, and dialog boxes. Dialog boxes themselves contain control windows that include radio buttons, pushbuttons, text boxes, edit boxes, list boxes, and drop-down list boxes. All these things have their own sets of messages and functions. When you integrate the canned messages and functions into your application's messages, functions, and windows, you hopefully have a well-ordered application.

The size and scope of the Windows API scares programmers. The number of source code lines required for even the simplest program fuels criticism. The yet-another paradigm of event-driven programming makes programmers ask, "What next?" These are obstacles that the new Windows/Mewel programmer must overcome. They are intimidating at first, but do not be put off; you can learn them quicker than you think. To paraphrase P.J. Plauger: If you were not a smart person, you wouldn't be reading this magazine.

Installation

The Mewel installation program is an INSTALL.BAT file that begins by telling you that if you are not installing from A: to C:\Mewel you must terminate the process and modify the INSTALL.BAT file. That is not an unreasonable thing to ask a programmer to do. The problem, however, is that the message scrolls away before you can read it. Apparently, the Magma programmers do not know how to write a DOS batch file that uses ECHO instead of REM to display a full-screen message. It is hard to believe that they ran the batch file on an 80-column screen before releasing it. That nuisance aside, the installation is simple enough. It creates the Mewel subdirectory and de-archives the files from the diskette to the hard disk. That's all there is to the installation. It finishes with another unreadable REM screen that tells you to read the text files that contain changes to the documentation.

If you install an upgrade to a previous Mewel installation, you will bump into another minor annoyance. Magma uses the LHARC.EXE archive program. To replace existing files, LHARC makes you verify the replacement of every file.

As packaged, you will need either Microsoft C or Turbo C++ to use Mewel. The Mewel installation procedure copies the Mewel header and library files into its own subdirectory. To get your compiler and linker to find the Mewel files, you will need to modify Microsoft's INCLUDE and LIB environment variables or the Turbo C++ TCCONFIG.TC and TURBOC.CFG files.

The Turbo C++ version is not a C++ product but instead uses the C compiler component of Turbo C++. If you prefer to use Turbo C 2.0, you may purchase the Mewel source code and recompile the libraries. Optionally, you may request the Turbo C 2.0 version of the libraries from Magma Systems. (Zortech C++ and JPI TopSpeed C versions are also available on request.) Finally, note that the Mewel libraries support the medium and large memory models for each compiler.

The Documentation

Documentation is the weakest part of the Mewel package. It is incomplete and contains many typographical, grammatical, and technical errors. Magma says that a new document is coming, one that will remedy the deficiencies of the existing manual. Until then you will need two of the three books from the Windows SDK to supplement the Mewel document. That does not mean that you need the SDK itself. Microsoft Press publishes the three-volume SDK documents separately, and you can find them in most book stores. You will need the SDK Programmer's Reference and the Guide to Programming. This increases the cost of Mewel by $70, the combined price of the two books.

As an example of the kind of problem I had with the Mewel documentation, consider this: The manual describes the EM_GETHANDLE message that returns the address of an edit control window's buffer. It does not, however, mention the corresponding EM_SETHANDLE message that changes the edit control window's buffer. I needed that message for the example program that accompanies this article, and I located it in the SDK books. There was a catch, however. The Mewel implementation of EM_GETHANDLE is different from that of Windows. Windows programs use handles to manage memory allocations. Mewel uses the standard C memory allocation functions, which use pointers rather than handles. Windows handles are 16-bit values, while the far pointers of the Mewel medium and large memory models are 32-bit values. As a result, the Mewel implementation uses a different parameter convention for sending the EM_SETHANDLE message. Because the Mewel documentation does not describe EM_SETHANDLE and because the Windows SDK documentation describes the Windows convention, the Mewel programmer is in the dark about how to send EM_SETHANDLE. This is not an isolated incident. Deficiencies and errors of this kind permeate the Mewel documentation.

The Source Code

How did I solve the EM_SETHANDLE problem? My copy of Mewel includes source code, and I used a GREP utility to find the EM_SETHANDLE treatment. I learned from the code what parameters were expected and how they were used. The source code is available for an extra $300. Inasmuch as a programmer needs the source code to find out how some of the functions and messages really work, Magma should include it at no cost -- at least until they provide adequate documentation.

There are several other reasons why a programmer would want the source code of a function library. You just saw that I needed it to solve a problem about how a feature worked. You shouldn't have to pay extra for that. You would also need source code to port Mewel to a compiler not supported by the original distribution. The code contains compile-time conditional statements that refer to Unix, Zortech, and others, so obviously someone has considered the problem. Certainly you'll need the source code if you want to modify the package. But beware that when you make custom modifications to commercial function libraries, you'll need to retrofit your improvements every time the vendor sends an upgrade.

Source code can be a security blanket. When you use a package such as Mewel, you invest a lot of time in the use of an API that might have only one source. If you absolutely need a modification that the vendor does not want to support and you have the source code, you have a way to get what you need. Besides, if the vendor goes out of business or drops the product, you are covered.

If you are thinking about buying the source code to learn about good C code, think again. The code is often difficult to read and makes liberal use of the goto statement in highly unstructured ways. In defense of the code, the Magma programmer contends that there are times when a programmer simply must use a goto. I've never run across such times.

Support

When you call Magma, the programmer who wrote Mewel answers the phone to answer your questions. He assured me that I was not getting special treatment just because I am writing about his product. I cannot imagine Magma being able to maintain that level of support, if the program gains the popularity it deserves. But while they can, it's the best support in the business.

I ran into a number of bugs in the program. The multiple document interface feature is new, and has not been thoroughly tested. After discussing the problems with Magma, I got an upgrade. They fixed most of the problems and inadvertently added some new ones, which they will fix in the next upgrade. The company is responsive and wants the product to be as correct as possible. When you phone to report bugs, you often hear a groan and a perceptible forehead slap as if the person on the other end could'a had a V-8.

Portability with Windows

A Mewel program can port to Windows, but only with some effort on your part. If you plan the program to be portable and understand the portability issues, you can minimize the scope of changes needed. It is possible to write a program that has compile-time conditional statements that manage the differences. Mewel includes macros and functions that constitute what they call "the Microsoft Windows Porting Layer." Part of this layer is available only when you buy the source code.

The MEMOPAD Program

I used Mewel to build MEMOPAD, a multiple document notepad program similar to the MULTIPAD example program that comes with the Windows SDK. MEMOPAD has fewer features than MULTIPAD, but it illustrates the use of Mewel in a multiple document application. Windows programmers will readily see the close resemblance to Windows programs.

Microsoft introduced the Multiple Document Interface in Windows 3.0. It provides for an application parent window to have multiple document child windows. The parent window has the application's menu, and the document windows have the data. The user decides how many documents to open. The MEMOPAD program uses text files as documents. You can have several different text files open at one time.

Mewel does not work correctly if a document window has or is derived from a control window class -- a list box, an edit box, and so on. You must declare a window of the class you want and make it a child of the document window. This becomes a visual problem if the window in question will have a border or scroll bars. These controls appear inside the border of the document window. A frame inside a frame is unattractive. A child edit window without a frame can occupy the entire client area of the document window, but if it has scroll bars, they appear without a frame inside the document window's frame. The MEMOPAD program works this way. Such a window configuration seems to be nonstandard. I have not used the Windows SDK Multiple Document Interface, so I do not know whether it has the same problem.

Listing One is memopad.h, which defines all the identifiers for windows, menu commands, controls, and strings. The program source file and the resource file both include this header file to associate the values of the identifiers with the resources and the code that use them.

Listing Two is memopad.rc, the resource file for the application. It defines the MEMOPAD menu and the text string values. Mewel includes a resource compiler similar to the one that comes with the Windows SDK. The resource compiler compiles the resource text file into the resource binary file that the runtime system uses. The text file has the .RC extension and the binary file has the .RES extension. A program can load the .RES file at runtime, or the resource compiler can write it into the program's .EXE file.

Using resource files separates the content and format of menus, dialog boxes, and strings from the program's source code. This practice makes it possible to modify resources without recompiling the program. In a Mewel application you can code the ASCII values of strings in the resource file and associate them with an identifier that the program refers to. This practice facilitates the development of applications where some values are customized for different user environments, perhaps for foreign language translations.

Listing Three is memopad.c. This is the MEMOPAD application code. It looks very much like a Windows program. It registers the frame and document window classes, creates the frame and MDI client windows, and enters the message sensing and dispatching loop. The FrameWndProc function receives and processes the messages that result when the user selects menu commands. The functions that it calls open empty notepad windows, load selected files into notepad windows, save the notepad contents to files, and print the contents of edit buffers. The program is simple, intended mainly to illustrate the use of Mewel and how it resembles the Windows SDK.

The File Open Dialog Box

The MEMOPAD program calls a function named DlgOpenFile to allow the user to select a file to load or to name a file to be saved. Mewel includes such a function, but because it does not look very much like the Windows 3.0 file open dialog box and because I wanted to illustrate how dialog boxes work, I redesigned the dialog box and rewrote the function. Listing Four is fileopen.h, the header file that defines the identifiers. Listing Five is fileopen.dlg, the text definition of the dialog box contents and format. The memopad.rc file includes this .DLG file. By maintaining separate .DLG files and including them in each program's .RC file, you can share common dialog boxes across applications.

Listing Five is fileopen.c, the program that implements the File Open dialog box. It displays the dialog box and allows the user to navigate the disk system to locate a file. When the user selects one, the function logs onto the disk drive where the file exists, changes to its subdirectory, and copies the selected filename into the calling function's memory as pointed to by the Fname argument.

MEMOPAD compiles to a 180K .EXE file with Turbo C++ 1.0. This is a big executable module for such a small program. Obviously, the CUA library is a big one. You will probably not use Mewel to develop TSR and other programs where memory requirements are tight.

Mewel includes a dialog box editor program that purports to be similar to the one included in the Windows SDK tools. The program is, however, mostly unusable. It does not allow you to move or resize the dialog box window, does not compile to .DLG format (.RES or C code only), and its composition tools are difficult to use. Although the manual identifies the program and represents it as a real tool, Magma says that it is only an example and that a more useful one is under development.

Performance

The downside of MEWEL is its performance. Your CUA programs are not going to be snappy on the slower processors. For example, if you run the MEMOPAD program on an 8-MHz AT, open five document windows, and select the Close All command on the Window menu, it takes MEWEL ten seconds to close all five windows. Other window display and swapping functions are similarly slow. Unless you intend to run your programs on fast machines, you might find MEWEL to be too sluggish. Windows 3.0 is no screamer on the 8-MHz machines, either, but it is somewhat faster than MEWEL. You would not expect a graphics window manager to outrun a text-based one, particularly when the APIs and design philosophies are the same.

The License

When I first saw Mewel, I was puzzled by a clause in the licensing agreement that prohibits users of the library from using it to develop text editor or word processing programs. It turns out that Magma markets a text editor and a word processor in addition to Mewel. They firmly assert that they do not want others using the results of Magma's hard work -- meaning Mewel -- to go into competition with Magma in the applications arena. It is understandable that they do not allow you to use Mewel to develop a video window product to compete with Mewel itself. But to prevent you from developing specific applications is unfair. They are commercially marketing the results of their so-called hard work and accepting your hard-earned money for it. Restricting your use of it in such a way is the same as if Borland and Microsoft were to prevent you from using their C compilers to develop word processors, spreadsheets, and desktop utilities.

Conclusion

Mewel is a relatively new product that was developed and is supported by a small company and which is competing in a marketplace dominated by better financed, better operated, and more experienced companies. Yet Mewel is unique. At the time I'm writing this, I know of no other DOS text-mode library that combines windows and mouse support, SAA-compliance, and the Windows SDK API. Until someone comes along with a competing library, Mewel is alone in this particular market. Despite the documentation problems, the restrictive clause in the license agreement, and the occasional bug, I can recommend this library to programmers who want SAA-compliant DOS programs, a bridge between Windows and DOS, or an easier road to learning Windows programming.

Products Mentioned

Mewel 3 Window System Magma Software Systems 15 Bodwell Terrace Millburn, NJ 07041 201-912-0192 System requirements: Microsoft C 5.1 or later, or Turbo C++, Turbo C 2.0, Zortech C++, and JPI TopSeed C versions available on request. $295 for MSC and TC++ libraries $595 for libraries with source code


_THE MEWEL WINDOW SYSTEM_
by Al Stevens



[LISTING ONE]
<a name="00ad_0011">

/* ------------ memopad.h ------------- */

/* -------- window identifiers ----------- */
#define ID_MAIN                1
#define ID_MDICLIENT           2
#define ID_EDITOR              3
#define ID_FIRSTEDITOR       100

/* ------- menu command identifiers -------- */
#define ID_NEWFILE             5
#define ID_OPENFILE            6
#define ID_SAVE                7
#define ID_SAVEAS              8
#define ID_PRINT               9
#define ID_EXIT               10

#define IDM_WINDOWTILE        12
#define IDM_WINDOWCASCADE     13
#define IDM_WINDOWICONS       14
#define IDM_WINDOWCLOSEALL    15

#define ID_HELP               99

/* -------- string identifiers --------- */
#define IDS_TITLE              0
#define IDS_HELP               1
#define IDS_ERROR              2
#define IDS_OVERWRITE          3
#define IDS_WRITEERROR         4
#define IDS_SELECTERROR        5
#define IDS_NOFILE             6
#define IDS_FORMFEED           7
#define IDS_UNTITLED           8






<a name="00ad_0012">
<a name="00ad_0013">
[LISTING TWO]
<a name="00ad_0013">

#include <style.h>
#include "memopad.h"

#include "fileopen.dlg"

MPmenu MENU
BEGIN
    POPUP "~File"
    BEGIN
        MENUITEM "~New",           ID_NEWFILE       SHADOW
        MENUITEM "~Open...",       ID_OPENFILE
        MENUITEM "~Save",          ID_SAVE
        MENUITEM "Save ~As...",    ID_SAVEAS
        MENUITEM SEPARATOR
        MENUITEM "~Print",         ID_PRINT
        MENUITEM SEPARATOR
        MENUITEM "E~xit",          ID_EXIT
    END
    POPUP "~Window"
    BEGIN
        MENUITEM "~Tile",          IDM_WINDOWTILE    SHADOW
        MENUITEM "~Cascade",       IDM_WINDOWCASCADE
        MENUITEM "Arrange ~Icons", IDM_WINDOWICONS
        MENUITEM "Close ~All",     IDM_WINDOWCLOSEALL
    END
    MENUITEM "~Help", ID_HELP HELP
END

STRINGTABLE
BEGIN
    IDS_TITLE,         "MemoPad"
    IDS_HELP,
"MemoPad is a multiple document\
memo processor. You can have\
several *.PAD documents open at\
one time. It demonstrates the\
MDI document feature of MEWEL."
    IDS_ERROR,         "Error!"
    IDS_OVERWRITE,     "Overwrite Existing File?"
    IDS_WRITEERROR,    "Cannot write file"
    IDS_SELECTERROR,   "Select an open document first"
    IDS_NOFILE,        "No such file"
    IDS_FORMFEED,      "Send a Form Feed?"
    IDS_UNTITLED,      "Untitled"
END







<a name="00ad_0014">
<a name="00ad_0015">
[LISTING THREE]
<a name="00ad_0015">

/* ------------ memopad.c -------------- */

#include <stdio.h>
#include <stdlib.h>
#include <window.h>
#include <string.h>
#include <sys\stat.h>
#include <io.h>
#include "memopad.h"

long FAR PASCAL FrameWndProc(HWND, WORD, WORD, DWORD);
long FAR PASCAL EditorProc(HWND, WORD, WORD, DWORD);
void NewFile(void);
void SelectFile(void);
void SaveFile(BOOL);
void PrintPad(void);
void OpenWindow(char *);
void LoadFile(HWND, char *, int);
void BuildEditor(HWND);
HWND GetEditorHandle(void);
int ErrorMessage(int);

char EditorClass[] = "Editor";
char FrameClass[] = "FrameClass";
char Untitled[26];

HWND hClient;
HWND hEditor;
HWND hFrame;

int hInstance;

void main(void)
{
    MSG event;
    WNDCLASS wndclass;
    CLIENTCREATESTRUCT ccs;
    char Title[26];

    WinInit();
    WinUseSysColors(NULLHWND, TRUE);
    MDIInitialize();

    /* Register the Editor Document Window */
    memset (&wndclass, 0, sizeof (wndclass));
    wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
    wndclass.lpfnWndProc   = EditorProc ;
    wndclass.lpszMenuName  = NULL;
    wndclass.lpszClassName = EditorClass;
    if (!RegisterClass (&wndclass))
        exit(1);

    /* Register the Frame Window */
    memset (&wndclass, 0, sizeof (wndclass));
    wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
    wndclass.lpfnWndProc   = FrameWndProc ;
    wndclass.lpszMenuName  = "MPMenu";
    wndclass.lpszClassName = FrameClass;
    if (!RegisterClass (&wndclass))
        exit(1);

    /* Open the Resource File */
    hInstance = OpenResourceFile("MEMOPAD");
    LoadString(hInstance, IDS_UNTITLED, Untitled, 25);

    /* Create the frame window */
    LoadString(hInstance, IDS_TITLE, Title, 25);
    hFrame = CreateWindow(
                         FrameClass,
                         Title,
                         WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN
                            | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
                         CW_USEDEFAULT,
                         CW_USEDEFAULT,
                         CW_USEDEFAULT,
                         CW_USEDEFAULT,
                         SYSTEM_COLOR,
                         ID_MAIN,
                         NULLHWND,
                         NULLHWND,
                         hInstance,
                         (LPSTR) NULL
                       );

    /* display the frame window */
    ShowWindow(hFrame, SW_SHOW);

    /* create the MDI client window */
    ccs.hWindowMenu = GetSubMenu(GetMenu(hFrame), 1);
    ccs.idFirstChild = ID_FIRSTEDITOR;

    hClient = CreateWindow("mdiclient",
                                  NULL,
                                  WS_CHILD | WS_CLIPCHILDREN |
                                        WS_CLIPSIBLINGS,
                                  0,0,0,0,
                                  SYSTEM_COLOR,
                                  ID_MDICLIENT,
                                  hFrame,
                                  NULL,
                                  hInstance,
                                  (LPSTR) &ccs);
    ShowWindow(hClient, SW_SHOW);

    /* set focus for keyboard users */
    SetFocus(hFrame);

    /* message loop */
    while (GetMessage(&event, NULLHWND, 0, 0))    {
        TranslateMessage(&event);
        DispatchMessage(&event);
    }

    CloseResourceFile(hInstance);
    exit(0);
}

/* wndproc for the frame window */
long FAR PASCAL FrameWndProc(HWND hWnd, WORD message,
                                WORD wParam, DWORD lParam)
{
    HWND hwndCheck;
    char Hmsg[501];

    switch (message)    {
        case WM_HELP:
            LoadString(hInstance, IDS_HELP, Hmsg, 500);
            MessageBox(hFrame, Hmsg, NULL, MB_OK);
            break;
        case WM_COMMAND:
            switch (wParam)    {
                case ID_NEWFILE:
                    NewFile();
                    break;
                case ID_OPENFILE:
                    SelectFile();
                    break;
                case ID_SAVE:
                    SaveFile(FALSE);
                    break;
                case ID_SAVEAS:
                    SaveFile(TRUE);
                    break;
                case ID_PRINT:
                    PrintPad();
                    break;
                case ID_EXIT:
                    PostQuitMessage(0);
                    break;
                case IDM_WINDOWTILE:
                    SendMessage(hClient,WM_MDITILE,0,0);
                    break;
                case IDM_WINDOWCASCADE:
                    SendMessage(hClient,WM_MDICASCADE,0,0);
                    break;
                case IDM_WINDOWICONS:
                    SendMessage(hClient,WM_MDIICONARRANGE,0,0);
                    break;
                case IDM_WINDOWCLOSEALL:
                    while ((hwndCheck =
                            GetWindow(hClient, GW_CHILD))
                                != NULLHWND)
                        SendMessage(hClient, WM_MDIDESTROY,
                            hwndCheck, 0);
                    break;
                default:
                    break;
            }
            break;
        default:
            break;
    }
    return DefFrameProc(hWnd,hClient,message,wParam,lParam);
}

/* The New command. Open an empty editor window */
void NewFile(void)
{
    OpenWindow(Untitled);
}

/* The Open... command. Select a file   */
void SelectFile(void)
{
    char FileName[64];
    if (DlgOpenFile(hFrame, "*.PAD", FileName))    {
        HWND hWnd, hEditor;
        /* test to see if the document is already open */
        if ((hWnd = FindWindow(EditorClass, FileName))
                                         != NULLHWND)    {
            /* document is open, activate its window */
            BringWindowToTop(hWnd);
            OpenIcon(hWnd);
            hEditor = GetTopWindow(hWnd);
            SetFocus(hEditor);
        }
        else
            OpenWindow(FileName);
    }
}

/* get the current active editor window handle  */
HWND GetEditorHandle(void)
{
    HWND hCurEd;

    hCurEd = GetFocus();
    if (!IsChild(hClient, GetParent(hCurEd)))
        hCurEd = NULLHWND;
    return hCurEd;
}

/* Save the notepad file  */
void SaveFile(BOOL SaveAs)
{
    char FileName[64];
    HWND hCurEd, hEditor;
    char *text;
    FILE *fp;

    /*  get the handle of the active notepad editor window */
    if ((hCurEd = GetEditorHandle()) != NULLHWND)    {
        hEditor = GetParent(hCurEd);
        /* --- get the editor window's file name --- */
        GetWindowText(hEditor, FileName, 64);
        /* get a name for untitled window or Save As command */
        if (SaveAs || strcmp(FileName, Untitled) == 0)    {
            if (!DlgOpenFile(hFrame, "*.PAD", FileName))
                return;
            if (access(FileName, 0) == 0)    {
                char omsg[81];
                LoadString(hInstance, IDS_OVERWRITE, omsg, 80);
                if (MessageBox(hFrame, omsg,
                            NULL, MB_YESNO) == IDNO)
                    return;
            }
            SetWindowText(hEditor, FileName);
        }
        /* - get the address of the editor text - */
        text = (char *) SendMessage(hCurEd, EM_GETHANDLE,0,0);
        if ((fp = fopen(FileName, "wt")) != NULL)    {
            fwrite(text, strlen(text), 1, fp);
            fclose(fp);
        }
        else
            ErrorMessage(IDS_WRITEERROR);
    }
    else
        ErrorMessage(IDS_SELECTERROR);
}

/* open a document window and load a file  */
void OpenWindow(char *FileName)
{
    MDICREATESTRUCT mcs;
    HWND hWnd, hEditor;
    struct stat sb;

    if (strcmp(FileName, Untitled) && stat(FileName, &sb))    {
        ErrorMessage(IDS_NOFILE);
        return;
    }

    mcs.szTitle = FileName;
    mcs.szClass = EditorClass;
    mcs.hOwner = hInstance;
    mcs.lParam = NULL;
    mcs.x = mcs.y = mcs.cy = mcs.cx = CW_USEDEFAULT;
    mcs.style = WS_CLIPCHILDREN;

    /* tell the client window to create the document window */
    hWnd = SendMessage(hClient, WM_MDICREATE, 0,
                        (LONG) (LPMDICREATESTRUCT) &mcs);

    hEditor = GetTopWindow(hWnd);
    SetFocus(hEditor);

    if (strcmp(FileName, Untitled))
        LoadFile(hEditor, FileName, (int) sb.st_size);
}

/* wndproc for the editor window  */
long FAR PASCAL EditorProc(HWND hWnd, WORD message,
                                WORD wParam, DWORD lParam)
{
    RECT rc;
    int rtn;

    switch (message)    {
        case WM_SIZE:
            /* Resize the edit control box. */
            GetClientRect (hWnd, &rc);

            WinSetSize(GetTopWindow(hWnd),
                rc.bottom-rc.top+1,
                rc.right-rc.left+1);
            break;
        case WM_SETFOCUS:    {
            rtn = DefMDIChildProc(hWnd,message,wParam,lParam);
            /* Set the focus on the editor window */
            SetFocus(GetTopWindow(hWnd));
            return rtn;
        }
        case WM_CREATE:
            /* create the file window's editor box */
            BuildEditor(hWnd);
            break;
        default:
            break;
    }
    return DefMDIChildProc(hWnd, message, wParam, lParam);
}

/* Create the editor window  */
void BuildEditor(HWND hWnd)
{
    CreateWindow(
            "edit",
            NULL,
            WS_CHILD | WS_CLIPCHILDREN | WS_VSCROLL |
                ES_MULTILINE | ES_AUTOVSCROLL,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            SYSTEM_COLOR,
            ID_EDITOR,
            hWnd,
            NULLHWND,
            hInstance,
            (LPSTR) NULL);
}

/* Load the notepad file into the editor text buffer  */
void LoadFile(HWND hEditor, char *FileName, int tLen)
{
    int bfsize;
    char *Buf;
    FILE *fp;

    Buf = (char *)
        SendMessage(hEditor, EM_GETHANDLE, 0, (long) &bfsize);
    if (bfsize < tLen+1)    {
        Buf = LocalReAlloc(Buf, tLen+1, 0);
        SendMessage(hEditor, EM_SETHANDLE, tLen+1, (long) Buf);
    }
    if (Buf != NULL)    {
        if ((fp = fopen(FileName, "rt")) != NULL)    {
            memset (Buf, 0, tLen+1);
            fread(Buf, tLen, 1, fp);
            SendMessage(hEditor, WM_SETTEXT, 0, (long) Buf);
            fclose(fp);
        }
    }
}

/* print the current notepad  */
void PrintPad(void)
{
    char FileName[64];
    HWND hCurEd;

    if ((hCurEd = GetEditorHandle()) != NULLHWND)    {
        char *text;
        char msg[81];
        HWND hEditor = GetParent(hCurEd);
        /* --- get the editor window's file name --- */
        GetWindowText(hEditor, FileName, 64);

        /* ---------- print the file name ---------- */
        fputs("\r\n", stdprn);
        fputs(FileName, stdprn);
        fputs(":\r\n\n", stdprn);

        /* ---- get the address of the editor text ----- */
        text = (char *) SendMessage(hCurEd, EM_GETHANDLE,0,0);

        /* ------- print the notepad text --------- */
        while (*text)    {
            if (*text == '\n')
                fputc('\r', stdprn);
            fputc(*text++, stdprn);
        }

        /* ------- follow with a form feed? --------- */
        LoadString(hInstance, IDS_FORMFEED, msg, 80);
        if (MessageBox(hFrame, msg, NULL, MB_YESNO) == IDYES)
            fputc('\f', stdprn);
    }
    else
        ErrorMessage(IDS_SELECTERROR);
}

/* Error message handler  */
int ErrorMessage(int ErrorNumber)
{
    char ErrorMsg[81];
    char Error[26];

    LoadString(hInstance, ErrorNumber, ErrorMsg, 80);
    LoadString(hInstance, IDS_ERROR, Error, 26);
    MessageBeep(0);
    return MessageBox(hFrame, ErrorMsg, Error, MB_OK);
}





<a name="00ad_0016">
<a name="00ad_0017">
[LISTING FOUR]
<a name="00ad_0017">

/* ------------ fileopen.h --------------- */

/* ------- file open dialog box identifiers --------- */
#define ID_FILEOPEN           20
#define ID_PATH               21
#define ID_FILES              22
#define ID_FILENAME           23
#define ID_DRIVE              24




<a name="00ad_0018">
<a name="00ad_0019">
[LISTING FIVE]
<a name="00ad_0019">

#include "fileopen.h"

ID_FILEOPEN DIALOG 17,4,46,17
CAPTION "Open File"
STYLE WS SYSMENU | WS_CAPTION | WS_MOVEBOX | WS_CLIPSIBLINGS |
      WIN_HAS_SHADOW
BEGIN
   TEXT "Filename:",  0,            20, 6, 9, 1
   EDIT "",           ID_FILENAME,  31, 6,29, 1 ES_AUTOHSCROLL |
                                               WIN_HAS_BORDER
   TEXT "Directory:", 0,            20, 7,10, 1
   TEXT "",           ID_PATH,      31, 7,29, 1
   LISTBOX "Files",         ID_FILES,    20, 9,14,11 LBS_SORT
   LISTBOX "Directories",    ID_DRIVE,    36, 9,14,11 LBS_SORT
   PUSHBUTTON "OK",          IDOK,        52, 9, 8, 3
   PUSHBUTTON "CANCEL",      IDCANCEL,    52,12, 8, 3
END




[LISTING SIX]

/* ----------- fileopen.c ------------- */

#include <window.h>
#include <string.h>
#include "fileopen.h"

static int pascal DlgFnOpen(HDLG, WORD, WORD, DWORD);
static BOOL InitDlgBox(HDLG);
static void StripPath(char *);

static char OrigSpec[80];
static char FileSpec[80];
static char FileName[80];

static int FileSelected;

#define HasWildCards(s) (strchr(s, '?') || strchr(s, '*'))

/* Dialog Box to select a file from the disk system  */
int pascal DlgOpenFile(HWND hParent, BYTE *Fpath, BYTE *Fname)
{
    HDLG hDlg;
    int  rtn;
    extern int hInstance;

    hDlg = LoadDialog(hInstance, MAKEINTRESOURCE(ID_FILEOPEN),
                hParent, DlgFnOpen);
    strncpy(FileSpec, Fpath, sizeof(FileSpec));
    strcpy(OrigSpec, FileSpec);

    if ((rtn = DialogBox(hDlg)) == TRUE)
        strcpy(Fname, FileName);
    else
        *Fname = '\0';

    return rtn;
}

/* Process dialog box messages  */
static int pascal DlgFnOpen(HDLG hDlg, WORD msg, WORD wParam,
                                            DWORD lParam)
{
    switch (msg)    {
        case WM_INITDIALOG:
            if (!InitDlgBox(hDlg))
                EndDialog(hDlg, 0);
            return TRUE;

        case WM_COMMAND:
            switch (wParam)    {
                case ID_FILENAME:
                    /* allow user to modify the file spec */
                    GetDlgItemText(hDlg, ID_FILENAME,
                            FileName, 64);
                    if (HasWildCards(FileName))    {
                        strcpy(OrigSpec, FileName);
                        StripPath(OrigSpec);
                    }
                    break;
                case IDOK:
                    if (HasWildCards(FileName))    {
                        /* no file name yet */
                        strcpy(FileSpec, FileName);
                        if (InitDlgBox(hDlg))    {
                            SetDlgItemText(hDlg, ID_FILENAME,
                                                FileSpec);
                            strcpy(OrigSpec, FileSpec);
                        }
                    }
                    else
                        EndDialog(hDlg, 1);
                    return TRUE;

                case IDCANCEL:
                    EndDialog(hDlg, 0);
                    return TRUE;

                case ID_FILES:
                    switch (HIWORD(lParam))    {
                        case LBN_SELCHANGE :
                            /* selected a different filename */
                            DlgDirSelect(hDlg, FileName,
                                        ID_FILES);
                            SetDlgItemText(hDlg, ID_FILENAME,
                                            FileName);
                            FileSelected = TRUE;
                            break;
                        case LBN_DBLCLK :
                            /* chose a file name */
                            DlgDirSelect(hDlg, FileName,
                                    ID_FILES);
                            EndDialog(hDlg, 1);
                            return TRUE;
                    }
                    break;
                case ID_DRIVE:
                    switch (HIWORD(lParam))    {
                        case LBN_SELCHANGE :
                            /* selected different drive/dir */
                            DlgDirSelect(hDlg, FileName,
                                                ID_DRIVE);
                            strcat(FileName, OrigSpec);
                            strcpy(FileSpec, FileName);
                            SetDlgItemText(hDlg, ID_FILENAME,
                                                    FileSpec);
                            break;
                        case LBN_DBLCLK :
                            /* chose drive/dir */
                            if (InitDlgBox(hDlg))
                            SetDlgItemText(hDlg, ID_FILENAME,
                                                    FileSpec);
                            else
                                strcpy(FileSpec, OrigSpec);
                            return TRUE;
                    }
                    break;

                default:
                    break;
            }
    }
    return FALSE;
}

/* Initialize the dialog box  */
static BOOL InitDlgBox(HDLG hDlg)
{
    FileSelected = FALSE;
    SetDlgItemText(hDlg, ID_FILENAME, FileSpec);
    if (!DlgDirList(hDlg, FileSpec, ID_FILES, ID_PATH, 0))
        return FALSE;
    /* MEWEL DlgDirList should do this, but does not */
    StripPath(FileSpec);
    return DlgDirList(hDlg, "*.*", ID_DRIVE, 0, 0xc010);
}

/* Strip the drive and path information from a file spec  */
static void StripPath(char *filespec)
{
    char *cp, *cp1;

    cp = strchr(filespec, ':');
    if (cp != NULL)
        cp++;
    else
        cp = filespec;
    while (TRUE)    {
        cp1 = strchr(cp, '\\');
        if (cp1 == NULL)
            break;
        cp = cp1+1;
    }
    strcpy(filespec, cp);
}


Copyright © 1991, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.