/* *@@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 M”ller. * 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 #include #include #include #include // needed for except.h #include // 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] */ STATIC 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] */ STATIC 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] */ STATIC 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] */ STATIC 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 ctlPostWmControl(hwnd, 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; }