diff options
| author | Cecilio Pardo | 2024-09-11 15:44:28 +0200 |
|---|---|---|
| committer | Eli Zaretskii | 2024-09-14 13:57:26 +0300 |
| commit | aa7dee58d81817285208471074f1af598ebf0c98 (patch) | |
| tree | 2ff7a33af0eebdd12d1890eaf205ef4a84838e1d /src/w32menu.c | |
| parent | db1eb8a282c1832fd34be049e80dcb1a3b59ade2 (diff) | |
| download | emacs-aa7dee58d81817285208471074f1af598ebf0c98.tar.gz emacs-aa7dee58d81817285208471074f1af598ebf0c98.zip | |
Support GUI dialogs and message boxes better on MS-Windows
* src/w32menu.c (TASKDIALOG_COMMON_BUTTON_FLAGS, TASKDIALOG_FLAGS)
(PFTASKDIALOGCALLBACK, TASKDIALOG_BUTTON, TASKDIALOGCONFIG)
(TDN_CREATED, TDM_ENABLE_BUTTON, TDF_ALLOW_DIALOG_CANCELLATION)
(TD_INFORMATION_ICON) [!MINGW_W64]: Define.
(TaskDialogIndirect_Proc): Declare function type.
(TASK_DIALOG_MAX_BUTTONS): New macro.
(task_dialog_callback): New callback function.
(w32_popup_dialog): Add dialog implementation using TaskDialog.
(globals_of_w32menu): Load TaskDialogIndirect from comctl32.dll.
(Bug#20481)
Diffstat (limited to 'src/w32menu.c')
| -rw-r--r-- | src/w32menu.c | 214 |
1 files changed, 212 insertions, 2 deletions
diff --git a/src/w32menu.c b/src/w32menu.c index cea4f4892a4..c3d147841b6 100644 --- a/src/w32menu.c +++ b/src/w32menu.c | |||
| @@ -52,6 +52,9 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ | |||
| 52 | 52 | ||
| 53 | #include "w32common.h" /* for osinfo_cache */ | 53 | #include "w32common.h" /* for osinfo_cache */ |
| 54 | 54 | ||
| 55 | #include "commctrl.h" | ||
| 56 | |||
| 57 | /* This only applies to OS versions prior to Vista. */ | ||
| 55 | #undef HAVE_DIALOGS /* TODO: Implement native dialogs. */ | 58 | #undef HAVE_DIALOGS /* TODO: Implement native dialogs. */ |
| 56 | 59 | ||
| 57 | #ifndef TRUE | 60 | #ifndef TRUE |
| @@ -77,6 +80,66 @@ typedef int (WINAPI * MessageBoxW_Proc) ( | |||
| 77 | IN const WCHAR *caption, | 80 | IN const WCHAR *caption, |
| 78 | IN UINT type); | 81 | IN UINT type); |
| 79 | 82 | ||
| 83 | #ifndef MINGW_W64 | ||
| 84 | /* mingw.org's MinGW doesn't have this in its header files. */ | ||
| 85 | typedef int TASKDIALOG_COMMON_BUTTON_FLAGS; | ||
| 86 | |||
| 87 | typedef int TASKDIALOG_FLAGS; | ||
| 88 | |||
| 89 | typedef HRESULT (CALLBACK *PFTASKDIALOGCALLBACK) ( | ||
| 90 | HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LONG_PTR lpRefData); | ||
| 91 | |||
| 92 | typedef struct _TASKDIALOG_BUTTON { | ||
| 93 | int nButtonID; | ||
| 94 | PCWSTR pszButtonText; | ||
| 95 | } TASKDIALOG_BUTTON; | ||
| 96 | |||
| 97 | typedef struct _TASKDIALOGCONFIG { | ||
| 98 | UINT cbSize; | ||
| 99 | HWND hwndParent; | ||
| 100 | HINSTANCE hInstance; | ||
| 101 | TASKDIALOG_FLAGS dwFlags; | ||
| 102 | TASKDIALOG_COMMON_BUTTON_FLAGS dwCommonButtons; | ||
| 103 | PCWSTR pszWindowTitle; | ||
| 104 | union { | ||
| 105 | HICON hMainIcon; | ||
| 106 | PCWSTR pszMainIcon; | ||
| 107 | } DUMMYUNIONNAME; | ||
| 108 | PCWSTR pszMainInstruction; | ||
| 109 | PCWSTR pszContent; | ||
| 110 | UINT cButtons; | ||
| 111 | const TASKDIALOG_BUTTON *pButtons; | ||
| 112 | int nDefaultButton; | ||
| 113 | UINT cRadioButtons; | ||
| 114 | const TASKDIALOG_BUTTON *pRadioButtons; | ||
| 115 | int nDefaultRadioButton; | ||
| 116 | PCWSTR pszVerificationText; | ||
| 117 | PCWSTR pszExpandedInformation; | ||
| 118 | PCWSTR pszExpandedControlText; | ||
| 119 | PCWSTR pszCollapsedControlText; | ||
| 120 | union { | ||
| 121 | HICON hFooterIcon; | ||
| 122 | PCWSTR pszFooterIcon; | ||
| 123 | } DUMMYUNIONNAME2; | ||
| 124 | PCWSTR pszFooter; | ||
| 125 | PFTASKDIALOGCALLBACK pfCallback; | ||
| 126 | LONG_PTR lpCallbackData; | ||
| 127 | UINT cxWidth; | ||
| 128 | } TASKDIALOGCONFIG; | ||
| 129 | |||
| 130 | # define TDN_CREATED 0 | ||
| 131 | # define TDM_ENABLE_BUTTON (WM_USER+111) | ||
| 132 | # define TDF_ALLOW_DIALOG_CANCELLATION 0x8 | ||
| 133 | # define TD_INFORMATION_ICON MAKEINTRESOURCEW (-3) | ||
| 134 | |||
| 135 | #endif | ||
| 136 | |||
| 137 | typedef HRESULT (WINAPI *TaskDialogIndirect_Proc) ( | ||
| 138 | IN const TASKDIALOGCONFIG *pTaskConfig, | ||
| 139 | OUT int *pnButton, | ||
| 140 | OUT int *pnRadioButton, | ||
| 141 | OUT BOOL *pfVerificationFlagChecked); | ||
| 142 | |||
| 80 | #ifdef NTGUI_UNICODE | 143 | #ifdef NTGUI_UNICODE |
| 81 | GetMenuItemInfoA_Proc get_menu_item_info = GetMenuItemInfoA; | 144 | GetMenuItemInfoA_Proc get_menu_item_info = GetMenuItemInfoA; |
| 82 | SetMenuItemInfoA_Proc set_menu_item_info = SetMenuItemInfoA; | 145 | SetMenuItemInfoA_Proc set_menu_item_info = SetMenuItemInfoA; |
| @@ -89,6 +152,8 @@ AppendMenuW_Proc unicode_append_menu = NULL; | |||
| 89 | MessageBoxW_Proc unicode_message_box = NULL; | 152 | MessageBoxW_Proc unicode_message_box = NULL; |
| 90 | #endif /* NTGUI_UNICODE */ | 153 | #endif /* NTGUI_UNICODE */ |
| 91 | 154 | ||
| 155 | static TaskDialogIndirect_Proc task_dialog_indirect; | ||
| 156 | |||
| 92 | #ifdef HAVE_DIALOGS | 157 | #ifdef HAVE_DIALOGS |
| 93 | static Lisp_Object w32_dialog_show (struct frame *, Lisp_Object, Lisp_Object, char **); | 158 | static Lisp_Object w32_dialog_show (struct frame *, Lisp_Object, Lisp_Object, char **); |
| 94 | #else | 159 | #else |
| @@ -101,14 +166,155 @@ static int fill_in_menu (HMENU, widget_value *); | |||
| 101 | 166 | ||
| 102 | void w32_free_menu_strings (HWND); | 167 | void w32_free_menu_strings (HWND); |
| 103 | 168 | ||
| 169 | #define TASK_DIALOG_MAX_BUTTONS 10 | ||
| 170 | |||
| 171 | static HRESULT CALLBACK | ||
| 172 | task_dialog_callback (HWND hwnd, UINT msg, WPARAM wParam, | ||
| 173 | LPARAM lParam, LONG_PTR callback_data) | ||
| 174 | { | ||
| 175 | switch (msg) | ||
| 176 | { | ||
| 177 | case TDN_CREATED: | ||
| 178 | /* Disable all buttons with ID >= 2000 */ | ||
| 179 | for (int i = 0; i < TASK_DIALOG_MAX_BUTTONS; i++) | ||
| 180 | SendMessage (hwnd, TDM_ENABLE_BUTTON, 2000 + i, FALSE); | ||
| 181 | break; | ||
| 182 | } | ||
| 183 | return S_OK; | ||
| 184 | } | ||
| 185 | |||
| 104 | Lisp_Object | 186 | Lisp_Object |
| 105 | w32_popup_dialog (struct frame *f, Lisp_Object header, Lisp_Object contents) | 187 | w32_popup_dialog (struct frame *f, Lisp_Object header, Lisp_Object contents) |
| 106 | { | 188 | { |
| 107 | |||
| 108 | check_window_system (f); | 189 | check_window_system (f); |
| 109 | 190 | ||
| 110 | #ifndef HAVE_DIALOGS | 191 | if (task_dialog_indirect) |
| 192 | { | ||
| 193 | int wide_len; | ||
| 194 | |||
| 195 | CHECK_CONS (contents); | ||
| 196 | |||
| 197 | /* Get the title as an UTF-16 string. */ | ||
| 198 | char *title = SSDATA (ENCODE_UTF_8 (XCAR (contents))); | ||
| 199 | wide_len = (sizeof (WCHAR) | ||
| 200 | * pMultiByteToWideChar (CP_UTF8, 0, title, -1, NULL, 0)); | ||
| 201 | WCHAR *title_w = alloca (wide_len); | ||
| 202 | pMultiByteToWideChar (CP_UTF8, 0, title, -1, title_w, wide_len); | ||
| 203 | |||
| 204 | /* Prepare the arrays with the dialog's buttons and return values. */ | ||
| 205 | TASKDIALOG_BUTTON buttons[TASK_DIALOG_MAX_BUTTONS]; | ||
| 206 | Lisp_Object button_values[TASK_DIALOG_MAX_BUTTONS]; | ||
| 207 | int button_count = 0; | ||
| 208 | Lisp_Object b = XCDR (contents); | ||
| 209 | |||
| 210 | while (!NILP (b)) | ||
| 211 | { | ||
| 212 | if (button_count >= TASK_DIALOG_MAX_BUTTONS) | ||
| 213 | { | ||
| 214 | /* We have too many buttons. We ignore the rest. */ | ||
| 215 | break; | ||
| 216 | } | ||
| 217 | |||
| 218 | Lisp_Object item = XCAR (b); | ||
| 219 | |||
| 220 | if (CONSP (item)) | ||
| 221 | { | ||
| 222 | /* A normal item (text . value) */ | ||
| 223 | Lisp_Object item_name = XCAR (item); | ||
| 224 | Lisp_Object item_value = XCDR (item); | ||
| 225 | |||
| 226 | CHECK_STRING (item_name); | ||
| 227 | |||
| 228 | item_name = ENCODE_UTF_8 (item_name); | ||
| 229 | wide_len = (sizeof (WCHAR) | ||
| 230 | * pMultiByteToWideChar (CP_UTF8, 0, SSDATA (item_name), | ||
| 231 | -1, NULL, 0)); | ||
| 232 | buttons[button_count].pszButtonText = alloca (wide_len); | ||
| 233 | pMultiByteToWideChar (CP_UTF8, 0, SSDATA (item_name), -1, | ||
| 234 | (LPWSTR) | ||
| 235 | buttons[button_count].pszButtonText, | ||
| 236 | wide_len); | ||
| 237 | buttons[button_count].nButtonID = 1000 + button_count; | ||
| 238 | button_values[button_count++] = item_value; | ||
| 239 | } | ||
| 240 | else if (NILP (item)) | ||
| 241 | { | ||
| 242 | /* A nil item means to put all following items on the | ||
| 243 | right. We ignore this. */ | ||
| 244 | } | ||
| 245 | else if (STRINGP (item)) | ||
| 246 | { | ||
| 247 | /* A string item means an unselectable button. We add a | ||
| 248 | button, and then need to disable it on the callback. We | ||
| 249 | use ids based on 2000 to mark these buttons. */ | ||
| 250 | Lisp_Object item_name = ENCODE_UTF_8 (item); | ||
| 251 | wide_len = (sizeof (WCHAR) | ||
| 252 | * pMultiByteToWideChar (CP_UTF8, 0, | ||
| 253 | SSDATA (item_name), | ||
| 254 | -1, NULL, 0)); | ||
| 255 | buttons[button_count].pszButtonText = alloca (wide_len); | ||
| 256 | pMultiByteToWideChar (CP_UTF8, 0, SSDATA (item_name), -1, | ||
| 257 | (LPWSTR) | ||
| 258 | buttons[button_count].pszButtonText, | ||
| 259 | wide_len); | ||
| 260 | buttons[button_count].nButtonID = 2000 + button_count; | ||
| 261 | button_values[button_count++] = Qnil; | ||
| 262 | } | ||
| 263 | else | ||
| 264 | { | ||
| 265 | error ("Incorrect dialog button specification"); | ||
| 266 | return Qnil; | ||
| 267 | } | ||
| 268 | |||
| 269 | b = XCDR (b); | ||
| 270 | } | ||
| 271 | |||
| 272 | int pressed_button = 0; | ||
| 111 | 273 | ||
| 274 | TASKDIALOGCONFIG config = { 0 }; | ||
| 275 | config.hwndParent = FRAME_W32_WINDOW (f); | ||
| 276 | config.cbSize = sizeof (config); | ||
| 277 | config.hInstance = hinst; | ||
| 278 | config.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION; | ||
| 279 | config.pfCallback = task_dialog_callback; | ||
| 280 | config.pszWindowTitle = L"Question"; | ||
| 281 | if (!NILP (header)) | ||
| 282 | { | ||
| 283 | config.pszWindowTitle = L"Information"; | ||
| 284 | config.pszMainIcon = TD_INFORMATION_ICON; | ||
| 285 | } | ||
| 286 | |||
| 287 | config.pszMainInstruction = title_w; | ||
| 288 | config.pButtons = buttons; | ||
| 289 | config.cButtons = button_count; | ||
| 290 | |||
| 291 | if (!SUCCEEDED (task_dialog_indirect (&config, &pressed_button, | ||
| 292 | NULL, NULL))) | ||
| 293 | quit (); | ||
| 294 | |||
| 295 | int button_index; | ||
| 296 | switch (pressed_button) | ||
| 297 | { | ||
| 298 | case IDOK: | ||
| 299 | /* This can only happen if no buttons were provided. The OK | ||
| 300 | button is automatically added by TaskDialogIndirect in that | ||
| 301 | case. */ | ||
| 302 | return Qt; | ||
| 303 | case IDCANCEL: | ||
| 304 | /* The user closed the dialog without using the buttons. */ | ||
| 305 | return quit (); | ||
| 306 | default: | ||
| 307 | /* One of the specified buttons. */ | ||
| 308 | button_index = pressed_button - 1000; | ||
| 309 | if (button_index >= 0 && button_index < button_count) | ||
| 310 | return button_values[button_index]; | ||
| 311 | return quit (); | ||
| 312 | } | ||
| 313 | } | ||
| 314 | |||
| 315 | /* If we get here, TaskDialog is not supported. Use MessageBox/Menu. */ | ||
| 316 | |||
| 317 | #ifndef HAVE_DIALOGS | ||
| 112 | /* Handle simple Yes/No choices as MessageBox popups. */ | 318 | /* Handle simple Yes/No choices as MessageBox popups. */ |
| 113 | if (is_simple_dialog (contents)) | 319 | if (is_simple_dialog (contents)) |
| 114 | return simple_dialog_show (f, contents, header); | 320 | return simple_dialog_show (f, contents, header); |
| @@ -1618,6 +1824,10 @@ syms_of_w32menu (void) | |||
| 1618 | void | 1824 | void |
| 1619 | globals_of_w32menu (void) | 1825 | globals_of_w32menu (void) |
| 1620 | { | 1826 | { |
| 1827 | HMODULE comctrl32 = GetModuleHandle ("comctl32.dll"); | ||
| 1828 | task_dialog_indirect = (TaskDialogIndirect_Proc) | ||
| 1829 | get_proc_addr (comctrl32, "TaskDialogIndirect"); | ||
| 1830 | |||
| 1621 | #ifndef NTGUI_UNICODE | 1831 | #ifndef NTGUI_UNICODE |
| 1622 | /* See if Get/SetMenuItemInfo functions are available. */ | 1832 | /* See if Get/SetMenuItemInfo functions are available. */ |
| 1623 | HMODULE user32 = GetModuleHandle ("user32.dll"); | 1833 | HMODULE user32 = GetModuleHandle ("user32.dll"); |