Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
Sorry for the delay for v2, there was an internet outage in my area.
dlls/comctl32/button.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/dlls/comctl32/button.c b/dlls/comctl32/button.c index f7497a1..0bd416d 100644 --- a/dlls/comctl32/button.c +++ b/dlls/comctl32/button.c @@ -215,6 +215,13 @@ static inline UINT get_button_type( LONG window_style ) return (window_style & BS_TYPEMASK); }
+static inline BOOL button_centers_text( LONG window_style ) +{ + /* Push button's text is centered by default, same for split buttons */ + UINT type = get_button_type(window_style); + return type <= BS_DEFPUSHBUTTON || type == BS_SPLITBUTTON || type == BS_DEFSPLITBUTTON; +} + /* paint a button of any type */ static inline void paint_button( BUTTON_INFO *infoPtr, LONG style, UINT action ) { @@ -308,9 +315,7 @@ static UINT BUTTON_BStoDT( DWORD style, DWORD ex_style ) case BS_RIGHT: dtStyle |= DT_RIGHT; break; case BS_CENTER: dtStyle |= DT_CENTER; break; default: - /* Pushbutton's text is centered by default */ - if (get_button_type(style) <= BS_DEFPUSHBUTTON) dtStyle |= DT_CENTER; - /* all other flavours have left aligned text */ + if (button_centers_text(style)) dtStyle |= DT_CENTER; }
if (ex_style & WS_EX_RIGHT) dtStyle = DT_RIGHT | (dtStyle & ~(DT_LEFT | DT_CENTER)); @@ -984,8 +989,7 @@ static void BUTTON_PositionRect(LONG style, const RECT *outerRect, RECT *innerRe
if (!(style & BS_CENTER)) { - /* Push button's text is centered by default, all other types have left aligned text */ - if (get_button_type(style) <= BS_DEFPUSHBUTTON) + if (button_centers_text(style)) style |= BS_CENTER; else style |= BS_LEFT;
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/comctl32/button.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
diff --git a/dlls/comctl32/button.c b/dlls/comctl32/button.c index 0bd416d..38c7d4c 100644 --- a/dlls/comctl32/button.c +++ b/dlls/comctl32/button.c @@ -875,6 +875,17 @@ static LRESULT CALLBACK BUTTON_WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, L } break;
+ case BCM_SETDROPDOWNSTATE: + new_state = wParam ? BST_DROPDOWNPUSHED : 0; + + if ((infoPtr->state ^ new_state) & BST_DROPDOWNPUSHED) + { + infoPtr->state &= ~BST_DROPDOWNPUSHED; + infoPtr->state |= new_state; + InvalidateRect(hWnd, NULL, FALSE); + } + break; + case BCM_SETTEXTMARGIN: { RECT *text_margin = (RECT *)lParam;
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/comctl32/button.c | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+)
diff --git a/dlls/comctl32/button.c b/dlls/comctl32/button.c index 38c7d4c..4b8a8f4 100644 --- a/dlls/comctl32/button.c +++ b/dlls/comctl32/button.c @@ -86,6 +86,9 @@ typedef struct _BUTTON_INFO INT note_length; DWORD image_type; /* IMAGE_BITMAP or IMAGE_ICON */ BUTTON_IMAGELIST imagelist; + UINT split_style; + HIMAGELIST glyph; /* this is a font character code when split_style doesn't have BCSS_IMAGE */ + SIZE glyph_size; RECT text_margin; union { @@ -243,6 +246,21 @@ static inline WCHAR *get_button_text( const BUTTON_INFO *infoPtr ) return buffer; }
+/* get the default glyph size for split buttons */ +static LONG get_default_glyph_size(const BUTTON_INFO *infoPtr) +{ + if (infoPtr->split_style & BCSS_IMAGE) + { + /* Size it to fit, including the left and right edges */ + int w, h; + if (!ImageList_GetIconSize(infoPtr->glyph, &w, &h)) w = 0; + return w + GetSystemMetrics(SM_CXEDGE) * 2; + } + + /* The glyph size relies on the default menu font's cell height */ + return GetSystemMetrics(SM_CYMENUCHECK); +} + static void init_custom_draw(NMCUSTOMDRAW *nmcd, const BUTTON_INFO *infoPtr, HDC hdc, const RECT *rc) { nmcd->hdr.hwndFrom = infoPtr->hwnd; @@ -834,6 +852,34 @@ static LRESULT CALLBACK BUTTON_WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, L return TRUE; }
+ case BCM_SETSPLITINFO: + { + BUTTON_SPLITINFO *info = (BUTTON_SPLITINFO*)lParam; + + if (!info) return TRUE; + + if (info->mask & (BCSIF_GLYPH | BCSIF_IMAGE)) + { + infoPtr->split_style &= ~BCSS_IMAGE; + if (!(info->mask & BCSIF_GLYPH)) + infoPtr->split_style |= BCSS_IMAGE; + infoPtr->glyph = info->himlGlyph; + infoPtr->glyph_size.cx = infoPtr->glyph_size.cy = 0; + } + + if (info->mask & BCSIF_STYLE) + infoPtr->split_style = info->uSplitStyle; + if (info->mask & BCSIF_SIZE) + infoPtr->glyph_size = info->size; + + /* Calculate fitting value for cx if invalid (cy is untouched) */ + if (infoPtr->glyph_size.cx <= 0) + infoPtr->glyph_size.cx = get_default_glyph_size(infoPtr); + + /* Windows doesn't invalidate or redraw it, so we don't, either */ + return TRUE; + } + case BM_GETCHECK: return infoPtr->state & 3;
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/comctl32/button.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+)
diff --git a/dlls/comctl32/button.c b/dlls/comctl32/button.c index 4b8a8f4..213ab29 100644 --- a/dlls/comctl32/button.c +++ b/dlls/comctl32/button.c @@ -466,6 +466,9 @@ static LRESULT CALLBACK BUTTON_WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, L infoPtr->hwnd = hWnd; infoPtr->parent = cs->hwndParent; infoPtr->style = cs->style; + infoPtr->split_style = BCSS_STRETCH; + infoPtr->glyph = (HIMAGELIST)0x36; /* Marlett down arrow char code */ + infoPtr->glyph_size.cx = get_default_glyph_size(infoPtr); return DefWindowProcW(hWnd, uMsg, wParam, lParam); }
@@ -880,6 +883,22 @@ static LRESULT CALLBACK BUTTON_WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, L return TRUE; }
+ case BCM_GETSPLITINFO: + { + BUTTON_SPLITINFO *info = (BUTTON_SPLITINFO*)lParam; + + if (!info) return FALSE; + + if (info->mask & BCSIF_STYLE) + info->uSplitStyle = infoPtr->split_style; + if (info->mask & (BCSIF_GLYPH | BCSIF_IMAGE)) + info->himlGlyph = infoPtr->glyph; + if (info->mask & BCSIF_SIZE) + info->size = infoPtr->glyph_size; + + return TRUE; + } + case BM_GETCHECK: return infoPtr->state & 3;
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/comctl32/tests/button.c | 211 +++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+)
diff --git a/dlls/comctl32/tests/button.c b/dlls/comctl32/tests/button.c index fef3de5..4de0769 100644 --- a/dlls/comctl32/tests/button.c +++ b/dlls/comctl32/tests/button.c @@ -1427,6 +1427,214 @@ static void register_parent_class(void) RegisterClassA(&cls); }
+static void test_bcm_splitinfo(HWND hwnd) +{ + UINT button = GetWindowLongA(hwnd, GWL_STYLE) & BS_TYPEMASK; + int glyph_size = GetSystemMetrics(SM_CYMENUCHECK); + int border_w = GetSystemMetrics(SM_CXEDGE) * 2; + BUTTON_SPLITINFO info, dummy; + HIMAGELIST img; + BOOL ret; + + memset(&info, 0xCC, sizeof(info)); + info.mask = 0; + memcpy(&dummy, &info, sizeof(info)); + + ret = SendMessageA(hwnd, BCM_GETSPLITINFO, 0, (LPARAM)&info); + if (ret != TRUE) + { + static BOOL once; + if (!once) + win_skip("BCM_GETSPLITINFO message is unavailable. Skipping related tests\n"); /* Pre-Vista */ + once = TRUE; + return; + } + ok(!memcmp(&info, &dummy, sizeof(info)), "[%u] split info struct was changed with mask = 0\n", button); + + ret = SendMessageA(hwnd, BCM_GETSPLITINFO, 0, 0); + ok(ret == FALSE, "[%u] expected FALSE, got %d\n", button, ret); + ret = SendMessageA(hwnd, BCM_SETSPLITINFO, 0, 0); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + + info.mask = BCSIF_GLYPH | BCSIF_SIZE | BCSIF_STYLE; + ret = SendMessageA(hwnd, BCM_GETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + ok(info.mask == (BCSIF_GLYPH | BCSIF_SIZE | BCSIF_STYLE), "[%u] wrong mask, got %u\n", button, info.mask); + ok(info.himlGlyph == (HIMAGELIST)0x36, "[%u] expected 0x36 default glyph, got 0x%p\n", button, info.himlGlyph); + ok(info.uSplitStyle == BCSS_STRETCH, "[%u] expected 0x%08x default style, got 0x%08x\n", button, BCSS_STRETCH, info.uSplitStyle); + ok(info.size.cx == glyph_size, "[%u] expected %d default size.cx, got %d\n", button, glyph_size, info.size.cx); + ok(info.size.cy == 0, "[%u] expected 0 default size.cy, got %d\n", button, info.size.cy); + + info.mask = BCSIF_SIZE; + info.size.cx = glyph_size + 7; + info.size.cy = 0; + ret = SendMessageA(hwnd, BCM_SETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + info.size.cx = info.size.cy = 0xdeadbeef; + ret = SendMessageA(hwnd, BCM_GETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + ok(info.mask == BCSIF_SIZE, "[%u] wrong mask, got %u\n", button, info.mask); + ok(info.size.cx == glyph_size + 7, "[%u] expected %d, got %d\n", button, glyph_size + 7, info.size.cx); + ok(info.size.cy == 0, "[%u] expected 0, got %d\n", button, info.size.cy); + + /* Invalid size.cx resets it to default glyph size, while size.cy is stored */ + info.size.cx = 0; + info.size.cy = -20; + ret = SendMessageA(hwnd, BCM_SETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + info.size.cx = info.size.cy = 0xdeadbeef; + ret = SendMessageA(hwnd, BCM_GETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + ok(info.mask == BCSIF_SIZE, "[%u] wrong mask, got %u\n", button, info.mask); + ok(info.size.cx == glyph_size, "[%u] expected %d, got %d\n", button, glyph_size, info.size.cx); + ok(info.size.cy == -20, "[%u] expected -20, got %d\n", button, info.size.cy); + + info.size.cx = -glyph_size - 7; + info.size.cy = -10; + ret = SendMessageA(hwnd, BCM_SETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + info.size.cx = info.size.cy = 0xdeadbeef; + ret = SendMessageA(hwnd, BCM_GETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + ok(info.mask == BCSIF_SIZE, "[%u] wrong mask, got %u\n", button, info.mask); + ok(info.size.cx == glyph_size, "[%u] expected %d, got %d\n", button, glyph_size, info.size.cx); + ok(info.size.cy == -10, "[%u] expected -10, got %d\n", button, info.size.cy); + + /* Set to a valid size other than glyph_size */ + info.mask = BCSIF_SIZE; + info.size.cx = glyph_size + 7; + info.size.cy = 11; + ret = SendMessageA(hwnd, BCM_SETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + info.size.cx = info.size.cy = 0xdeadbeef; + ret = SendMessageA(hwnd, BCM_GETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + ok(info.mask == BCSIF_SIZE, "[%u] wrong mask, got %u\n", button, info.mask); + ok(info.size.cx == glyph_size + 7, "[%u] expected %d, got %d\n", button, glyph_size + 7, info.size.cx); + ok(info.size.cy == 11, "[%u] expected 11, got %d\n", button, info.size.cy); + + /* Change the glyph, size.cx should be automatically adjusted and size.cy set to 0 */ + dummy.mask = BCSIF_GLYPH; + dummy.himlGlyph = (HIMAGELIST)0x35; + ret = SendMessageA(hwnd, BCM_SETSPLITINFO, 0, (LPARAM)&dummy); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + info.mask = BCSIF_GLYPH | BCSIF_SIZE; + ret = SendMessageA(hwnd, BCM_GETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + ok(info.mask == (BCSIF_GLYPH | BCSIF_SIZE), "[%u] wrong mask, got %u\n", button, info.mask); + ok(info.himlGlyph == (HIMAGELIST)0x35, "[%u] expected 0x35, got %p\n", button, info.himlGlyph); + ok(info.size.cx == glyph_size, "[%u] expected %d, got %d\n", button, glyph_size, info.size.cx); + ok(info.size.cy == 0, "[%u] expected 0, got %d\n", button, info.size.cy); + + /* Unless the size is specified manually */ + dummy.mask = BCSIF_GLYPH | BCSIF_SIZE; + dummy.himlGlyph = (HIMAGELIST)0x34; + dummy.size.cx = glyph_size + 11; + dummy.size.cy = 7; + ret = SendMessageA(hwnd, BCM_SETSPLITINFO, 0, (LPARAM)&dummy); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + ret = SendMessageA(hwnd, BCM_GETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + ok(info.mask == (BCSIF_GLYPH | BCSIF_SIZE), "[%u] wrong mask, got %u\n", button, info.mask); + ok(info.himlGlyph == (HIMAGELIST)0x34, "[%u] expected 0x34, got %p\n", button, info.himlGlyph); + ok(info.size.cx == glyph_size + 11, "[%u] expected %d, got %d\n", button, glyph_size, info.size.cx); + ok(info.size.cy == 7, "[%u] expected 7, got %d\n", button, info.size.cy); + + /* Add the BCSS_IMAGE style manually with the wrong BCSIF_GLYPH mask, should treat it as invalid image */ + info.mask = BCSIF_GLYPH | BCSIF_STYLE; + info.himlGlyph = (HIMAGELIST)0x37; + info.uSplitStyle = BCSS_IMAGE; + ret = SendMessageA(hwnd, BCM_SETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + info.mask |= BCSIF_SIZE; + ret = SendMessageA(hwnd, BCM_GETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + ok(info.mask == (BCSIF_GLYPH | BCSIF_STYLE | BCSIF_SIZE), "[%u] wrong mask, got %u\n", button, info.mask); + ok(info.himlGlyph == (HIMAGELIST)0x37, "[%u] expected 0x37, got %p\n", button, info.himlGlyph); + ok(info.uSplitStyle == BCSS_IMAGE, "[%u] expected 0x%08x style, got 0x%08x\n", button, BCSS_IMAGE, info.uSplitStyle); + ok(info.size.cx == border_w, "[%u] expected %d, got %d\n", button, border_w, info.size.cx); + ok(info.size.cy == 0, "[%u] expected 0, got %d\n", button, info.size.cy); + + /* Change the size to prevent ambiguity */ + dummy.mask = BCSIF_SIZE; + dummy.size.cx = glyph_size + 5; + dummy.size.cy = 4; + ret = SendMessageA(hwnd, BCM_SETSPLITINFO, 0, (LPARAM)&dummy); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + ret = SendMessageA(hwnd, BCM_GETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + ok(info.mask == (BCSIF_GLYPH | BCSIF_STYLE | BCSIF_SIZE), "[%u] wrong mask, got %u\n", button, info.mask); + ok(info.himlGlyph == (HIMAGELIST)0x37, "[%u] expected 0x37, got %p\n", button, info.himlGlyph); + ok(info.uSplitStyle == BCSS_IMAGE, "[%u] expected 0x%08x style, got 0x%08x\n", button, BCSS_IMAGE, info.uSplitStyle); + ok(info.size.cx == glyph_size + 5, "[%u] expected %d, got %d\n", button, glyph_size + 5, info.size.cx); + ok(info.size.cy == 4, "[%u] expected 4, got %d\n", button, info.size.cy); + + /* Now remove the BCSS_IMAGE style manually with the wrong BCSIF_IMAGE mask */ + info.mask = BCSIF_IMAGE | BCSIF_STYLE; + info.himlGlyph = (HIMAGELIST)0x35; + info.uSplitStyle = BCSS_STRETCH; + ret = SendMessageA(hwnd, BCM_SETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + info.mask |= BCSIF_SIZE; + ret = SendMessageA(hwnd, BCM_GETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + ok(info.mask == (BCSIF_IMAGE | BCSIF_STYLE | BCSIF_SIZE), "[%u] wrong mask, got %u\n", button, info.mask); + ok(info.himlGlyph == (HIMAGELIST)0x35, "[%u] expected 0x35, got %p\n", button, info.himlGlyph); + ok(info.uSplitStyle == BCSS_STRETCH, "[%u] expected 0x%08x style, got 0x%08x\n", button, BCSS_STRETCH, info.uSplitStyle); + ok(info.size.cx == glyph_size, "[%u] expected %d, got %d\n", button, glyph_size, info.size.cx); + ok(info.size.cy == 0, "[%u] expected 0, got %d\n", button, info.size.cy); + + /* Add a proper valid image, the BCSS_IMAGE style should be set automatically */ + img = pImageList_Create(42, 33, ILC_COLOR, 1, 1); + ok(img != NULL, "[%u] failed to create ImageList\n", button); + info.mask = BCSIF_IMAGE; + info.himlGlyph = img; + ret = SendMessageA(hwnd, BCM_SETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + info.mask |= BCSIF_STYLE | BCSIF_SIZE; + ret = SendMessageA(hwnd, BCM_GETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + ok(info.mask == (BCSIF_IMAGE | BCSIF_STYLE | BCSIF_SIZE), "[%u] wrong mask, got %u\n", button, info.mask); + ok(info.himlGlyph == img, "[%u] expected %p, got %p\n", button, img, info.himlGlyph); + ok(info.uSplitStyle == (BCSS_IMAGE | BCSS_STRETCH), "[%u] expected 0x%08x style, got 0x%08x\n", button, BCSS_IMAGE | BCSS_STRETCH, info.uSplitStyle); + ok(info.size.cx == 42 + border_w, "[%u] expected %d, got %d\n", button, 42 + border_w, info.size.cx); + ok(info.size.cy == 0, "[%u] expected 0, got %d\n", button, info.size.cy); + pImageList_Destroy(img); + dummy.mask = BCSIF_SIZE; + dummy.size.cx = glyph_size + 5; + dummy.size.cy = 4; + ret = SendMessageA(hwnd, BCM_SETSPLITINFO, 0, (LPARAM)&dummy); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + + /* Change it to a glyph; when both specified, BCSIF_GLYPH takes priority */ + info.mask = BCSIF_GLYPH | BCSIF_IMAGE; + info.himlGlyph = (HIMAGELIST)0x37; + ret = SendMessageA(hwnd, BCM_SETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + info.mask |= BCSIF_STYLE | BCSIF_SIZE; + ret = SendMessageA(hwnd, BCM_GETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + ok(info.mask == (BCSIF_GLYPH | BCSIF_IMAGE | BCSIF_STYLE | BCSIF_SIZE), "[%u] wrong mask, got %u\n", button, info.mask); + ok(info.himlGlyph == (HIMAGELIST)0x37, "[%u] expected 0x37, got %p\n", button, info.himlGlyph); + ok(info.uSplitStyle == BCSS_STRETCH, "[%u] expected 0x%08x style, got 0x%08x\n", button, BCSS_STRETCH, info.uSplitStyle); + ok(info.size.cx == glyph_size, "[%u] expected %d, got %d\n", button, glyph_size, info.size.cx); + ok(info.size.cy == 0, "[%u] expected 0, got %d\n", button, info.size.cy); + + /* Try a NULL image */ + info.mask = BCSIF_IMAGE; + info.himlGlyph = NULL; + ret = SendMessageA(hwnd, BCM_SETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + info.mask |= BCSIF_STYLE | BCSIF_SIZE; + ret = SendMessageA(hwnd, BCM_GETSPLITINFO, 0, (LPARAM)&info); + ok(ret == TRUE, "[%u] expected TRUE, got %d\n", button, ret); + ok(info.mask == (BCSIF_IMAGE | BCSIF_STYLE | BCSIF_SIZE), "[%u] wrong mask, got %u\n", button, info.mask); + ok(info.himlGlyph == NULL, "[%u] expected NULL, got %p\n", button, info.himlGlyph); + ok(info.uSplitStyle == (BCSS_IMAGE | BCSS_STRETCH), "[%u] expected 0x%08x style, got 0x%08x\n", button, BCSS_IMAGE | BCSS_STRETCH, info.uSplitStyle); + ok(info.size.cx == border_w, "[%u] expected %d, got %d\n", button, border_w, info.size.cx); + ok(info.size.cy == 0, "[%u] expected 0, got %d\n", button, info.size.cy); +} + static void test_button_data(void) { static const DWORD styles[] = @@ -1479,6 +1687,9 @@ static void test_button_data(void) ok(desc->style == (WS_CHILD | BS_NOTIFY | styles[i]), "Unexpected 'style' field.\n"); }
+ /* Data set and retrieved by these messages is valid for all buttons */ + test_bcm_splitinfo(hwnd); + DestroyWindow(hwnd); }
Signed-off-by: Nikolay Sivov nsivov@codeweavers.com
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=20123 Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/comctl32/button.c | 262 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 259 insertions(+), 3 deletions(-)
diff --git a/dlls/comctl32/button.c b/dlls/comctl32/button.c index 213ab29..47933c0 100644 --- a/dlls/comctl32/button.c +++ b/dlls/comctl32/button.c @@ -104,7 +104,11 @@ static void CB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action ); static void GB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action ); static void UB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action ); static void OB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action ); +static void SB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action ); static void BUTTON_CheckAutoRadioButton( HWND hwnd ); +static void get_split_button_rects(const BUTTON_INFO*, const RECT*, RECT*, RECT*); +static BOOL notify_split_button_dropdown(const BUTTON_INFO*, const POINT*, HWND); +static void draw_split_button_dropdown_glyph(const BUTTON_INFO*, HDC, RECT*);
#define MAX_BTN_TYPE 16
@@ -155,8 +159,8 @@ static const pfPaint btnPaintFunc[MAX_BTN_TYPE] = CB_Paint, /* BS_AUTORADIOBUTTON */ NULL, /* BS_PUSHBOX */ OB_Paint, /* BS_OWNERDRAW */ - PB_Paint, /* BS_SPLITBUTTON */ - PB_Paint, /* BS_DEFSPLITBUTTON */ + SB_Paint, /* BS_SPLITBUTTON */ + SB_Paint, /* BS_DEFSPLITBUTTON */ PB_Paint, /* BS_COMMANDLINK */ PB_Paint /* BS_DEFCOMMANDLINK */ }; @@ -554,6 +558,11 @@ static LRESULT CALLBACK BUTTON_WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, L infoPtr->state |= BUTTON_BTNPRESSED; SetCapture( hWnd ); } + else if (wParam == VK_UP || wParam == VK_DOWN) + { + /* Up and down arrows work on every button, and even with BCSS_NOSPLIT */ + notify_split_button_dropdown(infoPtr, NULL, hWnd); + } break;
case WM_LBUTTONDBLCLK: @@ -567,8 +576,14 @@ static LRESULT CALLBACK BUTTON_WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, L } /* fall through */ case WM_LBUTTONDOWN: - SetCapture( hWnd ); SetFocus( hWnd ); + + if ((btn_type == BS_SPLITBUTTON || btn_type == BS_DEFSPLITBUTTON) && + !(infoPtr->split_style & BCSS_NOSPLIT) && + notify_split_button_dropdown(infoPtr, &pt, hWnd)) + break; + + SetCapture( hWnd ); infoPtr->state |= BUTTON_BTNPRESSED; SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 ); break; @@ -579,6 +594,8 @@ static LRESULT CALLBACK BUTTON_WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, L /* fall through */ case WM_LBUTTONUP: state = infoPtr->state; + if (state & BST_DROPDOWNPUSHED) + SendMessageW(hWnd, BCM_SETDROPDOWNSTATE, FALSE, 0); if (!(state & BUTTON_BTNPRESSED)) break; infoPtr->state &= BUTTON_NSTATES; if (!(state & BST_PUSHED)) @@ -2044,6 +2061,245 @@ static void OB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action ) if (hrgn) DeleteObject( hrgn ); }
+ +/********************************************************************** + * Split Button Functions + */ +static void SB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action ) +{ + LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE); + LONG state = infoPtr->state; + UINT dtFlags = (UINT)-1L; + + RECT rc, push_rect, dropdown_rect; + NMCUSTOMDRAW nmcd; + HPEN pen, old_pen; + HBRUSH old_brush; + INT old_bk_mode; + LRESULT cdrf; + HWND parent; + HRGN hrgn; + + GetClientRect(infoPtr->hwnd, &rc); + + /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */ + if (infoPtr->font) SelectObject(hDC, infoPtr->font); + if (!(parent = GetParent(infoPtr->hwnd))) parent = infoPtr->hwnd; + SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd); + + hrgn = set_control_clipping(hDC, &rc); + + pen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME)); + old_pen = SelectObject(hDC, pen); + old_brush = SelectObject(hDC, GetSysColorBrush(COLOR_BTNFACE)); + old_bk_mode = SetBkMode(hDC, TRANSPARENT); + + init_custom_draw(&nmcd, infoPtr, hDC, &rc); + + /* Send erase notifications */ + cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd); + if (cdrf & CDRF_SKIPDEFAULT) goto cleanup; + + if (get_button_type(style) == BS_DEFSPLITBUTTON) + { + if (action != ODA_FOCUS) + Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom); + InflateRect(&rc, -1, -1); + /* The split will now be off by 1 pixel, but + that's exactly what Windows does as well */ + } + + get_split_button_rects(infoPtr, &rc, &push_rect, &dropdown_rect); + if (infoPtr->split_style & BCSS_NOSPLIT) + push_rect = rc; + + /* Skip the frame drawing if only focus has changed */ + if (action != ODA_FOCUS) + { + UINT flags = DFCS_BUTTONPUSH; + + if (style & BS_FLAT) flags |= DFCS_MONO; + else if (state & BST_PUSHED) + flags |= (get_button_type(style) == BS_DEFSPLITBUTTON) + ? DFCS_FLAT : DFCS_PUSHED; + + if (state & (BST_CHECKED | BST_INDETERMINATE)) + flags |= DFCS_CHECKED; + + if (infoPtr->split_style & BCSS_NOSPLIT) + DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags); + else + { + UINT dropdown_flags = flags & ~DFCS_CHECKED; + + if (state & BST_DROPDOWNPUSHED) + dropdown_flags = (dropdown_flags & ~DFCS_FLAT) | DFCS_PUSHED; + + /* Adjust for shadow and draw order so it looks properly */ + if (infoPtr->split_style & BCSS_ALIGNLEFT) + { + dropdown_rect.right++; + DrawFrameControl(hDC, &dropdown_rect, DFC_BUTTON, dropdown_flags); + dropdown_rect.right--; + DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags); + } + else + { + push_rect.right++; + DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags); + push_rect.right--; + DrawFrameControl(hDC, &dropdown_rect, DFC_BUTTON, dropdown_flags); + } + } + } + + if (cdrf & CDRF_NOTIFYPOSTERASE) + { + nmcd.dwDrawStage = CDDS_POSTERASE; + SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd); + } + + /* Send paint notifications */ + nmcd.dwDrawStage = CDDS_PREPAINT; + cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd); + if (cdrf & CDRF_SKIPDEFAULT) goto cleanup; + + /* Shrink push button rect so that the content won't touch the surrounding frame */ + InflateRect(&push_rect, -2, -2); + + if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS) + { + COLORREF old_color = SetTextColor(hDC, GetSysColor(COLOR_BTNTEXT)); + RECT label_rect = push_rect, image_rect, text_rect; + + dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &label_rect, &image_rect, &text_rect); + + if (dtFlags != (UINT)-1L) + BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &image_rect, &text_rect); + + draw_split_button_dropdown_glyph(infoPtr, hDC, &dropdown_rect); + SetTextColor(hDC, old_color); + } + + if (cdrf & CDRF_NOTIFYPOSTPAINT) + { + nmcd.dwDrawStage = CDDS_POSTPAINT; + SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd); + } + if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup; + + if (action == ODA_FOCUS || (state & BST_FOCUS)) + DrawFocusRect(hDC, &push_rect); + +cleanup: + SelectObject(hDC, old_pen); + SelectObject(hDC, old_brush); + SetBkMode(hDC, old_bk_mode); + SelectClipRgn(hDC, hrgn); + if (hrgn) DeleteObject(hrgn); + DeleteObject(pen); +} + +/* Given the full button rect of the split button, retrieve the push part and the dropdown part */ +static inline void get_split_button_rects(const BUTTON_INFO *infoPtr, const RECT *button_rect, + RECT *push_rect, RECT *dropdown_rect) +{ + *push_rect = *dropdown_rect = *button_rect; + + /* The dropdown takes priority if the client rect is too small, it will only have a dropdown */ + if (infoPtr->split_style & BCSS_ALIGNLEFT) + { + dropdown_rect->right = min(button_rect->left + infoPtr->glyph_size.cx, button_rect->right); + push_rect->left = dropdown_rect->right; + } + else + { + dropdown_rect->left = max(button_rect->right - infoPtr->glyph_size.cx, button_rect->left); + push_rect->right = dropdown_rect->left; + } +} + +/* Notify the parent if the point is within the dropdown and return TRUE (always notify if NULL) */ +static BOOL notify_split_button_dropdown(const BUTTON_INFO *infoPtr, const POINT *pt, HWND hwnd) +{ + NMBCDROPDOWN nmbcd; + + GetClientRect(hwnd, &nmbcd.rcButton); + if (pt) + { + RECT push_rect, dropdown_rect; + + get_split_button_rects(infoPtr, &nmbcd.rcButton, &push_rect, &dropdown_rect); + if (!PtInRect(&dropdown_rect, *pt)) + return FALSE; + + /* If it's already down (set manually via BCM_SETDROPDOWNSTATE), fake the notify */ + if (infoPtr->state & BST_DROPDOWNPUSHED) + return TRUE; + } + SendMessageW(hwnd, BCM_SETDROPDOWNSTATE, TRUE, 0); + + nmbcd.hdr.hwndFrom = hwnd; + nmbcd.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID); + nmbcd.hdr.code = BCN_DROPDOWN; + SendMessageW(GetParent(hwnd), WM_NOTIFY, nmbcd.hdr.idFrom, (LPARAM)&nmbcd); + + SendMessageW(hwnd, BCM_SETDROPDOWNSTATE, FALSE, 0); + return TRUE; +} + +/* Draw the split button dropdown glyph or image */ +static void draw_split_button_dropdown_glyph(const BUTTON_INFO *infoPtr, HDC hdc, RECT *rect) +{ + if (infoPtr->split_style & BCSS_IMAGE) + { + int w, h; + + /* When the glyph is an image list, Windows is very buggy with BCSS_STRETCH, + positions it weirdly and doesn't even stretch it, but instead extends the + image, leaking into other images in the list (or black if none). Instead, + we'll ignore this and just position it at center as without BCSS_STRETCH. */ + if (!ImageList_GetIconSize(infoPtr->glyph, &w, &h)) return; + + ImageList_Draw(infoPtr->glyph, + (ImageList_GetImageCount(infoPtr->glyph) == 1) ? 0 : get_draw_state(infoPtr) - 1, + hdc, rect->left + (rect->right - rect->left - w) / 2, + rect->top + (rect->bottom - rect->top - h) / 2, ILD_NORMAL); + } + else if (infoPtr->glyph_size.cy >= 0) + { + /* infoPtr->glyph is a character code from Marlett */ + HFONT font, old_font; + LOGFONTW logfont = { 0, 0, 0, 0, FW_NORMAL, 0, 0, 0, SYMBOL_CHARSET, 0, 0, 0, 0, + { 'M','a','r','l','e','t','t',0 } }; + if (infoPtr->glyph_size.cy) + { + /* BCSS_STRETCH preserves aspect ratio, uses minimum as size */ + if (infoPtr->split_style & BCSS_STRETCH) + logfont.lfHeight = min(infoPtr->glyph_size.cx, infoPtr->glyph_size.cy); + else + { + logfont.lfWidth = infoPtr->glyph_size.cx; + logfont.lfHeight = infoPtr->glyph_size.cy; + } + } + else logfont.lfHeight = infoPtr->glyph_size.cx; + + if ((font = CreateFontIndirectW(&logfont))) + { + old_font = SelectObject(hdc, font); + DrawTextW(hdc, (const WCHAR*)&infoPtr->glyph, 1, rect, + DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOCLIP | DT_NOPREFIX); + SelectObject(hdc, old_font); + DeleteObject(font); + } + } +} + + +/********************************************************************** + * Themed Paint Functions + */ static void PB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused) { RECT bgRect, textRect;
Signed-off-by: Nikolay Sivov nsivov@codeweavers.com
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=20123 Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
I've tried to make this match Windows 7's Aero Theme as much as I could, but I only had Luna theme to test with for Wine. However, the clipping behavior and focus rectangle do seem to match perfectly.
The only thing I'm concerned about is DrawThemeEdge, it draws a classic edge with the luna theme I downloaded. I'm not sure if it's a defect in Wine or what -- if there's a better way, I'd love some feedback on it.
dlls/comctl32/button.c | 113 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 2 deletions(-)
diff --git a/dlls/comctl32/button.c b/dlls/comctl32/button.c index 47933c0..1f29fb2 100644 --- a/dlls/comctl32/button.c +++ b/dlls/comctl32/button.c @@ -170,6 +170,7 @@ typedef void (*pfThemedPaint)( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc static void PB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused); static void CB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused); static void GB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused); +static void SB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
static const pfThemedPaint btnThemedPaintFunc[MAX_BTN_TYPE] = { @@ -185,8 +186,8 @@ static const pfThemedPaint btnThemedPaintFunc[MAX_BTN_TYPE] = CB_ThemedPaint, /* BS_AUTORADIOBUTTON */ NULL, /* BS_PUSHBOX */ NULL, /* BS_OWNERDRAW */ - NULL, /* BS_SPLITBUTTON */ - NULL, /* BS_DEFSPLITBUTTON */ + SB_ThemedPaint, /* BS_SPLITBUTTON */ + SB_ThemedPaint, /* BS_DEFSPLITBUTTON */ NULL, /* BS_COMMANDLINK */ NULL, /* BS_DEFCOMMANDLINK */ }; @@ -2526,6 +2527,114 @@ static void GB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, in if (hPrevFont) SelectObject(hDC, hPrevFont); }
+static void SB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused) +{ + HFONT old_font = infoPtr->font ? SelectObject(hDC, infoPtr->font) : NULL; + RECT rc, content_rect, push_rect, dropdown_rect; + NMCUSTOMDRAW nmcd; + LRESULT cdrf; + HWND parent; + + GetClientRect(infoPtr->hwnd, &rc); + init_custom_draw(&nmcd, infoPtr, hDC, &rc); + + parent = GetParent(infoPtr->hwnd); + if (!parent) parent = infoPtr->hwnd; + + /* Send erase notifications */ + cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd); + if (cdrf & CDRF_SKIPDEFAULT) goto cleanup; + + if (IsThemeBackgroundPartiallyTransparent(theme, BP_PUSHBUTTON, state)) + DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL); + + /* The zone outside the content is ignored for the dropdown (draws over) */ + GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &rc, &content_rect); + get_split_button_rects(infoPtr, &rc, &push_rect, &dropdown_rect); + + if (infoPtr->split_style & BCSS_NOSPLIT) + { + push_rect = rc; + DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &rc, NULL); + } + else + { + RECT r = { dropdown_rect.left, content_rect.top, dropdown_rect.right, content_rect.bottom }; + UINT edge = (infoPtr->split_style & BCSS_ALIGNLEFT) ? BF_RIGHT : BF_LEFT; + const RECT *clip = NULL; + + /* If only the dropdown is pressed, we need to draw it separately */ + if (state != PBS_PRESSED && (infoPtr->state & BST_DROPDOWNPUSHED)) + { + DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, PBS_PRESSED, &rc, &dropdown_rect); + clip = &push_rect; + } + DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &rc, clip); + + /* Draw the separator */ + DrawThemeEdge(theme, hDC, BP_PUSHBUTTON, state, &r, EDGE_ETCHED, edge, NULL); + + /* The content rect should be the content area of the push button */ + GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &push_rect, &content_rect); + } + + if (cdrf & CDRF_NOTIFYPOSTERASE) + { + nmcd.dwDrawStage = CDDS_POSTERASE; + SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd); + } + + /* Send paint notifications */ + nmcd.dwDrawStage = CDDS_PREPAINT; + cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd); + if (cdrf & CDRF_SKIPDEFAULT) goto cleanup; + + if (!(cdrf & CDRF_DOERASE)) + { + COLORREF old_color, color; + INT old_bk_mode; + WCHAR *text; + + if ((text = get_button_text(infoPtr))) + { + DrawThemeText(theme, hDC, BP_PUSHBUTTON, state, text, lstrlenW(text), dtFlags, 0, &content_rect); + heap_free(text); + } + + GetThemeColor(theme, BP_PUSHBUTTON, state, TMT_TEXTCOLOR, &color); + old_bk_mode = SetBkMode(hDC, TRANSPARENT); + old_color = SetTextColor(hDC, color); + + draw_split_button_dropdown_glyph(infoPtr, hDC, &dropdown_rect); + + SetTextColor(hDC, old_color); + SetBkMode(hDC, old_bk_mode); + } + + if (cdrf & CDRF_NOTIFYPOSTPAINT) + { + nmcd.dwDrawStage = CDDS_POSTPAINT; + SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd); + } + if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup; + + if (focused) + { + MARGINS margins; + + GetThemeMargins(theme, hDC, BP_PUSHBUTTON, state, TMT_CONTENTMARGINS, NULL, &margins); + + push_rect.left += margins.cxLeftWidth; + push_rect.top += margins.cyTopHeight; + push_rect.right -= margins.cxRightWidth; + push_rect.bottom -= margins.cyBottomHeight; + DrawFocusRect(hDC, &push_rect); + } + +cleanup: + if (old_font) SelectObject(hDC, old_font); +} + void BUTTON_Register(void) { WNDCLASSW wndClass;
Signed-off-by: Nikolay Sivov nsivov@codeweavers.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/comctl32/tests/button.c | 49 ++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+)
diff --git a/dlls/comctl32/tests/button.c b/dlls/comctl32/tests/button.c index 4de0769..bb9c87f 100644 --- a/dlls/comctl32/tests/button.c +++ b/dlls/comctl32/tests/button.c @@ -186,6 +186,7 @@ static LRESULT WINAPI test_parent_wndproc(HWND hwnd, UINT message, WPARAM wParam static HDC cd_first_hdc; struct message msg = { 0 }; NMCUSTOMDRAW *cd = (NMCUSTOMDRAW*)lParam; + NMBCDROPDOWN *bcd = (NMBCDROPDOWN*)lParam; LRESULT ret;
if (ignore_message( message )) return 0; @@ -252,6 +253,29 @@ static LRESULT WINAPI test_parent_wndproc(HWND hwnd, UINT message, WPARAM wParam return ret; }
+ if (message == WM_NOTIFY && bcd->hdr.code == BCN_DROPDOWN) + { + UINT button = GetWindowLongW(bcd->hdr.hwndFrom, GWL_STYLE) & BS_TYPEMASK; + RECT rc; + + GetClientRect(bcd->hdr.hwndFrom, &rc); + + ok(bcd->hdr.hwndFrom != NULL, "Received BCN_DROPDOWN with no hwnd attached, wParam %lu id %lu\n", + wParam, bcd->hdr.idFrom); + ok(bcd->hdr.idFrom == wParam, "[%u] Mismatch between wParam (%lu) and idFrom (%lu)\n", + button, wParam, bcd->hdr.idFrom); + ok(EqualRect(&rc, &bcd->rcButton), "[%u] Wrong rcButton, expected %s got %s\n", + button, wine_dbgstr_rect(&rc), wine_dbgstr_rect(&bcd->rcButton)); + + msg.message = message; + msg.flags = sent|parent|wparam|lparam|id; + msg.wParam = wParam; + msg.lParam = lParam; + msg.id = BCN_DROPDOWN; + add_message(sequences, COMBINED_SEQ_INDEX, &msg); + return 0; + } + if (message == WM_PAINT) { PAINTSTRUCT ps; @@ -569,6 +593,20 @@ static const struct message pre_post_pre_post_cd_seq[] = { 0 } };
+static const struct message bcn_dropdown_seq[] = +{ + { WM_KEYDOWN, sent|wparam|lparam, VK_DOWN, 0 }, + { BCM_SETDROPDOWNSTATE, sent|wparam|lparam|defwinproc, 1, 0 }, + { WM_NOTIFY, sent|parent|id, 0, 0, BCN_DROPDOWN }, + { BCM_SETDROPDOWNSTATE, sent|wparam|lparam|defwinproc, 0, 0 }, + { WM_KEYUP, sent|wparam|lparam, VK_DOWN, 0xc0000000 }, + { WM_PAINT, sent }, + { WM_DRAWITEM, sent|parent|optional }, /* for owner draw button */ + { WM_PAINT, sent|optional }, /* sometimes sent rarely */ + { WM_DRAWITEM, sent|parent|optional }, + { 0 } +}; + static HWND create_button(DWORD style, HWND parent) { HMENU menuid = 0; @@ -932,6 +970,17 @@ static void test_button_messages(void) }
disable_test_cd(); + + if (!broken(LOBYTE(LOWORD(GetVersion())) < 6)) /* not available pre-Vista */ + { + /* Send down arrow key to make the buttons send the drop down notification */ + flush_sequences(sequences, NUM_MSG_SEQUENCES); + SendMessageW(hwnd, WM_KEYDOWN, VK_DOWN, 0); + SendMessageW(hwnd, WM_KEYUP, VK_DOWN, 0xc0000000); + while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg); + ok_sequence(sequences, COMBINED_SEQ_INDEX, bcn_dropdown_seq, "BCN_DROPDOWN from the button", FALSE); + } + DestroyWindow(hwnd); }
Signed-off-by: Nikolay Sivov nsivov@codeweavers.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/comctl32/button.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-)
diff --git a/dlls/comctl32/button.c b/dlls/comctl32/button.c index 1f29fb2..e974f86 100644 --- a/dlls/comctl32/button.c +++ b/dlls/comctl32/button.c @@ -197,6 +197,7 @@ typedef BOOL (*pfGetIdealSize)(BUTTON_INFO *infoPtr, SIZE *size); static BOOL PB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size); static BOOL CB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size); static BOOL GB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size); +static BOOL SB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
static const pfGetIdealSize btnGetIdealSizeFunc[MAX_BTN_TYPE] = { PB_GetIdealSize, /* BS_PUSHBUTTON */ @@ -211,9 +212,9 @@ static const pfGetIdealSize btnGetIdealSizeFunc[MAX_BTN_TYPE] = { CB_GetIdealSize, /* BS_AUTORADIOBUTTON */ GB_GetIdealSize, /* BS_PUSHBOX */ GB_GetIdealSize, /* BS_OWNERDRAW */ + SB_GetIdealSize, /* BS_SPLITBUTTON */ + SB_GetIdealSize, /* BS_DEFSPLITBUTTON */ /* GetIdealSize() for following types are unimplemented, use BS_PUSHBUTTON's for now */ - PB_GetIdealSize, /* BS_SPLITBUTTON */ - PB_GetIdealSize, /* BS_DEFSPLITBUTTON */ PB_GetIdealSize, /* BS_COMMANDLINK */ PB_GetIdealSize /* BS_DEFCOMMANDLINK */ }; @@ -1347,6 +1348,25 @@ static BOOL PB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size) return TRUE; }
+static BOOL SB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size) +{ + LONG extra_width = infoPtr->glyph_size.cx * 2 + GetSystemMetrics(SM_CXEDGE); + SIZE label_size; + + if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0) + { + BUTTON_GetClientRectSize(infoPtr, size); + size->cx = max(size->cx, extra_width); + } + else + { + BUTTON_GetLabelIdealSize(infoPtr, size->cx, &label_size); + size->cx = label_size.cx + ((size->cx == 0) ? extra_width : 0); + size->cy = label_size.cy; + } + return TRUE; +} + /********************************************************************** * BUTTON_CalcLayoutRects *
Signed-off-by: Nikolay Sivov nsivov@codeweavers.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
This patch is mostly indentation changes. The actual changes are just addition of extra_width where applicable, testing for large_height to make sure the button's size.cy does not matter, and using the pushtype.style, as well as setting the split info.
dlls/comctl32/tests/button.c | 223 ++++++++++++++++++++--------------- 1 file changed, 130 insertions(+), 93 deletions(-)
diff --git a/dlls/comctl32/tests/button.c b/dlls/comctl32/tests/button.c index bb9c87f..f31f526 100644 --- a/dlls/comctl32/tests/button.c +++ b/dlls/comctl32/tests/button.c @@ -1896,7 +1896,18 @@ static void test_bcm_get_ideal_size(void) static const DWORD aligns[] = {0, BS_TOP, BS_LEFT, BS_RIGHT, BS_BOTTOM, BS_CENTER, BS_VCENTER, BS_RIGHTBUTTON, WS_EX_RIGHT}; DWORD default_style = WS_TABSTOP | WS_POPUP | WS_VISIBLE; - const LONG client_width = 400, client_height = 200; + const LONG client_width = 400, client_height = 200, extra_width = 123, large_height = 500; + struct + { + DWORD style; + LONG extra_width; + } pushtype[] = + { + { BS_PUSHBUTTON, 0 }, + { BS_DEFPUSHBUTTON, 0 }, + { BS_SPLITBUTTON, extra_width * 2 + GetSystemMetrics(SM_CXEDGE) }, + { BS_DEFSPLITBUTTON, extra_width * 2 + GetSystemMetrics(SM_CXEDGE) } + }; LONG image_width, height, line_count, text_width; HFONT hfont, prev_font; DWORD style, type; @@ -1912,7 +1923,7 @@ static void test_bcm_get_ideal_size(void) HIMAGELIST himl; BUTTON_IMAGELIST biml = {0}; RECT rect; - INT i, j; + INT i, j, k;
/* Check for NULL pointer handling */ hwnd = CreateWindowA(WC_BUTTONA, button_text, BS_PUSHBUTTON | default_style, 0, 0, client_width, client_height, @@ -1953,118 +1964,144 @@ static void test_bcm_get_ideal_size(void) return; }
- /* Tests for image placements */ - /* Prepare bitmap */ - image_width = 48; - height = 48; - hdc = GetDC(0); - hmask = CreateCompatibleBitmap(hdc, image_width, height); - hbmp = CreateCompatibleBitmap(hdc, image_width, height); +#define set_split_info(hwnd) do { \ + BUTTON_SPLITINFO _info; \ + int _ret; \ + _info.mask = BCSIF_SIZE; \ + _info.size.cx = extra_width; \ + _info.size.cy = large_height; \ + _ret = SendMessageA(hwnd, BCM_SETSPLITINFO, 0, (LPARAM)&_info); \ + ok(_ret == TRUE, "Expected BCM_SETSPLITINFO message to return true\n"); \ +} while (0)
- /* Only bitmap for push button, ideal size should be enough for image and text */ - hwnd = CreateWindowA(WC_BUTTONA, button_text, BS_DEFPUSHBUTTON | BS_BITMAP | default_style, 0, 0, client_width, - client_height, NULL, NULL, 0, NULL); - ok(hwnd != NULL, "Expect hwnd not NULL\n"); - SendMessageA(hwnd, BM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)hbmp); - SendMessageA(hwnd, WM_SETFONT, (WPARAM)hfont, (LPARAM)TRUE); - ZeroMemory(&size, sizeof(size)); - ret = SendMessageA(hwnd, BCM_GETIDEALSIZE, 0, (LPARAM)&size); - ok(ret, "Expect BCM_GETIDEALSIZE message to return true\n"); - /* Ideal size contains text rect even show bitmap only */ - ok((size.cx >= image_width + text_width && size.cy >= max(height, tm.tmHeight)), - "Expect ideal cx %d >= %d and ideal cy %d >= %d\n", size.cx, image_width + text_width, - size.cy, max(height, tm.tmHeight)); - DestroyWindow(hwnd); + for (k = 0; k < ARRAY_SIZE(pushtype); k++) + { + /* Tests for image placements */ + /* Prepare bitmap */ + image_width = 48; + height = 48; + hdc = GetDC(0); + hmask = CreateCompatibleBitmap(hdc, image_width, height); + hbmp = CreateCompatibleBitmap(hdc, image_width, height); + + /* Only bitmap for push button, ideal size should be enough for image and text */ + hwnd = CreateWindowA(WC_BUTTONA, button_text, pushtype[k].style | BS_BITMAP | default_style, 0, 0, client_width, + client_height, NULL, NULL, 0, NULL); + ok(hwnd != NULL, "Expect hwnd not NULL\n"); + set_split_info(hwnd); + SendMessageA(hwnd, BM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)hbmp); + SendMessageA(hwnd, WM_SETFONT, (WPARAM)hfont, (LPARAM)TRUE); + ZeroMemory(&size, sizeof(size)); + ret = SendMessageA(hwnd, BCM_GETIDEALSIZE, 0, (LPARAM)&size); + ok(ret, "Expect BCM_GETIDEALSIZE message to return true\n"); + /* Ideal size contains text rect even show bitmap only */ + ok(size.cx >= image_width + text_width + pushtype[k].extra_width && size.cy >= max(height, tm.tmHeight), + "Expect ideal cx %d >= %d and ideal cy %d >= %d\n", size.cx, + image_width + text_width + pushtype[k].extra_width, size.cy, max(height, tm.tmHeight)); + ok(size.cy < large_height, "Expect ideal cy %d < %d\n", size.cy, large_height); + DestroyWindow(hwnd);
- /* Image alignments when button has bitmap and text*/ - for (i = 0; i < ARRAY_SIZE(aligns); i++) - for (j = 0; j < ARRAY_SIZE(aligns); j++) + /* Image alignments when button has bitmap and text*/ + for (i = 0; i < ARRAY_SIZE(aligns); i++) + for (j = 0; j < ARRAY_SIZE(aligns); j++) + { + style = pushtype[k].style | default_style | aligns[i] | aligns[j]; + hwnd = CreateWindowA(WC_BUTTONA, button_text, style, 0, 0, client_width, client_height, NULL, NULL, 0, NULL); + ok(hwnd != NULL, "Expect hwnd not NULL\n"); + set_split_info(hwnd); + SendMessageA(hwnd, BM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)hbmp); + SendMessageA(hwnd, WM_SETFONT, (WPARAM)hfont, (LPARAM)TRUE); + ZeroMemory(&size, sizeof(size)); + ret = SendMessageA(hwnd, BCM_GETIDEALSIZE, 0, (LPARAM)&size); + ok(ret, "Expect BCM_GETIDEALSIZE message to return true\n"); + if (!(style & (BS_CENTER | BS_VCENTER)) || ((style & BS_CENTER) && (style & BS_CENTER) != BS_CENTER) + || !(style & BS_VCENTER) || (style & BS_VCENTER) == BS_VCENTER) + ok(size.cx >= image_width + text_width + pushtype[k].extra_width && size.cy >= max(height, tm.tmHeight), + "Style: 0x%08x expect ideal cx %d >= %d and ideal cy %d >= %d\n", style, size.cx, + image_width + text_width + pushtype[k].extra_width, size.cy, max(height, tm.tmHeight)); + else + ok(size.cx >= max(text_width, height) + pushtype[k].extra_width && size.cy >= height + tm.tmHeight, + "Style: 0x%08x expect ideal cx %d >= %d and ideal cy %d >= %d\n", style, size.cx, + max(text_width, height) + pushtype[k].extra_width, size.cy, height + tm.tmHeight); + ok(size.cy < large_height, "Expect ideal cy %d < %d\n", size.cy, large_height); + DestroyWindow(hwnd); + } + + /* Image list alignments */ + himl = pImageList_Create(image_width, height, ILC_COLOR, 1, 1); + pImageList_Add(himl, hbmp, 0); + biml.himl = himl; + for (i = 0; i < ARRAY_SIZE(imagelist_aligns); i++) { - style = BS_DEFPUSHBUTTON | default_style | aligns[i] | aligns[j]; - hwnd = CreateWindowA(WC_BUTTONA, button_text, style, 0, 0, client_width, client_height, NULL, NULL, 0, NULL); + biml.uAlign = imagelist_aligns[i]; + hwnd = CreateWindowA(WC_BUTTONA, button_text, pushtype[k].style | default_style, 0, 0, client_width, + client_height, NULL, NULL, 0, NULL); ok(hwnd != NULL, "Expect hwnd not NULL\n"); - SendMessageA(hwnd, BM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)hbmp); + set_split_info(hwnd); SendMessageA(hwnd, WM_SETFONT, (WPARAM)hfont, (LPARAM)TRUE); + SendMessageA(hwnd, BCM_SETIMAGELIST, 0, (LPARAM)&biml); ZeroMemory(&size, sizeof(size)); ret = SendMessageA(hwnd, BCM_GETIDEALSIZE, 0, (LPARAM)&size); ok(ret, "Expect BCM_GETIDEALSIZE message to return true\n"); - if (!(style & (BS_CENTER | BS_VCENTER)) || ((style & BS_CENTER) && (style & BS_CENTER) != BS_CENTER) - || !(style & BS_VCENTER) || (style & BS_VCENTER) == BS_VCENTER) - ok((size.cx >= image_width + text_width && size.cy >= max(height, tm.tmHeight)), - "Style: 0x%08x expect ideal cx %d >= %d and ideal cy %d >= %d\n", style, size.cx, - image_width + text_width, size.cy, max(height, tm.tmHeight)); + if (biml.uAlign == BUTTON_IMAGELIST_ALIGN_TOP || biml.uAlign == BUTTON_IMAGELIST_ALIGN_BOTTOM) + ok(size.cx >= max(text_width, height) + pushtype[k].extra_width && size.cy >= height + tm.tmHeight, + "Align:%d expect ideal cx %d >= %d and ideal cy %d >= %d\n", biml.uAlign, size.cx, + max(text_width, height) + pushtype[k].extra_width, size.cy, height + tm.tmHeight); + else if (biml.uAlign == BUTTON_IMAGELIST_ALIGN_LEFT || biml.uAlign == BUTTON_IMAGELIST_ALIGN_RIGHT) + ok(size.cx >= image_width + text_width + pushtype[k].extra_width && size.cy >= max(height, tm.tmHeight), + "Align:%d expect ideal cx %d >= %d and ideal cy %d >= %d\n", biml.uAlign, size.cx, + image_width + text_width + pushtype[k].extra_width, size.cy, max(height, tm.tmHeight)); else - ok((size.cx >= max(text_width, height) && size.cy >= height + tm.tmHeight), - "Style: 0x%08x expect ideal cx %d >= %d and ideal cy %d >= %d\n", style, size.cx, - max(text_width, height), size.cy, height + tm.tmHeight); + ok(size.cx >= image_width + pushtype[k].extra_width && size.cy >= height, + "Align:%d expect ideal cx %d >= %d and ideal cy %d >= %d\n", + biml.uAlign, size.cx, image_width + pushtype[k].extra_width, size.cy, height); + ok(size.cy < large_height, "Expect ideal cy %d < %d\n", size.cy, large_height); DestroyWindow(hwnd); }
- /* Image list alignments */ - himl = pImageList_Create(image_width, height, ILC_COLOR, 1, 1); - pImageList_Add(himl, hbmp, 0); - biml.himl = himl; - for (i = 0; i < ARRAY_SIZE(imagelist_aligns); i++) - { - biml.uAlign = imagelist_aligns[i]; - hwnd = CreateWindowA(WC_BUTTONA, button_text, BS_DEFPUSHBUTTON | default_style, 0, 0, client_width, + /* Icon as image */ + /* Create icon from bitmap */ + ZeroMemory(&icon_info, sizeof(icon_info)); + icon_info.fIcon = TRUE; + icon_info.hbmMask = hmask; + icon_info.hbmColor = hbmp; + hicon = CreateIconIndirect(&icon_info); + + /* Only icon, ideal size should be enough for image and text */ + hwnd = CreateWindowA(WC_BUTTONA, button_text, pushtype[k].style | BS_ICON | default_style, 0, 0, client_width, + client_height, NULL, NULL, 0, NULL); + ok(hwnd != NULL, "Expect hwnd not NULL\n"); + set_split_info(hwnd); + SendMessageA(hwnd, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hicon); + SendMessageA(hwnd, WM_SETFONT, (WPARAM)hfont, (LPARAM)TRUE); + ZeroMemory(&size, sizeof(size)); + ret = SendMessageA(hwnd, BCM_GETIDEALSIZE, 0, (LPARAM)&size); + ok(ret, "Expect BCM_GETIDEALSIZE message to return true\n"); + /* Ideal size contains text rect even show icons only */ + ok(size.cx >= image_width + text_width + pushtype[k].extra_width && size.cy >= max(height, tm.tmHeight), + "Expect ideal cx %d >= %d and ideal cy %d >= %d\n", size.cx, + image_width + text_width + pushtype[k].extra_width, size.cy, max(height, tm.tmHeight)); + ok(size.cy < large_height, "Expect ideal cy %d < %d\n", size.cy, large_height); + DestroyWindow(hwnd); + + /* Show icon and text */ + hwnd = CreateWindowA(WC_BUTTONA, button_text, pushtype[k].style | default_style, 0, 0, client_width, client_height, NULL, NULL, 0, NULL); ok(hwnd != NULL, "Expect hwnd not NULL\n"); + set_split_info(hwnd); + SendMessageA(hwnd, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hicon); SendMessageA(hwnd, WM_SETFONT, (WPARAM)hfont, (LPARAM)TRUE); - SendMessageA(hwnd, BCM_SETIMAGELIST, 0, (LPARAM)&biml); ZeroMemory(&size, sizeof(size)); ret = SendMessageA(hwnd, BCM_GETIDEALSIZE, 0, (LPARAM)&size); ok(ret, "Expect BCM_GETIDEALSIZE message to return true\n"); - if (biml.uAlign == BUTTON_IMAGELIST_ALIGN_TOP || biml.uAlign == BUTTON_IMAGELIST_ALIGN_BOTTOM) - ok((size.cx >= max(text_width, height) && size.cy >= height + tm.tmHeight), - "Align:%d expect ideal cx %d >= %d and ideal cy %d >= %d\n", biml.uAlign, size.cx, - max(text_width, height), size.cy, height + tm.tmHeight); - else if (biml.uAlign == BUTTON_IMAGELIST_ALIGN_LEFT || biml.uAlign == BUTTON_IMAGELIST_ALIGN_RIGHT) - ok((size.cx >= image_width + text_width && size.cy >= max(height, tm.tmHeight)), - "Align:%d expect ideal cx %d >= %d and ideal cy %d >= %d\n", biml.uAlign, size.cx, - image_width + text_width, size.cy, max(height, tm.tmHeight)); - else - ok(size.cx >= image_width && size.cy >= height, "Align:%d expect ideal cx %d >= %d and ideal cy %d >= %d\n", - biml.uAlign, size.cx, image_width, size.cy, height); + ok(size.cx >= image_width + text_width + pushtype[k].extra_width && size.cy >= max(height, tm.tmHeight), + "Expect ideal cx %d >= %d and ideal cy %d >= %d\n", size.cx, + image_width + text_width + pushtype[k].extra_width, size.cy, max(height, tm.tmHeight)); + ok(size.cy < large_height, "Expect ideal cy %d < %d\n", size.cy, large_height); DestroyWindow(hwnd); }
- /* Icon as image */ - /* Create icon from bitmap */ - ZeroMemory(&icon_info, sizeof(icon_info)); - icon_info.fIcon = TRUE; - icon_info.hbmMask = hmask; - icon_info.hbmColor = hbmp; - hicon = CreateIconIndirect(&icon_info); - - /* Only icon, ideal size should be enough for image and text */ - hwnd = CreateWindowA(WC_BUTTONA, button_text, BS_DEFPUSHBUTTON | BS_ICON | default_style, 0, 0, client_width, - client_height, NULL, NULL, 0, NULL); - ok(hwnd != NULL, "Expect hwnd not NULL\n"); - SendMessageA(hwnd, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hicon); - SendMessageA(hwnd, WM_SETFONT, (WPARAM)hfont, (LPARAM)TRUE); - ZeroMemory(&size, sizeof(size)); - ret = SendMessageA(hwnd, BCM_GETIDEALSIZE, 0, (LPARAM)&size); - ok(ret, "Expect BCM_GETIDEALSIZE message to return true\n"); - /* Ideal size contains text rect even show icons only */ - ok((size.cx >= image_width + text_width && size.cy >= max(height, tm.tmHeight)), - "Expect ideal cx %d >= %d and ideal cy %d >= %d\n", size.cx, image_width + text_width, size.cy, - max(height, tm.tmHeight)); - DestroyWindow(hwnd); - - /* Show icon and text */ - hwnd = CreateWindowA(WC_BUTTONA, button_text, BS_DEFPUSHBUTTON | default_style, 0, 0, client_width, - client_height, NULL, NULL, 0, NULL); - ok(hwnd != NULL, "Expect hwnd not NULL\n"); - SendMessageA(hwnd, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hicon); - SendMessageA(hwnd, WM_SETFONT, (WPARAM)hfont, (LPARAM)TRUE); - ZeroMemory(&size, sizeof(size)); - ret = SendMessageA(hwnd, BCM_GETIDEALSIZE, 0, (LPARAM)&size); - ok(ret, "Expect BCM_GETIDEALSIZE message to return true\n"); - ok((size.cx >= image_width + text_width && size.cy >= max(height, tm.tmHeight)), - "Expect ideal cx %d >= %d and ideal cy %d >= %d\n", size.cx, image_width + text_width, size.cy, - max(height, tm.tmHeight)); - DestroyWindow(hwnd); +#undef set_split_info
/* Checkbox */ /* Both bitmap and text for checkbox, ideal size is only enough for text because it doesn't support image(but not image list)*/
Signed-off-by: Nikolay Sivov nsivov@codeweavers.com