├── LICENSE ├── README.md ├── bubble.cpp ├── bubble.hpp └── sample.cc /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 r-lyeh (https://github.com/r-lyeh) 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | bubble 2 | ====== 3 | 4 | - Bubble is a simple and lightweight C++11 dialog library. 5 | - Bubble creates dialogs boxes, progress bars and radio buttons. 6 | - Bubble replaces MessageBox() and notify dialogs with ease. 7 | - Bubble provides callbacks to update dialogs. 8 | - Bubble has an expressive API based on strings. 9 | - Bubble has no external dependencies. Only native OS calls are used. 10 | - Bubble is zlib/libpng licensed. 11 | 12 | ### Samples 13 | ```c++ 14 | #include "bubble.hpp" 15 | 16 | int main() { 17 | bubble::show( 18 | "title.text=About;" 19 | "body.icon=14;" 20 | "body.text=Your app name here;" 21 | "progress=0;" 22 | "footer.icon=-3;" 23 | "footer.text=Made with Bubble dialog library (built " __DATE__ ")", 24 | []( bubble::vars &ui ) { 25 | ui["progress"] = ui["progress"] + 10; 26 | } ); 27 | 28 | bubble::notify("notify dialog, using icon #19", "hello world", 19); 29 | } 30 | ``` 31 | 32 | ### Possible output 33 | ![image](https://raw.github.com/r-lyeh/depot/master/bubble-snapshot-1.png) 34 | 35 | ![image](https://raw.github.com/r-lyeh/depot/master/bubble-snapshot-5.png) 36 | 37 | ### More samples 38 | Check [sample.cc](sample.cc) for a few examples 39 | 40 | ![image](https://raw.github.com/r-lyeh/depot/master/bubble-snapshot-2.png) 41 | 42 | ![image](https://raw.github.com/r-lyeh/depot/master/bubble-snapshot-3.png) 43 | 44 | ![image](https://raw.github.com/r-lyeh/depot/master/bubble-snapshot-4.png) 45 | 46 | ### Cons 47 | - Windows only (for now). 48 | 49 | ### API 50 | - `int bubble::show( string options, [callback] )` 51 | - `int bubble::show( map options, [callback] )` 52 | 53 | ### Variables 54 | - `timeout (int ms)` in milliseconds; [0] to stay forever 55 | - `progress (int pct)` [0..100] range; [-1] creates an infinite marquee 56 | - `title.text (string)` 57 | - `head.text (string)` 58 | - `body.icon (int)` see note below 59 | - `body.text (string)` 60 | - `footer.icon (int)` see note below 61 | - `footer.text (string)` 62 | - `style.minimizable (bool)` 63 | - `style.command_links (bool)` 64 | - `style.skippable (bool)` 65 | - `style.minimized (bool)` 66 | - `style.ontop (bool)` 67 | - `exit (int)` to close dialog and return exit code from callback 68 | - `[number].text (string)` 69 | - `[number].icon (int)` see note below 70 | 71 | ### Win32 icons 72 | - valid icons are in range {-1 = information, -2 = warning, -3 = error, -4 = admin/shield } 73 | - also in range [1..255] which maps to icons in imageres.dll, as follows: 74 | 75 | ![image](https://raw.github.com/r-lyeh/depot/master/bubble-imageresdll.png) 76 | 77 | ### Changelog 78 | - v1.1.0 (2015/09/25) 79 | - Add notify / taskbar dialogs 80 | - v1.0.0 (2015/06/12) 81 | - Diverse enhancements and clean ups 82 | - v0.0.0 (2014/xx/xx) 83 | - Initial commit 84 | -------------------------------------------------------------------------------- /bubble.cpp: -------------------------------------------------------------------------------- 1 | // Bubble is a simple and lightweight dialog library (for Windows) 2 | // Based on code by napalm @ netcore2k and Guillaume @ paralint.com. 3 | // - rlyeh, zlib/libpng licensed. 4 | 5 | // [ref] http://msdn.microsoft.com/en-us/library/windows/desktop/bb760441(v=vs.85).aspx 6 | // [ref] http://msdn.microsoft.com/en-us/library/windows/desktop/bb760540(v=vs.85).aspx 7 | // [ref] http://www.codeproject.com/Articles/16806/Vista-Goodies-in-C-Using-TaskDialogIndirect-to-Bui 8 | // [ref] http://msdn.microsoft.com/en-us/library/vstudio/dd234915.aspx 9 | // [ref] http://www.win7dll.info/imageres_dll_icons.png (on win32, try icons in [-4..-1] and [1..255] range) 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "bubble.hpp" 20 | 21 | #ifdef _WIN32 22 | # define UNICODE 23 | # define _UNICODE 24 | # include 25 | # include 26 | # include 27 | # include 28 | # if defined(_M_IX86) 29 | # pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"") 30 | # elif defined(_M_IA64) 31 | # pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' language='*'\"") 32 | # elif defined(_M_X64) 33 | # pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"") 34 | # else 35 | # pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") 36 | # endif 37 | # ifndef TD_SHIELD_ICON 38 | # define TD_SHIELD_ICON MAKEINTRESOURCEW(-4) 39 | # endif 40 | # if PSAPI_VERSION==1 41 | # pragma comment(lib, "Psapi.lib") 42 | # endif 43 | # pragma comment(lib, "Shell32.lib") 44 | # pragma comment(lib, "Ole32.lib") 45 | # pragma comment(lib, "Kernel32.lib") 46 | # pragma comment(lib, "User32.lib") 47 | # pragma comment(lib, "ComCtl32.lib") 48 | #endif 49 | 50 | #ifdef _WIN32 51 | #define $win32 $yes 52 | #define $welse $no 53 | #else 54 | #define $win32 $no 55 | #define $welse $yes 56 | #endif 57 | 58 | #define $yes(...) __VA_ARGS__ 59 | #define $no(...) 60 | 61 | namespace { 62 | using namespace bubble; 63 | 64 | bubble::vars& getDialog() { 65 | static std::map< std::thread::id, bubble::vars > all; 66 | std::thread::id self = std::this_thread::get_id(); 67 | return ( all[ self ] = all[ self ] ); 68 | } 69 | 70 | struct extra { 71 | int dummy = 0; 72 | std::function cb; 73 | std::vector options; 74 | bubble::vars copy; 75 | 76 | $win32( 77 | int progress_style = 0; 78 | ) 79 | }; 80 | 81 | extra& getExtra() { 82 | static std::map< std::thread::id, extra > all; 83 | std::thread::id self = std::this_thread::get_id(); 84 | return ( all[ self ] = all[ self ] ); 85 | } 86 | 87 | $win32( 88 | HRESULT CALLBACK TDCallback ( 89 | HWND hwnd, UINT uNotification, WPARAM wParam, 90 | LPARAM lParam, LONG_PTR dwRefData ) 91 | { 92 | switch ( uNotification ) 93 | { 94 | case TDN_DIALOG_CONSTRUCTED: 95 | 96 | // SendMessage( hwnd, TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE, IDOK, 1 ); // UAC, SHIELD 97 | 98 | SendMessage( hwnd, TDM_SET_MARQUEE_PROGRESS_BAR, 0 /*off*/, 0 ); // marquee off 99 | SendMessage( hwnd, TDM_SET_PROGRESS_BAR_MARQUEE, 1 /*on*/, 0 /*30ms*/ ); // marquee speed 100 | 101 | SendMessage( hwnd, TDM_SET_PROGRESS_BAR_RANGE, 0, MAKELPARAM(0, 100) ); // progress range 102 | SendMessage( hwnd, TDM_SET_PROGRESS_BAR_POS, int(0), 0 ); // progress at 103 | 104 | { 105 | bubble::vars &ui = getDialog(); 106 | if( ui["style.ontop"] ) { 107 | SetWindowPos(hwnd, // handle to window 108 | HWND_TOPMOST, // placement-order handle 109 | 0, // horizontal position 110 | 0, // vertical position 111 | 0, // width 112 | 0, // height 113 | SWP_SHOWWINDOW|SWP_NOSIZE|SWP_NOMOVE// window-positioning options 114 | ); 115 | SetWindowPos(hwnd, // handle to window 116 | HWND_TOP, 117 | 0, // horizontal position 118 | 0, // vertical position 119 | 0, // width 120 | 0, // height 121 | SWP_SHOWWINDOW|SWP_NOSIZE|SWP_NOMOVE// window-positioning options 122 | ); 123 | } 124 | else 125 | if( ui["style.minimized"] ) { 126 | SetWindowPos(hwnd, // handle to window 127 | HWND_BOTTOM, // placement-order handle 128 | 0, // horizontal position 129 | 0, // vertical position 130 | 0, // width 131 | 0, // height 132 | SWP_SHOWWINDOW|SWP_NOSIZE|SWP_NOMOVE// window-positioning options 133 | ); 134 | } 135 | } 136 | 137 | // refresh ui by simulating timer 138 | TDCallback( hwnd, TDN_TIMER, 0, lParam, dwRefData ); 139 | return S_OK; 140 | 141 | case TDN_DESTROYED: 142 | break; 143 | 144 | case TDN_HYPERLINK_CLICKED: 145 | ShellExecuteW( hwnd, L"open", (LPCWSTR) lParam, NULL, NULL, SW_SHOW ); 146 | 147 | // you can also elevate a process by using explorer. ie, 148 | // system("explorer.exe path/to/cmd.exe"); 149 | /* 150 | ShellExecute( 151 | NULL, 152 | L"runas", // Trick for requesting elevation, this is a verb not listed in the documentation above. 153 | L"cmd.exe", 154 | NULL, // params 155 | NULL, // directory 156 | SW_HIDE); 157 | */ 158 | 159 | break; 160 | 161 | case TDN_TIMER: { 162 | // reconstruct progress bar if needed 163 | bubble::vars &ui = getDialog(); 164 | extra &ex = getExtra(); 165 | bubble::vars © = ex.copy; 166 | 167 | if( ex.cb ) { 168 | ex.cb(ui); 169 | } 170 | 171 | if( ui["timeout"] > 200 ) { 172 | ui["timeout"] = ui["timeout"] - 200; 173 | } else { 174 | ui["timeout"] = 0; 175 | } 176 | 177 | enum { PERCENT, MARQUEE }; 178 | int progress_style = ( ui["progress"] >= 0 && ui["progress"] <= 100 ? PERCENT : MARQUEE ); 179 | if( ex.progress_style != progress_style ) { 180 | ex.progress_style = progress_style; 181 | 182 | if( progress_style == PERCENT ) { 183 | SendMessage( hwnd, TDM_SET_MARQUEE_PROGRESS_BAR, 0 /*show*/, 0 ); // in range; marquee off 184 | } else { 185 | SendMessage( hwnd, TDM_SET_MARQUEE_PROGRESS_BAR, 1 /*show*/, 0 ); // out of range; marquee on 186 | } 187 | } 188 | 189 | if( progress_style == PERCENT ) { 190 | if( copy["progress"] != ui["progress"] ) 191 | SendMessage( hwnd, TDM_SET_PROGRESS_BAR_POS, ui["progress"], 0 ); // at 192 | } 193 | 194 | // window title 195 | if( copy["title.text"] != ui["title.text"] ) 196 | SetWindowTextW( hwnd, ui["title.text"].c_str() ); 197 | 198 | // head 199 | if( copy["head.text"] != ui["head.text"] ) 200 | SendMessage(hwnd, TDM_SET_ELEMENT_TEXT, (WPARAM)TDE_MAIN_INSTRUCTION, (LPARAM)( ui["head.text"].c_str() )); 201 | 202 | // body 203 | if( copy["body.text"] != ui["body.text"] ) 204 | SendMessage(hwnd, TDM_SET_ELEMENT_TEXT, (WPARAM)TDE_CONTENT, (LPARAM)( ui["body.text"].c_str() )); 205 | 206 | // footer 207 | if( copy["footer.text"] != ui["footer.text"] ) 208 | SendMessage(hwnd, TDM_SET_ELEMENT_TEXT, (WPARAM)TDE_FOOTER, (LPARAM)( ui["footer.text"].c_str() )); 209 | 210 | // expanded 211 | if( copy["footer.extra"] != ui["footer.extra"] ) 212 | SendMessage(hwnd, TDM_SET_ELEMENT_TEXT, (WPARAM)TDE_EXPANDED_INFORMATION, (LPARAM)( ui["footer.extra"].c_str() )); 213 | 214 | // update big icon 215 | if( copy["body.icon"] != ui["body.icon"] ) { 216 | SendMessage(hwnd, TDM_UPDATE_ICON, (WPARAM)TDIE_ICON_MAIN, (LPARAM)MAKEINTRESOURCEW( ui["body.icon"] ) ); 217 | } 218 | 219 | // update footer icon 220 | if( copy["footer.icon"] != ui["footer.icon"] ) { 221 | SendMessage(hwnd, TDM_UPDATE_ICON, (WPARAM)TDIE_ICON_FOOTER, (LPARAM)MAKEINTRESOURCEW( ui["footer.icon"] ) ); 222 | } 223 | 224 | copy = ui; 225 | 226 | if( getDialog()["exit"] >= 0 ) { 227 | getDialog()["cancel_close"] = true; 228 | EndDialog(hwnd,getDialog()["exit"]); 229 | // cancel timer 230 | return S_OK; 231 | } 232 | 233 | return S_FALSE; 234 | } 235 | 236 | default: 237 | break; 238 | } 239 | 240 | return !getDialog()["cancel_close"] ? S_OK : S_FALSE; 241 | } 242 | 243 | // following code till end of namespace is actually used in notify(); functions 244 | 245 | ULONG_PTR GetParentProcessId() { // By Napalm @ NetCore2K 246 | ULONG ulSize = 0; 247 | ULONG_PTR pbi[6]; 248 | LONG (WINAPI *NtQueryInformationProcess)(HANDLE ProcessHandle, ULONG ProcessInformationClass, 249 | PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength); 250 | *(FARPROC *)&NtQueryInformationProcess = 251 | GetProcAddress( LoadLibraryA("NTDLL.DLL"), "NtQueryInformationProcess" ); 252 | return NtQueryInformationProcess 253 | && NtQueryInformationProcess( GetCurrentProcess(), 0, &pbi, sizeof(pbi), &ulSize ) >= 0 254 | && ulSize == sizeof(pbi) ? pbi[5] : (ULONG_PTR)-1; 255 | } 256 | 257 | HICON GetParentProcessIcon() { 258 | HICON icon = 0; 259 | DWORD parentid = GetParentProcessId(); 260 | if( parentid != (DWORD)((ULONG_PTR)-1) ) { 261 | HANDLE parent = OpenProcess( PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, FALSE, parentid ); 262 | if( parent ) { 263 | char parentname[ MAX_PATH ]; 264 | GetModuleFileNameExA( parent, 0, parentname, sizeof(parentname) ); 265 | ExtractIconExA( parentname, 0, 0, &icon, 1 ); 266 | CloseHandle( parent ); 267 | } 268 | } 269 | return icon; 270 | } 271 | 272 | class CQueryContinue : public IQueryContinue { 273 | protected: 274 | DWORD mDelay; 275 | DWORD mStarted; 276 | 277 | public: 278 | CQueryContinue(DWORD d = 0) : mDelay(0) { SetTimeout(d); } 279 | 280 | void SetTimeout(DWORD d) { mDelay = d; mStarted = GetTickCount(); } 281 | bool TimeoutReached() const { return mDelay ? (GetTickCount()-mStarted) > mDelay : false; } 282 | 283 | virtual ULONG STDMETHODCALLTYPE AddRef() { return 1; } 284 | virtual ULONG STDMETHODCALLTYPE Release() { return 0; } 285 | 286 | STDMETHODIMP QueryInterface(REFIID iid, void FAR* FAR* ppvObj) { 287 | if( iid == IID_IUnknown || iid == IID_IQueryContinue ) { 288 | *ppvObj = this; 289 | AddRef(); 290 | return NOERROR; 291 | } 292 | return ResultFromScode( E_NOINTERFACE ); 293 | } 294 | 295 | STDMETHODIMP QueryContinue(VOID) { 296 | return TimeoutReached() ? S_FALSE : S_OK; 297 | } 298 | }; 299 | 300 | struct NOTIFU_PARAM { 301 | bool mForceXP = false; 302 | HICON mIcon = GetParentProcessIcon(); 303 | std::wstring mText = L"\n"; 304 | std::wstring mTitle = L""; // optional 305 | DWORD mType = NIIF_USER | NIIF_LARGE_ICON; 306 | }; 307 | 308 | bool notify(const NOTIFU_PARAM& params, IQueryContinue *querycontinue, IUserNotificationCallback *notifcallback) 309 | { 310 | // Replace \n with actual CRLF pair 311 | const std::wstring crlf_text(L"\\n"); 312 | const std::wstring crlf(L"\n"); 313 | std::wstring text(params.mText); 314 | size_t look = 0; 315 | size_t found; 316 | while( (found = text.find(crlf_text, look)) != std::wstring::npos ) { 317 | text.replace(found, crlf_text.size(), crlf); 318 | look = found+1; 319 | } 320 | 321 | // try the Vista/Windows 7 interface unless XP flag is specified 322 | if( params.mForceXP ) { 323 | // Fall back to Windows XP 324 | // Using Windows XP interface IUserNotification 325 | IUserNotification *un = 0; 326 | HRESULT result = CoCreateInstance( CLSID_UserNotification, 0, CLSCTX_ALL, IID_IUserNotification, (void**)&un ); 327 | if( un && !FAILED(result) ) { 328 | result = un->SetIconInfo( params.mIcon, params.mTitle.c_str() ); 329 | result = un->SetBalloonInfo( params.mTitle.c_str(), text.c_str(), params.mType ); 330 | result = un->SetBalloonRetry( 0, 250, 0 ); // controls what happens when the X button is clicked on 331 | result = un->Show( querycontinue, 250 ); 332 | un->Release(); 333 | return true; 334 | } 335 | } 336 | 337 | // Using Vista interface IUserNotification2 338 | IUserNotification2 *un2 = 0; 339 | HRESULT result = CoCreateInstance( CLSID_UserNotification, 0, CLSCTX_ALL, IID_IUserNotification2, (void**)&un2 ); 340 | if( un2 && !FAILED(result) ) { 341 | result = un2->SetIconInfo( params.mIcon, params.mTitle.c_str() ); 342 | result = un2->SetBalloonInfo( params.mTitle.c_str(), text.c_str(), params.mType ); 343 | result = un2->SetBalloonRetry( 0, 250, 0 ); // controls what happens when the X button is clicked on 344 | result = un2->Show( querycontinue, 250, notifcallback ); 345 | un2->Release(); 346 | return true; 347 | } 348 | 349 | return false; 350 | } 351 | 352 | void notify( const bubble::string &text, const bubble::string &title, HICON icon ) { 353 | CoInitialize(0); 354 | NOTIFU_PARAM params; 355 | 356 | params.mTitle = title; 357 | params.mText = text; 358 | params.mIcon = icon; 359 | 360 | IQueryContinue *c = new CQueryContinue(); 361 | notify( params, c, 0 ); 362 | delete c; 363 | } 364 | 365 | ) 366 | 367 | } 368 | 369 | int bubble::show( const bubble::vars &d_, const std::function &cb2 ) 370 | { 371 | auto &dialog = ( getDialog() = d_ ); 372 | 373 | auto &ex = getExtra(); 374 | ex = extra(); 375 | ex.copy = bubble::vars(); 376 | ex.cb = cb2; 377 | auto &options = getExtra().options; 378 | 379 | { 380 | int each = 0; 381 | for( auto it = dialog.begin(); it = dialog.find( std::to_wstring(each) + L".text" ), it != dialog.end(); ++each ) { 382 | options.push_back( it->second ); 383 | dialog[ std::to_wstring(each) + L".icon" ] = dialog[ std::to_wstring(each) + L".icon" ]; 384 | } 385 | } 386 | 387 | $win32( 388 | std::map< int, int > choices; 389 | std::vector buttons( 16 ); 390 | 391 | for( auto i = 0u; i < options.size(); ++i ) { 392 | buttons[i] = {0}; 393 | buttons[i].pszButtonText = options[i].c_str(); 394 | buttons[i].nButtonID = ( dialog[ bubble::string(i) + L".icon" ].as() != -4 ? 100 + i : 1 ); 395 | choices[ buttons[i].nButtonID ] = i; 396 | } 397 | 398 | int nButtonPressed = 0; 399 | TASKDIALOGCONFIG config = {0}; 400 | 401 | config.pButtons = &buttons[0]; 402 | config.cButtons = (UINT)options.size(); 403 | 404 | config.cbSize = sizeof(config); 405 | config.hInstance = 0; //hInst; 406 | config.dwCommonButtons = 0; //TDCBF_OK_BUTTON; //TDCBF_CLOSE_BUTTON; //TDCBF_CANCEL_BUTTON; 407 | 408 | if( dialog.find("headialog.text") != dialog.end() ) { 409 | config.pszMainInstruction = L" "; 410 | } 411 | 412 | if( dialog.find("body.text") != dialog.end() || dialog.find("body.icon") != dialog.end() ) { 413 | config.pszContent = L" "; 414 | } 415 | 416 | if( dialog.find("footer.text") != dialog.end() || dialog.find("footer.icon") != dialog.end() ) { 417 | config.pszFooter = L" "; 418 | } 419 | 420 | if( dialog.find("footer.extra") != dialog.end() ) { 421 | config.pszExpandedInformation = L" "; 422 | } 423 | 424 | config.pszMainIcon = 0; //TD_INFORMATION_ICON; //MAKEINTRESOURCE(TD_SHIELD_ICON); 425 | config.pszFooterIcon = 0; 426 | 427 | if( dialog.find("progress") != dialog.end() ) { 428 | config.dwFlags |= TDF_SHOW_PROGRESS_BAR; 429 | } 430 | /* 431 | if( dialog.find("style.skippable") == dialog.end() ) { 432 | config.dwFlags |= TDF_ALLOW_DIALOG_CANCELLATION; 433 | } else { 434 | config.dwFlags &= ~TDF_ALLOW_DIALOG_CANCELLATION; 435 | }*/ 436 | if( dialog.find("style.minimizable") != dialog.end() ) { 437 | config.dwFlags |= TDF_CAN_BE_MINIMIZED; 438 | } 439 | if( dialog.find("style.command_links") != dialog.end() ) { 440 | config.dwFlags |= TDF_USE_COMMAND_LINKS; 441 | } 442 | config.dwFlags |= TDF_ENABLE_HYPERLINKS; 443 | //config.dwFlags |= 0x10000000ULL; // |= TDIF_SIZE_TO_CONTENT; // resize is pszContent is larger than 48 bytes (hidden flag) 444 | 445 | config.pfCallback = TDCallback; 446 | config.lpCallbackData = (LONG_PTR) (0); 447 | config.dwFlags |= TDF_CALLBACK_TIMER; 448 | 449 | dialog["exit"] = -1; 450 | HRESULT hr = TaskDialogIndirect(&config, &nButtonPressed, NULL, NULL); 451 | 452 | if( SUCCEEDED(hr) ) { 453 | if( choices.empty() ) { // a task dialog seems to have always an OK button (?) (can we remove it?) 454 | return nButtonPressed == -1 ? -1 : 0; 455 | } 456 | if( choices.find(nButtonPressed) != choices.end() ) { 457 | return choices[nButtonPressed]; 458 | } 459 | } 460 | ) 461 | 462 | return ~0; 463 | } 464 | 465 | int bubble::show( const bubble::string &input, const std::function &cb2 ) { 466 | bubble::vars v; 467 | 468 | // parse input text into map. format is key=value;key=value; [...] 469 | std::wstringstream split(input + L';'); 470 | for( bubble::string each; std::getline(split, each, L';'); ) { 471 | std::wstringstream token(each); 472 | for( bubble::string prev, key; std::getline(token, key, L'='); prev = key ) { 473 | v[ v.find(prev) == v.end() ? key : prev ] = key; 474 | } 475 | } 476 | 477 | for( auto &in : v ) { 478 | for( auto &ch : in.second ) { 479 | /**/ if( ch == '\a' ) ch = '='; 480 | else if( ch == '\b' ) ch = ';'; 481 | } 482 | } 483 | 484 | return show( v, cb2 ); 485 | } 486 | 487 | void bubble::notify( const bubble::string &text, const bubble::string &title ) { 488 | $win32( 489 | ::notify( text, title, GetParentProcessIcon() ); 490 | ) 491 | } 492 | void bubble::notify( const bubble::string &text, const bubble::string &title, int icon_no ) { 493 | $win32( 494 | static HINSTANCE hDll = LoadLibraryA( "imageres.dll" ); // also, "shell32.dll" or GetModuleHandle(NULL) 495 | assert( hDll ); 496 | ::notify( text, title, LoadIcon( hDll, MAKEINTRESOURCE( icon_no ) ) ); 497 | ) 498 | } 499 | void bubble::notify( const bubble::string &text, const bubble::string &title, const bubble::string &icon_file ) { 500 | $win32( 501 | ::notify( text, title, (HICON)LoadImageW( // returns a HANDLE so we have to cast to HICON 502 | NULL, // hInstance must be NULL when loading from a file 503 | icon_file.c_str(),// the icon file name 504 | IMAGE_ICON, // specifies that the file is an icon 505 | 0, // width of the image (we'll specify default later on) 506 | 0, // height of the image 507 | LR_LOADFROMFILE| // we want to load a file (as opposed to a resource) 508 | LR_DEFAULTSIZE| // default metrics based on the type (IMAGE_ICON, 32x32) 509 | LR_SHARED // let the system release the handle when it's no longer used 510 | ) ); 511 | ) 512 | } 513 | 514 | #undef $yes 515 | #undef $no 516 | #undef $win32 517 | #undef $welse 518 | -------------------------------------------------------------------------------- /bubble.hpp: -------------------------------------------------------------------------------- 1 | // Bubble is a simple and lightweight dialog library (for Windows) 2 | // Based on code by napalm @ netcore2k and Guillaume @ paralint.com. 3 | // - rlyeh, zlib/libpng licensed. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define BUBBLE_VERSION "1.1.0" /* (2015/09/25) Add notify / taskbar dialogs 14 | #define BUBBLE_VERSION "1.0.0" // (2015/06/12) Diverse enhancements and clean ups 15 | #define BUBBLE_VERSION "0.0.0" // (2014/05/20) Initial commit */ 16 | 17 | namespace bubble { 18 | struct string : std::wstring { 19 | string() : std::wstring() 20 | {} 21 | template 22 | string( const T &t ) : std::wstring() { 23 | std::wstringstream ss; 24 | if( ss << std::setprecision(20) << t ) 25 | this->assign( ss.str() ); 26 | } 27 | template 28 | string( const std::string::value_type (&str)[N] ) : std::wstring() { 29 | std::string s(str); 30 | this->assign( s.begin(), s.end() ); 31 | } 32 | template 33 | string( const std::wstring::value_type (&str)[N] ) : std::wstring( str ) 34 | {} 35 | string( const std::string &t ) : std::wstring( t.begin(), t.end() ) 36 | {} 37 | string( const std::wstring &t ) : std::wstring( t ) 38 | {} 39 | template 40 | bool operator>>( T &t ) const { 41 | std::wstringstream ss( *this ); 42 | return ss >> t ? true : (t = T(), false); 43 | } 44 | template 45 | string &operator<<( const T &t ) { 46 | std::wstringstream ss; 47 | return ss << t ? ( this->assign( *this + ss.str() ), *this ) : *this; 48 | } 49 | string &operator<<( const std::string &t ) { 50 | return operator<<( std::wstring( t.begin(), t.end() )); 51 | } 52 | template 53 | T as() const { 54 | T t; 55 | std::wstringstream ss( *this ); 56 | return ss >> t ? t : T(); 57 | } 58 | std::wstring str() const { 59 | return *this; 60 | } 61 | operator int() const { 62 | return as(); 63 | } 64 | }; 65 | 66 | using vars = std::map; 67 | 68 | int show( const bubble::string &options, const std::function &cb2 = std::function() ); 69 | int show( const bubble::vars &map, const std::function &cb2 = std::function() ); 70 | 71 | void notify( const bubble::string &text, const bubble::string &title ); 72 | void notify( const bubble::string &text, const bubble::string &title, int icon_number ); 73 | void notify( const bubble::string &text, const bubble::string &title, const bubble::string &icon_file ); 74 | } 75 | -------------------------------------------------------------------------------- /sample.cc: -------------------------------------------------------------------------------- 1 | #include "bubble.hpp" 2 | #include 3 | 4 | int main() { 5 | bubble::notify("notify dialog, using icon #19", "hello world", 19); 6 | 7 | bubble::show( 8 | "title.text=About;" 9 | "body.icon=14;" 10 | "body.text=Your app name here;" 11 | "progress=0;" 12 | "footer.icon=-3;" 13 | "footer.text=Made with Bubble dialog library (built " __DATE__ ")", []( bubble::vars &ui ) { 14 | ui["progress"] = ui["progress"] + 10; 15 | } ); 16 | 17 | bubble::show( { 18 | {"title.text", "progress bar"}, 19 | {"body.text", "this is a progress bar"}, 20 | {"body.icon", "warning"}, 21 | {"progress", 0} 22 | }, 23 | []( bubble::vars &ui ) { 24 | ui["progress"] = int(ui["progress"]) + 10; 25 | } 26 | ); 27 | 28 | int choice = bubble::show( bubble::string() 29 | << "body.text=This is a radio dialog;" 30 | << "0.text=" << "Say hello;" 31 | << "1.text=" << "Say goodbye\nsuboption;" 32 | << "2.icon=" << -4 << ";" 33 | << "2.text=Say nothing;" 34 | << "style.command_links=" << false << ";" 35 | << "footer.icon=40;footer.text=Made with Bubble!" ); 36 | 37 | bubble::show( bubble::string() << "body.text=option #" << choice << " selected" ); 38 | 39 | bubble::show( 40 | { 41 | {"0.text", "cancel!"}, 42 | {"1.text", "continue" }, 43 | {"style.minimizable", true}, 44 | {"style.skippable", true}, 45 | {"style.command_links", true}, 46 | {"progress", 0}, 47 | {"body.icon", 18 }, 48 | {"footer.text", "im here"}, 49 | {"timeout", 5000} 50 | }, 51 | []( bubble::vars &ui ) { 52 | static int timer = 0; timer = (++timer)%4; 53 | 54 | ui["progress"] = ui["progress"] + 1; 55 | 56 | ui["title.text"] = L"Modal test"; 57 | 58 | ui["head.text"] = L"Change Password - " + std::to_wstring( ui["timeout"] / 1000 ); 59 | 60 | ui["body.text"] = bubble::string( L"Update in progress " ) + std::wstring( timer, L'.' ); 61 | 62 | ui["footer.text"] = L"This is a footer. Made with Bubble!"; 63 | 64 | ui["footer.icon"] = ( ui["footer.icon"] + 1 ) % 0xFF; 65 | 66 | if( ui["timeout"] <= 0 ) { 67 | ui["exit"] = 0; 68 | } 69 | } ); 70 | 71 | // parent icon 72 | bubble::notify("this is another notify dialog, using parent icon", "hello 2"); 73 | } 74 | --------------------------------------------------------------------------------