UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2023 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "pch.h" 30 31 32 #include "window.h" 33 34 #include "appmenu.h" 35 #include "commandbar.h" 36 #include "container.h" 37 #include "util.h" 38 #include "button.h" 39 40 #include "../common/context.h" 41 #include "../common/object.h" 42 43 #include <stdlib.h> 44 45 #include <cx/mempool.h> 46 47 #include "MainWindow.xaml.h" 48 49 50 #include <Windows.h> 51 #include <shobjidl.h> 52 #include <iostream> 53 54 using namespace winrt; 55 using namespace Microsoft::UI::Xaml; 56 using namespace Microsoft::UI::Xaml::Controls; 57 using namespace Microsoft::UI::Xaml::Controls::Primitives; 58 using namespace Microsoft::UI::Xaml::XamlTypeInfo; 59 using namespace Microsoft::UI::Xaml::Markup; 60 using namespace Windows::UI::Xaml::Interop; 61 using namespace winrt::Windows::Foundation; 62 using namespace winrt::Windows::Storage::Pickers; 63 64 UiWindow::UiWindow(winrt::Microsoft::UI::Xaml::Window& win) : window(win) {} 65 66 UiObject* ui_window(const char* title, void* window_data) { 67 UiObject* obj = ui_simple_window(title, window_data); 68 69 /* 70 if (uic_get_menu_list()) { 71 // create/add menubar 72 MenuBar mb = ui_create_menubar(obj); 73 mb.VerticalAlignment(VerticalAlignment::Top); 74 obj->container->Add(mb, false); 75 } 76 */ 77 78 if (uic_toolbar_isenabled()) { 79 // create a grid for the toolbar: ColumnDefinitions="Auto, *, Auto" 80 Grid toolbar_grid = Grid(); 81 GridLength gl; 82 gl.Value = 0; 83 gl.GridUnitType = GridUnitType::Auto; 84 85 ColumnDefinition coldef0 = ColumnDefinition(); 86 coldef0.Width(gl); 87 toolbar_grid.ColumnDefinitions().Append(coldef0); 88 89 gl.Value = 1; 90 gl.GridUnitType = GridUnitType::Star; 91 ColumnDefinition coldef1 = ColumnDefinition(); 92 coldef1.Width(gl); 93 toolbar_grid.ColumnDefinitions().Append(coldef1); 94 95 gl.Value = 0; 96 gl.GridUnitType = GridUnitType::Auto; 97 ColumnDefinition coldef2 = ColumnDefinition(); 98 coldef2.Width(gl); 99 toolbar_grid.ColumnDefinitions().Append(coldef2); 100 101 // rowdef 102 gl.Value = 0; 103 gl.GridUnitType = GridUnitType::Auto; 104 RowDefinition rowdef = RowDefinition(); 105 rowdef.Height(gl); 106 toolbar_grid.RowDefinitions().Append(rowdef); 107 108 109 // create commandbar 110 CxList* def_l = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT); 111 CxList* def_c = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER); 112 CxList* def_r = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT); 113 114 bool addappmenu = true; 115 if (cxListSize(def_r) > 0) { 116 CommandBar toolbar_r = ui_create_toolbar(obj, def_r, addappmenu); 117 toolbar_grid.SetColumn(toolbar_r, 2); 118 toolbar_grid.SetRow(toolbar_r, 0); 119 toolbar_grid.Children().Append(toolbar_r); 120 addappmenu = false; 121 } 122 if (cxListSize(def_c) > 0) { 123 CommandBar toolbar_c = ui_create_toolbar(obj, def_c, addappmenu); 124 toolbar_c.HorizontalAlignment(HorizontalAlignment::Center); 125 toolbar_grid.SetColumn(toolbar_c, 1); 126 toolbar_grid.SetRow(toolbar_c, 0); 127 toolbar_grid.Children().Append(toolbar_c); 128 addappmenu = false; 129 } 130 if (cxListSize(def_l) > 0) { 131 CommandBar toolbar_l = ui_create_toolbar(obj, def_l, addappmenu); 132 toolbar_grid.SetColumn(toolbar_l, 0); 133 toolbar_grid.SetRow(toolbar_l, 0); 134 toolbar_grid.Children().Append(toolbar_l); 135 } 136 137 toolbar_grid.VerticalAlignment(VerticalAlignment::Top); 138 obj->container->Add(toolbar_grid, false); 139 } 140 141 return obj; 142 } 143 144 UIEXPORT UiObject* ui_simple_window(const char *title, void *window_data) { 145 CxMempool* mp = cxBasicMempoolCreate(256); 146 UiObject* obj = (UiObject*)cxCalloc(mp->allocator, 1, sizeof(UiObject)); 147 148 obj->ctx = uic_context(obj, mp); 149 obj->window = window_data; 150 151 Window window = Window(); 152 //Window window = make<winui::implementation::MainWindow>(); 153 154 winrt::Windows::Foundation::Uri resourceLocator{ "ms-appx:///MainWindow.xaml"L }; 155 Application::LoadComponent(window, resourceLocator, ComponentResourceLocation::Nested); 156 157 window.ExtendsContentIntoTitleBar(true); 158 159 Grid grid = Grid(); 160 window.Content(grid); 161 162 StackPanel titleBar = StackPanel(); 163 Thickness titleBarPadding = { 10, 5, 5, 10 }; 164 titleBar.Padding(titleBarPadding); 165 titleBar.Orientation(Orientation::Horizontal); 166 TextBlock titleLabel = TextBlock(); 167 titleBar.Children().Append(titleLabel); 168 169 if (title) { 170 wchar_t* wtitle = str2wstr(title, nullptr); 171 window.Title(wtitle); 172 titleLabel.Text(hstring(wtitle)); 173 free(wtitle); 174 } 175 176 window.SetTitleBar(titleBar); 177 178 obj->wobj = new UiWindow(window); 179 ui_context_add_window_destructor(obj->ctx, obj->wobj); 180 181 window.Closed([obj](IInspectable const& sender, WindowEventArgs) { 182 if (obj->ctx->close_callback) { 183 UiEvent evt; 184 evt.obj = obj; 185 evt.document = obj->ctx->document; 186 evt.window = obj->window; 187 evt.eventdata = NULL; 188 evt.intval = 0; 189 obj->ctx->close_callback(&evt, obj->ctx->close_data); 190 } else { 191 ui_context_destroy(obj->ctx); 192 } 193 }); 194 195 obj->container = new UiBoxContainer(grid, UI_BOX_CONTAINER_VBOX, 0, 0); 196 197 titleBar.VerticalAlignment(VerticalAlignment::Top); 198 obj->container->Add(titleBar, false); 199 200 obj->window = window_data; 201 202 return obj; 203 } 204 205 static void dialog_button_add_callback(ContentDialog dialog, Button button, int num, UiObject *obj, ui_callback onclick, void *onclickdata) { 206 button.Click([dialog, num, obj, onclick, onclickdata](IInspectable const& sender, RoutedEventArgs) { 207 if (onclick) { 208 UiEvent evt; 209 evt.obj = obj; 210 evt.window = obj->window; 211 evt.document = obj->ctx->document; 212 evt.eventdata = nullptr; 213 evt.intval = num; 214 onclick(&evt, onclickdata); 215 } 216 dialog.Hide(); 217 }); 218 } 219 220 UIEXPORT UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs args) { 221 UiWindow *window = parent->wobj; 222 if (!window) { 223 return NULL; 224 } 225 226 CxMempool* mp = cxBasicMempoolCreate(256); 227 UiObject* obj = (UiObject*)cxCalloc(mp->allocator, 1, sizeof(UiObject)); 228 229 obj->ctx = uic_context(obj, mp); 230 231 ContentDialog dialog = ContentDialog(); 232 UIElement elm = dialog; 233 UiWidget* widget = new UiWidget(elm); 234 ui_context_add_widget_destructor(obj->ctx, widget); 235 obj->widget = widget; 236 237 if (args.title) { 238 wchar_t* wtitle = str2wstr(args.title, nullptr); 239 dialog.Title(box_value(wtitle)); 240 free(wtitle); 241 } 242 243 244 Grid dialogContent = Grid(); 245 GridLength gl; 246 247 // content row 248 gl.Value = 1; 249 gl.GridUnitType = GridUnitType::Star; 250 RowDefinition rowdef0 = RowDefinition(); 251 rowdef0.Height(gl); 252 dialogContent.RowDefinitions().Append(rowdef0); 253 254 // button row 255 gl.Value = 0; 256 gl.GridUnitType = GridUnitType::Auto; 257 RowDefinition rowdef1 = RowDefinition(); 258 rowdef1.Height(gl); 259 dialogContent.RowDefinitions().Append(rowdef1); 260 261 // coldef 262 gl.Value = 1; 263 gl.GridUnitType = GridUnitType::Star; 264 ColumnDefinition coldef = ColumnDefinition(); 265 coldef.Width(gl); 266 dialogContent.ColumnDefinitions().Append(coldef); 267 268 // content 269 Grid grid = Grid(); 270 grid.SetRow(grid, 0); 271 grid.SetColumn(grid, 0); 272 dialogContent.Children().Append(grid); 273 obj->container = new UiBoxContainer(grid, UI_BOX_CONTAINER_VBOX, 0, 0); 274 275 // buttons 276 Grid buttons = Grid(); 277 Thickness btnsMargin = { (double)0, (double)10, (double)0, (double)0 }; 278 buttons.Margin(btnsMargin); 279 280 RowDefinition btnrowdef = RowDefinition(); 281 gl.Value = 0; 282 gl.GridUnitType = GridUnitType::Auto; 283 btnrowdef.Height(gl); 284 buttons.RowDefinitions().Append(btnrowdef); 285 286 gl.Value = 1; 287 gl.GridUnitType = GridUnitType::Auto; 288 int c = 0; 289 if (args.lbutton1) { 290 ColumnDefinition bcoldef = ColumnDefinition(); 291 bcoldef.Width(gl); 292 buttons.ColumnDefinitions().Append(bcoldef); 293 294 Button btn = Button(); 295 ui_set_button_label(btn, args.lbutton1, NULL, NULL, UI_LABEL_TEXT); 296 Thickness margin = { (double)5, (double)5, (double)5, (double)5 }; 297 btn.Margin(margin); 298 btn.HorizontalAlignment(HorizontalAlignment::Stretch); 299 dialog_button_add_callback(dialog, btn, 1, obj, args.onclick, args.onclickdata); 300 301 buttons.SetRow(btn, 0); 302 buttons.SetColumn(btn, c++); 303 buttons.Children().Append(btn); 304 } 305 if (args.lbutton2) { 306 ColumnDefinition bcoldef = ColumnDefinition(); 307 bcoldef.Width(gl); 308 buttons.ColumnDefinitions().Append(bcoldef); 309 310 Button btn = Button(); 311 ui_set_button_label(btn, args.lbutton2, NULL, NULL, UI_LABEL_TEXT); 312 Thickness margin = { (double)5, (double)5, (double)5, (double)5 }; 313 btn.Margin(margin); 314 btn.HorizontalAlignment(HorizontalAlignment::Stretch); 315 dialog_button_add_callback(dialog, btn, 2, obj, args.onclick, args.onclickdata); 316 317 buttons.SetRow(btn, 0); 318 buttons.SetColumn(btn, c++); 319 buttons.Children().Append(btn); 320 } 321 if (args.rbutton3) { 322 ColumnDefinition bcoldef = ColumnDefinition(); 323 bcoldef.Width(gl); 324 buttons.ColumnDefinitions().Append(bcoldef); 325 326 Button btn = Button(); 327 ui_set_button_label(btn, args.rbutton3, NULL, NULL, UI_LABEL_TEXT); 328 Thickness margin = { (double)5, (double)5, (double)5, (double)5 }; 329 btn.Margin(margin); 330 btn.HorizontalAlignment(HorizontalAlignment::Stretch); 331 dialog_button_add_callback(dialog, btn, 3, obj, args.onclick, args.onclickdata); 332 333 buttons.SetRow(btn, 0); 334 buttons.SetColumn(btn, c++); 335 buttons.Children().Append(btn); 336 } 337 if (args.rbutton4) { 338 ColumnDefinition bcoldef = ColumnDefinition(); 339 bcoldef.Width(gl); 340 buttons.ColumnDefinitions().Append(bcoldef); 341 342 Button btn = Button(); 343 ui_set_button_label(btn, args.rbutton4, NULL, NULL, UI_LABEL_TEXT); 344 Thickness margin = { (double)5, (double)5, (double)5, (double)5 }; 345 btn.Margin(margin); 346 btn.HorizontalAlignment(HorizontalAlignment::Stretch); 347 dialog_button_add_callback(dialog, btn, 4, obj, args.onclick, args.onclickdata); 348 349 buttons.SetRow(btn, 0); 350 buttons.SetColumn(btn, c++); 351 buttons.Children().Append(btn); 352 } 353 354 dialogContent.SetRow(buttons, 1); 355 dialogContent.SetColumn(buttons, 0); 356 dialogContent.Children().Append(buttons); 357 358 359 dialog.Content(dialogContent); 360 dialog.XamlRoot(window->window.Content().XamlRoot()); 361 362 obj->widget->Show = [dialog]() { 363 dialog.ShowAsync(); 364 }; 365 366 return obj; 367 } 368 369 void ui_window_size(UiObject *obj, int width, int height) { 370 UIWINDOW win = obj->wobj; 371 if (win) { 372 winrt::Windows::Graphics::SizeInt32 wsize; 373 wsize.Width = width; 374 wsize.Height = height; 375 win->window.AppWindow().Resize(wsize); 376 } 377 } 378 379 380 381 382 static Windows::Foundation::IAsyncAction create_dialog_async(UiObject *obj, UiDialogArgs args) { 383 UiObject* current = uic_current_obj(obj); 384 Window parentWindow = current->wobj->window; 385 386 ContentDialog dialog = ContentDialog(); 387 dialog.XamlRoot(parentWindow.Content().XamlRoot()); 388 389 if (args.title) { 390 wchar_t *str = str2wstr(args.title, nullptr); 391 dialog.Title(winrt::box_value(str)); 392 free(str); 393 } 394 395 TextBox textfield{ nullptr }; 396 PasswordBox password{ nullptr }; 397 if(args.input || args.password) { 398 StackPanel panel = StackPanel(); 399 panel.Orientation(Orientation::Vertical); 400 if (args.content) { 401 wchar_t *str = str2wstr(args.content, nullptr); 402 TextBlock label = TextBlock(); 403 label.Text(str); 404 panel.Children().Append(label); 405 free(str); 406 } 407 408 Thickness margin = { 0, 5, 0, 0 }; 409 if (args.password) { 410 password = PasswordBox(); 411 password.Margin(margin); 412 panel.Children().Append(password); 413 } else { 414 textfield = TextBox(); 415 textfield.Margin(margin); 416 panel.Children().Append(textfield); 417 } 418 419 panel.Margin(margin); 420 421 dialog.Content(panel); 422 423 } else { 424 if (args.content) { 425 wchar_t *str = str2wstr(args.content, nullptr); 426 dialog.Content(winrt::box_value(str)); 427 free(str); 428 } 429 } 430 431 if (args.button1_label) { 432 wchar_t *str = str2wstr(args.button1_label, nullptr); 433 dialog.PrimaryButtonText(winrt::hstring(str)); 434 free(str); 435 dialog.DefaultButton(ContentDialogButton::Primary); 436 } 437 if (args.button2_label) { 438 wchar_t *str = str2wstr(args.button2_label, nullptr); 439 dialog.SecondaryButtonText(winrt::hstring(str)); 440 free(str); 441 } 442 if (args.closebutton_label) { 443 wchar_t *str = str2wstr(args.closebutton_label, nullptr); 444 dialog.CloseButtonText(winrt::hstring(str)); 445 free(str); 446 } 447 448 ContentDialogResult result = co_await dialog.ShowAsync(); 449 450 if (args.result) { 451 UiEvent evt; 452 evt.obj = current; 453 evt.document = current->ctx->document; 454 evt.window = current->window; 455 evt.eventdata = NULL; 456 evt.intval = 0; 457 if (result == ContentDialogResult::Primary) { 458 evt.intval = 1; 459 } else if (result == ContentDialogResult::Secondary) { 460 evt.intval = 2; 461 } 462 463 if (args.password) { 464 std::wstring wstr(password.Password()); 465 char *text = wchar2utf8(wstr.c_str(), wstr.length()); 466 evt.eventdata = text; 467 } else if (args.input) { 468 std::wstring wstr(textfield.Text()); 469 char *text = wchar2utf8(wstr.c_str(), wstr.length()); 470 evt.eventdata = text; 471 } 472 473 args.result(&evt, args.resultdata); 474 475 if (evt.eventdata) { 476 free(evt.eventdata); 477 } 478 } 479 } 480 481 UIEXPORT void ui_dialog_create(UiObject *obj, UiDialogArgs args) { 482 create_dialog_async(obj, args); 483 } 484 485 486 487 // --------------------------------------- File Dialog --------------------------------------- 488 489 static void filedialog_callback( 490 UiObject *obj, 491 ui_callback file_selected_callback, 492 void *cbdata, 493 winrt::Windows::Foundation::Collections::IVectorView<winrt::Windows::Storage::StorageFile> result) 494 { 495 UiFileList flist; 496 flist.nfiles = result.Size(); 497 flist.files = new char*[flist.nfiles]; 498 499 int i = 0; 500 for (auto const& file : result) { 501 winrt::hstring path = file.Path(); 502 flist.files[i++] = wchar2utf8(path.c_str(), path.size()); 503 } 504 505 UiEvent evt; 506 evt.obj = obj; 507 evt.document = obj->ctx->document; 508 evt.window = obj->window; 509 evt.eventdata = &flist; 510 evt.intval = 0; 511 file_selected_callback(&evt, cbdata); 512 513 for (int i = 0; i < flist.nfiles;i++) { 514 free(flist.files[i]); 515 } 516 delete[] flist.files; 517 } 518 519 static Windows::Foundation::IAsyncAction open_filedialog_async(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) { 520 FileOpenPicker openFileDialog = FileOpenPicker(); 521 auto initializeWithWindow { openFileDialog.as<::IInitializeWithWindow>() 522 }; 523 524 HWND hwnd{ nullptr }; 525 winrt::check_hresult(obj->wobj->window.as<IWindowNative>()->get_WindowHandle(&hwnd)); 526 527 initializeWithWindow->Initialize(hwnd); 528 529 openFileDialog.FileTypeFilter().Append("*"L); 530 531 if ((mode & UI_FILEDIALOG_SELECT_MULTI) == UI_FILEDIALOG_SELECT_MULTI) { 532 auto files = co_await openFileDialog.PickMultipleFilesAsync(); 533 filedialog_callback(obj, file_selected_callback, cbdata, files); 534 } else { 535 auto file = co_await openFileDialog.PickSingleFileAsync(); 536 auto files = single_threaded_vector<winrt::Windows::Storage::StorageFile>(); 537 files.Append(file); 538 filedialog_callback(obj, file_selected_callback, cbdata, files.GetView()); 539 } 540 } 541 542 static Windows::Foundation::IAsyncAction save_filedialog_async(UiObject *obj, char *name, ui_callback file_selected_callback, void *cbdata) { 543 IFileSaveDialog *saveFileDialog; 544 545 HRESULT hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_ALL, IID_IFileSaveDialog, reinterpret_cast<void**>(&saveFileDialog)); 546 if (FAILED(hr)) 547 { 548 co_return; 549 } 550 551 if (name) { 552 wchar_t *wname = str2wstr(name, NULL); 553 saveFileDialog->SetFileName(wname); 554 free(wname); 555 free(name); 556 } 557 558 559 hr = saveFileDialog->Show(NULL); 560 if (SUCCEEDED(hr)) { 561 IShellItem *item; 562 hr = saveFileDialog->GetResult(&item); 563 if (SUCCEEDED(hr)) { 564 PWSTR wpath; 565 hr = item->GetDisplayName(SIGDN_FILESYSPATH, &wpath); 566 567 if (SUCCEEDED(hr)) { 568 char *path = wchar2utf8(wpath, lstrlen(wpath)); 569 CoTaskMemFree(wpath); 570 571 UiFileList flist; 572 flist.nfiles = 1; 573 flist.files = new char*[1]; 574 flist.files[0] = path; 575 576 UiEvent evt; 577 evt.obj = obj; 578 evt.document = obj->ctx->document; 579 evt.window = obj->window; 580 evt.eventdata = &flist; 581 evt.intval = 0; 582 file_selected_callback(&evt, cbdata); 583 584 free(path); 585 delete[] flist.files; 586 } 587 item->Release(); 588 } 589 } 590 591 // cleanup 592 saveFileDialog->Release(); 593 } 594 595 static Windows::Foundation::IAsyncAction folderdialog_async(UiObject *obj, ui_callback file_selected_callback, void *cbdata) { 596 FolderPicker folderPicker = FolderPicker(); 597 auto initializeWithWindow { folderPicker.as<::IInitializeWithWindow>() 598 }; 599 600 HWND hwnd{ nullptr }; 601 winrt::check_hresult(obj->wobj->window.as<IWindowNative>()->get_WindowHandle(&hwnd)); 602 603 initializeWithWindow->Initialize(hwnd); 604 605 folderPicker.FileTypeFilter().Append("*"L); 606 607 auto folder = co_await folderPicker.PickSingleFolderAsync(); 608 if (folder) { 609 winrt::hstring hpath = folder.Path(); 610 char *cpath = wchar2utf8(hpath.c_str(), hpath.size()); 611 612 UiFileList flist; 613 flist.nfiles = 1; 614 flist.files = &cpath; 615 616 UiEvent evt; 617 evt.obj = obj; 618 evt.document = obj->ctx->document; 619 evt.window = obj->window; 620 evt.eventdata = &flist; 621 evt.intval = 0; 622 file_selected_callback(&evt, cbdata); 623 } 624 } 625 626 UIEXPORT void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) { 627 if ((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) { 628 folderdialog_async(obj, file_selected_callback, cbdata); 629 } else { 630 open_filedialog_async(obj, mode, file_selected_callback, cbdata); 631 } 632 } 633 634 UIEXPORT void ui_savefiledialog(UiObject *obj, const char *name, ui_callback file_selected_callback, void *cbdata) { 635 char *n = name ? _strdup(name) : NULL; 636 save_filedialog_async(obj, n, file_selected_callback, cbdata); 637 } 638