用于Linux的SDL跨平台消息框

时间:2021-12-10 16:34:00

I am working on a cross platform game engine - which is working great (I am using SDL). However I want a simple way to display a message box to the user without having to rely on SDL or OpenGL (to render to the screen) e.g. what if the window is destroyed or hasn't been created yet so I cannot render a message to the screen?

我正在开发一个跨平台游戏引擎——它运行得很好(我正在使用SDL)。但是,我想要一种简单的方式来向用户显示消息框,而不必依赖SDL或OpenGL(向屏幕显示),例如,如果窗口被破坏或尚未创建,因此我无法向屏幕显示消息,该怎么办?

I have implemented a message box function with multiple implementations for each platform: the Windows implementation uses MessageBox, the Mac OS X implementation uses NSAlert from Cocoa and I don't know what I can use for the linux implementation. I was thinking X11 because that is what SDL uses for windowing on linux.

我已经为每个平台实现了一个消息框函数,其中包含多个实现:Windows实现使用MessageBox, Mac OS X实现使用来自Cocoa的NSAlert,我不知道linux实现可以使用什么。我想到X11是因为SDL在linux上使用的窗口。

I have tried other answers but they are either too vague or require me to re-rig my entire game engine with X11 or something. I am trying to find a solution that is independent of the application (like the windows MessageBox function which can be used in console applications).

我也尝试过其他的答案,但它们要么太模糊,要么要求我用X11之类的东西重新安装整个游戏引擎。我正在尝试寻找一个独立于应用程序的解决方案(比如可以在控制台应用程序中使用的windows MessageBox函数)。

NOTE: All of the code for the Mac and Windows implementation works fine it's just the Linux implementation I need help with.

注意:Mac和Windows实现的所有代码都运行良好,这只是我需要帮助的Linux实现。

Oh and when I compile on Mac OS X I take advantage of Objective-C++ so I can mix the Cocoa (Objective-C) with my C++ msgbox() function.

噢,当我在Mac OS X上编译时,我利用了Objective-C+,这样我就可以把Cocoa (Objective-C)和c++ msgbox()函数混合在一起了。

Here is the code I have so far for the Windows & Mac implementation:

下面是我到目前为止为Windows和Mac实现编写的代码:

msgbox.h

msgbox.h

#ifndef MSGBOX_H
#define MSGBOX_H

//Cross-platform message box method.
#include "platform.h"
#include "string.h"

//This is my own cross platform enum for message boxes.
//This enumeration 'overlaps' with some declarations in windows.h but that is fine.
enum    //Message box values.
{
    MB_OK,  //For OK message box and return value.
    MB_OKCANCEL,
    MB_YESNO,
    MB_RETRYCANCEL,
    MB_YESNOCANCEL,
    MB_ABORTRETRYIGNORE,
    MB_CANCELTRYCONTINUE,
    MB_CANCEL,
    MB_YES,
    MB_NO,
    MB_RETRY,
    MB_IGNORE,
    MB_TRYAGAIN,
    MB_CONTINUE,
    MB_ABORT,
};

//The message box function (multiple implementations for each platform).
int msgbox(string msg, string title, int buttons);

#endif // MSGBOX_H

msgbox.cpp

msgbox.cpp

#include "msgbox.h"

#if CURRENT_PLATFORM == PLATFORM_WINDOWS    //We can use the windows API for our messagebox.

#include <windows.h>    //For the message box function.
#define IDTRYAGAIN 10   //Some fixes to help this application compile.
#define IDCONTINUE 11

int msgbox(string msg, string title, int buttons)
{
    //Display the mesagebox.
    int retval = MessageBox(NULL, msg.c_str(), title.c_str(), buttons | MB_ICONEXCLAMATION | MB_SYSTEMMODAL);

    //Map the windows return value to ours.
    switch(retval)
    {
    case IDOK:      return MB_OK;
    case IDCANCEL:  return MB_CANCEL;
    case IDYES:     return MB_YES;
    case IDNO:      return MB_NO;
    case IDRETRY:   return MB_RETRY;
    case IDIGNORE:  return MB_IGNORE;
    case IDTRYAGAIN:return MB_TRYAGAIN;
    case IDCONTINUE:return MB_CONTINUE;
    }
}

#elif CURRENT_PLATFORM == PLATFORM_MACOSX   //Use Cocoa to display the message box.

