├── .editorconfig ├── examples ├── CMakeLists.txt └── example.cpp ├── .gitignore ├── LICENSE ├── CMakeLists.txt ├── README.md ├── src ├── boxer_win.cpp ├── boxer_linux.cpp └── boxer_mac.mm └── include └── boxer └── boxer.h /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 3 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project definition 2 | cmake_minimum_required(VERSION 3.10) 3 | project(Boxer-Examples VERSION 1.0.0 LANGUAGES CXX) 4 | 5 | # Executable definition and features 6 | add_executable(${PROJECT_NAME} "example.cpp") 7 | target_link_libraries(${PROJECT_NAME} PRIVATE Boxer) 8 | if (WIN32) 9 | target_compile_definitions(Boxer PUBLIC UNICODE) 10 | endif (WIN32) 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | 30 | # Built files 31 | build/ 32 | -------------------------------------------------------------------------------- /examples/example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char* argv[]) 4 | { 5 | boxer::show("Simple message boxes are very easy to create.", "Simple Example"); 6 | 7 | boxer::show(u8"Boxer accepts UTF-8 strings. 💯", u8"Unicode 👍"); 8 | 9 | boxer::show("There are a few different message box styles to choose from.", "Style Example", boxer::Style::Error); 10 | 11 | boxer::Selection selection = boxer::Selection::Yes; 12 | while (selection == boxer::Selection::Yes) 13 | { 14 | selection = boxer::show("Different buttons may be used, and the user's selection can be checked. Would you like to see this message again?", "Selection Example", boxer::Style::Question, boxer::Buttons::YesNo); 15 | } 16 | 17 | return 0; 18 | } 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Aaron Jacobs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project definition 2 | cmake_minimum_required(VERSION 3.10) 3 | project(Boxer VERSION 1.0.0 LANGUAGES CXX) 4 | 5 | set(LINUX FALSE) 6 | if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") 7 | set(LINUX TRUE) 8 | endif() 9 | 10 | # Source files 11 | set(SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src") 12 | set(INC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include") 13 | set(SOURCES "${INC_DIR}/boxer/boxer.h") 14 | if (APPLE) 15 | list(APPEND SOURCES 16 | "${SRC_DIR}/boxer_mac.mm" 17 | ) 18 | elseif (WIN32) 19 | list(APPEND SOURCES 20 | "${SRC_DIR}/boxer_win.cpp" 21 | ) 22 | elseif (LINUX) 23 | list(APPEND SOURCES 24 | "${SRC_DIR}/boxer_linux.cpp" 25 | ) 26 | endif () 27 | 28 | # Library definition and features 29 | add_library(${PROJECT_NAME} ${SOURCES}) 30 | target_include_directories(${PROJECT_NAME} PUBLIC "${INC_DIR}") 31 | target_compile_features(${PROJECT_NAME} PUBLIC cxx_strong_enums cxx_nullptr) 32 | 33 | # Platform-specific dependencies 34 | if (APPLE) 35 | find_library(COCOA_LIBRARY Cocoa) 36 | target_link_libraries(${PROJECT_NAME} PUBLIC ${COCOA_LIBRARY}) 37 | elseif (LINUX) 38 | find_package(PkgConfig REQUIRED) 39 | pkg_check_modules(GTK3 REQUIRED gtk+-3.0) 40 | target_link_libraries(${PROJECT_NAME} PUBLIC ${GTK3_LIBRARIES}) 41 | target_include_directories(${PROJECT_NAME} PRIVATE ${GTK3_INCLUDE_DIRS}) 42 | endif () 43 | 44 | # Symbol exporting 45 | if (BUILD_SHARED_LIBS) 46 | target_compile_definitions(${PROJECT_NAME} PRIVATE "BOXER_BUILD_DLL") 47 | target_compile_definitions(${PROJECT_NAME} INTERFACE "BOXER_DLL") 48 | endif () 49 | 50 | # Build options 51 | option(BOXER_BUILD_EXAMPLES "Build example programs" OFF) 52 | if (BOXER_BUILD_EXAMPLES) 53 | add_subdirectory("examples") 54 | endif() 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Boxer 🥊 2 | 3 | ## Introduction 4 | 5 | Boxer is a simple library that allows for easy cross-platform creation of message boxes / alerts / what have you. 6 | 7 | ## Example 8 | 9 | macOS: 10 | 11 | ![macOS](https://user-images.githubusercontent.com/1409522/213894782-72c37b24-bdb3-4b29-a847-cbff7748b1fe.png) 12 | 13 | Windows: 14 | 15 | ![Windows](https://user-images.githubusercontent.com/1409522/213894790-55cf2be8-bcc0-4867-95e0-7741993f07eb.png) 16 | 17 | Linux: 18 | 19 | ![Linux](https://user-images.githubusercontent.com/1409522/213894798-1bb1c279-5190-4108-b49c-08a28c7dfc29.png) 20 | 21 | ## Language 22 | 23 | Boxer is written in C++, though it has a C branch available as well. 24 | 25 | ## Compiling Boxer 26 | 27 | Boxer is set up to be built with CMake. 28 | 29 | To generate a static library, execute CMake with the root of the repo as the source directory. Additionally, the example program can be built by enabling the BOXER_BUILD_EXAMPLES option. 30 | 31 | On Linux, Boxer requires the gtk+-3.0 package. 32 | 33 | ## Including Boxer 34 | 35 | Wherever you want to use Boxer, just include the header: 36 | 37 | ```c++ 38 | #include 39 | ``` 40 | 41 | ## Linking Against Boxer 42 | 43 | ### Static 44 | 45 | If Boxer was built statically, just link against the generated static library. 46 | 47 | ### CMake 48 | 49 | To compile Boxer along with another application using CMake, first add the Boxer subdirectory: 50 | 51 | ```cmake 52 | add_subdirectory("path/to/Boxer") 53 | ``` 54 | 55 | Then link against the Boxer library: 56 | 57 | ```cmake 58 | target_link_libraries( Boxer) 59 | ``` 60 | 61 | ## Using Boxer 62 | 63 | To create a message box using Boxer, call the 'show' method in the 'boxer' namespace and provide a message and title: 64 | 65 | ```c++ 66 | boxer::show("Simple message boxes are very easy to create.", "Simple Example"); 67 | ``` 68 | 69 | A style / set of buttons may also be specified, and the user's selection can be determined from the function's return value: 70 | 71 | ```c++ 72 | boxer::Selection sel = boxer::show("Make a choice:", "Decision", boxer::Style::Warning, boxer::Buttons::YesNo); 73 | ``` 74 | 75 | Calls to 'show' are blocking - execution of your program will not continue until the user dismisses the message box. 76 | 77 | ### Encoding 78 | 79 | Boxer accepts strings encoded in UTF-8: 80 | 81 | ```c++ 82 | boxer::show(u8"Boxer accepts UTF-8 strings. 💯", u8"Unicode 👍"); 83 | ``` 84 | 85 | On Windows, `UNICODE` needs to be defined when compiling Boxer to enable UTF-8 support: 86 | 87 | ```cmake 88 | if (WIN32) 89 | target_compile_definitions(Boxer PRIVATE UNICODE) 90 | endif (WIN32) 91 | ``` 92 | -------------------------------------------------------------------------------- /src/boxer_win.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #ifndef WIN32_LEAN_AND_MEAN 4 | #define WIN32_LEAN_AND_MEAN 5 | #endif 6 | #include 7 | 8 | namespace boxer 9 | { 10 | 11 | namespace 12 | { 13 | #if defined(UNICODE) 14 | bool utf8ToUtf16(const char* utf8String, std::wstring& utf16String) 15 | { 16 | int count = MultiByteToWideChar(CP_UTF8, 0, utf8String, -1, nullptr, 0); 17 | if (count <= 0) 18 | { 19 | return false; 20 | } 21 | 22 | utf16String = std::wstring(static_cast(count), L'\0'); 23 | return MultiByteToWideChar(CP_UTF8, 0, utf8String, -1, &utf16String[0], count) > 0; 24 | } 25 | #endif // defined(UNICODE) 26 | 27 | UINT getIcon(Style style) 28 | { 29 | switch (style) 30 | { 31 | case Style::Info: 32 | return MB_ICONINFORMATION; 33 | case Style::Warning: 34 | return MB_ICONWARNING; 35 | case Style::Error: 36 | return MB_ICONERROR; 37 | case Style::Question: 38 | return MB_ICONQUESTION; 39 | default: 40 | return MB_ICONINFORMATION; 41 | } 42 | } 43 | 44 | UINT getButtons(Buttons buttons) 45 | { 46 | switch (buttons) 47 | { 48 | case Buttons::OK: 49 | case Buttons::Quit: // There is no 'Quit' button on Windows :( 50 | return MB_OK; 51 | case Buttons::OKCancel: 52 | return MB_OKCANCEL; 53 | case Buttons::YesNo: 54 | return MB_YESNO; 55 | default: 56 | return MB_OK; 57 | } 58 | } 59 | 60 | Selection getSelection(int response, Buttons buttons) 61 | { 62 | switch (response) 63 | { 64 | case IDOK: 65 | return buttons == Buttons::Quit ? Selection::Quit : Selection::OK; 66 | case IDCANCEL: 67 | return Selection::Cancel; 68 | case IDYES: 69 | return Selection::Yes; 70 | case IDNO: 71 | return Selection::No; 72 | default: 73 | return Selection::None; 74 | } 75 | } 76 | } // namespace 77 | 78 | Selection show(const char* message, const char* title, Style style, Buttons buttons) 79 | { 80 | UINT flags = MB_TASKMODAL; 81 | 82 | flags |= getIcon(style); 83 | flags |= getButtons(buttons); 84 | 85 | #if defined(UNICODE) 86 | std::wstring wideMessage; 87 | std::wstring wideTitle; 88 | if (!utf8ToUtf16(message, wideMessage) || !utf8ToUtf16(title, wideTitle)) 89 | { 90 | return Selection::Error; 91 | } 92 | 93 | const WCHAR* messageArg = wideMessage.c_str(); 94 | const WCHAR* titleArg = wideTitle.c_str(); 95 | #else 96 | const char* messageArg = message; 97 | const char* titleArg = title; 98 | #endif 99 | 100 | return getSelection(MessageBox(nullptr, messageArg, titleArg, flags), buttons); 101 | } 102 | 103 | } // namespace boxer 104 | -------------------------------------------------------------------------------- /src/boxer_linux.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace boxer 5 | { 6 | 7 | namespace 8 | { 9 | GtkMessageType getMessageType(Style style) 10 | { 11 | switch (style) 12 | { 13 | case Style::Info: 14 | return GTK_MESSAGE_INFO; 15 | case Style::Warning: 16 | return GTK_MESSAGE_WARNING; 17 | case Style::Error: 18 | return GTK_MESSAGE_ERROR; 19 | case Style::Question: 20 | return GTK_MESSAGE_QUESTION; 21 | default: 22 | return GTK_MESSAGE_INFO; 23 | } 24 | } 25 | 26 | GtkButtonsType getButtonsType(Buttons buttons) 27 | { 28 | switch (buttons) 29 | { 30 | case Buttons::OK: 31 | return GTK_BUTTONS_OK; 32 | case Buttons::OKCancel: 33 | return GTK_BUTTONS_OK_CANCEL; 34 | case Buttons::YesNo: 35 | return GTK_BUTTONS_YES_NO; 36 | case Buttons::Quit: 37 | return GTK_BUTTONS_CLOSE; 38 | default: 39 | return GTK_BUTTONS_OK; 40 | } 41 | } 42 | 43 | Selection getSelection(gint response) 44 | { 45 | switch (response) 46 | { 47 | case GTK_RESPONSE_OK: 48 | return Selection::OK; 49 | case GTK_RESPONSE_CANCEL: 50 | return Selection::Cancel; 51 | case GTK_RESPONSE_YES: 52 | return Selection::Yes; 53 | case GTK_RESPONSE_NO: 54 | return Selection::No; 55 | case GTK_RESPONSE_CLOSE: 56 | return Selection::Quit; 57 | default: 58 | return Selection::None; 59 | } 60 | } 61 | } // namespace 62 | 63 | Selection show(const char* message, const char* title, Style style, Buttons buttons) 64 | { 65 | if (!gtk_init_check(0, nullptr)) 66 | { 67 | return Selection::Error; 68 | } 69 | 70 | // Create a parent window to stop gtk_dialog_run from complaining 71 | GtkWidget* parent = gtk_window_new(GTK_WINDOW_TOPLEVEL); 72 | 73 | GtkWidget* dialog = gtk_message_dialog_new(GTK_WINDOW(parent), 74 | GTK_DIALOG_MODAL, 75 | getMessageType(style), 76 | getButtonsType(buttons), 77 | "%s", 78 | message); 79 | gtk_window_set_title(GTK_WINDOW(dialog), title); 80 | 81 | gtk_window_set_gravity(GTK_WINDOW(parent), GDK_GRAVITY_CENTER); 82 | gtk_window_set_gravity(GTK_WINDOW(dialog), GDK_GRAVITY_CENTER); 83 | gtk_window_set_position(GTK_WINDOW(parent), GTK_WIN_POS_CENTER); 84 | gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER); 85 | 86 | Selection selection = getSelection(gtk_dialog_run(GTK_DIALOG(dialog))); 87 | 88 | gtk_widget_destroy(GTK_WIDGET(dialog)); 89 | gtk_widget_destroy(GTK_WIDGET(parent)); 90 | while (g_main_context_iteration(nullptr, false)); 91 | 92 | return selection; 93 | } 94 | 95 | } // namespace boxer 96 | -------------------------------------------------------------------------------- /include/boxer/boxer.h: -------------------------------------------------------------------------------- 1 | #ifndef BOXER_H 2 | #define BOXER_H 3 | 4 | #if defined(BOXER_DLL) && defined(BOXER_BUILD_DLL) 5 | /*! 6 | * BOXER_DLL must be defined by applications that are linking against the DLL version of the Boxer library. 7 | * BOXER_BUILD_DLL is defined when compiling the DLL version of the library. 8 | */ 9 | #error "You may not have both BOXER_DLL and BOXER_BUILD_DLL defined" 10 | #endif 11 | 12 | /*! 13 | * BOXERAPI is used to declare public API classes / functions for export from the DLL / shared library / dynamic library 14 | */ 15 | #if defined(_WIN32) && defined(BOXER_BUILD_DLL) 16 | // We are building Boxer as a Win32 DLL 17 | #define BOXERAPI __declspec(dllexport) 18 | #elif defined(_WIN32) && defined(BOXER_DLL) 19 | // We are calling Boxer as a Win32 DLL 20 | #define BOXERAPI __declspec(dllimport) 21 | #elif defined(__GNUC__) && defined(BOXER_BUILD_DLL) 22 | // We are building Boxer as a shared / dynamic library 23 | #define BOXERAPI __attribute__((visibility("default"))) 24 | #else 25 | // We are building or calling Boxer as a static library 26 | #define BOXERAPI 27 | #endif 28 | 29 | namespace boxer 30 | { 31 | 32 | /*! 33 | * Options for styles to apply to a message box 34 | */ 35 | enum class Style 36 | { 37 | Info, 38 | Warning, 39 | Error, 40 | Question 41 | }; 42 | 43 | /*! 44 | * Options for buttons to provide on a message box 45 | */ 46 | enum class Buttons 47 | { 48 | OK, 49 | OKCancel, 50 | YesNo, 51 | Quit 52 | }; 53 | 54 | /*! 55 | * Possible responses from a message box. 'None' signifies that no option was chosen, and 'Error' signifies that an 56 | * error was encountered while creating the message box. 57 | */ 58 | enum class Selection 59 | { 60 | OK, 61 | Cancel, 62 | Yes, 63 | No, 64 | Quit, 65 | None, 66 | Error 67 | }; 68 | 69 | /*! 70 | * The default style to apply to a message box 71 | */ 72 | constexpr Style kDefaultStyle = Style::Info; 73 | 74 | /*! 75 | * The default buttons to provide on a message box 76 | */ 77 | constexpr Buttons kDefaultButtons = Buttons::OK; 78 | 79 | /*! 80 | * Blocking call to create a modal message box with the given message, title, style, and buttons 81 | */ 82 | BOXERAPI Selection show(const char* message, const char* title, Style style, Buttons buttons); 83 | 84 | /*! 85 | * Convenience function to call show() with the default buttons 86 | */ 87 | inline Selection show(const char* message, const char* title, Style style) 88 | { 89 | return show(message, title, style, kDefaultButtons); 90 | } 91 | 92 | /*! 93 | * Convenience function to call show() with the default style 94 | */ 95 | inline Selection show(const char* message, const char* title, Buttons buttons) 96 | { 97 | return show(message, title, kDefaultStyle, buttons); 98 | } 99 | 100 | /*! 101 | * Convenience function to call show() with the default style and buttons 102 | */ 103 | inline Selection show(const char* message, const char* title) 104 | { 105 | return show(message, title, kDefaultStyle, kDefaultButtons); 106 | } 107 | 108 | } // namespace boxer 109 | 110 | #endif 111 | -------------------------------------------------------------------------------- /src/boxer_mac.mm: -------------------------------------------------------------------------------- 1 | #include 2 | #import 3 | 4 | namespace boxer 5 | { 6 | 7 | namespace 8 | { 9 | #if defined(MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12 10 | const NSAlertStyle kInformationalStyle = NSAlertStyleInformational; 11 | const NSAlertStyle kWarningStyle = NSAlertStyleWarning; 12 | const NSAlertStyle kCriticalStyle = NSAlertStyleCritical; 13 | #else 14 | const NSAlertStyle kInformationalStyle = NSInformationalAlertStyle; 15 | const NSAlertStyle kWarningStyle = NSWarningAlertStyle; 16 | const NSAlertStyle kCriticalStyle = NSCriticalAlertStyle; 17 | #endif 18 | 19 | #if defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_9 20 | using ModalResponse = NSModalResponse; 21 | #elif defined(MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 22 | using ModalResponse = NSInteger; 23 | #else 24 | using ModalResponse = int; 25 | #endif 26 | 27 | NSString* const kOkStr = @"OK"; 28 | NSString* const kCancelStr = @"Cancel"; 29 | NSString* const kYesStr = @"Yes"; 30 | NSString* const kNoStr = @"No"; 31 | NSString* const kQuitStr = @"Quit"; 32 | 33 | NSAlertStyle getAlertStyle(Style style) 34 | { 35 | switch (style) 36 | { 37 | case Style::Info: 38 | return kInformationalStyle; 39 | case Style::Warning: 40 | return kWarningStyle; 41 | case Style::Error: 42 | return kCriticalStyle; 43 | case Style::Question: 44 | return kWarningStyle; 45 | default: 46 | return kInformationalStyle; 47 | } 48 | } 49 | 50 | void setButtons(NSAlert* alert, Buttons buttons) 51 | { 52 | switch (buttons) 53 | { 54 | case Buttons::OK: 55 | [alert addButtonWithTitle:kOkStr]; 56 | break; 57 | case Buttons::OKCancel: 58 | [alert addButtonWithTitle:kOkStr]; 59 | [alert addButtonWithTitle:kCancelStr]; 60 | break; 61 | case Buttons::YesNo: 62 | [alert addButtonWithTitle:kYesStr]; 63 | [alert addButtonWithTitle:kNoStr]; 64 | break; 65 | case Buttons::Quit: 66 | [alert addButtonWithTitle:kQuitStr]; 67 | break; 68 | default: 69 | [alert addButtonWithTitle:kOkStr]; 70 | } 71 | } 72 | 73 | Selection getSelection(ModalResponse index, Buttons buttons) 74 | { 75 | switch (buttons) 76 | { 77 | case Buttons::OK: 78 | return index == NSAlertFirstButtonReturn ? Selection::OK : Selection::None; 79 | case Buttons::OKCancel: 80 | if (index == NSAlertFirstButtonReturn) 81 | { 82 | return Selection::OK; 83 | } 84 | else if (index == NSAlertSecondButtonReturn) 85 | { 86 | return Selection::Cancel; 87 | } 88 | else 89 | { 90 | return Selection::None; 91 | } 92 | case Buttons::YesNo: 93 | if (index == NSAlertFirstButtonReturn) 94 | { 95 | return Selection::Yes; 96 | } 97 | else if (index == NSAlertSecondButtonReturn) 98 | { 99 | return Selection::No; 100 | } 101 | else 102 | { 103 | return Selection::None; 104 | } 105 | case Buttons::Quit: 106 | return index == NSAlertFirstButtonReturn ? Selection::Quit : Selection::None; 107 | default: 108 | return Selection::None; 109 | } 110 | } 111 | } // namespace 112 | 113 | Selection show(const char* message, const char* title, Style style, Buttons buttons) 114 | { 115 | NSAlert* alert = [[NSAlert alloc] init]; 116 | 117 | [alert setMessageText:[NSString stringWithUTF8String:title]]; 118 | [alert setInformativeText:[NSString stringWithUTF8String:message]]; 119 | 120 | [alert setAlertStyle:getAlertStyle(style)]; 121 | setButtons(alert, buttons); 122 | 123 | // Force the alert to appear on top of any other windows 124 | [[alert window] setLevel:NSModalPanelWindowLevel]; 125 | 126 | Selection selection = getSelection([alert runModal], buttons); 127 | [alert release]; 128 | 129 | return selection; 130 | } 131 | 132 | } // namespace boxer 133 | --------------------------------------------------------------------------------