mirror of
https://git.savannah.gnu.org/git/emacs.git
synced 2024-11-22 07:09:54 +00:00
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)
This commit is contained in:
parent
db1eb8a282
commit
aa7dee58d8
@ -1594,9 +1594,10 @@ for instance using the window manager, then this produces a quit and
|
||||
Lisp_Object selection
|
||||
= FRAME_TERMINAL (f)->popup_dialog_hook (f, header, contents);
|
||||
#ifdef HAVE_NTGUI
|
||||
/* NTGUI supports only simple dialogs with Yes/No choices. For
|
||||
other dialogs, it returns the symbol 'unsupported--w32-dialog',
|
||||
as a signal for the caller to fall back to the emulation code. */
|
||||
/* NTGUI on Windows versions before Vista supports only simple
|
||||
dialogs with Yes/No choices. For other dialogs, it returns the
|
||||
symbol 'unsupported--w32-dialog', as a signal for the caller to
|
||||
fall back to the emulation code. */
|
||||
if (!EQ (selection, Qunsupported__w32_dialog))
|
||||
#endif
|
||||
return selection;
|
||||
|
214
src/w32menu.c
214
src/w32menu.c
@ -52,6 +52,9 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
|
||||
|
||||
#include "w32common.h" /* for osinfo_cache */
|
||||
|
||||
#include "commctrl.h"
|
||||
|
||||
/* This only applies to OS versions prior to Vista. */
|
||||
#undef HAVE_DIALOGS /* TODO: Implement native dialogs. */
|
||||
|
||||
#ifndef TRUE
|
||||
@ -77,6 +80,66 @@ typedef int (WINAPI * MessageBoxW_Proc) (
|
||||
IN const WCHAR *caption,
|
||||
IN UINT type);
|
||||
|
||||
#ifndef MINGW_W64
|
||||
/* mingw.org's MinGW doesn't have this in its header files. */
|
||||
typedef int TASKDIALOG_COMMON_BUTTON_FLAGS;
|
||||
|
||||
typedef int TASKDIALOG_FLAGS;
|
||||
|
||||
typedef HRESULT (CALLBACK *PFTASKDIALOGCALLBACK) (
|
||||
HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LONG_PTR lpRefData);
|
||||
|
||||
typedef struct _TASKDIALOG_BUTTON {
|
||||
int nButtonID;
|
||||
PCWSTR pszButtonText;
|
||||
} TASKDIALOG_BUTTON;
|
||||
|
||||
typedef struct _TASKDIALOGCONFIG {
|
||||
UINT cbSize;
|
||||
HWND hwndParent;
|
||||
HINSTANCE hInstance;
|
||||
TASKDIALOG_FLAGS dwFlags;
|
||||
TASKDIALOG_COMMON_BUTTON_FLAGS dwCommonButtons;
|
||||
PCWSTR pszWindowTitle;
|
||||
union {
|
||||
HICON hMainIcon;
|
||||
PCWSTR pszMainIcon;
|
||||
} DUMMYUNIONNAME;
|
||||
PCWSTR pszMainInstruction;
|
||||
PCWSTR pszContent;
|
||||
UINT cButtons;
|
||||
const TASKDIALOG_BUTTON *pButtons;
|
||||
int nDefaultButton;
|
||||
UINT cRadioButtons;
|
||||
const TASKDIALOG_BUTTON *pRadioButtons;
|
||||
int nDefaultRadioButton;
|
||||
PCWSTR pszVerificationText;
|
||||
PCWSTR pszExpandedInformation;
|
||||
PCWSTR pszExpandedControlText;
|
||||
PCWSTR pszCollapsedControlText;
|
||||
union {
|
||||
HICON hFooterIcon;
|
||||
PCWSTR pszFooterIcon;
|
||||
} DUMMYUNIONNAME2;
|
||||
PCWSTR pszFooter;
|
||||
PFTASKDIALOGCALLBACK pfCallback;
|
||||
LONG_PTR lpCallbackData;
|
||||
UINT cxWidth;
|
||||
} TASKDIALOGCONFIG;
|
||||
|
||||
# define TDN_CREATED 0
|
||||
# define TDM_ENABLE_BUTTON (WM_USER+111)
|
||||
# define TDF_ALLOW_DIALOG_CANCELLATION 0x8
|
||||
# define TD_INFORMATION_ICON MAKEINTRESOURCEW (-3)
|
||||
|
||||
#endif
|
||||
|
||||
typedef HRESULT (WINAPI *TaskDialogIndirect_Proc) (
|
||||
IN const TASKDIALOGCONFIG *pTaskConfig,
|
||||
OUT int *pnButton,
|
||||
OUT int *pnRadioButton,
|
||||
OUT BOOL *pfVerificationFlagChecked);
|
||||
|
||||
#ifdef NTGUI_UNICODE
|
||||
GetMenuItemInfoA_Proc get_menu_item_info = GetMenuItemInfoA;
|
||||
SetMenuItemInfoA_Proc set_menu_item_info = SetMenuItemInfoA;
|
||||
@ -89,6 +152,8 @@ AppendMenuW_Proc unicode_append_menu = NULL;
|
||||
MessageBoxW_Proc unicode_message_box = NULL;
|
||||
#endif /* NTGUI_UNICODE */
|
||||
|
||||
static TaskDialogIndirect_Proc task_dialog_indirect;
|
||||
|
||||
#ifdef HAVE_DIALOGS
|
||||
static Lisp_Object w32_dialog_show (struct frame *, Lisp_Object, Lisp_Object, char **);
|
||||
#else
|
||||
@ -101,14 +166,155 @@ static int fill_in_menu (HMENU, widget_value *);
|
||||
|
||||
void w32_free_menu_strings (HWND);
|
||||
|
||||
#define TASK_DIALOG_MAX_BUTTONS 10
|
||||
|
||||
static HRESULT CALLBACK
|
||||
task_dialog_callback (HWND hwnd, UINT msg, WPARAM wParam,
|
||||
LPARAM lParam, LONG_PTR callback_data)
|
||||
{
|
||||
switch (msg)
|
||||
{
|
||||
case TDN_CREATED:
|
||||
/* Disable all buttons with ID >= 2000 */
|
||||
for (int i = 0; i < TASK_DIALOG_MAX_BUTTONS; i++)
|
||||
SendMessage (hwnd, TDM_ENABLE_BUTTON, 2000 + i, FALSE);
|
||||
break;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
Lisp_Object
|
||||
w32_popup_dialog (struct frame *f, Lisp_Object header, Lisp_Object contents)
|
||||
{
|
||||
|
||||
check_window_system (f);
|
||||
|
||||
#ifndef HAVE_DIALOGS
|
||||
if (task_dialog_indirect)
|
||||
{
|
||||
int wide_len;
|
||||
|
||||
CHECK_CONS (contents);
|
||||
|
||||
/* Get the title as an UTF-16 string. */
|
||||
char *title = SSDATA (ENCODE_UTF_8 (XCAR (contents)));
|
||||
wide_len = (sizeof (WCHAR)
|
||||
* pMultiByteToWideChar (CP_UTF8, 0, title, -1, NULL, 0));
|
||||
WCHAR *title_w = alloca (wide_len);
|
||||
pMultiByteToWideChar (CP_UTF8, 0, title, -1, title_w, wide_len);
|
||||
|
||||
/* Prepare the arrays with the dialog's buttons and return values. */
|
||||
TASKDIALOG_BUTTON buttons[TASK_DIALOG_MAX_BUTTONS];
|
||||
Lisp_Object button_values[TASK_DIALOG_MAX_BUTTONS];
|
||||
int button_count = 0;
|
||||
Lisp_Object b = XCDR (contents);
|
||||
|
||||
while (!NILP (b))
|
||||
{
|
||||
if (button_count >= TASK_DIALOG_MAX_BUTTONS)
|
||||
{
|
||||
/* We have too many buttons. We ignore the rest. */
|
||||
break;
|
||||
}
|
||||
|
||||
Lisp_Object item = XCAR (b);
|
||||
|
||||
if (CONSP (item))
|
||||
{
|
||||
/* A normal item (text . value) */
|
||||
Lisp_Object item_name = XCAR (item);
|
||||
Lisp_Object item_value = XCDR (item);
|
||||
|
||||
CHECK_STRING (item_name);
|
||||
|
||||
item_name = ENCODE_UTF_8 (item_name);
|
||||
wide_len = (sizeof (WCHAR)
|
||||
* pMultiByteToWideChar (CP_UTF8, 0, SSDATA (item_name),
|
||||
-1, NULL, 0));
|
||||
buttons[button_count].pszButtonText = alloca (wide_len);
|
||||
pMultiByteToWideChar (CP_UTF8, 0, SSDATA (item_name), -1,
|
||||
(LPWSTR)
|
||||
buttons[button_count].pszButtonText,
|
||||
wide_len);
|
||||
buttons[button_count].nButtonID = 1000 + button_count;
|
||||
button_values[button_count++] = item_value;
|
||||
}
|
||||
else if (NILP (item))
|
||||
{
|
||||
/* A nil item means to put all following items on the
|
||||
right. We ignore this. */
|
||||
}
|
||||
else if (STRINGP (item))
|
||||
{
|
||||
/* A string item means an unselectable button. We add a
|
||||
button, and then need to disable it on the callback. We
|
||||
use ids based on 2000 to mark these buttons. */
|
||||
Lisp_Object item_name = ENCODE_UTF_8 (item);
|
||||
wide_len = (sizeof (WCHAR)
|
||||
* pMultiByteToWideChar (CP_UTF8, 0,
|
||||
SSDATA (item_name),
|
||||
-1, NULL, 0));
|
||||
buttons[button_count].pszButtonText = alloca (wide_len);
|
||||
pMultiByteToWideChar (CP_UTF8, 0, SSDATA (item_name), -1,
|
||||
(LPWSTR)
|
||||
buttons[button_count].pszButtonText,
|
||||
wide_len);
|
||||
buttons[button_count].nButtonID = 2000 + button_count;
|
||||
button_values[button_count++] = Qnil;
|
||||
}
|
||||
else
|
||||
{
|
||||
error ("Incorrect dialog button specification");
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
b = XCDR (b);
|
||||
}
|
||||
|
||||
int pressed_button = 0;
|
||||
|
||||
TASKDIALOGCONFIG config = { 0 };
|
||||
config.hwndParent = FRAME_W32_WINDOW (f);
|
||||
config.cbSize = sizeof (config);
|
||||
config.hInstance = hinst;
|
||||
config.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION;
|
||||
config.pfCallback = task_dialog_callback;
|
||||
config.pszWindowTitle = L"Question";
|
||||
if (!NILP (header))
|
||||
{
|
||||
config.pszWindowTitle = L"Information";
|
||||
config.pszMainIcon = TD_INFORMATION_ICON;
|
||||
}
|
||||
|
||||
config.pszMainInstruction = title_w;
|
||||
config.pButtons = buttons;
|
||||
config.cButtons = button_count;
|
||||
|
||||
if (!SUCCEEDED (task_dialog_indirect (&config, &pressed_button,
|
||||
NULL, NULL)))
|
||||
quit ();
|
||||
|
||||
int button_index;
|
||||
switch (pressed_button)
|
||||
{
|
||||
case IDOK:
|
||||
/* This can only happen if no buttons were provided. The OK
|
||||
button is automatically added by TaskDialogIndirect in that
|
||||
case. */
|
||||
return Qt;
|
||||
case IDCANCEL:
|
||||
/* The user closed the dialog without using the buttons. */
|
||||
return quit ();
|
||||
default:
|
||||
/* One of the specified buttons. */
|
||||
button_index = pressed_button - 1000;
|
||||
if (button_index >= 0 && button_index < button_count)
|
||||
return button_values[button_index];
|
||||
return quit ();
|
||||
}
|
||||
}
|
||||
|
||||
/* If we get here, TaskDialog is not supported. Use MessageBox/Menu. */
|
||||
|
||||
#ifndef HAVE_DIALOGS
|
||||
/* Handle simple Yes/No choices as MessageBox popups. */
|
||||
if (is_simple_dialog (contents))
|
||||
return simple_dialog_show (f, contents, header);
|
||||
@ -1618,6 +1824,10 @@ syms_of_w32menu (void)
|
||||
void
|
||||
globals_of_w32menu (void)
|
||||
{
|
||||
HMODULE comctrl32 = GetModuleHandle ("comctl32.dll");
|
||||
task_dialog_indirect = (TaskDialogIndirect_Proc)
|
||||
get_proc_addr (comctrl32, "TaskDialogIndirect");
|
||||
|
||||
#ifndef NTGUI_UNICODE
|
||||
/* See if Get/SetMenuItemInfo functions are available. */
|
||||
HMODULE user32 = GetModuleHandle ("user32.dll");
|
||||
|
Loading…
Reference in New Issue
Block a user