/** Microsoft Windows **/ /** Copyright(c) Microsoft Corp., 1991, 1992 **/ /* * MODULE NAME: BTNLIST.C * AUTHOR: John Rivard * Microsoft Corp. * (johnri@microsoft.com) * SHORT DESCRIPTION: Button ListBox Control * FUNCTIONS: InitButtonListBoxClass * UnInitButtonListBoxClass * ButtonListBoxProc * BL_OnCreate * BL_OnDestroy * BL_OnSetFocus * BL_OnKillFocus * BL_OnDrawItem * BL_OnMeasureItem * BL_OnCompareItem * BL_OnCharToItem * BL_OnDeleteItem * BL_OnGetDlgCode * BL_OnCtlColor * BL_OnCommand * SubListBoxProc * Sub_OnLButtonDown * Sub_OnLButtonUp * Sub_OnMouseMove * Sub_OnKey * CreateListButton * DeleteListButton * CreateButtonBitmap * FILE HISTORY: * johnri 03-09-92 Create. * johnri 04-29-92 Port from standalone DLL to COMMCTRL.DLL */ #include "ctlspriv.h" /* commctrl private definitions */ /******* Definitions and typedefs ***/ /* Use the first definition if you want to clean up unreferenced params */ #if 0 #define Reference(x) #else #define Reference(x) (x) = (x) #endif // Standard list box control for button listbox #define LISTBOX "ListBox" #define ID_LISTBOX 1 // Button listbox info; data for the entire control #define GWL_BLINFO 0 typedef struct tagBLINFO { BOOL fNoScroll; int cxButton; int cyButton; int nTrackButton; int cButtonMax; } BLINFO, NEAR *PBLINFO; // List button data; data for each button typedef struct tagLBD { DWORD dwItemData; // user item data for // LB_SETITEMDATA and LB_GETITEMDATA BOOL fButtonDown;// TRUE if button pressed UINT chUpper; // button key uppercase UINT chLower; // button key lowercase HBITMAP hbmpUp; // bitmap for up button HBITMAP hbmpDown; // bitmap for down button RECT rcText; // text rectangle for up button char szText[1]; // button text } LISTBUTTONDATA, NEAR *PLISTBUTTONDATA; /******* Internal Function Declarations ***********/ // Control and Subclass Window Procedures LRESULT CALLBACK ButtonListBoxProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK SubListBoxProc(HWND, UINT, WPARAM, LPARAM); // ButtonListBoxProc Message Handlers BOOL NEAR PASCAL BL_OnCreate(HWND hwnd, CREATESTRUCT FAR* lpCreateStruct); void NEAR PASCAL BL_OnDestroy(HWND hwnd); BOOL NEAR PASCAL BL_OnSetFocus(HWND hwnd, HWND hwndOldFocus); void NEAR PASCAL BL_OnKillFocus(HWND hwnd, HWND hwndOldFocus); void NEAR PASCAL BL_OnDrawItem(HWND hwnd, const DRAWITEMSTRUCT FAR* lpDrawItem); void NEAR PASCAL BL_OnMeasureItem(HWND hwnd, MEASUREITEMSTRUCT FAR* lpMeasureItem); int NEAR PASCAL BL_OnCompareItem(HWND hwnd, const COMPAREITEMSTRUCT FAR* lpCompareItem); int NEAR PASCAL BL_OnCharToItem(HWND hwnd, UINT ch, HWND hwndListbox, int iCaret); void NEAR PASCAL BL_OnDeleteItem(HWND hwnd, const DELETEITEMSTRUCT FAR* lpDeleteItem); UINT NEAR PASCAL BL_OnGetDlgCode(HWND hwnd, MSG FAR* lpmsg); HBRUSH NEAR PASCAL BL_OnCtlColor(HWND hwnd, HDC hdc, HWND hwndChild, int type); void NEAR PASCAL BL_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify); LRESULT NEAR PASCAL BL_OnButtonListBox(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); // SubListBoxProc Message Handlers void NEAR PASCAL Sub_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags); LONG NEAR PASCAL Sub_OnLButtonUp(HWND hwnd, int x, int y, UINT keyFlags); void NEAR PASCAL Sub_OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags); void NEAR PASCAL Sub_OnKey(HWND hwnd, UINT vk, BOOL fDown, int cRepeat, UINT flags); // Miscellaneous functions PLISTBUTTONDATA NEAR PASCAL CreateListButton(HWND hLB, CREATELISTBUTTON FAR* lpCLB); VOID NEAR PASCAL DeleteListButton(PLISTBUTTONDATA pLBD); HBITMAP NEAR PASCAL CreateButtonBitmap(HWND hLB, int nWidth, int nHeight, BOOL fButtonDown, HBITMAP hUserBitmap, LPCSTR lpszUserText, LPRECT rcText); // Debugging #ifdef DEBUG static void cdecl DebugPrintf(LPCSTR lpsz, ...); #define DEBUGPRINTF(arglist) DebugPrintf arglist #else #define DEBUGPRINTF(arglist) #endif /******* Global Data *****/ // Global - REVIEW_32 static BOOL fInitResult = FALSE; // result of initialization static WNDPROC lpDefListBoxProc = NULL; // Default ListBox proc static HBRUSH hBrushBackground = NULL; // Control background brush /* * NAME: InitButtonListBoxClass * SYNOPSIS: Init the control class and module. * ENTRY: hInstance HINSTANCE DLL instance handle * EXIT: return BOOL Result of initialization * NOTES: If called more than once it only returns the result * the first initialization. */ #pragma code_seg(CODESEG_INIT) BOOL FAR PASCAL InitButtonListBoxClass(HINSTANCE hInstance) { WNDCLASS wc; // Button List Control Class if (!GetClassInfo(hInstance, s_szBUTTONLISTBOX, &wc)) { #ifndef WIN32 extern LRESULT CALLBACK _ButtonListBoxProc(HWND, UINT, WPARAM, LPARAM); wc.lpfnWndProc = _ButtonListBoxProc; #else wc.lpfnWndProc = (WNDPROC)ButtonListBoxProc; #endif fInitResult = FALSE; wc.style = CS_DBLCLKS | CS_PARENTDC | CS_GLOBALCLASS; wc.cbClsExtra = 0; wc.cbWndExtra = sizeof(PBLINFO); wc.hInstance = hInstance; wc.hIcon = NULL; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); wc.lpszMenuName = (LPCSTR)NULL; wc.lpszClassName = s_szBUTTONLISTBOX; if (!RegisterClass(&wc)) return FALSE; hBrushBackground = GetStockObject(GRAY_BRUSH); if (!hBrushBackground) return FALSE; } return (fInitResult = TRUE); } #pragma code_seg() LRESULT CALLBACK ButtonListBoxProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) /* * NAME: ButtonListBoxProc * SYNOPSIS: Window proc for class buttonlistbox. * ENTRY: hwnd HWND Window handle * uMsg UINT Window message * wParam WPARAM message dependent param * lParam LPARAM message dependent param * EXIT: return LRESULT message dependent * NOTES: This window proc handles message to the Button ListBox * control. Since the button listbox is implemented by * using a child listbox control, all listbox messages are * forwarded to the child listbox. * Other messages are handled to provide the specific * functionality of the button listbox control and to process * the owner-draw messages from the child listbox. */ { switch (uMsg) { HANDLE_MSG(hwnd, WM_DRAWITEM, BL_OnDrawItem); // Draw a button HANDLE_MSG(hwnd, WM_MEASUREITEM, BL_OnMeasureItem); // Measure a button HANDLE_MSG(hwnd, WM_COMPAREITEM, BL_OnCompareItem); // Compare two buttons HANDLE_MSG(hwnd, WM_CHARTOITEM, BL_OnCharToItem);// Keyboard jump HANDLE_MSG(hwnd, WM_DELETEITEM, BL_OnDeleteItem);// Delete a button HANDLE_MSG(hwnd, WM_SETFOCUS, BL_OnSetFocus);// Set focus to child listbox HANDLE_MSG(hwnd, WM_CREATE, BL_OnCreate);// Init buttonlistbox HANDLE_MSG(hwnd, WM_DESTROY, BL_OnDestroy);// Cleanup buttonlistbox HANDLE_MSG(hwnd, WM_GETDLGCODE, BL_OnGetDlgCode);// Tell dlg mgr about us // Set control bkgnd color #ifdef WIN32 HANDLE_MSG(hwnd, WM_CTLCOLORLISTBOX, BL_OnCtlColor); #else // WIN32 HANDLE_MSG(hwnd, WM_CTLCOLOR, BL_OnCtlColor); #endif HANDLE_MSG(hwnd, WM_COMMAND, BL_OnCommand);// Forward commands from the child listbox default: // Forward button listbox messages to button listbox msg handler if (uMsg >= WM_USER) return BL_OnButtonListBox(hwnd, uMsg, wParam, lParam); // Pass all other messages to the default window proc else return DefWindowProc(hwnd, uMsg, wParam, lParam); } } PLISTBUTTONDATA NEAR PASCAL GetListButtonData(HWND hLB, int iItem) { DWORD dw; dw = ListBox_GetItemData(hLB, iItem); if (dw == LB_ERR) return NULL; else return (PLISTBUTTONDATA)(UINT)dw; } LRESULT NEAR PASCAL BL_OnButtonListBox(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) /* * NAME: BL_OnButtonListBox * SYNOPSIS: Handle list box messages for button listbox * ENTRY: hLB HWND window handle of child listbox * uMsg UINT listbox message * wParam WPARAM message dependent * lParam LPARAM message dependent * EXIT: return LRESULT message dependent */ { PLISTBUTTONDATA pLBD; PBLINFO pbli; int cItems; HWND hLB; hLB = GetDlgItem(hwnd, ID_LISTBOX); pbli = (PBLINFO)GetWindowInt(hwnd, 0); // Handle button list box messages switch (uMsg) { case BL_ADDBUTTON: if (!pbli) return BL_ERR; cItems = ListBox_GetCount(hLB); DEBUGPRINTF(("AddButton: Max %d, Count %d", pbli->cButtonMax, cItems)); if (cItems >= pbli->cButtonMax) { DEBUGPRINTF(("AddButton: BL_ERR")); return BL_ERR; } pLBD = CreateListButton(hLB, (CREATELISTBUTTON FAR*)lParam); if (!pLBD) return BL_ERRSPACE; else return ListBox_AddItemData(hLB, (UINT)pLBD); case BL_DELETEBUTTON: return ListBox_DeleteString(hLB, wParam); case BL_GETCARETINDEX: return ListBox_GetCaretIndex(hLB); case BL_GETCOUNT: return ListBox_GetCount(hLB); case BL_GETCURSEL: return ListBox_GetCurSel(hLB); case BL_GETITEMDATA: pLBD = GetListButtonData(hLB, wParam); if (pLBD) return (LRESULT)pLBD->dwItemData; else return (LRESULT)BL_ERR; case BL_GETITEMRECT: return ListBox_GetItemRect(hLB, wParam, lParam); case BL_GETTEXT: pLBD = GetListButtonData(hLB, wParam); if (pLBD) { lstrcpy((LPSTR)lParam, pLBD->szText); return (LRESULT)lstrlen(pLBD->szText); } else return BL_ERR; case BL_GETTEXTLEN: pLBD = GetListButtonData(hLB, wParam); if (pLBD) return (LRESULT)lstrlen(pLBD->szText); else return (LRESULT)BL_ERR; case BL_GETTOPINDEX: return ListBox_GetTopIndex(hLB); case BL_INSERTBUTTON: if (!pbli) return BL_ERR; cItems = ListBox_GetCount(hLB); if (cItems >= pbli->cButtonMax) return BL_ERR; pLBD = CreateListButton(hLB, (CREATELISTBUTTON FAR*)lParam); if (!pLBD) return BL_ERRSPACE; else return ListBox_InsertItemData(hLB, wParam, (UINT)pLBD); case BL_RESETCONTENT: return ListBox_ResetContent(hLB); case BL_SETCARETINDEX: return ListBox_SetCaretIndex(hLB, wParam); case BL_SETCURSEL: return ListBox_SetCurSel(hLB, wParam); case BL_SETITEMDATA: pLBD = GetListButtonData(hLB, wParam); if (pLBD) { pLBD->dwItemData = (DWORD)lParam; return (LRESULT)BL_OKAY; } else return (LRESULT)BL_ERR; case BL_SETTOPINDEX: return ListBox_SetTopIndex(hLB, wParam); default: DEBUGPRINTF(("BL_OnButtonListBox: unknown message %d", uMsg)); return (LRESULT)BL_ERR; } } /* * NAME: BL_OnCreate * SYNOPSIS: Handle WM_CREATE for button listbox * ENTRY: hwnd HWND window handle * lpCS CREATESTRUCT FAR* window create data * EXIT: return BOOL TRUE if success, else false * NOTES: When a button listbox is created it must position itself * along one of the edges of the parent dialog as specified * by the style bits and create the child listbox. * The dimensions of the buttons within the child listbox * are determined by the cx and cy parameters in the * CREATESTRUCT. (For other controls, these would indicate * the width and height of the entire control, but that is * determined by the parent dialog window size.) The cx and * cy values come from the CONTROL statement in the dialog * template. * This function subclasses the child listbox with the * SubListBoxProc. */ BOOL NEAR PASCAL BL_OnCreate(HWND hwnd, CREATESTRUCT FAR* lpCS) { typedef enum tagORIENTATION { VERTICAL, HORIZONTAL } ORIENTATION; ORIENTATION orientation; BOOL fNoScroll; PBLINFO pbli; DWORD dwStyle; DWORD dwListBoxStyle; HWND hListBox; BYTE buttonsPerListbox; // Setup the users styles and get the button dimensions from the style dwStyle = lpCS->style; orientation = (dwStyle & BLS_VERTICAL) ? VERTICAL : HORIZONTAL; fNoScroll = (dwStyle & BLS_NOSCROLL) != 0L; if ((buttonsPerListbox = (BYTE)(dwStyle & BLS_NUMBUTTONS)) == 0) buttonsPerListbox = 1; // force a border around the control by default and get rid of // the non-standard window styles. dwStyle |= WS_BORDER; SetWindowLong(hwnd, GWL_STYLE, dwStyle); // Allocate a global structure for holding data for the entire // button listbox control and set the pointer in the window pbli = (PBLINFO)LocalAlloc(LPTR, sizeof(BLINFO)); if (!pbli) { DEBUGPRINTF(("BL_OnCreate: could not allocate pbli")); return FALSE; } SetWindowInt(hwnd, 0, (int)pbli); // Init values for pbli pbli->fNoScroll = fNoScroll; pbli->nTrackButton = -1; // no button currently down pbli->cButtonMax = 3000; // very large integer pbli->cxButton = lpCS->cx; pbli->cyButton = lpCS->cy; /* Adust the width and height of the control to fit the * requested number of buttons */ if (orientation == HORIZONTAL) { lpCS->cx = buttonsPerListbox * pbli->cxButton + 2 * g_cxBorder + ((pbli->fNoScroll) ? 0 : MulDiv(pbli->cxButton, 2, 3)); lpCS->cy = pbli->cyButton; lpCS->cy += (pbli->fNoScroll) ? 2 * g_cyBorder : (g_cyHScroll + g_cyBorder); /* if no scrollbar, calculate the max number of buttons that fit */ if (pbli->fNoScroll) pbli->cButtonMax = MulDiv(lpCS->cx, 1, pbli->cxButton); } else { lpCS->cy = buttonsPerListbox * pbli->cyButton + 2 * g_cyBorder + ((pbli->fNoScroll) ? 0 : MulDiv(pbli->cyButton, 2, 3)); lpCS->cx = pbli->cxButton; lpCS->cx += (pbli->fNoScroll) ? 2 * g_cxBorder : (g_cxVScroll + g_cxBorder); /* if no scrollbar, calculate the max number of buttons that fit */ if (pbli->fNoScroll) pbli->cButtonMax = MulDiv(lpCS->cy, 1, pbli->cyButton); } /* Now change the control size to fit the calculated buttons */ SetWindowPos(hwnd, NULL, lpCS->x, lpCS->y, lpCS->cx, lpCS->cy, SWP_NOZORDER | SWP_NOACTIVATE); // Set the standard style bits for all button child listboxes // Set style for vertical/horizontal // Set style for no scrollbars dwListBoxStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_BORDER | LBS_NOTIFY | LBS_SORT | LBS_NOINTEGRALHEIGHT | LBS_OWNERDRAWFIXED | LBS_WANTKEYBOARDINPUT | LBS_DISABLENOSCROLL; if (orientation == HORIZONTAL) dwListBoxStyle |= (WS_HSCROLL | LBS_MULTICOLUMN); else dwListBoxStyle |= WS_VSCROLL; if (fNoScroll) dwListBoxStyle &= ~(WS_HSCROLL | WS_VSCROLL); // Create the child list box hListBox = CreateWindowEx( WS_EX_NOPARENTNOTIFY, "ListBox", // class "", // window name dwListBoxStyle, // style 0, // left 0, // top lpCS->cx - 2 * g_cxBorder, // width lpCS->cy - 2 * g_cyBorder, // height hwnd, // parent (HMENU)ID_LISTBOX, // control id of the child listbox HINST_THISDLL, // instance NULL // no createparams ); if (!hListBox) { DEBUGPRINTF(("BL_OnCreate: could not create child listbox")); LocalFree((HLOCAL)pbli); return FALSE; } // Sub-class the list box // Note that window procedures in protect mode only DLL's may be called // directly. if (!lpDefListBoxProc) lpDefListBoxProc = (WNDPROC)GetWindowLong(hListBox, GWL_WNDPROC); SetWindowLong(hListBox, GWL_WNDPROC, (LONG)SubListBoxProc); return TRUE; } void NEAR PASCAL BL_OnDestroy(HWND hwnd) /* * NAME: BL_OnDestroy * SYNOPSIS: Handle WM_DESTROY for button listbox * ENTRY: hwnd HWND Window handle * EXIT: void * NOTES: Clean up memory allocated in BL_OnCreate. */ { PBLINFO pbli; // Free up the button list info data pbli = (PBLINFO)GetWindowInt(hwnd, 0); if (pbli != NULL) LocalFree((HLOCAL)pbli); } /* * NAME: BL_OnSetFocus * SYNOPSIS: Handle WM_SETFOCUS for button listbox * ENTRY: hwnd HWND Window getting focus * hwndOldFocus HWND Window losing focus * EXIT: void * NOTES: Pass focus to child listbox. */ BOOL NEAR PASCAL BL_OnSetFocus(HWND hwnd, HWND hwndOldFocus) { HWND hLB; Reference(hwndOldFocus); hLB = GetDlgItem(hwnd, ID_LISTBOX); SetFocus(hLB); // Be sure there is alwyas a current selection // or else the spacebar will not select a button. if (ListBox_GetCurSel(hLB) == LB_ERR) ListBox_SetCurSel(hLB, ListBox_GetCaretIndex(hLB)); return 0; } /* * NAME: BL_OnDrawItem * SYNOPSIS: Handle WM_DRAWITEM for button listbox * ENTRY: hwnd HWND Window handle * lpDrawItem DRAWITEMSTRUCT FAR* * EXIT: void * NOTES: BitBlt the up or down button and draw the focus rect. */ void NEAR PASCAL BL_OnDrawItem(HWND hwnd, const DRAWITEMSTRUCT FAR* lpDrawItem) { PLISTBUTTONDATA pLBD; HDC hMemoryDC; HBITMAP hOldBitmap; HBITMAP hBitmap; PBLINFO pbli; pLBD = (PLISTBUTTONDATA)(UINT)(lpDrawItem->itemData); if (!pLBD) return; pbli = (PBLINFO)GetWindowInt(hwnd, 0); if (!pbli) return; /* Draw the standard button */ if ((lpDrawItem->itemAction & ODA_DRAWENTIRE) || (lpDrawItem->itemAction & ODA_SELECT)) { hBitmap = (pLBD->fButtonDown) ? pLBD->hbmpDown : pLBD->hbmpUp; hMemoryDC = CreateCompatibleDC(lpDrawItem->hDC); hOldBitmap = SelectObject(hMemoryDC, hBitmap); if (hOldBitmap) { BitBlt(lpDrawItem->hDC, lpDrawItem->rcItem.left, lpDrawItem->rcItem.top, lpDrawItem->rcItem.right - lpDrawItem->rcItem.left, lpDrawItem->rcItem.bottom - lpDrawItem->rcItem.top, hMemoryDC, 0, 0, SRCCOPY); SelectObject(hMemoryDC, hOldBitmap); } DeleteDC(hMemoryDC); } /* Draw the focus rect */ if (lpDrawItem->itemAction & ODA_FOCUS) { RECT rcFocus; CopyRect(&rcFocus, &pLBD->rcText); OffsetRect(&rcFocus, lpDrawItem->rcItem.left, lpDrawItem->rcItem.top); InflateRect(&rcFocus, 1, 1); if (pLBD->fButtonDown) OffsetRect(&rcFocus, 2, 2); DrawFocusRect(lpDrawItem->hDC, &rcFocus); } } /* * NAME: BL_OnMeasureItem * SYNOPSIS: Handle WM_MEASUREITEM for button listbox * ENTRY: hwnd HWND window handle * lpMeasureItem MEASUREITEM FAR* * EXIT: void * NOTES: Return the item width and height for the button listbox. * The item width and height are not equal to the button * width and height since we want the button borders to * overlap by 1 pixel. */ void NEAR PASCAL BL_OnMeasureItem(HWND hwnd, MEASUREITEMSTRUCT FAR* lpMeasureItem) { PBLINFO pbli; pbli = (PBLINFO)GetWindowInt(hwnd, 0); if (!pbli) return; lpMeasureItem->itemWidth = pbli->cxButton; lpMeasureItem->itemHeight = pbli->cyButton; } /* * NAME: BL_OnCompareItem * SYNOPSIS: Handle WM_COMPAREITEM for button listbox * ENTRY: hwnd HWND window handle * lpCompareItem COMPAREITEMSTRUCT FAR* * EXIT: int -1 if item1 < item2 * 0 if item1 == item 2 * 1 if item2 > item2 * NOTES: The comparison is based on the button text. */ int NEAR PASCAL BL_OnCompareItem(HWND hwnd, const COMPAREITEMSTRUCT FAR* lpCompareItem) { PLISTBUTTONDATA pLBD1, pLBD2; Reference(hwnd); pLBD1 = (PLISTBUTTONDATA)(UINT)lpCompareItem->itemData1; pLBD2 = (PLISTBUTTONDATA)(UINT)lpCompareItem->itemData2; return lstrcmpi(pLBD1->szText, pLBD2->szText); } /* * NAME: BL_OnCharToItem * SYNOPSIS: Handle WM_CHARTOITEM for button listbox * ENTRY: hwnd HWND window handle * ch UINT character input * hwndListBox HWND listbox control handle * iCaret int current caret position * EXIT: return int -2 if button selected * -1 if not * NOTES: Find next button whose text begins with ch starting * with the current button. */ int NEAR PASCAL BL_OnCharToItem(HWND hwnd, UINT ch, HWND hwndListbox, int iCaret) { PLISTBUTTONDATA pLBD; int cbItems; int nItem; Reference(hwnd); cbItems = ListBox_GetCount(hwndListbox); for (nItem = iCaret + 1; nItem < cbItems; nItem++) { pLBD = GetListButtonData(hwndListbox, nItem); if (!pLBD) return -1; if ((pLBD->chUpper == ch) || (pLBD->chLower == ch)) { ListBox_SetCurSel(hwndListbox, nItem); return -2; } } for (nItem = 0; nItem < iCaret; nItem++) { pLBD = GetListButtonData(hwndListbox, nItem); if (!pLBD) return -1; if ((pLBD->chUpper == ch) || (pLBD->chLower == ch)) { ListBox_SetCurSel(hwndListbox, nItem); return -2; } } return -1; } /* * NAME: BL_OnDeleteItem * SYNOPSIS: Handle WM_DELETEITEM for button listbox * ENTRY: hwnd HWND window handle * lpDeleteItem DELETEITEMSTRUCT FAR* * EXIT: void * NOTES: Clean up the stuff that we created in CreateListButton and * forward the message to the parent dialog. */ void NEAR PASCAL BL_OnDeleteItem(HWND hwnd, const DELETEITEMSTRUCT FAR* lpDeleteItem) { PLISTBUTTONDATA pLBD; DELETEITEMSTRUCT di; pLBD = (PLISTBUTTONDATA)(UINT)lpDeleteItem->itemData; if (!pLBD) return; di.CtlType = lpDeleteItem->CtlType; di.CtlID = GetDlgCtrlID(hwnd); di.itemID = lpDeleteItem->itemID; di.hwndItem = hwnd; di.itemData = pLBD->dwItemData; SendMessage(GetParent(hwnd), WM_DELETEITEM, (WPARAM)di.CtlID, (LPARAM)(LPSTR)&di); DeleteListButton(pLBD); } /* * NAME: BL_OnGetDlgCode * SYNOPSIS: Handle WM_GETDLGCODE for button listbox * ENTRY: hwnd HWND window handle * lpmsg MSG FAR* * EXIT: return UINT dialog code * NOTES: Get the code from the child listbox and also set * the button bit. */ UINT NEAR PASCAL BL_OnGetDlgCode(HWND hwnd, MSG FAR* lpmsg) { UINT uCode; uCode = FORWARD_WM_GETDLGCODE(GetDlgItem(hwnd, ID_LISTBOX), lpmsg, SendMessage); uCode |= DLGC_BUTTON; return uCode; } /* * NAME: BL_OnCtlColor * SYNOPSIS: Handle WM_CTLCOLOR for button listbox * ENTRY: hwnd HWND window handle * hcd HDC dc of window * hwndChild HWND control handle * type int control type * EXIT: return HBRUSH Button listbox bg color */ HBRUSH NEAR PASCAL BL_OnCtlColor(HWND hwnd, HDC hdc, HWND hwndChild, int type) { Reference(hwnd); Reference(hdc); Reference(hwndChild); Reference(type); return hBrushBackground; } /* * NAME: BL_OnCommand * SYNOPSIS: Handle WM_COMMAND for button listbox * ENTRY: hwnd HWND window handle * hwndCtl HWND control handle * codeNotiry UINT notify code from control * EXIT: void * NOTES: Pass the message to the parent dialog as though the * button listbox generated it instead of the child listbox. */ void NEAR PASCAL BL_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) { id = GetDlgCtrlID(hwnd); hwndCtl = hwnd; hwnd = GetParent(hwnd); UpdateWindow(hwndCtl); FORWARD_WM_COMMAND(hwnd, id, hwndCtl, codeNotify, SendMessage); } /* * NAME: CreateButtonBitmap * SYNOPSIS: Create a new bitmap for the up or down button. * ENTRY: nWidth int width of button * nHeight int height of button * fButtonDown BOOL TRUE if button pressed * hUserBitmap HBITMAP user bitmap to draw on button * lpszUserText LPCSTR user text to draw on button * rcText LPRECT rect of text in button * EXIT: return HBITMAP new bitmap for button * rcText LPRECT rect of button text */ HBITMAP NEAR PASCAL CreateButtonBitmap(HWND hLB, int nWidth, int nHeight, BOOL fButtonDown, HBITMAP hUserBitmap, LPCSTR lpszUserText, LPRECT rcText) { HDC hdc; HDC hMemoryDC; HBRUSH hOldBrush; HBRUSH hBlackBrush; HBITMAP hBitmap; HBITMAP hOldBitmap; BITMAP bmButton; HDC hUserDC; HBITMAP hOldUserBitmap; BITMAP bm; int nWidthT, nHeightT; int textWidth, textHeight; int xDest, yDest; int cxDest, cyDest; char szText[40]; int cbText; HFONT hFontText; HFONT hOldFont; DWORD dwLBStyle; BOOL fRightBorder; BOOL fBottomBorder; LOGFONT lf; #ifndef WIN32 LOGFONT_32 lf32; #endif #define BORDER_WIDTH 1 #define HILIGHT_WIDTH 2 #define FACE_BORDER_WIDTH (BORDER_WIDTH+HILIGHT_WIDTH+1) #define PICT_BORDER_WIDTH (FACE_BORDER_WIDTH+0) #define TEXT_BORDER_WIDTH (FACE_BORDER_WIDTH+0) #define TEXTLINES 1 // Get the listbox style dwLBStyle = (DWORD)GetWindowLong(hLB, GWL_STYLE); fRightBorder = ((dwLBStyle & LBS_MULTICOLUMN) != 0L); fBottomBorder = !fRightBorder; // Create drawing DC and bitmap based upon the desktop window // BUGBUG, not error checks! hdc = GetDC(NULL); hMemoryDC = CreateCompatibleDC(hdc); hBitmap = CreateCompatibleBitmap(hdc, nWidth, nHeight); hOldBitmap = SelectObject(hMemoryDC, hBitmap); GetObject(hBitmap, sizeof(BITMAP), &bmButton); // Draw the button face hOldBrush = SelectObject(hMemoryDC, g_hbrBtnFace); PatBlt(hMemoryDC, 0, 0, nWidth, nHeight, PATCOPY); // Draw the button border hBlackBrush = GetStockObject(BLACK_BRUSH); SelectObject(hMemoryDC, hBlackBrush); if (fRightBorder) PatBlt(hMemoryDC, nWidth - 1, 0, 1, nHeight, PATCOPY); if (fBottomBorder) PatBlt(hMemoryDC, 0, nHeight - 1, nWidth, 1, PATCOPY); // subtract out the border if (fRightBorder) nWidth--; if (fBottomBorder) nHeight--; // Draw the highlights and shadow if (fButtonDown) { SelectObject(hMemoryDC, g_hbrBtnShadow); PatBlt(hMemoryDC, 0, 0, nWidth, 1, PATCOPY); PatBlt(hMemoryDC, 0, 0, 1, nHeight, PATCOPY); } else { SelectObject(hMemoryDC, g_hbrBtnHighlight); PatBlt(hMemoryDC, 0, 0, nWidth, 2, PATCOPY); PatBlt(hMemoryDC, 0, 0, 2, nHeight, PATCOPY); SelectObject(hMemoryDC, g_hbrBtnShadow); PatBlt(hMemoryDC, nWidth - 2, 1, 1, nHeight - 1, PATCOPY); PatBlt(hMemoryDC, nWidth - 1, 0, 1, nHeight, PATCOPY); PatBlt(hMemoryDC, 1, nHeight - 2, nWidth - 1, 1, PATCOPY); PatBlt(hMemoryDC, 0, nHeight - 1, nWidth, 1, PATCOPY); } // Superimpose the user text in lower 1/3 of button #ifdef WIN32 SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, FALSE); #else SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lf32), &lf32, FALSE); lf.lfHeight = (int)lf32.lfHeight; lf.lfWidth = (int)lf32.lfWidth; lf.lfEscapement = (int)lf32.lfEscapement; lf.lfOrientation = (int)lf32.lfOrientation; lf.lfWeight = (int)lf32.lfWeight; hmemcpy(&lf.lfItalic, &lf32.lfCommon, sizeof(COMMONFONT)); #endif hFontText = CreateFontIndirect(&lf); cbText = lstrlen(lpszUserText); #ifdef WIN32 // BUGBUG: I see no reason why the WIN32 side should assume that // lpszUserText will fit into szText. lstrcpy(szText, lpszUserText); #else //sizeof(c_szEllipses) is 0, since this is an extern string //lstrcpyn(szText, lpszUserText, sizeof(szText) - sizeof(s_szElipsis)); lstrcpyn(szText, lpszUserText, sizeof(szText)); #endif hOldFont = SelectObject(hMemoryDC, hFontText); SetTextColor(hMemoryDC, g_clrBtnText); SetBkMode(hMemoryDC, TRANSPARENT); SetTextAlign(hMemoryDC, TA_TOP); MGetTextExtent(hMemoryDC, szText, cbText, &textWidth, &textHeight); nWidthT = nWidth - 2 * TEXT_BORDER_WIDTH; nHeightT = nHeight - 2 * TEXT_BORDER_WIDTH; xDest = TEXT_BORDER_WIDTH + ((textWidth < nWidthT) ? (nWidthT - textWidth) / 2 : 0); yDest = TEXT_BORDER_WIDTH + nHeightT - TEXTLINES * (textHeight); rcText->top = yDest; rcText->left = xDest; rcText->right = rcText->left + min(textWidth, nWidthT); rcText->bottom = rcText->top + TEXTLINES * (textHeight); // Elipsize text if needed if (textWidth > nWidthT) { int nWidthText; int cbTextT; MGetTextExtent(hMemoryDC, c_szEllipses, lstrlen(c_szEllipses), &nWidthText, NULL); nWidthT -= nWidthText; // BUGBUG: if the first character's extent is > nWidthT, we clobber cyDest. // fortunately it isn't in use yet... We'll also not truncate the text // for the elipsis. for (cbTextT = 0; cbTextT < cbText; cbTextT++) { MGetTextExtent(hMemoryDC, szText, cbTextT, &nWidthText, NULL); if (nWidthText > nWidthT) break; } szText[--cbTextT] = 0; lstrcat(szText, c_szEllipses); cbText = lstrlen(szText); } if (fButtonDown) { xDest += 2; yDest += 2; OffsetRect(rcText, 2, 2); } ExtTextOut(hMemoryDC, xDest, yDest, ETO_CLIPPED, rcText, szText, cbText, NULL); if (fButtonDown) OffsetRect(rcText, -2, -2); // if the bitmaps are compatible, // Superimpose the user bitmap centered horizontally and // vertically in above rcText GetObject(hUserBitmap, sizeof(BITMAP), &bm); if (bm.bmPlanes == bmButton.bmPlanes) { nWidthT = nWidth - 2 * PICT_BORDER_WIDTH; nHeightT = rcText->top - PICT_BORDER_WIDTH; xDest = PICT_BORDER_WIDTH + ((bm.bmWidth < nWidthT) ? (nWidthT - bm.bmWidth) / 2 : 0); cxDest = (bm.bmWidth < nWidthT) ? bm.bmWidth : nWidthT; yDest = PICT_BORDER_WIDTH + ((bm.bmHeight < nHeightT) ? (nHeightT - bm.bmHeight) / 2 : 0); cyDest = (bm.bmHeight < nHeightT) ? bm.bmHeight : nHeightT; if (fButtonDown) { xDest += 2; yDest += 2; } hUserDC = CreateCompatibleDC(hdc); hOldUserBitmap = SelectObject(hUserDC, hUserBitmap); if (hOldUserBitmap) { BitBlt(hMemoryDC, xDest, yDest, cxDest, cyDest, hUserDC, 0, 0, SRCCOPY); SelectObject(hUserDC, hOldUserBitmap); } DeleteDC(hUserDC); } // Cleanup SelectObject(hMemoryDC, hOldBrush); SelectObject(hMemoryDC, hOldBitmap); SelectObject(hMemoryDC, hOldFont); DeleteObject(hFontText); DeleteDC(hMemoryDC); ReleaseDC(NULL, hdc); return hBitmap; } /* * NAME: SubListBoxProc * SYNOPSIS: ListBox subclassing window proc for the button listbox * child listbox. * ENTRY: hwnd HWND Window handle of listbox * uMsg UINT Window message * wParam WPARAM message dependent param * lParam LPARAM message dependent param * EXIT: return LRESULT message dependent * NOTES: This window proc handles messages to perform hit-testing * on the button items in the listbox and does pre-processing * of messages that add or change item data. * Messages that are not explicitly handled are forwarded * to the default listbox window proc. */ LRESULT CALLBACK SubListBoxProc(HWND hLB, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { HANDLE_MSG(hLB, WM_LBUTTONDOWN, Sub_OnLButtonDown); HANDLE_MSG(hLB, WM_LBUTTONUP, Sub_OnLButtonUp); HANDLE_MSG(hLB, WM_MOUSEMOVE, Sub_OnMouseMove); HANDLE_MSG(hLB, WM_KEYDOWN, Sub_OnKey); HANDLE_MSG(hLB, WM_KEYUP, Sub_OnKey); } return CallWindowProc(lpDefListBoxProc, hLB, uMsg, wParam, lParam); } /* * NAME: Sub_OnLButtonDown * SYNOPSIS: Handle the WM_LBUTTONDOWN message for the child listbox * ENTRY: hLB HWND window handle of listbox * fDoubldClick BOOL TRUE if double click, else FALSE * x int horizontal mouse coordinate * y int vertical mouse coordinate * keyFlags UINT flags from the VK message * EXIT: void * NOTES: On a mouse button down, check to see which item the mouse * is in. Set the fButtonDown flag for the pressed button * and invalidate the button so that it will be drawn pressed. * Save the item number of the pressed button for use in the * Sub_OnLButtonUp and Sub_OnMouseMove handlers. */ void NEAR PASCAL Sub_OnLButtonDown(HWND hLB, BOOL fDoubleClick, int x, int y, UINT keyFlags) { PLISTBUTTONDATA pLBD; int nItem; int cbItem; RECT rcItem; POINT pt; PBLINFO pbli; Reference(fDoubleClick); pbli = (PBLINFO)GetWindowInt(GetParent(hLB), 0); if (!pbli) return; cbItem = ListBox_GetCount(hLB); pt.x = x; pt.y = y; for (nItem = 0; nItem < cbItem; nItem++) { pLBD = GetListButtonData(hLB, nItem); if (!pLBD) return; ListBox_GetItemRect(hLB, nItem, &rcItem); if (PtInRect(&rcItem, pt)) { pLBD->fButtonDown = TRUE; pbli->nTrackButton = nItem; InvalidateRect(hLB, &rcItem, FALSE); } else pLBD->fButtonDown = FALSE; } // tell list box this item was pressed CallWindowProc(lpDefListBoxProc, hLB, WM_LBUTTONDOWN, (WPARAM)keyFlags, MAKELPARAM(x, y)); } /* * NAME: Sub_OnLButtonUp * SYNOPSIS: Handle the WM_LBUTTONUP message for the child listbox * ENTRY: hLB HWND window handle of listbox * x int horizontal mouse coordinate * y int vertical mouse coordinate * keyFlags UINT flags from the VK message * EXIT: void * NOTES: If we're not tracking a button press, forward the message * to the default listbox proc. * Otherwise, set all buttons to up and test if the mouse * went up in the pressed button. If so, send the doubld click * message to the listbox to cause a WM_COMMAND:BLN_CLICKED * notification from the listbox. */ LONG NEAR PASCAL Sub_OnLButtonUp(HWND hLB, int x, int y, UINT keyFlags) { PLISTBUTTONDATA pLBD; int nItem; int cbItem; RECT rcItem; POINT pt; PBLINFO pbli; int nPressedItem = -1; pbli = (PBLINFO)GetWindowInt(GetParent(hLB), 0); if (!pbli) return 0; cbItem = ListBox_GetCount(hLB); pt.x = x; pt.y = y; if (pbli->nTrackButton == -1) { CallWindowProc(lpDefListBoxProc, hLB, WM_LBUTTONUP, (WPARAM)keyFlags, MAKELPARAM(x, y)); return 0; } for (nItem = 0; nItem < cbItem; nItem++) { pLBD = GetListButtonData(hLB, nItem); if (!pLBD) return 0; if (pLBD->fButtonDown) nPressedItem = nItem; pLBD->fButtonDown = FALSE; } if (nPressedItem != -1) { // BOOM! We got a button press pLBD = GetListButtonData(hLB, nPressedItem); if (!pLBD) return 0; ListBox_GetItemRect(hLB, nPressedItem, &rcItem); InvalidateRect(hLB, &rcItem, FALSE); CallWindowProc(lpDefListBoxProc, hLB, WM_LBUTTONDBLCLK, (WPARAM)keyFlags, MAKELPARAM(x, y)); } pbli->nTrackButton = -1; CallWindowProc(lpDefListBoxProc, hLB, WM_LBUTTONUP, (WPARAM)keyFlags, MAKELPARAM(x, y)); return 0; } /* * NAME: Sub_OnMouseMove * SYNOPSIS: Handle the WM_MOUSEMOVE message for the child listbox * ENTRY: hLB HWND window handle of the listbox * x int horizontal mouse coordinate * y int vertical mouse coordinate * keyFlags UINT flags from the VK message * EXIT: void * NOTES: If we're not tracking a button press, forward the * message to the default listbox proc. * Otherwise, if the mouse enters or leaves the button rectangle, * redraw the button in the up or down state. */ void NEAR PASCAL Sub_OnMouseMove(HWND hLB, int x, int y, UINT keyFlags) { PLISTBUTTONDATA pLBD; int nItem; RECT rcItem; POINT pt; PBLINFO pbli; BOOL fInRect; // Pass to listbox if not button down if (!(keyFlags & MK_LBUTTON)) { CallWindowProc(lpDefListBoxProc, hLB, WM_MOUSEMOVE, (WPARAM)keyFlags, MAKELPARAM(x, y)); return; } pbli = (PBLINFO)GetWindowInt(GetParent(hLB), 0); if (!pbli) return; pt.x = x; pt.y = y; nItem = pbli->nTrackButton; if (nItem == -1) return; ListBox_GetItemRect(hLB, nItem, &rcItem); pLBD = GetListButtonData(hLB, nItem); if (!pLBD) return; fInRect = PtInRect(&rcItem, pt); if (fInRect != pLBD->fButtonDown) { pLBD->fButtonDown = fInRect; InvalidateRect(hLB, &rcItem, FALSE); } } /* * NAME: Sub_OnKey * SYNOPSIS: Handle the WM_KEYDOWN and WM_KEYUP message for the * child listbox * ENTRY: hLB HWND window handle of the listbox * fDown BOOL TRUE if keydown, else false * cRepeat int repeat count * flags UINT flags from the VK message * EXIT: void * NOTES: If the spacebar goes down, paint the current button * as pressed. When it goes up, paint the current button * as uppressed and send a double click to the listbox * for the button to cause a WM_COMMAND:BLN_CLICKED * notification from the listbox. */ void NEAR PASCAL Sub_OnKey(HWND hLB, UINT vk, BOOL fDown, int cRepeat, UINT flags) { PLISTBUTTONDATA pLBD; int nItem; RECT rcItem; PBLINFO pbli; pbli = (PBLINFO)GetWindowInt(GetParent(hLB), 0); if (!pbli) return; if (pbli->nTrackButton >= 0) return; if (vk == VK_SPACE) { nItem = ListBox_GetCurSel(hLB); if (nItem >= 0) { pLBD = GetListButtonData(hLB, nItem); if (!pLBD) return; if (pLBD->fButtonDown != fDown) { pLBD->fButtonDown = fDown; ListBox_GetItemRect(hLB, nItem, &rcItem); InvalidateRect(hLB, &rcItem, FALSE); // When the key goes up, fake a double click // to generate a WM_COMMAND notification if (!fDown) CallWindowProc(lpDefListBoxProc, hLB, WM_LBUTTONDBLCLK, (WPARAM)flags, MAKELPARAM(rcItem.left, rcItem.top)); } } } CallWindowProc(lpDefListBoxProc, hLB, fDown ? WM_KEYDOWN : WM_KEYUP, (WPARAM)vk, MAKELPARAM((UINT)cRepeat, flags)); } PLISTBUTTONDATA NEAR PASCAL CreateListButton(HWND hLB, CREATELISTBUTTON FAR* lpCLB) /* * NAME: CreateListButton * SYNOPSIS: Setup internal fields in the LISTBUTTONDATA structure * ENTRY: hLB HWND window handle of child listbox * pLBD PLISTBUTTONDATA ptr to input structure * EXIT: return PLISTBUTTONDATA ptr to output structure * NULL if an error occurs * NOTES: This function should is called in response to the BL_ADDBUTTON and BL_INSERTBUTTON messages. */ { PBLINFO pbli; PLISTBUTTONDATA pNewLBD; if (!lpCLB) { DEBUGPRINTF(("CreateListButton: !lpCLB")); return NULL; } if (lpCLB->cbSize != sizeof(CREATELISTBUTTON)) { DEBUGPRINTF(("CreateListButton: lpCLB->cbSize wrong")); return NULL; } if (!lpCLB->hBitmap) { DEBUGPRINTF(("CreateListButton: !lpCLB->hBitmap")); return NULL; } if (!lpCLB->lpszText) { DEBUGPRINTF(("CreateListButton: !lpCLB->lpszText")); return NULL; } pbli = (PBLINFO)GetWindowInt(GetParent(hLB), 0); if (!pbli) { DEBUGPRINTF(("CreateListButton: !pbli")); return NULL; } pNewLBD = (PLISTBUTTONDATA)LocalAlloc(LPTR, sizeof(LISTBUTTONDATA) + lstrlen(lpCLB->lpszText)); if (!pNewLBD) { DEBUGPRINTF(("CreateListButton: !LocalAlloc pNewLBD")); return NULL; } // copy over user item data pNewLBD->dwItemData = lpCLB->dwItemData; lstrcpy(pNewLBD->szText, lpCLB->lpszText); // init internal fields pNewLBD->fButtonDown = FALSE; pNewLBD->chUpper = (UINT)LOWORD(AnsiUpper((LPSTR)(DWORD)(BYTE)pNewLBD->szText[0])); pNewLBD->chLower = (UINT)LOWORD(AnsiLower((LPSTR)(DWORD)(BYTE)pNewLBD->szText[0])); // create the up and down bitmaps pNewLBD->hbmpUp = CreateButtonBitmap(hLB, pbli->cxButton, pbli->cyButton, FALSE, // button not down lpCLB->hBitmap, pNewLBD->szText, &pNewLBD->rcText); if (!pNewLBD->hbmpUp) { DEBUGPRINTF(("CreateListButton: !CreateButtonBitmap() UP")); goto error; } pNewLBD->hbmpDown = CreateButtonBitmap(hLB, pbli->cxButton, pbli->cyButton, TRUE, // button down lpCLB->hBitmap, pNewLBD->szText, &pNewLBD->rcText); if (!pNewLBD->hbmpDown) { DEBUGPRINTF(("CreateListButton: !CreateButtonBitmap() DOWN")); goto error; } return pNewLBD; error: DeleteListButton(pNewLBD); return NULL; } VOID NEAR PASCAL DeleteListButton(PLISTBUTTONDATA pLBD) /* * NAME: DeleteListButton * SYNOPSIS: Free memory and internal objects in allocated from CreateListButton * ENTRY: pLBD PLISTBUTTONDATA * EXIT: void */ { if (pLBD) { if (pLBD->hbmpUp) DeleteObject(pLBD->hbmpUp); if (pLBD->hbmpDown) DeleteObject(pLBD->hbmpDown); LocalFree((HLOCAL)pLBD); } } #ifdef DEBUG static void cdecl DebugPrintf(LPCSTR lpsz, int first, ...) { char sz[256]; wvsprintf(sz, lpsz, &first); OutputDebugString(sz); OutputDebugString("\n\r"); } #endif