Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

display.message_box() #2427

Merged
merged 25 commits into from
Oct 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion buildconfig/stubs/pygame/display.pyi
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from typing import Dict, List, Optional, Tuple, Union, overload
from typing import Dict, List, Optional, Tuple, Union, overload, Literal

from pygame.constants import FULLSCREEN
from pygame.surface import Surface

from pygame._sdl2 import Window

from ._common import (
ColorValue,
Coordinate,
Expand Down Expand Up @@ -87,3 +89,12 @@ def is_fullscreen() -> bool: ...
def is_vsync() -> bool: ...
def get_current_refresh_rate() -> int: ...
def get_desktop_refresh_rates() -> List[int]: ...
def message_box(
title: str,
message: Optional[str] = None,
message_type: Literal["info", "warn", "error"] = "info",
parent_window: Optional[Window] = None,
buttons: Sequence[str] = ("OK",),
return_button: int = 0,
escape_button: Optional[int] = None,
) -> int: ...
30 changes: 30 additions & 0 deletions docs/reST/ref/display.rst
Original file line number Diff line number Diff line change
Expand Up @@ -802,4 +802,34 @@ required).
.. versionadded:: 2.2.0
.. ## pygame.display.set_allow_screensaver ##

.. function:: message_box

| :sl:`Create a native GUI message box`
| :sg:`message_box(title, message=None, message_type='info', parent_window=None, buttons=('OK',), return_button=0, escape_button=None) -> int`

:param str title: A title string.
:param str message: A message string. If this parameter is set to ``None``, the message will be the title.
:param str message_type: Set the type of message_box, could be ``"info"``, ``"warn"`` or ``"error"``.
:param tuple buttons: An optional sequence of button name strings to show to the user.
:param int return_button: Button index to use if the return key is hit, ``0`` by default.
:param int escape_button: Button index to use if the escape key is hit, ``None`` for no button linked by default.
..
(Uncomment this after the window API is published)
:param Window parent_window: The parent window of the message_box
..

:return: The index of the button that was pushed.

This function should be called on the thread that ``set_mode()`` is called.
It will block execution of that thread until the user clicks a button or
closes the message_box.

This function may be called at any time, even before ``pygame.init()``.

yunline marked this conversation as resolved.
Show resolved Hide resolved
Negative values of ``return_button`` and ``escape_button`` are allowed
just like standard Python list indexing.

.. versionadded:: 2.4.0


.. ## pygame.display ##
205 changes: 204 additions & 1 deletion src_c/display.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ _display_state_cleanup(_DisplayState *state)
}
}

// prevent this code block from being linked twice
// (this code block is copied by window.c)
#ifndef BUILD_STATIC

#if !defined(__APPLE__)
static char *icon_defaultname = "pygame_icon.bmp";
static int icon_colorkey = 0;
Expand Down Expand Up @@ -176,6 +180,8 @@ pg_display_resource(char *filename)
return result;
}

#endif // BUILD_STATIC

/* init routines */
static PyObject *
pg_display_quit(PyObject *self, PyObject *_null)
Expand Down Expand Up @@ -2686,6 +2692,198 @@ pg_set_allow_screensaver(PyObject *self, PyObject *arg, PyObject *kwargs)
Py_RETURN_NONE;
}

