| 2458 | } |
| 2459 | |
| 2460 | void ImageViewer::saveImageDialog() { |
| 2461 | if (!mCurrentImage) { |
| 2462 | return; |
| 2463 | } |
| 2464 | |
| 2465 | if (mFileDialogThread) { |
| 2466 | tlog::warning("File dialog already running."); |
| 2467 | return; |
| 2468 | } |
| 2469 | |
| 2470 | const auto runDialog = [this]() { |
| 2471 | const auto threadGuard = ScopeGuard{[this]() { |
| 2472 | scheduleToUiThread([this]() { |
| 2473 | focusWindow(); |
| 2474 | if (mFileDialogThread && mFileDialogThread->joinable()) { |
| 2475 | mFileDialogThread->join(); |
| 2476 | } |
| 2477 | |
| 2478 | mFileDialogThread = nullptr; |
| 2479 | }); |
| 2480 | }}; |
| 2481 | |
| 2482 | try { |
| 2483 | const auto paths = file_dialog( |
| 2484 | this, |
| 2485 | FileDialogType::Save, |
| 2486 | { |
| 2487 | {"exr", "OpenEXR image" }, |
| 2488 | {"hdr", "HDR image" }, |
| 2489 | {"bmp", "Bitmap Image File" }, |
| 2490 | {"jpg,jpeg", "JPEG image" }, |
| 2491 | {"jxl", "JPEG-XL image" }, |
| 2492 | {"png", "Portable Network Graphics image"}, |
| 2493 | {"qoi", "Quite OK Image format" }, |
| 2494 | {"tga", "Truevision TGA image" }, |
| 2495 | } |
| 2496 | ); |
| 2497 | |
| 2498 | if (paths.empty() || paths.front().empty()) { |
| 2499 | return; |
| 2500 | } |
| 2501 | |
| 2502 | scheduleToUiThread([this, path = paths.front()]() { |
| 2503 | try { |
| 2504 | mImageCanvas->saveImage(path); |
| 2505 | } catch (const ImageSaveError& e) { showErrorDialog(fmt::format("Failed to save image: {}", e.what())); } |
| 2506 | }); |
| 2507 | } catch (const runtime_error& e) { |
| 2508 | const auto error = fmt::format("Save dialog: {}", e.what()); |
| 2509 | scheduleToUiThread([this, error]() { showErrorDialog(error); }); |
| 2510 | } |
| 2511 | }; |
| 2512 | |
| 2513 | #if defined(__APPLE__) || defined(_WIN32) |
| 2514 | runDialog(); |
| 2515 | #else |
| 2516 | mFileDialogThread = make_unique<thread>(runDialog); |
| 2517 | #endif |