int msgbox(string msg, string title, int buttons)
{
    NSString* defbutton = nil;
    NSString* altbutton = nil;
    NSString* otherbutton = nil;

    switch(buttons)
    {
    default:
    case MB_OK:
        defbutton = @"Ok";
        break;

    case MB_OKCANCEL:
        defbutton = @"Ok";
        altbutton = @"Cancel";
        break;

    case MB_RETRYCANCEL:
        defbutton = @"Retry";
        altbutton = @"Cancel";
        break;

    case MB_YESNO:
        defbutton = @"Yes";
        altbutton = @"No";
        break;

    case MB_YESNOCANCEL:
        defbutton = @"Yes";
        altbutton = @"No";
        otherbutton = @"Cancel";
        break;

    case MB_ABORTRETRYIGNORE:
        defbutton = @"Abort";
        altbutton = @"Retry";
        otherbutton = @"Ignore";
        break;

    case MB_CANCELTRYCONTINUE:
        defbutton = @"Cancel";
        altbutton = @"Try Again";
        otherbutton = @"Continue";
        break;
    }

    NSAlert* alert = [NSAlert alertWithMessageText:[NSString     stringWithCString:title.c_str() encoding:[NSString defaultCStringEncoding]]
                                 defaultButton:defbutton
                               alternateButton:altbutton
                                   otherButton:otherbutton
                     informativeTextWithFormat:@"%s", msg.c_str()];

    //brings this 'application' to the front.
    [[NSRunningApplication currentApplication]     activateWithOptions:NSApplicationActivateIgnoringOtherApps];
    NSInteger retval = [alert runModal];

    //Convert the NSAlert return values into my MB_* return values.
    if(retval == NSAlertDefaultReturn)
    {
        switch(buttons)
        {
        case MB_OK:
        case MB_OKCANCEL:
            return MB_OK;

        case MB_YESNO:
        case MB_YESNOCANCEL:
            return MB_YES;

        case MB_ABORTRETRYIGNORE:
            return MB_ABORT;

        case MB_CANCELTRYCONTINUE:
            return MB_CANCEL;

        case MB_RETRYCANCEL:
            return MB_RETRY;
        }
    } else if(retval == NSAlertAlternateReturn)
    {
        switch(buttons)
        {
        case MB_OKCANCEL:
        case MB_RETRYCANCEL:
            return MB_CANCEL;

        case MB_YESNO:
        case MB_YESNOCANCEL:
            return MB_NO;

        case MB_ABORTRETRYIGNORE:
            return MB_RETRY;

        case MB_CANCELTRYCONTINUE:
            return MB_TRYAGAIN;
        }
    } else if(retval == NSAlertOtherReturn)
    {
        switch(buttons)
        {
        case MB_YESNOCANCEL:
            return MB_CANCEL;

        case MB_ABORTRETRYIGNORE:
            return MB_IGNORE;

        case MB_CANCELTRYCONTINUE:
            return MB_CONTINUE;
        }
    }

    return NULL;
}

#else

int msgbox(string msg, string title, int buttons)
{
    //WHAT DO I DO??????
    return 0;
}

//#error No implementation of message boxes on current platform!
#endif // CURRENT_PLATFORM

EDIT: I prefer not to use Qt for a few reasons: it is too heavy, it doesn't work on my main computer & it doesn't give me enough control over the program. Anyway I am trying to make this game engine from scratch as a hobby project without relying on other libraries (I am going to replace SDL eventually with my own code).

编辑:我不喜欢使用Qt有几个原因:它太重了,不能在我的主计算机上工作,它不能给我足够的控制程序。无论如何,我正在尝试将这个游戏引擎作为一个业余项目,而不依赖于其他库(我最终将用我自己的代码替换SDL)。

2 个解决方案

#1


2  

I created a simple wrapper function that uses SDL_ShowMessageBox from SDL 2.0 and it replaces the previous code I submitted and it works on Linux, Mac & Windows.

我创建了一个简单的包装函数,使用SDL 2.0中的SDL_ShowMessageBox,它替换了我之前提交的代码,并在Linux、Mac和Windows上运行。