static PyObject *
pg_message_box(PyObject *self, PyObject *arg, PyObject *kwargs)
{
const char *title = NULL;
PyObject *message = Py_None, *parent_window = Py_None;
const char *msgbox_type = "info";
PyObject *buttons = NULL;
PyObject *escape_button_index_obj = Py_None;

int return_button_index = 0;

static char *keywords[] = {"title", "message", "message_type",
"parent_window", "buttons", "return_button",
"escape_button", NULL};

if (!PyArg_ParseTupleAndKeywords(
arg, kwargs, "s|OsO!OiO", keywords, &title, &message, &msgbox_type,
&pgWindow_Type, &parent_window, &buttons, &return_button_index,
&escape_button_index_obj)) {
return NULL;
}

int escape_button_index = 0;
SDL_bool escape_button_used = SDL_FALSE;
if (escape_button_index_obj != Py_None) {
escape_button_index = PyLong_AsLong(escape_button_index_obj);
yunline marked this conversation as resolved.
Show resolved Hide resolved
if (escape_button_index == -1 && PyErr_Occurred())
return NULL;
escape_button_used = SDL_TRUE;
}

SDL_MessageBoxData msgbox_data;

msgbox_data.flags = 0;
if (!strcmp(msgbox_type, "info")) {
msgbox_data.flags |= SDL_MESSAGEBOX_INFORMATION;
}
else if (!strcmp(msgbox_type, "warn")) {
msgbox_data.flags |= SDL_MESSAGEBOX_WARNING;
}
else if (!strcmp(msgbox_type, "error")) {
msgbox_data.flags |= SDL_MESSAGEBOX_ERROR;
}
else {
PyErr_Format(PyExc_ValueError,
"type should be 'info', 'warn' or 'error', "
"got '%s'",
msgbox_type);
return NULL;
}

#if SDL_VERSION_ATLEAST(2, 0, 12)
msgbox_data.flags |= SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT;
#endif

if (parent_window == Py_None)
msgbox_data.window = NULL;
else
msgbox_data.window = ((pgWindowObject *)parent_window)->_win;

msgbox_data.colorScheme = NULL; // use system color scheme settings
ankith26 marked this conversation as resolved.
Show resolved Hide resolved

msgbox_data.title = title;
if (PyUnicode_Check(message)) {
msgbox_data.message = PyUnicode_AsUTF8(message);
if (!msgbox_data.message)
return NULL;
}
else if (message == Py_None) {
msgbox_data.message = title;
}
else {
PyErr_Format(PyExc_TypeError, "'message' must be str, not '%s'",
message->ob_type->tp_name);
return NULL;
}

SDL_MessageBoxButtonData *buttons_data = NULL;

if (buttons == NULL) {
buttons_data = malloc(sizeof(SDL_MessageBoxButtonData));
buttons_data->flags = 0;
buttons_data->buttonid = 0;
buttons_data->text = "OK";

msgbox_data.numbuttons = 1;

if (-1 > return_button_index || return_button_index >= 1) {
PyErr_SetString(PyExc_IndexError,
"return_button index out of range");
goto error;
}
buttons_data->flags |= SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT;

if (escape_button_used) {
if (-1 > escape_button_index || escape_button_index >= 1) {
PyErr_SetString(PyExc_IndexError,
"escape_button index out of range");
goto error;
}
buttons_data->flags |= SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT;
}
}
else {
if (!PySequence_Check(buttons) || PyUnicode_Check(buttons)) {
PyErr_Format(PyExc_TypeError,
"'buttons' should be a sequence of string, got '%s'",
buttons->ob_type->tp_name);
return NULL;
}
Py_ssize_t num_buttons = PySequence_Size(buttons);
msgbox_data.numbuttons = (int)num_buttons;
if (num_buttons < 0) {
return NULL;
}
else if (num_buttons == 0) {
return RAISE(PyExc_TypeError,
"'buttons' should contain at least 1 button");
}

if (return_button_index < 0) {
return_button_index = (int)num_buttons + return_button_index;
}
if (0 > return_button_index || return_button_index >= num_buttons) {
return RAISE(PyExc_IndexError, "return_button index out of range");
}
if (escape_button_used) {
if (escape_button_index < 0) {
escape_button_index = (int)num_buttons + escape_button_index;
}
if (0 > escape_button_index ||
escape_button_index >= num_buttons) {
return RAISE(PyExc_IndexError,
"escape_button index out of range");
}
}

buttons_data = malloc(sizeof(SDL_MessageBoxButtonData) * num_buttons);
for (Py_ssize_t i = 0; i < num_buttons; i++) {
#if SDL_VERSION_ATLEAST(2, 0, 12)
PyObject *btn_name_obj = PySequence_GetItem(buttons, i);
#else
PyObject *btn_name_obj =
PySequence_GetItem(buttons, num_buttons - i - 1);
#endif
if (!btn_name_obj)
goto error;

if (!PyUnicode_Check(btn_name_obj)) {
PyErr_SetString(PyExc_TypeError,
"'buttons' should be a sequence of string");
goto error;
}

const char *btn_name = PyUnicode_AsUTF8(btn_name_obj);
if (!btn_name)
goto error;

buttons_data[i].text = btn_name;
#if SDL_VERSION_ATLEAST(2, 0, 12)
buttons_data[i].buttonid = (int)i;
#else
buttons_data[i].buttonid = (int)(num_buttons - i - 1);
#endif
buttons_data[i].flags = 0;
if (return_button_index == buttons_data[i].buttonid)
buttons_data[i].flags |=
SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT;
if (escape_button_used &&
escape_button_index == buttons_data[i].buttonid)
buttons_data[i].flags |=
SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT;
}
}

msgbox_data.buttons = buttons_data;

int clicked_button_id;

if (SDL_ShowMessageBox(&msgbox_data, &clicked_button_id)) {
PyErr_SetString(pgExc_SDLError, SDL_GetError());
goto error;
}

free(buttons_data);
return PyLong_FromLong(clicked_button_id);

error:
free(buttons_data);
return NULL;
}

