[PATCH 7/7] comctl32/button: Support BCM_GETIDEALSIZE message.

Zhiyi Zhang zzhang at codeweavers.com
Wed Sep 5 09:59:34 CDT 2018


Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=24623
Signed-off-by: Zhiyi Zhang <zzhang at codeweavers.com>
---
 dlls/comctl32/button.c       | 195 ++++++++++++++++++++++-
 dlls/comctl32/tests/button.c | 296 +++++++++++++++++++++++++++++++++++
 include/commctrl.h           |   2 +
 3 files changed, 491 insertions(+), 2 deletions(-)

diff --git a/dlls/comctl32/button.c b/dlls/comctl32/button.c
index a9ca619e89..5621fda4f3 100644
--- a/dlls/comctl32/button.c
+++ b/dlls/comctl32/button.c
@@ -28,7 +28,6 @@
  *  - WM_SETFOCUS: For (manual or automatic) radio buttons, send the parent window BN_CLICKED
  *  - WM_NCCREATE: Turns any BS_OWNERDRAW button into a BS_PUSHBUTTON button.
  *  - WM_SYSKEYUP
- *  - BCM_GETIDEALSIZE
  *
  *  Notifications
  *  - BCN_HOTITEMCHANGE
@@ -42,7 +41,6 @@
  *
  *  Structures/Macros/Definitions
  *  - NMBCHOTITEM
- *  - Button_GetIdealSize
  */
 
 #include <stdarg.h>
@@ -187,6 +185,32 @@ static const pfThemedPaint btnThemedPaintFunc[MAX_BTN_TYPE] =
     NULL,           /* BS_DEFCOMMANDLINK */
 };
 
+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 const pfGetIdealSize btnGetIdealSizeFunc[MAX_BTN_TYPE] = {
+    PB_GetIdealSize, /* BS_PUSHBUTTON */
+    PB_GetIdealSize, /* BS_DEFPUSHBUTTON */
+    CB_GetIdealSize, /* BS_CHECKBOX */
+    CB_GetIdealSize, /* BS_AUTOCHECKBOX */
+    CB_GetIdealSize, /* BS_RADIOBUTTON */
+    GB_GetIdealSize, /* BS_3STATE */
+    GB_GetIdealSize, /* BS_AUTO3STATE */
+    GB_GetIdealSize, /* BS_GROUPBOX */
+    PB_GetIdealSize, /* BS_USERBUTTON */
+    CB_GetIdealSize, /* BS_AUTORADIOBUTTON */
+    GB_GetIdealSize, /* BS_PUSHBOX */
+    GB_GetIdealSize, /* BS_OWNERDRAW */
+    /* 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 */
+};
+
 static inline UINT get_button_type( LONG window_style )
 {
     return (window_style & BS_TYPEMASK);
@@ -846,6 +870,15 @@ static LRESULT CALLBACK BUTTON_WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, L
         return TRUE;
     }
 
+    case BCM_GETIDEALSIZE:
+    {
+        SIZE *size = (SIZE *)lParam;
+
+        if (!size) return FALSE;
+
+        return btnGetIdealSizeFunc[btn_type](infoPtr, size);
+    }
+
     case WM_NCHITTEST:
         if(btn_type == BS_GROUPBOX) return HTTRANSPARENT;
         /* fall through */
@@ -1038,6 +1071,164 @@ static const RECT *BUTTON_GetTextMargin(const BUTTON_INFO *infoPtr)
         return &oneMargin;
 }
 
