// // Copyright Contributors to the MaterialX Project // SPDX-License-Identifier: Apache-2.0 // #include #include #if defined(_WIN32) #include #endif FileDialog::FileDialog(int flags) : _flags(flags) { } void FileDialog::setTitle(const std::string& title) { _title = title; } void FileDialog::setTypeFilters(const mx::StringVec& typeFilters) { _filetypes.clear(); for (auto typefilter : typeFilters) { std::string minusExt = typefilter.substr(1, typefilter.size() - 1); std::pair filterPair = { minusExt, minusExt }; _filetypes.push_back(filterPair); } } void FileDialog::open() { clearSelected(); _openFlag = true; } bool FileDialog::isOpened() { return _isOpened; } bool FileDialog::hasSelected() { return !_selectedFilenames.empty(); } mx::FilePath FileDialog::getSelected() { return _selectedFilenames.empty() ? mx::FilePath() : _selectedFilenames[0]; } void FileDialog::clearSelected() { _selectedFilenames.clear(); } void FileDialog::display() { // Only call the dialog if it's not already displayed if (!_openFlag || _isOpened) { return; } _openFlag = false; // Check if we want to save or open bool save = !(_flags & SelectDirectory) && (_flags & EnterNewFilename); mx::StringVec result = launchFileDialog(_filetypes, save, false); std::string path = result.empty() ? "" : result.front(); if (!path.empty()) { _selectedFilenames.push_back(path); } _isOpened = false; } #if !defined(__APPLE__) mx::StringVec launchFileDialog(const std::vector>& filetypes, bool save, bool multiple) { static const int FILE_DIALOG_MAX_BUFFER = 16384; if (save && multiple) { throw mx::Exception("save and multiple must not both be true."); } #if defined(_WIN32) OPENFILENAME ofn; ZeroMemory(&ofn, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); char tmp[FILE_DIALOG_MAX_BUFFER]; ofn.lpstrFile = tmp; ZeroMemory(tmp, FILE_DIALOG_MAX_BUFFER); ofn.nMaxFile = FILE_DIALOG_MAX_BUFFER; ofn.nFilterIndex = 1; std::string filter; if (!save && filetypes.size() > 1) { filter.append("Supported file types ("); for (size_t i = 0; i < filetypes.size(); ++i) { filter.append("*."); filter.append(filetypes[i].first); if (i + 1 < filetypes.size()) filter.append(";"); } filter.append(")"); filter.push_back('\0'); for (size_t i = 0; i < filetypes.size(); ++i) { filter.append("*."); filter.append(filetypes[i].first); if (i + 1 < filetypes.size()) filter.append(";"); } filter.push_back('\0'); } for (auto pair : filetypes) { filter.append(pair.second); filter.append(" (*."); filter.append(pair.first); filter.append(")"); filter.push_back('\0'); filter.append("*."); filter.append(pair.first); filter.push_back('\0'); } filter.push_back('\0'); ofn.lpstrFilter = filter.data(); if (save) { ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT; if (GetSaveFileNameA(&ofn) == FALSE) return {}; } else { ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; if (multiple) ofn.Flags |= OFN_ALLOWMULTISELECT; if (GetOpenFileNameA(&ofn) == FALSE) return {}; } size_t i = 0; mx::StringVec result; while (tmp[i] != '\0') { result.emplace_back(&tmp[i]); i += result.back().size() + 1; } if (result.size() > 1) { for (i = 1; i < result.size(); ++i) { result[i] = result[0] + "\\" + result[i]; } result.erase(begin(result)); } if (save && ofn.nFilterIndex > 0) { auto ext = filetypes[ofn.nFilterIndex - 1].first; if (ext != "*") { ext.insert(0, "."); auto& name = result.front(); if (name.size() <= ext.size() || name.compare(name.size() - ext.size(), ext.size(), ext) != 0) { name.append(ext); } } } return result; #else char buffer[FILE_DIALOG_MAX_BUFFER]; buffer[0] = '\0'; std::string cmd = "zenity --file-selection "; // The safest separator for multiple selected paths is /, since / can never occur // in file names. Only where two paths are concatenated will there be two / following // each other. if (multiple) cmd += "--multiple --separator=\"/\" "; if (save) cmd += "--save "; cmd += "--file-filter=\""; for (auto pair : filetypes) cmd += "\"*." + pair.first + "\" "; cmd += "\""; FILE* output = popen(cmd.c_str(), "r"); if (output == nullptr) throw mx::Exception("popen() failed -- could not launch zenity!"); while (fgets(buffer, FILE_DIALOG_MAX_BUFFER, output) != NULL) ; pclose(output); std::string paths(buffer); paths.erase(std::remove(paths.begin(), paths.end(), '\n'), paths.end()); mx::StringVec result; while (!paths.empty()) { size_t end = paths.find("//"); if (end == std::string::npos) { result.emplace_back(paths); paths = ""; } else { result.emplace_back(paths.substr(0, end)); paths = paths.substr(end + 1); } } return result; #endif } #endif