Feedback Search Top Backward Forward
EDM/2

Building an Editor - Part 3

Written by Eric Slaats and Marc Mittelmeijer

 
Part1 Part2 Part3

Introduction

In the last two articles in this series we created a small editor based on the MLE control. We didn't receive as much mail as the previous article, however. (Maybe because the mail server we are attached to crashed?) So if you sent mail to us and didn't receive a reply, please send it again. Some of the mail we received (thanks for the kudo's) mentioned some items worth repeating here.

Memory allocated?

We received some mail from people who had problems recompiling the samples because the new and delete statements are used. Two things have to be said about this.

  • new and delete are C++ functions, so in order to use them, you've got to use a C++ compiler. If a straight C compiler is used, these statements are not recognized. If you don't want to use these statements, malloc and free or the OS/2 API calls DosAllocMem and DosFreeMem may be used. All these methods should provide you with the amount of memory requested.
  • The second item is a bug in the way memory is allocated in the EDITOR2 example. It was Graham TerMarsch who brought this to our attention. If memory is allocated by using new[], it should be freed by using delete[]. This isn't fatal,the program will compile and work just fine. On the other hand it may cause some erratic behavior. Besides that if only the pointer is freed, the allocated memory will not be freed! In the examples included with this article, this bug is corrected. (Thanks Graham.)

Problems with the resource file!

Some people who tried to recompile the included resource file with RC or other compilers then the Borland resource compiler got errors. The cause for this is that the Borland compiler doesn't need the os2.h include in the resource file. It just assumes it is there. If the line


#include 
is added, the resource file will compile just fine.

In this article we will cover how to change the fonts used by the MLE editor, and a way to save a standard font for the editor. To save font information, we will use a .INI file.

The button icons are somewhat smaller in the EDITOR3 example. We use a 1280/1024 screen, so the 32x32 buttons don't look obnoxiously large. Included in the EDITOR3.ZIP file you'll find a set of 28x28 bitmaps for the buttons.

Some Strategy

As mentioned in earlier articles, we think every developer should go the extra mile to fully implement drag and drop. Of course this also implied fonts. Unfortunately, the MLE control has a few caveats when it comes to handling presentation parameters. We will look at a way around them.

What strategy should we take? When it comes to fonts there are an awful lot of arguments to consider. You only have to look at the FONTMETRICS structure (or the derived FATTRS structure) to get our point. The questions are: do we need all this information to change the font in the MLE editor? And what information do we need to save the currently used font?

One way to change a font in an MLE control is using drag-n-drop. If a font palette drop is used, something known as a called presentation parameter is used to change the font. A presentation parameter is actually a simple string with a description of the parameter in question. Handling a presentation parameter string is a lot simpler then messing with the FONTMETRICS (or the FATTRS structure) structure. For the simple things we want to accomplish, the presentation parameter information is an ideal tool and we'll use that as a starting point.

We want to be able to change fonts of the editor in two ways:

  1. Dropping a font on the MLE.
  2. Using the standard font dialog.

If you tried a font drop with the EDITOR2 example you should have found that this already worked! You should also have found that some of the font attributes aren't displayed properly! For example, take the outline attribute on a Times Roman font. You will find that it won't display this font! Maybe you've also noticed that the font isn't saved when you shut the editor down. Something to fix these things has to be done.

The font in an MLE control can be changed in two separate ways:

  • A MLM_SETFONT message can be posted to the control. To do this we need a FATTRS structure which contains all the necessary font information. (We will see more about the FATTRS structure in a moment.)
  • Or, we can use the WinSetPresParam function and set the presentation parameters for the control. (It is this function the PM uses to associate a presentation parameter with a window. On other words, this function is invoked when a font is dropped on the MLE.)
  • The effect of both methods is the same when looking at the editor (however, we can only set the font-attributes by using the MLM_SETFONT message, we can't set them with a drop!)
Note: Both the methods used for setting the font can be undone by the undo function supplied by the MLE (MLM_UNDO message).

Inside the MLE control though both methods aren't completely compatible. If the font is set by using a presentation parameter drop, the MLE will process this drop. If we query the font in use by an MLE after a font drop - using the MLM_QUERYFONT message - the FATTRS structure returned by the query will contain the correct values. So the MLE processes a font-drop in such a way that the dropped font can be queried by MLM_QUERYFONT!

On the other hand if we query the font presentation parameter (WinQueryPresParam with the presentation parameter PP_FONTNAMESIZE), we get none or the last font dropped. So the presentation parameter attached to the control doesn't have to stand for the same font as the control is displaying! So we should always query the MLE if we want to know what font is in use! (This may give some trouble if we want to know the point size of a font which is very easily obtained from a presentation parameter but it's almost impossible to extract this from a FATTRS structure.)

So what do we need?

  • A way to capture the font-drop event so the presentation parameter can be intercepted to fix the attribute problem.
  • A way to convert the attributes contained in the font presentation parameter to a FATTRS structure member.
  • A way to remember the point size of the font in use.

If we've got this, we can build a program which will always show the correct font, no matter if a presentation parameter is used, or MLM_xxxxxFONT message is used.

The Standard Font Dialog

One of the ways to change the font in the MLE control is to invoke the MLM_SETFONT message. This message sends a FATTRS structure to the MLE. This structure describes fonts and must be filled with valid values so the MLE can select the proper font.

The easiest way to obtain these font-values (FATTR structure) is by using the standard font dialog. Just like the standard file dialog, this dialog was included in OS/2 2.0 to prevent programmers from making all sorts of dialogs to change fonts. And just like the standard file dialog it has a ridiculous number of things to be initialized before it may be used.

fontdialogwindow.gif

To use this dialog a FONTDLG structure has to be initialized before invoking the dialog.


typedef struct _FONTDLG
  {
  ULONG         cbSize;            // Structure size
  HPS           hpsScreen;         // Screen presentation space
  HPS           hpsPrinter;        // Printer presentation space
  PSZ           pszTitle;          // Dialog title string
  PSZ           pszPreview;        // Font-preview window string
  PSZ           pszPtSizeList;     // Application-provided point size list
  PFNWP         pfnDlgProc;        // Custom dialog procedure
  PSZ           pszFamilyname;     // Family name buffer
  FIXED         fxPointSize;       // Point size of the font
  ULONG         fl;                // FNTS_* flags
  ULONG         flFlags;           // FNTF_* flags
  ULONG         flType;            // The selected type bits
  ULONG         flTypeMask;        // Mask of type bits to use
  ILONG         flStyle;           // Selected style bits
  ULONG         flStyleMask;       // Mask of style bits to use
  LONG          clrFore;           // Font foreground color
  LONG          clrBack;           // Font background color
  ULONG         ulUser;            // Application-defined
  LONG          lReturn;           // Return value
  LONG          lSRC;              // System return code
  LONG          lEmHeight;         // Em height
  LONG          lXHeight;          // X height
  LONG          lExternalLeading;  // External leading
  HMODULE       hMod;              // Module for custom dialog resources
  FATTRS        fAttrs;            // Font-attribute structure
  SHORT         sNominalPointSize; // Font point size
  USHORT        usWeight;          // Font weight
  USHORT        usWidth;           // Font width
  SHORT         x;                 // The x-axis dialog position
  SHORT         y;                 // The y-axis dialog position
  USHORT        usDlgId;           // Dialog ID
  USHORT        usFamilyBufLen;    // Buffersize
  USHORT        usReserved;        // Reserved
  } FONTDLG;
It's hell to fill all these values, so we don't want to fill every member off this structure if we don't need to do it. Before we can use the font dialog, we fill every item with a NULL value; then we fill the members that are essential for the handling of the font dialog.

The following members are absolutely necessary to use the font dialog.


FontDlg.cbSize         = sizeof(FONTDLG);  // Set cbsize (Mandatory)
FontDlg.pszFamilyname  = szCurrentFont;    // Buffer for fontname
                                           // (previously defined)
FontDlg.usFamilyBufLen = FACESIZE;         // Size fontname buffer
FontDlg.clrFore        = CLR_NEUTRAL;      // Foreground color for sample
FontDlg.clrBack        = CLR_DEFAULT;      // Background color for sample
To show that with only these values we can make a font dialog work, a FONTDLG example is included in the file EDITOR3.ZIP. FONTDLG.ZIP contains the complete working code for this example.

Treating the font dialog this way in an editor isn't rather user friendly. Usually we want the font dialog to display the font currently in use, including the font size and the attributes (underline etc.). To obtain this information at least one other member of the FONTDLG structure has to be filled. This is the field containing the FATTRS structure. This structure contains the most important font information and is also used by the MLE control. A filled FATTRS structure containing the information about the font currently in use can be retrieved from the MLE using the MLM_QUERYFONT message.

It would be nice if this is enough to initialize the font dialog. Well, it is. We can set the fl field off the FONTDLG structure and the fontdialog will set itself up according to the values contained in the fAttrs field.


FontDlg.fl = FNTS_INITFROMFATTRS;           // Init FontDialog from FATTRS
WinSendMsg(hwndMLE, MLM_QUERYFONT, &FontDlg.fAttrs, 0L);
                                            // Retrieve font values from MLE
For now we have enough to build the function that can be incorporated in the editor. Only a few details have to be taken care off. Before we start we have to set all the values of the FONTDLG structure to NULL. Besides that we want the values set by the dialog to be remembered so that the next time the dialog is invoked the proper values (e.g. point size) will show. We can achieve both by declaring the FONTDLG variable static. If a variable is declared as a static, its contents will be remembered until the next time the function in which it is declared is called again. Besides that, static initializes the variable with NULL values.

The new function to change the fonts in the MLE editor will look something like this.


case IDM_CHANGEFONT:
  {
  char      szCurrentFont[FACESIZE];             // Setup fontname buffer
  static    FONTDLG  FontDlg;                    // values FILEDLG set to 0
  FontDlg.cbSize          = sizeof(FONTDLG);     // Set cbsize (Mandatory)
  FontDlg.f               = FNTS_INITFROMFATTRS; // Init from FATTRS
  FontDlg.usFamilyBufLen  = FACESIZE;            // Size fontname buffer
  FontDlg.pszFamilyname   = szCurrentFont;       // Fontname buffer
  FontDlg.clrFore         = CLR_NEUTRAL;         // Foreground color for sample
  FontDlg.clrBack         = CLR_DEFAULT;         // Background color for sample
  WinSendMsg(hwndMLE, MLM_QUERYFONT, &FontDlg.fAttrs, 0L);
                                                 // Retrieve font values
  WinFontDlg(HWND_DESKTOP, hwnd, &FontDlg);      // Start font dialog
  if (FontDlg.lReturn == DID_OK)                 // OK pressed ?
     WinSendMsg(hwndMLE,                         // Activate new font
         MLM_SETFONT,
         &FontDlg.fAttrs,
         NULL);
  }
break;
If we use it the first time, the default font for the MLE will be displayed. Every time the font dialog is invoked, the last font used is displayed, including the attributes and the point size used (remembered in the static FONTDLG structure).

Because this is a small and elegant solution, something has to go wrong, and indeed something does. We want the editor to incorporate every goody OS/2 has to offer, so dropping a font on the MLE window should result in a font change. If we try this it seems to work just fine. However there are a few situations where things go wrong.

  1. If a font without further attributes (underline, outline, strikeout) is dropped, the MLE takes over the dropped font and it can be queried correctly. If the font dialog is opened the right font will show; however, the font size won't be correct. The dialog will show the last font size used! So we have to find a way to restore the correct font size in the FONTDLG structure which is used for setting up the fontdialog.

    Apparently the WinFontDlg() function cannot determine the point size from the FATTRS structure (well, neither can we).

  2. If a font with attributes is dropped on the MLE the attributes won't show. It isn't that the MLE control isn't capable of showing these attributes; it just won't show them if dropped in the form of a presentation parameter.

So if we want to support complete font dropping, we've got to do something about this. To accomplish this we have to capture a font drop.

Detecting the Font Presentation Parameter

What's a presentation parameter?

A presentation parameter is a character string that represents certain presentation values such as color- and font- values. These values can be used when displaying a window. If we drop a font on the titlebar of a window, the titlebar (also a window) will show that font. The same thing happens with colors.

Note: Although OS/2 supports most of the presentation parameters for the standard controls in most cases, there are some points when things aren't completely supported. An example of this is the font drop on an MLE. Another example is a color drop on an MLE. (A MLE excepts only indexed colors.) Also, not all OS/2 applications support presentation parameters; the application has to be utilized to do so!

For this article we are only interested in the PP_FONTNAMESIZE presentation parameter. The drop of this presentation parameter is what we want to take actions upon. If this presentation parameter is queried from the MLE, a string is retrieved that looks something like this:


pointsize.facename.attr1.attr2.attr3
This structure is always used by PP_FONTNAMESIZE to describe a font. The items in this string are separated by a dot (.). Only the first two items are mandatory. So the following examples are valid presentation parameter strings.

"8.Helv"
"18.Courier Bold"
"24.Times New Roman Bold Italic"
"12.System Proportional"
There are five possible attributes a font can have,although only three of them are supported by the standard font dialog and by the FONTDROP dialog. The attributes bold and italic are normally supported by the font style, so they are omitted in the font dialog. However these attributes can still be used. If we use bold on a already bold font it gets wider. If we use italic on a already italic font, the font gets italicized more.

These are the five possible font attributes:

  • Italic
  • Outline
  • Strikeout
  • Underscore
  • Bold
So the following presentation parameter strings are also valid:

"8.Helv.Outline"
"18.Courier Bold.Bold"
"24.Times New Roman Bold Italic.Italic.Underscore.Bold"
"12.System Proportional.Strikeout"
Now we know how a presentation parameter looks and what the possible values are, we've only got to capture a fontdrop to fix the two points mentioned in the previous section.

Besides that we've learned another thing. There aren't three but five font attributes. The attributes bold and italic can't be accessed through the standard font dialog or by the font palette. Let's take a small detour and find out how we can use these attributes in our MLE editor.

Applying the Bold and Italic Attributes

We can set and query the current font in the MLE by using the MLM_SETFONT and MLM_QUERYFONT messages. By using these messages, we can build two very simple functions that will enable us to change the contents of a MLE to bold or to italic.

Before we look into these functions, lets take a look at the fsSelection field of the FATTRS structure. This field contains the font attributes. If this field contains a 0, no attributes are attached to the font. The following values can be or'd together to make a valid entry for fsSelection.

  • FATTRS_SEL_ITALIC
  • FATTRS_SEL_UNDERSCORE
  • FATTRS_SEL_BOLD
  • FATTRS_SEL_STRIKEOUT
  • FATTRS_SEL_OUTLINE
We're only interested in the bold and italic attributes because the rest can be set through a font drop or by invoking the font dialog.

To change the font to bold or italic, two new entries are added to the edit menu: bold and italic. We want these to be toggle entries. (Yeah yeah we know a checkmark in the menu should be in place.) If one of these menu options is chosen, the font in the MLE must add the attribute if it isn't there or delete the attribute if it is there. To accomplish this we'll use the XOR oparator (^).

The code example to toggle the bold attribute for the current font looks like this:


case IDM_BOLD:
  {
  FATTRS fAttrs;
  WinSendMsg( hwndMLE, MLM_QUERYFONT, &fAttrs, NULL ); // Query font
  fAttrs.fsSelection ^= FATTR_SEL_BOLD;                // XOR with bold attribute
  WinSendMsg( hwndMLE, MLM_SETFONT, &fAttrs, NULL );   // Set font
  }
break;
This is the end of the detour. Let's get back to the article.

Capturing a Font Drop

What happens if a presentation parameter (font, color or scheme) is dropped on a window, and why do we want to capture this event?

We want to capture this event for two reasons (as mentioned in the section about the standard font dialog).

  1. It is very hard to determine the font's point size from the FATTRS value as used by the MLE. If we set a font by using the font dialog, the point size set can be remembered. If a font drop occurs, the point size of the dropped font may differ from the point size remembered in the FONTDLG structure. So by the next call of the Font dialog we've got a problem.

    However, the point size can easily be derived from a presentation parameter. So, we want to extract the point size from the dropped font presentation parameter and store it in the FONTDLG structure.

  2. A font drop with fonts that has attributes attached isn't processed properly by the MLE. By capturing a font drop we can check on the attributes of the dropped font and set them according to the dropped font.
To build a solution for both problems we need the presentation parameter string of the dropped font. Let's see how we can obtain the string that describes the dropped font.

If a presentation parameter drop is performed, a WM_PRESPARAMCHANGED message is sent to the window the drop is performed upon. (This message also occurs if the presentation parameters are set using the WinSetPresParam function, or if the presentation paramaters are inherited.)

If this message is send, the first parameter of the WM_PRESPARAMCHANGED message contains the type of presentation parameter dropped. There are quite a lot (29 to be exact) of presentation parameters but in this case we're only interested in the PP_FONTNAMESIZE attribute (font name and size attribute).

In our case we have to intercept the WM_PRESPARAMCHANGED message in the MLE window standard procedure. This means subclassing the MLE window. Fortunately, we've already done this to process file drops (see EDM/2 3-6)! So we only have to add another case for WM_PRESPARAMCHANGED.

If a WM_PRESPARAMCHANGED occurs we can query the presentation parameters of a window by calling the WinQueryPresParam function. This function enables us to query a presentation parameter string. In our case we want to query the PP_FONTNAMESIZE parameter. The WinQueryPresParam function is rather complex; it looks like this:


WinQueryPresParam(hwnd,          // Window handle
   idAttrType1,                  // Pres param attribute 1
   idAttrType2,                  // Pres param attribute 2
   pidAttrTypeFound,             // Attrib ID found
   cbAttrValueLen,               // Size of found attribute
   pAttrValue,                   // Found attribute value
   flOptions);                   // Flags
This function takes two presentation parameter attributes. It will search for the second presentation parameter if the first one isn't found. Since we only act upon a WM_PRESPARAMCHANGED with the first parameter set to PP_FONTSIZENAME, we are sure a presentation parameter is present. This means we can ignore the second presentation parameter value in this function.

The only other parameter of this function that we can use is the flOptions parameter. With the flags in this field we can control the query in such a way that we don't query the inherited presentation parameter values from owners, but the value in the queried window (the MLE window in our case). To do this we have to fill flOptions with the QPF_NOINHERIT value.

If we capture the presentation parameter string dropped on the MLE, what actions do we have to take?

Well first we've got to send the WM_PRESPARAMCHNAGED message to the original MLE window procedure to make sure the presentation parameter is processed so that the correct font value is attached to the MLE and will appear in the FATTRS structure if we query the MLE.

The original MLE window procedure is queried at the start of the subclass (see EDM 2-6).

A short reminder: the pointer to the original MLE window procedure is stored in the window word attached to the MLE. This window word is retrieved at the start of the MLE subclass. So the following code makes the original MLE-window procedure available.


PFNWP oldMLEProc;

oldMLEProc = (PFNWP) WinQueryWindowULong(hwnd, QWL_USER);
                       // Get Winproc pointer from winword
Now that the pointer to the original window procedure is known, we can call this procedure with the passed parameters to make sure the drop is processed by the MLE.

What's next? Let's take the problems one at the time. First we'll define a way to extract the point size from the dropped font and store it in the FONTDLG structure so the right point size will show if we invoke the font dialog. This has a rather ugly penalty. If we want to keep the point size of the used font in the FONTDLG structure, we have to make this variable public. Otherwise it can't be accessed in the MLE subclass as in the mormal window procedure. We will still use static in the definition because that will insure that the values in the structure will be set to NULL.

The entry in the MLE subclass may look like this if we fix the point size problem.


case WM_PRESPARAMCHANGED:
  {
  if ((ULONG)mpParm1 == PP_FONTNAMESIZE)
    {
    oldMLEProc(hwndMLE, msg, mpParm1, mpParm2); // Call original MLE procedure
    CHAR  PPAttrValue[FACESIZE+64];             // PP string buffer
    WinQueryPresParam(hwndMLE,                  // Query dropped font
       PP_FONTNAMESIZE,
       0,
       NULL,
       FACESIZE+64,
       &PPAttrValue,
       QPF_NOINHERIT);
//----------------------------------------------------------------------------
// set point size in FontDlg
//----------------------------------------------------------------------------
    char String[3];
    FontDlg.fxPointSize = (atoi(strncpy(String,
                          PPAttrValue,
                          ((ULONG)strchr(PPAttrValue,'.') -
                          (ULONG)PPAttrValue)
                          )    )     ) << 16;
    return(0);
    }
  }
break;
The first part of this code sample shouldn't contain any surprises. The second part however is something different. It's a strange brew of all sorts of string functions. (To use these string functions, we've got to include string.h in the file.)

We want to retrieve the point size from the presentation parameter string. The point size is represented by all the characters before the first occurence of a dot (.). So the first thing we do is extract the characters before the dot, so we have a string that contains the point size in text. To achieve this, we first query the position of the dot in presentation parameter string by using the strchr function. This function returns a pointer to a substring in the presentation parameter string that starts with the dot. If we subtract the pointer to the presentation parameter string from this value we get the number of characters at the beginning of the presentation parameter string that contain the point size.


(ULONG)strchr(PPAttrValue,'.') - (ULONG)PPAttrValue)
Next the number of characters returned from the above function are copied in a string from where it can be converted into a number. The strncpy can be used to copy a certain number of characters from the beginning of a string to a destination string. We can be fairly sure that the point size won't exceed 999, so a character string with length 3 should be enough to contain the point size.

char String[3];

strncpy(String,PPAttrValue,((ULONG)strchr(PPAttrValue,'.')-(ULONG)PPAttrValue));
The next step is to convert the contents of the variable String to an integer. This can be done immediately because the strncpy returns the pointer to String. The function normaly used to convert an ASCII string to an integer is the atoi function.

However, the normal point size isn't the value we can put into a fxPointSize field of a FONTDLG structure. The fxPointsize field is of the FIXED type, which is a signed integer fraction (16:16). This means we've got to multiply the point size value we found by 65536 (2^16). An easier way to accomplish this is to perform a 16 bit shift to the left (<< 16).

This leads to the following construction:


FontDlg.fxPointSize = (atoi(strncpy(String,
                      PPAttrValue,
                      ((ULONG)strchr(PPAttrValue,'.') -
                      (ULONG)PPAttrValue)
                      )    )    ) << 16;
This takes care of the first problem, now the second one, the fontattributes that aren't set by a drop.

First we've got to check if one of the attributes is set in the presentation parameter string. Before we can do this, we've got to know how these attributes are represented in the the presentation parameter string. In one of the previous sections we've seen that the attributes are appended to the presentation parameter string (dot separated). In the presentation parameter string the attributes are set by the following text strings:

Italic, Outline, Strikeout, Underscore, Bold

The strstr function can be used to check if a string appears as a substring in another string. So this function is used to check for every attribute if it appears in the presentation parameter string, for example for the Italic attribute:


if (strstr(PPAttrValue, ".Italic"))
If a attribute is found, we've got to set this in the fsSelection field of the FATTRS structure so the font can be manually set by a MLM_SETFONT.

As we saw in a previous section, there are five different font attributes. They are represented by the following five values:

  • FATTRS_SEL_ITALIC
  • FATTRS_SEL_UNDERSCORE
  • FATTRS_SEL_BOLD
  • FATTRS_SEL_STRIKEOUT
  • FATTRS_SEL_OUTLINE
Knowing this we can build a valid value for fsSelection by checking on every attribute and adding these to the fsSelection.

short usfsSelection = 0;
if (strstr(PPAttrValue, ".Italic"))
    usfsSelection = usfsSelection + FATTR_SEL_ITALIC;
if (strstr(PPAttrValue, ".Outline"))
    usfsSelection = usfsSelection + FATTR_SEL_OUTLINE;
if (strstr(PPAttrValue, ".Strikeout"))
    usfsSelection = usfsSelection + FATTR_SEL_STRIKEOUT;
if (strstr(PPAttrValue, ".Underscore"))
    usfsSelection = usfsSelection + FATTR_SEL_UNDERSCORE;
if (strstr(PPAttrValue, ".Bold"))
    usfsSelection = usfsSelection + FATTR_SEL_BOLD;
If one of these attributes has been detected, the usfsSelection has a value greater then 0 so we can simply check it with an if-statement. Next we query the font currently active in the MLE, (this should be the font dropped, the call to oldMLEproc has set the current font.) and change the fsSelection in the FATTRS structure.

if (usfsSelection)
    {
    FATTRS fAttrs;
    WinSendMsg( hwnd, MLM_QUERYFONT, &fAttrs, NULL );
    fAttrs.fsSelection = usfsSelection;
    WinSendMsg( hwnd, MLM_SETFONT, & fAttrs, NULL );
    }
This approach has one flaw: if a font with attributes is dropped, the font change can't be undone by the undo function. This is because the font is changed twice. Once by the drop and once by the attributes set. So if a undo is invoked, only the attributes set will be undone.

The complete code for the WM_PRESPARAMCHANGED message:


case WM_PRESPARAMCHANGED:
  {
  if ((ULONG)mpParm1 == PP_FONTNAMESIZE)
      {
      oldMLEProc(hwndMLE, msg, mpParm1, mpParm2);
      CHAR  PPAttrValue[FACESIZE+64];
      WinQueryPresParam(hwndMLE,
         PP_FONTNAMESIZE,
         0,
         NULL,
         FACESIZE+64,
         &PPAttrValue,
         QPF_NOINHERIT);
      //--------------------------------------------------------------
      // set pointsize in FontDlg
      //--------------------------------------------------------------
      char String[3];
      FontDlg.fxPointSize = (atoi(strncpy(String,
                            PPAttrValue,
                            ((ULONG)strchr(PPAttrValue,'.') -
                            (ULONG)PPAttrValue)
                            )   )    ) << 16;
      //--------------------------------------------------------------
      // set attributes in MLE and in FontDlg FATTRS]
      //--------------------------------------------------------------
      short usfsSelection = 0;
      if (strstr(PPAttrValue, ".Italic"))
          usfsSelection = usfsSelection + FATTR_SEL_ITALIC;
      if (strstr(PPAttrValue, ".Outline"))
          usfsSelection = usfsSelection + FATTR_SEL_OUTLINE;
      if (strstr(PPAttrValue, ".Strikeout"))
          usfsSelection = usfsSelection + FATTR_SEL_STRIKEOUT;
      if (strstr(PPAttrValue, ".Underscore"))
          usfsSelection = usfsSelection + FATTR_SEL_UNDERSCORE;
      if (strstr(PPAttrValue, ".Bold"))
          usfsSelection = usfsSelection + FATTR_SEL_BOLD;

      if (usfsSelection)
          {
          FATTRS fAttrs;
          WinSendMsg( hwnd, MLM_QUERYFONT, &fAttrs, NULL );
          fAttrs.fsSelection = usfsSelection;
          WinSendMsg( hwnd, MLM_SETFONT, & fAttrs, NULL );
          }
      return(0);
      }
  }
break;

Saving information in OS2.INI

It's a pain, constantly having to change the font settings if an application is started. So let's have some more fun with the MLE and look at an easy way to save the font information used in the MLE editor. The method described here will only be used to save font information in the OS2.INI file so the last font used in the editor will automatically be loaded as the editor is started again. However, roughly the same method is used in the SMALLED application to save font information in the extended attributes of text files. This way font information can be stored with a file while this information isn't stored in the file!

When we're at it, we'll take a small detour and take a look at how the window size and position can be saved in the OS2.INI.

Most applications will have to save some general information at one time or another. In the old DOS days this kind of information was stored in a setup file which was read when the application was started. The problem with these kind of files was that every developer invented the wheel again and took his own approach. When OS/2 came along a structured method for storing this kind of information was provided in the profile API. Through the profile function calls a programmer can create a private profile for an application or store information in the OS/2.INI file.

The OS2.INI file is a general profile for applications under OS/2. This file is read at boot time and is available in memory, so fast access is insured.

The OS/2 profile files (INI files) are stored in a binary format and are accessable in a sort off database like fashion. The general form in which data is stored in a profile looks has a three level layout.


Application name
      Key name
           Key Data
      Key name
           Key data
      :
      :
      Key name
           Key data

Application name
      :
      :
In a INI file more application names can be present. Under every application data can be stored under a key name. The form for this data is free. It can be a long, a character string, a structure etc. as long as it doesn't exceed the 64 Kb limit.

Now we know that data can be stored and retrieved (we will see exactly how in a minute) in any form we like, we have to find a convenient way to store font data.

We have two options: we can save the font information as a FATTRS structure or to save it as presentation parameter information. The FATTRS structure has as disadvantage that we can't save the point size. To cope with this a FONTMETRICS structure has to be used. This is a bad case off overkill for the simple effect we want to achieve. Using presentation parameter information has the advantage that we've already got a routine process this information. So a call to WinSetPresParam is enough. The first step in saving font information is building a function that can build a presentation parameter string from the FATTRS information that can be retrieved from the MLE and the point size information from the FONTDLG structure. The information about how a presentation parameter string looks can be found in the section Desecting the Font presentation parameter.

Filling a string with values is rather easy, we can simply use the sprintf statement.


// FontDlg == Global
CHAR    PPAttrValue[FACESIZE+64];                    // PP string
FATTRS fAttrs;                                       // Font information
WinSendMsg( hwndMLE, MLM_QUERYFONT, &fAttrs, NULL ); // Query current font

sprintf(PPAttrValue, "%d.%s", FontDlg.fxPointSize << 16,
fAttrs.szFacename);
This code sample isn't too hard. The only thing we've got to take note off are the dots used in the presentation parameter and the right shift of the fxPointsize value to obtain a valid point size.

There is still one thing missing from the presentation parameter string composed in this way: this is the font attributes (if used). To find out if an attribute is used in a font we conduct bitwise and on the fAttrs.fsSelection value with all the possible attribute values. If an attribute is found we simply add it to ppAttrValue. The code to achieve this looks like this:


if (fAttrs.fsSelection & FATTR_SEL_ITALIC)
     strcat(PPAttrValue,".Italic");
if (fAttrs.fsSelection & FATTR_SEL_OUTLINE)
     strcat(PPAttrValue,".Outline");
if (fAttrs.fsSelection & FATTR_SEL_STRIKEOUT)
     strcat(PPAttrValue,".Strikeout");
if (fAttrs.fsSelection & FATTR_SEL_UNDERSCORE)
     strcat(PPAttrValue,".Underscore");
if (fAttrs.fsSelection & FATTR_SEL_BOLD)
     strcat(PPAttrValue,".Bold");
Now that we have a way to build a presentation parameter string from the available font information, let's take a look at how we can save this information in the OS/2 INI file. Before we talk code we have to find a convenient place in the program flow where we can perform this save. In moments like this you really come to love OS/2. It has a message WM_SAVEAPPLICATION message that is sent when the application is about to shut down. We want to save the font information at shutdown time, so this message is all we need.

Note: Make sure that the WinDefWindowProc is called after you process this message!

There are two functions that can be used to write data to a profile: PrfWriteProfileData for binary data and PrfWriteProfileString for a character string, which is what we're interested in. The syntax looks like this:


HINI   hini;     // Initialization-file handle
PSZ    pszApp;   // Application name
PSZ    pszKey;   // Key name
PSZ    pszData;  // Text string
BOOL   fSuccess; // Success indicator

fSuccess = PrfWriteProfileString(hini,
                   pszApp,
                   pszKey,
                   pszData);
For HINI we got to obtain initialization handle if we want to use a private profile. However if we are using the OS2.INI file we can use a predefined handle, HINI_USERPROFILE.

To fill the pszApp and the pszKey values, it is good practice to add defines in the header file. So you can be sure that the same entries are used for storing as well as receiving data.


//----------------------------------------------
// In editor3.h
//----------------------------------------------
#define SAVE_NAME  "EDITOR3"
#define PPFONT     "PPFONT"

//----------------------------------------------
// During WM_SAVEAPPLICATION
//----------------------------------------------
PrfWriteProfileString(HINI_USERPROFILE,
             SAVE_NAME,
             PPFONT,
             PPAttrValue);
I promised another small detour. Here it is. The most common action to take at WM_SAVEAPPLICATION is to save the current window size and place. It's done so often that a special function has been provided. This is the WinStoreWindowPos function. A nice spin-off of this function is that it will save the presentation parameter parameters of the frame-window and all it's (added) controls. So if we change the color of the toolbar or the font used in the statusbar, it will be remembered!

The WinStoreWindowPos takes the handle of the window to save as a parameter, as well as the application name and the keyname under which this information must be saved. Knowing this we can finaly write the compete code for the WM_SAVEAPPLICATION message.


case WM_SAVEAPPLICATION:
  {
  CHAR  PPAttrValue[FACESIZE+64];                      // PP string
  FATTRS fAttrs;                                       // Font information
  WinSendMsg( hwndMLE, MLM_QUERYFONT, &fAttrs, NULL ); // Query current font
  sprintf(PPAttrValue,                                 // Fill PP string
     "%d.%s",
     FontDlg.fxPointSize << 16,
     fAttrs.szFacename);
  if (fAttrs.fsSelection & FATTR_SEL_ITALIC)     // Check on Italic attribute
     strcat(PPAttrValue,".Italic");
  if (fAttrs.fsSelection & FATTR_SEL_OUTLINE)    // Check on Outline attribute
     strcat(PPAttrValue,".Outline");
  if (fAttrs.fsSelection & FATTR_SEL_STRIKEOUT)  // Check on Strikeout attribute
     strcat(PPAttrValue,".Strikeout");
  if (fAttrs.fsSelection & FATTR_SEL_UNDERSCORE) // Check on Underscore attribute
     strcat(PPAttrValue,".Underscore");
  if (fAttrs.fsSelection & FATTR_SEL_BOLD)       // Check on Bold attribute
     strcat(PPAttrValue,".Bold");
  PrfWriteProfileString(HINI_USERPROFILE,        // Save font PP in OS2.INI
            SAVE_NAME,
            PPFONT,
            PPAttrValue);
  WinStoreWindowPos(SAVE_NAME;              // Save window size and position
            SAVE_WINPOS,
            WinQueryWindow(hwnd,QW_PARENT));// Query frame handle
  }
break;
case WM_SAVEAPPLICATION:
  {
  CHAR  PPAttrValue[FACESIZE+64];                      // PP string
  FATTRS fAttrs;                                       // Font information
  WinSendMsg( hwndMLE, MLM_QUERYFONT, &fAttrs, NULL ); // Query current font
  sprintf(PPAttrValue,                                 // Fill PP string
     "%d.%s",
     FontDlg.fxPointSize << 16,
     fAttrs.szFacename);
  if (fAttrs.fsSelection & FATTR_SEL_ITALIC)     // Check on Italic attribute
     strcat(PPAttrValue,".Italic");
  if (fAttrs.fsSelection & FATTR_SEL_OUTLINE)    // Check on Outline attribute
     strcat(PPAttrValue,".Outline");
  if (fAttrs.fsSelection & FATTR_SEL_STRIKEOUT)  // Check on Strikeout attribute
     strcat(PPAttrValue,".Strikeout");
  if (fAttrs.fsSelection & FATTR_SEL_UNDERSCORE) // Check on Underscore attribute
     strcat(PPAttrValue,".Underscore");
  if (fAttrs.fsSelection & FATTR_SEL_BOLD)       // Check on Bold attribute
     strcat(PPAttrValue,".Bold");
  PrfWriteProfileString(HINI_USERPROFILE,        // Save font PP in OS2.INI
            SAVE_NAME,
            PPFONT,
            PPAttrValue);
  WinStoreWindowPos(SAVE_NAME;              // Save window size and position
            SAVE_WINPOS,
            WinQueryWindow(hwnd,QW_PARENT));// Query frame handle
  }
break;

Retrieving Font Information from OS2.INI

Saving the font and size/position information is one thing, we also need to retrieve this information the moment the editor is started.

The size/position information has to be loaded after the window is started, but before it becomes visible. Also the MLE window and the statusbar window must be created because they participate in the WM_FORMAT and the WM_SIZE message.

The font information must be handled in the same fashion. It must be loaded after the MLE window is loaded, but before any file information is shown (for example a file drop on the editor icon).

Normaly this kind of action would be handled during the WM_CREATE message. However in this case the MLE doesn't exist during the WM_CREATE. In EDITOR2 we have chosen to create the MLE window directly after the main frame window. The WM_CREATE message is send by the creation of the frame window, so this isn't a good spot.

The ideal spot, in our opinion, is right after the toolbar is loaded.

To restore the window size and place (and the presentation parameter attached to all it's controls) we use the WinSetWindowPos function. This function will look in the OS2.INI file for a given application and key name and restore the information previously saved by a WinStoreWindowPos call.


WinRestoreWindowPos(SAVE_NAME,
              SAVE_WINPOS,
              hwndFrame);
Making the call just like this isn't enough. If WinRestoreWinPos does find the stored information in the INI file, it applies it to the window identified by hwndFrame. It doesn't show the window or activate it. To accomplish this we have to make another call. The WinSetWindowPos function (a very nice function to know about) can be used to make the window visible, activate the window and place it on top of the z-order.

We know if the WinRestoreWinPos function had been succesfull if it returns a TRUE. If it didn't retrieve any information from the OS2.INI file (for example during the first time editor3 is started) we have to set the size and place ourselves. The following piece of code will take care of business.


//---------------------------------------------------------------------------
// Set Main window (Frame) size and position
//---------------------------------------------------------------------------
if (WinRestoreWindowPos(SAVE_NAME, // Get and set size en place from OS2.INI
                SAVE_WINPOS,
                hwndFrame))
     WinSetWindowPos(hwndFrame,    // Activate window
               HWND_TOP,           // (PM will position through
               0,0,0,0,            //  WinRestoreWindowPos)
               SWP_ACTIVATE |      // Activate window
               SWP_SHOW);          // Show Window else
     WinSetWindowPos(hwndFrame,    // If no entry in OS2.INI
               NULLHANDLE,         // default position 10,10
               10,10,600,400,      // with SIZE 600,400
               SWP_ACTIVATE|SWP_MOVE|
               SWP_SIZE    |SWP_SHOW);
For setting the font information we have to use another function. We saved the font information by calling the PrfWriteProfileString function. This information can be retrieved by calling the counterpart of this function, the PrfQueryProfileString. This function will look into the OS2.INI file for the given application and key, if it doesn't find it, it will return a default value.

HINI     hini;          // Initialization-file handle
PSZ      pszApp;        // Application name
PSZ      pszKey;        // Key name
PSZ      pszDefault;    // Default string
PVOID    pBuffer;       // Profile string
ULONG    cchBufferMax;  // Maximum string length
ULONG    pulLength;     // String length returned

pulLength = PrfQueryProfileString(hini,
                        pszApp,
                        pszKey,
                        pszDefault,
                        pBuffer,
                        cchBufferMax);
For the default value we can define a presentation parameter string that contains the standard system font.

"12.System Proportional"
If no value is found, the MLE is set to the default system font. Once a value is retrieved we've got to set it. To set a font through a presentation parameter we've got to use the WinSetPresParam function.

HWND   hwnd;            // Window handle           (MLE handle)
ULONG  idAttrType;      // Attribute type identity (PP_FONTNAMESIZE)

ULONG  cbAttrValueLen;  // Byte count of the data passed in the pAttrValue
parameter

PVOID  pAttrValue;      // Attribute value
BOOL   fSuccess;        // Success indicator

fSuccess = WinSetPresParam(hwnd,
                   idAttrType,
                   cbAttrValueLen,
                   pAttrValue);
The beauty of using this function is that it will automatically invoke the WM_PRESPARAMCHANGED message. So all the right moves will be made to initiate the point size value in the FontDlg variable and set the fAttrs value in the MLE control. Even previously set bold and italic attributes will be handled correctly.

//----------------------------------------------------------------------------
// Set font from the value in OS2.INI
//----------------------------------------------------------------------------
char PPAttrValue[FACESIZE+64];
PrfQueryProfileString(HINI_USERPROFILE,
                (PSZ) SAVE_NAME,
                (PSZ) PPFONT,
                (PSZ) "12.System Proportional",
                (PVOID) PPAttrValue,
                (ULONG) FACESIZE+64);
WinSetPresParam(hwndMLE, PP_FONTNAMESIZE, strlen(PPAttrValue)+1, PPAttrValue);

Concluding notes

This is the third (and probably the last) article in a describing a way to build a small fast editor that can easily be embedded in another application. We know it isn't complete yet. For example, search and replace is a serious omission. We hope the articles will be useful for your own program development. (We received some mail stating so [grin] .) The articles about the MLE editor are for a large part based on the SMALLED application. This application is available as share/freeware on the Hobbes ftp site. Currently it's at version 0.96 and can be retrieved as SMALED96.ZIP.

The EDITOR3 program can be made somewhat simpler if you use the easy toolbar described in EDM3-8 'Easy Buttonbars'. Besides that I (Eric) am planning another article based on easy buttonbars to add bubble help to a buttonbar. Bubble help is a popup window under the buttonbar containing help info about a button if you mouse over it. So further enhancement based on what we've written for EDM/2 is possible.

Feel free to use the code any way you like and send us your comments.

The examples are all build and compiled with Borland C++ 2.0.

Part1 Part2 Part3