[PATCH 1/1] comctl32: Add basic custom draw support for tooltips

Jason Edmeades jason.edmeades at googlemail.com
Wed Apr 2 18:25:38 CDT 2008


Whilst playing with a test program to experiment with custom draw tooltips,
I coded the attached patch (with tests) which was sufficient to get it
working.

Note: I dont like the SetCursor call in the test which forces the popup,
but wine doesnt support the TTM_ call to pop up a tooltip immediately,
and the other way (tracking tooltips, self activated) works nicely on wine
but fails when compiled with .NET 2003+ (See URL below for better explanation)
http://www.codeproject.com/KB/miscctrl/tooltipzen.aspx?fid=4069&df=90&mpp=25&noise=3&sort=Position&view=Quick&fr=26&select=2018653#xx2018653xx
---
 dlls/comctl32/tests/tooltips.c |  174 ++++++++++++++++++++++++++++++++++++++++
 dlls/comctl32/tooltips.c       |   59 +++++++++++++-
 2 files changed, 230 insertions(+), 3 deletions(-)

diff --git a/dlls/comctl32/tests/tooltips.c b/dlls/comctl32/tests/tooltips.c
index 713fa24..a2d49e8 100644
--- a/dlls/comctl32/tests/tooltips.c
+++ b/dlls/comctl32/tests/tooltips.c
@@ -1,5 +1,6 @@
 /*
  * Copyright 2005 Dmitry Timoshkov
+ * Copyright 2008 Jason Edmeades
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -60,9 +61,182 @@ static void test_create_tooltip(void)
     DestroyWindow(parent);
 }
 
+/* try to make sure pending X events have been processed before continuing */
+static void flush_events(int waitTime)
+{
+    MSG msg;
+    int diff = waitTime;
+    DWORD time = GetTickCount() + waitTime;
+
+    while (diff > 0)
+    {
+        if (MsgWaitForMultipleObjects( 0, NULL, FALSE, min(10,diff), QS_ALLEVENTS) != WAIT_TIMEOUT) {
+            while (PeekMessage( &msg, 0, 0, 0, PM_REMOVE )) DispatchMessage( &msg );
+        }
+        diff = time - GetTickCount();
+    }
+}
+
+static int CD_Stages;
+static LRESULT CD_Result;
+static HWND g_hwnd;
+
+#define TEST_CDDS_PREPAINT           0x00000001
+#define TEST_CDDS_POSTPAINT          0x00000002
+#define TEST_CDDS_PREERASE           0x00000004
+#define TEST_CDDS_POSTERASE          0x00000008
+#define TEST_CDDS_ITEMPREPAINT       0x00000010
+#define TEST_CDDS_ITEMPOSTPAINT      0x00000020
+#define TEST_CDDS_ITEMPREERASE       0x00000040
+#define TEST_CDDS_ITEMPOSTERASE      0x00000080
+#define TEST_CDDS_SUBITEM            0x00000100
+
+static LRESULT CALLBACK CustomDrawWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+    switch(msg) {
+
+    case WM_DESTROY:
+        PostQuitMessage(0);
+        break;
+
+    case WM_NOTIFY:
+        if (((NMHDR *)lParam)->code == NM_CUSTOMDRAW) {
+            NMTTCUSTOMDRAW *ttcd = (NMTTCUSTOMDRAW*) lParam;
+            ok(ttcd->nmcd.hdr.hwndFrom == g_hwnd, "Unexpected hwnd source %x (%x)\n",
+                 (int)ttcd->nmcd.hdr.hwndFrom, (int) g_hwnd);
+            ok(ttcd->nmcd.hdr.idFrom == 0x1234ABCD, "Unexpected id %x\n", (int)ttcd->nmcd.hdr.idFrom);
+
+            switch (ttcd->nmcd.dwDrawStage) {
+            case CDDS_PREPAINT     : CD_Stages |= TEST_CDDS_PREPAINT; break;
+            case CDDS_POSTPAINT    : CD_Stages |= TEST_CDDS_POSTPAINT; break;
+            case CDDS_PREERASE     : CD_Stages |= TEST_CDDS_PREERASE; break;
+            case CDDS_POSTERASE    : CD_Stages |= TEST_CDDS_POSTERASE; break;
+            case CDDS_ITEMPREPAINT : CD_Stages |= TEST_CDDS_ITEMPREPAINT; break;
+            case CDDS_ITEMPOSTPAINT: CD_Stages |= TEST_CDDS_ITEMPOSTPAINT; break;
+            case CDDS_ITEMPREERASE : CD_Stages |= TEST_CDDS_ITEMPREERASE; break;
+            case CDDS_ITEMPOSTERASE: CD_Stages |= TEST_CDDS_ITEMPOSTERASE; break;
+            case CDDS_SUBITEM      : CD_Stages |= TEST_CDDS_SUBITEM; break;
+            default: CD_Stages = -1;
+            }
+
+            if (ttcd->nmcd.dwDrawStage == CDDS_PREPAINT) return CD_Result;
+        }
+        /* drop through */
+
+    default:
+        return DefWindowProcA(hWnd, msg, wParam, lParam);
+    }
+
+    return 0L;
+}
+
+static void test_customdraw(void) {
+    static struct {
+        LRESULT FirstReturnValue;
+        int ExpectedCalls;
+    } expectedResults[] = {
+        /* Valid notification responses */
+        {CDRF_DODEFAULT, TEST_CDDS_PREPAINT},
+        {CDRF_SKIPDEFAULT, TEST_CDDS_PREPAINT},
+        {CDRF_NOTIFYPOSTPAINT, TEST_CDDS_PREPAINT | TEST_CDDS_POSTPAINT},
+
+        /* Invalid notification responses */
+        {CDRF_NOTIFYITEMDRAW, TEST_CDDS_PREPAINT},
+        {CDRF_NOTIFYPOSTERASE, TEST_CDDS_PREPAINT},
+        {CDRF_NOTIFYSUBITEMDRAW, TEST_CDDS_PREPAINT},
+        {CDRF_NEWFONT, TEST_CDDS_PREPAINT}
+    };
+
+   int       iterationNumber;
+   WNDCLASSA wc;
+   LRESULT   lResult;
+
+   /* Create a class to use the custom draw wndproc */
+   wc.style = CS_HREDRAW | CS_VREDRAW;
+   wc.cbClsExtra = 0;
+   wc.cbWndExtra = 0;
+   wc.hInstance = GetModuleHandleA(NULL);
+   wc.hIcon = NULL;
+   wc.hCursor = LoadCursorA(NULL, IDC_ARROW);
+   wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW);
+   wc.lpszMenuName = NULL;
+   wc.lpszClassName = "CustomDrawClass";
+   wc.lpfnWndProc = CustomDrawWndProc;
+   RegisterClass(&wc);
+
+   for (iterationNumber = 0;
+        iterationNumber < sizeof(expectedResults)/sizeof(expectedResults[0]);
+        iterationNumber++) {
+
+       HWND parent, hwndTip;
+       TOOLINFO toolInfo = { 0 };
+
+       /* Create a main window */
+       parent = CreateWindowEx(0, "CustomDrawClass", NULL,
+                               WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX |
+                               WS_MAXIMIZEBOX | WS_VISIBLE,
+                               50, 50,
+                               300, 300,
+                               NULL, NULL, NULL, 0);
+       ok(parent != NULL, "Creation of main window failed\n");
+
+       /* Make it show */
+       ShowWindow(parent, SW_SHOWNORMAL);
+       flush_events(100);
+
+       /* Create Tooltip */
+       hwndTip = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS,
+                                NULL, TTS_NOPREFIX | TTS_ALWAYSTIP,
+                                CW_USEDEFAULT, CW_USEDEFAULT,
+                                CW_USEDEFAULT, CW_USEDEFAULT,
+                                parent, NULL, GetModuleHandleA(NULL), 0);
+       ok(hwndTip != NULL, "Creation of tooltip window failed\n");
+
+       /* Set up parms for the wndproc to handle */
+       CD_Stages = 0;
+       CD_Result = expectedResults[iterationNumber].FirstReturnValue;
+       g_hwnd    = hwndTip;
+
+       /* Make it topmost, as per the MSDN */
+       SetWindowPos(hwndTip, HWND_TOPMOST, 0, 0, 0, 0,
+             SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
+
+       /* Create a tool */
+       toolInfo.cbSize = sizeof(TOOLINFO);
+       toolInfo.hwnd = parent;
+       toolInfo.hinst = GetModuleHandleA(NULL);
+       toolInfo.uFlags = TTF_SUBCLASS;
+       toolInfo.uId = (UINT_PTR)0x1234ABCD;
+       toolInfo.lpszText = (LPSTR)"This is a test tooltip";
+       toolInfo.lParam = 0xdeadbeef;
+       GetClientRect (parent, &toolInfo.rect);
+       lResult = SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo);
+       ok(lResult, "Adding the tool to the tooltip failed\n");
+
+       /* Make tooltip appear quickly */
+       SendMessage(hwndTip, TTM_SETDELAYTIME, (WPARAM)TTDT_INITIAL, (LPARAM)MAKELONG(1,0));
+
+       /* Put cursor inside window, tooltip will appear immediately */
+       SetCursorPos(100, 100);
+       flush_events(2000);
+
+       /* Check CustomDraw results */
+       ok(CD_Stages == expectedResults[iterationNumber].ExpectedCalls,
+          "CustomDraw run %d stages %x, expected %x\n", iterationNumber, CD_Stages,
+          expectedResults[iterationNumber].ExpectedCalls);
+
+       /* Clean up */
+       DestroyWindow(hwndTip);
+       DestroyWindow(parent);
+   }
+
+
+}
+
 START_TEST(tooltips)
 {
     InitCommonControls();
 
     test_create_tooltip();
+    test_customdraw();
 }
