[PATCH v2 06/10] comctl32/button: Implement split buttons
Gabriel Ivăncescu
gabrielopcode at gmail.com
Wed Apr 3 12:06:29 CDT 2019
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=20123
Signed-off-by: Gabriel Ivăncescu <gabrielopcode at 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;
--
2.20.1
More information about the wine-devel
mailing list