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
30NAMESPACE_BEGIN(nanogui)
31
32extern std::map<GLFWwindow *, Screen *> __nanogui_screens;
33
34#if defined(__APPLE__)
35 extern void disable_saved_application_state_osx();
36#endif
37
38void 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
62static bool mainloop_active = false;
63
64void 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
123void leave() {
124 mainloop_active = false;
125}
126
127bool active() {
128 return mainloop_active;
129}
130
131void shutdown() {
132 glfwTerminate();
133}
134
135std::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
156int __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
168std::vector<std::pair<int, std::string>>
169loadImageDirectory(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
205std::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__)
211std::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
342void 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
352Object::~Object() { }
353
354NAMESPACE_END(nanogui)
355
356