[PATCH 4/7] comctl32/button: Implement command links

Gabriel Ivăncescu gabrielopcode at gmail.com
Fri Apr 19 07:14:00 CDT 2019


Signed-off-by: Gabriel Ivăncescu <gabrielopcode at gmail.com>
---

Command Links have hot-tracking even when they don't use a theme. The default
glyph (green arrow) is used when the button has no image (bitmap/icon) or
imagelist, and seems to be hardcoded (i.e. BM_GETIMAGE and BCM_GETIMAGELIST
must return NULL to pass the tests).

I've added the glyph as a bitmap with 3 hardcoded states to avoid duplication,
they are very close to the ones in Windows 7 in appearance. I don't know
where Windows stores them but I placed them in comctl32's resources. They
were created from scratch by just filling a custom-drawn arrow shape and
adding a white outline + shadow to it. (I can supply the 1024x1024 original
image before I downsampled it, if that's needed)

Note that the glyphs are 17x17, which seems weird, but it is the exact size
required to fit the alignment on Windows. I've been setting an image with
BM_SETIMAGE on Windows and comparing to see the button's text alignment,
only a 17x17 image properly aligned the text. They are always 17x17, even
if the app is DPI aware and the font is larger.

Lastly the "disabled" state of the glyph is white with 50% transparency (no
shadow but a dark outline). When changing the background color on Windows,
this is what's needed to match it.

Note that this bitmap glyph is only used when there's no theme. Themes use
BP_COMMANDLINKGLYPH to draw the default glyph and it looks differently. And
it also works with BCCL_NOGLYPH in the button's image list (invalid ImageList
with size 0).

 dlls/comctl32/button.c        | 191 +++++++++++++++++++++++++++++++++-
 dlls/comctl32/comctl32.h      |   3 +
 dlls/comctl32/comctl32.rc     |   3 +
 dlls/comctl32/idb_cmdlink.bmp | Bin 0 -> 3522 bytes
 4 files changed, 195 insertions(+), 2 deletions(-)
 create mode 100644 dlls/comctl32/idb_cmdlink.bmp

diff --git a/dlls/comctl32/button.c b/dlls/comctl32/button.c
index baa58d8..4f2fbce 100644
--- a/dlls/comctl32/button.c
+++ b/dlls/comctl32/button.c
@@ -105,6 +105,7 @@ 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 CL_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);
@@ -161,8 +162,8 @@ static const pfPaint btnPaintFunc[MAX_BTN_TYPE] =
     OB_Paint,    /* BS_OWNERDRAW */
     SB_Paint,    /* BS_SPLITBUTTON */
     SB_Paint,    /* BS_DEFSPLITBUTTON */
