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
|