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