-    PB_Paint,    /* BS_COMMANDLINK */
-    PB_Paint     /* BS_DEFCOMMANDLINK */
+    CL_Paint,    /* BS_COMMANDLINK */
+    CL_Paint     /* BS_DEFCOMMANDLINK */
 };
 
 typedef void (*pfThemedPaint)( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
@@ -219,6 +220,12 @@ static const pfGetIdealSize btnGetIdealSizeFunc[MAX_BTN_TYPE] = {
     PB_GetIdealSize  /* BS_DEFCOMMANDLINK */
 };
 
+/* Fixed margin for command links, regardless of DPI (based on tests done on Windows) */
+enum { command_link_margin = 6 };
+
+/* The width and height for the default command link glyph (when there's no image) */
+enum { command_link_defglyph_size = 17 };
+
 static inline UINT get_button_type( LONG window_style )
 {
     return (window_style & BS_TYPEMASK);
@@ -2297,6 +2304,186 @@ static void draw_split_button_dropdown_glyph(const BUTTON_INFO *infoPtr, HDC hdc
 }
 
 
+/**********************************************************************
+ *       Command Link Functions
+ */
+static void CL_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
+{
+    LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
+    LONG state = infoPtr->state;
+
+    RECT rc, content_rect;
+    NMCUSTOMDRAW nmcd;
+    HPEN pen, old_pen;
+    HBRUSH old_brush;
+    INT old_bk_mode;
+    LRESULT cdrf;
+    HWND parent;
+    HRGN hrgn;
+
+    GetClientRect(infoPtr->hwnd, &rc);
+
+    /* Command Links are not affected by the button's font, and are based
+       on the default message font. Furthermore, they are not affected by
+       any of the alignment styles (and always align with the top-left). */
+    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;
+    content_rect = rc;
+
+    if (get_button_type(style) == BS_DEFCOMMANDLINK)
+    {
+        if (action != ODA_FOCUS)
+            Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
+        InflateRect(&rc, -1, -1);
+    }
+
+    /* Skip the frame drawing if only focus has changed */
+    if (action != ODA_FOCUS)
+    {
+        if (!(state & (BST_HOT | BST_PUSHED | BST_CHECKED | BST_INDETERMINATE)))
+            FillRect(hDC, &rc, GetSysColorBrush(COLOR_BTNFACE));
+        else
+        {
+            UINT flags = DFCS_BUTTONPUSH;
+
+            if (style & BS_FLAT) flags |= DFCS_MONO;
+            else if (state & BST_PUSHED) flags |= DFCS_PUSHED;
+
+            if (state & (BST_CHECKED | BST_INDETERMINATE))
+                flags |= DFCS_CHECKED;
+            DrawFrameControl(hDC, &rc, DFC_BUTTON, 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;
+
+    if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
+    {
+        UINT flags = IsWindowEnabled(infoPtr->hwnd) ? DSS_NORMAL : DSS_DISABLED;
+        COLORREF old_color = SetTextColor(hDC, GetSysColor(flags == DSS_NORMAL ?
+                                                           COLOR_BTNTEXT : COLOR_GRAYTEXT));
+        HIMAGELIST defimg = NULL;
+        NONCLIENTMETRICSW ncm;
+        UINT txt_h = 0;
+        SIZE img_size;
+
+        /* Command Links ignore the margins of the image list or its alignment */
+        if (infoPtr->u.image || infoPtr->imagelist.himl)
+            img_size = BUTTON_GetImageSize(infoPtr);
+        else
+        {
+            img_size.cx = img_size.cy = command_link_defglyph_size;
+            defimg = ImageList_LoadImageW(COMCTL32_hModule, (LPCWSTR)MAKEINTRESOURCE(IDB_CMDLINK),
+                                          img_size.cx, 3, CLR_NONE, IMAGE_BITMAP, LR_CREATEDIBSECTION);
+        }
+
+        /* Shrink rect by the command link margin, except on bottom (just the frame) */
+        InflateRect(&content_rect, -command_link_margin, -command_link_margin);
+        content_rect.bottom += command_link_margin - 2;
+
+        ncm.cbSize = sizeof(ncm);
+        if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0))
+        {
+            LONG note_weight = ncm.lfMessageFont.lfWeight;
+            RECT r = content_rect;
+            WCHAR *text;
+            HFONT font;
+
+            if (img_size.cx) r.left += img_size.cx + command_link_margin;
+
+            /* Draw the text */
+            ncm.lfMessageFont.lfWeight = FW_BOLD;
+            if ((font = CreateFontIndirectW(&ncm.lfMessageFont)))
+            {
+                if ((text = get_button_text(infoPtr)))
+                {
+                    SelectObject(hDC, font);
+                    txt_h = DrawTextW(hDC, text, -1, &r,
+                                      DT_TOP | DT_LEFT | DT_WORDBREAK | DT_END_ELLIPSIS);
+                    heap_free(text);
+                }
+                DeleteObject(font);
+            }
+
+            /* Draw the note */
+            ncm.lfMessageFont.lfWeight = note_weight;
+            if (infoPtr->note && (font = CreateFontIndirectW(&ncm.lfMessageFont)))
+            {
+                r.top += txt_h + 2;
+                SelectObject(hDC, font);
+                DrawTextW(hDC, infoPtr->note, infoPtr->note_length, &r,
+                          DT_TOP | DT_LEFT | DT_WORDBREAK | DT_NOPREFIX);
+                DeleteObject(font);
+            }
+        }
+
+        /* Position the image at the vertical center of the drawn text (not note) */
+        txt_h = min(txt_h, content_rect.bottom - content_rect.top);
+        if (img_size.cy < txt_h) content_rect.top += (txt_h - img_size.cy) / 2;
+
+        content_rect.right = content_rect.left + img_size.cx;
+        content_rect.bottom = content_rect.top + img_size.cy;
+
+        if (defimg)
+        {
+            int i = 0;
+            if (flags == DSS_DISABLED) i = 2;
+            else if (state & BST_HOT)  i = 1;
+
+            ImageList_Draw(defimg, i, hDC, content_rect.left, content_rect.top, ILD_NORMAL);
+            ImageList_Destroy(defimg);
+        }
+        else
+            BUTTON_DrawImage(infoPtr, hDC, NULL, flags, &content_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) goto cleanup;
+
+    if (action == ODA_FOCUS || (state & BST_FOCUS))
+    {
+        InflateRect(&rc, -2, -2);
+        DrawFocusRect(hDC, &rc);
+    }
+
+cleanup:
+    SelectObject(hDC, old_pen);
+    SelectObject(hDC, old_brush);
+    SetBkMode(hDC, old_bk_mode);
+    SelectClipRgn(hDC, hrgn);
+    if (hrgn) DeleteObject(hrgn);
+    DeleteObject(pen);
+}
+
+
 /**********************************************************************
  *       Themed Paint Functions
  */
diff --git a/dlls/comctl32/comctl32.h b/dlls/comctl32/comctl32.h
index b68b914..f3e889c 100644
--- a/dlls/comctl32/comctl32.h
+++ b/dlls/comctl32/comctl32.h
@@ -80,6 +80,9 @@ extern HBRUSH  COMCTL32_hPattern55AABrush DECLSPEC_HIDDEN;
 
 #define IDT_CHECK        401
 
+/* Command Link arrow */
+#define IDB_CMDLINK      402
+
 
 /* Cursors */
 #define IDC_MOVEBUTTON                  102
diff --git a/dlls/comctl32/comctl32.rc b/dlls/comctl32/comctl32.rc
index 3f8e148..c9aa1ba 100644
--- a/dlls/comctl32/comctl32.rc
+++ b/dlls/comctl32/comctl32.rc
@@ -144,6 +144,9 @@ IDB_HIST_SMALL BITMAP idb_hist_small.bmp
 /* @makedep: idb_hist_large.bmp */
 IDB_HIST_LARGE BITMAP idb_hist_large.bmp
 
+/* @makedep: idb_cmdlink.bmp */
+IDB_CMDLINK BITMAP idb_cmdlink.bmp
+
 /* @makedep: idc_copy.cur */
 IDC_COPY CURSOR idc_copy.cur
 
diff --git a/dlls/comctl32/idb_cmdlink.bmp b/dlls/comctl32/idb_cmdlink.bmp
new file mode 100644
index 0000000000000000000000000000000000000000..4b3f07b3aae4259a653d8de2efa12ec413dcb252
GIT binary patch
literal 3522
zcmb`JX-rgC6vr<v#T9L-Ye|7-OwdS!3&wsZG`3b-H9|tQe5h^B5SWw+`_dUekl+$a
zM{R?9v`TH7mRPjZ#3CA7M8Y-*4pS?XfDR56l>M>2)Bk<ro6HEzV9H5;@4ol$z5jR4
zJ9pc#?bci-**aW(z#8x}aA97!GWHef-Dg{nUW|X4f-0`&1y+Og;7zauxB?g8%qBy_
z**7LqKLd at t!P{Uf*bY7b{$MGXNIa9FflvJ}b15h&@G}~XXP-QI($Lk_WhyT(zXHWz
zz~fm97J=ztJev#+t9EQ`?DD9nsDPN5n3>j>j&Jz5;r`7s7!0uk0|OmtW+nMOaHXp5
z+d^e!<!`9t at oWZuz#U8lwl+#5T~N~6URTl&OjyHKk0mB1t|~1pJqv%n%gM<}i-?Gr
zXV<{~LH at oQUVDUugsf|BZq9EVGKoI-82K11)b;55 at 7%d_32lPFMz9jh2Yg<Pts$Dk
zl}{P1?6goPIMjg5Xw&I*aiS=m04d;Dc6PQ_rBd;4j%7S=uK_-{13W!Fw_!~EgnlC_
z&0(~xg3+=nMr-cQr+ at p*-Pf*NJCAleo_7IXV)z<?6=iIVw4N~XX~q8l{%z0@#?^qt
zXhU9nD=I3o;ZG(=6$IgUYHF%#bW7>Y5`{t$c<$V}eT|Kcr6!Z9wxgrtpP`{40exv_
zO#pcpGl|VrzSP~_-E!v4nQZjr>p=i`o#RL&)Eq#HCML0^Wn2x&SPcK&4R}OGMlMz=
zm99ZSL7@!|4PV2bbg;*4HlM=W*(tY|%c7zpWm{WYjW8he<}@A at zci|7&tK7$Qm3W^
z_ci2y%Y!_AXI6IWw~LP-KQ=%s0UCVnEYHu+|3cCj6nc-h9~8A_6=k$WQ<_;#2U|6?
zzQKb$?=vgPN24`lf0C1vw-go at W@Bs|!kFe9C at wBe!5YNlkjbd4t2?DutKXCR at v^_a
z-*DoAPJHKvFD<^zBo^f}vf~##+n}jd4`Y2OLU$g^ir(H{<H=T?xWVX4ORJd$JxT{?
zo9#8YFB*;JeT<8X at OdBD3;4L=@OwE9DK~h|Dk&*B5gi at P=Z0h>BO^}_-`h`XE-UDj
z at 7aWiEsda-mX;a_1OhKf<KcetuTYRX#^bBEofBVgufd(sYPDOD-yfd&pYbdP9tXdF
zK9;@6-D!*uek70#^Wrkv4G6;mrIjYp=hs6i;^t=*esdS8{|uuyau?FvQ_R9iMq3Lu
z(BR-;Pgz;n1qdt$Zg{>TX^cSQ?<Cq?6H2Ok&@k+xC}S8Y{#Z!!t}`p!Do1NrypiJh
z2xID-+S=M9)z#I9^?JRos;Vj-x!+?OPknv;Y2<|O<5oi~tKdu4 at bGXg+V+DXFbIYL
zZTvZqW*lJ>K4-lNIT&neYAVM6*aQ)NM4IYMV^c{W&H9zmtO7<WZj7yAbqF_ at _lq#^
zmtj75hlhu|1qTOz4Bz=$#&eT~J^W-sLc*uEedK%8&&FO9h`AHf+1aTD1 at KwW6eZL2
z1B|ADIfq=y)b&VgZ*MmqJ$m#L^j!d^In}rejXWkX=Mpsf#@CRwY)km8K%Vx)$1IQv
zzj87&Gh=MqjCYSU*B$xHHFcRpmkdTO=@!Z!m5EqOnsak=Bhi<iF<)}3(Pt8;Ttxq8
zl(kP>L)Lz7ulyFa7rB}t*E=c^`%L8MFv^h~Kkguw%BZluQ0(dHG3DjuC896iR~?=$
z?KEyd;{r5VUQ9#wF)1nOEzFzq&^(WQZ6D&`<6**v at gIU*Ht=|W*w@$h at aom8aS-5V
zWd2Y1nZ=<^hKBrsxVX60@$vB=;(Y1oEG9R%E8+eE?hDQfSr}J3?76$pfS=d+xN;f?
M7n=+XuJa=HAE4=@9{>OV

literal 0
HcmV?d00001

-- 
2.21.0




More information about the wine-devel mailing list