windows: Message boxes use Task Dialogs if possible (thanks, Jack!).
authorRyan C. Gordon <icculus@icculus.org>
Wed, 28 Feb 2018 01:54:22 -0500
changeset 11909b2e68bf41993
parent 11908 97092601ea78
child 11910 9947d9f539e8
windows: Message boxes use Task Dialogs if possible (thanks, Jack!).

This lets the message box have an icon. Unless the app has opted-in to using
the v6 common controls, though, this will fall back to the usual SDL message
boxes.
src/video/windows/SDL_windowsmessagebox.c
src/video/windows/SDL_windowstaskdialog.h
     1.1 --- a/src/video/windows/SDL_windowsmessagebox.c	Wed Feb 28 01:23:49 2018 -0500
     1.2 +++ b/src/video/windows/SDL_windowsmessagebox.c	Wed Feb 28 01:54:22 2018 -0500
     1.3 @@ -26,7 +26,7 @@
     1.4  
     1.5  #include "SDL_assert.h"
     1.6  #include "SDL_windowsvideo.h"
     1.7 -
     1.8 +#include "SDL_windowstaskdialog.h"
     1.9  
    1.10  #ifndef SS_EDITCONTROL
    1.11  #define SS_EDITCONTROL  0x2000
    1.12 @@ -341,8 +341,9 @@
    1.13      return dialog;
    1.14  }
    1.15  
    1.16 -int
    1.17 -WIN_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
    1.18 +/* This function is called if a Task Dialog is unsupported. */
    1.19 +static int
    1.20 +WIN_ShowOldMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
    1.21  {
    1.22      WIN_DialogData *dialog;
    1.23      int i, x, y;
    1.24 @@ -491,6 +492,121 @@
    1.25      return 0;
    1.26  }
    1.27  
    1.28 +/* TaskDialogIndirect procedure
    1.29 + * This is because SDL targets Windows XP (0x501), so this is not defined in the platform SDK.
    1.30 + */
    1.31 +typedef HRESULT(FAR WINAPI *TASKDIALOGINDIRECTPROC)(_In_ const TASKDIALOGCONFIG *pTaskConfig, _Out_opt_ int *pnButton, _Out_opt_ int *pnRadioButton, _Out_opt_ BOOL *pfVerificationFlagChecked);
    1.32 +
    1.33 +int
    1.34 +WIN_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
    1.35 +{
    1.36 +    HWND ParentWindow = NULL;
    1.37 +    wchar_t *wmessage;
    1.38 +    wchar_t *wtitle;
    1.39 +    TASKDIALOGCONFIG TaskConfig;
    1.40 +    TASKDIALOG_BUTTON *pButtons;
    1.41 +    TASKDIALOG_BUTTON *pButton;
    1.42 +    HMODULE hComctl32;
    1.43 +    TASKDIALOGINDIRECTPROC pfnTaskDialogIndirect;
    1.44 +    HRESULT hr;
    1.45 +    int nButton;
    1.46 +    int nCancelButton;
    1.47 +    int i;
    1.48 +
    1.49 +    /* If we cannot load comctl32.dll use the old messagebox! */
    1.50 +    hComctl32 = LoadLibrary(TEXT("Comctl32.dll"));
    1.51 +    if (hComctl32 == NULL) {
    1.52 +        return WIN_ShowOldMessageBox(messageboxdata,buttonid);
    1.53 +    }
    1.54 +    
    1.55 +    /* If TaskDialogIndirect doesn't exist use the old messagebox!
    1.56 +       This will fail prior to Windows Vista.
    1.57 +       The manifest file in the application may require targeting version 6 of comctl32.dll, even
    1.58 +       when we use LoadLibrary here!
    1.59 +       If you don't want to bother with manifests, put this #pragma in your app's source code somewhere:
    1.60 +       pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0'  processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
    1.61 +     */
    1.62 +    pfnTaskDialogIndirect = (TASKDIALOGINDIRECTPROC) GetProcAddress(hComctl32, "TaskDialogIndirect");
    1.63 +    if (pfnTaskDialogIndirect == NULL) {
    1.64 +        FreeLibrary(hComctl32);
    1.65 +        return WIN_ShowOldMessageBox(messageboxdata, buttonid);
    1.66 +    }
    1.67 +
    1.68 +    /* If we have a parent window, get the Instance and HWND for them
    1.69 +       so that our little dialog gets exclusive focus at all times. */
    1.70 +    if (messageboxdata->window) {
    1.71 +        ParentWindow = ((SDL_WindowData *) messageboxdata->window->driverdata)->hwnd;
    1.72 +    }
    1.73 +
    1.74 +    wmessage = WIN_UTF8ToString(messageboxdata->message);
    1.75 +    wtitle = WIN_UTF8ToString(messageboxdata->title);
    1.76 +
    1.77 +    SDL_zero(TaskConfig);
    1.78 +    TaskConfig.cbSize = sizeof (TASKDIALOGCONFIG);
    1.79 +    TaskConfig.hwndParent = ParentWindow;
    1.80 +    TaskConfig.dwFlags = TDF_SIZE_TO_CONTENT;
    1.81 +    TaskConfig.pszWindowTitle = wtitle;
    1.82 +    if (messageboxdata->flags & SDL_MESSAGEBOX_ERROR) {
    1.83 +        TaskConfig.pszMainIcon = TD_ERROR_ICON;
    1.84 +    } else if (messageboxdata->flags & SDL_MESSAGEBOX_WARNING) {
    1.85 +        TaskConfig.pszMainIcon = TD_WARNING_ICON;
    1.86 +    } else if (messageboxdata->flags & SDL_MESSAGEBOX_INFORMATION) {
    1.87 +        TaskConfig.pszMainIcon = TD_INFORMATION_ICON;
    1.88 +    } else {
    1.89 +        TaskConfig.pszMainIcon = NULL;
    1.90 +    }
    1.91 +
    1.92 +    TaskConfig.pszContent = wmessage;
    1.93 +    TaskConfig.cButtons = messageboxdata->numbuttons;
    1.94 +    pButtons = SDL_malloc(sizeof (TASKDIALOG_BUTTON) * messageboxdata->numbuttons);
    1.95 +    TaskConfig.nDefaultButton = 0;
    1.96 +    for (i = 0; i < messageboxdata->numbuttons; i++)
    1.97 +    {
    1.98 +        pButton = &pButtons[messageboxdata->numbuttons-1-i];
    1.99 +        if (messageboxdata->buttons[i].flags & SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT) {
   1.100 +            nCancelButton = messageboxdata->buttons[i].buttonid;
   1.101 +            pButton->nButtonID = 2;
   1.102 +        } else {
   1.103 +            pButton->nButtonID = messageboxdata->buttons[i].buttonid + 1;
   1.104 +            if (pButton->nButtonID >= 2) {
   1.105 +                pButton->nButtonID++;
   1.106 +            }
   1.107 +        }
   1.108 +        pButton->pszButtonText = WIN_UTF8ToString(messageboxdata->buttons[i].text);
   1.109 +        if (messageboxdata->buttons[i].flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) {
   1.110 +            TaskConfig.nDefaultButton = pButton->nButtonID;
   1.111 +        }
   1.112 +    }
   1.113 +    TaskConfig.pButtons = pButtons;
   1.114 +
   1.115 +    /* Show the Task Dialog */
   1.116 +    hr = pfnTaskDialogIndirect(&TaskConfig, &nButton, NULL, NULL);
   1.117 +
   1.118 +    /* Free everything */
   1.119 +    FreeLibrary(hComctl32);
   1.120 +    SDL_free(wmessage);
   1.121 +    SDL_free(wtitle);
   1.122 +    for (i = 0; i < messageboxdata->numbuttons; i++) {
   1.123 +        SDL_free((wchar_t *) pButtons[i].pszButtonText);
   1.124 +    }
   1.125 +    SDL_free(pButtons);
   1.126 +
   1.127 +    /* Check the Task Dialog was successful and give the result */
   1.128 +    if (SUCCEEDED(hr)) {
   1.129 +        if (nButton == 2) {
   1.130 +            *buttonid = nCancelButton;
   1.131 +        } else if (nButton > 2) {
   1.132 +            *buttonid = nButton-1-1;
   1.133 +        } else {
   1.134 +            *buttonid = nButton-1;
   1.135 +        }
   1.136 +        return 0;
   1.137 +    }
   1.138 +
   1.139 +    /* We failed showing the Task Dialog, use the old message box! */
   1.140 +    return WIN_ShowOldMessageBox(messageboxdata, buttonid);
   1.141 +}
   1.142 +
   1.143  #endif /* SDL_VIDEO_DRIVER_WINDOWS */
   1.144  
   1.145  /* vi: set ts=4 sw=4 expandtab: */
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/src/video/windows/SDL_windowstaskdialog.h	Wed Feb 28 01:54:22 2018 -0500
     2.3 @@ -0,0 +1,156 @@
     2.4 +/*
     2.5 +  Simple DirectMedia Layer
     2.6 +  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
     2.7 +
     2.8 +  This software is provided 'as-is', without any express or implied
     2.9 +  warranty.  In no event will the authors be held liable for any damages
    2.10 +  arising from the use of this software.
    2.11 +
    2.12 +  Permission is granted to anyone to use this software for any purpose,
    2.13 +  including commercial applications, and to alter it and redistribute it
    2.14 +  freely, subject to the following restrictions:
    2.15 +
    2.16 +  1. The origin of this software must not be misrepresented; you must not
    2.17 +     claim that you wrote the original software. If you use this software
    2.18 +     in a product, an acknowledgment in the product documentation would be
    2.19 +     appreciated but is not required.
    2.20 +  2. Altered source versions must be plainly marked as such, and must not be
    2.21 +     misrepresented as being the original software.
    2.22 +  3. This notice may not be removed or altered from any source distribution.
    2.23 +*/
    2.24 +#include <pshpack1.h>
    2.25 +
    2.26 +typedef HRESULT(CALLBACK *PFTASKDIALOGCALLBACK)(_In_ HWND hwnd, _In_ UINT msg, _In_ WPARAM wParam, _In_ LPARAM lParam, _In_ LONG_PTR lpRefData);
    2.27 +
    2.28 +enum _TASKDIALOG_FLAGS
    2.29 +{
    2.30 +    TDF_ENABLE_HYPERLINKS = 0x0001,
    2.31 +    TDF_USE_HICON_MAIN = 0x0002,
    2.32 +    TDF_USE_HICON_FOOTER = 0x0004,
    2.33 +    TDF_ALLOW_DIALOG_CANCELLATION = 0x0008,
    2.34 +    TDF_USE_COMMAND_LINKS = 0x0010,
    2.35 +    TDF_USE_COMMAND_LINKS_NO_ICON = 0x0020,
    2.36 +    TDF_EXPAND_FOOTER_AREA = 0x0040,
    2.37 +    TDF_EXPANDED_BY_DEFAULT = 0x0080,
    2.38 +    TDF_VERIFICATION_FLAG_CHECKED = 0x0100,
    2.39 +    TDF_SHOW_PROGRESS_BAR = 0x0200,
    2.40 +    TDF_SHOW_MARQUEE_PROGRESS_BAR = 0x0400,
    2.41 +    TDF_CALLBACK_TIMER = 0x0800,
    2.42 +    TDF_POSITION_RELATIVE_TO_WINDOW = 0x1000,
    2.43 +    TDF_RTL_LAYOUT = 0x2000,
    2.44 +    TDF_NO_DEFAULT_RADIO_BUTTON = 0x4000,
    2.45 +    TDF_CAN_BE_MINIMIZED = 0x8000,
    2.46 +    //#if (NTDDI_VERSION >= NTDDI_WIN8)
    2.47 +    TDF_NO_SET_FOREGROUND = 0x00010000, // Don't call SetForegroundWindow() when activating the dialog
    2.48 +                                        //#endif // (NTDDI_VERSION >= NTDDI_WIN8)
    2.49 +                                        TDF_SIZE_TO_CONTENT = 0x01000000  // used by ShellMessageBox to emulate MessageBox sizing behavior
    2.50 +};
    2.51 +typedef int TASKDIALOG_FLAGS;                         // Note: _TASKDIALOG_FLAGS is an int
    2.52 +
    2.53 +typedef enum _TASKDIALOG_MESSAGES
    2.54 +{
    2.55 +    TDM_NAVIGATE_PAGE = WM_USER + 101,
    2.56 +    TDM_CLICK_BUTTON = WM_USER + 102, // wParam = Button ID
    2.57 +    TDM_SET_MARQUEE_PROGRESS_BAR = WM_USER + 103, // wParam = 0 (nonMarque) wParam != 0 (Marquee)
    2.58 +    TDM_SET_PROGRESS_BAR_STATE = WM_USER + 104, // wParam = new progress state
    2.59 +    TDM_SET_PROGRESS_BAR_RANGE = WM_USER + 105, // lParam = MAKELPARAM(nMinRange, nMaxRange)
    2.60 +    TDM_SET_PROGRESS_BAR_POS = WM_USER + 106, // wParam = new position
    2.61 +    TDM_SET_PROGRESS_BAR_MARQUEE = WM_USER + 107, // wParam = 0 (stop marquee), wParam != 0 (start marquee), lparam = speed (milliseconds between repaints)
    2.62 +    TDM_SET_ELEMENT_TEXT = WM_USER + 108, // wParam = element (TASKDIALOG_ELEMENTS), lParam = new element text (LPCWSTR)
    2.63 +    TDM_CLICK_RADIO_BUTTON = WM_USER + 110, // wParam = Radio Button ID
    2.64 +    TDM_ENABLE_BUTTON = WM_USER + 111, // lParam = 0 (disable), lParam != 0 (enable), wParam = Button ID
    2.65 +    TDM_ENABLE_RADIO_BUTTON = WM_USER + 112, // lParam = 0 (disable), lParam != 0 (enable), wParam = Radio Button ID
    2.66 +    TDM_CLICK_VERIFICATION = WM_USER + 113, // wParam = 0 (unchecked), 1 (checked), lParam = 1 (set key focus)
    2.67 +    TDM_UPDATE_ELEMENT_TEXT = WM_USER + 114, // wParam = element (TASKDIALOG_ELEMENTS), lParam = new element text (LPCWSTR)
    2.68 +    TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE = WM_USER + 115, // wParam = Button ID, lParam = 0 (elevation not required), lParam != 0 (elevation required)
    2.69 +    TDM_UPDATE_ICON = WM_USER + 116  // wParam = icon element (TASKDIALOG_ICON_ELEMENTS), lParam = new icon (hIcon if TDF_USE_HICON_* was set, PCWSTR otherwise)
    2.70 +} TASKDIALOG_MESSAGES;
    2.71 +
    2.72 +typedef enum _TASKDIALOG_NOTIFICATIONS
    2.73 +{
    2.74 +    TDN_CREATED = 0,
    2.75 +    TDN_NAVIGATED = 1,
    2.76 +    TDN_BUTTON_CLICKED = 2,            // wParam = Button ID
    2.77 +    TDN_HYPERLINK_CLICKED = 3,            // lParam = (LPCWSTR)pszHREF
    2.78 +    TDN_TIMER = 4,            // wParam = Milliseconds since dialog created or timer reset
    2.79 +    TDN_DESTROYED = 5,
    2.80 +    TDN_RADIO_BUTTON_CLICKED = 6,            // wParam = Radio Button ID
    2.81 +    TDN_DIALOG_CONSTRUCTED = 7,
    2.82 +    TDN_VERIFICATION_CLICKED = 8,             // wParam = 1 if checkbox checked, 0 if not, lParam is unused and always 0
    2.83 +    TDN_HELP = 9,
    2.84 +    TDN_EXPANDO_BUTTON_CLICKED = 10            // wParam = 0 (dialog is now collapsed), wParam != 0 (dialog is now expanded)
    2.85 +} TASKDIALOG_NOTIFICATIONS;
    2.86 +
    2.87 +typedef struct _TASKDIALOG_BUTTON
    2.88 +{
    2.89 +    int     nButtonID;
    2.90 +    PCWSTR  pszButtonText;
    2.91 +} TASKDIALOG_BUTTON;
    2.92 +
    2.93 +typedef enum _TASKDIALOG_ELEMENTS
    2.94 +{
    2.95 +    TDE_CONTENT,
    2.96 +    TDE_EXPANDED_INFORMATION,
    2.97 +    TDE_FOOTER,
    2.98 +    TDE_MAIN_INSTRUCTION
    2.99 +} TASKDIALOG_ELEMENTS;
   2.100 +
   2.101 +typedef enum _TASKDIALOG_ICON_ELEMENTS
   2.102 +{
   2.103 +    TDIE_ICON_MAIN,
   2.104 +    TDIE_ICON_FOOTER
   2.105 +} TASKDIALOG_ICON_ELEMENTS;
   2.106 +
   2.107 +#define TD_WARNING_ICON         MAKEINTRESOURCEW(-1)
   2.108 +#define TD_ERROR_ICON           MAKEINTRESOURCEW(-2)
   2.109 +#define TD_INFORMATION_ICON     MAKEINTRESOURCEW(-3)
   2.110 +#define TD_SHIELD_ICON          MAKEINTRESOURCEW(-4)
   2.111 +
   2.112 +enum _TASKDIALOG_COMMON_BUTTON_FLAGS
   2.113 +{
   2.114 +    TDCBF_OK_BUTTON = 0x0001, // selected control return value IDOK
   2.115 +    TDCBF_YES_BUTTON = 0x0002, // selected control return value IDYES
   2.116 +    TDCBF_NO_BUTTON = 0x0004, // selected control return value IDNO
   2.117 +    TDCBF_CANCEL_BUTTON = 0x0008, // selected control return value IDCANCEL
   2.118 +    TDCBF_RETRY_BUTTON = 0x0010, // selected control return value IDRETRY
   2.119 +    TDCBF_CLOSE_BUTTON = 0x0020  // selected control return value IDCLOSE
   2.120 +};
   2.121 +typedef int TASKDIALOG_COMMON_BUTTON_FLAGS;           // Note: _TASKDIALOG_COMMON_BUTTON_FLAGS is an int
   2.122 +
   2.123 +typedef struct _TASKDIALOGCONFIG
   2.124 +{
   2.125 +    UINT        cbSize;
   2.126 +    HWND        hwndParent;                             // incorrectly named, this is the owner window, not a parent.
   2.127 +    HINSTANCE   hInstance;                              // used for MAKEINTRESOURCE() strings
   2.128 +    TASKDIALOG_FLAGS                dwFlags;            // TASKDIALOG_FLAGS (TDF_XXX) flags
   2.129 +    TASKDIALOG_COMMON_BUTTON_FLAGS  dwCommonButtons;    // TASKDIALOG_COMMON_BUTTON (TDCBF_XXX) flags
   2.130 +    PCWSTR      pszWindowTitle;                         // string or MAKEINTRESOURCE()
   2.131 +    union
   2.132 +    {
   2.133 +        HICON   hMainIcon;
   2.134 +        PCWSTR  pszMainIcon;
   2.135 +    } DUMMYUNIONNAME;
   2.136 +    PCWSTR      pszMainInstruction;
   2.137 +    PCWSTR      pszContent;
   2.138 +    UINT        cButtons;
   2.139 +    const TASKDIALOG_BUTTON  *pButtons;
   2.140 +    int         nDefaultButton;
   2.141 +    UINT        cRadioButtons;
   2.142 +    const TASKDIALOG_BUTTON  *pRadioButtons;
   2.143 +    int         nDefaultRadioButton;
   2.144 +    PCWSTR      pszVerificationText;
   2.145 +    PCWSTR      pszExpandedInformation;
   2.146 +    PCWSTR      pszExpandedControlText;
   2.147 +    PCWSTR      pszCollapsedControlText;
   2.148 +    union
   2.149 +    {
   2.150 +        HICON   hFooterIcon;
   2.151 +        PCWSTR  pszFooterIcon;
   2.152 +    } DUMMYUNIONNAME2;
   2.153 +    PCWSTR      pszFooter;
   2.154 +    PFTASKDIALOGCALLBACK pfCallback;
   2.155 +    LONG_PTR    lpCallbackData;
   2.156 +    UINT        cxWidth;                                // width of the Task Dialog's client area in DLU's. If 0, Task Dialog will calculate the ideal width.
   2.157 +} TASKDIALOGCONFIG;
   2.158 +
   2.159 +#include <poppack.h>