1 | /* |
2 | nanogui/nanogui.cpp -- Basic initialization and utility routines |
3 | |
4 | NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>. |
5 | The widget drawing code is based on the NanoVG demo application |
6 | by Mikko Mononen. |
7 | |
8 | All rights reserved. Use of this source code is governed by a |
9 | BSD-style license that can be found in the LICENSE.txt file. |
10 | */ |
11 | |
12 | #include <nanogui/screen.h> |
13 | |
14 | #if defined(_WIN32) |
15 | # include <windows.h> |
16 | #endif |
17 | |
18 | #include <nanogui/opengl.h> |
19 | #include <map> |
20 | #include <thread> |
21 | #include <chrono> |
22 | #include <iostream> |
23 | |
24 | #if !defined(_WIN32) |
25 | # include <locale.h> |
26 | # include <signal.h> |
27 | # include <dirent.h> |
28 | #endif |
29 | |
30 | NAMESPACE_BEGIN(nanogui) |
31 | |
32 | extern std::map<GLFWwindow *, Screen *> __nanogui_screens; |
33 | |
34 | #if defined(__APPLE__) |
35 | extern void disable_saved_application_state_osx(); |
36 | #endif |
37 | |
38 | void init() { |
39 | #if !defined(_WIN32) |
40 | /* Avoid locale-related number parsing issues */ |
41 | setlocale(LC_NUMERIC, "C" ); |
42 | #endif |
43 | |
44 | #if defined(__APPLE__) |
45 | disable_saved_application_state_osx(); |
46 | #endif |
47 | |
48 | glfwSetErrorCallback( |
49 | [](int error, const char *descr) { |
50 | if (error == GLFW_NOT_INITIALIZED) |
51 | return; /* Ignore */ |
52 | std::cerr << "GLFW error " << error << ": " << descr << std::endl; |
53 | } |
54 | ); |
55 | |
56 | if (!glfwInit()) |
57 | throw std::runtime_error("Could not initialize GLFW!" ); |
58 | |
59 | glfwSetTime(0); |
60 | } |
61 | |
62 | static bool mainloop_active = false; |
63 | |
64 | void mainloop(int refresh) { |
65 | if (mainloop_active) |
66 | throw std::runtime_error("Main loop is already running!" ); |
67 | |
68 | mainloop_active = true; |
69 | |
70 | std::thread refresh_thread; |
71 | if (refresh > 0) { |
72 | /* If there are no mouse/keyboard events, try to refresh the |
73 | view roughly every 50 ms (default); this is to support animations |
74 | such as progress bars while keeping the system load |
75 | reasonably low */ |
76 | refresh_thread = std::thread( |
77 | [refresh]() { |
78 | std::chrono::milliseconds time(refresh); |
79 | while (mainloop_active) { |
80 | std::this_thread::sleep_for(time); |
81 | glfwPostEmptyEvent(); |
82 | } |
83 | } |
84 | ); |
85 | } |
86 | |
87 | try { |
88 | while (mainloop_active) { |
89 | int numScreens = 0; |
90 | for (auto kv : __nanogui_screens) { |
91 | Screen *screen = kv.second; |
92 | if (!screen->visible()) { |
93 | continue; |
94 | } else if (glfwWindowShouldClose(screen->glfwWindow())) { |
95 | screen->setVisible(false); |
96 | continue; |
97 | } |
98 | screen->drawAll(); |
99 | numScreens++; |
100 | } |
101 | |
102 | if (numScreens == 0) { |
103 | /* Give up if there was nothing to draw */ |
104 | mainloop_active = false; |
105 | break; |
106 | } |
107 | |
108 | /* Wait for mouse/keyboard or empty refresh events */ |
109 | glfwWaitEvents(); |
110 | } |
111 | |
112 | /* Process events once more */ |
113 | glfwPollEvents(); |
114 | } catch (const std::exception &e) { |
115 | std::cerr << "Caught exception in main loop: " << e.what() << std::endl; |
116 | leave(); |
117 | } |
118 | |
119 | if (refresh > 0) |
120 | refresh_thread.join(); |
121 | } |
122 | |
123 | void leave() { |
124 | mainloop_active = false; |
125 | } |
126 | |
127 | bool active() { |
128 | return mainloop_active; |
129 | } |
130 | |
131 | void shutdown() { |
132 | glfwTerminate(); |
133 | } |
134 | |
135 | std::array<char, 8> utf8(int c) { |
136 | std::array<char, 8> seq; |
137 | int n = 0; |
138 | if (c < 0x80) n = 1; |
139 | else if (c < 0x800) n = 2; |
140 | else if (c < 0x10000) n = 3; |
141 | else if (c < 0x200000) n = 4; |
142 | else if (c < 0x4000000) n = 5; |
143 | else if (c <= 0x7fffffff) n = 6; |
144 | seq[n] = '\0'; |
145 | switch (n) { |
146 | case 6: seq[5] = 0x80 | (c & 0x3f); c = c >> 6; c |= 0x4000000; |
147 | case 5: seq[4] = 0x80 | (c & 0x3f); c = c >> 6; c |= 0x200000; |
148 | case 4: seq[3] = 0x80 | (c & 0x3f); c = c >> 6; c |= 0x10000; |
149 | case 3: seq[2] = 0x80 | (c & 0x3f); c = c >> 6; c |= 0x800; |
150 | case 2: seq[1] = 0x80 | (c & 0x3f); c = c >> 6; c |= 0xc0; |
151 | case 1: seq[0] = c; |
152 | } |
153 | return seq; |
154 | } |
155 | |
156 | int __nanogui_get_image(NVGcontext *ctx, const std::string &name, uint8_t *data, uint32_t size) { |
157 | static std::map<std::string, int> iconCache; |
158 | auto it = iconCache.find(name); |
159 | if (it != iconCache.end()) |
160 | return it->second; |
161 | int iconID = nvgCreateImageMem(ctx, 0, data, size); |
162 | if (iconID == 0) |
163 | throw std::runtime_error("Unable to load resource data." ); |
164 | iconCache[name] = iconID; |
165 | return iconID; |
166 | } |
167 | |
168 | std::vector<std::pair<int, std::string>> |
169 | loadImageDirectory(NVGcontext *ctx, const std::string &path) { |
170 | std::vector<std::pair<int, std::string> > result; |
171 | #if !defined(_WIN32) |
172 | DIR *dp = opendir(path.c_str()); |
173 | if (!dp) |
174 | throw std::runtime_error("Could not open image directory!" ); |
175 | struct dirent *ep; |
176 | while ((ep = readdir(dp))) { |
177 | const char *fname = ep->d_name; |
178 | #else |
179 | WIN32_FIND_DATA ffd; |
180 | std::string searchPath = path + "/*.*" ; |
181 | HANDLE handle = FindFirstFileA(searchPath.c_str(), &ffd); |
182 | if (handle == INVALID_HANDLE_VALUE) |
183 | throw std::runtime_error("Could not open image directory!" ); |
184 | do { |
185 | const char *fname = ffd.cFileName; |
186 | #endif |
187 | if (strstr(fname, "png" ) == nullptr) |
188 | continue; |
189 | std::string fullName = path + "/" + std::string(fname); |
190 | int img = nvgCreateImage(ctx, fullName.c_str(), 0); |
191 | if (img == 0) |
192 | throw std::runtime_error("Could not open image data!" ); |
193 | result.push_back( |
194 | std::make_pair(img, fullName.substr(0, fullName.length() - 4))); |
195 | #if !defined(_WIN32) |
196 | } |
197 | closedir(dp); |
198 | #else |
199 | } while (FindNextFileA(handle, &ffd) != 0); |
200 | FindClose(handle); |
201 | #endif |
202 | return result; |
203 | } |
204 | |
205 | std::string file_dialog(const std::vector<std::pair<std::string, std::string>> &filetypes, bool save) { |
206 | auto result = file_dialog(filetypes, save, false); |
207 | return result.empty() ? "" : result.front(); |
208 | } |
209 | |
210 | #if !defined(__APPLE__) |
211 | std::vector<std::string> file_dialog(const std::vector<std::pair<std::string, std::string>> &filetypes, bool save, bool multiple) { |
212 | static const int FILE_DIALOG_MAX_BUFFER = 16384; |
213 | if (save && multiple) { |
214 | throw std::invalid_argument("save and multiple must not both be true." ); |
215 | } |
216 | |
217 | #if defined(_WIN32) |
218 | OPENFILENAMEW ofn; |
219 | ZeroMemory(&ofn, sizeof(OPENFILENAMEW)); |
220 | ofn.lStructSize = sizeof(OPENFILENAMEW); |
221 | wchar_t tmp[FILE_DIALOG_MAX_BUFFER]; |
222 | ofn.lpstrFile = tmp; |
223 | ZeroMemory(tmp, sizeof(tmp)); |
224 | ofn.nMaxFile = FILE_DIALOG_MAX_BUFFER; |
225 | ofn.nFilterIndex = 1; |
226 | |
227 | std::string filter; |
228 | |
229 | if (!save && filetypes.size() > 1) { |
230 | filter.append("Supported file types (" ); |
231 | for (size_t i = 0; i < filetypes.size(); ++i) { |
232 | filter.append("*." ); |
233 | filter.append(filetypes[i].first); |
234 | if (i + 1 < filetypes.size()) |
235 | filter.append(";" ); |
236 | } |
237 | filter.append(")" ); |
238 | filter.push_back('\0'); |
239 | for (size_t i = 0; i < filetypes.size(); ++i) { |
240 | filter.append("*." ); |
241 | filter.append(filetypes[i].first); |
242 | if (i + 1 < filetypes.size()) |
243 | filter.append(";" ); |
244 | } |
245 | filter.push_back('\0'); |
246 | } |
247 | for (auto pair : filetypes) { |
248 | filter.append(pair.second); |
249 | filter.append(" (*." ); |
250 | filter.append(pair.first); |
251 | filter.append(")" ); |
252 | filter.push_back('\0'); |
253 | filter.append("*." ); |
254 | filter.append(pair.first); |
255 | filter.push_back('\0'); |
256 | } |
257 | filter.push_back('\0'); |
258 | |
259 | int size = MultiByteToWideChar(CP_UTF8, 0, &filter[0], (int)filter.size(), NULL, 0); |
260 | std::wstring wfilter(size, 0); |
261 | MultiByteToWideChar(CP_UTF8, 0, &filter[0], (int)filter.size(), &wfilter[0], size); |
262 | |
263 | ofn.lpstrFilter = wfilter.data(); |
264 | |
265 | if (save) { |
266 | ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT; |
267 | if (GetSaveFileNameW(&ofn) == FALSE) |
268 | return {}; |
269 | } else { |
270 | ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; |
271 | if (multiple) |
272 | ofn.Flags |= OFN_ALLOWMULTISELECT; |
273 | if (GetOpenFileNameW(&ofn) == FALSE) |
274 | return {}; |
275 | } |
276 | |
277 | size_t i = 0; |
278 | std::vector<std::string> result; |
279 | while (tmp[i] != '\0') { |
280 | std::string filename; |
281 | int tmpSize = (int)wcslen(&tmp[i]); |
282 | if (tmpSize > 0) { |
283 | int filenameSize = WideCharToMultiByte(CP_UTF8, 0, &tmp[i], tmpSize, NULL, 0, NULL, NULL); |
284 | filename.resize(filenameSize, 0); |
285 | WideCharToMultiByte(CP_UTF8, 0, &tmp[i], tmpSize, &filename[0], filenameSize, NULL, NULL); |
286 | } |
287 | |
288 | result.emplace_back(filename); |
289 | i += tmpSize + 1; |
290 | } |
291 | |
292 | if (result.size() > 1) { |
293 | for (i = 1; i < result.size(); ++i) { |
294 | result[i] = result[0] + "\\" + result[i]; |
295 | } |
296 | result.erase(begin(result)); |
297 | } |
298 | |
299 | return result; |
300 | #else |
301 | char buffer[FILE_DIALOG_MAX_BUFFER]; |
302 | buffer[0] = '\0'; |
303 | |
304 | std::string cmd = "zenity --file-selection " ; |
305 | // The safest separator for multiple selected paths is /, since / can never occur |
306 | // in file names. Only where two paths are concatenated will there be two / following |
307 | // each other. |
308 | if (multiple) |
309 | cmd += "--multiple --separator=\"/\" " ; |
310 | if (save) |
311 | cmd += "--save " ; |
312 | cmd += "--file-filter=\"" ; |
313 | for (auto pair : filetypes) |
314 | cmd += "\"*." + pair.first + "\" " ; |
315 | cmd += "\"" ; |
316 | FILE *output = popen(cmd.c_str(), "r" ); |
317 | if (output == nullptr) |
318 | throw std::runtime_error("popen() failed -- could not launch zenity!" ); |
319 | while (fgets(buffer, FILE_DIALOG_MAX_BUFFER, output) != NULL) |
320 | ; |
321 | pclose(output); |
322 | std::string paths(buffer); |
323 | paths.erase(std::remove(paths.begin(), paths.end(), '\n'), paths.end()); |
324 | |
325 | std::vector<std::string> result; |
326 | while (!paths.empty()) { |
327 | size_t end = paths.find("//" ); |
328 | if (end == std::string::npos) { |
329 | result.emplace_back(paths); |
330 | paths = "" ; |
331 | } else { |
332 | result.emplace_back(paths.substr(0, end)); |
333 | paths = paths.substr(end + 1); |
334 | } |
335 | } |
336 | |
337 | return result; |
338 | #endif |
339 | } |
340 | #endif |
341 | |
342 | void Object::decRef(bool dealloc) const noexcept { |
343 | --m_refCount; |
344 | if (m_refCount == 0 && dealloc) { |
345 | delete this; |
346 | } else if (m_refCount < 0) { |
347 | fprintf(stderr, "Internal error: Object reference count < 0!\n" ); |
348 | abort(); |
349 | } |
350 | } |
351 | |
352 | Object::~Object() { } |
353 | |
354 | NAMESPACE_END(nanogui) |
355 | |
356 | |