Feedback Search Top Backward Forward
EDM/2

Questions and Answers

Written by Larry Salomon

 

Questions and Answers

Welcome to this month's "Questions and Answers"! Each month, I collect various questions sent to me via email and try to answer each directly; the ones that I feel contribute the most to developers, whether in terms of information or as a nifty trick to tuck into your cap, get published in this column.

Unfortunately, no questions were submitted this month so we will discuss some errors in the "Programming Reference" documentation. The following topics will be covered:

  • Cascading Menus
  • Dropping on a Printer
  • Message Box Help

Cascading Menus

OS/2 2.0 added a new feature to menus, which is used extensively throughout the Workplace Shell. These are called "conditional cascading menus" (I will refer to them as CCM's), which look similar to "pull-rights", but have the following differences in appearance and behavior:

  • CCM's have a pushbutton instead of simply a right arrow, which looks like the right arrow on a scrollbar.

  • CCM's have a default selection. If you only click on the submenu name, the default action is taken.

Unfortunately, there is no documentation discussing how to code this in your PM applications, with the exception of the mentioning of the menu style. Therefore, let us discuss this new style and introduce two new menu messages.


MS_CONDITIONALCASCADE

Setting this style indicates that the SUBMENU is to be a CCM. Unfortunately, using this in the resource file does not work. Instead, you must explicitly set it in your code like below:


   HWND hwndMenu;
   USHORT usSubmenu;
   MENUITEM miItem;

   WinSendMsg(hwndMenu,
              MM_QUERYITEM,
              MPFROM2SHORT(usSubmenu,TRUE),
              MPFROMP(&miItem));

   ulStyle=WinQueryWindowULong(miItem.hwndSubMenu,
                               QWL_STYLE);

   ulStyle|=MS_CONDITIONALCASCADE;

   WinSetWindowULong(miItem.hwndSubMenu,
                     QWL_STYLE,
                     ulStyle);

hwndMenu was loaded with WinLoadMenu (specifying HWND_OBJECT as the "frame window" in preparation for calling WinPopupMenu. usSubmenu is the resource id of the SUBMENU to convert to a CCM.


MM_SETDEFAULTITEMID and MM_QUERYDEFAULTITEMID

These two messages are used to set and query the default item in a CCM. They should be sent to the SUBMENU window handle (miItem.hwndSubMenu), but I'm not sure if the former must be sent to it (the latter must). They have the following form:


MM_SETDEFAULTITEMID

mpParm1

sDefault (SHORT)

Specifies the default menu item.


bSearchSubmenus (BOOL)

Specifies whether or not submenus should be searched or not.


TRUE   Search submenus
FALSE   Do not search submenus

mpParm2 (BIT32)

Reserved

NULL   Reserved value.

Returns

bSuccess (BOOL)

Success indicator

TRUE   Successful completion
FALSE   Error occurred

Notes

Setting the default item id does not set the menu item's attribute to MIA_CHECKED. This responsibility is left to the programmer.


MM_QUERYDEFAULTITEMID

mpParm1 (BIT32)

Reserved

NULL   Reserved value.

mpParm2 (BIT32)

Reserved

NULL   Reserved value.

Returns

sDefault (SHORT)

Specifies the default item.

  • 0 - There is no default item associated with the submenu.
  • Other - Menu item id of the default item.

Encapsulation

All of this can be encapsulized into a single procedure. The code for this is shown below:


BOOL setCascadeDefault(HWND hwndMenu,
                       USHORT usSubmenu,
                       USHORT usDefault)
//---------------------------------------------
// This function sets the default menuitem for
// the specified CCM and checks the menuitem.
//
// Input:  hwndMenu - specifies the menu
//                    window handle.
//         usSubmenu - specifies the id of
//                     the cascade menu.
//         usDefault - specifies the id of
//                     the default menuitem.
// Returns:  TRUE if successful, FALSE otherwise.
//---------------------------------------------
{
   MENUITEM miItem;
   ULONG ulStyle;

   WinSendMsg(hwndMenu,
              MM_QUERYITEM,
              MPFROM2SHORT(usSubmenu,TRUE),
              MPFROMP(&miItem));
   ulStyle=WinQueryWindowULong(miItem.hwndSubMenu,
                               QWL_STYLE);
   ulStyle|=MS_CONDITIONALCASCADE;
   WinSetWindowULong(miItem.hwndSubMenu,
                     QWL_STYLE,ulStyle);

   WinSendMsg(miItem.hwndSubMenu,
              MM_SETDEFAULTITEMID,
              MPFROM2SHORT(usDefault,FALSE),
              0L);
   WinSendMsg(miItem.hwndSubMenu,
              MM_SETITEMATTR,
              MPFROM2SHORT(usDefault,FALSE),
              MPFROM2SHORT(MIA_CHECKED,MIA_CHECKED));

   return TRUE;
}

Dropping on a Printer

In the "Direct Manipulation" chapter of Volume 4 of the "Redbooks", it states that for the rendering mechanism DRM_PRINT the printer sends the source of the drag operation a DM_PRINTOBJECT message to print the object(s). That's all well and good, until you try to use the information in the "Programmer's Reference" to process this message. Well, direct manipulation is a real drag (pun intended) as it is, but even more so when the documentation is incorrect about a fundamental message used by the system.

The documentation states that mpParm1 will point to the DRAGINFO structure corresponding to the drag operation in progress. Bzzz! Thanks for playing, and Bob will tell you what your consolation prize is. One can glean what the true value of this parameter is by looking at the name assigned in the documentation - pDragItem. You guessed it; it points to the DRAGITEM structure corresponding to an item that was dropped on the printer object.

While this only makes sense, this poses an interesting design decision. If your application has a container and the user drags 10 items to the printer, you'll get 10 different DM_PRINTOBJECT messages. Since we all know that printing should be done in a separate thread, do we start 10 different threads to handle each message? What if 50 objects were dropped? 100?

This can easily get out of hand, and the system can easily run out of resources (or it did when I originally implemented it this way for one of my applications), so an alternative method of accomplishing this will need to be developed; this is left to the programmer, since the solutions are various and are specific to an application.

A second, more serious, problem is that of the second parameter, passed in mpParm2. It points to a PRINTDEST structure, which is defined in <os2def.h> as such:


   typedef struct _PRINTDEST {
      ULONG cb;
      LONG lType;
      PSZ pszToken;
      LONG lCount;
      PDEVOPENDATA pdopData;
      ULONG fl;
      PSZ pszPrinter;
   } PRINTDEST, *PPRINTDEST;

This should save you a lot of time, since the DEVOPENSTRUC structure is already initialized and is disguised as a DEVOPENDATA structure. However, when you try to use the pdosData field in your call to DevOpenDC, you get a PMERR_INV_DRIVER_DATA error. This is particularly funny, since the printer specified this as the data to use, yet won't accept it if you use it.

The only solution is to query the default driver data yourself using DevPostDeviceModes. But wait! You don't have the device name for the printer! Well, making the assumption that your machine doesn't have more than one printer attached to the queue you dropped the object on, you can use the SplQueryQueue function to determine the missing information. Sparing you the boring details, I'll cut-to-the-quick and will simply show you the procedure below:


BOOL initPrinter(HAB habAnchor,
                 PPRINTDEST ppdPrinter,
                 PDEVOPENSTRUCT pdosPrinter)

//---------------------------------------------
// This function will query the default driver
// data for the printer specified in ppdPrinter.
// It is the caller's responsibility to free
// the data using free().
//
// Input:  habAnchor - anchor block of the calling
//                     thread.
//         ppdPrinter - pointer to the PRINTDEST
//                      structure passed via
//                      the DM_PRINTOBJECT message.
// Output:  pdosPrinter - points to the variable
//                        which received the new
//                        DEVOPENSTRUC structure.
// Returns:  TRUE if successful, FALSE otherwise.
//------------------------------------------------
{
   ULONG ulNeeded;
   PPRQINFO3 ppiQueue;
   CHAR achDriver[64];
   CHAR achDevice[64];
   PCHAR pchPos;

   *pdosPrinter=*((PDEVOPENSTRUC)
                  (ppdPrinter->pdopData));

   SplQueryQueue(NULL,
                 pdosPrinter->pszLogAddress,
                 3,
                 NULL,
                 0,
                 &ulNeeded);

   ppiQueue=malloc(ulNeeded);
   if (ppiQueue==NULL) {
      return FALSE;
   } /* endif */

   SplQueryQueue(NULL,
                 pdosPrinter->pszLogAddress,
                 3,
                 ppiQueue,
                 ulNeeded,
                 &ulNeeded);

   strcpy(achDriver,ppiQueue->pszDriverName);
   free(ppiQueue);

   pchPos=strchr(achDriver,'.');
   if (pchPos!=NULL) {
      *pchPos=0;
      strcpy(achDevice,pchPos+1);
   } else {
      achDevice[0]=0;
   } /* endif */

   ulNeeded=DevPostDeviceModes(habAnchor,
                               NULL,
                               achDriver,
                               achDevice,
                               ppdPrinter->pszPrinter,
                               DPDM_QUERYJOBPROP);

   pdosPrinter->pdriv=malloc(ulNeeded);
   if (pdosPrinter->pdriv==NULL) {
      return FALSE;
   } /* endif */

   DevPostDeviceModes(habAnchor,
                      pdosPrinter->pdriv,
                      achDriver,
                      achDevice,
                      ppdPrinter->pszPrinter,
                      DPDM_QUERYJOBPROP);

   if ((ppdPrinter->fl & PD_JOB_PROPERTY)!=0) {
      DevPostDeviceModes(habAnchor,
                         pdosPrinter->pdriv,
                         achDriver,
                         achDevice,
                         NULL,
                         DPDM_POSTJOBPROP);
   } /* endif */

   return TRUE;
}

Message Box Help

Message boxes are an easy way to communicate something to the user from your PM application. However, one can only say so much in a message box, so PM allows you to add online help for each one. To correspond a particular message with a help panel, the panel resource id is specified in the 5th parameter and MB_HELP must be specified in the flags.

So how do you process the help requests? The documentation states that a help hook must be used, and that (because the Help Manager also uses a help hook) it should be installed before creating the help instance so that you can receive the help messages. Unfortunately, doing this won't work because the documentation is incorrect. If you look up the entry for WinSetHook, you'll read that this installs the hook at the head of the hook chain, so (doing a few abstract calculations in your head) you can see that setting your help hook before creating the help instance results in the Help Manager's hook being positioned before yours. Calling WinSetHook after WinAssociateHelpInstance will fix this problem and your help hook will start seeing the messages. Don't forget to return FALSE if the message isn't processed, or the Help Manager's hook won't see them!

As if this wasn't enough, the documentation on the help hook is incorrect also. It states that the second parameter (sMode) can have one of the following four values:

  • HFM_MENU
  • HFM_MB
  • HFM_WINDOW
  • HFM_APPLICATION

but none of these constants are defined in the toolkit! Instead, sMode can have one of the following three values:

  • HLPM_FRAME
  • HLPM_WINDOW
  • HLPM_MENU

HLPM_WINDOW and HLPM_MENU seem to correctly behave according to the documentation for HFM_WINDOW and HFM_MENU, respectively. There is no corresponding constant for HFM_MB though; instead, message box help requests have the mode HLPM_WINDOW with the topic number specifying the help panel id used in the WinMessageBox call.