[3/5] comctl32: Implement TaskDialogIndirect as a custom dialog box (try 3)

Joachim Priesner joachim.priesner at web.de
Thu Feb 26 09:57:55 CST 2015


The amount of functionality is the same as with the old MessageBox solution,
except now both the "content" and "main instruction" texts are shown.
The dialog should now also be more easily extendable.

Most of this code is adapted from user32/msgbox.c.

Try 3 that makes dialog creation succeed even when using invalid resources.
---
 dlls/comctl32/Makefile.in  |   2 +-
 dlls/comctl32/comctl32.h   |   6 +
 dlls/comctl32/comctl32.rc  |  14 ++
 dlls/comctl32/taskdialog.c | 398 +++++++++++++++++++++++++++++++++++++++++----
 4 files changed, 389 insertions(+), 31 deletions(-)

diff --git a/dlls/comctl32/Makefile.in b/dlls/comctl32/Makefile.in
index e1a6812..adcb55f 100644
--- a/dlls/comctl32/Makefile.in
+++ b/dlls/comctl32/Makefile.in
@@ -1,7 +1,7 @@
 EXTRADEFS = -D_COMCTL32_
 MODULE    = comctl32.dll
 IMPORTLIB = comctl32
-IMPORTS   = uuid user32 gdi32 advapi32
+IMPORTS   = uuid user32 gdi32 advapi32 shlwapi
 DELAYIMPORTS = winmm uxtheme
 
 C_SRCS = \
diff --git a/dlls/comctl32/comctl32.h b/dlls/comctl32/comctl32.h
index b9b0574..84b8fa0 100644
--- a/dlls/comctl32/comctl32.h
+++ b/dlls/comctl32/comctl32.h
@@ -65,6 +65,12 @@ extern HBRUSH  COMCTL32_hPattern55AABrush DECLSPEC_HIDDEN;
 
 #define IDS_SEPARATOR      1024
 
+/* Task dialog */
+#define IDD_TASKDIALOG       510
+
+#define IDC_MAIN_INSTRUCTION 512
+#define IDC_CONTENT          513
+
 /* Toolbar imagelist bitmaps */
 #define IDB_STD_SMALL       120
 #define IDB_STD_LARGE       121
diff --git a/dlls/comctl32/comctl32.rc b/dlls/comctl32/comctl32.rc
index 87e2fd1..fa8f7ea 100644
--- a/dlls/comctl32/comctl32.rc
+++ b/dlls/comctl32/comctl32.rc
@@ -93,6 +93,20 @@ BEGIN
   LISTBOX       IDC_TOOLBARBTN_LBOX, 194,17,120,100,LBS_NOTIFY | LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | LBS_DISABLENOSCROLL | WS_BORDER | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP
 END
 
+IDD_TASKDIALOG DIALOG 100, 80, 216, 168
+STYLE DS_MODALFRAME | DS_NOIDLEMSG | DS_SETFONT | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+BEGIN
+  LTEXT         "",       IDC_MAIN_INSTRUCTION, 32, 4, 176, 48, WS_CHILD | WS_VISIBLE | WS_GROUP | SS_NOPREFIX
+  LTEXT         "",       IDC_CONTENT, 32, 4, 176, 48, WS_CHILD | WS_VISIBLE | WS_GROUP | SS_NOPREFIX
+  PUSHBUTTON    "OK",     IDOK, 16, 56, 50, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP
+  PUSHBUTTON    "&Yes",   IDYES, 306, 56, 50, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP
+  PUSHBUTTON    "&No",    IDNO, 364, 56, 50, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP
+  PUSHBUTTON    "Cancel", IDCANCEL, 74, 56, 50, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP
+  PUSHBUTTON    "&Retry", IDRETRY, 190, 56, 50, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP
+  PUSHBUTTON    "&Close", IDCLOSE, 132, 56, 50, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP
+END
+
+
 LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
 
 #define WINE_FILEDESCRIPTION_STR "Wine Common Controls"