diff --git a/dlls/comctl32/tooltips.c b/dlls/comctl32/tooltips.c
index 7661ffa..48d7757 100644
--- a/dlls/comctl32/tooltips.c
+++ b/dlls/comctl32/tooltips.c
@@ -202,6 +202,44 @@ TOOLTIPS_InitSystemSettings (TOOLTIPS_INFO *infoPtr)
     infoPtr->hTitleFont = CreateFontIndirectW (&nclm.lfStatusFont);
 }
 
+/* Custom draw routines */
+static void
+TOOLTIPS_customdraw_fill(NMTTCUSTOMDRAW *lpnmttcd,
+                         const HWND hwnd,
+                         HDC hdc, const RECT *rcBounds, UINT uFlags)
+{
+    TOOLTIPS_INFO *infoPtr = TOOLTIPS_GetInfoPtr(hwnd);
+
+    ZeroMemory(lpnmttcd, sizeof(NMTTCUSTOMDRAW));
+    lpnmttcd->uDrawFlags = uFlags;
+    lpnmttcd->nmcd.hdr.hwndFrom = hwnd;
+    lpnmttcd->nmcd.hdr.code     = NM_CUSTOMDRAW;
+    if (infoPtr->nCurrentTool != -1) {
+        TTTOOL_INFO *toolPtr = &infoPtr->tools[infoPtr->nCurrentTool];
+        lpnmttcd->nmcd.hdr.idFrom = toolPtr->uId;
+    }
+    lpnmttcd->nmcd.hdc = hdc;
+    lpnmttcd->nmcd.rc = *rcBounds;
+    /* FIXME - dwItemSpec, uItemState, lItemlParam */
+}
+
+static inline DWORD
+TOOLTIPS_notify_customdraw (DWORD dwDrawStage, NMTTCUSTOMDRAW *lpnmttcd)
+{
+    LRESULT result = CDRF_DODEFAULT;
+    lpnmttcd->nmcd.dwDrawStage = dwDrawStage;
+
+    TRACE("Notifying stage %d, flags %x, id %x\n", lpnmttcd->nmcd.dwDrawStage,
+          lpnmttcd->uDrawFlags, lpnmttcd->nmcd.hdr.code);
+
+    result = SendMessageW(GetParent(lpnmttcd->nmcd.hdr.hwndFrom), WM_NOTIFY,
+                          0, (LPARAM)lpnmttcd);
+
+    TRACE("Notify result %x\n", (unsigned int)result);
+
+    return result;
+}
+
 static void
 TOOLTIPS_Refresh (HWND hwnd, HDC hdc)
 {
@@ -213,6 +251,8 @@ TOOLTIPS_Refresh (HWND hwnd, HDC hdc)
     UINT uFlags = DT_EXTERNALLEADING;
     HRGN hRgn = NULL;
     DWORD dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
+    NMTTCUSTOMDRAW nmttcd;
+    DWORD cdmode;
 
     if (infoPtr->nMaxTipWidth > -1)
 	uFlags |= DT_WORDBREAK;
@@ -224,6 +264,13 @@ TOOLTIPS_Refresh (HWND hwnd, HDC hdc)
 
     oldBkMode = SetBkMode (hdc, TRANSPARENT);
     SetTextColor (hdc, infoPtr->clrText);
+    hOldFont = SelectObject (hdc, infoPtr->hFont);
+
+    /* Custom draw - Call PrePaint once initial properties set up     */
+    /* Note: Contrary to MSDN, CDRF_SKIPDEFAULT still draws a tooltip */
+    TOOLTIPS_customdraw_fill(&nmttcd, hwnd, hdc, &rc, uFlags);
+    cdmode = TOOLTIPS_notify_customdraw(CDDS_PREPAINT, &nmttcd);
+    uFlags = nmttcd.uDrawFlags;
 
     if (dwStyle & TTS_BALLOON)
     {
@@ -259,6 +306,7 @@ TOOLTIPS_Refresh (HWND hwnd, HDC hdc)
             RECT rcTitle = {rc.left, rc.top, rc.right, rc.bottom};
             int height;
             BOOL icon_present;
+            HFONT prevFont;
 
             /* draw icon */
             icon_present = infoPtr->hTitleIcon && 
@@ -270,9 +318,9 @@ TOOLTIPS_Refresh (HWND hwnd, HDC hdc)
             rcTitle.bottom = rc.top + ICON_HEIGHT;
 
             /* draw title text */
-            hOldFont = SelectObject (hdc, infoPtr->hTitleFont);
+            prevFont = SelectObject (hdc, infoPtr->hTitleFont);
             height = DrawTextW(hdc, infoPtr->pszTitle, -1, &rcTitle, DT_BOTTOM | DT_SINGLELINE | DT_NOPREFIX);
-            SelectObject (hdc, hOldFont);
+            SelectObject (hdc, prevFont);
             rc.top += height + BALLOON_TITLE_TEXT_SPACING;
         }
     }
@@ -286,8 +334,13 @@ TOOLTIPS_Refresh (HWND hwnd, HDC hdc)
     }
 
     /* draw text */
-    hOldFont = SelectObject (hdc, infoPtr->hFont);
     DrawTextW (hdc, infoPtr->szTipText, -1, &rc, uFlags);
+
+    /* Custom draw - Call PostPaint after drawing */
+    if (cdmode & CDRF_NOTIFYPOSTPAINT) {
+        TOOLTIPS_notify_customdraw(CDDS_POSTPAINT, &nmttcd);
+    }
+
     /* be polite and reset the things we changed in the dc */
     SelectObject (hdc, hOldFont);
     SetBkMode (hdc, oldBkMode);
-- 
1.5.3.2




More information about the wine-patches mailing list