1 #include "UserInteractions.h"
2
3 #include <windows.h>
4 #include <shlobj.h>
5 #include <commdlg.h>
6 #include <string>
7 #include <fstream>
8 #include <sstream>
9 #include <system_error>
10
11 namespace
12 {
13
wideToUtf8(const std::wstring & wide)14 std::string wideToUtf8(const std::wstring &wide)
15 {
16 if (wide.empty())
17 return std::string();
18
19 const auto size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, wide.c_str(), wide.size(),
20 nullptr, 0, nullptr, nullptr);
21 if (size)
22 {
23 std::string utf8;
24 utf8.resize(size);
25 if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, wide.c_str(), wide.size(), &utf8[0],
26 utf8.size(), nullptr, nullptr))
27 return utf8;
28 }
29 throw std::system_error(GetLastError(), std::system_category());
30 }
31
utf8ToWide(const std::string & utf8)32 std::wstring utf8ToWide(const std::string &utf8)
33 {
34 if (utf8.empty())
35 return std::wstring();
36
37 const auto size =
38 MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8.c_str(), utf8.size(), nullptr, 0);
39 if (size)
40 {
41 std::wstring wide;
42 wide.resize(size);
43 if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8.c_str(), utf8.size(), &wide[0],
44 wide.size()))
45 return wide;
46 }
47 throw std::system_error(GetLastError(), std::system_category());
48 }
49
browseFolder(const std::string & initialDirectory,const std::function<void (std::string)> & callbackOnOpen)50 void browseFolder(const std::string &initialDirectory,
51 const std::function<void(std::string)> &callbackOnOpen)
52 {
53 struct BrowseCallback
54 {
55 static int CALLBACK proc(HWND hwnd, UINT uMsg, LPARAM, LPARAM lpData)
56 {
57 if (uMsg == BFFM_INITIALIZED)
58 SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);
59 return 0;
60 }
61 };
62
63 auto path_param = utf8ToWide(initialDirectory);
64
65 BROWSEINFO bi{};
66 bi.lpszTitle = L"Browse for folder...";
67 bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
68 bi.lpfn = BrowseCallback::proc;
69 bi.lParam = LPARAM(path_param.c_str());
70
71 if (auto pidl = SHBrowseForFolder(&bi))
72 {
73 WCHAR path[MAX_PATH];
74 SHGetPathFromIDList(&*pidl, path);
75 CoTaskMemFree(pidl);
76 callbackOnOpen(wideToUtf8(path));
77 }
78 }
79
80 } // anonymous namespace
81
82 namespace Surge::UserInteractions
83 {
84
promptError(const std::string & message,const std::string & title,SurgeGUIEditor * guiEditor)85 void promptError(const std::string &message, const std::string &title, SurgeGUIEditor *guiEditor)
86 {
87 MessageBox(::GetActiveWindow(), utf8ToWide(message).c_str(), utf8ToWide(title).c_str(),
88 MB_OK | MB_ICONERROR);
89 }
90
promptInfo(const std::string & message,const std::string & title,SurgeGUIEditor * guiEditor)91 void promptInfo(const std::string &message, const std::string &title, SurgeGUIEditor *guiEditor)
92 {
93 MessageBox(::GetActiveWindow(), utf8ToWide(message).c_str(), utf8ToWide(title).c_str(),
94 MB_OK | MB_ICONINFORMATION);
95 }
96
promptOKCancel(const std::string & message,const std::string & title,SurgeGUIEditor * guiEditor)97 MessageResult promptOKCancel(const std::string &message, const std::string &title,
98 SurgeGUIEditor *guiEditor)
99 {
100 if (MessageBox(::GetActiveWindow(), utf8ToWide(message).c_str(), utf8ToWide(title).c_str(),
101 MB_YESNO | MB_ICONQUESTION) != IDYES)
102 return UserInteractions::CANCEL;
103
104 return UserInteractions::OK;
105 }
106
openURL(const std::string & url)107 void openURL(const std::string &url)
108 {
109 ShellExecute(nullptr, L"open", utf8ToWide(url).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
110 }
111
showHTML(const std::string & html)112 void showHTML(const std::string &html)
113 {
114 WCHAR pathBuf[MAX_PATH];
115 if (!GetTempPath(MAX_PATH, pathBuf))
116 return;
117
118 // Prepending file:// opens in the browser instead of the associated app for .html (which might
119 // be unset)
120 std::wostringstream fns;
121 fns << L"file://" << pathBuf << L"surge-data." << rand() << L".html";
122
123 auto fileName(fns.str());
124 std::ofstream out(fileName.c_str() + 7);
125 if (out << html << std::flush)
126 ShellExecute(nullptr, L"open", fileName.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
127 }
128
openFolderInFileBrowser(const std::string & folder)129 void openFolderInFileBrowser(const std::string &folder) { openURL(folder); }
130
promptFileOpenDialog(const std::string & initialDirectory,const std::string & filterSuffix,const std::string & filterDescription,std::function<void (std::string)> callbackOnOpen,bool canSelectDirectories,bool canCreateDirectories,SurgeGUIEditor * guiEditor)131 void promptFileOpenDialog(const std::string &initialDirectory, const std::string &filterSuffix,
132 const std::string &filterDescription,
133 std::function<void(std::string)> callbackOnOpen,
134 bool canSelectDirectories, bool canCreateDirectories,
135 SurgeGUIEditor *guiEditor)
136 {
137 if (canSelectDirectories)
138 {
139 browseFolder(initialDirectory, callbackOnOpen);
140 return;
141 }
142
143 // With many thanks to
144 // https://www.daniweb.com/programming/software-development/code/217307/a-simple-getopenfilename-example
145
146 // this also helped!
147 // https://stackoverflow.com/questions/34201213/c-lpstr-and-string-trouble-with-zero-terminated-strings
148 std::wstring fullFilter;
149 fullFilter.append(utf8ToWide(filterDescription));
150 fullFilter.push_back(0);
151 fullFilter.append(utf8ToWide("*" + filterSuffix));
152 fullFilter.push_back(0);
153
154 std::wstring initialDirectoryNative(utf8ToWide(initialDirectory));
155 WCHAR szFile[1024];
156 OPENFILENAME ofn = {};
157 ofn.lStructSize = sizeof(ofn);
158 ofn.hwndOwner = nullptr;
159 ofn.lpstrFile = szFile;
160 ofn.lpstrFile[0] = 0;
161 ofn.nMaxFile = std::size(szFile);
162 ofn.lpstrFilter = fullFilter.c_str();
163 ofn.nFilterIndex = 0;
164 ofn.lpstrFileTitle = nullptr;
165 ofn.nMaxFileTitle = 0;
166 ofn.lpstrInitialDir = initialDirectoryNative.c_str();
167 ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
168
169 if (GetOpenFileName(&ofn))
170 callbackOnOpen(wideToUtf8(ofn.lpstrFile));
171 }
172
173 } // namespace Surge::UserInteractions
174