1 // Copyright (c) 2017 Matthew Brennan Jones <matthew.brennan.jones@gmail.com> 2 // Boost Software License - Version 1.0 3 // A simple message box for the D programming language 4 // https://github.com/workhorsy/d-message-box 5 6 7 /++ 8 A simple message box for the D programming language 9 10 It should work without requiring any 3rd party GUI toolkits. But will work with what 11 it can find on your OS at runtime. 12 13 It tries to use the following: 14 15 * DlangUI (win32 on Windows or SDL2 on Linux) 16 17 * SDL_ShowSimpleMessageBox (Derelict SDL2) 18 19 * MessageBoxW (Windows) 20 21 * Zenity (Gtk/Gnome) 22 23 * Kdialog (KDE) 24 25 * gxmessage (X11) 26 27 Home page: 28 $(LINK https://github.com/workhorsy/d-message-box) 29 30 Version: 0.3.0 31 32 License: 33 Boost Software License - Version 1.0 34 35 Examples: 36 ---- 37 import std.stdio : stdout, stderr; 38 import message_box : MessageBox, IconType; 39 40 int main(string[] args) { 41 // Create the message box 42 auto dialog = new MessageBox("Party Time", "The roof is on fire!", IconType.Warning); 43 44 // Set the error handler 45 dialog.onError((Throwable err) { 46 stderr.writefln("Failed to show message box: %s", err); 47 }); 48 49 // Show the message box 50 dialog.show(); 51 52 return 0; 53 } 54 ---- 55 +/ 56 57 module message_box; 58 59 bool is_sdl2_loadable = false; 60 bool use_log = false; 61 62 static this() { 63 import std.stdio : stdout; 64 65 // Figure out if the SDL2 libraries can be loaded 66 version (Have_derelict_sdl2) { 67 import derelict.sdl2.sdl : DerelictSDL2, SharedLibVersion, SharedLibLoadException; 68 try { 69 DerelictSDL2.load(SharedLibVersion(2, 0, 2)); 70 is_sdl2_loadable = true; 71 stdout.writefln("SDL was found ..."); 72 } catch (SharedLibLoadException) { 73 stdout.writefln("SDL was NOT found ..."); 74 } 75 } 76 } 77 78 79 /++ 80 If true will print output of external program to console. 81 Params: 82 is_logging = If true will print to output 83 +/ 84 public void setUseLog(bool is_logging) { 85 use_log = is_logging; 86 } 87 88 /++ 89 Returns if external program logging is on or off. 90 +/ 91 public bool getUseLog() { 92 return use_log; 93 } 94 95 /++ 96 The type of icon to show in the message box. Some message boxes will not show 97 the icon. 98 99 ---- 100 enum IconType { 101 None, 102 Information, 103 Error, 104 Warning, 105 } 106 ---- 107 +/ 108 109 enum IconType { 110 None, 111 Information, 112 Error, 113 Warning, 114 } 115 116 abstract class MessageBoxBase { 117 this(string title, string message, IconType icon_type) { 118 _title = title; 119 _message = message; 120 _icon_type = icon_type; 121 } 122 123 void onError(void delegate(Throwable err) cb) { 124 _on_error_cb = cb; 125 } 126 127 void fireOnError(Throwable err) { 128 // Remove the callback before use to prevent infinite recursion 129 auto old_cb = _on_error_cb; 130 _on_error_cb = null; 131 132 // Fire the callback 133 if (old_cb) old_cb(err); 134 135 // Restore the callback 136 _on_error_cb = old_cb; 137 } 138 139 void show(); 140 141 string _title; 142 string _message; 143 IconType _icon_type; 144 void delegate(Throwable err) _on_error_cb; 145 } 146 147 /++ 148 The MessageBox class 149 +/ 150 class MessageBox { 151 import compressed_file : CompressedFile; 152 import message_box_dlangui : MessageBoxDlangUI; 153 import message_box_sdl : MessageBoxSDL; 154 /* 155 import message_box_win32 : MessageBoxWin32; 156 */ 157 import message_box_zenity : MessageBoxZenity; 158 import message_box_kdialog : MessageBoxKdialog; 159 import message_box_gxmessage : MessageBoxGxmessage; 160 161 /++ 162 Sets up the message box with the desired title, message, and icon. Does not 163 show it until the show method is called. 164 Params: 165 title = The string to show in the message box title 166 message = The string to show in the message box body 167 icon = The type of icon to show in the message box 168 Throws: 169 If it fails to find any programs or libraries to make a message box with. 170 +/ 171 this(string title, string message, IconType icon_type) { 172 assertInitialized(); 173 if (MessageBoxDlangUI.isSupported()) { 174 _dialog = new MessageBoxDlangUI(_temp_dir, title, message, icon_type); 175 } else if (MessageBoxSDL.isSupported()) { 176 _dialog = new MessageBoxSDL(title, message, icon_type); 177 /* 178 } else if (MessageBoxWin32.isSupported()) { 179 _dialog = new MessageBoxWin32(title, message, icon_type); 180 */ 181 } else if (MessageBoxZenity.isSupported()) { 182 _dialog = new MessageBoxZenity(title, message, icon_type); 183 } else if (MessageBoxKdialog.isSupported()) { 184 _dialog = new MessageBoxKdialog(title, message, icon_type); 185 } else if (MessageBoxGxmessage.isSupported()) { 186 _dialog = new MessageBoxGxmessage(title, message, icon_type); 187 } else { 188 throw new Exception("Failed to find a way to make a message box."); 189 } 190 } 191 192 /++ 193 This method is called if there is an error when showing the message box. 194 Params: 195 cb = The call back to fire when there is an error. 196 +/ 197 void onError(void delegate(Throwable err) cb) { 198 assertInitialized(); 199 _dialog._on_error_cb = cb; 200 } 201 202 /++ 203 Shows the message box. Will block until it is closed. 204 +/ 205 void show() { 206 assertInitialized(); 207 _dialog.show(); 208 } 209 210 public static void init() { 211 import data : compressed_files; 212 import extract : extractFiles; 213 import std.file : tempDir, exists, mkdir; 214 import std.path : buildPath; 215 import std.algorithm : map; 216 import std.ascii : letters; 217 import std.random : uniform; 218 import std.range : iota, array; 219 import std.conv : to; 220 221 // Generate a random unused temporary directory name 222 do { 223 string dir_name = iota(10).map!((_) => letters[uniform(0, $)]).array.to!string; 224 _temp_dir = buildPath(tempDir(), dir_name); 225 } while(exists(_temp_dir)); 226 227 // Create the directory 228 mkdir(_temp_dir); 229 230 // Extract the files into the directory 231 extractFiles(_temp_dir, compressed_files, delegate(int percent) { 232 //dialog.setPercent(percent); 233 }); 234 } 235 236 public static void cleanup() { 237 import std.file : exists, rmdirRecurse, FileException; 238 import std.stdio : stderr; 239 240 // Remove the temporary directory 241 if (_temp_dir && exists(_temp_dir)) { 242 try { 243 rmdirRecurse(_temp_dir); 244 } catch (FileException err) { 245 stderr.writefln(`Failed to remove the directory "%s"`, _temp_dir); 246 } 247 } 248 } 249 250 private static void assertInitialized() { 251 // Just return if initialized 252 if (_temp_dir) return; 253 254 throw new Exception("MessageBox.init should be called before use."); 255 } 256 257 static string _temp_dir = null; 258 MessageBoxBase _dialog; 259 }