SDL 2.0 can be found at (http://www.libsdl.org/tmp/download-2.0.php).

SDL 2.0可以在(http://www.libsdl.org/tmp/download-2.0.php)找到。

You have to build SDL 2 yourself on Linux - just download the source code in the page provided then extract the archive and follow the install instructions in INSTALL.txt (after you build SDL 2 the libraries are put in the /usr/local/lib folder - you may need to move them or tell your linker where they are (the include files are in the include directory).

您必须自己在Linux上构建sdl2—只需在提供的页面中下载源代码,然后提取存档并遵循安装中的安装说明。txt(构建SDL 2之后,库被放在/usr/local/lib文件夹中——您可能需要移动它们,或者告诉链接器它们在哪里(包含文件在include目录中)。

Here is the code:

这是代码:

Example (using my function):

示例(使用函数):

int i = showMessageBox(mySDLWindow, "Message", "Title", 3, MB_BUTTONS("BUTTON 1", "BUTTON 2", "BUTTON 3"), 0);
 printf("Button %i was pressed", i + 1);

messagebox.h:

messagebox.h:

//Cross-platform message box method.
#include <string>
#include <SDL/SDL.h> //SDL 2.0 header file

//Helper macro
#define MB_BUTTONS(...) ((char*[]) {__VA_ARGS__})

//Flexible message box function.
//Returns the index of button pressed on success or a negative value on a failure.
//The parent argument can be set to NULL if not available.
int showMessageBox(SDL_Window *parent, std::string msg, std::string title,
                     int count, char* buttons[], int defbutton = 0);

messagebox.cpp:

messagebox.cpp:

//Complex function
int showMessageBox(SDL_Window *parent, string msg, string title,
                     int count, char* buttons[], int defbutton)
{
    //Variables.
    int resultButton = 0;
    SDL_MessageBoxData mbdata;

    //Set the message box information.
    mbdata.flags = SDL_MESSAGEBOX_INFORMATION;
    mbdata.message = msg.c_str();
    mbdata.title = title.c_str();
    mbdata.colorScheme = NULL;
    mbdata.window = parent;
    mbdata.numbuttons = count;

    //Allocate buttons.
    SDL_MessageBoxButtonData *butarray = new SDL_MessageBoxButtonData[mbdata.numbuttons];

    //Set the button values.
    for(unsigned char i = 0; i < mbdata.numbuttons; i++)
    {
        //Is this button the default button?
        if(i == defbutton)
        {
            butarray[i].flags = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT;
        } else
        {
            butarray[i].flags = 0;
        }

        //Set button text.
        if(buttons[i] != NULL)
        {
            butarray[i].text = buttons[i];
        }

        //Set button ID.
        butarray[i].buttonid = i;
    }

    //Set the message box data's button array.
    mbdata.buttons = butarray;

    //Display the message box.
    int retval = SDL_ShowMessageBox(&mbdata, &resultButton);

    //Deallocate the buttons array to prevent memory leaks.
    delete[] butarray;        

    //Return the result (-1 on failure or if the dialog was closed).
    return retval < 0 ? -1 : resultButton;
}

#2


1  

I'm using gdialog/kdialog and pass the message on the command line. Here's the code:

我正在使用gdialog/kdialog并在命令行上传递消息。这是代码:

#include <cstdlib>
#include <string>

const char * getDialogCommand() {
  if (::system(NULL)) {
    if (::system("which gdialog") == 0)
      return "gdialog";
    else if (::system("which kdialog") == 0)
      return "kdialog";
  }
  return NULL;
}

void showWarning(const std::string & warning) {
  const char * dialogCommand = getDialogCommand();
  if (dialogCommand) {
    std::string command = dialogCommand;
    command += " --title \"Message Box Title\" --msgbox \"" + warning + "\"";
    int result = ::system(command.c_str());
    if (result == 0)
      return; // success
  }

  // fail-safe method here, using stdio perhaps, depends on your application
}

This isn't the most robust code in world, but at least in our games, I've never seen it fail. Code-wise it's dependency free, however you have to make sure that you don't use characters in the string which screw up the command line, i.e. escape characters like <, >, &, !\, and everything non-ASCII.

这不是世界上最健壮的代码,但至少在我们的游戏中,我从未见过它失败。从代码上讲,它是无依赖性的,但是您必须确保在导致命令行出错的字符串中不使用字符,例如转义字符<、>和!\,以及所有非ascii字符。

Please also note that SDL 2.0 has SDL_ShowMessageBox.

还请注意,SDL 2.0有SDL_ShowMessageBox。

#1


2  

I created a simple wrapper function that uses SDL_ShowMessageBox from SDL 2.0 and it replaces the previous code I submitted and it works on Linux, Mac & Windows.

我创建了一个简单的包装函数,使用SDL 2.0中的SDL_ShowMessageBox,它替换了我之前提交的代码,并在Linux、Mac和Windows上运行。

SDL 2.0 can be found at (http://www.libsdl.org/tmp/download-2.0.php).

SDL 2.0可以在(http://www.libsdl.org/tmp/download-2.0.php)找到。

You have to build SDL 2 yourself on Linux - just download the source code in the page provided then extract the archive and follow the install instructions in INSTALL.txt (after you build SDL 2 the libraries are put in the /usr/local/lib folder - you may need to move them or tell your linker where they are (the include files are in the include directory).

您必须自己在Linux上构建sdl2—只需在提供的页面中下载源代码,然后提取存档并遵循安装中的安装说明。txt(构建SDL 2之后,库被放在/usr/local/lib文件夹中——您可能需要移动它们,或者告诉链接器它们在哪里(包含文件在include目录中)。

Here is the code:

这是代码:

Example (using my function):

示例(使用函数):

int i = showMessageBox(mySDLWindow, "Message", "Title", 3, MB_BUTTONS("BUTTON 1", "BUTTON 2", "BUTTON 3"), 0);
 printf("Button %i was pressed", i + 1);

messagebox.h:

messagebox.h:

//Cross-platform message box method.
#include <string>
#include <SDL/SDL.h> //SDL 2.0 header file

//Helper macro
#define MB_BUTTONS(...) ((char*[]) {__VA_ARGS__})

//Flexible message box function.
//Returns the index of button pressed on success or a negative value on a failure.
//The parent argument can be set to NULL if not available.
int showMessageBox(SDL_Window *parent, std::string msg, std::string title,
                     int count, char* buttons[], int defbutton = 0);

messagebox.cpp:

messagebox.cpp:

//Complex function
int showMessageBox(SDL_Window *parent, string msg, string title,
                     int count, char* buttons[], int defbutton)
{
    //Variables.
    int resultButton = 0;
    SDL_MessageBoxData mbdata;

    //Set the message box information.
    mbdata.flags = SDL_MESSAGEBOX_INFORMATION;
    mbdata.message = msg.c_str();
    mbdata.title = title.c_str();
    mbdata.colorScheme = NULL;
    mbdata.window = parent;
    mbdata.numbuttons = count;

    //Allocate buttons.
    SDL_MessageBoxButtonData *butarray = new SDL_MessageBoxButtonData[mbdata.numbuttons];

    //Set the button values.
    for(unsigned char i = 0; i < mbdata.numbuttons; i++)
    {
        //Is this button the default button?
        if(i == defbutton)
        {
            butarray[i].flags = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT;
        } else
        {
            butarray[i].flags = 0;
        }

        //Set button text.
        if(buttons[i] != NULL)
        {
            butarray[i].text = buttons[i];
        }

        //Set button ID.
        butarray[i].buttonid = i;
    }

    //Set the message box data's button array.
    mbdata.buttons = butarray;

    //Display the message box.
    int retval = SDL_ShowMessageBox(&mbdata, &resultButton);

    //Deallocate the buttons array to prevent memory leaks.
    delete[] butarray;        

    //Return the result (-1 on failure or if the dialog was closed).
    return retval < 0 ? -1 : resultButton;
}

#2


1  

I'm using gdialog/kdialog and pass the message on the command line. Here's the code:

我正在使用gdialog/kdialog并在命令行上传递消息。这是代码:

#include <cstdlib>
#include <string>

const char * getDialogCommand() {
  if (::system(NULL)) {
    if (::system("which gdialog") == 0)
      return "gdialog";
    else if (::system("which kdialog") == 0)
      return "kdialog";
  }
  return NULL;
}

void showWarning(const std::string & warning) {
  const char * dialogCommand = getDialogCommand();
  if (dialogCommand) {
    std::string command = dialogCommand;
    command += " --title \"Message Box Title\" --msgbox \"" + warning + "\"";
    int result = ::system(command.c_str());
    if (result == 0)
      return; // success
  }

  // fail-safe method here, using stdio perhaps, depends on your application
}

This isn't the most robust code in world, but at least in our games, I've never seen it fail. Code-wise it's dependency free, however you have to make sure that you don't use characters in the string which screw up the command line, i.e. escape characters like <, >, &, !\, and everything non-ASCII.

这不是世界上最健壮的代码,但至少在我们的游戏中,我从未见过它失败。从代码上讲,它是无依赖性的,但是您必须确保在导致命令行出错的字符串中不使用字符,例如转义字符<、>和!\,以及所有非ascii字符。

Please also note that SDL 2.0 has SDL_ShowMessageBox.

还请注意,SDL 2.0有SDL_ShowMessageBox。