LCOV - code coverage report
Current view: top level - src/gui/dialogs - file_dialog_native.cpp (source / functions) Coverage Total Hit
Test: merged.info Lines: 2.6 % 77 2
Test Date: 2026-06-01 11:15:25 Functions: 100.0 % 1 1

            Line data    Source code
       1              : // =============================================================================
       2              : // Native file dialog implementations (Windows, macOS, Linux)
       3              : // Save dialog implementation
       4              : // =============================================================================
       5              : 
       6              : #include "gui/dialogs/file_dialog.h"
       7              : #include <cstring>
       8              : 
       9              : #ifdef _WIN32
      10              : #define WIN32_LEAN_AND_MEAN
      11              : #include <windows.h>
      12              : #include <commdlg.h>
      13              : #include <shlobj.h>
      14              : #endif
      15              : 
      16              : #ifdef __APPLE__
      17              : #include <TargetConditionals.h>
      18              : #include <cstdio>
      19              : #include <unistd.h>
      20              : #include <sys/wait.h>
      21              : #include <fcntl.h>
      22              : #endif
      23              : 
      24              : #ifndef _WIN32
      25              : #include <sys/wait.h>
      26              : #endif
      27              : 
      28              : namespace Amplitron {
      29              : 
      30              : #ifdef AMPLITRON_HEADLESS
      31            6 : std::string show_save_dialog(const std::string&, const std::string&, const std::string&) {
      32            8 :     return "";
      33              : }
      34              : #else
      35              : 
      36              : #ifdef _WIN32
      37            0 : std::string show_save_dialog(const std::string& default_name,
      38              :                              const std::string& filter_desc,
      39              :                              const std::string& filter_ext) {
      40            0 :     char filename[MAX_PATH];
      41            0 :     std::strncpy(filename, default_name.c_str(), MAX_PATH - 1);
      42            0 :     filename[MAX_PATH - 1] = '\0';
      43              : 
      44              :     // Build filter string: "WAV Audio (*.wav)\0*.wav\0All Files (*.*)\0*.*\0\0"
      45            0 :     char filter[256];
      46            0 :     std::memset(filter, 0, sizeof(filter));
      47            0 :     int pos = 0;
      48            0 :     pos += snprintf(filter + pos, 256 - pos, "%s (*.%s)", filter_desc.c_str(), filter_ext.c_str());
      49            0 :     pos++; // null separator
      50            0 :     pos += snprintf(filter + pos, 256 - pos, "*.%s", filter_ext.c_str());
      51            0 :     pos++; // null separator
      52            0 :     pos += snprintf(filter + pos, 256 - pos, "All Files (*.*)");
      53            0 :     pos++;
      54            0 :     pos += snprintf(filter + pos, 256 - pos, "*.*");
      55              :     // double null terminator is already there from memset
      56              : 
      57              :     // Get desktop/documents as initial dir
      58            0 :     char initial_dir[MAX_PATH] = "";
      59            0 :     SHGetFolderPathA(NULL, CSIDL_DESKTOP, NULL, 0, initial_dir);
      60              : 
      61            0 :     OPENFILENAMEA ofn;
      62            0 :     std::memset(&ofn, 0, sizeof(ofn));
      63            0 :     ofn.lStructSize = sizeof(ofn);
      64            0 :     ofn.hwndOwner = NULL;
      65            0 :     ofn.lpstrFilter = filter;
      66            0 :     ofn.lpstrFile = filename;
      67            0 :     ofn.nMaxFile = MAX_PATH;
      68            0 :     ofn.lpstrInitialDir = initial_dir;
      69            0 :     ofn.lpstrTitle = "Save Recording As";
      70            0 :     ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR;
      71            0 :     ofn.lpstrDefExt = filter_ext.c_str();
      72              : 
      73            0 :     if (GetSaveFileNameA(&ofn)) {
      74            0 :         return std::string(filename);
      75              :     }
      76            0 :     return "";
      77              : }
      78              : 
      79              : #elif defined(__APPLE__) && !TARGET_OS_IOS
      80            0 : std::string show_save_dialog(const std::string& default_name,
      81              :                              const std::string& /*filter_desc*/,
      82              :                              const std::string& filter_ext) {
      83              :     // Use osascript to show a native NSSavePanel
      84            0 :     std::string cmd = "osascript -e 'set theFile to POSIX path of (choose file name "
      85              :                       "with prompt \"Save Recording As\" "
      86            0 :                       "default name \"" + default_name + "\")' 2>/dev/null";
      87              : 
      88            0 :     FILE* pipe = popen(cmd.c_str(), "r");
      89            0 :     if (!pipe) return "";
      90              : 
      91              :     char buf[1024];
      92            0 :     std::string result;
      93            0 :     while (fgets(buf, sizeof(buf), pipe)) {
      94            0 :         result += buf;
      95              :     }
      96            0 :     pclose(pipe);
      97              : 
      98              :     // Trim trailing newline
      99            0 :     while (!result.empty() && (result.back() == '\n' || result.back() == '\r'))
     100            0 :         result.pop_back();
     101              : 
     102            0 :     if (result.empty()) return "";
     103              : 
     104              :     // Ensure it ends with the correct extension
     105            0 :     if (result.size() < filter_ext.size() + 1 ||
     106            0 :         result.substr(result.size() - filter_ext.size() - 1) != "." + filter_ext) {
     107            0 :         result += "." + filter_ext;
     108            0 :     }
     109              : 
     110            0 :     return result;
     111            0 : }
     112              : 
     113              : #else // Linux
     114            0 : std::string show_save_dialog(const std::string& default_name,
     115              :                              const std::string& filter_desc,
     116              :                              const std::string& filter_ext) {
     117              :     // Try zenity first, then kdialog
     118              :     std::string cmd = "zenity --file-selection --save --confirm-overwrite "
     119              :                       "--title='Save Recording As' "
     120            0 :                       "--filename='" + default_name + "' "
     121            0 :                       "--file-filter='" + filter_desc + " (*." + filter_ext + ")|*." + filter_ext + "' "
     122            0 :                       "--file-filter='All Files (*)|*' 2>/dev/null";
     123              : 
     124            0 :     FILE* pipe = popen(cmd.c_str(), "r");
     125            0 :     if (!pipe) return "";
     126              : 
     127              :     char buf[1024];
     128            0 :     std::string result;
     129            0 :     while (fgets(buf, sizeof(buf), pipe)) {
     130            0 :         result += buf;
     131              :     }
     132            0 :     int status = pclose(pipe);
     133              : 
     134              :     // If zenity failed (not installed), try kdialog
     135            0 :     if (status != 0) {
     136            0 :         cmd = "kdialog --getsavefilename ~/ '*." + filter_ext + "|" + filter_desc + "' "
     137            0 :               "--title 'Save Recording As' 2>/dev/null";
     138            0 :         pipe = popen(cmd.c_str(), "r");
     139            0 :         if (!pipe) return "";
     140            0 :         result.clear();
     141            0 :         while (fgets(buf, sizeof(buf), pipe)) {
     142            0 :             result += buf;
     143              :         }
     144            0 :         pclose(pipe);
     145              :     }
     146              : 
     147              :     // Trim trailing newline
     148            0 :     while (!result.empty() && (result.back() == '\n' || result.back() == '\r'))
     149            0 :         result.pop_back();
     150              : 
     151            0 :     if (result.empty()) return "";
     152              : 
     153              :     // Ensure it ends with the correct extension
     154            0 :     if (result.size() < filter_ext.size() + 1 ||
     155            0 :         result.substr(result.size() - filter_ext.size() - 1) != "." + filter_ext) {
     156            0 :         result += "." + filter_ext;
     157              :     }
     158              : 
     159            0 :     return result;
     160            0 : }
     161              : #endif
     162              : 
     163              : #endif // AMPLITRON_HEADLESS
     164              : 
     165              : } // namespace Amplitron
        

Generated by: LCOV version 2.0-1