Index: trunk/src/helpers/cctl_combo.c
===================================================================
--- trunk/src/helpers/cctl_combo.c	(revision 128)
+++ trunk/src/helpers/cctl_combo.c	(revision 128)
@@ -0,0 +1,724 @@
+
+/*
+ *@@sourcefile cctl_combo.c:
+ *      super combo box control, which is, essentially, a
+ *      subclassed entry field with a list box attached to
+ *      it.
+ *
+ *      Compared to the standard PM combo box, this one has a
+ *      number of advantages:
+ *
+ *      --  It doesn't require the parent window to have the
+ *          WS_CLIPCHILDREN style bit cleared.
+ *
+ *      --  It has proper window positioning; the size of the
+ *          enhanced combo is the size of the entry field, while
+ *          the PM combo wants to have the size of the expanded
+ *          list box too, which isn't easy to handle.
+ *
+ *      See ctlComboFromEntryField for details.
+ *
+ *      Note: Version numbering in this file relates to XWorkplace version
+ *            numbering.
+ *
+ *@@header "helpers\comctl.h"
+ *@@added V0.9.16 (2002-01-01) [umoeller]
+ */
+
+/*
+ *      Copyright (C) 2001-2002 Ulrich Mller.
+ *      This file is part of the "XWorkplace helpers" source package.
+ *      This is free software; you can redistribute it and/or modify
+ *      it under the terms of the GNU General Public License as published
+ *      by the Free Software Foundation, in version 2 as it comes in the
+ *      "COPYING" file of the XWorkplace main distribution.
+ *      This program is distributed in the hope that it will be useful,
+ *      but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *      GNU General Public License for more details.
+ */
+
+#define OS2EMX_PLAIN_CHAR
+    // this is needed for "os2emx.h"; if this is defined,
+    // emx will define PSZ as _signed_ char, otherwise
+    // as unsigned char
+
+#define INCL_DOSEXCEPTIONS
+#define INCL_DOSPROCESS
+#define INCL_DOSSEMAPHORES
+#define INCL_DOSERRORS
+
+#define INCL_WINWINDOWMGR
+#define INCL_WINFRAMEMGR
+#define INCL_WININPUT
+#define INCL_WINPOINTERS
+
+#define INCL_WINBUTTONS
+#define INCL_WINENTRYFIELDS
+#define INCL_WINLISTBOXES
+
+#define INCL_GPILOGCOLORTABLE
+#define INCL_GPIBITMAPS
+#include <os2.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <setjmp.h>             // needed for except.h
+#include <assert.h>             // needed for except.h
+
+#include "setup.h"                      // code generation and debugging options
+
+#include "helpers\except.h"             // exception handling
+#include "helpers\winh.h"
+
+#include "helpers\comctl.h"
+
+#pragma hdrstop
+
+/*
+ *@@category: Helpers\PM helpers\Window classes\Super combo box
+ *      See cctl_combo.c.
+ */
+
+/* ******************************************************************
+ *
+ *   Super Combination Box control
+ *
+ ********************************************************************/
+
+#define COMBO_BUTTON_WIDTH      20
+
+#define ID_COMBO_BUTTON         1001
+#define ID_COMBO_LISTBOX        1002
+
+/*
+ *@@ COMBODATA:
+ *
+ *@@added V0.9.9 (2001-03-17) [umoeller]
+ */
+
+typedef struct _COMBODATA
+{
+    PFNWP       pfnwpOrigEntryField,
+                pfnwpOrigButton;
+    ULONG       flStyle;
+
+    // position of entire combo
+    LONG        x,
+                y,
+                cx,
+                cy;
+
+    HWND        hwndButton,
+                hwndListbox;
+
+    HBITMAP     hbmButton;
+    SIZEL       szlButton;          // bitmap dimensions
+
+} COMBODATA, *PCOMBODATA;
+
+/*
+ *@@ PaintButtonBitmap:
+ *
+ *@@added V0.9.9 (2001-03-17) [umoeller]
+ */
+
+VOID PaintButtonBitmap(HWND hwnd,
+                       PCOMBODATA pcd)
+{
+    HPS hps;
+    RECTL rcl;
+    POINTL ptlDest;
+
+    hps = WinGetPS(hwnd);
+    WinQueryWindowRect(hwnd, &rcl);
+
+    ptlDest.x = (rcl.xRight - pcd->szlButton.cx) / 2;
+    ptlDest.y = (rcl.yTop - pcd->szlButton.cy) / 2;
+    WinDrawBitmap(hps,
+                  pcd->hbmButton,
+                  NULL,
+                  &ptlDest,
+                  0, 0,
+                  DBM_NORMAL);
+
+    WinReleasePS(hps);
+}
+
+/*
+ *@@ fnwpSubclassedComboButton:
+ *      window proc the combobox's button is subclassed with.
+ *      This is only for WM_PAINT because BN_PAINT is really
+ *      not that great for painting a button that looks like
+ *      a standard button.
+ *
+ *@@added V0.9.9 (2001-03-17) [umoeller]
+ */
+
+MRESULT EXPENTRY fnwpSubclassedComboButton(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
+{
+    MRESULT mrc = 0;
+    PCOMBODATA pcd;
+
+    switch (msg)
+    {
+        case WM_PAINT:
+            if (pcd = (PCOMBODATA)WinQueryWindowPtr(hwnd, QWL_USER))
+            {
+                mrc = pcd->pfnwpOrigButton(hwnd, msg, mp1, mp2);
+
+                PaintButtonBitmap(hwnd, pcd);
+            }
+        break;
+
+        /*
+         * default:
+         *
+         */
+
+        default:
+            if (pcd = (PCOMBODATA)WinQueryWindowPtr(hwnd, QWL_USER))
+                mrc = pcd->pfnwpOrigButton(hwnd, msg, mp1, mp2);
+        break;
+    }
+
+    return (mrc);
+}
+
+/*
+ *@@ ShowListbox:
+ *
+ *@@added V0.9.9 (2001-03-17) [umoeller]
+ */
+
+VOID ShowListbox(HWND hwnd,      // in: subclassed entry field
+                 PCOMBODATA pcd,
+                 BOOL fShow)    // in: TRUE == show, FALSE == hide
+{
+    BOOL fHilite = FALSE;
+
+    if (fShow)
+    {
+        // list box is invisible:
+        SWP swp;
+        POINTL ptl;
+        WinQueryWindowPos(hwnd, &swp);
+
+        _Pmpf(("showing lb"));
+
+        // convert to desktop
+        ptl.x = swp.x;
+        ptl.y = swp.y;
+        WinMapWindowPoints(WinQueryWindow(hwnd, QW_PARENT), // from
+                           HWND_DESKTOP,    // to
+                           &ptl,
+                                // SWP.y comes before SWP.x
+                           1);
+
+        WinSetWindowPos(pcd->hwndListbox,
+                        HWND_TOP,
+                        ptl.x + COMBO_BUTTON_WIDTH,
+                        ptl.y - 100,
+                        swp.cx,
+                        100,
+                        SWP_MOVE | SWP_SIZE | SWP_ZORDER | SWP_NOREDRAW);
+        WinSetParent(pcd->hwndListbox,
+                     HWND_DESKTOP,
+                     TRUE);        // redraw
+
+        // set focus to subclassed entry field in any case;
+        // we never let the listbox get the focus
+        WinSetFocus(HWND_DESKTOP, hwnd);
+
+        fHilite = TRUE;
+    }
+    else
+    {
+        // list box is showing:
+        HWND hwndFocus = WinQueryFocus(HWND_DESKTOP);
+        _Pmpf(("hiding listbox"));
+
+        WinSetParent(pcd->hwndListbox,
+                     HWND_OBJECT,
+                     TRUE);         // redraw now
+        // give focus back to entry field
+        if (hwndFocus == pcd->hwndListbox)
+            WinSetFocus(HWND_DESKTOP, hwnd);
+    }
+
+    WinSendMsg(pcd->hwndButton,
+               BM_SETHILITE,
+               (MPARAM)fHilite,
+               0);
+    PaintButtonBitmap(pcd->hwndButton, pcd);
+}
+
+/*
+ *@@ fnwpComboSubclass:
+ *
+ *@@added V0.9.9 (2001-03-17) [umoeller]
+ */
+
+MRESULT EXPENTRY fnwpComboSubclass(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
+{
+    MRESULT     mrc = 0;
+    PCOMBODATA  pcd;
+
+    switch (msg)
+    {
+        /*
+         * WM_ADJUSTWINDOWPOS:
+         *
+         */
+
+        case WM_ADJUSTWINDOWPOS:
+            if (pcd = (PCOMBODATA)WinQueryWindowPtr(hwnd, QWL_USER))
+            {
+                PSWP pswp = (PSWP)mp1;
+
+                if (pswp->fl & SWP_SIZE)
+                    // if we're being sized, make us smaller so that
+                    // there's room for the button
+                    pswp->cx -= COMBO_BUTTON_WIDTH;
+
+                mrc = pcd->pfnwpOrigEntryField(hwnd, msg, mp1, mp2);
+            }
+        break;
+
+        /*
+         * WM_WINDOWPOSCHANGED:
+         *
+         */
+
+        case WM_WINDOWPOSCHANGED:
+            if (pcd = (PCOMBODATA)WinQueryWindowPtr(hwnd, QWL_USER))
+            {
+                PSWP pswpNew = (PSWP)mp1;
+
+                if (pswpNew->fl & (SWP_SIZE | SWP_MOVE))
+                {
+                    // moved or sized:
+                    SWP swp;
+                    WinQueryWindowPos(hwnd, &swp);
+                    WinSetWindowPos(pcd->hwndButton,
+                                    0,
+                                    pswpNew->x + pswpNew->cx, // has already been truncated!
+                                    pswpNew->y,
+                                    COMBO_BUTTON_WIDTH,
+                                    pswpNew->cy,
+                                    SWP_MOVE | SWP_SIZE);
+                }
+
+                mrc = pcd->pfnwpOrigEntryField(hwnd, msg, mp1, mp2);
+            }
+        break;
+
+        /*
+         * WM_SETFOCUS:
+         *      hide listbox if focus is going away from us
+         */
+
+        case WM_SETFOCUS:
+            if (pcd = (PCOMBODATA)WinQueryWindowPtr(hwnd, QWL_USER))
+            {
+                if (!mp2)
+                    // we're losing focus:
+                    // is listbox currently showing?
+                    ShowListbox(hwnd,
+                                pcd,
+                                FALSE);
+
+                mrc = pcd->pfnwpOrigEntryField(hwnd, msg, mp1, mp2);
+            }
+        break;
+
+        /*
+         * WM_COMMAND:
+         *      show/hide listbox if the button gets pressed.
+         */
+
+        case WM_COMMAND:
+            if (pcd = (PCOMBODATA)WinQueryWindowPtr(hwnd, QWL_USER))
+            {
+                if (SHORT1FROMMP(mp1) == ID_COMBO_BUTTON)
+                {
+                    // button clicked:
+                    ShowListbox(hwnd,
+                                pcd,
+                                // check state of list box
+                                (WinQueryWindow(pcd->hwndListbox, QW_PARENT)
+                                 == WinQueryObjectWindow(HWND_DESKTOP)));
+
+                    // do not call parent
+                    break;
+
+                } // end if ((SHORT)mp1 == ID_COMBO_BUTTON)
+
+                mrc = pcd->pfnwpOrigEntryField(hwnd, msg, mp1, mp2);
+            }
+        break;
+
+        /*
+         * WM_CONTROL:
+         *      handle notifications from listbox.
+         */
+
+        case WM_CONTROL:
+            if (pcd = (PCOMBODATA)WinQueryWindowPtr(hwnd, QWL_USER))
+            {
+                USHORT usid = SHORT1FROMMP(mp1),
+                       uscode = SHORT2FROMMP(mp1);
+                if (usid == ID_COMBO_LISTBOX)
+                {
+                    switch (uscode)
+                    {
+                        case LN_ENTER:
+                        break;
+
+                        case LN_SELECT:
+                        {
+                            SHORT sSelected = winhQueryLboxSelectedItem(pcd->hwndListbox,
+                                                                        LIT_FIRST);
+                            PSZ psz = NULL;
+                            if (sSelected != LIT_NONE)
+                            {
+                                psz = winhQueryLboxItemText(pcd->hwndListbox,
+                                                            sSelected);
+                            }
+                            WinSetWindowText(hwnd, psz);
+                            if (psz)
+                            {
+                                WinPostMsg(hwnd,
+                                           EM_SETSEL,
+                                           MPFROM2SHORT(0, strlen(psz)),
+                                           0);
+                                free(psz);
+                            }
+                        break; }
+
+                        case LN_SETFOCUS:
+                            // when the list box gets the focus, always
+                            // set focus to ourselves
+                            WinSetFocus(HWND_DESKTOP, hwnd);
+                        break;
+                    }
+
+                    // forward list box notifications to
+                    // our own owner, but replace the id
+                    // with the combo box id
+                    WinPostMsg(WinQueryWindow(hwnd, QW_OWNER),
+                               WM_CONTROL,
+                               MPFROM2SHORT(WinQueryWindowUShort(hwnd, QWS_ID),
+                                            uscode),
+                               mp2);
+
+                    // do not call parent
+                    break;
+
+                } // end if (usid == ID_COMBO_LISTBOX)
+
+                mrc = pcd->pfnwpOrigEntryField(hwnd, msg, mp1, mp2);
+            }
+        break;
+
+        /*
+         * WM_CHAR:
+         *
+         */
+
+        case WM_CHAR:
+            if (pcd = (PCOMBODATA)WinQueryWindowPtr(hwnd, QWL_USER))
+            {
+                USHORT usFlags    = SHORT1FROMMP(mp1);
+                // USHORT usch       = SHORT1FROMMP(mp2);
+                USHORT usvk       = SHORT2FROMMP(mp2);
+
+                if ((usFlags & KC_KEYUP) == 0)
+                {
+                    if (usFlags & KC_VIRTUALKEY)
+                    {
+                        switch (usvk)
+                        {
+                            case VK_DOWN:
+                            case VK_UP:
+                                // if alt is pressed with these, show/hide listbox
+                                if (usFlags & KC_ALT)
+                                    WinPostMsg(hwnd,
+                                               CBM_SHOWLIST,
+                                               (MPARAM)(WinQueryWindow(pcd->hwndListbox, QW_PARENT)
+                                                        == WinQueryObjectWindow(HWND_DESKTOP)),
+                                               0);
+                                else
+                                {
+                                    // just up or down, no alt:
+                                    // select next or previous item in list box
+                                    SHORT sSelected = winhQueryLboxSelectedItem(pcd->hwndListbox,
+                                                                                LIT_FIRST),
+                                          sNew = 0;
+
+                                    if (usvk == VK_DOWN)
+                                    {
+                                        if (sSelected != LIT_NONE)
+                                        {
+                                            if (sSelected < WinQueryLboxCount(pcd->hwndListbox))
+                                                sNew = sSelected + 1;
+                                        }
+                                        // else: sNew still 0
+                                    }
+                                    else
+                                    {
+                                        // up:
+                                        if (    (sSelected != LIT_NONE)
+                                             && (sSelected > 0)
+                                           )
+                                            sNew = sSelected - 1;
+                                    }
+
+                                    winhSetLboxSelectedItem(pcd->hwndListbox,
+                                                            sNew,
+                                                            TRUE);
+                                }
+                            break;
+                        }
+                    }
+                }
+
+                // call parent only if this is not a drop-down list
+                if ((pcd->flStyle & CBS_DROPDOWNLIST) == 0)
+                    mrc = pcd->pfnwpOrigEntryField(hwnd, msg, mp1, mp2);
+                else
+                    // forward to owner
+                    WinSendMsg(WinQueryWindow(hwnd, QW_OWNER),
+                               msg,
+                               mp1,
+                               mp2);
+            }
+        break;
+
+        /*
+         * CBM_ISLISTSHOWING:
+         *      implementation of the original combobox msg.
+         */
+
+        case CBM_ISLISTSHOWING:
+            if (pcd = (PCOMBODATA)WinQueryWindowPtr(hwnd, QWL_USER))
+            {
+                mrc = (MPARAM)(WinQueryWindow(pcd->hwndListbox, QW_PARENT)
+                                     == WinQueryObjectWindow(HWND_DESKTOP));
+            }
+        break;
+
+        /*
+         * CBM_SHOWLIST:
+         *      implementation of the original combobox msg.
+         */
+
+        case CBM_SHOWLIST:
+            if (pcd = (PCOMBODATA)WinQueryWindowPtr(hwnd, QWL_USER))
+            {
+                ShowListbox(hwnd,
+                            pcd,
+                            (BOOL)mp1);
+            }
+        break;
+
+        /*
+         * list box messages:
+         *      forward all these to the listbox and
+         *      return the listbox return value.
+         */
+
+        case LM_INSERTITEM:
+        case LM_SETTOPINDEX:
+        case LM_QUERYTOPINDEX:
+        case LM_DELETEITEM:
+        case LM_SELECTITEM:
+        case LM_QUERYSELECTION:
+        case LM_SETITEMTEXT:
+        case LM_QUERYITEMTEXT:
+        case LM_SEARCHSTRING:
+        case LM_DELETEALL:
+            if (pcd = (PCOMBODATA)WinQueryWindowPtr(hwnd, QWL_USER))
+                mrc = WinSendMsg(pcd->hwndListbox, msg, mp1, mp2);
+        break;
+
+        /*
+         * WM_DESTROY:
+         *
+         */
+
+        case WM_DESTROY:
+            if (pcd = (PCOMBODATA)WinQueryWindowPtr(hwnd, QWL_USER))
+            {
+                WinDestroyWindow(pcd->hwndButton);
+                WinDestroyWindow(pcd->hwndListbox);
+
+                mrc = pcd->pfnwpOrigEntryField(hwnd, msg, mp1, mp2);
+
+                free(pcd);
+            }
+        break;
+
+        /*
+         * default:
+         *
+         */
+
+        default:
+            if (pcd = (PCOMBODATA)WinQueryWindowPtr(hwnd, QWL_USER))
+                mrc = pcd->pfnwpOrigEntryField(hwnd, msg, mp1, mp2);
+        break;
+    }
+
+    return (mrc);
+}
+
+/*
+ *@@ ctlComboFromEntryField:
+ *      turns a standard entry field control into an
+ *      XComboBox.
+ *
+ *      The XComboBox is intended to work like a standard
+ *      combobox, but it doesn't have the silly limitation
+ *      that the size of the combobox is assumed to be
+ *      the size of the dropped-down combobox. This limitation
+ *      makes it impossible to use standard comboboxes in
+ *      windows which have the WS_CLIPCHILDREN style because
+ *      the entire combo area will always be clipped out.
+ *
+ *      This is not a full reimplementation. Only drop-down
+ *      and drop-down list comboboxes are supported. Besides,
+ *      the XComboBox is essentially a subclassed entryfield,
+ *      so there might be limitations.
+ *
+ *      On input to this function, with flStyle, specify
+ *      either CBS_DROPDOWN or CBS_DROPDOWNLIST. CBS_SIMPLE
+ *      is not supported.
+ *
+ *      Supported messages to the XComboBox after this funcion
+ *      has been called:
+ *
+ *      -- CBM_ISLISTSHOWING
+ *
+ *      -- CBM_SHOWLIST
+ *
+ *      -- LM_QUERYITEMCOUNT
+ *
+ *      -- LM_INSERTITEM
+ *
+ *      -- LM_SETTOPINDEX
+ *
+ *      -- LM_QUERYTOPINDEX
+ *
+ *      -- LM_DELETEITEM
+ *
+ *      -- LM_SELECTITEM
+ *
+ *      -- LM_QUERYSELECTION
+ *
+ *      -- LM_SETITEMTEXT
+ *
+ *      -- LM_QUERYITEMTEXT
+ *
+ *      -- LM_SEARCHSTRING
+ *
+ *      -- LM_DELETEALL
+ *
+ *      NOTE: This occupies QWL_USER of the entryfield.
+ *
+ *@@added V0.9.9 (2001-03-17) [umoeller]
+ */
+
+BOOL ctlComboFromEntryField(HWND hwnd,          // in: entry field to be converted
+                            ULONG flStyle)      // in: combo box styles
+{
+    BOOL brc = FALSE;
+    PFNWP pfnwpOrig;
+    if (pfnwpOrig = WinSubclassWindow(hwnd,
+                                      fnwpComboSubclass))
+    {
+        PCOMBODATA pcd;
+        if (pcd = (PCOMBODATA)malloc(sizeof(*pcd)))
+        {
+            SWP swp;
+            BITMAPINFOHEADER2 bmih2;
+
+            memset(pcd, 0, sizeof(*pcd));
+            pcd->pfnwpOrigEntryField = pfnwpOrig;
+            pcd->flStyle = flStyle;
+
+            WinSetWindowPtr(hwnd, QWL_USER, pcd);
+
+            WinQueryWindowPos(hwnd, &swp);
+            pcd->x = swp.x;
+            pcd->y = swp.y;
+            pcd->cx = swp.cx;
+            pcd->cy = swp.cy;
+
+            swp.cx -= COMBO_BUTTON_WIDTH;
+            WinSetWindowPos(hwnd,
+                            0,
+                            0, 0,
+                            swp.cx, swp.cy,
+                            SWP_SIZE | SWP_NOADJUST);       // circumvent subclassing
+
+            pcd->hbmButton = WinGetSysBitmap(HWND_DESKTOP,
+                                             SBMP_COMBODOWN);
+            bmih2.cbFix = sizeof(bmih2);
+            GpiQueryBitmapInfoHeader(pcd->hbmButton,
+                                     &bmih2);
+            pcd->szlButton.cx = bmih2.cx;
+            pcd->szlButton.cy = bmih2.cy;
+
+            pcd->hwndButton = WinCreateWindow(WinQueryWindow(hwnd, QW_PARENT),
+                                              WC_BUTTON,
+                                              "",
+                                              WS_VISIBLE
+                                                | BS_PUSHBUTTON | BS_NOPOINTERFOCUS,
+                                              swp.x + swp.cx - COMBO_BUTTON_WIDTH,
+                                              swp.y,
+                                              COMBO_BUTTON_WIDTH,
+                                              swp.cy,
+                                              hwnd,     // owner == entry field!
+                                              hwnd,     // insert behind entry field
+                                              ID_COMBO_BUTTON,
+                                              NULL,
+                                              NULL);
+            WinSetWindowPtr(pcd->hwndButton, QWL_USER, pcd);
+            pcd->pfnwpOrigButton = WinSubclassWindow(pcd->hwndButton,
+                                                     fnwpSubclassedComboButton);
+
+            pcd->hwndListbox = WinCreateWindow(HWND_OBJECT,      // parent, for now
+                                               WC_LISTBOX,
+                                               "?",
+                                               WS_VISIBLE | WS_SAVEBITS | WS_CLIPSIBLINGS
+                                                 | LS_NOADJUSTPOS,
+                                               0,
+                                               0,
+                                               0,
+                                               0,
+                                               hwnd,     // owner == entry field!
+                                               HWND_TOP,     // insert behind entry field
+                                               ID_COMBO_LISTBOX,
+                                               NULL,
+                                               NULL);
+
+            // finally, set style of entry field... we force
+            // these flags no matter what the original style
+            // was
+            /* WinSetWindowBits(hwnd,
+                             QWL_STYLE,
+                             // bits to set:
+                            (flStyle & CBS_DROPDOWNLIST)
+                                ? ES_READONLY
+                                : 0,
+                             // mask:
+                             ES_READONLY); */
+        }
+    }
+
+    return (brc);
+}
+
+
