|
|
|
|
@@ -1,39 +1,23 @@
|
|
|
|
|
#include "util/file_util.h"
|
|
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
// Include Windows-specific headers
|
|
|
|
|
#include <shobjidl.h>
|
|
|
|
|
#include <windows.h>
|
|
|
|
|
#else // Linux and MacOS
|
|
|
|
|
#include <dirent.h>
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
#include <cerrno>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#include <cstdlib>
|
|
|
|
|
#include <filesystem>
|
|
|
|
|
#include <fstream>
|
|
|
|
|
#include <sstream>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
|
|
|
|
|
#include "app/core/features.h"
|
|
|
|
|
#include "util/platform_paths.h"
|
|
|
|
|
|
|
|
|
|
namespace yaze {
|
|
|
|
|
namespace util {
|
|
|
|
|
|
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
|
|
|
|
|
|
std::string GetFileExtension(const std::string &filename) {
|
|
|
|
|
size_t dot = filename.find_last_of('.');
|
|
|
|
|
if (dot == std::string::npos) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
return filename.substr(dot + 1);
|
|
|
|
|
return fs::path(filename).extension().string();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string GetFileName(const std::string &filename) {
|
|
|
|
|
size_t slash = filename.find_last_of('/');
|
|
|
|
|
if (slash == std::string::npos) {
|
|
|
|
|
return filename;
|
|
|
|
|
}
|
|
|
|
|
return filename.substr(slash + 1);
|
|
|
|
|
return fs::path(filename).filename().string();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string LoadFile(const std::string &filename) {
|
|
|
|
|
@@ -51,9 +35,14 @@ std::string LoadFile(const std::string &filename) {
|
|
|
|
|
return contents;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string LoadConfigFile(const std::string &filename) {
|
|
|
|
|
std::string LoadFileFromConfigDir(const std::string &filename) {
|
|
|
|
|
auto config_dir = PlatformPaths::GetConfigDirectory();
|
|
|
|
|
if (!config_dir.ok()) {
|
|
|
|
|
return ""; // Or handle error appropriately
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fs::path filepath = *config_dir / filename;
|
|
|
|
|
std::string contents;
|
|
|
|
|
std::string filepath = GetConfigDirectory() + "/" + filename;
|
|
|
|
|
std::ifstream file(filepath);
|
|
|
|
|
if (file.is_open()) {
|
|
|
|
|
std::stringstream buffer;
|
|
|
|
|
@@ -65,7 +54,12 @@ std::string LoadConfigFile(const std::string &filename) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SaveFile(const std::string &filename, const std::string &contents) {
|
|
|
|
|
std::string filepath = GetConfigDirectory() + "/" + filename;
|
|
|
|
|
auto config_dir = PlatformPaths::GetConfigDirectory();
|
|
|
|
|
if (!config_dir.ok()) {
|
|
|
|
|
// Or handle error appropriately
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
fs::path filepath = *config_dir / filename;
|
|
|
|
|
std::ofstream file(filepath);
|
|
|
|
|
if (file.is_open()) {
|
|
|
|
|
file << contents;
|
|
|
|
|
@@ -86,642 +80,8 @@ std::string GetResourcePath(const std::string &resource_path) {
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string ExpandHomePath(const std::string& path) {
|
|
|
|
|
if (path.empty() || path[0] != '~') {
|
|
|
|
|
return path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char* home = nullptr;
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
home = std::getenv("USERPROFILE");
|
|
|
|
|
if (!home) {
|
|
|
|
|
home = std::getenv("HOMEDRIVE");
|
|
|
|
|
const char* homePath = std::getenv("HOMEPATH");
|
|
|
|
|
if (home && homePath) {
|
|
|
|
|
static std::string full_path;
|
|
|
|
|
full_path = std::string(home) + std::string(homePath);
|
|
|
|
|
home = full_path.c_str();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
home = std::getenv("HOME");
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if (!home) {
|
|
|
|
|
return path; // Fallback to original path if HOME not found
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Replace ~ with home directory
|
|
|
|
|
if (path.size() == 1 || path[1] == '/') {
|
|
|
|
|
return std::string(home) + path.substr(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool EnsureConfigDirectoryExists() {
|
|
|
|
|
std::string config_dir = GetConfigDirectory();
|
|
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
// Windows directory creation
|
|
|
|
|
DWORD attr = GetFileAttributesA(config_dir.c_str());
|
|
|
|
|
if (attr == INVALID_FILE_ATTRIBUTES) {
|
|
|
|
|
// Directory doesn't exist, create it
|
|
|
|
|
if (!CreateDirectoryA(config_dir.c_str(), NULL)) {
|
|
|
|
|
DWORD error = GetLastError();
|
|
|
|
|
if (error != ERROR_ALREADY_EXISTS) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
// Unix-like directory creation
|
|
|
|
|
struct stat st;
|
|
|
|
|
if (stat(config_dir.c_str(), &st) != 0) {
|
|
|
|
|
// Directory doesn't exist, create it with 0755 permissions
|
|
|
|
|
if (mkdir(config_dir.c_str(), 0755) != 0) {
|
|
|
|
|
if (errno != EEXIST) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string GetConfigDirectory() {
|
|
|
|
|
std::string config_directory = ".yaze";
|
|
|
|
|
Platform platform;
|
|
|
|
|
#if defined(__APPLE__) && defined(__MACH__)
|
|
|
|
|
#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
|
|
|
|
|
platform = Platform::kiOS;
|
|
|
|
|
#elif TARGET_OS_MAC == 1
|
|
|
|
|
platform = Platform::kMacOS;
|
|
|
|
|
#else
|
|
|
|
|
platform = Platform::kMacOS; // Default for macOS
|
|
|
|
|
#endif
|
|
|
|
|
#elif defined(_WIN32)
|
|
|
|
|
platform = Platform::kWindows;
|
|
|
|
|
#elif defined(__linux__)
|
|
|
|
|
platform = Platform::kLinux;
|
|
|
|
|
#else
|
|
|
|
|
platform = Platform::kUnknown;
|
|
|
|
|
#endif
|
|
|
|
|
switch (platform) {
|
|
|
|
|
case Platform::kWindows:
|
|
|
|
|
config_directory = "~/AppData/Roaming/yaze";
|
|
|
|
|
break;
|
|
|
|
|
case Platform::kMacOS:
|
|
|
|
|
case Platform::kLinux:
|
|
|
|
|
config_directory = "~/.config/yaze";
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
// Expand the home directory path
|
|
|
|
|
return ExpandHomePath(config_directory);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
|
|
|
|
|
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
|
|
|
|
|
#include <nfd.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
std::string FileDialogWrapper::ShowOpenFileDialog() {
|
|
|
|
|
// Use global feature flag to choose implementation
|
|
|
|
|
if (core::FeatureFlags::get().kUseNativeFileDialog) {
|
|
|
|
|
return ShowOpenFileDialogNFD();
|
|
|
|
|
} else {
|
|
|
|
|
return ShowOpenFileDialogBespoke();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string FileDialogWrapper::ShowOpenFileDialogNFD() {
|
|
|
|
|
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
|
|
|
|
|
NFD_Init();
|
|
|
|
|
nfdu8char_t *out_path = NULL;
|
|
|
|
|
nfdu8filteritem_t filters[1] = {{"ROM Files", "sfc,smc"}};
|
|
|
|
|
nfdopendialogu8args_t args = {0};
|
|
|
|
|
args.filterList = filters;
|
|
|
|
|
args.filterCount = 1;
|
|
|
|
|
nfdresult_t result = NFD_OpenDialogU8_With(&out_path, &args);
|
|
|
|
|
if (result == NFD_OKAY) {
|
|
|
|
|
std::string file_path(out_path);
|
|
|
|
|
NFD_FreePath(out_path);
|
|
|
|
|
NFD_Quit();
|
|
|
|
|
return file_path;
|
|
|
|
|
} else if (result == NFD_CANCEL) {
|
|
|
|
|
NFD_Quit();
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
NFD_Quit();
|
|
|
|
|
return "";
|
|
|
|
|
#else
|
|
|
|
|
// NFD not available - fallback to bespoke
|
|
|
|
|
return ShowOpenFileDialogBespoke();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string FileDialogWrapper::ShowOpenFileDialogBespoke() {
|
|
|
|
|
// Use Windows COM-based IFileOpenDialog as fallback when NFD not available
|
|
|
|
|
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string result;
|
|
|
|
|
IFileOpenDialog *pFileOpen = NULL;
|
|
|
|
|
|
|
|
|
|
// Create the FileOpenDialog object
|
|
|
|
|
hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
|
|
|
|
|
IID_IFileOpenDialog, reinterpret_cast<void**>(&pFileOpen));
|
|
|
|
|
|
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
|
// Set file type filters
|
|
|
|
|
COMDLG_FILTERSPEC rgSpec[] = {
|
|
|
|
|
{L"ROM Files", L"*.sfc;*.smc"},
|
|
|
|
|
{L"All Files", L"*.*"}
|
|
|
|
|
};
|
|
|
|
|
pFileOpen->SetFileTypes(ARRAYSIZE(rgSpec), rgSpec);
|
|
|
|
|
pFileOpen->SetFileTypeIndex(1);
|
|
|
|
|
pFileOpen->SetDefaultExtension(L"sfc");
|
|
|
|
|
|
|
|
|
|
// Show the Open dialog
|
|
|
|
|
hr = pFileOpen->Show(NULL);
|
|
|
|
|
|
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
|
// Get the file name from the dialog
|
|
|
|
|
IShellItem *pItem;
|
|
|
|
|
hr = pFileOpen->GetResult(&pItem);
|
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
|
PWSTR pszFilePath;
|
|
|
|
|
hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
|
|
|
|
|
|
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
|
// Convert wide string to narrow string
|
|
|
|
|
int size_needed = WideCharToMultiByte(CP_UTF8, 0, pszFilePath, -1, NULL, 0, NULL, NULL);
|
|
|
|
|
if (size_needed > 0) {
|
|
|
|
|
std::vector<char> buffer(size_needed);
|
|
|
|
|
WideCharToMultiByte(CP_UTF8, 0, pszFilePath, -1, buffer.data(), size_needed, NULL, NULL);
|
|
|
|
|
result = std::string(buffer.data());
|
|
|
|
|
}
|
|
|
|
|
CoTaskMemFree(pszFilePath);
|
|
|
|
|
}
|
|
|
|
|
pItem->Release();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pFileOpen->Release();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CoUninitialize();
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string FileDialogWrapper::ShowSaveFileDialog(const std::string& default_name,
|
|
|
|
|
const std::string& default_extension) {
|
|
|
|
|
// Use global feature flag to choose implementation
|
|
|
|
|
if (core::FeatureFlags::get().kUseNativeFileDialog) {
|
|
|
|
|
return ShowSaveFileDialogNFD(default_name, default_extension);
|
|
|
|
|
} else {
|
|
|
|
|
return ShowSaveFileDialogBespoke(default_name, default_extension);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string FileDialogWrapper::ShowSaveFileDialogNFD(const std::string& default_name,
|
|
|
|
|
const std::string& default_extension) {
|
|
|
|
|
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
|
|
|
|
|
NFD_Init();
|
|
|
|
|
nfdu8char_t *out_path = NULL;
|
|
|
|
|
|
|
|
|
|
nfdsavedialogu8args_t args = {0};
|
|
|
|
|
if (!default_extension.empty()) {
|
|
|
|
|
// Create filter for the save dialog
|
|
|
|
|
static nfdu8filteritem_t filters[3] = {
|
|
|
|
|
{"Theme Files", "theme"},
|
|
|
|
|
{"YAZE Project Files", "yaze"},
|
|
|
|
|
{"ROM Files", "sfc,smc"}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (default_extension == "theme") {
|
|
|
|
|
args.filterList = &filters[0];
|
|
|
|
|
args.filterCount = 1;
|
|
|
|
|
} else if (default_extension == "yaze") {
|
|
|
|
|
args.filterList = &filters[1];
|
|
|
|
|
args.filterCount = 1;
|
|
|
|
|
} else if (default_extension == "sfc" || default_extension == "smc") {
|
|
|
|
|
args.filterList = &filters[2];
|
|
|
|
|
args.filterCount = 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!default_name.empty()) {
|
|
|
|
|
args.defaultName = default_name.c_str();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nfdresult_t result = NFD_SaveDialogU8_With(&out_path, &args);
|
|
|
|
|
if (result == NFD_OKAY) {
|
|
|
|
|
std::string file_path(out_path);
|
|
|
|
|
NFD_FreePath(out_path);
|
|
|
|
|
NFD_Quit();
|
|
|
|
|
return file_path;
|
|
|
|
|
} else if (result == NFD_CANCEL) {
|
|
|
|
|
NFD_Quit();
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
NFD_Quit();
|
|
|
|
|
return "";
|
|
|
|
|
#else
|
|
|
|
|
// NFD not available - fallback to bespoke
|
|
|
|
|
return ShowSaveFileDialogBespoke(default_name, default_extension);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string FileDialogWrapper::ShowSaveFileDialogBespoke(const std::string& default_name,
|
|
|
|
|
const std::string& default_extension) {
|
|
|
|
|
// Use Windows COM-based IFileSaveDialog as fallback when NFD not available
|
|
|
|
|
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string result;
|
|
|
|
|
IFileSaveDialog *pFileSave = NULL;
|
|
|
|
|
|
|
|
|
|
// Create the FileSaveDialog object
|
|
|
|
|
hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_ALL,
|
|
|
|
|
IID_IFileSaveDialog, reinterpret_cast<void**>(&pFileSave));
|
|
|
|
|
|
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
|
// Set file type filters based on extension
|
|
|
|
|
if (default_extension == "theme") {
|
|
|
|
|
COMDLG_FILTERSPEC rgSpec[] = {{L"Theme Files", L"*.theme"}};
|
|
|
|
|
pFileSave->SetFileTypes(ARRAYSIZE(rgSpec), rgSpec);
|
|
|
|
|
pFileSave->SetDefaultExtension(L"theme");
|
|
|
|
|
} else if (default_extension == "yaze") {
|
|
|
|
|
COMDLG_FILTERSPEC rgSpec[] = {{L"YAZE Project Files", L"*.yaze"}};
|
|
|
|
|
pFileSave->SetFileTypes(ARRAYSIZE(rgSpec), rgSpec);
|
|
|
|
|
pFileSave->SetDefaultExtension(L"yaze");
|
|
|
|
|
} else if (default_extension == "sfc" || default_extension == "smc") {
|
|
|
|
|
COMDLG_FILTERSPEC rgSpec[] = {
|
|
|
|
|
{L"SFC ROM Files", L"*.sfc"},
|
|
|
|
|
{L"SMC ROM Files", L"*.smc"}
|
|
|
|
|
};
|
|
|
|
|
pFileSave->SetFileTypes(ARRAYSIZE(rgSpec), rgSpec);
|
|
|
|
|
pFileSave->SetDefaultExtension(default_extension == "sfc" ? L"sfc" : L"smc");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set default filename if provided
|
|
|
|
|
if (!default_name.empty()) {
|
|
|
|
|
int size_needed = MultiByteToWideChar(CP_UTF8, 0, default_name.c_str(), -1, NULL, 0);
|
|
|
|
|
if (size_needed > 0) {
|
|
|
|
|
std::vector<wchar_t> wname(size_needed);
|
|
|
|
|
MultiByteToWideChar(CP_UTF8, 0, default_name.c_str(), -1, wname.data(), size_needed);
|
|
|
|
|
pFileSave->SetFileName(wname.data());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Show the Save dialog
|
|
|
|
|
hr = pFileSave->Show(NULL);
|
|
|
|
|
|
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
|
// Get the file name from the dialog
|
|
|
|
|
IShellItem *pItem;
|
|
|
|
|
hr = pFileSave->GetResult(&pItem);
|
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
|
PWSTR pszFilePath;
|
|
|
|
|
hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
|
|
|
|
|
|
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
|
// Convert wide string to narrow string
|
|
|
|
|
int size_needed = WideCharToMultiByte(CP_UTF8, 0, pszFilePath, -1, NULL, 0, NULL, NULL);
|
|
|
|
|
if (size_needed > 0) {
|
|
|
|
|
std::vector<char> buffer(size_needed);
|
|
|
|
|
WideCharToMultiByte(CP_UTF8, 0, pszFilePath, -1, buffer.data(), size_needed, NULL, NULL);
|
|
|
|
|
result = std::string(buffer.data());
|
|
|
|
|
}
|
|
|
|
|
CoTaskMemFree(pszFilePath);
|
|
|
|
|
}
|
|
|
|
|
pItem->Release();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pFileSave->Release();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CoUninitialize();
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string FileDialogWrapper::ShowOpenFolderDialog() {
|
|
|
|
|
// Use global feature flag to choose implementation
|
|
|
|
|
if (core::FeatureFlags::get().kUseNativeFileDialog) {
|
|
|
|
|
return ShowOpenFolderDialogNFD();
|
|
|
|
|
} else {
|
|
|
|
|
return ShowOpenFolderDialogBespoke();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string FileDialogWrapper::ShowOpenFolderDialogNFD() {
|
|
|
|
|
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
|
|
|
|
|
NFD_Init();
|
|
|
|
|
nfdu8char_t *out_path = NULL;
|
|
|
|
|
nfdresult_t result = NFD_PickFolderU8(&out_path, NULL);
|
|
|
|
|
if (result == NFD_OKAY) {
|
|
|
|
|
std::string folder_path(out_path);
|
|
|
|
|
NFD_FreePath(out_path);
|
|
|
|
|
NFD_Quit();
|
|
|
|
|
return folder_path;
|
|
|
|
|
} else if (result == NFD_CANCEL) {
|
|
|
|
|
NFD_Quit();
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
NFD_Quit();
|
|
|
|
|
return "";
|
|
|
|
|
#else
|
|
|
|
|
// NFD not available - fallback to bespoke
|
|
|
|
|
return ShowOpenFolderDialogBespoke();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string FileDialogWrapper::ShowOpenFolderDialogBespoke() {
|
|
|
|
|
// Use Windows COM-based IFileOpenDialog in folder-picking mode as fallback
|
|
|
|
|
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string result;
|
|
|
|
|
IFileOpenDialog *pFileOpen = NULL;
|
|
|
|
|
|
|
|
|
|
// Create the FileOpenDialog object
|
|
|
|
|
hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
|
|
|
|
|
IID_IFileOpenDialog, reinterpret_cast<void**>(&pFileOpen));
|
|
|
|
|
|
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
|
// Set options to pick folders instead of files
|
|
|
|
|
DWORD dwOptions;
|
|
|
|
|
hr = pFileOpen->GetOptions(&dwOptions);
|
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
|
hr = pFileOpen->SetOptions(dwOptions | FOS_PICKFOLDERS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
|
// Show the Open dialog
|
|
|
|
|
hr = pFileOpen->Show(NULL);
|
|
|
|
|
|
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
|
// Get the folder path from the dialog
|
|
|
|
|
IShellItem *pItem;
|
|
|
|
|
hr = pFileOpen->GetResult(&pItem);
|
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
|
PWSTR pszFolderPath;
|
|
|
|
|
hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFolderPath);
|
|
|
|
|
|
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
|
// Convert wide string to narrow string
|
|
|
|
|
int size_needed = WideCharToMultiByte(CP_UTF8, 0, pszFolderPath, -1, NULL, 0, NULL, NULL);
|
|
|
|
|
if (size_needed > 0) {
|
|
|
|
|
std::vector<char> buffer(size_needed);
|
|
|
|
|
WideCharToMultiByte(CP_UTF8, 0, pszFolderPath, -1, buffer.data(), size_needed, NULL, NULL);
|
|
|
|
|
result = std::string(buffer.data());
|
|
|
|
|
}
|
|
|
|
|
CoTaskMemFree(pszFolderPath);
|
|
|
|
|
}
|
|
|
|
|
pItem->Release();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pFileOpen->Release();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CoUninitialize();
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(
|
|
|
|
|
const std::string &folder_path) {
|
|
|
|
|
std::vector<std::string> subdirectories;
|
|
|
|
|
WIN32_FIND_DATA findFileData;
|
|
|
|
|
HANDLE hFind = FindFirstFile((folder_path + "\\*").c_str(), &findFileData);
|
|
|
|
|
if (hFind != INVALID_HANDLE_VALUE) {
|
|
|
|
|
do {
|
|
|
|
|
if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
|
|
|
|
if (strcmp(findFileData.cFileName, ".") != 0 &&
|
|
|
|
|
strcmp(findFileData.cFileName, "..") != 0) {
|
|
|
|
|
subdirectories.emplace_back(findFileData.cFileName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} while (FindNextFile(hFind, &findFileData) != 0);
|
|
|
|
|
FindClose(hFind);
|
|
|
|
|
}
|
|
|
|
|
return subdirectories;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(
|
|
|
|
|
const std::string &folder_path) {
|
|
|
|
|
std::vector<std::string> files;
|
|
|
|
|
WIN32_FIND_DATA findFileData;
|
|
|
|
|
HANDLE hFind = FindFirstFile((folder_path + "\\*").c_str(), &findFileData);
|
|
|
|
|
if (hFind != INVALID_HANDLE_VALUE) {
|
|
|
|
|
do {
|
|
|
|
|
if (!(findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
|
|
|
|
files.emplace_back(findFileData.cFileName);
|
|
|
|
|
}
|
|
|
|
|
} while (FindNextFile(hFind, &findFileData) != 0);
|
|
|
|
|
FindClose(hFind);
|
|
|
|
|
}
|
|
|
|
|
return files;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#elif defined(__linux__)
|
|
|
|
|
|
|
|
|
|
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
|
|
|
|
|
#include <nfd.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
std::string FileDialogWrapper::ShowOpenFileDialog() {
|
|
|
|
|
// Use global feature flag to choose implementation
|
|
|
|
|
if (core::FeatureFlags::get().kUseNativeFileDialog) {
|
|
|
|
|
return ShowOpenFileDialogNFD();
|
|
|
|
|
} else {
|
|
|
|
|
return ShowOpenFileDialogBespoke();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string FileDialogWrapper::ShowOpenFileDialogNFD() {
|
|
|
|
|
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
|
|
|
|
|
NFD_Init();
|
|
|
|
|
nfdu8char_t *out_path = NULL;
|
|
|
|
|
nfdu8filteritem_t filters[1] = {{"Rom File", "sfc,smc"}};
|
|
|
|
|
nfdopendialogu8args_t args = {0};
|
|
|
|
|
args.filterList = filters;
|
|
|
|
|
args.filterCount = 1;
|
|
|
|
|
nfdresult_t result = NFD_OpenDialogU8_With(&out_path, &args);
|
|
|
|
|
if (result == NFD_OKAY) {
|
|
|
|
|
std::string file_path_linux(out_path);
|
|
|
|
|
NFD_FreePath(out_path);
|
|
|
|
|
NFD_Quit();
|
|
|
|
|
return file_path_linux;
|
|
|
|
|
} else if (result == NFD_CANCEL) {
|
|
|
|
|
NFD_Quit();
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
NFD_Quit();
|
|
|
|
|
return "Error: NFD_OpenDialog";
|
|
|
|
|
#else
|
|
|
|
|
// NFD not available - fallback to bespoke
|
|
|
|
|
return ShowOpenFileDialogBespoke();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string FileDialogWrapper::ShowOpenFileDialogBespoke() {
|
|
|
|
|
// Implement bespoke file dialog or return placeholder
|
|
|
|
|
// This would contain the custom Linux implementation
|
|
|
|
|
return ""; // Placeholder for bespoke implementation
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string FileDialogWrapper::ShowSaveFileDialogNFD(const std::string& default_name,
|
|
|
|
|
const std::string& default_extension) {
|
|
|
|
|
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
|
|
|
|
|
NFD_Init();
|
|
|
|
|
nfdu8char_t *out_path = NULL;
|
|
|
|
|
|
|
|
|
|
nfdsavedialogu8args_t args = {0};
|
|
|
|
|
if (!default_extension.empty()) {
|
|
|
|
|
// Create filter for the save dialog
|
|
|
|
|
static nfdu8filteritem_t filters[3] = {
|
|
|
|
|
{"Theme File", "theme"},
|
|
|
|
|
{"Project File", "yaze"},
|
|
|
|
|
{"ROM File", "sfc,smc"}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (default_extension == "theme") {
|
|
|
|
|
args.filterList = &filters[0];
|
|
|
|
|
args.filterCount = 1;
|
|
|
|
|
} else if (default_extension == "yaze") {
|
|
|
|
|
args.filterList = &filters[1];
|
|
|
|
|
args.filterCount = 1;
|
|
|
|
|
} else if (default_extension == "sfc" || default_extension == "smc") {
|
|
|
|
|
args.filterList = &filters[2];
|
|
|
|
|
args.filterCount = 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!default_name.empty()) {
|
|
|
|
|
args.defaultName = default_name.c_str();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nfdresult_t result = NFD_SaveDialogU8_With(&out_path, &args);
|
|
|
|
|
if (result == NFD_OKAY) {
|
|
|
|
|
std::string file_path(out_path);
|
|
|
|
|
NFD_FreePath(out_path);
|
|
|
|
|
NFD_Quit();
|
|
|
|
|
return file_path;
|
|
|
|
|
} else if (result == NFD_CANCEL) {
|
|
|
|
|
NFD_Quit();
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
NFD_Quit();
|
|
|
|
|
return "";
|
|
|
|
|
#else
|
|
|
|
|
// NFD not available - fallback to bespoke
|
|
|
|
|
return ShowSaveFileDialogBespoke(default_name, default_extension);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string FileDialogWrapper::ShowSaveFileDialogBespoke(const std::string& default_name,
|
|
|
|
|
const std::string& default_extension) {
|
|
|
|
|
// Basic Linux implementation using system command
|
|
|
|
|
// For CI/CD, just return a placeholder path
|
|
|
|
|
if (!default_name.empty() && !default_extension.empty()) {
|
|
|
|
|
return default_name + "." + default_extension;
|
|
|
|
|
}
|
|
|
|
|
return ""; // For now return empty - full implementation can be added later
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string FileDialogWrapper::ShowSaveFileDialog(const std::string& default_name,
|
|
|
|
|
const std::string& default_extension) {
|
|
|
|
|
// Use global feature flag to choose implementation
|
|
|
|
|
if (core::FeatureFlags::get().kUseNativeFileDialog) {
|
|
|
|
|
return ShowSaveFileDialogNFD(default_name, default_extension);
|
|
|
|
|
} else {
|
|
|
|
|
return ShowSaveFileDialogBespoke(default_name, default_extension);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string FileDialogWrapper::ShowOpenFolderDialog() {
|
|
|
|
|
// Use global feature flag to choose implementation
|
|
|
|
|
if (core::FeatureFlags::get().kUseNativeFileDialog) {
|
|
|
|
|
return ShowOpenFolderDialogNFD();
|
|
|
|
|
} else {
|
|
|
|
|
return ShowOpenFolderDialogBespoke();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string FileDialogWrapper::ShowOpenFolderDialogNFD() {
|
|
|
|
|
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
|
|
|
|
|
NFD_Init();
|
|
|
|
|
nfdu8char_t *out_path = NULL;
|
|
|
|
|
nfdresult_t result = NFD_PickFolderU8(&out_path, NULL);
|
|
|
|
|
if (result == NFD_OKAY) {
|
|
|
|
|
std::string folder_path_linux(out_path);
|
|
|
|
|
NFD_FreePath(out_path);
|
|
|
|
|
NFD_Quit();
|
|
|
|
|
return folder_path_linux;
|
|
|
|
|
} else if (result == NFD_CANCEL) {
|
|
|
|
|
NFD_Quit();
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
NFD_Quit();
|
|
|
|
|
return "Error: NFD_PickFolder";
|
|
|
|
|
#else
|
|
|
|
|
// NFD not available - fallback to bespoke
|
|
|
|
|
return ShowOpenFolderDialogBespoke();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string FileDialogWrapper::ShowOpenFolderDialogBespoke() {
|
|
|
|
|
// Implement bespoke folder dialog or return placeholder
|
|
|
|
|
// This would contain the custom macOS implementation
|
|
|
|
|
return ""; // Placeholder for bespoke implementation
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(
|
|
|
|
|
const std::string &folder_path) {
|
|
|
|
|
std::vector<std::string> subdirectories;
|
|
|
|
|
DIR *dir;
|
|
|
|
|
struct dirent *ent;
|
|
|
|
|
if ((dir = opendir(folder_path.c_str())) != NULL) {
|
|
|
|
|
while ((ent = readdir(dir)) != NULL) {
|
|
|
|
|
if (ent->d_type == DT_DIR) {
|
|
|
|
|
if (strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0) {
|
|
|
|
|
subdirectories.push_back(ent->d_name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
closedir(dir);
|
|
|
|
|
}
|
|
|
|
|
return subdirectories;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(
|
|
|
|
|
const std::string &folder_path) {
|
|
|
|
|
std::vector<std::string> files;
|
|
|
|
|
DIR *dir;
|
|
|
|
|
struct dirent *ent;
|
|
|
|
|
if ((dir = opendir(folder_path.c_str())) != NULL) {
|
|
|
|
|
while ((ent = readdir(dir)) != NULL) {
|
|
|
|
|
if (ent->d_type == DT_REG) {
|
|
|
|
|
files.push_back(ent->d_name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
closedir(dir);
|
|
|
|
|
}
|
|
|
|
|
return files;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
// Note: FileDialogWrapper implementations are in src/app/platform/file_dialog.mm
|
|
|
|
|
// (platform-specific implementations to avoid duplicate symbols)
|
|
|
|
|
|
|
|
|
|
} // namespace util
|
|
|
|
|
} // namespace yaze
|