| 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 | |