home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / mclb.zip / lb.pak / MCLB.C < prev    next >
C/C++ Source or Header  |  1995-10-29  |  86KB  |  1,815 lines

  1. /***************************************************************************/
  2. /***************************************************************************/
  3. /*                        DISCLAIMER OF WARRANTIES.                        */
  4. /***************************************************************************/
  5. /***************************************************************************/
  6. /*                                                                         */
  7. /*  Copyright (C) 1995 IBM Corporation                                     */
  8. /*                                                                         */
  9. /*      DISCLAIMER OF WARRANTIES.  The following [enclosed] code is        */
  10. /*      sample code created by IBM Corporation. This sample code is not    */
  11. /*      part of any standard or IBM product and is provided to you solely  */
  12. /*      for  the purpose of assisting you in the development of your       */
  13. /*      applications.  The code is provided "AS IS", without               */
  14. /*      warranty of any kind.  IBM shall not be liable for any damages     */
  15. /*      arising out of your use of the sample code, even if they have been */
  16. /*      advised of the possibility of such damages.                        */
  17. /***************************************************************************/
  18. /*------- Multi-Column ListBox (MCLB) custom PM control ------------*/
  19. /*                                                                  */
  20. /* Author:            Mark McMillan                                 */
  21. /*                    IBM Corp.                                     */
  22. /*                    Research Triangle Park, NC                    */
  23. /*                    USA                                           */
  24. /*                                                                  */
  25. /* Original Concepts: Charles Cooper                                */
  26. /*                    Warwick Development Group                     */
  27. /*                    IBM UK Ltd., PO Box 31, Birmingham Road       */
  28. /*                                                                  */
  29. /* Copyright (c) IBM Corp. 1995                                     */
  30. /*------------------------------------------------------------------*/
  31.  
  32. /******************************************************************************/
  33. /* CHANGES                                                                    */
  34. /*   July 1995  : Complete rewrite                                            */
  35. /*   Aug 04 1995: Added "Size" field to all structures passed on              */
  36. /*                the WinCreateWindow() API.                                  */
  37. /*   Aug 17 1995: Added MCLBS_CUASELECT style to de-select all items on left  */
  38. /*                mouse click like the container does.                        */
  39. /*   Sep 15 1995: Fixed handling of LM_SELECTITEM with mp1=LIT_NONE           */
  40. /*   Oct 09 1995: Allow null InitSizes in MCLBINFO during create.  All column */
  41. /*                sizes will be equal.                                        */
  42. /******************************************************************************/
  43.  
  44. #define INCL_BASE
  45. #define INCL_PM
  46. #define INCL_WINSTDDRAG
  47.  
  48. #include <os2.h>
  49. #include <stdio.h>
  50. #include <stdlib.h>
  51. #include <string.h>
  52. #include <ctype.h>
  53. #include <stddef.h>
  54.  
  55. /*--- Macros ---*/
  56. #define QUERYSELECTION(ListHwnd, Index) (SHORT)WinSendMsg(ListHwnd, LM_QUERYSELECTION, MPFROMSHORT(Index), MPVOID)
  57.  
  58. /*--- Constants ---*/
  59. #define MCLB_CLASS     "MCLBCls"        /* Class name of MCLB control  */
  60. #define MCLB_CLASS_SEP "MCLBSep"        /* Class name of separators    */
  61. #define MCLB_CLASS_FRAME "MCLBFrm"      /* Class name of frame window  */
  62. #define RESERVED_SIZE  sizeof(PVOID)*2  /* Size of MCLBCls reserved window area */
  63. #define MAX_INT        32767            /* Max short integer */
  64. #define MCLB_FRAME_ID  MAX_INT          /* ID of frame window */
  65. #define TITLEMARGIN_LEFT   1            /* Margin on title text */
  66. #define TITLEMARGIN_RIGHT  1
  67.  
  68. /* Internally used messages -- must not collide with MCLB messages in MCLB.H  */
  69. #define MSG_PROCESS_SCROLL (WM_USER+500) /* Process an LN_SCROLL notification */
  70. #define MSG_FOCUSCHILD     (WM_USER+501) /* Set focus to a child window       */
  71.  
  72. typedef struct _GENERICLIST {           /* Generic linked-list structure */
  73.   ULONG  Size;                          /* Structures start with size of data */
  74.   struct _GENERICLIST *Next, *Prev;     /* All must have links as next 2 elements */
  75.   } GENERICLIST;
  76.  
  77. #include "mclb.h"                       /* Externalized definitions */
  78.  
  79. /* Internal definitions */
  80.  
  81. typedef struct _MCLBINSTDATA MCLBINSTDATA;
  82. typedef struct _MCLBCOLDATA  MCLBCOLDATA;
  83.  
  84. struct _MCLBINSTDATA {                  /* Per-MCLB instance data                                  */
  85.   ULONG  Size;                          /* Size (reqd since ptr is passed in WinCreateWindow())    */
  86.   MCLBCOLDATA *ColList;                 /* Head of linked list of per-column data                  */
  87.   char   *Title;                        /* Title strings                                           */
  88.   char   TitleFont[MAX_FONTLEN];        /* Title font (null for default system font)               */
  89.   char   ListFont[MAX_FONTLEN];         /* List  font (null for default system font)               */
  90.   ULONG  TitleBColor;                   /* Title background color                                  */
  91.   ULONG  TitleFColor;                   /* Title foreground color                                  */
  92.   ULONG  ListBColor;                    /* List  background color                                  */
  93.   ULONG  ListFColor;                    /* List  foreground color                                  */
  94.   char   TabChar;                       /* Data column separator character                         */
  95.   char   _Padd[3];                      /* Padding for TabChar to 4 bytes                          */
  96.   HWND   Parent, Owner;                 /* Parent and owner windows of MCLB                        */
  97.   HWND   MCLBHwnd;                      /* Control window handle                                   */
  98.   HWND   Frame;                         /* Title/frame window handle                               */
  99.   HWND   FocusChild;                    /* Last child window to have the focus                     */
  100.   ULONG  Style;                         /* MCLB + LS_* style flags                                 */
  101.   LONG   ListBoxCy;                     /* Vertical size of listboxes, in pixels                   */
  102.   LONG   UsableCx;                      /* Usable width of window (cx - 1 scrollbar - separators)  */
  103.   BOOL   InControl;                     /* Currently processing a WM_CONTROL message               */
  104.   BOOL   PPChanging;                    /* Currently changing all listboxs presentation params.    */
  105.   BOOL   AdjustingSize;                 /* Currently adjusting window size                         */
  106.   BOOL   Initializing;                  /* Currently creating/initializing the MCLB.               */
  107.   USHORT Cols;                          /* Number of columns                                       */
  108.   USHORT Id;                            /* Control ID                                              */
  109.   USHORT ControlCol;                    /* ID of column during WM_CONTROL processing               */
  110. };
  111.  
  112. struct _MCLBCOLDATA {                   /* Per-column data, linked list element                    */
  113.   ULONG  Size;                          /* Length of this structure                                */
  114.   struct _MCLBCOLDATA *Next, *Prev;     /* Forward/backward list pointers                          */
  115.   struct _MCLBCOLDATA *RightCol;        /* Ptr to col that follows visually, left to right         */
  116.   MCLBINSTDATA *InstData;               /* Ptr to main MCLB instance data                          */
  117.   HWND   BoxHwnd;                       /* Listbox control window handle                           */
  118.   HWND   SepHwnd;                       /* Separator to right of this column                       */
  119.   PFNWP  ListBoxProc;                   /* Original listbox window procedure                       */
  120.   char   *Title;                        /* Column title string                                     */
  121.   LONG   CurrSize;                      /* Current size (pixels) of this column                    */
  122.   ULONG  ColStyle;                      /* Column style (not currently used, could be used for non-sizable cols) */
  123.   ULONG  ColFColor;                     /* Column color (not currently used)                       */
  124.   ULONG  ColBColor;                     /* Column color (not currently used)                       */
  125.   BOOL   InScroll;                      /* Currently processing LN_SCROLL message                  */
  126.   USHORT ColNum;                        /* Column number (sequential), 1-based  -- also window ID of listbox */
  127. };
  128.  
  129. /* Internal function prototypes */
  130.  
  131. BOOL _Optlink    MCLBRegisterClasses(HAB Hab);
  132. void _Optlink    MCLBCreateColumn(HWND MCLBHwnd, ULONG Style, MCLBCOLDATA *ColData);
  133. void _Optlink    MCLBSizeChildren(MCLBINSTDATA *InstData, ULONG Cx, ULONG Cy);
  134. PVOID _Optlink   MCLBListInsert(PVOID Element, ULONG Size);
  135. void _Optlink    MCLBListDelete(PVOID Element);
  136. LONG _Optlink    MCLBColorIndex(HPS Hps, LONG RGBColor);
  137. void _Optlink    MCLBTracking(HWND hwnd, MCLBCOLDATA *LColData, USHORT Option);
  138. void _Optlink    MCLBSubStr(char *Source, char *Target, int WordNum, char Delim);
  139. void _Optlink    MCLBSelectAllColumns(MCLBINSTDATA *InstData, HWND SourceHwnd, SHORT Index, BOOL Select);
  140. SHORT _Optlink   MCLBLocateListboxItem( HWND hwnd, SHORT Y);
  141.  
  142. /* Window procedures */
  143. MRESULT EXPENTRY MCLBMainProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2);
  144. MRESULT EXPENTRY MCLBFrameProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2);
  145. MRESULT EXPENTRY MCLBSepProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2);
  146. MRESULT EXPENTRY MCLBListBoxSubclassProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2);
  147.  
  148.  
  149. /******************************************************************************/
  150. PVOID _Optlink MCLBListInsert(PVOID Element, ULONG Size)
  151. /******************************************************************************/
  152. /* Generic linked-list insert routine.  Given an element of a double-linked   */
  153. /* list, a new element will be inserted after it.  The new element will be    */
  154. /* allocated to the size specified.  The first two pointers of the data       */
  155. /* element are used for forward and backward pointers.  The remainder of the  */
  156. /* new element is initialized to binary zeros.                                */
  157. /******************************************************************************/
  158. {
  159. GENERICLIST *New, *Current;
  160.  
  161.   Current = (GENERICLIST *)Element;  /* Avoid lots of typcasting */
  162.  
  163.   /* Allocate new element of specified size and zero it */
  164.   New = malloc(Size);
  165.   memset(New, 0, Size);
  166.  
  167.   /* Insert new element after current element */
  168.   New->Size = Size;           /* Initialize first ULONG to size of element */
  169.   New->Next = Current->Next;  /* Fixup forward links */
  170.   Current->Next = New;
  171.   if (New->Next != NULL)      /* Fixup backward links */
  172.     (New->Next)->Prev = New;
  173.   New->Prev = Current;
  174.  
  175.   return New;
  176. }
  177.  
  178. /******************************************************************************/
  179. void _Optlink MCLBListDelete(PVOID Element)
  180. /******************************************************************************/
  181. /* Generic linked-list delete routine.  This routine will delete the element  */
  182. /* of the linked list specified.  The storage for the element will be freed.  */
  183. /* Note that there is no head-pointer fixup -- it is assumed the first node   */
  184. /* of the list is never deleted.  This simplifies the parameters and code at  */
  185. /* the slight expense of extra storage for one unused list element.           */
  186. /******************************************************************************/
  187. {
  188. GENERICLIST *Current;
  189.  
  190.   Current = (GENERICLIST *)Element;  /* Avoid lots of typcasting */
  191.  
  192.   /* Fixup forward links -- no need to test for head of list */
  193.   (Current->Prev)->Next = Current->Next;
  194.  
  195.   /* Fixup reverse links -- check for end of list */
  196.   if ((Current->Next) != NULL)
  197.     (Current->Next)->Prev = Current->Prev;
  198.    
  199.   free(Current);
  200. }
  201.  
  202. /*****************************************************************************/
  203. HWND EXPENTRY MCLBCreateWindow(
  204.                 HWND      Parent,   // Parent window
  205.                 HWND      Owner,    // Owner to recv messages
  206.                 ULONG     Style,    // Style flags (MCLBS_*)
  207.                 LONG      x,        // Window position
  208.                 LONG      y,
  209.                 LONG      cx,       // Window size
  210.                 LONG      cy,
  211.                 HWND      Behind,   // Place behind this window
  212.                 USHORT    Id,       // Window ID
  213.                 MCLBINFO  *Info)    // MCLB create structure
  214. /*****************************************************************************/
  215. /* This procedure creates the MCLB custom control window.                    */
  216. /*****************************************************************************/
  217. {
  218.   /* Register classes we need with PM */
  219.   if (!MCLBRegisterClasses(WinQueryAnchorBlock(Owner)))
  220.     return NULLHANDLE;
  221.  
  222.   /* Do some sanity checking before creating the window. */
  223.   if ((Info == NULL) ||            // Bad create pointer
  224.       (Info->Cols < 1) ||          // No columns
  225.       (Info->Titles == NULL) ||    // No titles
  226.       (Info->Size != sizeof(MCLBINFO))) // Incorrect structure size
  227.       return NULLHANDLE;
  228.  
  229.   /* Fixup some style flags for consistency */
  230.   if (Style & (MCLBS_CUASELECT | LS_MULTIPLESEL | LS_EXTENDEDSEL))
  231.     Style = Style | (LS_MULTIPLESEL | LS_EXTENDEDSEL);
  232.  
  233.   /* Create the main control window.  It will do all the */
  234.   /* initialization work during WM_CREATE.               */
  235.  
  236.   return WinCreateWindow(Parent,          // Parent of this control
  237.                          MCLB_CLASS,      // Our custom class
  238.                          "",              // No title
  239.                          Style,           // Our style flags
  240.                          x,y,             // Position
  241.                          cx,cy,           // Size
  242.                          Owner,           // Owner for messages
  243.                          Behind,          // Z order
  244.                          Id,              // ID
  245.                          Info,            // Ctrl data (mp2 in WM_CREATE)
  246.                          NULL);           // No pres params
  247. }
  248.  
  249. /*****************************************************************************/
  250. BOOL _Optlink MCLBRegisterClasses(HAB Hab)
  251. /*****************************************************************************/
  252. /* Register classes needed for the MCLB control.  Note we must always        */
  253. /* register the class, even if it already exists.  Otherwise an app using    */
  254. /* this control in a DLL may free and reload the DLL and the class           */
  255. /* window proc pointer would be out of date.  Reregistration will replace    */
  256. /* existing class parms.                                                     */
  257. /*****************************************************************************/
  258. {
  259.   WinRegisterClass(Hab,                           
  260.                    MCLB_CLASS_SEP,     // Separator windows
  261.                    (PFNWP)MCLBSepProc,
  262.                    CS_SIZEREDRAW,
  263.                    sizeof(PVOID));     // 1 ptr in window words
  264.  
  265.   WinRegisterClass(Hab,                           
  266.                    MCLB_CLASS_FRAME,   // Frame/title window
  267.                    (PFNWP)MCLBFrameProc,
  268.                    CS_SIZEREDRAW,
  269.                    sizeof(PVOID));     // 1 ptr in window words
  270.  
  271.   WinRegisterClass(Hab,
  272.                    MCLB_CLASS,         // Main control window
  273.                    (PFNWP)MCLBMainProc,
  274.                    CS_CLIPCHILDREN|CS_SIZEREDRAW,
  275.                    RESERVED_SIZE);
  276.   return TRUE;
  277. }
  278.  
  279.  
  280. /*****************************************************************************/
  281. MRESULT EXPENTRY MCLBMainProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
  282. /*****************************************************************************/
  283. /* Main window procedure for the MCLB class.  This window does no painting,  */
  284. /* all painting is done by child windows which are sized/positioned to cover */
  285. /* all of the window area.                                                   */
  286. /*****************************************************************************/
  287. {
  288. MCLBINSTDATA *InstData;
  289.  
  290.   /* Our inst data is +1 ptr in the window words, first ptr */
  291.   /* is reserved for the application to use.                */
  292.  
  293.   InstData = WinQueryWindowPtr(hwnd, sizeof(PVOID));
  294.  
  295.   switch (msg) {
  296.  
  297.     case WM_CREATE: {
  298.       MCLBINFO *Info;                  // Ptr to create data from caller
  299.       MCLBCOLDATA *ColData, *PrevCol;  // Per-column data
  300.       int      i;
  301.       char     *CurrTtl;               // For parsing title strings
  302.       SWP      Pos;
  303.  
  304.       /* Create and initialize the instance data */
  305.       InstData = malloc(sizeof(MCLBINSTDATA));
  306.       WinSetWindowPtr(hwnd, sizeof(PVOID), InstData);
  307.  
  308.       memset(InstData, 0x00, sizeof(MCLBINSTDATA));
  309.       /* Copy create parameters to the instance data */
  310.       Info = (MCLBINFO *)mp1;
  311.       InstData->Size        = sizeof(MCLBINSTDATA);
  312.       InstData->Initializing= TRUE;
  313.       InstData->MCLBHwnd    = hwnd;
  314.       InstData->Cols        = Info->Cols;
  315.       InstData->TabChar     = Info->TabChar;
  316.       InstData->Id          = WinQueryWindowUShort(hwnd, QWS_ID);
  317.       InstData->Style       = WinQueryWindowULong(hwnd, QWL_STYLE);
  318.       InstData->Parent      = WinQueryWindow(hwnd, QW_PARENT);
  319.       InstData->Owner       = WinQueryWindow(hwnd, QW_OWNER);
  320.       InstData->TitleBColor = Info->TitleBColor;
  321.       InstData->TitleFColor = Info->TitleFColor;
  322.       InstData->ListBColor  = Info->ListBColor;
  323.       InstData->ListFColor  = Info->ListFColor;
  324.       strcpy(InstData->TitleFont, Info->TitleFont);
  325.       strcpy(InstData->ListFont,  Info->ListFont);
  326.  
  327.       /* Create dummy head-of-list item */
  328.       InstData->ColList = malloc(sizeof(MCLBCOLDATA));
  329.       memset(InstData->ColList, 0x00, sizeof(MCLBCOLDATA));
  330.       InstData->ColList->Size = sizeof(MCLBCOLDATA);
  331.  
  332.       CurrTtl = malloc(strlen(Info->Titles)+1);  // Space to extract titles
  333.  
  334.       /* Create linked list of per-column data.  We use a linked */
  335.       /* list to make it easier to add column insert/delete      */
  336.       /* capabilites later (not currently implemented).          */
  337.  
  338.       ColData = InstData->ColList;
  339.       for (i=0; i<InstData->Cols; i++) {
  340.         PrevCol = ColData;                                      // Note previous column
  341.         ColData = MCLBListInsert(ColData, sizeof(MCLBCOLDATA)); // Create linked list node
  342.         if (Info->InitSizes != NULL)
  343.           ColData->CurrSize = Info->InitSizes[i];               // Caller set initial size
  344.         else
  345.           ColData->CurrSize = 100;                              // Make all same size
  346.         ColData->ColNum   = i+1;  // 1-based column number      // Column num is also window ID
  347.         ColData->InstData = InstData;                           // Save ptr to inst data also
  348.         MCLBSubStr(Info->Titles, CurrTtl, i+1, InstData->TabChar);
  349.         ColData->Title    = strdup(CurrTtl);                    // Save title of this column
  350.  
  351.         /* Update prev columns RightCol ptr to new column */
  352.         PrevCol->RightCol  = ColData;                           // Create forward node links
  353.         MCLBCreateColumn(hwnd, InstData->Style, ColData);       // Create the column window(s)
  354.  
  355.         /* Set column pres parameters if any supplied */
  356.         if ((InstData->ListFont)[0] != '\0')
  357.           WinSetPresParam(ColData->BoxHwnd, PP_FONTNAMESIZE, strlen(InstData->ListFont)+1, InstData->ListFont);
  358.         if (InstData->ListFColor != InstData->ListBColor) {
  359.           WinSetPresParam(ColData->BoxHwnd, PP_FOREGROUNDCOLOR, sizeof(LONG), &(InstData->ListFColor));
  360.           WinSetPresParam(ColData->BoxHwnd, PP_BACKGROUNDCOLOR, sizeof(LONG), &(InstData->ListBColor));
  361.         }
  362.  
  363.       }
  364.       free(CurrTtl);
  365.  
  366.       /* Create window for drawing title area and set presentation parameters. */
  367.       InstData->Frame = WinCreateWindow(hwnd,   // Parent of this control
  368.                          MCLB_CLASS_FRAME,// Our custom class
  369.                          "",              // No title
  370.                          WS_VISIBLE|WS_CLIPSIBLINGS, // Style flags
  371.                          0,0,0,0,         // Position
  372.                          hwnd,            // Owner for messages
  373.                          HWND_BOTTOM,     // Z order (under all other children)
  374.                          MCLB_FRAME_ID,   // ID
  375.                          InstData,        // Ctrl data (mp2 in WM_CREATE)
  376.                          NULL);           // No pres params
  377.  
  378.       if ((InstData->TitleFont)[0] != '\0')
  379.         WinSetPresParam(InstData->Frame, PP_FONTNAMESIZE, strlen(InstData->TitleFont)+1, InstData->TitleFont);
  380.  
  381.       if (InstData->TitleFColor != InstData->TitleBColor) {
  382.         WinSetPresParam(InstData->Frame, PP_FOREGROUNDCOLOR, sizeof(LONG), &(InstData->TitleFColor));
  383.         WinSetPresParam(InstData->Frame, PP_BACKGROUNDCOLOR, sizeof(LONG), &(InstData->TitleBColor));
  384.       }
  385.       else {
  386.         /* Use default colors */
  387.         InstData->TitleBColor = WinQuerySysColor(HWND_DESKTOP, SYSCLR_WINDOW, 0L);
  388.         InstData->TitleFColor = WinQuerySysColor(HWND_DESKTOP, SYSCLR_WINDOWTEXT, 0L);
  389.       }
  390.  
  391.       /* Finally, size and position the child windows we just created */
  392.  
  393.       WinQueryWindowPos(hwnd, &Pos);
  394.       MCLBSizeChildren(InstData, Pos.cx, Pos.cy);
  395.       InstData->Initializing = FALSE;
  396.       return (MRESULT)FALSE;
  397.       } // end of WM_CREATE
  398.  
  399.     case WM_DESTROY: {
  400.       MCLBCOLDATA *CurrCol;
  401.  
  402.       /* Delete each node in the column list */
  403.       for (CurrCol = InstData->ColList->Next; CurrCol!=NULL; CurrCol=InstData->ColList->Next) {
  404.         free(CurrCol->Title);     // Free title string
  405.         MCLBListDelete(CurrCol);  // Delete node of list, next comes to top of list
  406.       }
  407.  
  408.       /* Free dummy listhead node and instance data */
  409.       free(InstData->ColList);
  410.       free(InstData);
  411.       break;
  412.       }
  413.  
  414.     case WM_SIZE: 
  415.       /* MCLB window has been resized, so resize/pos children within it. */
  416.       MCLBSizeChildren(InstData, SHORT1FROMMP(mp2), SHORT2FROMMP(mp2));
  417.  
  418.       break;
  419.  
  420.     /*-------------------------------------------------------------------------*/
  421.     /* Process WM_CONTROL related messages from listbox children               */
  422.     /*-------------------------------------------------------------------------*/
  423.  
  424.     case WM_CONTROL: {
  425.       MRESULT Rc;
  426.       /* Received a notification messages from one of the list boxes.*/
  427.       /* We keep the ID (col num) of the listbox in our instance     */
  428.       /* data so it can be queried by the owner during processing    */
  429.       /* of the WM_CONTROL messages we send.  This is the only way   */
  430.       /* for the owner to know which column caused the WM_CONTROL.   */
  431.  
  432.       /* For most WM_CONTROL messages, we modify mp1 with the ID     */
  433.       /* of the MCLB and mp2 with the window handle so the message   */
  434.       /* appears to be comming from the MCLB window.                 */
  435.  
  436.       /* If we are currently processing a previous control message,  */
  437.       /* ignore this one (e.g. it was generated by our own actions). */
  438.       if (InstData->InControl)
  439.         return 0;
  440.  
  441.       InstData->InControl = TRUE; // Note we are now processing a WM_CONTROL
  442.       InstData->ControlCol= SHORT1FROMMP(mp1); // Note column that sent it
  443.  
  444.       switch (SHORT2FROMMP(mp1)) {
  445.         case LN_ENTER:
  446.         case LN_KILLFOCUS:
  447.         case LN_SETFOCUS:
  448.           /* For these messages, just pass it on to the owner changing the ID to */
  449.           /* the MCLB control ID, and putting our handle in mp2.                 */
  450.           Rc = WinSendMsg(InstData->Owner, WM_CONTROL,
  451.                      MPFROM2SHORT(InstData->Id, SHORT2FROMMP(mp1)),
  452.                      MPFROMHWND(hwnd));
  453.           break;
  454.  
  455.         case LN_SCROLL:
  456.           /* One of the listboxes is about to scroll (but has not done it yet).  */
  457.           /* We *post* a message to ourselves so we can look at the new scroll   */
  458.           /* position after the scroll is done.  Also pass up to owner.          */
  459.           WinPostMsg(hwnd, MSG_PROCESS_SCROLL, mp1, mp2);
  460.           Rc = WinSendMsg(InstData->Owner, WM_CONTROL,
  461.                      MPFROM2SHORT(InstData->Id, SHORT2FROMMP(mp1)),
  462.                      MPFROMHWND(hwnd));
  463.           break;
  464.  
  465.         case LN_SELECT: {
  466.           USHORT SelectCol;    // Column of original selection
  467.           SHORT  SelectIndex;  // Selected item
  468.           MCLBCOLDATA *CurrCol;
  469.  
  470.           /* An item was selected or de-selected in one of the listboxes.  For   */
  471.           /* single-select listboxes, just make the same selection in all the    */
  472.           /* other listboxes.                                                    */
  473.           SelectCol = SHORT1FROMMP(mp1);
  474.           if (!(InstData->Style & (LS_MULTIPLESEL|LS_EXTENDEDSEL))) {
  475.             SHORT TopIndex;    // Top item of selected column
  476.  
  477.             SelectIndex = (SHORT)WinSendMsg((HWND)mp2, LM_QUERYSELECTION, MPFROMSHORT(LIT_FIRST), MPVOID);
  478.             TopIndex    = (SHORT)WinSendMsg((HWND)mp2, LM_QUERYTOPINDEX, MPVOID, MPVOID);
  479.  
  480.             /* Now make same selection in all other listboxes.  Using the LM_SELECTITEM */
  481.             /* message can cause the listbox to scroll, so we reset the top index       */
  482.             /* after each insert.  This does cause a little flashing due to the scroll  */
  483.             /* but it is better than complete disable/repaint of the window.            */
  484.             for (CurrCol = InstData->ColList->Next; CurrCol!=NULL; CurrCol=CurrCol->Next) {
  485.               if (CurrCol->ColNum != SelectCol) {  // Don't select in original column
  486.                 WinSendMsg(CurrCol->BoxHwnd, LM_SELECTITEM,  MPFROMSHORT(SelectIndex), MPFROMSHORT(TRUE));
  487.                 WinSendMsg(CurrCol->BoxHwnd, LM_SETTOPINDEX, MPFROMSHORT(TopIndex), MPVOID);
  488.               }
  489.             }
  490.           } // end single select
  491.           else {
  492.             /* For multiple select, we must compare two columns -- the one on which a   */
  493.             /* select/deselect was made, and another with the previous selections in it.*/
  494.             /* PM does not tell us what item was selected or deselected, so the only    */
  495.             /* way to find out is to compare columns.  Ugh.                             */
  496.             HWND NewHwnd, OldHwnd;
  497.             SHORT  FindIndex;
  498.  
  499.             NewHwnd = (HWND)mp2;
  500.             if (SelectCol == 1)  // Locate some other column
  501.               OldHwnd = WinWindowFromID(hwnd, 2);
  502.             else
  503.               OldHwnd = WinWindowFromID(hwnd, 1);
  504.  
  505.             /* Find all selected items in current column and make sure they are selected */
  506.             /* in the other (old) columns.                                               */
  507.             SelectIndex = QUERYSELECTION(NewHwnd, LIT_FIRST);
  508.             if (SelectIndex == LIT_NONE)    // Easy -- deselect everything
  509.               MCLBSelectAllColumns(InstData, NewHwnd, LIT_NONE, FALSE);
  510.             else while (SelectIndex != LIT_NONE) {
  511.               if (SelectIndex == 0)
  512.                 FindIndex = LIT_FIRST;   // Backup one to find in other column
  513.               else
  514.                 FindIndex = SelectIndex-1;
  515.               if (QUERYSELECTION(OldHwnd, FindIndex) != SelectIndex) { // Not selected in old cols
  516.                 MCLBSelectAllColumns(InstData, NewHwnd, SelectIndex, TRUE);
  517.               }
  518.               SelectIndex = QUERYSELECTION(NewHwnd, SelectIndex);
  519.             }
  520.  
  521.             /* Search old column for selections not in new column (e.g.    */
  522.             /* this must have been a de-select action).                    */
  523.             SelectIndex = QUERYSELECTION(OldHwnd, LIT_FIRST);
  524.             if (SelectIndex != LIT_NONE) {  // Should always be true
  525.               while (SelectIndex != LIT_NONE) {
  526.                 if (SelectIndex == 0)
  527.                   FindIndex = LIT_FIRST;   // Backup one to find in other column
  528.                 else
  529.                   FindIndex = SelectIndex-1;
  530.                 if (QUERYSELECTION(NewHwnd, FindIndex) != SelectIndex) { // Not selected in new col
  531.                   MCLBSelectAllColumns(InstData, NewHwnd, SelectIndex, FALSE);
  532.                 }
  533.                 SelectIndex = QUERYSELECTION(OldHwnd, SelectIndex);
  534.               }
  535.             }
  536.           } // if multi-select
  537.  
  538.           /* Tell owner that a select occured */
  539.           Rc = WinSendMsg(InstData->Owner, WM_CONTROL,
  540.                      MPFROM2SHORT(InstData->Id, SHORT2FROMMP(mp1)),
  541.                      MPFROMHWND(hwnd));
  542.  
  543.           } // end LN_SELECT processing
  544.           break;
  545.  
  546.         default:
  547.           /* This a control message we don't recognize, just pass it */
  548.           /* up to the owner to handle.  Note we do not modify mp2   */
  549.           /* since it may be used for something other than the std   */
  550.           /* PM usage.                                               */
  551.           Rc = WinSendMsg(InstData->Owner, WM_CONTROL,
  552.                      MPFROM2SHORT(InstData->Id, SHORT2FROMMP(mp1)),
  553.                      mp2);
  554.  
  555.       } // switch on LN_* message
  556.  
  557.       InstData->InControl = FALSE;
  558.       return Rc;
  559.  
  560.       } // end WM_CONTROL
  561.  
  562.  
  563.     case MSG_PROCESS_SCROLL: {
  564.       /* This message is posted from an LN_SCROLL notification from one of the   */
  565.       /* listboxes.  By the time we get this the scrolling is done.  Mp1 and mp2 */
  566.       /* are passed from the original LN_SCROLL.  We now scroll all the listbox  */
  567.       /* controls to match the one that did the scrolling.                       */
  568.       SHORT NewTop;
  569.       HWND  OrigHwnd;
  570.       MCLBCOLDATA *CurrCol;
  571.  
  572.       OrigHwnd = WinWindowFromID(hwnd, SHORT1FROMMP(mp1));  // Listbox that generated LN_SCROLL
  573.  
  574.       NewTop = (SHORT)WinSendMsg(OrigHwnd, LM_QUERYTOPINDEX, MPVOID, MPVOID);
  575.  
  576.       /* Walk list of columns and set each topindex */
  577.       for (CurrCol = InstData->ColList->RightCol; CurrCol!=NULL; CurrCol=CurrCol->RightCol) {
  578.         if (NewTop != (SHORT)WinSendMsg(CurrCol->BoxHwnd, LM_QUERYTOPINDEX, MPVOID, MPVOID))
  579.           WinSendMsg(CurrCol->BoxHwnd, LM_SETTOPINDEX, MPFROMSHORT(NewTop), MPVOID);
  580.       }
  581.       return 0;
  582.       } // end of MSG_PROCESS_SCROLL
  583.  
  584.     case WM_MEASUREITEM:
  585.     case WM_DRAWITEM:
  586.       /* These messages are sent from the listbox controls for OWNERDRAW style */
  587.       /* listboxes.  The owner must handle them.  We place the column number   */
  588.       /* in SHORT 2 of mp1 which is unused by PM.                              */
  589.  
  590.       return WinSendMsg(InstData->Owner, msg, MPFROM2SHORT(InstData->Id, SHORT1FROMMP(mp1)), mp2);
  591.  
  592.     /*-------------------------------------------------------------------------*/
  593.     /* Process LM_* messages on behalf of listbox children                     */
  594.     /*-------------------------------------------------------------------------*/
  595.  
  596.     case LM_INSERTITEM: {
  597.       /* Application is inserting an item into the MCLB.  The message symantics */
  598.       /* are extended for MCLB to include the column by which to sort in SHORT2 */
  599.       /* of mp1 (unused by normal LM_INSERTITEM message).  The item text is     */
  600.       /* separated into columns by the tab value specified when the MCLB was    */
  601.       /* first created.                                                         */
  602.  
  603.       USHORT SortCol;    // Column number we must first insert to get sorting
  604.       SHORT  InsIndex;   // Index at which inserts must be done for other cols
  605.       MCLBCOLDATA *CurrCol;
  606.       char   *TextBuff;  // Buffer to extract text strings
  607.       int    i;
  608.  
  609.       /* Sanity check */
  610.       SortCol = max(1,SHORT2FROMMP(mp1));    // 1-based column number
  611.       if (SortCol > InstData->Cols)
  612.         return MRFROMSHORT(LIT_ERROR);
  613.  
  614.       /* Find the sort column.  It is the N'th in the (non-display order) list */
  615.       CurrCol = InstData->ColList->Next;
  616.       for (i=1; i<SortCol; i++)
  617.         CurrCol = CurrCol->Next;
  618.  
  619.       /* Get string to insert in the sort column */
  620.       TextBuff = malloc(strlen((char *)mp2)+1);
  621.       if (TextBuff == NULL)
  622.         return MRFROMSHORT(LIT_MEMERROR);
  623.       MCLBSubStr((char *)mp2, TextBuff, SortCol, InstData->TabChar);
  624.  
  625.       /* Insert it in the sort column and note index where it went (or err) */
  626.       /* In Warp, should only fail if >32K entries.                         */
  627.       InsIndex = (SHORT)WinSendMsg(CurrCol->BoxHwnd, LM_INSERTITEM, mp1, MPFROMP(TextBuff));
  628.       if ((InsIndex == LIT_ERROR) || (InsIndex == LIT_MEMERROR)) {
  629.         free(TextBuff);
  630.         return MRFROMSHORT(InsIndex);
  631.       }
  632.  
  633.       /* Now insert strings into other listboxes at same position */
  634.       for (CurrCol = InstData->ColList->Next; CurrCol!=NULL; CurrCol=CurrCol->Next) {
  635.         if (CurrCol->ColNum != SortCol) { // Don't insert sort column twice!
  636.           /* Get this columns text and insert it */
  637.           MCLBSubStr((char *)mp2, TextBuff, CurrCol->ColNum, InstData->TabChar);
  638.           WinSendMsg(CurrCol->BoxHwnd, LM_INSERTITEM, MPFROMSHORT(InsIndex), MPFROMP(TextBuff));
  639.         }
  640.       }
  641.  
  642.       /* Cleanup and return index of insertion to caller */
  643.       free(TextBuff);
  644.       return MRFROMSHORT(InsIndex);
  645.  
  646.       } // end LM_INSERTITEM
  647.  
  648.     #if defined(LM_INSERTMULTITEMS)  // New listbox msg for Warp 3.0
  649.     case LM_INSERTMULTITEMS: {
  650.       /* Application is inserting multiple items into the MCLB.  This message is   */
  651.       /* a bit of a problem because if sorting is specified then PM will change    */
  652.       /* the item order of (all) the listbox items and we will not be able to      */
  653.       /* keep the other columns in sync.  A performance-poor solution is to sort   */
  654.       /* each entry as it is added (somewhat defeating the purpose of this         */
  655.       /* high-performance multiple-insert message).  A fancier solution might be   */
  656.       /* to set the item handles before the insert to a sequence number, then      */
  657.       /* read them after the sort and discover the new order.  We would have to    */
  658.       /* save/restore the application's item handles somewhere else.  The          */
  659.       /* "real" solution is an MCLB-specific sorting message which is independant  */
  660.       /* of all the INSERT messages.                                               */
  661.  
  662.       /* So basically, the poor-mans solution is to just call LM_INSERT for each   */
  663.       /* message in the list using the sorting options specified.                  */
  664.  
  665.       /* Note we use one of the reserved slots in the LBOXINFO structure to spec   */
  666.       /* the sort column.                                                          */
  667.  
  668.       int    i;
  669.       SHORT  Rc;
  670.       LBOXINFO *MultInfo;   // Ptr to mult-insert info
  671.       char   **MultStrings; // Ptr to array of string pointers
  672.       BOOL   Enabled;       // Window was enabled
  673.  
  674.       MultInfo = PVOIDFROMMP(mp1);
  675.       MultStrings = PVOIDFROMMP(mp2);
  676.  
  677.       Enabled = WinIsWindowVisible(InstData->MCLBHwnd);
  678.       if (Enabled)
  679.         WinEnableWindowUpdate(InstData->MCLBHwnd, FALSE);
  680.  
  681.       for (i=0, Rc=0; ((i<MultInfo->ulItemCount) && (Rc>0)) ; i++) {
  682.         Rc=SHORTFROMMR(WinSendMsg(hwnd, LM_INSERTITEM, MPFROM2SHORT(MultInfo->lItemIndex, MultInfo->reserved2), MPFROMP(MultStrings[i])));
  683.       } /* endfor */
  684.  
  685.       if (Enabled)
  686.         WinEnableWindowUpdate(InstData->MCLBHwnd, TRUE);
  687.  
  688.       return Rc;
  689.       #endif  // Warp 3.0
  690.  
  691.     case LM_SETITEMTEXT: {
  692.       /* Set text of each column from delimited string.  Note order of fields */
  693.       /* is the numerical column order, not the display order.                */
  694.       MCLBCOLDATA *CurrCol;
  695.       char        *Buff;
  696.       MRESULT     Rc;
  697.  
  698.       Buff = malloc(strlen((char *)mp2)+1);
  699.  
  700.       for (CurrCol = InstData->ColList->Next; CurrCol!=NULL; CurrCol=CurrCol->Next) {
  701.         MCLBSubStr((char *)mp2, Buff, CurrCol->ColNum, InstData->TabChar);
  702.         Rc = WinSendMsg(CurrCol->BoxHwnd, LM_SETITEMTEXT, mp1, MPFROMP(Buff));
  703.       }
  704.       return Rc;
  705.       }
  706.  
  707.     case LM_DELETEALL:
  708.     case LM_DELETEITEM:
  709.     case LM_SETITEMHEIGHT:
  710.     case LM_SETTOPINDEX: {
  711.       /* Send these messages directly to each listbox for processing */
  712.       MCLBCOLDATA *CurrCol;
  713.       MRESULT     Rc;
  714.  
  715.       for (CurrCol = InstData->ColList->RightCol; CurrCol!=NULL; CurrCol=CurrCol->RightCol)
  716.         Rc = WinSendMsg(CurrCol->BoxHwnd, msg, mp1, mp2);
  717.       return Rc;
  718.       }
  719.  
  720.     case LM_QUERYITEMCOUNT: 
  721.     case LM_QUERYSELECTION: 
  722.     case LM_QUERYITEMHANDLE:
  723.     case LM_QUERYTOPINDEX:  
  724.     case LM_SETITEMHANDLE:  
  725.     case WM_QUERYCONVERTPOS:
  726.       /* These messages can be handled by any of the listboxes, so we use the first */
  727.       return WinSendMsg(InstData->ColList->RightCol->BoxHwnd, msg, mp1, mp2);
  728.  
  729.     case LM_SELECTITEM: {
  730.       /* Normally this message can be handled by any one listbox (e.g. first), but   */
  731.       /* in the case of mp1=LIT_NONE, PM does not send an LN_SELECT message so other */
  732.       /* columns don't automatically get updated.  In that case we must propagate    */
  733.       /* this message to every listbox.                                              */
  734.       MCLBCOLDATA *CurrCol;
  735.       MRESULT     Rc;
  736.       for (CurrCol = InstData->ColList->RightCol; CurrCol!=NULL; CurrCol=CurrCol->RightCol)
  737.         Rc = WinSendMsg(CurrCol->BoxHwnd, msg, mp1, mp2);
  738.       return Rc;
  739.       }
  740.  
  741.     case LM_QUERYITEMTEXTLENGTH: {
  742.       /* This message is interpreted to mean the length of all the columns in  */
  743.       /* a given row, including tab separator character.                       */
  744.       MCLBCOLDATA *CurrCol;
  745.       SHORT       Len, Sum;
  746.  
  747.       Sum = 0;
  748.       for (CurrCol = InstData->ColList->RightCol; CurrCol!=NULL; CurrCol=CurrCol->RightCol) {
  749.         Len = (SHORT)WinSendMsg(CurrCol->BoxHwnd, LM_QUERYITEMTEXTLENGTH, mp1, mp2);
  750.         if (Len == LIT_ERROR)
  751.           return MRFROMSHORT(LIT_ERROR);
  752.         Sum = Sum + Len + 1;  // Include a tab separator in count
  753.       }
  754.       return MRFROMSHORT(Sum-1);  // Don't count last tab or any trailing null
  755.       }
  756.  
  757.     case LM_QUERYITEMTEXT: {
  758.       /* Return all the columns text separated with tab chars */
  759.       MCLBCOLDATA *CurrCol;
  760.       SHORT       Len, Max;
  761.       char        *Buff;
  762.  
  763.       Max = SHORT2FROMMP(mp1);
  764.       Buff= (char *)mp2;
  765.  
  766.       for (CurrCol = InstData->ColList->RightCol; CurrCol!=NULL; CurrCol=CurrCol->RightCol) {
  767.         Len = (SHORT)WinSendMsg(CurrCol->BoxHwnd, LM_QUERYITEMTEXT, MPFROM2SHORT(SHORT1FROMMP(mp1),Max), MPFROMP(Buff));
  768.         Buff = Buff + Len;
  769.         Max  = Max - Len;
  770.         if ((Max > 0) && (CurrCol->RightCol != NULL)) {  // Add delimiter if room for it & more to come
  771.           *Buff = InstData->TabChar;
  772.           Buff++;
  773.           Max--;
  774.         }
  775.       }
  776.  
  777.       return MRFROMSHORT(SHORT2FROMMP(mp1)-Max);  // Len is original minus what is left
  778.       }
  779.  
  780.     case LM_SEARCHSTRING:
  781.       //... need to fix this to search full MCLB-wide column.  would also be nice
  782.       //... to have MCLB-specific message to search a single specified column.  for
  783.       //... now we just cheat and search the first one.
  784.       return WinSendMsg(InstData->ColList->RightCol->BoxHwnd, msg, mp1, mp2);
  785.  
  786.     /*-------------------------------------------------------------------------*/
  787.     /* Process MCLB-specific messages                                          */
  788.     /*-------------------------------------------------------------------------*/
  789.  
  790.     case MCLB_SETTITLES: {
  791.       char *CurrTtl;
  792.       MCLBCOLDATA *CurrCol;
  793.       int  i;
  794.  
  795.       /* mp1 is pointer to new title string, delimited by tab character */
  796.       /* in column (not display) order.                                 */
  797.  
  798.       CurrTtl = malloc(strlen((char *)mp1)+1);  // Space to extract titles
  799.  
  800.       for (CurrCol=InstData->ColList->Next, i=0; CurrCol!=NULL; CurrCol=CurrCol->Next, i++) {
  801.         MCLBSubStr((char *)mp1, CurrTtl, i+1, InstData->TabChar);
  802.         free(CurrCol->Title);              // free old title
  803.         CurrCol->Title = strdup(CurrTtl);  // set new title
  804.       }
  805.       free(CurrTtl);
  806.       return 0;
  807.       }
  808.  
  809.     case MCLB_SETTITLEFONT: 
  810.       /* mp1 is pointer to font name, or NULL to use default font */
  811.       if ((char *)mp1 == NULL)
  812.         return (MRESULT)WinRemovePresParam(InstData->Frame, PP_FONTNAMESIZE);
  813.       else
  814.         return (MRESULT)WinSetPresParam(InstData->Frame, PP_FONTNAMESIZE, strlen((char *)mp1)+1, (char *)mp1);
  815.  
  816.     case MCLB_SETTITLECOLORS:
  817.       /* mp1 is forground color, mp2 is background.  If same, use default colors */
  818.       if (mp1 == mp2) {
  819.         WinRemovePresParam(InstData->Frame, PP_FOREGROUNDCOLOR);
  820.         return (MRESULT)WinRemovePresParam(InstData->Frame, PP_BACKGROUNDCOLOR);
  821.       }
  822.       WinSetPresParam(InstData->Frame, PP_FOREGROUNDCOLOR, sizeof(LONG), &mp1);
  823.       return (MRESULT)WinSetPresParam(InstData->Frame, PP_BACKGROUNDCOLOR, sizeof(LONG), &mp2);
  824.  
  825.     case MCLB_SETLISTFONT:
  826.       /* mp1 is pointer to font name, or NULL to use default font. */
  827.       /* Just set the font on first list, it will set all others.  */
  828.       if ((char *)mp1 == NULL)
  829.         return (MRESULT)WinRemovePresParam(InstData->ColList->RightCol->BoxHwnd, PP_FONTNAMESIZE);
  830.       else
  831.         return (MRESULT)WinSetPresParam(InstData->ColList->RightCol->BoxHwnd, PP_FONTNAMESIZE, strlen((char *)mp1)+1, (char *)mp1);
  832.  
  833.     case MCLB_SETLISTCOLORS: {
  834.       int i;
  835.       MCLBCOLDATA *CurrCol;
  836.       BOOL Rc;
  837.  
  838.       /* mp1 is forground color, mp2 is background.  If same, use default colors */
  839.       /* If multi-color style we need to do this on each listbox.                */
  840.       if (InstData->Style & MCLBS_MULTICOLOR)
  841.         i = InstData->Cols;
  842.       else
  843.         i = 1;
  844.  
  845.       CurrCol = InstData->ColList->RightCol;
  846.       for (i=1; i<=InstData->Cols; i++) {
  847.         if (mp1 == mp2) {
  848.           WinRemovePresParam(CurrCol->BoxHwnd, PP_FOREGROUNDCOLOR);
  849.           Rc=WinRemovePresParam(CurrCol->BoxHwnd, PP_BACKGROUNDCOLOR);
  850.         }
  851.         else {
  852.           WinSetPresParam(CurrCol->BoxHwnd, PP_FOREGROUNDCOLOR, sizeof(LONG), &mp1);
  853.           Rc=WinSetPresParam(CurrCol->BoxHwnd, PP_BACKGROUNDCOLOR, sizeof(LONG), &mp2);
  854.         }
  855.         CurrCol = CurrCol->RightCol;
  856.       }
  857.       return (MRESULT)Rc;
  858.       }
  859.  
  860.     case MCLB_QUERYCTLCOL:
  861.       /* This is assumed to be sent during the processing of a WM_CONTROL */
  862.       /* message we have sent to the owner.  The owner can SEND us this   */
  863.       /* message to discover which column originated the WM_CONTROL msg.  */
  864.       return MRFROMSHORT(InstData->ControlCol);
  865.  
  866.     case MCLB_QUERYSTYLE:
  867.       /* Return (only) the MCLB style flags */
  868.       return MRFROMLONG(InstData->Style & MCLBS_MASK);
  869.  
  870.     case MCLB_QUERYFULLSIZE:
  871.       /* Return width (pixels) available for column data */
  872.       return MRFROMLONG(InstData->UsableCx);
  873.  
  874.     case MCLB_SETCOLSIZES: {
  875.       /* Set width of each column.  mp1 is ptr to array of LONG.  We assume      */
  876.       /* caller supplies an array with correct number of entries.  After setting */
  877.       /* new column sizes, we call our resize algorithm to update the display    */
  878.       /* and make sure columns fit into available display area.                  */
  879.       MCLBCOLDATA *CurrCol;
  880.       int         i;
  881.       SWP         Pos;
  882.  
  883.       for (i=0, CurrCol = InstData->ColList->Next; CurrCol!=NULL; i++, CurrCol=CurrCol->Next)
  884.         CurrCol->CurrSize = ((LONG *)mp1)[i];
  885.  
  886.       WinQueryWindowPos(hwnd, &Pos);
  887.       MCLBSizeChildren(InstData, Pos.cx, Pos.cy);
  888.       return 0;
  889.       }
  890.  
  891.     case MCLB_QUERYCOLSIZES: {
  892.       /* mp1 is a pointer to an array of LONGs.  We assume there are at least as */
  893.       /* many elements as columns.                                               */
  894.       LONG     *SizeList;
  895.       MCLBCOLDATA *CurrCol;
  896.       int      i;
  897.  
  898.       SizeList = (LONG *)PVOIDFROMMP(mp1);
  899.       for (i=0, CurrCol = InstData->ColList->Next; CurrCol!=NULL; i++, CurrCol=CurrCol->Next) {
  900.         /* Save this columns width */
  901.         SizeList[i] = CurrCol->CurrSize;
  902.       }
  903.  
  904.       return 0;
  905.       }
  906.  
  907.     case MCLB_QUERYINFO: {
  908.       /* mp1 is a pointer to an MCLBINFO structure.  We will fill it in with the */
  909.       /* current MCLB values.  The InitSizes array will be set to the current    */
  910.       /* column sizes, in pixels.  Note sender of this message must free the     */
  911.       /* title string and array of LONG size values.                             */
  912.       MCLBINFO *Info;
  913.       MCLBCOLDATA *CurrCol;
  914.       char     *Title;
  915.       int      i;
  916.  
  917.       Info = (MCLBINFO *)mp1;
  918.  
  919.       memset(Info, 0x00, sizeof(MCLBINFO));
  920.       Info->Cols = InstData->Cols;
  921.       Info->TabChar = InstData->TabChar;
  922.       Info->InitSizes = malloc(sizeof(LONG) * InstData->Cols);
  923.  
  924.       Title = strdup("");
  925.       for (i=0, CurrCol = InstData->ColList->Next; CurrCol!=NULL; i++, CurrCol=CurrCol->Next) {
  926.         /* Append this columns title to title string */
  927.         Title = realloc(Title, strlen(Title)+strlen(CurrCol->Title)+2);  // Get memory for this column title
  928.         strcat(Title, CurrCol->Title);
  929.         strcat(Title, &(InstData->TabChar));
  930.         /* Save this columns width */
  931.         Info->InitSizes[i] = CurrCol->CurrSize;
  932.       }
  933.       Title[strlen(Title)-1] = '\0';  // trim final separator
  934.       Info->Titles = Title;
  935.  
  936.       /* Get presentation parameters which are kept current in the instance data */
  937.       strcpy(Info->TitleFont, InstData->TitleFont);
  938.       strcpy(Info->ListFont, InstData->ListFont);
  939.       Info->TitleFColor = InstData->TitleFColor;
  940.       Info->TitleBColor = InstData->TitleBColor;
  941.       Info->ListFColor  = InstData->ListFColor;  // undefined for MCLBS_MULTICOLOR style
  942.       Info->ListBColor  = InstData->ListBColor;
  943.  
  944.       return 0;
  945.       }
  946.  
  947.     /*-------------------------------------------------------------------------*/
  948.     /* Process focus-change related messages                                   */
  949.     /*-------------------------------------------------------------------------*/
  950.  
  951.     case WM_FOCUSCHANGE:
  952.       /* Us or a child is getting/losing the focus.  Keep track of any children with */
  953.       /* focus so that we can process Alt/Ctl keystrokes properly.  We cannot        */
  954.       /* change focus during this message, so we post a msg to do it later.          */
  955.   
  956.       if ((BOOL)SHORT1FROMMP(mp2)) {               // MCLB is getting the focus
  957.         if (InstData->FocusChild == NULLHANDLE)    // first column is default
  958.           InstData->FocusChild = InstData->ColList->RightCol->BoxHwnd;
  959.         WinPostMsg(hwnd, MSG_FOCUSCHILD, MPVOID, MPVOID);
  960.       }
  961.       break;    // proceed with normal processing
  962.   
  963.     case MSG_FOCUSCHILD:
  964.       /* Message posted from WM_FOCUSCHANGE.  If we still have the focus, set the focus */
  965.       /* to the last child that had it (we never want the focus on the MCLB window      */
  966.       /* itself).                                                                       */
  967.   
  968.       if (WinQueryFocus(HWND_DESKTOP) == hwnd)
  969.         WinSetFocus(HWND_DESKTOP, InstData->FocusChild);
  970.       return 0;
  971.  
  972.  
  973.   } // switch on msg
  974.  
  975.   return WinDefWindowProc(hwnd, msg, mp1, mp2);
  976. }
  977.  
  978. /*****************************************************************************/
  979. MRESULT EXPENTRY MCLBFrameProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
  980. /*****************************************************************************/
  981. /* This window procedure is for the frame/title window of the MCLB control.  */
  982. /* This window is sized by the MCLBSizeChildren() function, and it is always */
  983. /* the full size of the control window.  Note this is not a "frame"          */
  984. /* window in the PM-sense, it just provides a visual frame around the MCLB.  */
  985. /*****************************************************************************/
  986. {
  987. MCLBINSTDATA *InstData;
  988.  
  989.   InstData = WinQueryWindowPtr(hwnd, 0L);
  990.  
  991.   switch (msg) {
  992.     case WM_CREATE:
  993.       InstData = (MCLBINSTDATA *)mp1;
  994.       WinSetWindowPtr(hwnd, 0L, InstData);
  995.       return (MRESULT)FALSE;
  996.  
  997.     case WM_BUTTON1DOWN:
  998.       /* Set focus to MCLB window */
  999.       WinSetFocus(HWND_DESKTOP, InstData->MCLBHwnd);
  1000.       return (MRESULT)TRUE;
  1001.  
  1002.     case WM_PRESPARAMCHANGED:
  1003.       if (!InstData->Initializing)   // Skip this when MCLB is being created
  1004.       switch (LONGFROMMP(mp1)) {
  1005.         case PP_FONTNAMESIZE: {
  1006.           SWP Pos;
  1007.           if (WinQueryPresParam(hwnd, PP_FONTNAMESIZE, 0L, NULL, MAX_FONTLEN, &InstData->TitleFont, QPF_NOINHERIT) == 0)
  1008.             (InstData->TitleFont)[0] = '\0';
  1009.           WinQueryWindowPos(InstData->MCLBHwnd, &Pos);
  1010.           MCLBSizeChildren(InstData, Pos.cx, Pos.cy);
  1011.           WinInvalidateRect(hwnd, NULL, FALSE);
  1012.           // Tell owner about the pres parm change
  1013.           WinSendMsg(InstData->Owner, WM_CONTROL, MPFROM2SHORT(InstData->Id, MCLBN_PPCHANGED),
  1014.                                                 MPFROM2SHORT(0, MCLBPP_FONT));
  1015.           }
  1016.           break;
  1017.         case PP_BACKGROUNDCOLOR:
  1018.           /* Save new color and redraw */
  1019.           WinQueryPresParam(hwnd, PP_BACKGROUNDCOLOR, 0L, NULL, sizeof(LONG), &InstData->TitleBColor, 0);
  1020.           WinInvalidateRect(hwnd, NULL, FALSE);
  1021.           // Tell owner about the pres parm change
  1022.           WinSendMsg(InstData->Owner, WM_CONTROL, MPFROM2SHORT(InstData->Id, MCLBN_PPCHANGED),
  1023.                                                   MPFROM2SHORT(0, MCLBPP_BACKCOLOR));
  1024.           break;
  1025.         case PP_FOREGROUNDCOLOR:
  1026.           /* Save new color and redraw */
  1027.           WinQueryPresParam(hwnd, PP_FOREGROUNDCOLOR, 0L, NULL, sizeof(LONG), &InstData->TitleFColor, 0);
  1028.           WinInvalidateRect(hwnd, NULL, FALSE);
  1029.           // Tell owner about the pres parm change
  1030.           WinSendMsg(InstData->Owner, WM_CONTROL, MPFROM2SHORT(InstData->Id, MCLBN_PPCHANGED),
  1031.                                                   MPFROM2SHORT(0, MCLBPP_FORECOLOR));
  1032.           break;
  1033.       }
  1034.       return 0;
  1035.  
  1036.     case WM_PAINT: {
  1037.       RECTL   Rect;
  1038.       POINTL  Point;
  1039.       HPS     Hps;
  1040.       SWP     Pos;
  1041.       SWP     LBPos;
  1042.       LONG    Top, Bottom;
  1043.       LONG    TitleColor;
  1044.       MCLBCOLDATA *CurrCol;
  1045.  
  1046.       Hps = WinBeginPaint(hwnd, NULLHANDLE, &Rect);
  1047.       WinQueryWindowPos(hwnd, &Pos);
  1048.       WinQueryWindowPos(InstData->ColList->Next->BoxHwnd, &LBPos);
  1049.       Top    = Pos.cy - 1;
  1050.  //   Bottom = InstData->ListBoxCy;
  1051.       Bottom = LBPos.cy;
  1052.  
  1053.       /* Fill title area with title background color */
  1054.       Rect.xLeft   = 0;
  1055.       Rect.xRight  = Pos.cx-1;
  1056.       Rect.yBottom = Bottom;
  1057.       Rect.yTop    = Top;
  1058.       WinFillRect(Hps, &Rect, MCLBColorIndex(Hps, InstData->TitleBColor));
  1059.  
  1060.       /* Match border color of listboxes to draw title border */
  1061.       GpiSetColor(Hps, SYSCLR_WINDOWFRAME);
  1062.       Point.x = 0;
  1063.       Point.y = Bottom;
  1064.       GpiMove(Hps, &Point);
  1065.       Point.y = Top;
  1066.       GpiLine(Hps, &Point);
  1067.       Point.x = Pos.cx - 1;
  1068.       GpiLine(Hps, &Point);
  1069.       Point.y = Bottom;
  1070.       GpiLine(Hps, &Point);
  1071.  
  1072.       /* Draw each title string centered over column listbox */
  1073.  
  1074.       TitleColor = MCLBColorIndex(Hps, InstData->TitleFColor);
  1075.       for (CurrCol = InstData->ColList->RightCol; CurrCol!=NULL; CurrCol=CurrCol->RightCol) {
  1076.         WinQueryWindowPos(CurrCol->BoxHwnd, &Pos);
  1077.         Rect.yBottom = Bottom;
  1078.         Rect.yTop    = Top;
  1079.         Rect.xLeft = Pos.x + TITLEMARGIN_LEFT;
  1080.         Rect.xRight= Pos.x + Pos.cx
  1081.                            - WinQuerySysValue(HWND_DESKTOP, SV_CXVSCROLL)
  1082.                            - 1
  1083.                            - TITLEMARGIN_RIGHT;
  1084.         GpiSetColor(Hps, TitleColor);
  1085.         WinDrawText(Hps, -1L, CurrCol->Title, &Rect, 0L, 0L, DT_CENTER|DT_VCENTER|DT_TEXTATTRS);
  1086.  
  1087.         /* For non-resizable MCLB, we extend the left border of the          */
  1088.         /* listbox through the title area since there is no separator window */
  1089.         if (InstData->Style & MCLBS_NOCOLRESIZE) {
  1090.           GpiSetColor(Hps, SYSCLR_WINDOWFRAME);
  1091.           Point.x = Pos.x;
  1092.           Point.y = Bottom -1;
  1093.           GpiMove(Hps, &Point);
  1094.           Point.y = Top;
  1095.           GpiLine(Hps, &Point);
  1096.         }
  1097.       }
  1098.  
  1099.       WinEndPaint(Hps);
  1100.       return 0;
  1101.       }
  1102.  
  1103.   } // switch on msg
  1104.  
  1105.   return WinDefWindowProc(hwnd, msg, mp1, mp2);
  1106. }
  1107.  
  1108. /*****************************************************************************/
  1109. MRESULT EXPENTRY MCLBSepProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
  1110. /*****************************************************************************/
  1111. /* Main window procedure for the separator window class.  This window        */
  1112. /* handles the resizing process.  Separator windows are not created when the */
  1113. /* MCLBS_NOCOLRESIZE style is specified.                                     */
  1114. /*****************************************************************************/
  1115. {
  1116. MCLBCOLDATA *ColData;
  1117.  
  1118.   ColData = WinQueryWindowPtr(hwnd, 0L);
  1119.  
  1120.   switch (msg) {
  1121.     case WM_CREATE:
  1122.       WinSetWindowPtr(hwnd, 0L, (MCLBCOLDATA *)mp1);
  1123.       break;
  1124.  
  1125.     case WM_MOUSEMOVE:
  1126.       /* When the mouse moves on the separator, change it to a dbl-arrow */
  1127.       WinSetPointer(HWND_DESKTOP,
  1128.                     WinQuerySysPointer(HWND_DESKTOP, SPTR_SIZEWE, FALSE));
  1129.       return (MRESULT)TRUE;
  1130.  
  1131.     case WM_BUTTON1DOWN:
  1132.       /* Display and process a tracking rectangle to resize the columns */
  1133.       MCLBTracking(hwnd, ColData, 0);
  1134.       return (MRESULT)TRUE;
  1135.  
  1136.     case WM_PAINT: {
  1137.       RECTL   Rect;
  1138.       POINTL  Point;
  1139.       HPS     Hps;
  1140.       SWP     Pos;
  1141.  
  1142.       Hps = WinBeginPaint(hwnd, NULLHANDLE, &Rect);
  1143.       WinQueryWindowPos(hwnd, &Pos);
  1144.       /* Draw edge connecting adjacent listboxes at bottom of window */
  1145.       Point.x = Pos.cx-1;
  1146.       Point.y = 0;
  1147.       GpiSetColor(Hps, CLR_WHITE);
  1148.       GpiLine(Hps, &Point);
  1149.  
  1150.       Rect.xLeft   = 0;
  1151.       Rect.yBottom = 1;
  1152.       Rect.xRight  = Pos.cx;  // WinDrawBorder() stays inside the rectangle
  1153.       Rect.yTop    = Pos.cy;
  1154.       WinDrawBorder(Hps, &Rect, 1L, 1L,
  1155.                     SYSCLR_WINDOWFRAME, SYSCLR_WINDOW,
  1156.                     DB_PATCOPY|DB_INTERIOR);
  1157.       WinEndPaint(Hps);
  1158.       return 0;
  1159.     }
  1160.  
  1161.   } // switch on msg
  1162.  
  1163.   return WinDefWindowProc(hwnd, msg, mp1, mp2);
  1164. }
  1165.  
  1166. /*****************************************************************************/
  1167. MRESULT EXPENTRY MCLBListBoxSubclassProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
  1168. /*****************************************************************************/
  1169. /* This is a subclass procedure for the MCLB listbox controls.  We just      */
  1170. /* watch for a few key messages here.                                        */
  1171. /*****************************************************************************/
  1172. {
  1173. MCLBCOLDATA *ColData;    // Data for this column
  1174. MCLBCOLDATA *CurrCol;    // Linked list of columns
  1175. MCLBINSTDATA *InstData;  // MCLB instance data
  1176.  
  1177. #define KEYDOWN(key) (WinGetKeyState(HWND_DESKTOP,key) & 0x8000)
  1178.  
  1179.   ColData = WinQueryWindowPtr(hwnd, 0L);
  1180.   InstData = ColData->InstData;
  1181.  
  1182.   switch (msg) {
  1183.       case WM_BUTTON1DOWN: {
  1184.         SHORT n, i, NewCursor;
  1185.  
  1186.         if (!(InstData->Style & MCLBS_CUASELECT))  // Do nothing special if not CUA style
  1187.           break;
  1188.  
  1189.         if (KEYDOWN(VK_SHIFT)| KEYDOWN(VK_CTRL))   // Let listbox handle CTRL and SHIFT selections
  1190.           break;
  1191.  
  1192.         /* Now this is a left-button selection of a listbox item.  For CUA-like    */
  1193.         /* selection we must de-select all other items in the listbox (normal      */
  1194.         /* listbox does this only if the item clicked on is not already selected). */
  1195.  
  1196.         NewCursor = MCLBLocateListboxItem( hwnd, SHORT2FROMMP(mp1) );
  1197.  
  1198.         n = (SHORT)WinSendMsg( hwnd, LM_QUERYITEMCOUNT, 0L, 0L );
  1199.         for (i=0; i<n; i++ ) 
  1200.           if (i != NewCursor)
  1201.             WinSendMsg(hwnd, LM_SELECTITEM, MPFROMSHORT(i), MPFROMSHORT(FALSE));
  1202.  
  1203.         break;
  1204.         }
  1205.  
  1206.     case WM_FOCUSCHANGE:
  1207.       /* Record the fact the we are the child that has focus */
  1208.       InstData->FocusChild = hwnd;
  1209.       break;
  1210.  
  1211.     case WM_BEGINSELECT:
  1212.     case WM_BEGINDRAG:
  1213.     case WM_ENDDRAG:
  1214.     case WM_ENDSELECT:
  1215.     case WM_CONTEXTMENU:
  1216.       /* Lisbox proc absorbes this but does not use it.  Send it to */
  1217.       /* our owner like the PM docs say it should be.  Otherwise    */
  1218.       /* cannot use direct-manipulation listbox (DMLB) subclass.    */
  1219.       return WinSendMsg(InstData->MCLBHwnd, msg, mp1, mp2);
  1220.  
  1221.     case WM_PRESPARAMCHANGED:
  1222.       if (!InstData->Initializing)   // Skip this when MCLB is being created
  1223.       switch (LONGFROMMP(mp1)) {
  1224.         case PP_FONTNAMESIZE: {
  1225.           SWP Pos;
  1226.           /* Must propagate font changes to all other listbox controls, but */
  1227.           /* not if this was caused by some other listboxs font change.     */
  1228.           if (InstData->PPChanging) // First listbox to change does everything
  1229.             break;
  1230.  
  1231.           /* Get new font name */
  1232.           if (WinQueryPresParam(hwnd, PP_FONTNAMESIZE, 0L, NULL, MAX_FONTLEN, &InstData->ListFont, QPF_NOINHERIT) == 0)
  1233.             (InstData->ListFont)[0] = '\0';
  1234.  
  1235.           /* Tell all other listboxes */
  1236.           InstData->PPChanging = TRUE;
  1237.           for (CurrCol=InstData->ColList->Next; CurrCol!=NULL; CurrCol=CurrCol->Next)
  1238.             WinSetPresParam(CurrCol->BoxHwnd, PP_FONTNAMESIZE, strlen(InstData->ListFont)+1, InstData->ListFont);
  1239.  
  1240.           InstData->PPChanging = FALSE;
  1241.           // Recaluclate sizes so listboxs can adjust to prevent clipping if
  1242.           // they are not LS_NOADJUSTPOS style.
  1243.           WinQueryWindowPos(InstData->MCLBHwnd, &Pos);
  1244.           MCLBSizeChildren(InstData, Pos.cx, Pos.cy);
  1245.           // Tell owner about the pres parm change
  1246.           WinSendMsg(InstData->Owner, WM_CONTROL, MPFROM2SHORT(InstData->Id, MCLBN_PPCHANGED),
  1247.                                                   MPFROM2SHORT(ColData->ColNum, MCLBPP_FONT));
  1248.           }
  1249.           break;
  1250.         case PP_BACKGROUNDCOLOR:
  1251.           /* Propagate colors to other listboxes if not multi-color style */
  1252.           if (!(InstData->PPChanging)) { // We are fist to get this, tell others if necessary
  1253.             InstData->PPChanging = TRUE;
  1254.             WinQueryPresParam(hwnd, PP_BACKGROUNDCOLOR, 0L, NULL, sizeof(LONG), &InstData->ListBColor, 0);
  1255.             if (!(InstData->Style & MCLBS_MULTICOLOR)) {
  1256.               for (CurrCol=InstData->ColList->Next; CurrCol!=NULL; CurrCol=CurrCol->Next)
  1257.                 WinSetPresParam(CurrCol->BoxHwnd, PP_BACKGROUNDCOLOR, sizeof(LONG), &InstData->ListBColor);
  1258.             }
  1259.             InstData->PPChanging = FALSE;
  1260.             // Tell owner about the pres parm change
  1261.             WinSendMsg(InstData->Owner, WM_CONTROL, MPFROM2SHORT(InstData->Id, MCLBN_PPCHANGED),
  1262.                                                     MPFROM2SHORT(ColData->ColNum, MCLBPP_BACKCOLOR));
  1263.           }
  1264.           break;
  1265.         case PP_FOREGROUNDCOLOR:
  1266.           /* Propagate colors to other listboxes if not multi-color style set */
  1267.           if (!(InstData->PPChanging)) { // We are fist to get this, tell others if necessary
  1268.             InstData->PPChanging = TRUE;
  1269.             WinQueryPresParam(hwnd, PP_FOREGROUNDCOLOR, 0L, NULL, sizeof(LONG), &InstData->ListFColor, 0);
  1270.             if (!(InstData->Style & MCLBS_MULTICOLOR)) {
  1271.               for (CurrCol=InstData->ColList->Next; CurrCol!=NULL; CurrCol=CurrCol->Next)
  1272.                 WinSetPresParam(CurrCol->BoxHwnd, PP_FOREGROUNDCOLOR, sizeof(LONG), &InstData->ListFColor);
  1273.             }
  1274.             InstData->PPChanging = FALSE;
  1275.             // Tell owner about the pres parm change
  1276.             WinSendMsg(InstData->Owner, WM_CONTROL, MPFROM2SHORT(InstData->Id, MCLBN_PPCHANGED),
  1277.                                                     MPFROM2SHORT(ColData->ColNum, MCLBPP_FORECOLOR));
  1278.           }
  1279.           break;
  1280.       }
  1281.       break;  // Pass on all pres param changes to listbox proc
  1282.  
  1283.     case WM_CHAR: {
  1284.       /* We watch for CTRL/ALT arrow keys so the user can move focus between      */
  1285.       /* columns and resize columns with the keyboard (no mouse).                 */
  1286.       USHORT KeyStatus;
  1287.       USHORT VirtKey;
  1288.       MCLBCOLDATA *ColTemp;
  1289.   
  1290.       KeyStatus = SHORT1FROMMP(mp1);
  1291.       VirtKey   = SHORT2FROMMP(mp2);
  1292.   
  1293.       if ((KeyStatus & (KC_VIRTUALKEY|KC_ALT|KC_KEYUP)) ==  // Virtual key, ALT pressed, downstroke
  1294.                        (KC_VIRTUALKEY|KC_ALT)) {
  1295.         switch (VirtKey) {
  1296.           case VK_LEFT:  /*----- ALT-Left keystroke -----*/
  1297.             /* Move focus 1 column left if possible */
  1298.             for (ColTemp=InstData->ColList->RightCol; ColTemp->RightCol!=NULL; ColTemp=ColTemp->RightCol) {
  1299.               if (ColTemp->RightCol->BoxHwnd == hwnd) {
  1300.                 WinSetFocus(HWND_DESKTOP, ColTemp->BoxHwnd);
  1301.                 break;
  1302.               }
  1303.             }   
  1304.             return (MRESULT)TRUE;
  1305.   
  1306.           case VK_RIGHT: /*----- ALT-Right keystroke -----*/
  1307.             /* Move focus 1 column right if possible */
  1308.             if (ColData->RightCol != NULL)
  1309.               WinSetFocus(HWND_DESKTOP, ColData->RightCol->BoxHwnd);
  1310.             return (MRESULT)TRUE;
  1311.         } // switch on virtual key
  1312.         break; // some other ALT key -- pass it on
  1313.       } // if ALT key pressed
  1314.   
  1315.       if ((KeyStatus & (KC_VIRTUALKEY|KC_CTRL|KC_KEYUP)) ==  // Virtual key, CTRL pressed, downstroke
  1316.                        (KC_VIRTUALKEY|KC_CTRL)) {
  1317.         switch (VirtKey) {
  1318.           case VK_LEFT:  /*----- CTRL-Left keystroke -----*/
  1319.           case VK_RIGHT: /*----- CTRL-Right keystroke -----*/
  1320.             /* Start tracking separator assoicated with this column */
  1321.               if (ColData->SepHwnd != NULLHANDLE)                   // It has a separator
  1322.                 MCLBTracking(ColData->SepHwnd, ColData, TF_SETPOINTERPOS);
  1323.             return (MRESULT)TRUE;                                   // All done
  1324.         } // switch on virtual key
  1325.         break; // some other CTRL key -- pass it on
  1326.       } // if CTRL key pressed
  1327.       break;
  1328.   
  1329.     } // end WM_CHAR processing
  1330.   }
  1331.  
  1332.   /* Call original listbox window proc */
  1333.   return (ColData->ListBoxProc)(hwnd, msg, mp1, mp2);
  1334. }
  1335.  
  1336.  
  1337. /*****************************************************************************/
  1338. void _Optlink MCLBSizeChildren(MCLBINSTDATA *InstData, ULONG Cx, ULONG Cy)
  1339. /*****************************************************************************/
  1340. /* Give an outer window size and position, calculate the size and position   */
  1341. /* of all the MCLB child windows.                                            */
  1342. /*****************************************************************************/
  1343. {
  1344.   LONG  SepCx;         // Width of separator windows
  1345.   LONG  VScrollCx;     // Width of vert scroll
  1346.   LONG  BoxCx;         // Width of a particular listbox
  1347.   HPS   Hps;           // Pres space for textbox measurements
  1348.   RECTL Rect;
  1349.   MCLBCOLDATA *CurrCol;
  1350.   LONG SumX;           // Sum of X sizes or delta-X size
  1351.   LONG DifX;           // Sum of X sizes or delta-X size
  1352. //LONG RemX;           // Remainder of equal distribution
  1353.   BOOL WasVisible;     // Window was visible during this operation
  1354.   USHORT Magic1,Magic2;// Magic adjustments for self-adjusting listbox style
  1355.   LONG HeadingCy;      // Minimum height needed for headings
  1356.  
  1357.   /* Set size of frame to entire window (always) */
  1358.   WinSetWindowPos(InstData->Frame, HWND_BOTTOM, 0, 0, Cx, Cy, SWP_MOVE|SWP_SIZE|SWP_ZORDER);
  1359.  
  1360.   if (InstData->Style & MCLBS_NOCOLRESIZE)
  1361.     SepCx = 1;         // Non-sizing separators are just 1 pixel wide
  1362.   else
  1363.     SepCx = WinQuerySysValue(HWND_DESKTOP, SV_CXSIZEBORDER);
  1364.  
  1365.   VScrollCx  = WinQuerySysValue(HWND_DESKTOP, SV_CXVSCROLL);
  1366.  
  1367.   /* Use WinDrawText() to see how big (vertically) the headings will be */
  1368.   /* by using 1.5X the font size.  Note headings may be drawn   larger  */
  1369.   /* if LS_NOADJUSTPOS style is not used (because listboxs may adjust   */
  1370.   /* themselves to a smaller size and we make up for it in the heading).*/
  1371.  
  1372.   Hps = WinGetPS(InstData->Frame);     // Use frame window fonts for this
  1373.   memset(&Rect, 0x00, sizeof(RECTL));
  1374.   Rect.yTop   = MAX_INT;
  1375.   Rect.xRight = MAX_INT;
  1376.   WinDrawText(Hps, -1, " ", &Rect, 0L, 0L, DT_QUERYEXTENT|DT_LEFT|DT_BOTTOM);
  1377.   HeadingCy = Rect.yTop - Rect.yBottom;
  1378.   HeadingCy += HeadingCy / 2;
  1379.   WinReleasePS(Hps);
  1380.  
  1381.   /* Listboxes are window size minus headings minus borders */
  1382.   InstData->ListBoxCy = Cy - HeadingCy - 1;
  1383.  
  1384.   /* Usable listbox area is width of windows minus 1 scrollbar, separators and left border */
  1385.   InstData->UsableCx = Cx - VScrollCx - (SepCx * ((InstData->Cols)-1)) - 1;
  1386.  
  1387.   /* If current column widths do not fit exactly in available space, */
  1388.   /* use style-specific algorithm to resize them.                    */
  1389.  
  1390.   for (SumX=0, CurrCol = InstData->ColList->RightCol; CurrCol!=NULL; CurrCol=CurrCol->RightCol)
  1391.     SumX = SumX + CurrCol->CurrSize;
  1392.   if (SumX != InstData->UsableCx) {
  1393.     switch (InstData->Style & MCLBS_SIZEMETHOD_MASK) {
  1394.       case MCLBS_SIZEMETHOD_CUSTOM: {
  1395.         //---------------------------------------------------------------------------------------
  1396.         LONG *ResizeData;
  1397.         int i;
  1398.     
  1399.         /* Build array with new size and each columns current size */
  1400.         ResizeData = malloc((InstData->Cols + 1)*sizeof(LONG));
  1401.         ResizeData[0] = InstData->UsableCx;
  1402.         for (CurrCol = InstData->ColList->RightCol, i=0; CurrCol!=NULL; CurrCol=CurrCol->RightCol, i++)
  1403.           ResizeData[i] = CurrCol->CurrSize;
  1404.     
  1405.         /* Let owner setup new column sizes.  If owner chooses not to, use */
  1406.         /* default proportional method.                                    */
  1407.         if ((BOOL)WinSendMsg(InstData->Owner, WM_CONTROL, MPFROM2SHORT(InstData->Id, MCLBN_CUSTOMSIZE), MPFROMP(ResizeData))) {
  1408.           for (CurrCol = InstData->ColList->RightCol, i=0; CurrCol!=NULL; CurrCol=CurrCol->RightCol, i++)
  1409.             CurrCol->CurrSize = ResizeData[i];
  1410.           free(ResizeData);
  1411.           break;
  1412.         }
  1413.         free(ResizeData);
  1414.         // NOTE: fall through next case for default behaviour
  1415.         }
  1416.       case MCLBS_SIZEMETHOD_PROP: {
  1417.         //---------------------------------------------------------------------------------------
  1418.         LONG Percent;
  1419.         Percent = (InstData->UsableCx*1000L) / SumX;
  1420.         DifX = 0;
  1421.         for (CurrCol = InstData->ColList->RightCol; CurrCol!=NULL; CurrCol=CurrCol->RightCol) {
  1422.           CurrCol->CurrSize = (CurrCol->CurrSize * Percent) / 1000;
  1423.       
  1424.           /* If column would exceed usable width, limit it to usable width.  */
  1425.           /* This can occure due to rounding errors in above calculations    */
  1426.           if (CurrCol->CurrSize + DifX > InstData->UsableCx)
  1427.             CurrCol->CurrSize = InstData->UsableCx - DifX;
  1428.       
  1429.           DifX = DifX + CurrCol->CurrSize;  // Track width used so far
  1430.         }
  1431.         break;
  1432.         }
  1433.       case MCLBS_SIZEMETHOD_LEFT: {
  1434.         //---------------------------------------------------------------------------------------
  1435.         DifX = InstData->UsableCx - SumX;      // How much change was there?
  1436.         /* If size increased, just widen 1st column by that much.  If size */
  1437.         /* decreased, shrink columns left to right down to zero.           */
  1438.         if (DifX >= 0)
  1439.           InstData->ColList->RightCol->CurrSize += DifX;
  1440.         else {
  1441.           for (CurrCol = InstData->ColList->RightCol; ((CurrCol!=NULL) && (DifX < 0L)); CurrCol=CurrCol->RightCol) {
  1442.             SumX = min(CurrCol->CurrSize, labs(DifX));  // How much can we take from this col
  1443.             CurrCol->CurrSize = CurrCol->CurrSize - SumX;
  1444.             DifX = DifX + SumX;                         // Move diff toward zero
  1445.           }
  1446.         }
  1447.         break;
  1448.         }
  1449.     //case MCLBS_SIZEMETHOD_EQUAL:
  1450.       ////---------------------------------------------------------------------------------------
  1451.       // This method works but is not all that useful.  The proportional method
  1452.       // generally gives better results and is less code.
  1453.       ////---------------------------------------------------------------------------------------
  1454.       //SumX = (InstData->UsableCx - SumX) / (LONG)InstData->Cols;  // per-column delta
  1455.       //RemX = (InstData->UsableCx - SumX) % (LONG)InstData->Cols;  // remainder
  1456.       //if (SumX < 0)
  1457.       //  RemX = -1L * labs(RemX);  // force neg remainder
  1458.       //else
  1459.       //  RemX = labs(RemX);        // force pos remainder
  1460.       //DifX = 0;
  1461.       //for (CurrCol = InstData->ColList->RightCol; CurrCol!=NULL; CurrCol=CurrCol->RightCol) {
  1462.       //  CurrCol->CurrSize = max(0L, CurrCol->CurrSize + SumX);
  1463.       //  if (RemX < 0) {
  1464.       //    CurrCol->CurrSize--;
  1465.       //    RemX++;
  1466.       //  }
  1467.       //  else if (RemX > 0) {
  1468.       //    CurrCol->CurrSize++;
  1469.       //    RemX--;
  1470.       //  }
  1471.       //  /* If column would exceed usable width, limit it to usable width.  */
  1472.       //  /* This can occure when some columns resize to <0 and we can only make them 0 */
  1473.       //  if (CurrCol->CurrSize + DifX > InstData->UsableCx)
  1474.       //    CurrCol->CurrSize = InstData->UsableCx - DifX;
  1475.       //
  1476.       //  DifX = DifX + CurrCol->CurrSize;  // Track width used so far
  1477.       //}
  1478.     } // switch on resize method
  1479.   }  // if usable size changed
  1480.         
  1481.   /* If updates would show on display, disable them until all done.  This */
  1482.   /* allows owner to disable the MCLB while several resize or pres param  */
  1483.   /* changes are done.                                                    */
  1484.   if (WinIsWindowVisible(InstData->MCLBHwnd)) {
  1485.     WinEnableWindowUpdate(InstData->MCLBHwnd, FALSE);
  1486.     WasVisible = TRUE;
  1487.   }
  1488.   else
  1489.     WasVisible = FALSE;
  1490.  
  1491.   /* Walk list of columns in visual order and set each size/position */
  1492.   SumX = 1;                     // Init running count
  1493.   if (InstData->Style & LS_NOADJUSTPOS) {
  1494.     Magic1 = 0;           // No magic adjustment reqd for exact-size listboxs
  1495.     Magic2 = 0;                 
  1496.   }
  1497.   else {
  1498.     Magic1 = 1;           // Ignore magic numbers... no logic to them at all, 
  1499.     Magic2 = 4;           //   just emperically derived values.               
  1500.   }
  1501.   for (CurrCol=InstData->ColList->RightCol; CurrCol!=NULL; CurrCol=CurrCol->RightCol) {
  1502.  
  1503.     /* Col sizes should fit exactly in available space, but if not */
  1504.     /* force-fit the last column.                                  */
  1505.     if (CurrCol->RightCol == NULL)
  1506.       CurrCol->CurrSize = (Cx-VScrollCx) - SumX;
  1507.  
  1508.     BoxCx = CurrCol->CurrSize;
  1509.  
  1510.     WinSetWindowPos(CurrCol->BoxHwnd,
  1511.                     HWND_TOP,
  1512.                     SumX-1+Magic1,             // Next pixel, minus border
  1513.                     Magic1,                    // Always at bottom
  1514.                     BoxCx+VScrollCx+1-Magic2,  // Width incl hidden scrollbar and border
  1515.                     InstData->ListBoxCy,       // All boxes same height
  1516.                     SWP_MOVE|SWP_SIZE);
  1517.      
  1518.     SumX = SumX + BoxCx;
  1519.  
  1520.     /* Put separator just to right of box, extending up through title area */
  1521.     /* (no sep window for last column or for NORESIZE style).              */
  1522.  
  1523.     if ((CurrCol->SepHwnd != NULLHANDLE) && (!(InstData->Style & MCLBS_NOCOLRESIZE))) {
  1524.       WinSetWindowPos(CurrCol->SepHwnd,
  1525.                       HWND_TOP,
  1526.                       SumX,               // Right of box
  1527.                       0,                  // Bottom of window
  1528.                       SepCx,              // Width
  1529.                       Cy,                 // Full window height
  1530.                       SWP_MOVE|SWP_SIZE|SWP_ZORDER);
  1531.     
  1532.       SumX = SumX + SepCx;                // Next open pixel
  1533.     }
  1534.     if (InstData->Style & MCLBS_NOCOLRESIZE) // Skip one pixel for noresize style
  1535.       SumX++;                                //   so left border will show
  1536.  
  1537.   } // while more columns
  1538.  
  1539.   if (WasVisible)  // reshow window if we disabled it
  1540.     WinEnableWindowUpdate(InstData->MCLBHwnd, TRUE);
  1541.  
  1542. }
  1543.  
  1544. /*****************************************************************************/
  1545. void _Optlink MCLBCreateColumn(HWND MCLBHwnd, ULONG Style, MCLBCOLDATA *ColData)
  1546. /*****************************************************************************/
  1547. /* Create one column of the MCLB.  Some of the ColData structure has already */
  1548. /* been filled in.                                                           */
  1549. /*****************************************************************************/
  1550. {
  1551. //HENUM HEnum;    /* Window enumeration handle  */
  1552. //HWND  Next;     /* Next window in enumeration */
  1553. LONG  LBStyle;  /* Listbox styles             */
  1554.  
  1555.   /* Enforce some listbox styles, remove our own from the bit flags */
  1556.   LBStyle = Style & ~MCLBS_MASK;   // Remove MCLB-specific style bits
  1557.   LBStyle = LBStyle |          // Add required styles
  1558.             WS_CLIPSIBLINGS |  // Clip other MCLB children
  1559.             WS_VISIBLE ;       // Make it visible
  1560.       //    LS_NOADJUSTPOS;    // Make it the exact specified size
  1561.  
  1562.   /* Create listbox control for this column */
  1563.   ColData->BoxHwnd = WinCreateWindow(
  1564.                         MCLBHwnd,          // MCLB window is parent
  1565.                         WC_LISTBOX,        // Std PM listbox class
  1566.                         "",                // No window text
  1567.                         LBStyle,           // Calculated style flags
  1568.                         0,0,0,0,           // Size/pos will be set later
  1569.                         MCLBHwnd,          // MCLB window is owner & gets all messages
  1570.                         HWND_TOP,          // Put on top of other children
  1571.                         ColData->ColNum,   // 1-based column number
  1572.                         NULL, NULL);       // No ctrl data or pres params
  1573.  
  1574.   if (ColData->BoxHwnd == NULLHANDLE)      // Quit if failure
  1575.     return;
  1576.  
  1577.   /* For this column, subclass the listbox, hide the vertical scrollbar, */
  1578.   /* and create the separator window to the right.  If this is the last  */
  1579.   /* column we don't hide or create a separator.                         */
  1580.  
  1581.   WinSetWindowPtr(ColData->BoxHwnd, 0L, ColData); // Set window ptr to column data
  1582.   ColData->ListBoxProc = WinSubclassWindow(ColData->BoxHwnd, (PFNWP)MCLBListBoxSubclassProc);
  1583.  
  1584.   if ((ColData->InstData->Cols != ColData->ColNum) &&      /* For non-last columns and              */
  1585.      (!(ColData->InstData->Style & MCLBS_NOCOLRESIZE))) {  /* resizable style controls only.        */
  1586.  
  1587.     // --- No need to hide scroll bars, all except last is covered by adjacent column
  1588.     //HEnum = WinBeginEnumWindows(ColData->BoxHwnd);         /* Enumerate the children of the listbox */
  1589.     //while ((Next=WinGetNextWindow(HEnum))!=NULLHANDLE)     /* Get the handle of the next window     */
  1590.     //  if (WinQueryWindowULong(Next,QWL_STYLE) & SBS_VERT)  /* If it is a vertical scrollbar         */
  1591.     //    WinShowWindow(Next, FALSE);                        /*     then hide it                      */
  1592.     //WinEndEnumWindows(HEnum);                              /* End the enumeration                   */
  1593.  
  1594.     /* Create separator window for column separator to right of this column */
  1595.  
  1596.     ColData->SepHwnd = WinCreateWindow(
  1597.                           MCLBHwnd,          // MCLB window is parent
  1598.                           MCLB_CLASS_SEP,    // Separator class name
  1599.                           "",                // No window text
  1600.                           WS_CLIPSIBLINGS |  // Clip other MCLB children
  1601.                           WS_VISIBLE,        // Make it visible
  1602.                           0,0,0,0,           // Size/pos will be set later
  1603.                           MCLBHwnd,          // MCLB window is owner & gets all messages
  1604.                           HWND_TOP,          // Put on top of other children
  1605.                           0,                 // ID is not of interest
  1606.                           ColData,           // Pass column data in mp1 of WM_CREATE
  1607.                           NULL);             // No pres params
  1608.  
  1609.   }  // if not last column
  1610.  
  1611. }
  1612.  
  1613. /*****************************************************************************/
  1614. LONG _Optlink MCLBColorIndex(HPS Hps, LONG RGBColor)
  1615. /*****************************************************************************/
  1616. /* Create an entry in the color table for the given RGB value.  The new      */
  1617. /* index is returned.  This allows a given RGB value to be selected by index.*/
  1618. /*****************************************************************************/
  1619. {
  1620. LONG ClrTableInfo[4];        // Color table info
  1621.  
  1622.   /* Get number of colors in current table */
  1623.   GpiQueryColorData(Hps, 4L, ClrTableInfo);
  1624.   /* Create new index for this RGB color */
  1625.   GpiCreateLogColorTable(Hps, 0L, LCOLF_CONSECRGB,
  1626.                          ClrTableInfo[QCD_LCT_HIINDEX] + 1,
  1627.                          1L,
  1628.                          &RGBColor);
  1629.  
  1630.   /* Return index of new entry */
  1631.   return ClrTableInfo[QCD_LCT_HIINDEX] + 1;
  1632. }
  1633.  
  1634. /*****************************************************************************/
  1635. void _Optlink MCLBTracking(HWND hwnd, MCLBCOLDATA *LColData, USHORT Option)
  1636. /*****************************************************************************/
  1637. /* Display and process a tracking rectangle for the given separator window.  */
  1638. /* The Option is used on the WinTrackRect() call.  The column data passed is */
  1639. /* for the column to the left of the separator.                              */
  1640. /*****************************************************************************/
  1641. {
  1642. TRACKINFO Track;                // PM tracking API data
  1643. MCLBCOLDATA *RColData;          // Column data for right of separator
  1644. MCLBINSTDATA *InstData;         // Ptr to overall control instance data
  1645. SWP       swpBoxL;              // Column left of separator
  1646. SWP       swpBoxR;              // Column right of separator
  1647. SWP       swpSep;               // Separator window
  1648. LONG      diff, vScroll;
  1649. LONG      temp;
  1650.  
  1651.   RColData = LColData->RightCol; // There is always a col to right of separator
  1652.   InstData = LColData->InstData; // Get ptr to control instance data area
  1653.  
  1654.   /* Get sizes of adjacent columns */
  1655.   WinQueryWindowPos(LColData->BoxHwnd, &swpBoxL);
  1656.   WinQueryWindowPos(RColData->BoxHwnd, &swpBoxR);
  1657.   WinQueryWindowPos(hwnd, &swpSep);
  1658.  
  1659.   /* Adjust sizes by vertical scroll bar width */
  1660.   vScroll = (USHORT)WinQuerySysValue(HWND_DESKTOP, SV_CXVSCROLL);
  1661.   swpBoxL.cx -= vScroll;
  1662.   swpBoxR.cx -= vScroll;
  1663.  
  1664.   /* Setup data for tracking API */
  1665.   Track.cxBorder = Track.cyBorder = swpSep.cx/2;  // Track width = 1/2 separator window
  1666.  
  1667.   Track.rclTrack.xLeft = swpSep.x;                // Initial tracking rectangle
  1668.   Track.rclTrack.xRight = swpSep.x + swpSep.cx;
  1669.   Track.rclTrack.yBottom = swpSep.y;
  1670.   Track.rclTrack.yTop = swpSep.y + swpSep.cy;
  1671.  
  1672.   /* Left bound of track is left edge of left column */
  1673.   temp = swpBoxL.x;
  1674.   Track.rclBoundary.xLeft = temp;
  1675.  
  1676.   /* Right bound is right edge of right column */
  1677.   temp = swpBoxR.x + swpBoxR.cx;
  1678.   Track.rclBoundary.xRight = temp;
  1679.  
  1680.   /* Top and bottom bounds are fixed */
  1681.   Track.rclBoundary.yBottom = swpSep.y;
  1682.   Track.rclBoundary.yTop = swpSep.y + swpSep.cy;
  1683.  
  1684.   /* Size of tracking rectangle is size of separator window */
  1685.   Track.ptlMaxTrackSize.x = Track.ptlMinTrackSize.x = swpSep.cx;
  1686.   Track.ptlMaxTrackSize.y = Track.ptlMinTrackSize.y = swpSep.cy;
  1687.  
  1688.   /* Keyboard increments */
  1689.   Track.cxGrid = Track.cxKeyboard = 2;
  1690.  
  1691.   /* Keep rectangle in the window and move all of it */
  1692.   Track.fs = TF_MOVE | TF_ALLINBOUNDARY | Option;
  1693.  
  1694.   /* Set pointer to dbl-arrow if we are using the keyboard */
  1695.   if (Option == TF_SETPOINTERPOS)
  1696.     WinSetPointer(HWND_DESKTOP, WinQuerySysPointer(HWND_DESKTOP, SPTR_SIZEWE, FALSE));
  1697.  
  1698.   /* Finally, let PM display and process the rectangle until user lets go   */
  1699.   /* using the control window as the tracking surface.                      */
  1700.  
  1701.   if (WinTrackRect(InstData->MCLBHwnd, NULLHANDLE, &Track)) {
  1702.      SWP Pos;
  1703.  
  1704.      diff = (USHORT)Track.rclTrack.xLeft - swpSep.x;
  1705.  
  1706.      /* Calculate new column widths */
  1707.  
  1708.      LColData->CurrSize = swpBoxL.cx + diff;
  1709.      RColData->CurrSize = swpBoxR.cx - diff;
  1710.      WinQueryWindowPos(InstData->MCLBHwnd, &Pos);
  1711.      MCLBSizeChildren(InstData, Pos.cx, Pos.cy);
  1712.  
  1713.      /* Notify MCLB owner that column widths changed */
  1714.      WinSendMsg(InstData->Owner, WM_CONTROL,
  1715.                 MPFROM2SHORT(InstData->Id, MCLBN_COLSIZED), 
  1716.                 MPFROM2SHORT(LColData->ColNum, 0));
  1717.  
  1718.   } // if tracking sucessfull
  1719. }
  1720.  
  1721. /*****************************************************************************/
  1722. void _Optlink MCLBSelectAllColumns(MCLBINSTDATA *InstData, HWND SourceHwnd, SHORT Index, BOOL Select)
  1723. /*****************************************************************************/
  1724. /* Select the Index item in all columns of the MCLB except for the Source    */
  1725. /* column.  Select controls select/deselect option.                          */
  1726. /*****************************************************************************/
  1727. {
  1728. MCLBCOLDATA *CurrCol;
  1729.  
  1730.   /* Walk list of columns setting selection on all but source */
  1731.   CurrCol = InstData->ColList->RightCol; // First col in list
  1732.   while (CurrCol != NULL) {
  1733.     if (CurrCol->BoxHwnd != SourceHwnd)
  1734.       WinSendMsg(CurrCol->BoxHwnd, LM_SELECTITEM,
  1735.                                    MPFROMSHORT(Index), MPFROMSHORT(Select));
  1736.     CurrCol = CurrCol->RightCol;
  1737.   }
  1738. }
  1739.  
  1740.  
  1741. /*****************************************************************************/
  1742. void _Optlink MCLBSubStr(char *Source, char *Target, int WordNum, char Delim)
  1743. /*****************************************************************************/
  1744. /* Extract the WordNum'th delimited string from Source and copy to Target.   */
  1745. /* Word number is 1-based.  Source is not altered.                           */
  1746. /*****************************************************************************/
  1747. {
  1748. int i;
  1749.  
  1750.   for (i=1; (i<WordNum); i++) {  // Skip StrNum-1 delimiters
  1751.     while ((*Source!=Delim) && (*Source!='\0'))
  1752.       Source++;
  1753.     if (*Source!='\0')  // Skip delimiter, if one
  1754.       Source++;
  1755.   }
  1756.  
  1757.   while ((*Source!=Delim) && (*Source!='\0')) { // Copy up to delim or end
  1758.     *Target = *Source;
  1759.     Target++;
  1760.     Source++;
  1761.   }
  1762.  
  1763.   *Target = '\0';  // Append trailing null
  1764. }
  1765.  
  1766.  
  1767. /*----------------------------------------------------------------------*/
  1768. SHORT _Optlink MCLBLocateListboxItem( HWND hwnd, SHORT Y)
  1769. /*----------------------------------------------------------------------*/
  1770. /* Parameters:                                                          */
  1771. /*   hwnd         Listbox of interest                                   */
  1772. /*   Y            Y position of the pointer                             */
  1773. /*----------------------------------------------------------------------*/
  1774. /* Given a Y window coordinate, this function will calculate the item   */
  1775. /* number of the listbox item at that position.  This is a short version*/
  1776. /* of DMLBLocateListboxItem().                                          */
  1777. /*----------------------------------------------------------------------*/
  1778. {
  1779.    RECTL   Rect;
  1780.    POINTL  Points[2];
  1781.    HPS     hps;
  1782.    LONG    VertSize;
  1783.    SHORT   ItemNum;
  1784.  
  1785.    /* If this is an OWNERDRAW listbox, ask owner how big items are.  */
  1786.  
  1787.    VertSize = 0;
  1788.    if (WinQueryWindowULong(hwnd, QWL_STYLE) & LS_OWNERDRAW)
  1789.      VertSize = SHORT1FROMMR(                               // Take returned Height
  1790.                 WinSendMsg(WinQueryWindow(hwnd, QW_OWNER),  // Query owner of listbox
  1791.                    WM_MEASUREITEM,                          // Ask owner the size
  1792.                    MPFROMSHORT(WinQueryWindowUShort(hwnd, QWS_ID)),  // Listbox ID
  1793.                    MPFROMSHORT(0)));                        // First item
  1794.  
  1795.    if (VertSize == 0) {
  1796.      /* For a normal listbox, items are the size of the font.  To */
  1797.      /* determine the size we get the bounding box of a space.    */
  1798.      hps = WinGetPS(hwnd);
  1799.      GpiQueryTextBox(hps, 1L, " ", 2, Points);
  1800.      VertSize = Points[TXTBOX_TOPLEFT].y - Points[TXTBOX_BOTTOMLEFT].y;
  1801.      WinReleasePS(hps);
  1802.    }
  1803.  
  1804.    WinQueryWindowRect(hwnd, &Rect);
  1805.    Rect.yTop = Rect.yTop-2;         /* Listbox frame is 2 pixels */
  1806.  
  1807.    /* Calculate item number of item under the pointer */
  1808.    ItemNum = (SHORT)WinSendMsg(hwnd, LM_QUERYTOPINDEX, 0L, 0L)
  1809.              + ((Rect.yTop-Y)/VertSize);
  1810.  
  1811.    /* Return item under pointer */
  1812.    return ItemNum;
  1813. }
  1814.   
  1815.