diff --git a/dlls/comctl32/taskdialog.c b/dlls/comctl32/taskdialog.c
index 84e3e76..fe6822c 100644
--- a/dlls/comctl32/taskdialog.c
+++ b/dlls/comctl32/taskdialog.c
@@ -17,51 +17,389 @@
  */
 
 #include "comctl32.h"
-#include "winuser.h"
+#include "shlwapi.h"
+
+#include "windef.h"
+#include "winbase.h"
+#include "wingdi.h"
+#include "winternl.h"
+#include "dlgs.h"
 #include "wine/debug.h"
+#include "wine/unicode.h"
 
 WINE_DEFAULT_DEBUG_CHANNEL(commctrl);
 
+/* Returns a pointer to the string specified by "text", which may be a resource identifier. */
+static const WCHAR* TASKDIALOG_GetText(HINSTANCE instance, const WCHAR *text)
+{
+    if (IS_INTRESOURCE(text))
+    {
+        /* Note that on Windows creating the dialog fails if LoadStringW fails,
+         * while we simply ignore the invalid resource and return NULL. */
+        const WCHAR* result;
+        return LoadStringW(instance, LOWORD(text), (LPWSTR)&result, 0) > 0 ? result : NULL;
+    }
+    else return text;
+}
+
+/*
+ * TaskDialog layout (not everything implemented yet):
+ *
+ * +------------------------------------------------------------+
+ * | Window title (fallback: executable file name)            X |
+ * +------------------------------------------------------------+
+ * | MAIN   Main instruction                                    |
+ * | ICON                                                       |
+ * | HERE   Content                                             |
+ * |                                                            |
+ * |        Expanded information if TDF_EXPAND_FOOTER AREA      |
+ * |        is not set.                                         |
+ * |                                                            |
+ * |        [Progress Bar or Marquee Progress Bar_____________] |
+ * |                                                            |
+ * |        (*) Radio button 1                                  |
+ * |        ( ) Radio button 2                                  |
+ * |                                                            |
+ * |        => User buttons if TDF_USE_COMMAND_LINKS or         |
+ * |           TDF_USE_COMMAND_LINKS_NOICON is set              |
+ * |                                                            |
+ * |        => In this case, the user buttons don't appear      |
+ * |           below.                                           |
+ * |                                                            |
+ * +============================================================+
+ * | (V) Expand/collapse        [User buttons] [Common buttons] |
+ * | [ ] Verification checkbox                                  |
+ * +============================================================+
+ * | ICON  Footer text                                          |
+ * |       which can span multiple lines.                       |
+ * +============================================================+
+ * | Expanded information if TDF_EXPAND_FOOTER_AREA is set      |
+ * +------------------------------------------------------------+
+ */
+static void TASKDIALOG_OnInit(HWND hwnd, TASKDIALOGCONFIG *config)
+{
+    HFONT normalFont, mainInstructionFont;
+    LOGFONTW mainInstructionFontAttributes = {0};
+    HWND defaultButton = 0;
+    HDC hdc;
+    HMONITOR monitor = 0;
+    MONITORINFO monitorInfo;
+    int nButtons = 0;
+    int i, currentX, currentY;
+    int buttonWidth, buttonAreaWidth, buttonHeight;
+    int windowClientWidth, windowClientHeight, windowLeft, windowTop;
+    int windowBorderHeight, windowBorderWidth;
+    int mainAreaTextLeft, mainAreaTextWidth;
+    int contentTextWidth, contentTextHeight;
+    int mainInstructionWidth, mainInstructionHeight;
+    int spacing;
+    const WCHAR *contentText, *mainInstructionText, *txt;
+    RECT rect;
+    static const int commonButtons[6] = { IDOK, IDYES, IDNO, IDCANCEL, IDRETRY, IDCLOSE };
+
+    /* Compute spacing between the dialog elements: 5 dialog units. */
+    rect.top = rect.left = 0;
+    rect.right = rect.bottom = 5;
+    MapDialogRect(hwnd, &rect);
+    spacing = rect.right - rect.left;
+
+    /* Get the texts to be displayed. */
+    TRACE("pszWindowTitle=%s\n", debugstr_w(config->pszWindowTitle));
+    txt = TASKDIALOG_GetText(config->hInstance, config->pszWindowTitle);
+    if (!txt)
+    {
+        /* If the supplied window title is invalid, display the executable file name instead. */
+        WCHAR *windowTitleBuffer = Alloc((MAX_PATH + 1) * sizeof(WCHAR));
+        if (!windowTitleBuffer)
+        {
+            EndDialog(hwnd, E_OUTOFMEMORY);
+            return;
+        }
+        GetModuleFileNameW(NULL, windowTitleBuffer, MAX_PATH);
+        SetWindowTextW(hwnd, PathFindFileNameW(windowTitleBuffer));
+        Free(windowTitleBuffer);
+        windowTitleBuffer = NULL;
+    }
+    else SetWindowTextW(hwnd, txt);
+
+    TRACE("pszMainInstruction=%s\n", debugstr_w(config->pszMainInstruction));
+    mainInstructionText = TASKDIALOG_GetText(config->hInstance, config->pszMainInstruction);
+    SetWindowTextW(GetDlgItem(hwnd, IDC_MAIN_INSTRUCTION), mainInstructionText);
+
+    TRACE("pszContent=%s\n", debugstr_w(config->pszContent));
+    contentText = TASKDIALOG_GetText(config->hInstance, config->pszContent);
+    SetWindowTextW(GetDlgItem(hwnd, IDC_CONTENT), contentText);
+
+    if (config->pszFooter)
+    {
+        FIXME("pszFooter=%s\n", debugstr_w(config->pszFooter));
+    }
+    if (config->pszVerificationText)
+    {
+        FIXME("pszVerificationText=%s\n", debugstr_w(config->pszVerificationText));
+    }
+    if (config->pszExpandedInformation)
+    {
+        FIXME("pszExpandedInformation=%s\n", debugstr_w(config->pszExpandedInformation));
+    }
+    if (config->pszExpandedControlText)
+    {
+        FIXME("pszExpandedControlText=%s\n", debugstr_w(config->pszExpandedControlText));
+    }
+    if (config->pszCollapsedControlText)
+    {
+        FIXME("pszCollapsedControlText=%s\n", debugstr_w(config->pszCollapsedControlText));
+    }
+
+    /* Add user-defined buttons. */
+    if (config->cButtons)
+        FIXME("Custom task dialog buttons not implemented\n");
+    if (config->cRadioButtons)
+        FIXME("Task dialog radio buttons not implemented\n");
+
+    /* Destroy unused common buttons. */
+    TRACE("dwCommonButtons=%x\n", config->dwCommonButtons);
+    if (!(config->dwCommonButtons & TDCBF_YES_BUTTON))
+        DestroyWindow(GetDlgItem(hwnd, IDYES));
+    else nButtons++;
+    if (!(config->dwCommonButtons & TDCBF_NO_BUTTON))
+        DestroyWindow(GetDlgItem(hwnd, IDNO));
+    else nButtons++;
+    if (!(config->dwCommonButtons & TDCBF_CANCEL_BUTTON))
+        DestroyWindow(GetDlgItem(hwnd, IDCANCEL));
+    else nButtons++;
+    if (!(config->dwCommonButtons & TDCBF_RETRY_BUTTON))
+        DestroyWindow(GetDlgItem(hwnd, IDRETRY));
+    else nButtons++;
+    if (!(config->dwCommonButtons & TDCBF_CLOSE_BUTTON))
+        DestroyWindow(GetDlgItem(hwnd, IDCLOSE));
+    else nButtons++;
+    /* If no buttons are specified, the OK button remains. */
+    if (!(config->dwCommonButtons & TDCBF_OK_BUTTON) && nButtons > 0)
+        DestroyWindow(GetDlgItem(hwnd, IDOK));
+    else nButtons++;
+
+    /* Position everything. */
+    GetWindowRect(hwnd, &rect);
+    windowBorderHeight = rect.bottom - rect.top;
+    windowBorderWidth  = rect.right - rect.left;
+    GetClientRect(hwnd, &rect);
+    windowBorderHeight -= rect.bottom - rect.top;
+    windowBorderWidth  -= rect.right - rect.left;
+
+    /* Set a bold font for the main instruction. */
+    hdc = GetDC(hwnd);
+    normalFont = SelectObject(hdc, (HFONT)SendMessageW(hwnd, WM_GETFONT, 0, 0));
+
+    GetObjectW(normalFont, sizeof(mainInstructionFontAttributes), &mainInstructionFontAttributes);
+    mainInstructionFontAttributes.lfWeight = FW_BOLD;
+    mainInstructionFont = CreateFontIndirectW(&mainInstructionFontAttributes);
+    SendMessageW(GetDlgItem(hwnd, IDC_MAIN_INSTRUCTION), WM_SETFONT, (WPARAM)mainInstructionFont, 0);
+
+    /* Calculate maximum button text width and height. */
+    buttonHeight = buttonWidth = 1;
+    for (i = 0; i < (sizeof(commonButtons) / sizeof(commonButtons[0])); i++)
+    {
+        HWND hItem = GetDlgItem(hwnd, commonButtons[i]);
+        if (GetWindowLongW(hItem, GWL_STYLE) & WS_VISIBLE)
+        {
+            WCHAR buttonText[1024]; /* enough to fit a (translated) text of any of the common buttons */
+            int w, h;
+            if (GetWindowTextW(hItem, buttonText, 1024))
+            {
+                DrawTextW(hdc, buttonText, -1, &rect, DT_LEFT | DT_EXPANDTABS | DT_CALCRECT);
+                h = rect.bottom - rect.top;
+                w = rect.right - rect.left;
+                if (h > buttonHeight) buttonHeight = h;
+                if (w > buttonWidth) buttonWidth = w;
+            }
+        }
+    }
+
+    /* Give the buttons some white space. */
+    buttonHeight *= 2;
+    buttonWidth = max(buttonWidth, buttonHeight * 2) * 2;
+
+    buttonAreaWidth = (buttonWidth + spacing) * nButtons - spacing;
+    mainAreaTextLeft = spacing;
+
+    /* Calculate main area text size. The texts are at least as wide as the button area. */
+    GetClientRect(GetDlgItem(hwnd, IDC_MAIN_INSTRUCTION), &rect);
+    rect.top = rect.left = rect.bottom = 0;
+    rect.right = max(rect.right, buttonAreaWidth);
+    SelectObject(hdc, mainInstructionFont);
+    DrawTextW(hdc, mainInstructionText, -1, &rect,
+              DT_LEFT | DT_EXPANDTABS | DT_WORDBREAK | DT_CALCRECT);
+    DeleteObject(mainInstructionFont);
+    mainInstructionWidth = rect.right;
+    mainInstructionHeight = rect.bottom;
+
+    GetClientRect(GetDlgItem(hwnd, IDC_CONTENT), &rect);
+    rect.top = rect.left = rect.bottom = 0;
+    rect.right = max(rect.right, buttonAreaWidth);
+    SelectObject(hdc, normalFont);
+    DrawTextW(hdc, contentText, -1, &rect,
+              DT_LEFT | DT_EXPANDTABS | DT_WORDBREAK | DT_CALCRECT);
+    ReleaseDC(hwnd, hdc);
+    contentTextWidth = rect.right;
+    contentTextHeight = rect.bottom;
+
+    mainAreaTextWidth = max(mainInstructionWidth, contentTextWidth);
+    windowClientWidth = max(spacing + buttonAreaWidth + spacing,
+                            mainAreaTextLeft + mainAreaTextWidth + spacing);
+    if (config->cxWidth)
+        FIXME("Ignoring config->cxWidth.\n");
+
+    /* Position everything from top to bottom. */
+    currentY = spacing;
+
+    /* Position the texts in the main area. */
+    if (mainInstructionHeight)
+    {
+        SetWindowPos(GetDlgItem(hwnd, IDC_MAIN_INSTRUCTION), 0, mainAreaTextLeft, currentY,
+                     mainInstructionWidth, mainInstructionHeight,
+                     SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW);
+        currentY += mainInstructionHeight + spacing;
+    }
+    else DestroyWindow(GetDlgItem(hwnd, IDC_MAIN_INSTRUCTION));
+
+    if (contentTextHeight)
+    {
+        SetWindowPos(GetDlgItem(hwnd, IDC_CONTENT), 0, mainAreaTextLeft, currentY,
+                     contentTextWidth, contentTextHeight,
+                     SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW);
+        currentY += contentTextHeight + spacing;
+    }
+    else DestroyWindow(GetDlgItem(hwnd, IDC_CONTENT));
+
+    /* Position the buttons: right-aligned */
+    currentX = windowClientWidth - spacing - buttonAreaWidth;
+    for (i = 0; i < (sizeof(commonButtons) / sizeof(commonButtons[0])); i++)
+    {
+        HWND hItem = GetDlgItem(hwnd, commonButtons[i]);
+        if (GetWindowLongW(hItem, GWL_STYLE) & WS_VISIBLE)
+        {
+            if (defaultButton == 0 || config->nDefaultButton == commonButtons[i])
+                defaultButton = hItem;
+            SetWindowPos(hItem, 0, currentX, currentY, buttonWidth, buttonHeight,
+                         SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW);
+            currentX += buttonWidth + spacing;
+        }
+    }
+
+    if (defaultButton)
+    {
+        SetFocus(defaultButton);
+        SendMessageW(defaultButton, BM_SETSTYLE, BS_DEFPUSHBUTTON, TRUE);
+    }
+
+    currentY += buttonHeight + spacing;
+    windowClientHeight = currentY;
+
+    /* Query parent window/desktop size and center window. */
+    monitor = MonitorFromWindow(config->hwndParent ? config->hwndParent : GetActiveWindow(),
+                                MONITOR_DEFAULTTOPRIMARY);
+    monitorInfo.cbSize = sizeof(monitorInfo);
+    GetMonitorInfoW(monitor, &monitorInfo);
+    windowLeft = (monitorInfo.rcWork.left + monitorInfo.rcWork.right
+                  - (windowClientWidth + windowBorderWidth)) / 2;
+    windowTop = (monitorInfo.rcWork.top + monitorInfo.rcWork.bottom
+                 - (windowClientHeight + windowBorderHeight)) / 2;
+    SetWindowPos(hwnd, 0, windowLeft, windowTop,
+                 windowClientWidth + windowBorderWidth, windowClientHeight + windowBorderHeight,
+                 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW);
+}
+
+
+/**************************************************************************
+ *           TASKDIALOG_DlgProc
+ *
+ * Dialog procedure for task dialogs.
+ */
+static INT_PTR CALLBACK TASKDIALOG_DlgProc(HWND hwnd, UINT message,
+                                           WPARAM wParam, LPARAM lParam)
+{
+    switch(message)
+    {
+        case WM_INITDIALOG:
+        {
+            TASKDIALOGCONFIG *config = (TASKDIALOGCONFIG*)lParam;
+            TASKDIALOG_OnInit(hwnd, config);
+            break;
+        }
+
+        case WM_COMMAND:
+        {
+            if (LOWORD(wParam) == IDCANCEL)
+            {
+                /* TODO: Dialog may be cancelled only if the IDCANCEL button is present
+                 * or the TDF_ALLOW_DIALOG_CANCELLATION flag is set. */
+                FIXME("Not checking if dialog cancellation is allowed\n");
+            }
+            EndDialog(hwnd, wParam);
+            break;
+        }
+
+        default:
+        {
+            TRACE("Message number 0x%04x is being ignored.\n", message);
+            break;
+        }
+    }
+    return 0;
+}
+
 /***********************************************************************
  * TaskDialogIndirect [COMCTL32.@]
  */
 HRESULT WINAPI TaskDialogIndirect(const TASKDIALOGCONFIG *pTaskConfig, int *pnButton,
                                   int *pnRadioButton, BOOL *pfVerificationFlagChecked)
 {
-    UINT uType = 0;
-    INT  ret;
-    FIXME("%p, %p, %p, %p\n", pTaskConfig, pnButton, pnRadioButton, pfVerificationFlagChecked);
+    LPVOID template;
+    HRSRC hRes;
+    INT_PTR ret;
 
+    TRACE("pTaskConfig=%p, pnButton=%p\n", pTaskConfig, pnButton);
     if (pnButton) *pnButton = 0;
 
     if (!pTaskConfig || pTaskConfig->cbSize != sizeof(TASKDIALOGCONFIG))
         return E_INVALIDARG;
 
-    if (pTaskConfig->dwCommonButtons & TDCBF_YES_BUTTON &&
-        pTaskConfig->dwCommonButtons & TDCBF_NO_BUTTON &&
-        pTaskConfig->dwCommonButtons & TDCBF_CANCEL_BUTTON)
-        uType |= MB_YESNOCANCEL;
-    else
-    if (pTaskConfig->dwCommonButtons & TDCBF_YES_BUTTON &&
-        pTaskConfig->dwCommonButtons & TDCBF_NO_BUTTON)
-        uType |= MB_YESNO;
-    else
-    if (pTaskConfig->dwCommonButtons & TDCBF_RETRY_BUTTON &&
-        pTaskConfig->dwCommonButtons & TDCBF_CANCEL_BUTTON)
-        uType |= MB_RETRYCANCEL;
-    else
-    if (pTaskConfig->dwCommonButtons & TDCBF_OK_BUTTON &&
-        pTaskConfig->dwCommonButtons & TDCBF_CANCEL_BUTTON)
-        uType |= MB_OKCANCEL;
+    TRACE("hWndParent=%p, hInstance=%p, dwCommonButtons=%x",
+          pTaskConfig->hwndParent, pTaskConfig->hInstance, pTaskConfig->dwCommonButtons);
+
+    if (!(hRes = FindResourceExW(COMCTL32_hModule, (LPWSTR)RT_DIALOG,
+                                 MAKEINTRESOURCEW(IDD_TASKDIALOG), LANG_NEUTRAL)))
+    {
+        ERR("Cannot find TASKDIALOG resource\n");
+        return E_FAIL;
+    }
+    if (!(template = LoadResource(COMCTL32_hModule, hRes)))
+    {
+        ERR("Cannot load TASKDIALOG resource\n");
+        return E_FAIL;
+    }
+
+    ret = DialogBoxIndirectParamW(pTaskConfig->hInstance, template, pTaskConfig->hwndParent,
+                                  TASKDIALOG_DlgProc, (LPARAM)pTaskConfig);
+    if (pnRadioButton)
+    {
+        FIXME("Task dialog radio buttons not implemented\n");
+        *pnRadioButton = pTaskConfig->nDefaultButton;
+    }
+    if (pfVerificationFlagChecked)
+    {
+        FIXME("Task dialog verification check box not implemented\n");
+        *pfVerificationFlagChecked = TRUE;
+    }
+    if (ret > 0)
+    {
+        if (pnButton) *pnButton = ret;
+        return S_OK;
+    }
     else
-    if (pTaskConfig->dwCommonButtons & TDCBF_OK_BUTTON)
-        uType |= MB_OK;
-    ret = MessageBoxW(pTaskConfig->hwndParent, pTaskConfig->pszMainInstruction,
-                      pTaskConfig->pszWindowTitle, uType);
-    FIXME("dwCommonButtons=%x uType=%x ret=%x\n", pTaskConfig->dwCommonButtons, uType, ret);
-
-    if (pnButton) *pnButton = ret;
-    if (pnRadioButton) *pnRadioButton = pTaskConfig->nDefaultButton;
-    if (pfVerificationFlagChecked) *pfVerificationFlagChecked = TRUE;
-    return S_OK;
+    {
+        /* DialogBoxIndirect returns -1 on failure. */
+        return ret == -1 ? E_FAIL : ret;
+    }
 }
-- 
1.8.4.5




More information about the wine-patches mailing list