+static void BUTTON_GetClientRectSize(BUTTON_INFO *infoPtr, SIZE *size)
+{
+    RECT rect;
+    GetClientRect(infoPtr->hwnd, &rect);
+    size->cx = rect.right - rect.left;
+    size->cy = rect.bottom - rect.top;
+}
+
+static void BUTTON_GetTextIdealSize(BUTTON_INFO *infoPtr, LONG maxWidth, SIZE *size)
+{
+    WCHAR *text = get_button_text(infoPtr);
+    HDC hdc;
+    RECT rect;
+    const RECT *margin = BUTTON_GetTextMargin(infoPtr);
+
+    if (maxWidth != 0)
+    {
+        maxWidth -= margin->right + margin->right;
+        if (maxWidth <= 0) maxWidth = 1;
+    }
+
+    hdc = GetDC(infoPtr->hwnd);
+    rect = BUTTON_GetTextRect(infoPtr, hdc, text, maxWidth);
+    ReleaseDC(infoPtr->hwnd, hdc);
+    heap_free(text);
+
+    size->cx = rect.right - rect.left + margin->left + margin->right;
+    size->cy = rect.bottom - rect.top + margin->top + margin->bottom;
+}
+
+static void BUTTON_GetLabelIdealSize(BUTTON_INFO *infoPtr, LONG maxWidth, SIZE *size)
+{
+    LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
+    SIZE imageSize;
+    SIZE textSize;
+    BOOL horizontal;
+
+    imageSize = BUTTON_GetImageSize(infoPtr);
+    if (infoPtr->imagelist.himl)
+    {
+        imageSize.cx += infoPtr->imagelist.margin.left + infoPtr->imagelist.margin.right;
+        imageSize.cy += infoPtr->imagelist.margin.top + infoPtr->imagelist.margin.bottom;
+        if (infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_TOP
+            || infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_BOTTOM)
+            horizontal = FALSE;
+        else
+            horizontal = TRUE;
+    }
+    else
+    {
+        /* horizontal alignment flags has priority over vertical ones if both are specified */
+        if (!(style & (BS_CENTER | BS_VCENTER)) || ((style & BS_CENTER) && (style & BS_CENTER) != BS_CENTER)
+            || !(style & BS_VCENTER) || (style & BS_VCENTER) == BS_VCENTER)
+            horizontal = TRUE;
+        else
+            horizontal = FALSE;
+    }
+
+    if (horizontal)
+    {
+        if (maxWidth != 0)
+        {
+            maxWidth -= imageSize.cx;
+            if (maxWidth <= 0) maxWidth = 1;
+        }
+        BUTTON_GetTextIdealSize(infoPtr, maxWidth, &textSize);
+        size->cx = textSize.cx + imageSize.cx;
+        size->cy = max(textSize.cy, imageSize.cy);
+    }
+    else
+    {
+        BUTTON_GetTextIdealSize(infoPtr, maxWidth, &textSize);
+        size->cx = max(textSize.cx, imageSize.cx);
+        size->cy = textSize.cy + imageSize.cy;
+    }
+}
+
+static BOOL GB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
+{
+    BUTTON_GetClientRectSize(infoPtr, size);
+    return TRUE;
+}
+
+static BOOL CB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
+{
+    LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
+    WCHAR *text = get_button_text(infoPtr);
+    HDC hdc;
+    HFONT hfont;
+    SIZE labelSize;
+    INT textOffset;
+    INT textLength = 0;
+    double scaleX;
+    double scaleY;
+    LONG checkboxWidth, checkboxHeight;
+    LONG maxWidth = 0;
+
+    if (text) textLength = lstrlenW(text);
+    heap_free(text);
+    if (textLength == 0)
+    {
+        BUTTON_GetClientRectSize(infoPtr, size);
+        return TRUE;
+    }
+
+    hdc = GetDC(infoPtr->hwnd);
+    scaleX = GetDeviceCaps(hdc, LOGPIXELSX) / 96.0;
+    scaleY = GetDeviceCaps(hdc, LOGPIXELSY) / 96.0;
+    if ((hfont = infoPtr->font)) SelectObject(hdc, hfont);
+    GetCharWidthW(hdc, '0', '0', &textOffset);
+    textOffset /= 2;
+    ReleaseDC(infoPtr->hwnd, hdc);
+
+    checkboxWidth = 12 * scaleX + 1;
+    checkboxHeight = 12 * scaleY + 1;
+    if (size->cx)
+    {
+        maxWidth = size->cx - checkboxWidth - textOffset;
+        if (maxWidth <= 0) maxWidth = 1;
+    }
+
+    /* Checkbox doesn't support both image(but not image list) and text */
+    if (!(style & (BS_ICON | BS_BITMAP)) && infoPtr->u.image)
+        BUTTON_GetTextIdealSize(infoPtr, maxWidth, &labelSize);
+    else
+        BUTTON_GetLabelIdealSize(infoPtr, maxWidth, &labelSize);
+
+    size->cx = labelSize.cx + checkboxWidth + textOffset;
+    size->cy = max(labelSize.cy, checkboxHeight);
+
+    return TRUE;
+}
+
+static BOOL PB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
+{
+    WCHAR *text = get_button_text(infoPtr);
+    SIZE labelSize;
+    INT textLength = 0;
+
+    if (text) textLength = lstrlenW(text);
+
+    if (textLength == 0)
+    {
+        BUTTON_GetClientRectSize(infoPtr, size);
+        heap_free(text);
+        return TRUE;
+    }
+    heap_free(text);
+
+    /* Ideal size include text size even if image only flags(BS_ICON, BS_BITMAP) are specified */
+    BUTTON_GetLabelIdealSize(infoPtr, size->cx, &labelSize);
+
+    size->cx = labelSize.cx;
+    size->cy = labelSize.cy;
+
+    return TRUE;
+}
+
 /**********************************************************************
  *       BUTTON_CalcLayoutRects
  *
diff --git a/dlls/comctl32/tests/button.c b/dlls/comctl32/tests/button.c
index 1fd644b6f2..81461dee44 100644
--- a/dlls/comctl32/tests/button.c
+++ b/dlls/comctl32/tests/button.c
@@ -1422,6 +1422,301 @@ static void test_state(void)
     }
 }
 
+static void test_bcm_get_ideal_size(void)
+{
+    static const WCHAR text2_w[] = {'t', 'e', 'x', 't', '\n', 't', 'e', 'x', 't', 0};
+    static const WCHAR text_w[] = {'t', 'e', 'x', 't', 0};
+    static const DWORD imagelist_aligns[] = {BUTTON_IMAGELIST_ALIGN_LEFT, BUTTON_IMAGELIST_ALIGN_RIGHT,
+                                             BUTTON_IMAGELIST_ALIGN_TOP, BUTTON_IMAGELIST_ALIGN_BOTTOM,
+                                             BUTTON_IMAGELIST_ALIGN_CENTER};
+    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;
+    LONG client_width = 200, client_height = 200;
+    LONG width, height, line_count;
+    DWORD style, type;
+    BOOL ret;
+    HWND hwnd;
+    HDC hdc;
+    HFONT hfont;
+    LOGFONTA lf;
+    TEXTMETRICA tm;
+    SIZE size;
+    HBITMAP hmask, hbmp;
+    ICONINFO icon_info;
+    HICON hicon;
+    HIMAGELIST himl;
+    BUTTON_IMAGELIST biml = {0};
+    INT i, j;
+
+    /* Check for NULL pointer handling */
+    hwnd = CreateWindowW(WC_BUTTONW, text_w, BS_PUSHBUTTON | default_style, 0, 0, client_width, client_height, NULL,
+                         NULL, 0, NULL);
+    ret = SendMessageA(hwnd, BCM_GETIDEALSIZE, 0, 0);
+    ok(!ret, "Expect BCM_GETIDEALSIZE message to return false.\n");
+
+    /* Set font so that the test is consistent on Wine and Windows */
+    ZeroMemory(&lf, sizeof(lf));
+    lf.lfWeight = FW_NORMAL;
+    lf.lfHeight = 20;
+    lstrcpyA(lf.lfFaceName, "Tahoma");
+    hfont = CreateFontIndirectA(&lf);
+
+    /* Get tmHeight */
+    hdc = GetDC(hwnd);
+    GetTextMetricsA(hdc, &tm);
+    ReleaseDC(hwnd, hdc);
+    DestroyWindow(hwnd);
+
+    /* XP and 2003 doesn't support command links, getting ideal size with button having only text just return client size on these platforms */
+    hwnd = CreateWindowA(WC_BUTTONA, "test", BS_DEFCOMMANDLINK | default_style, 0, 0, client_width, client_height, NULL,
+                         NULL, 0, NULL);
+    ok(hwnd != NULL, "Expect hwnd not NULL\n");
+    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 (size.cx == client_width && size.cy == client_width)
+    {
+        /* on XP and 2003, buttons with image are not supported */
+        win_skip("Skipping further tests on XP and 2003\n");
+        return;
+    }
+
+    /* Tests for image placements */
+    /* Prepare bitmap */
+    width = 48;
+    height = 48;
+    hdc = GetDC(0);
+    hmask = CreateCompatibleBitmap(hdc, width, height);
+    hbmp = CreateCompatibleBitmap(hdc, width, height);
+
+    /* Only bitmap for push button, ideal size should be enough for image and text */
+    hwnd = CreateWindowA(WC_BUTTONA, "WWWW", 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 >= width + 4 * tm.tmMaxCharWidth && size.cy >= max(height, tm.tmHeight)),
+       "Expect ideal cx %d >= %d and ideal cy %d >= %d\n", size.cx, width + 4 * tm.tmMaxCharWidth, size.cy,
+       max(height, tm.tmHeight));
+    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++)
+        {
+            style = BS_DEFPUSHBUTTON | default_style | aligns[i] | aligns[j];
+            hwnd = CreateWindowA(WC_BUTTONA, "WWWW", 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");
+            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 >= width + 4 * tm.tmMaxCharWidth && size.cy >= max(height, tm.tmHeight)),
+                   "Style: 0x%08x expect ideal cx %d >= %d and ideal cy %d >= %d\n", style, size.cx,
+                   width + 4 * tm.tmMaxCharWidth, size.cy, max(height, tm.tmHeight));
+            else
+                ok((size.cx >= max(4 * tm.tmMaxCharWidth, height) && size.cy >= height + tm.tmHeight),
+                   "Style: 0x%08x expect ideal cx %d >= %d and ideal cy %d >= %d\n", style, size.cx,
+                   max(4 * tm.tmMaxCharWidth, height), size.cy, height + tm.tmHeight);
+            DestroyWindow(hwnd);
+        }
+
+    /* Image list alignments */
+    himl = pImageList_Create(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, "WWWW", BS_DEFPUSHBUTTON | default_style, 0, 0, client_width, client_height,
+                             NULL, NULL, 0, NULL);
+        ok(hwnd != NULL, "Expect hwnd not NULL\n");
+        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(4 * tm.tmMaxCharWidth, height) && size.cy >= height + tm.tmHeight),
+               "Align:%d expect ideal cx %d >= %d and ideal cy %d >= %d\n", biml.uAlign, size.cx,
+               max(4 * tm.tmMaxCharWidth, height), size.cy, height + tm.tmHeight);
+        else if (biml.uAlign == BUTTON_IMAGELIST_ALIGN_LEFT || biml.uAlign == BUTTON_IMAGELIST_ALIGN_RIGHT)
+            ok((size.cx >= width + 4 * tm.tmMaxCharWidth && size.cy >= max(height, tm.tmHeight)),
+               "Align:%d expect ideal cx %d >= %d and ideal cy %d >= %d\n", biml.uAlign, size.cx,
+               width + 4 * tm.tmMaxCharWidth, size.cy, max(height, tm.tmHeight));
+        else
+            ok(size.cx >= width && size.cy >= height, "Align:%d expect ideal cx %d >= %d and ideal cy %d >= %d\n",
+               biml.uAlign, size.cx, width, size.cy, 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, "WWWW", 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 >= width + 4 * tm.tmMaxCharWidth && size.cy >= max(height, tm.tmHeight)),
+       "Expect ideal cx %d >= %d and ideal cy %d >= %d\n", size.cx, width + 4 * tm.tmMaxCharWidth, size.cy,
+       max(height, tm.tmHeight));
+    DestroyWindow(hwnd);
+
+    /* Show icon and text */
+    hwnd = CreateWindowA(WC_BUTTONA, "WWWW", 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 >= width + 4 * tm.tmMaxCharWidth && size.cy >= max(height, tm.tmHeight)),
+       "Expect ideal cx %d >= %d and ideal cy %d >= %d\n", size.cx, width + 4 * tm.tmMaxCharWidth, size.cy,
+       max(height, tm.tmHeight));
+    DestroyWindow(hwnd);
+
+    /* Checkbox */
+    /* Both bitmap and text for checkbox, ideal size is only enough for text because it doesn't support image(but not image list)*/
+    hwnd = CreateWindowA(WC_BUTTONA, "WWWW", BS_AUTOCHECKBOX | 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");
+    ok((size.cx <= width + 4 * tm.tmMaxCharWidth && size.cx >= 4 * tm.tmMaxCharWidth
+        && size.cy <= max(height, tm.tmHeight) && size.cy >= tm.tmHeight),
+       "Expect ideal cx %d within range (%d, %d ) and ideal cy %d within range (%d, %d )\n", size.cx,
+       4 * tm.tmMaxCharWidth, width + 4 * tm.tmMaxCharWidth, size.cy, tm.tmHeight, max(height, tm.tmHeight));
+    DestroyWindow(hwnd);
+
+    /* Both image list and text for checkbox, ideal size should have enough for image list and text */
+    biml.uAlign = BUTTON_IMAGELIST_ALIGN_LEFT;
+    hwnd = CreateWindowA(WC_BUTTONA, "WWWW", BS_AUTOCHECKBOX | BS_BITMAP | default_style, 0, 0, client_width,
+                         client_height, NULL, NULL, 0, NULL);
+    ok(hwnd != NULL, "Expect hwnd not NULL\n");
+    SendMessageA(hwnd, BCM_SETIMAGELIST, 0, (LPARAM)&biml);
+    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 >= width + 4 * tm.tmMaxCharWidth && size.cy >= max(height, tm.tmHeight)),
+       "Expect ideal cx %d >= %d and ideal cy %d >= %d\n", size.cx, width + 4 * tm.tmMaxCharWidth, size.cy,
+       max(height, tm.tmHeight));
+    DestroyWindow(hwnd);
+
+    /* Only bitmap for checkbox, ideal size should have enough for image and text */
+    hwnd = CreateWindowA(WC_BUTTONA, "WWWW", BS_AUTOCHECKBOX | 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");
+    ok((size.cx >= width + 4 * tm.tmMaxCharWidth && size.cy >= max(height, tm.tmHeight)),
+       "Expect ideal cx %d >= %d and ideal cy %d >= %d\n", size.cx, width + 4 * tm.tmMaxCharWidth, size.cy,
+       max(height, tm.tmHeight));
+    DestroyWindow(hwnd);
+
+    /* Test button with only text */
+    /* No text */
+    for (type = BS_PUSHBUTTON; type <= BS_DEFCOMMANDLINK; type++)
+    {
+        style = type | default_style;
+        hwnd = CreateWindowA(WC_BUTTONA, "", style, 0, 0, client_width, client_height, NULL, NULL, 0, NULL);
+        ok(hwnd != NULL, "Expect hwnd not NULL\n");
+        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 (type == BS_COMMANDLINK || type == BS_DEFCOMMANDLINK)
+        {
+            todo_wine ok((size.cx == 0 && size.cy > 0), "Style 0x%08x expect ideal cx %d >= %d and ideal cy %d >= %d\n",
+                         style, size.cx, 0, size.cy, 0);
+        }
+        else
+        {
+            ok(size.cx == client_width && size.cy == client_height,
+               "Style 0x%08x expect size.cx == %d and size.cy == %d, got size.cx: %d size.cy: %d\n", style,
+               client_width, client_height, size.cx, size.cy);
+        }
+        DestroyWindow(hwnd);
+    }
+
+    /* Single line and multiple lines text */
+    for (line_count = 1; line_count <= 2; line_count++)
+    {
+        for (type = BS_PUSHBUTTON; type <= BS_DEFCOMMANDLINK; type++)
+        {
+            style = line_count > 1 ? type | BS_MULTILINE : type;
+            style |= default_style;
+
+            hwnd = CreateWindowW(WC_BUTTONW, (line_count == 2 ? text2_w : text_w), style, 0, 0, client_width,
+                                 client_height, NULL, NULL, 0, NULL);
+            ok(hwnd != NULL, "Expect hwnd not NULL\n");
+            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 (type == BS_3STATE || type == BS_AUTO3STATE || type == BS_GROUPBOX || type == BS_PUSHBOX
+                || type == BS_OWNERDRAW)
+            {
+                ok(size.cx == client_width && size.cy == client_height,
+                   "Style 0x%08x expect ideal size (%d,%d), got (%d,%d)\n", style, client_width, client_height, size.cx,
+                   size.cy);
+            }
+            else if (type == BS_COMMANDLINK || type == BS_DEFCOMMANDLINK)
+            {
+                todo_wine ok((size.cx == 0 && size.cy > 0),
+                             "Style 0x%08x expect ideal cx %d >= %d and ideal cy %d >= %d\n", style, size.cx, 0,
+                             size.cy, 0);
+            }
+            else
+            {
+                width = 0;
+                height = line_count == 2 ? 2 * tm.tmHeight : tm.tmHeight;
+                ok(size.cx >= width && size.cy >= height,
+                   "Style 0x%08x expect ideal cx %d >= %d and ideal cy %d >= %d\n", style, size.cx, width, size.cy,
+                   height);
+            }
+            DestroyWindow(hwnd);
+        }
+    }
+
+    pImageList_Destroy(himl);
+    DestroyIcon(hicon);
+    DeleteObject(hbmp);
+    DeleteObject(hmask);
+    ReleaseDC(0, hdc);
+    DeleteObject(hfont);
+}
+
 START_TEST(button)
 {
     ULONG_PTR ctx_cookie;
@@ -1443,6 +1738,7 @@ START_TEST(button)
     test_get_set_imagelist();
     test_get_set_textmargin();
     test_state();
+    test_bcm_get_ideal_size();
 
     unload_v6_module(ctx_cookie, hCtx);
 }
diff --git a/include/commctrl.h b/include/commctrl.h
index 936b2e4026..77f460a99d 100644
--- a/include/commctrl.h
+++ b/include/commctrl.h
@@ -1109,6 +1109,8 @@ typedef struct tagNMBCHOTITEM
   (BOOL)SNDMSG(button, BCM_GETTEXTMARGIN, 0, (LPARAM)(margin))
 #define Button_SetTextMargin(button, margin)  \
   (BOOL)SNDMSG(button, BCM_SETTEXTMARGIN, 0, (LPARAM)(margin))
+#define Button_GetIdealSize(button, size)  \
+  (BOOL)SNDMSG(button, BCM_GETIDEALSIZE, 0, (LPARAM)(size))
 
 /* Toolbar */
 
-- 
2.18.0




More information about the wine-devel mailing list