aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorCecilio Pardo2024-09-11 15:44:28 +0200
committerEli Zaretskii2024-09-14 13:57:26 +0300
commitaa7dee58d81817285208471074f1af598ebf0c98 (patch)
tree2ff7a33af0eebdd12d1890eaf205ef4a84838e1d /src
parentdb1eb8a282c1832fd34be049e80dcb1a3b59ade2 (diff)
downloademacs-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')
-rw-r--r--src/menu.c7
-rw-r--r--src/w32menu.c214
2 files changed, 216 insertions, 5 deletions
diff --git a/src/menu.c b/src/menu.c
index de4d0964e9c..6b4aaef1715 100644
--- a/src/menu.c
+++ b/src/menu.c
@@ -1594,9 +1594,10 @@ for instance using the window manager, then this produces a quit and
1594 Lisp_Object selection 1594 Lisp_Object selection
1595 = FRAME_TERMINAL (f)->popup_dialog_hook (f, header, contents); 1595 = FRAME_TERMINAL (f)->popup_dialog_hook (f, header, contents);
1596#ifdef HAVE_NTGUI 1596#ifdef HAVE_NTGUI
1597 /* NTGUI supports only simple dialogs with Yes/No choices. For 1597 /* NTGUI on Windows versions before Vista supports only simple
1598 other dialogs, it returns the symbol 'unsupported--w32-dialog', 1598 dialogs with Yes/No choices. For other dialogs, it returns the
1599 as a signal for the caller to fall back to the emulation code. */ 1599 symbol 'unsupported--w32-dialog', as a signal for the caller to
1600 fall back to the emulation code. */
1600 if (!EQ (selection, Qunsupported__w32_dialog)) 1601 if (!EQ (selection, Qunsupported__w32_dialog))
1601#endif 1602#endif
1602 return selection; 1603 return selection;
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
137typedef 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
81GetMenuItemInfoA_Proc get_menu_item_info = GetMenuItemInfoA; 144GetMenuItemInfoA_Proc get_menu_item_info = GetMenuItemInfoA;
82SetMenuItemInfoA_Proc set_menu_item_info = SetMenuItemInfoA; 145SetMenuItemInfoA_Proc set_menu_item_info = SetMenuItemInfoA;
@@ -89,6 +152,8 @@ AppendMenuW_Proc unicode_append_menu = NULL;
89MessageBoxW_Proc unicode_message_box = NULL; 152MessageBoxW_Proc unicode_message_box = NULL;
90#endif /* NTGUI_UNICODE */ 153#endif /* NTGUI_UNICODE */
91 154
155static TaskDialogIndirect_Proc task_dialog_indirect;
156
92#ifdef HAVE_DIALOGS 157#ifdef HAVE_DIALOGS
93static Lisp_Object w32_dialog_show (struct frame *, Lisp_Object, Lisp_Object, char **); 158static 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
102void w32_free_menu_strings (HWND); 167void w32_free_menu_strings (HWND);
103 168
169#define TASK_DIALOG_MAX_BUTTONS 10
170
171static HRESULT CALLBACK
172task_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
104Lisp_Object 186Lisp_Object
105w32_popup_dialog (struct frame *f, Lisp_Object header, Lisp_Object contents) 187w32_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)
1618void 1824void
1619globals_of_w32menu (void) 1825globals_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");