Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
AIEditorFileDialog.cpp
Go to the documentation of this file.
1/**
2 * @file AIEditorFileDialog.cpp
3 * @brief Implementation of AIEditorFileDialog
4 * @author Olympe Engine
5 * @date 2026-02-19
6 */
7
9#include "../../system/system_utils.h"
10#include "../../third_party/nfd/nfd.hpp"
11#include <vector>
12#include <cstring>
13
14#ifdef _WIN32
15#include <windows.h>
16#endif
17
18namespace Olympe {
19namespace AI {
20
21// ============================================================================
22// Platform-specific string conversion
23// ============================================================================
24
25#ifdef _WIN32
26std::wstring AIEditorFileDialog::ToNFDString(const std::string& str)
27{
28 if (str.empty()) {
29 return std::wstring();
30 }
31
32 // Convert UTF-8 to UTF-16 (Windows wide string)
33 int wideSize = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0);
34 if (wideSize == 0) {
35 return std::wstring();
36 }
37
38 std::wstring wideStr(static_cast<size_t>(wideSize), L'\0');
39 MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &wideStr[0], wideSize);
40
41 // Remove trailing null character
42 if (!wideStr.empty() && wideStr[wideStr.size() - 1] == L'\0') {
43 wideStr.resize(wideStr.size() - 1);
44 }
45
46 return wideStr;
47}
48
49std::string AIEditorFileDialog::FromNFDString(const wchar_t* wstr)
50{
51 if (wstr == nullptr) {
52 return std::string();
53 }
54
55 // Convert UTF-16 to UTF-8
56 int utf8Size = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr);
57 if (utf8Size == 0) {
58 return std::string();
59 }
60
61 std::string utf8Str(static_cast<size_t>(utf8Size), '\0');
62 WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &utf8Str[0], utf8Size, nullptr, nullptr);
63
64 // Remove trailing null character
65 if (!utf8Str.empty() && utf8Str[utf8Str.size() - 1] == '\0') {
66 utf8Str.resize(utf8Str.size() - 1);
67 }
68
69 return utf8Str;
70}
71#else
72std::string AIEditorFileDialog::ToNFDString(const std::string& str)
73{
74 return str;
75}
76
77std::string AIEditorFileDialog::FromNFDString(const char* str)
78{
79 if (str == nullptr) {
80 return std::string();
81 }
82 return std::string(str);
83}
84#endif
85
86// ============================================================================
87// Filter conversion
88// ============================================================================
89
91{
92 // Convert comma-separated list to semicolon-separated
93 // "json,btree" -> "json;btree"
94 std::string result = filterList;
95 for (size_t i = 0; i < result.size(); ++i) {
96 if (result[i] == ',') {
97 result[i] = ';';
98 }
99 }
100 return result;
101}
102
103// ============================================================================
104// File Dialog Methods
105// ============================================================================
106
108 const std::string& filterList,
109 const std::string& defaultPath)
110{
111 // Initialize NFD
112 nfdresult_t initResult = NFD::Init();
113 if (initResult != NFD_OKAY) {
114 SYSTEM_LOG << "[AIEditorFileDialog] ERROR: Failed to initialize NFD" << std::endl;
115 return std::string();
116 }
117
118 // Convert filter list
120
121 // Create filter items and persistent strings
122 std::vector<nfdnfilteritem_t> filterItems;
123#ifdef _WIN32
124 std::wstring filterName;
125 std::wstring filterSpec;
126#else
127 std::string filterSpec;
128#endif
129
130 if (!nfdFilters.empty()) {
132#ifdef _WIN32
133 filterName = L"AI Graphs";
135 item.name = filterName.c_str();
136 item.spec = filterSpec.c_str();
137#else
138 item.name = "AI Graphs";
140 item.spec = filterSpec.c_str();
141#endif
142 filterItems.push_back(item);
143 }
144
145 // Convert default path
146#ifdef _WIN32
147 std::wstring wDefaultPath = ToNFDString(defaultPath);
148 const wchar_t* defaultPathPtr = wDefaultPath.empty() ? nullptr : wDefaultPath.c_str();
149#else
150 const char* defaultPathPtr = defaultPath.empty() ? nullptr : defaultPath.c_str();
151#endif
152
153 // Open dialog
154 nfdnchar_t* outPath = nullptr;
155 nfdresult_t result = NFD::OpenDialog(
156 outPath,
157 filterItems.empty() ? nullptr : filterItems.data(),
158 static_cast<nfdfiltersize_t>(filterItems.size()),
160 );
161
162 std::string selectedPath;
163
164 if (result == NFD_OKAY) {
165 // Convert result to std::string
167 NFD::FreePath(outPath);
168
169 SYSTEM_LOG << "[AIEditorFileDialog] Selected file: " << selectedPath << std::endl;
170 }
171 else if (result == NFD_CANCEL) {
172 // User canceled - this is normal, no need to log
173 }
174 else {
175 const char* error = NFD::GetError();
176 SYSTEM_LOG << "[AIEditorFileDialog] ERROR: " << (error ? error : "Unknown error") << std::endl;
177 }
178
179 // Cleanup NFD
180 NFD::Quit();
181
182 return selectedPath;
183}
184
186 const std::string& filterList,
187 const std::string& defaultPath,
188 const std::string& defaultName)
189{
190 // Initialize NFD
191 nfdresult_t initResult = NFD::Init();
192 if (initResult != NFD_OKAY) {
193 SYSTEM_LOG << "[AIEditorFileDialog] ERROR: Failed to initialize NFD" << std::endl;
194 return std::string();
195 }
196
197 // Convert filter list
199
200 // Create filter items and persistent strings
201 std::vector<nfdnfilteritem_t> filterItems;
202#ifdef _WIN32
203 std::wstring filterName;
204 std::wstring filterSpec;
205#else
206 std::string filterSpec;
207#endif
208
209 if (!nfdFilters.empty()) {
211#ifdef _WIN32
212 filterName = L"AI Graphs";
214 item.name = filterName.c_str();
215 item.spec = filterSpec.c_str();
216#else
217 item.name = "AI Graphs";
219 item.spec = filterSpec.c_str();
220#endif
221 filterItems.push_back(item);
222 }
223
224 // Convert default path and name
225#ifdef _WIN32
226 std::wstring wDefaultPath = ToNFDString(defaultPath);
227 std::wstring wDefaultName = ToNFDString(defaultName);
228 const wchar_t* defaultPathPtr = wDefaultPath.empty() ? nullptr : wDefaultPath.c_str();
229 const wchar_t* defaultNamePtr = wDefaultName.empty() ? nullptr : wDefaultName.c_str();
230#else
231 const char* defaultPathPtr = defaultPath.empty() ? nullptr : defaultPath.c_str();
232 const char* defaultNamePtr = defaultName.empty() ? nullptr : defaultName.c_str();
233#endif
234
235 // Open dialog
236 nfdnchar_t* outPath = nullptr;
237 nfdresult_t result = NFD::SaveDialog(
238 outPath,
239 filterItems.empty() ? nullptr : filterItems.data(),
240 static_cast<nfdfiltersize_t>(filterItems.size()),
243 );
244
245 std::string selectedPath;
246
247 if (result == NFD_OKAY) {
248 // Convert result to std::string
250 NFD::FreePath(outPath);
251
252 SYSTEM_LOG << "[AIEditorFileDialog] Save to: " << selectedPath << std::endl;
253 }
254 else if (result == NFD_CANCEL) {
255 // User canceled - this is normal, no need to log
256 }
257 else {
258 const char* error = NFD::GetError();
259 SYSTEM_LOG << "[AIEditorFileDialog] ERROR: " << (error ? error : "Unknown error") << std::endl;
260 }
261
262 // Cleanup NFD
263 NFD::Quit();
264
265 return selectedPath;
266}
267
268std::string AIEditorFileDialog::OpenFolder(const std::string& defaultPath)
269{
270 // Initialize NFD
271 nfdresult_t initResult = NFD::Init();
272 if (initResult != NFD_OKAY) {
273 SYSTEM_LOG << "[AIEditorFileDialog] ERROR: Failed to initialize NFD" << std::endl;
274 return std::string();
275 }
276
277 // Convert default path
278#ifdef _WIN32
279 std::wstring wDefaultPath = ToNFDString(defaultPath);
280 const wchar_t* defaultPathPtr = wDefaultPath.empty() ? nullptr : wDefaultPath.c_str();
281#else
282 const char* defaultPathPtr = defaultPath.empty() ? nullptr : defaultPath.c_str();
283#endif
284
285 // Open dialog
286 nfdnchar_t* outPath = nullptr;
287 nfdresult_t result = NFD::PickFolder(outPath, defaultPathPtr);
288
289 std::string selectedPath;
290
291 if (result == NFD_OKAY) {
292 // Convert result to std::string
294 NFD::FreePath(outPath);
295
296 SYSTEM_LOG << "[AIEditorFileDialog] Selected folder: " << selectedPath << std::endl;
297 }
298 else if (result == NFD_CANCEL) {
299 // User canceled - this is normal, no need to log
300 }
301 else {
302 const char* error = NFD::GetError();
303 SYSTEM_LOG << "[AIEditorFileDialog] ERROR: " << (error ? error : "Unknown error") << std::endl;
304 }
305
306 // Cleanup NFD
307 NFD::Quit();
308
309 return selectedPath;
310}
311
313{
314 const char* error = NFD::GetError();
315 if (error != nullptr) {
316 return std::string(error);
317 }
318 return std::string();
319}
320
321} // namespace AI
322} // namespace Olympe
Native file dialog wrapper for AI Editor (Phase 1.5)
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
static std::string ConvertFiltersToNFD(const std::string &filterList)
Convert comma-separated filter list to NFD format.
static std::string FromNFDString(const char *str)
static std::string OpenFile(const std::string &filterList="json,btree", const std::string &defaultPath="")
Open a native file dialog to select an existing file.
static std::string ToNFDString(const std::string &str)
Convert std::string to native NFD character type.
static std::string OpenFolder(const std::string &defaultPath="")
Open a native folder selection dialog.
static std::string GetLastError()
Get last error message from NFD.
static std::string SaveFile(const std::string &filterList="json,btree", const std::string &defaultPath="", const std::string &defaultName="new_ai_graph.json")
Open a native save file dialog.
< Provides AssetID and INVALID_ASSET_ID
#define SYSTEM_LOG