diff --git a/dlls/comctl32/button.c b/dlls/comctl32/button.c index 98695857442..8b5a654efc3 100644 --- a/dlls/comctl32/button.c +++ b/dlls/comctl32/button.c @@ -92,6 +92,8 @@ typedef struct _BUTTON_INFO HWND hwnd; LONG state; HFONT font; + WCHAR *note; + INT note_length; union { HICON icon; @@ -234,6 +236,14 @@ HRGN set_control_clipping( HDC hdc, const RECT *rect ) return hrgn; } +static WCHAR *heap_strndupW(const WCHAR *src, size_t length) +{ + size_t size = (length + 1) * sizeof(WCHAR); + WCHAR *dst = heap_alloc(size); + if (dst) memcpy(dst, src, size); + return dst; +} + /********************************************************************** * Convert button styles to flags used by DrawText. */ @@ -336,6 +346,7 @@ static LRESULT CALLBACK BUTTON_WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, L case WM_NCDESTROY: SetWindowLongPtrW( hWnd, 0, 0 ); + heap_free(infoPtr->note); heap_free(infoPtr); break; @@ -574,6 +585,81 @@ static LRESULT CALLBACK BUTTON_WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, L return 1; /* success. FIXME: check text length */ } + case BCM_SETNOTE: + { + WCHAR *note = (WCHAR *)lParam; + if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK) + { + SetLastError(ERROR_NOT_SUPPORTED); + return FALSE; + } + + heap_free(infoPtr->note); + if (note) + { + infoPtr->note_length = lstrlenW(note); + infoPtr->note = heap_strndupW(note, infoPtr->note_length); + } + + if (!note || !infoPtr->note) + { + infoPtr->note_length = 0; + infoPtr->note = heap_alloc_zero(sizeof(WCHAR)); + } + + SetLastError(NO_ERROR); + return TRUE; + } + + case BCM_GETNOTE: + { + DWORD *size = (DWORD *)wParam; + WCHAR *buffer = (WCHAR *)lParam; + INT length = 0; + + if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK) + { + SetLastError(ERROR_NOT_SUPPORTED); + return FALSE; + } + + if (!buffer || !size || !infoPtr->note) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + if (*size > 0) + { + length = min(*size - 1, infoPtr->note_length); + memcpy(buffer, infoPtr->note, length * sizeof(WCHAR)); + buffer[length] = '\0'; + } + + if (*size < infoPtr->note_length + 1) + { + *size = infoPtr->note_length + 1; + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + else + { + SetLastError(NO_ERROR); + return TRUE; + } + } + + case BCM_GETNOTELENGTH: + { + if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK) + { + SetLastError(ERROR_NOT_SUPPORTED); + return 0; + } + + return infoPtr->note_length; + } + case WM_SETFONT: infoPtr->font = (HFONT)wParam; if (lParam) InvalidateRect(hWnd, NULL, TRUE); diff --git a/dlls/comctl32/tests/button.c b/dlls/comctl32/tests/button.c index b3bc23da702..56547c6963b 100644 --- a/dlls/comctl32/tests/button.c +++ b/dlls/comctl32/tests/button.c @@ -28,6 +28,7 @@ #include "msg.h" #define IS_WNDPROC_HANDLE(x) (((ULONG_PTR)(x) >> 16) == (~0u >> 16)) +#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) static BOOL (WINAPI *pSetWindowSubclass)(HWND, SUBCLASSPROC, UINT_PTR, DWORD_PTR); static BOOL (WINAPI *pRemoveWindowSubclass)(HWND, SUBCLASSPROC, UINT_PTR); @@ -805,6 +806,162 @@ static void test_button_class(void) DestroyWindow(hwnd); } +static void test_note(void) +{ + HWND hwnd; + BOOL ret; + WCHAR test_w[] = {'t', 'e', 's', 't', 0}; + WCHAR tes_w[] = {'t', 'e', 's', 0}; + WCHAR deadbeef_w[] = {'d', 'e', 'a', 'd', 'b', 'e', 'e', 'f', 0}; + WCHAR buffer_w[10]; + DWORD size; + DWORD error; + INT type; + + hwnd = create_button(BS_COMMANDLINK, NULL); + ok(hwnd != NULL, "Expect hwnd not null\n"); + SetLastError(0xdeadbeef); + size = ARRAY_SIZE(buffer_w); + ret = SendMessageA(hwnd, BCM_GETNOTE, (WPARAM)&size, (LPARAM)buffer_w); + error = GetLastError(); + if (!ret && error == 0xdeadbeef) + { + win_skip("BCM_GETNOTE message is unavailable. Skipping note tests\n"); /* xp or 2003 */ + DestroyWindow(hwnd); + return; + } + DestroyWindow(hwnd); + + for (type = BS_PUSHBUTTON; type <= BS_DEFCOMMANDLINK; type++) + { + if (type == BS_DEFCOMMANDLINK || type == BS_COMMANDLINK) + { + hwnd = create_button(type, NULL); + ok(hwnd != NULL, "Expect hwnd not null\n"); + + /* Get note when note hasn't been not set yet */ + SetLastError(0xdeadbeef); + lstrcpyW(buffer_w, deadbeef_w); + size = ARRAY_SIZE(buffer_w); + ret = SendMessageA(hwnd, BCM_GETNOTE, (WPARAM)&size, (LPARAM)buffer_w); + error = GetLastError(); + ok(!ret, "Expect BCM_GETNOTE return false\n"); + ok(!lstrcmpW(buffer_w, deadbeef_w), "Expect note: %s, got: %s\n", + wine_dbgstr_w(deadbeef_w), wine_dbgstr_w(buffer_w)); + ok(size == ARRAY_SIZE(buffer_w), "Got: %d\n", size); + ok(error == ERROR_INVALID_PARAMETER, "Expect last error: 0x%08x, got: 0x%08x\n", + ERROR_INVALID_PARAMETER, error); + + /* Get note length when note is not set */ + ret = SendMessageA(hwnd, BCM_GETNOTELENGTH, 0, 0); + ok(ret == 0, "Expect note length: %d, got: %d\n", 0, ret); + + /* Successful set note, get note and get note length */ + SetLastError(0xdeadbeef); + ret = SendMessageA(hwnd, BCM_SETNOTE, 0, (LPARAM)test_w); + ok(ret, "Expect BCM_SETNOTE return true\n"); + error = GetLastError(); + ok(error == NO_ERROR, "Expect last error: 0x%08x, got: 0x%08x\n", NO_ERROR, error); + + SetLastError(0xdeadbeef); + lstrcpyW(buffer_w, deadbeef_w); + size = ARRAY_SIZE(buffer_w); + ret = SendMessageA(hwnd, BCM_GETNOTE, (WPARAM)&size, (LPARAM)buffer_w); + ok(ret, "Expect BCM_GETNOTE return true\n"); + ok(!lstrcmpW(buffer_w, test_w), "Expect note: %s, got: %s\n", wine_dbgstr_w(test_w), + wine_dbgstr_w(buffer_w)); + ok(size == ARRAY_SIZE(buffer_w), "Got: %d\n", size); + error = GetLastError(); + ok(error == NO_ERROR, "Expect last error: 0x%08x, got: 0x%08x\n", NO_ERROR, error); + + ret = SendMessageA(hwnd, BCM_GETNOTELENGTH, 0, 0); + ok(ret == ARRAY_SIZE(test_w) - 1, "Got: %d\n", ret); + + /* Insufficient buffer, return partial string */ + SetLastError(0xdeadbeef); + lstrcpyW(buffer_w, deadbeef_w); + size = ARRAY_SIZE(test_w) - 1; + ret = SendMessageA(hwnd, BCM_GETNOTE, (WPARAM)&size, (LPARAM)buffer_w); + ok(!ret, "Expect BCM_GETNOTE return false\n"); + ok(!lstrcmpW(buffer_w, tes_w), "Expect note: %s, got: %s\n", wine_dbgstr_w(tes_w), + wine_dbgstr_w(buffer_w)); + ok(size == ARRAY_SIZE(test_w), "Got: %d\n", size); + error = GetLastError(); + ok(error == ERROR_INSUFFICIENT_BUFFER, "Expect last error: 0x%08x, got: 0x%08x\n", + ERROR_INSUFFICIENT_BUFFER, error); + + /* Set note with NULL buffer */ + SetLastError(0xdeadbeef); + ret = SendMessageA(hwnd, BCM_SETNOTE, 0, 0); + ok(ret, "Expect BCM_SETNOTE return false\n"); + error = GetLastError(); + ok(error == NO_ERROR, "Expect last error: 0x%08x, got: 0x%08x\n", NO_ERROR, error); + + /* Check that set note with NULL buffer make note empty */ + SetLastError(0xdeadbeef); + lstrcpyW(buffer_w, deadbeef_w); + size = ARRAY_SIZE(buffer_w); + ret = SendMessageA(hwnd, BCM_GETNOTE, (WPARAM)&size, (LPARAM)buffer_w); + ok(ret, "Expect BCM_GETNOTE return true\n"); + ok(lstrlenW(buffer_w) == 0, "Expect note length 0\n"); + ok(size == ARRAY_SIZE(buffer_w), "Got: %d\n", size); + error = GetLastError(); + ok(error == NO_ERROR, "Expect last error: 0x%08x, got: 0x%08x\n", NO_ERROR, error); + ret = SendMessageA(hwnd, BCM_GETNOTELENGTH, 0, 0); + ok(ret == 0, "Expect note length: %d, got: %d\n", 0, ret); + + /* Get note with NULL buffer */ + SetLastError(0xdeadbeef); + size = ARRAY_SIZE(buffer_w); + ret = SendMessageA(hwnd, BCM_GETNOTE, (WPARAM)&size, 0); + ok(!ret, "Expect BCM_SETNOTE return false\n"); + ok(size == ARRAY_SIZE(buffer_w), "Got: %d\n", size); + error = GetLastError(); + ok(error == ERROR_INVALID_PARAMETER, "Expect last error: 0x%08x, got: 0x%08x\n", + ERROR_INVALID_PARAMETER, error); + + /* Get note with NULL size */ + SetLastError(0xdeadbeef); + lstrcpyW(buffer_w, deadbeef_w); + ret = SendMessageA(hwnd, BCM_GETNOTE, 0, (LPARAM)buffer_w); + ok(!ret, "Expect BCM_SETNOTE return false\n"); + ok(!lstrcmpW(buffer_w, deadbeef_w), "Expect note: %s, got: %s\n", + wine_dbgstr_w(deadbeef_w), wine_dbgstr_w(buffer_w)); + error = GetLastError(); + ok(error == ERROR_INVALID_PARAMETER, "Expect last error: 0x%08x, got: 0x%08x\n", + ERROR_INVALID_PARAMETER, error); + + /* Get note with zero size */ + SetLastError(0xdeadbeef); + size = 0; + lstrcpyW(buffer_w, deadbeef_w); + ret = SendMessageA(hwnd, BCM_GETNOTE, (WPARAM)&size, (LPARAM)buffer_w); + ok(!ret, "Expect BCM_GETNOTE return false\n"); + ok(!lstrcmpW(buffer_w, deadbeef_w), "Expect note: %s, got: %s\n", + wine_dbgstr_w(deadbeef_w), wine_dbgstr_w(buffer_w)); + ok(size == 1, "Got: %d\n", size); + error = GetLastError(); + ok(error == ERROR_INSUFFICIENT_BUFFER, "Expect last error: 0x%08x, got: 0x%08x\n", + ERROR_INSUFFICIENT_BUFFER, error); + + DestroyWindow(hwnd); + } + else + { + hwnd = create_button(type, NULL); + ok(hwnd != NULL, "Expect hwnd not null\n"); + SetLastError(0xdeadbeef); + size = ARRAY_SIZE(buffer_w); + ret = SendMessageA(hwnd, BCM_GETNOTE, (WPARAM)&size, (LPARAM)buffer_w); + ok(!ret, "Expect BCM_GETNOTE return false\n"); + error = GetLastError(); + ok(error == ERROR_NOT_SUPPORTED, "Expect last error: 0x%08x, got: 0x%08x\n", + ERROR_NOT_SUPPORTED, error); + DestroyWindow(hwnd); + } + } +} + static void register_parent_class(void) { WNDCLASSA cls; @@ -837,6 +994,7 @@ START_TEST(button) test_button_class(); test_button_messages(); + test_note(); unload_v6_module(ctx_cookie, hCtx); } diff --git a/include/commctrl.h b/include/commctrl.h index ae70cfd6981..3898f3bee50 100644 --- a/include/commctrl.h +++ b/include/commctrl.h @@ -1093,6 +1093,14 @@ typedef struct tagNMBCHOTITEM #define BS_COMMANDLINK 0x0000000E #define BS_DEFCOMMANDLINK 0x0000000F +/* Button macros */ +#define Button_SetNote(button, note) \ + (BOOL)SNDMSG(button, BCM_SETNOTE, 0, (LPARAM)(note)) +#define Button_GetNote(button, buffer, size) \ + (BOOL)SNDMSG(button, BCM_GETNOTE, (WPARAM)(size), (LPARAM)(buffer)) +#define Button_GetNoteLength(button) \ + (LRESULT)SNDMSG(button, BCM_GETNOTELENGTH, 0, 0) + /* Toolbar */ #define TOOLBARCLASSNAMEA "ToolbarWindow32"