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;