Ember
Loading...
Searching...
No Matches
ProjectManagerDialog.cpp
Go to the documentation of this file.
6#include <fstream>
7#include <nlohmann/json.hpp>
8#include <wx/datetime.h>
9#include <wx/filedlg.h>
10#include <wx/filename.h>
11#include <wx/msgdlg.h>
12#include <wx/statbox.h>
13#include <wx/statline.h>
14#include <wx/stdpaths.h>
15
17 EVT_LIST_ITEM_SELECTED(ID_PROJECT_LIST, ProjectManagerDialog::OnProjectSelected)
18 EVT_LIST_ITEM_ACTIVATED(ID_PROJECT_LIST, ProjectManagerDialog::OnProjectActivated)
19 EVT_BUTTON(ID_OPEN, ProjectManagerDialog::OnOpen) EVT_BUTTON(ID_BROWSE, ProjectManagerDialog::OnBrowse)
20 EVT_BUTTON(ID_NEW_PROJECT, ProjectManagerDialog::OnNewProject)
21 EVT_BUTTON(ID_REMOVE_FROM_RECENT, ProjectManagerDialog::OnRemoveFromRecent)
22 EVT_BUTTON(ID_CLEAR_RECENT, ProjectManagerDialog::OnClearRecent)
24
26 : EmberUI::ScalableDialog(parent, wxID_ANY, "Open BehaviorTree Project", wxSize(1400, 1000)) {
27 CreateLayout();
28 LoadRecentProjects();
29 UpdateButtonStates();
30 Centre();
31}
32
34
36 wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
37
38 // Header
39 wxStaticText *header = new wxStaticText(this, wxID_ANY, "Recent Projects");
40 wxFont headerFont = header->GetFont();
41 headerFont.SetPointSize(headerFont.GetPointSize() + 2);
42 headerFont.MakeBold();
43 header->SetFont(headerFont);
44 mainSizer->Add(header, 0, wxALL, 10);
45
46 // Splitter: Left = project list, Right = details
47 wxSplitterWindow *splitter =
48 new wxSplitterWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_3D | wxSP_LIVE_UPDATE);
49
50 // Left panel - Recent projects list
51 wxPanel *leftPanel = new wxPanel(splitter);
52 wxBoxSizer *leftSizer = new wxBoxSizer(wxVERTICAL);
53
54 m_projectList = new wxListCtrl(leftPanel, ID_PROJECT_LIST, wxDefaultPosition, wxDefaultSize,
55 wxLC_REPORT | wxLC_SINGLE_SEL | wxBORDER_SUNKEN);
56 m_projectList->InsertColumn(0, "Project Name", wxLIST_FORMAT_LEFT, 200);
57 m_projectList->InsertColumn(1, "Path", wxLIST_FORMAT_LEFT, 200);
58
59 leftSizer->Add(m_projectList, 1, wxEXPAND | wxALL, 5);
60 leftPanel->SetSizer(leftSizer);
61
62 // Right panel - Project details
63 wxPanel *rightPanel = new wxPanel(splitter);
64 wxBoxSizer *rightSizer = new wxBoxSizer(wxVERTICAL);
65
66 wxStaticBox *detailsBox = new wxStaticBox(rightPanel, wxID_ANY, "Project Details");
67 wxStaticBoxSizer *detailsSizer = new wxStaticBoxSizer(detailsBox, wxVERTICAL);
68
69 // Project name
70 wxBoxSizer *nameSizer = new wxBoxSizer(wxHORIZONTAL);
71 nameSizer->Add(new wxStaticText(rightPanel, wxID_ANY, "Name:"), 0, wxRIGHT, 10);
72 m_projectNameLabel = new wxStaticText(rightPanel, wxID_ANY, "-");
73 wxFont boldFont = m_projectNameLabel->GetFont();
74 boldFont.MakeBold();
75 m_projectNameLabel->SetFont(boldFont);
76 nameSizer->Add(m_projectNameLabel, 1, wxEXPAND);
77 detailsSizer->Add(nameSizer, 0, wxEXPAND | wxALL, 5);
78
79 // Description
80 wxBoxSizer *descSizer = new wxBoxSizer(wxHORIZONTAL);
81 descSizer->Add(new wxStaticText(rightPanel, wxID_ANY, "Description:"), 0, wxRIGHT, 10);
82 m_projectDescLabel = new wxStaticText(rightPanel, wxID_ANY, "-");
83 descSizer->Add(m_projectDescLabel, 1, wxEXPAND);
84 detailsSizer->Add(descSizer, 0, wxEXPAND | wxALL, 5);
85
86 // Path
87 wxBoxSizer *pathSizer = new wxBoxSizer(wxHORIZONTAL);
88 pathSizer->Add(new wxStaticText(rightPanel, wxID_ANY, "Location:"), 0, wxRIGHT, 10);
89 m_projectPathLabel = new wxStaticText(rightPanel, wxID_ANY, "-");
90 pathSizer->Add(m_projectPathLabel, 1, wxEXPAND);
91 detailsSizer->Add(pathSizer, 0, wxEXPAND | wxALL, 5);
92
93 // File count
94 wxBoxSizer *filesSizer = new wxBoxSizer(wxHORIZONTAL);
95 filesSizer->Add(new wxStaticText(rightPanel, wxID_ANY, "Files:"), 0, wxRIGHT, 10);
96 m_fileCountLabel = new wxStaticText(rightPanel, wxID_ANY, "-");
97 filesSizer->Add(m_fileCountLabel, 1, wxEXPAND);
98 detailsSizer->Add(filesSizer, 0, wxEXPAND | wxALL, 5);
99
100 // Last modified
101 wxBoxSizer *modSizer = new wxBoxSizer(wxHORIZONTAL);
102 modSizer->Add(new wxStaticText(rightPanel, wxID_ANY, "Last Modified:"), 0, wxRIGHT, 10);
103 m_lastModifiedLabel = new wxStaticText(rightPanel, wxID_ANY, "-");
104 modSizer->Add(m_lastModifiedLabel, 1, wxEXPAND);
105 detailsSizer->Add(modSizer, 0, wxEXPAND | wxALL, 5);
106
107 rightSizer->Add(detailsSizer, 0, wxEXPAND | wxALL, 5);
108 rightSizer->AddStretchSpacer(1);
109 rightPanel->SetSizer(rightSizer);
110
111 // Split the window
112 splitter->SplitVertically(leftPanel, rightPanel, 350);
113 splitter->SetMinimumPaneSize(200);
114
115 mainSizer->Add(splitter, 1, wxEXPAND | wxLEFT | wxRIGHT, 10);
116
117 mainSizer->Add(new wxStaticLine(this), 0, wxEXPAND | wxALL, 10);
118
119 // Bottom buttons
120 wxBoxSizer *buttonSizer = new wxBoxSizer(wxHORIZONTAL);
121
122 m_openBtn = new wxButton(this, ID_OPEN, "Open");
123 m_openBtn->SetMinSize(wxSize(100, -1));
124 buttonSizer->Add(m_openBtn, 0, wxRIGHT, 5);
125
126 m_browseBtn = new wxButton(this, ID_BROWSE, "Browse...");
127 buttonSizer->Add(m_browseBtn, 0, wxRIGHT, 5);
128
129 m_newProjectBtn = new wxButton(this, ID_NEW_PROJECT, "New Project...");
130 buttonSizer->Add(m_newProjectBtn, 0, wxRIGHT, 5);
131
132 buttonSizer->AddSpacer(20);
133
134 m_removeBtn = new wxButton(this, ID_REMOVE_FROM_RECENT, "Remove from List");
135 buttonSizer->Add(m_removeBtn, 0, wxRIGHT, 5);
136
137 m_clearBtn = new wxButton(this, ID_CLEAR_RECENT, "Clear All");
138 buttonSizer->Add(m_clearBtn, 0, wxRIGHT, 5);
139
140 buttonSizer->AddStretchSpacer(1);
141
142 m_cancelBtn = new wxButton(this, wxID_CANCEL, "Cancel");
143 buttonSizer->Add(m_cancelBtn, 0);
144
145 mainSizer->Add(buttonSizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10);
146
147 SetSizer(mainSizer);
148}
149
151 m_projectList->DeleteAllItems();
152
153 auto &projectManager = EmberCore::ProjectManager::GetInstance();
154 const auto &recentProjects = projectManager.GetRecentProjects();
155
156 for (size_t i = 0; i < recentProjects.size(); ++i) {
157 const auto &path = recentProjects[i];
158
159 // Try to load project to get its name
160 wxString projectName = "Unknown";
161 wxFileName fn(wxString::FromUTF8(path));
162
163 // Check if file exists
164 if (wxFileExists(wxString::FromUTF8(path))) {
165 // Try to load just the name from the project file
166 std::ifstream file(path);
167 if (file.is_open()) {
168 try {
169 nlohmann::json json;
170 file >> json;
171 if (json.contains("project_name")) {
172 projectName = wxString::FromUTF8(json["project_name"].get<std::string>());
173 }
174 } catch (...) {
175 // Use filename as fallback
176 projectName = fn.GetName();
177 }
178 file.close();
179 }
180 } else {
181 projectName = fn.GetName() + " (not found)";
182 }
183
184 long item = m_projectList->InsertItem(i, projectName);
185 m_projectList->SetItem(item, 1, fn.GetPath());
186 m_projectList->SetItemData(item, i);
187 }
188
189 // Update button states
191}
192
193void ProjectManagerDialog::UpdateProjectDetails(const wxString &projectPath) {
194 // Try to load project details
195 std::ifstream file(projectPath.ToStdString());
196 if (!file.is_open()) {
198 m_projectNameLabel->SetLabel("File not found");
199 return;
200 }
201
202 try {
203 nlohmann::json json;
204 file >> json;
205 file.close();
206
207 // Name
208 if (json.contains("project_name")) {
209 m_projectNameLabel->SetLabel(wxString::FromUTF8(json["project_name"].get<std::string>()));
210 } else {
211 m_projectNameLabel->SetLabel("-");
212 }
213
214 // Description
215 if (json.contains("description")) {
216 wxString desc = wxString::FromUTF8(json["description"].get<std::string>());
217 if (desc.IsEmpty())
218 desc = "(none)";
219 m_projectDescLabel->SetLabel(desc);
220 } else {
221 m_projectDescLabel->SetLabel("(none)");
222 }
223
224 // Path
225 wxFileName fn(projectPath);
226 m_projectPathLabel->SetLabel(fn.GetPath());
227
228 // File count
229 if (json.contains("resources")) {
230 size_t count = json["resources"].size();
231 m_fileCountLabel->SetLabel(wxString::Format("%zu XML file(s)", count));
232 } else {
233 m_fileCountLabel->SetLabel("0 files");
234 }
235
236 // Last modified
237 if (json.contains("modified_timestamp")) {
238 int64_t timestamp = json["modified_timestamp"].get<int64_t>();
239 wxDateTime dt;
240 dt.Set(static_cast<time_t>(timestamp));
241 m_lastModifiedLabel->SetLabel(dt.Format("%Y-%m-%d %H:%M:%S"));
242 } else {
243 // Get file modification time
244 wxFileName fn2(projectPath);
245 if (fn2.FileExists()) {
246 wxDateTime dt = fn2.GetModificationTime();
247 m_lastModifiedLabel->SetLabel(dt.Format("%Y-%m-%d %H:%M:%S"));
248 } else {
249 m_lastModifiedLabel->SetLabel("-");
250 }
251 }
252
253 } catch (...) {
255 m_projectNameLabel->SetLabel("Error reading project");
256 }
257}
258
260 m_projectNameLabel->SetLabel("-");
261 m_projectDescLabel->SetLabel("-");
262 m_projectPathLabel->SetLabel("-");
263 m_fileCountLabel->SetLabel("-");
264 m_lastModifiedLabel->SetLabel("-");
265}
266
268 bool hasSelection = m_projectList->GetSelectedItemCount() > 0;
269 bool hasItems = m_projectList->GetItemCount() > 0;
270
271 m_openBtn->Enable(hasSelection);
272 m_removeBtn->Enable(hasSelection);
273 m_clearBtn->Enable(hasItems);
274}
275
277 long item = event.GetIndex();
278
279 auto &projectManager = EmberCore::ProjectManager::GetInstance();
280 const auto &recentProjects = projectManager.GetRecentProjects();
281
282 if (item < static_cast<long>(recentProjects.size())) {
283 wxString path = wxString::FromUTF8(recentProjects[item]);
285 m_selectedPath = path;
286 }
287
289}
290
292 // Double-click opens the project
293 wxCommandEvent dummyEvent;
294 OnOpen(dummyEvent);
295}
296
297void ProjectManagerDialog::OnOpen(wxCommandEvent &event) {
298 if (m_selectedPath.IsEmpty()) {
299 return;
300 }
301
302 // Check if file exists
303 if (!wxFileExists(m_selectedPath)) {
304 int result = wxMessageBox("The project file no longer exists. Remove it from the recent list?",
305 "File Not Found", wxYES_NO | wxICON_WARNING, this);
306
307 if (result == wxYES) {
308 auto &projectManager = EmberCore::ProjectManager::GetInstance();
309 projectManager.RemoveFromRecentProjects(m_selectedPath.ToStdString());
312 m_selectedPath.Clear();
313 }
314 return;
315 }
316
317 EndModal(wxID_OK);
318}
319
320void ProjectManagerDialog::OnBrowse(wxCommandEvent &event) {
321 wxFileDialog openFileDialog(this, "Open BehaviorTree Project", "", "",
322 "BehaviorTree Project (*.btproj)|*.btproj|All files (*.*)|*.*",
323 wxFD_OPEN | wxFD_FILE_MUST_EXIST);
324
325 if (openFileDialog.ShowModal() == wxID_CANCEL) {
326 return;
327 }
328
329 m_selectedPath = openFileDialog.GetPath();
330 EndModal(wxID_OK);
331}
332
333void ProjectManagerDialog::OnNewProject(wxCommandEvent &event) {
334 // Show the new project dialog
335 BehaviorTreeProjectDialog dialog(this, nullptr);
336 int result = dialog.ShowModal();
337
338 if (result == wxID_OK) {
339 auto project = dialog.GetProject();
340 if (project) {
341 // Get default my_projects directory
342 wxString defaultProjectsDir = EmberForge::ResourcePath::GetDir("my_projects");
343
344 // Create directory if it doesn't exist
345 if (!wxFileName::DirExists(defaultProjectsDir)) {
346 wxFileName::Mkdir(defaultProjectsDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL);
347 }
348
349 // Ask where to save the project
350 wxFileDialog saveDialog(this, "Save BehaviorTree Project", defaultProjectsDir,
351 wxString::FromUTF8(project->GetName()) + ".btproj",
352 "BehaviorTree Project (*.btproj)|*.btproj", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
353
354 if (saveDialog.ShowModal() == wxID_OK) {
355 wxString projectPath = saveDialog.GetPath();
356 project->SetProjectFilePath(projectPath.ToStdString());
357
358 if (project->SaveToFile(projectPath.ToStdString())) {
359 // Add to recent projects
360 auto &projectManager = EmberCore::ProjectManager::GetInstance();
361 projectManager.AddToRecentProjects(projectPath.ToStdString());
362
363 // Set as selected and close dialog
364 m_selectedPath = projectPath;
365 EndModal(wxID_OK);
366 } else {
367 wxMessageBox("Failed to save project file.", "Error", wxOK | wxICON_ERROR, this);
368 }
369 }
370 }
371 }
372}
373
374void ProjectManagerDialog::OnRemoveFromRecent(wxCommandEvent &event) {
375 if (m_selectedPath.IsEmpty()) {
376 return;
377 }
378
379 auto &projectManager = EmberCore::ProjectManager::GetInstance();
380 projectManager.RemoveFromRecentProjects(m_selectedPath.ToStdString());
381
384 m_selectedPath.Clear();
386}
387
388void ProjectManagerDialog::OnClearRecent(wxCommandEvent &event) {
389 int result = wxMessageBox("Clear all recent projects from the list?\n\n"
390 "This will not delete the actual project files.",
391 "Confirm Clear", wxYES_NO | wxICON_QUESTION, this);
392
393 if (result != wxYES) {
394 return;
395 }
396
397 auto &projectManager = EmberCore::ProjectManager::GetInstance();
398 projectManager.ClearRecentProjects();
399
402 m_selectedPath.Clear();
404}
405
406void ProjectManagerDialog::OnCancel(wxCommandEvent &event) {
407 m_selectedPath.Clear();
408 EndModal(wxID_CANCEL);
409}
BehaviorTreeProjectDialog::OnProjectNameChanged BehaviorTreeProjectDialog::OnRemoveFiles wxEND_EVENT_TABLE() BehaviorTreeProjectDialog
MainFrame::OnExit MainFrame::OnNewProject MainFrame::OnCloseProject MainFrame::OnToggleMaximize MainFrame::OnPreviousScene MainFrame::OnPreferences MainFrame::OnEditorTool EVT_BUTTON(ID_MonitorTool, MainFrame::OnMonitorTool) EVT_PAINT(MainFrame
Definition MainFrame.cpp:63
wxBEGIN_EVENT_TABLE(ProjectManagerDialog, EmberUI::ScalableDialog) EVT_BUTTON(ID_OPEN
Centralized resource path management for EmberForge.
Dialog for creating and configuring BehaviorTree projects.
std::shared_ptr< EmberCore::BehaviorTreeProject > GetProject() const
Get the configured project (after dialog is closed with OK)
static ProjectManager & GetInstance()
static wxString GetDir(const wxString &relativeDir)
Get the full path to a resource directory.
DPI-aware dialog base class for scalable layouts.
Dialog for opening and managing BehaviorTree projects.
void OnClearRecent(wxCommandEvent &event)
wxStaticText * m_projectPathLabel
void OnProjectSelected(wxListEvent &event)
wxStaticText * m_projectDescLabel
wxStaticText * m_lastModifiedLabel
void OnOpen(wxCommandEvent &event)
void UpdateProjectDetails(const wxString &projectPath)
void OnProjectActivated(wxListEvent &event)
wxStaticText * m_projectNameLabel
void OnBrowse(wxCommandEvent &event)
wxStaticText * m_fileCountLabel
void OnCancel(wxCommandEvent &event)
void OnRemoveFromRecent(wxCommandEvent &event)
void OnNewProject(wxCommandEvent &event)
Definition Panel.h:8