static PyMethodDef _pg_display_methods[] = {
{"init", (PyCFunction)pg_display_init, METH_NOARGS, DOC_DISPLAY_INIT},
{"quit", (PyCFunction)pg_display_quit, METH_NOARGS, DOC_DISPLAY_QUIT},
Expand Down Expand Up @@ -2757,7 +2955,8 @@ static PyMethodDef _pg_display_methods[] = {
METH_NOARGS, DOC_DISPLAY_GETALLOWSCREENSAVER},
{"set_allow_screensaver", (PyCFunction)pg_set_allow_screensaver,
METH_VARARGS | METH_KEYWORDS, DOC_DISPLAY_SETALLOWSCREENSAVER},

{"message_box", (PyCFunction)pg_message_box, METH_VARARGS | METH_KEYWORDS,
DOC_DISPLAY_MESSAGEBOX},
{NULL, NULL, 0, NULL}};

#ifndef PYPY_VERSION
Expand Down Expand Up @@ -2798,6 +2997,10 @@ MODINIT_DEFINE(display)
if (PyErr_Occurred()) {
return NULL;
}
import_pygame_window();
if (PyErr_Occurred()) {
return NULL;
}

/* type preparation */
if (PyType_Ready(&pgVidInfo_Type) < 0) {
Expand Down
1 change: 1 addition & 0 deletions src_c/doc/display_doc.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@
#define DOC_DISPLAY_ISVSYNC "is_vsync() -> bool\nReturns True if vertical synchronisation for pygame.display.flip() and pygame.display.update() is enabled"
#define DOC_DISPLAY_GETCURRENTREFRESHRATE "get_current_refresh_rate() -> int\nReturns the screen refresh rate or 0 if unknown"
#define DOC_DISPLAY_GETDESKTOPREFRESHRATES "get_desktop_refresh_rates() -> list\nReturns the screen refresh rates for all displays (in windowed mode)."
#define DOC_DISPLAY_MESSAGEBOX "message_box(title, message=None, message_type='info', parent_window=None, buttons=('OK',), return_button=0, escape_button=None) -> int\nCreate a native GUI message box"
9 changes: 7 additions & 2 deletions src_c/static.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ import_pygame_joystick(void)
{
}

void
import_pygame_window(void)
{
}

PyMODINIT_FUNC
PyInit_base(void);
PyMODINIT_FUNC
Expand Down Expand Up @@ -349,6 +354,8 @@ PyInit_pygame_static()
#include "simd_blitters_avx2.c"
#include "simd_blitters_sse2.c"

#include "window.c"

#undef pgVidInfo_Type
#undef pgVidInfo_New

Expand Down Expand Up @@ -422,5 +429,3 @@ PyInit_pygame_static()
#undef MAX
#undef MIN
#include "scale2x.c"

#include "window.c"
5 changes: 0 additions & 5 deletions src_c/window.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@

#include "doc/sdl2_video_doc.h"

// prevent that code block copied from display.c from being linked twice
#ifndef BUILD_STATIC

#if !defined(__APPLE__)
static char *icon_defaultname = "pygame_icon.bmp";
static int icon_colorkey = 0;
Expand Down Expand Up @@ -94,8 +91,6 @@ pg_display_resource(char *filename)
return result;
}

#endif // BUILD_STATIC

static PyTypeObject pgWindow_Type;

#define pgWindow_Check(x) \
Expand Down
Loading
Loading